Commit ff4bad47 ff4bad47defb064f09a0157839d3865b43c21cb6 by 沈秋雨

fix

1 parent df390ea0
...@@ -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,11 +494,12 @@ def main() -> None: ...@@ -453,11 +494,12 @@ 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):
499 return {
458 "song_id": r["song_id"], 500 "song_id": r["song_id"],
459 "audio_path": r["local_path"], 501 "audio_path": str(path),
460 "variant": "self", 502 "variant": variant,
461 "sample_class": "positive", 503 "sample_class": "positive",
462 "expected_song_id": r["song_id"], 504 "expected_song_id": r["song_id"],
463 "expected": "duplicate", 505 "expected": "duplicate",
...@@ -467,7 +509,24 @@ def main() -> None: ...@@ -467,7 +509,24 @@ def main() -> None:
467 "platform_name": r["platform_name"], 509 "platform_name": r["platform_name"],
468 "duration": r["duration"], 510 "duration": r["duration"],
469 "is_main_version": r["is_main_version"], 511 "is_main_version": r["is_main_version"],
470 }) 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"
......