CLI на yandex-music (device-flow auth, fetch/report/apply), жанр через реальное дерево client.genres(), язык гибридом (track_supplement + алфавит).
5.6 KiB
Архитектура
Консольная утилита на Python, которая читает лайкнутые треки Яндекс.Музыки и раскладывает их
по плейлистам вида Жанр — RU / Жанр — INT, используя неофициальную библиотеку yandex-music.
Структура
sort_ym/
config.py — чтение config.toml (throttle, пути к кэшу/токену)
auth.py — OAuth device-flow, хранение токена в .token
ymclient.py — создание Client, батч-нарезка списков
fetch.py — лайки -> полные Track -> cache/tracks.json (докачивается инкрементально)
genres.py — реальное дерево жанров Яндекса -> 11 крупных корзин (ROOT_BUCKET)
language.py — гибридная детекция языка (API текста / жанр-подсказка / алфавит)
classify.py — (корзина, язык) -> имя плейлиста
report.py — сборка строк отчёта, запись out/report.csv, сводки
apply.py — создание плейлистов + идемпотентное добавление треков
cli.py — команды auth / fetch / report / apply
tests/
test_genres.py, test_language.py — тесты чистых функций (маппинг, детекция языка)
cache/ — кэш API (в .gitignore)
out/ — отчёты (в .gitignore)
Поток данных
auth → .token
fetch → users_likes_tracks() → client.tracks(batch) → cache/tracks.json
report → genres.load_or_fetch_catalog() → cache/genre_catalog.json
→ language.fetch_api_languages() → cache/lyrics_lang.json
→ build_rows() → out/report.csv (dry-run, аккаунт не меняется)
apply --yes → users_playlists_create / users_playlists_insert_track
Ключевые решения
-
Жанр трека доступен только через альбом (
Track.albums[0].genre), у самого трека поля жанра нет. Раскладка на 11 крупных групп сделана через реальное деревоclient.genres()(родитель → под-жанры), а не через угаданный список слагов — так таблицаROOT_BUCKETвgenres.pyопирается на подтверждённые id, а не на предположения. Жанры без совпадения падают в корзинуotherи печатаются в выводеreportдля ручного дополнения таблицы. -
Язык определяется гибридно, в порядке приоритета:
client.track_supplement(track_id).lyrics.text_language— единственное поле в библиотеке с реальным языком текста песни. Оно есть только в устаревшем (deprecated) методеtrack_supplement, а не в новомtracks_lyrics()— тот текст возвращает, но язык не отдаёт. Используется сознательно, несмотря на пометку deprecated в самой библиотеке.- Подсказка по слагу жанра (
rusrap,shanson,bard, ...). - Эвристика по алфавиту названия/исполнителя (кириллица vs латиница,
unicodedata). - Иначе —
UNKNOWN→ отдельный плейлист «Не определено».
-
Идентификаторы треков: у трека есть composite
track_id("id:album_id", используется как ключ кэша и для сверки с уже добавленными треками в плейлисте) и отдельныеid/album_id(нужны как раздельные параметры дляtrack_supplement,tracks_lyricsиusers_playlists_insert_track). -
Идемпотентность
apply: перед добавлением треков в плейлист читается его текущий состав (users_playlists(kind)), уже присутствующие треки пропускаются. Повторный запускapplyбезопасен и не создаёт дублей. Лайки не трогаются, ничего не удаляется. -
Троттлинг и резюмируемость: все сетевые циклы (загрузка треков, языков) делают паузу между запросами (
config.toml,[throttle]) и пишут кэш атомарно (temp-файл + rename) после каждого батча — прерванный прогон можно просто перезапустить, уже полученное не запрашивается повторно.
Хранилище
.token— OAuth-токен аккаунта (не коммитится).cache/tracks.json— метаданные лайкнутых треков.cache/genre_catalog.json— плоский снимок дерева жанров Яндекса (id -> title, root_id).cache/lyrics_lang.json— язык текста песни по треку (id -> language code | null).out/report.csv— предпросмотр распределения по плейлистам передapply.