[English](README.md) # Sort YM Раскладывает лайкнутые треки Яндекс.Музыки по плейлистам вида `Жанр — RU` / `Жанр — INT`. ``` $ python -m sort_ym report Распределение по плейлистам: Рок — INT: 143 Рок — RU: 287 Рэп — RU: 512 ... Отчёт сохранён: out/report.csv ``` ## Установка Требования: Python 3.11+ (используется `tomllib` из стандартной библиотеки), Windows/Linux/macOS, доступ в интернет. ``` python -m venv .venv .venv\Scripts\pip install -r requirements.txt ``` Для разработки (тесты): ``` .venv\Scripts\pip install -r requirements-dev.txt ``` ## Что делает ``` .venv\Scripts\python -m sort_ym auth # войти в аккаунт Яндекса через device-flow и сохранить токен в .token .venv\Scripts\python -m sort_ym fetch # скачать лайкнутые треки (cache/tracks.json) и жанры их артистов (cache/artist_genres.json); аккаунт не меняет .venv\Scripts\python -m sort_ym report # посчитать жанр и язык каждого трека, сохранить out/report.csv (аккаунт не меняет) .venv\Scripts\python -m sort_ym apply --limit 20 --yes # пробный запуск: создать плейлисты и добавить в них первые 20 треков .venv\Scripts\python -m sort_ym apply --yes # создать плейлисты и добавить в них все треки (меняет аккаунт) .venv\Scripts\python -m sort_ym digest # сводка библиотеки (топ-исполнители, жанры) в out/digest.md - чтобы вставить в чат с LLM; сеть не используется ``` При повторных запусках `fetch` докачивает только новые треки, а `apply` не дублирует уже добавленные треки. ## Полная очистка кэша Если требуется начать с чистого листа (переполучить все данные с нуля), удалите папку `cache/`: ``` Remove-Item -Recurse -Force cache ``` Кэш состоит из четырёх файлов: - `cache/tracks.json` — список лайкнутых треков и их метаданные (включая id артистов); - `cache/artist_genres.json` — жанровые теги каждого артиста (может быть несколько на артиста); - `cache/genre_catalog.json` — снимок дерева жанров Яндекса на момент последнего запуска; - `cache/lyrics_lang.json` — определённые языки текстов для каждого трека. Удаление `cache/` заставит `fetch` и `report` переполучить всё с нуля при следующем запуске. Также можно удалить сгенерированные отчёты (`out/report.csv`, `out/digest.md`): ``` Remove-Item -Recurse -Force out ``` Это просто результаты анализа, их удаление не повлияет на следующий запуск `report`/`digest`. **Важно:** файл `.token` (OAuth-токен) не входит в кэш и не удаляется при очистке `cache/`. Очистка кэша вас не разлогирует. Если нужна повторная авторизация, удалите `.token` отдельно: ``` Remove-Item .token ``` ## Как определяется жанр и язык - **Жанр** определяется на **под-жанровом** уровне (Рок, Панк, Инди, Альтернатива, Метал, ..., не только 11 крупных групп), комбинируя два сигнала: 1. Жанр **альбома** трека (`genre_raw`) — сигнал по конкретному треку. 2. Жанры **артиста** (`Artist.genres`, может быть несколько тегов на артиста, точнее одного альбомного жанра) — сигнал по исполнителю в целом. Если сигналы совпадают — берётся под-жанр как есть. При конфликте: если альбомный тег общий ("зонтичный", вроде `rock`/`alternative`/`pop` — типичный дефолт Яндекса низкой уверенности) — побеждает жанр артиста; если альбомный тег точный/специфичный (`numetal`, `dnb`, `shanson`) — побеждает он, потому что это, скорее всего, реальный эксперимент артиста в другом жанре, а не шум. Так один и тот же артист с разными по духу треками (например, рОк-трек и панк-трек) раскладывается по разным плейлистам, а не весь скопом по одному "основному" жанру. Под-жанровые группы меньше `small_group_min` (по умолчанию 12 треков, `config.toml`) схлопываются в родительскую крупную корзину (Поп, Рок, Метал, Рэп, Электроника, Джаз и блюз, Классика, Фолк и world, Инди и альтернатива, Саундтреки, Разное), чтобы не плодить плейлисты на 1-2 трека. Реализация — `sort_ym/genres.py` (`classify_track`, `ROOT_BUCKET`) — таблица корней легко дополняется, если Яндекс вернёт незнакомый корневой жанр. - **Язык** — гибрид: 1. Если у трека есть текст песни — реальный язык из API (`ru` → RU, иначе → INT). 2. Иначе — подсказка по жанру (`rusrap`, `shanson`, `bard`, ...). 3. Иначе — по алфавиту названия/исполнителя (кириллица/латиница). 4. Если ничего не сработало — трек попадает в `... — Не определено`. После `report` в консоли также печатается список нераспознанных жанров (попавших в «Разное»), чтобы можно было дополнить таблицу в `sort_ym/genres.py`. ## Дайджест для LLM Полный построчный список из ~2000 треков LLM переварить не может так же качественно, как компактную сводку — поэтому `digest` не выгружает треки построчно, а агрегирует библиотеку в несколько разделов с жёстко ограниченным размером: не более 11 строк по крупным жанрам, ~34 по под-жанрам, 3 по языкам, ~13 по десятилетиям, а топ-исполнителей и топ-альбомов ограничен `config.toml`, а не размером библиотеки. Всё, что не попало в топ, схлопывается в одну фразу с суммарными числами. Итоговый файл — не больше пары сотен строк независимо от того, 500 треков в библиотеке или 10000. Команда полностью офлайн: она не обращается к сети и не требует `.token`, читает только уже загруженный `cache/`. Нужно, чтобы `report` был запущен хотя бы раз (для `cache/genre_catalog.json`). ``` $ python -m sort_ym digest Жанры: Инди и альтернатива: 680 Рок: 590 ... Дайджест сохранён: out/digest.md ``` Пример `out/digest.md` (сокращённо): ``` ## Жанры (крупные корзины) - Инди и альтернатива — 680 (34%): INT 571 / RU 108 - Рок — 590 (29%): INT 518 / RU 72 ... ## Десятилетия - 2010-е — 620 (31%): Рок 280, Инди и альтернатива 200, Метал 90 ... ## Топ-40 исполнителей 1. Billy Talent — 66 (Рок; rock, punk) ... Ещё 1140 исполнителей с 1-4 треками, 1737 упоминаний суммарно; из них 835 — по одному треку. ## Топ-15 альбомов 1. Slipknot — Iowa (2001) — 12 ... Ещё 1401 альбом с 1-3 лайкнутыми треками. Треков с альбомов, где лайкнуто 2 и более: 732 из 2005 (37%). ``` **Важно:** поля `год выхода` и `название альбома` появились в кэше треков не с самого начала — если `cache/tracks.json` был создан старой версией `fetch`, разделы «Десятилетия» и «Топ альбомов» покажут «Неизвестно» до первого перезапуска `python -m sort_ym fetch` (он один раз доперекачает весь кэш, дальше — как обычно, только новые треки). ## Handy things - Первый прогон `apply` делайте с `--limit 20 --yes` и проверьте результат в приложении, прежде чем запускать на все ~2000 треков. - Настройки задержек между запросами — в `config.toml` (`[throttle]`). - Минимальный размер под-жанрового плейлиста — `config.toml` (`[classify] small_group_min`). - Сколько исполнителей/альбомов показывать поимённо в дайджесте — `config.toml` (`[digest] top_artists`, `top_albums`) или флаги `--top`/`--top-albums` команды `digest`. ## Риски (неофициальный API) Официального API для личной библиотеки Яндекс.Музыки нет — инструмент использует неофициальную библиотеку `yandex-music`. OAuth-токен в `.token` даёт полный доступ к аккаунту Яндекса — храните его локально и не публикуйте. Инструмент выполняет только безопасные действия (чтение лайков, создание плейлистов, добавление треков) и ничего не удаляет. ## For developers Python 3.11+, `yandex-music==3.0.0`, стандартная библиотека (`tomllib`, `csv`, `unicodedata`). ``` .venv\Scripts\python -m pytest tests/ -v .venv\Scripts\python -m sort_ym auth .venv\Scripts\python -m sort_ym fetch .venv\Scripts\python -m sort_ym report .venv\Scripts\python -m sort_ym apply --limit 20 --yes .venv\Scripts\python -m sort_ym digest ``` Структура кода и поток данных — в [ARCHITECTURE.ru.md](ARCHITECTURE.ru.md). ## Credits Andrew Pimenov ## License [MIT License](LICENSE)