Sort_YM/README.ru.md
Andrew Pimenov b991236ffa report: pick individual --extra column groups instead of all-or-nothing
--extra now takes group names (timestamp/duration/version/album/artist/all)
so you can add just what you need, e.g. --extra timestamp. Also split
README command lists into core vs. extra sections for readability.
2026-08-11 01:38:37 +03:00

263 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[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` не дублирует уже добавленные треки.
### Дополнительно: другой плейлист вместо лайков
```
.venv\Scripts\python -m sort_ym report --source <ссылка> # то же самое, но для чужого/другого плейлиста по ссылке
.venv\Scripts\python -m sort_ym digest --source <ссылка> # дайджест чужого/другого плейлиста
.venv\Scripts\python -m sort_ym apply --source <ссылка> --label Друг --yes # раскидать его треки по своим плейлистам "Жанр — RU (Друг)"
```
Подробнее — раздел «Анализ и раскидка другого плейлиста» ниже.
### Дополнительно: гибкая настройка report.csv
По умолчанию `report` группирует и сортирует треки по целевому плейлисту с базовым набором колонок.
Эти флаги это меняют — от самого востребованного к более редкому:
```
.venv\Scripts\python -m sort_ym report --order playlist # без сортировки по плейлистам - треки в исходном порядке источника (как в лайках/--source плейлисте)
.venv\Scripts\python -m sort_ym report --extra timestamp # + дата добавления трека (added_at)
.venv\Scripts\python -m sort_ym report --extra album # + дата релиза и лайки альбома (release_date, album_likes_count)
.venv\Scripts\python -m sort_ym report --extra artist # + счётчики/рейтинг первого артиста трека
.venv\Scripts\python -m sort_ym report --extra duration # + длительность трека (duration_ms)
.venv\Scripts\python -m sort_ym report --extra version # + пометки версии трека/альбома (ремикс, юбилейное издание и т.п.)
.venv\Scripts\python -m sort_ym report --extra timestamp album # можно комбинировать несколько групп через пробел
.venv\Scripts\python -m sort_ym report --extra all --order playlist # все доп. колонки сразу + порядок как в источнике
```
## Полная очистка кэша
Если требуется начать с чистого листа (переполучить все данные с нуля), удалите папку `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.
Без `--source` команда полностью офлайн: не обращается к сети, не требует `.token`, читает только
уже загруженный `cache/` (нужно, чтобы `report` был запущен хотя бы раз, для
`cache/genre_catalog.json`). С `--source` (см. раздел «Анализ и раскидка другого плейлиста» ниже)
нужны и сеть, и токен, но `fetch` заранее не требуется.
```
$ 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` (он один раз доперекачает
весь кэш, дальше — как обычно, только новые треки).
## Анализ и раскидка другого плейлиста
`report`, `digest` и `apply` умеют работать не только с лайками, но и с любым плейлистом
Яндекс.Музыки (своим другим или чужим публичным) — по ссылке, флагом `--source`.
Как получить ссылку: откройте плейлист в веб-плеере Яндекс.Музыки и скопируйте адрес из строки
браузера — вида `https://music.yandex.ru/users/<логин>/playlists/<номер>`.
```
python -m sort_ym report --source https://music.yandex.ru/users/vasya/playlists/1001
python -m sort_ym digest --source https://music.yandex.ru/users/vasya/playlists/1001
python -m sort_ym apply --source https://music.yandex.ru/users/vasya/playlists/1001 --label Вася --yes
```
- **`--label <имя>`** (только для `apply`) — треки лягут в отдельные плейлисты вида
«Рок — RU (Вася)» вместо общих «Рок — RU». Без `--label` они попадут в те же плейлисты, что и
ваши лайкнутые треки того же жанра/языка — это осознанный дефолт, но именно поэтому для чужого
плейлиста стоит явно решить, нужно ли смешивать чужой вкус со своим.
- **Никакого кэша и истории**: данные внешнего плейлиста загружаются заново при каждом запуске
и никогда не сохраняются на диск (в отличие от лайков в `cache/tracks.json`) — так и задумано.
Повторный запуск на той же ссылке снова скачает всё с нуля.
- **Приватные плейлисты**: если плейлист недоступен (приватный либо истёк токен), команда выдаст
понятную ошибку — либо попросите владельца сделать плейлист публичным, либо перезайдите
(`python -m sort_ym auth`).
- **Точность языка чуть ниже**: для внешнего источника язык не уточняется через API текста песни
(это отдельный запрос на трек, и без кэша он был бы медленным на каждый запуск) — только по
уже накопленному `cache/lyrics_lang.json` и эвристикам. Без `--label` трек может попасть не в
тот языковой плейлист, а если его потом лайкнуть — окажется добавлен в оба.
- Для `apply` нет пробного режима с `--source` (только `--limit`) — сначала проверьте `report
--source`, чтобы увидеть распределение по плейлистам до реальной вставки.
- Результаты `report --source`/`digest --source` сохраняются в отдельные файлы —
`out/report_source.csv`/`out/digest_source.md` — и не затирают ваши основные `out/report.csv`/
`out/digest.md`.
- `report --source <ссылка> --order playlist` — треки в CSV пойдут в том же порядке, что и в самом
плейлисте, без пересортировки по целевому плейлисту.
## 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
.venv\Scripts\python -m sort_ym report --source https://music.yandex.ru/users/vasya/playlists/1001
```
Структура кода и поток данных — в [ARCHITECTURE.ru.md](ARCHITECTURE.ru.md).
## Credits
Andrew Pimenov
## License
[MIT License](LICENSE)