mirror of
https://github.com/Horizontal42/Sort_YM.git
synced 2026-11-10 15:42:02 +03:00
CLI на yandex-music (device-flow auth, fetch/report/apply), жанр через реальное дерево client.genres(), язык гибридом (track_supplement + алфавит).
75 lines
5.6 KiB
Markdown
75 lines
5.6 KiB
Markdown
# Архитектура
|
||
|
||
Консольная утилита на 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` для ручного дополнения таблицы.
|
||
|
||
- **Язык** определяется гибридно, в порядке приоритета:
|
||
1. `client.track_supplement(track_id).lyrics.text_language` — единственное поле в библиотеке
|
||
с реальным языком текста песни. Оно есть только в устаревшем (deprecated) методе
|
||
`track_supplement`, а не в новом `tracks_lyrics()` — тот текст возвращает, но язык не отдаёт.
|
||
Используется сознательно, несмотря на пометку deprecated в самой библиотеке.
|
||
2. Подсказка по слагу жанра (`rusrap`, `shanson`, `bard`, ...).
|
||
3. Эвристика по алфавиту названия/исполнителя (кириллица vs латиница, `unicodedata`).
|
||
4. Иначе — `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`.
|