fix
Showing
2 changed files
with
81 additions
and
15 deletions
| ... | @@ -27,6 +27,13 @@ python scripts/aliyun_dna/evaluate_aliyun_dna.py \ | ... | @@ -27,6 +27,13 @@ python scripts/aliyun_dna/evaluate_aliyun_dna.py \ |
| 27 | --queries composition_testset_20/queries.csv \ | 27 | --queries composition_testset_20/queries.csv \ |
| 28 | --out results/aliyun_dna_eval.csv \ | 28 | --out results/aliyun_dna_eval.csv \ |
| 29 | --duplicate-threshold 0.8 | 29 | --duplicate-threshold 0.8 |
| 30 | |||
| 31 | # 测试翻唱库 | ||
| 32 | python scripts/aliyun_dna/evaluate_aliyun_dna.py \ | ||
| 33 | --queries downloads_db/queries.csv \ | ||
| 34 | --out results/dna_eval.csv \ | ||
| 35 | --db-id f08aee1806c14df782e03cc1ab93f1ea \ | ||
| 36 | --concurrency 4 | ||
| 30 | """ | 37 | """ |
| 31 | 38 | ||
| 32 | import argparse | 39 | import argparse | ... | ... |
| ... | @@ -54,6 +54,42 @@ logger = logging.getLogger(__name__) | ... | @@ -54,6 +54,42 @@ logger = logging.getLogger(__name__) |
| 54 | 54 | ||
| 55 | DB_DSN = "postgresql://postgres:postgres@localhost:5432/embed_db" | 55 | DB_DSN = "postgresql://postgres:postgres@localhost:5432/embed_db" |
| 56 | 56 | ||
| 57 | # 正样本变体:对主版本做轻度变换,模拟不同编码/剪辑场景 | ||
| 58 | # (variant_name, ffmpeg_extra_args_or_None) | ||
| 59 | POSITIVE_VARIANTS: list[tuple[str, list[str] | None]] = [ | ||
| 60 | # 转码为 128kbps MP3,模拟不同平台压缩率 | ||
| 61 | ("mp3_128k", ["-codec:a", "libmp3lame", "-b:a", "128k"]), | ||
| 62 | # 重采样至 22050Hz,模拟不同采样率上传 | ||
| 63 | ("resample_22k", ["-ar", "22050", "-ac", "1"]), | ||
| 64 | # 去掉前后各 5%,模拟用户剪掉片头片尾 | ||
| 65 | ("trim_5pct", None), | ||
| 66 | ] | ||
| 67 | |||
| 68 | |||
| 69 | def _make_variant(src: Path, dst: Path, ffmpeg_args: list[str] | None, duration: float | None = None) -> bool: | ||
| 70 | """生成单个正样本变体,返回是否成功。""" | ||
| 71 | import subprocess | ||
| 72 | if ffmpeg_args is None: | ||
| 73 | # trim_5pct:需要先探测时长 | ||
| 74 | if duration is None: | ||
| 75 | probe = subprocess.run( | ||
| 76 | ["ffprobe", "-v", "error", "-show_entries", "format=duration", | ||
| 77 | "-of", "default=noprint_wrappers=1:nokey=1", str(src)], | ||
| 78 | capture_output=True, text=True, | ||
| 79 | ) | ||
| 80 | try: | ||
| 81 | duration = float(probe.stdout.strip()) | ||
| 82 | except ValueError: | ||
| 83 | return False | ||
| 84 | ss = duration * 0.05 | ||
| 85 | t = duration * 0.90 | ||
| 86 | cmd = ["ffmpeg", "-y", "-i", str(src), | ||
| 87 | "-ss", f"{ss:.3f}", "-t", f"{t:.3f}", "-ac", "1", str(dst)] | ||
| 88 | else: | ||
| 89 | cmd = ["ffmpeg", "-y", "-i", str(src), *ffmpeg_args, "-ac", "1", str(dst)] | ||
| 90 | result = subprocess.run(cmd, capture_output=True) | ||
| 91 | return result.returncode == 0 | ||
| 92 | |||
| 57 | # fetch_records 查询返回的列,也是 --export-csv / --from-csv 的 CSV 格式 | 93 | # fetch_records 查询返回的列,也是 --export-csv / --from-csv 的 CSV 格式 |
| 58 | RECORDS_FIELDS = [ | 94 | RECORDS_FIELDS = [ |
| 59 | "record_id", "song_id", "song_name", "original_singer", | 95 | "record_id", "song_id", "song_name", "original_singer", |
| ... | @@ -284,6 +320,8 @@ def main() -> None: | ... | @@ -284,6 +320,8 @@ def main() -> None: |
| 284 | ap.add_argument("--timeout", type=int, default=60, help="单文件下载超时秒数(默认 60)") | 320 | ap.add_argument("--timeout", type=int, default=60, help="单文件下载超时秒数(默认 60)") |
| 285 | ap.add_argument("--overwrite", action="store_true", help="覆盖已存在的文件") | 321 | ap.add_argument("--overwrite", action="store_true", help="覆盖已存在的文件") |
| 286 | ap.add_argument("--dsn", default=DB_DSN, help="PostgreSQL 连接串") | 322 | ap.add_argument("--dsn", default=DB_DSN, help="PostgreSQL 连接串") |
| 323 | ap.add_argument("--no-positive-variants", action="store_true", | ||
| 324 | help="不生成正样本变体(mp3_128k/resample_22k/trim_5pct),只用 self 查询") | ||
| 287 | ap.add_argument( | 325 | ap.add_argument( |
| 288 | "--export-csv", metavar="FILE", | 326 | "--export-csv", metavar="FILE", |
| 289 | help="只导出查询结果到 CSV 后退出,不执行下载(在本地机器上运行)", | 327 | help="只导出查询结果到 CSV 后退出,不执行下载(在本地机器上运行)", |
| ... | @@ -440,8 +478,11 @@ def main() -> None: | ... | @@ -440,8 +478,11 @@ def main() -> None: |
| 440 | ok_results = [r for r in results if r.get("status") == "ok" and r.get("local_path")] | 478 | ok_results = [r for r in results if r.get("status") == "ok" and r.get("local_path")] |
| 441 | 479 | ||
| 442 | ref_rows, query_rows = [], [] | 480 | ref_rows, query_rows = [], [] |
| 481 | variants_dir = output_dir / "variants" | ||
| 482 | |||
| 443 | for r in ok_results: | 483 | for r in ok_results: |
| 444 | if int(r["is_main_version"]) == 1: | 484 | if int(r["is_main_version"]) == 1: |
| 485 | src = Path(r["local_path"]) | ||
| 445 | ref_rows.append({ | 486 | ref_rows.append({ |
| 446 | "song_id": r["song_id"], | 487 | "song_id": r["song_id"], |
| 447 | "audio_path": r["local_path"], | 488 | "audio_path": r["local_path"], |
| ... | @@ -453,21 +494,39 @@ def main() -> None: | ... | @@ -453,21 +494,39 @@ def main() -> None: |
| 453 | "genre": r["genre"], | 494 | "genre": r["genre"], |
| 454 | "language_tag": r["language_tag"], | 495 | "language_tag": r["language_tag"], |
| 455 | }) | 496 | }) |
| 456 | # 正样本:主版本自身查询(验证入库是否成功) | 497 | |
| 457 | query_rows.append({ | 498 | def _query_row(path, variant): |
| 458 | "song_id": r["song_id"], | 499 | return { |
| 459 | "audio_path": r["local_path"], | 500 | "song_id": r["song_id"], |
| 460 | "variant": "self", | 501 | "audio_path": str(path), |
| 461 | "sample_class": "positive", | 502 | "variant": variant, |
| 462 | "expected_song_id": r["song_id"], | 503 | "sample_class": "positive", |
| 463 | "expected": "duplicate", | 504 | "expected_song_id": r["song_id"], |
| 464 | "song_name": r["song_name"], | 505 | "expected": "duplicate", |
| 465 | "original_singer": r["original_singer"], | 506 | "song_name": r["song_name"], |
| 466 | "singer_name": r["original_singer"], | 507 | "original_singer": r["original_singer"], |
| 467 | "platform_name": r["platform_name"], | 508 | "singer_name": r["original_singer"], |
| 468 | "duration": r["duration"], | 509 | "platform_name": r["platform_name"], |
| 469 | "is_main_version": r["is_main_version"], | 510 | "duration": r["duration"], |
| 470 | }) | 511 | "is_main_version": r["is_main_version"], |
| 512 | } | ||
| 513 | |||
| 514 | # self:原始文件直接作为查询(验证基本入库) | ||
| 515 | query_rows.append(_query_row(src, "self")) | ||
| 516 | |||
| 517 | # 轻度变体正样本 | ||
| 518 | if not args.no_positive_variants: | ||
| 519 | variants_dir.mkdir(parents=True, exist_ok=True) | ||
| 520 | duration = float(r["duration"]) if r.get("duration") else None | ||
| 521 | for vname, ffargs in POSITIVE_VARIANTS: | ||
| 522 | ext = ".mp3" if vname == "mp3_128k" else src.suffix | ||
| 523 | dst = variants_dir / f"{r['song_id']}_{vname}{ext}" | ||
| 524 | if not dst.exists(): | ||
| 525 | ok = _make_variant(src, dst, ffargs, duration) | ||
| 526 | if not ok: | ||
| 527 | logger.warning("变体生成失败,跳过: song_id=%s %s", r["song_id"], vname) | ||
| 528 | continue | ||
| 529 | query_rows.append(_query_row(dst, vname)) | ||
| 471 | else: | 530 | else: |
| 472 | # 负样本:翻唱/其他版本,不应匹配到 DNA 库中的主版本 | 531 | # 负样本:翻唱/其他版本,不应匹配到 DNA 库中的主版本 |
| 473 | platform = r["platform_name"] or "unknown" | 532 | platform = r["platform_name"] or "unknown" | ... | ... |
-
Please register or sign in to post a comment