diff --git a/sort_ym/cli.py b/sort_ym/cli.py index 0bd47c5..fcfcbd1 100644 --- a/sort_ym/cli.py +++ b/sort_ym/cli.py @@ -71,16 +71,19 @@ def cmd_lyrics(args: argparse.Namespace) -> None: if not tracks_cache: raise SystemExit("Кэш треков пуст. Сначала запустите: python -m sort_ym fetch") - # Тот же вызов, что в cmd_report: инкрементальный, на заполненном кэше почти no-op, - # но без него RU-гейт опирался бы только на алфавитную эвристику. - ids_needing_lang = [str(t["id"]) for t in tracks_cache.values() if t["lyrics_available"]] - lang_cache = language.fetch_api_languages(client, ids_needing_lang, cfg.cache_dir, cfg.lyrics_request_delay) + lang_cache: dict[str, str | None] = {} + if not args.all_languages: + # Тот же вызов, что в cmd_report: инкрементальный, на заполненном кэше почти no-op, + # но без него RU-гейт опирался бы только на алфавитную эвристику. + ids_needing_lang = [str(t["id"]) for t in tracks_cache.values() if t["lyrics_available"]] + lang_cache = language.fetch_api_languages(client, ids_needing_lang, cfg.cache_dir, cfg.lyrics_request_delay) - ids = lyrics.ru_lyric_track_ids(tracks_cache, lang_cache) + ids = lyrics.ru_lyric_track_ids(tracks_cache, lang_cache, all_languages=args.all_languages) if args.limit is not None: ids = ids[: args.limit] - print(f"RU-треков с текстом: {len(ids)}") + label = "треков" if args.all_languages else "RU-треков" + print(f"{label} с текстом: {len(ids)}") lyrics.fetch_lyrics_text(client, ids, cfg.cache_dir, cfg.lyrics_request_delay) @@ -263,6 +266,11 @@ def main(argv: list[str] | None = None) -> None: p_lyrics = sub.add_parser("lyrics", help="загрузить тексты песен RU-треков в cache/lyrics_text.json") p_lyrics.add_argument("--limit", type=int, default=None, help="ограничить количество треков (для пробного запуска)") + p_lyrics.add_argument( + "--all-languages", + action="store_true", + help="не фильтровать по языку - загрузить тексты всех треков с лирикой, а не только RU", + ) p_analyze = sub.add_parser( "analyze", diff --git a/sort_ym/lyrics.py b/sort_ym/lyrics.py index f440d7d..1864c83 100644 --- a/sort_ym/lyrics.py +++ b/sort_ym/lyrics.py @@ -13,13 +13,19 @@ from .ymclient import with_retries LYRICS_TEXT_CACHE_FILE = "lyrics_text.json" -def ru_lyric_track_ids(tracks_cache: dict[str, dict], lang_cache: dict[str, str | None]) -> list[str]: - """Числовые id RU-треков, у которых вообще есть текст. +def ru_lyric_track_ids( + tracks_cache: dict[str, dict], + lang_cache: dict[str, str | None], + all_languages: bool = False, +) -> list[str]: + """Числовые id треков, у которых вообще есть текст. - Гейт двойной: lyrics_available (иначе track_supplement заведомо вернёт запись без lyrics) - и RU - анализ INT-лирики вне скоупа фичи. Дедупликация нужна потому, что один и тот же - трек может быть лайкнут с разных альбомов: ключи tracks_cache при этом разные, а числовой - id (и, соответственно, ключ кэша текстов) - один. + По умолчанию - только RU: на незнакомом языке текст песни несёт для языковой модели меньше + сигнала, чем разбор трека, который слушатель понимает дословно. all_languages=True снимает + языковой фильтр, оставляя только lyrics_available (иначе track_supplement заведомо вернёт + запись без lyrics). Дедупликация нужна потому, что один и тот же трек может быть лайкнут с + разных альбомов: ключи tracks_cache при этом разные, а числовой id (и, соответственно, ключ + кэша текстов) - один. """ ids: list[str] = [] seen: set[str] = set() @@ -29,10 +35,12 @@ def ru_lyric_track_ids(tracks_cache: dict[str, dict], lang_cache: dict[str, str tid = str(t["id"]) if tid in seen: continue - lang = language.detect_language(t["title"], t["artists"], t["genre_raw"], lang_cache.get(tid)) - if lang == "RU": - seen.add(tid) - ids.append(tid) + if not all_languages: + lang = language.detect_language(t["title"], t["artists"], t["genre_raw"], lang_cache.get(tid)) + if lang != "RU": + continue + seen.add(tid) + ids.append(tid) return ids diff --git a/tests/test_cli.py b/tests/test_cli.py index 49c549c..2e4586d 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -20,11 +20,22 @@ def test_apply_without_yes_fails_regardless_of_source(): def test_lyrics_command_is_wired(monkeypatch): called = {} - monkeypatch.setattr(cli, "cmd_lyrics", lambda args: called.update(limit=args.limit)) + monkeypatch.setattr( + cli, "cmd_lyrics", lambda args: called.update(limit=args.limit, all_languages=args.all_languages) + ) cli.main(["lyrics", "--limit", "5"]) - assert called == {"limit": 5} + assert called == {"limit": 5, "all_languages": False} + + +def test_lyrics_command_all_languages_flag(monkeypatch): + called = {} + monkeypatch.setattr(cli, "cmd_lyrics", lambda args: called.update(all_languages=args.all_languages)) + + cli.main(["lyrics", "--all-languages"]) + + assert called == {"all_languages": True} def test_analyze_command_is_wired(monkeypatch): diff --git a/tests/test_lyrics.py b/tests/test_lyrics.py index 48e67c9..fc4ddd7 100644 --- a/tests/test_lyrics.py +++ b/tests/test_lyrics.py @@ -55,6 +55,23 @@ def test_ru_lyric_track_ids_deduplicates_same_numeric_id(): assert lyrics.ru_lyric_track_ids(tracks_cache, lang_cache={}) == ["100"] +def test_ru_lyric_track_ids_all_languages_includes_int(): + tracks_cache = { + "100:10": track(100, title="Тоска", artists=["Гражданская Оборона"]), + "200:10": track(200, title="Come as You Are", artists=["Nirvana"]), + } + + ids = lyrics.ru_lyric_track_ids(tracks_cache, lang_cache={}, all_languages=True) + + assert ids == ["100", "200"] + + +def test_ru_lyric_track_ids_all_languages_still_gates_on_lyrics_available(): + tracks_cache = {"100:10": track(100, lyrics_available=False)} + + assert lyrics.ru_lyric_track_ids(tracks_cache, lang_cache={}, all_languages=True) == [] + + class FakeLyrics: def __init__(self, full_lyrics): self.full_lyrics = full_lyrics