mirror of
https://github.com/Horizontal42/Sort_YM.git
synced 2026-11-10 18:02:00 +03:00
293 lines
21 KiB
Markdown
293 lines
21 KiB
Markdown
[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; сеть не используется
|
||
.venv\Scripts\python -m sort_ym lyrics # загрузить тексты песен русскоязычных треков (cache/lyrics_text.json), аккаунт не меняется
|
||
.venv\Scripts\python -m sort_ym analyze # разобрать эти тексты локальной моделью Ollama (cache/lyrics_analysis.json), без интернета, идёт часами, продолжается после обрыва
|
||
```
|
||
|
||
При повторных запусках `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 --timestamp # + дата добавления трека (added_at)
|
||
.venv\Scripts\python -m sort_ym report --album # + дата релиза и лайки альбома (release_date, album_likes_count)
|
||
.venv\Scripts\python -m sort_ym report --artist # + счётчики/рейтинг первого артиста трека
|
||
.venv\Scripts\python -m sort_ym report --duration # + длительность трека (duration_ms)
|
||
.venv\Scripts\python -m sort_ym report --version # + пометки версии трека/альбома (ремикс, юбилейное издание и т.п.)
|
||
.venv\Scripts\python -m sort_ym report --timestamp --album # флаги можно комбинировать
|
||
.venv\Scripts\python -m sort_ym report --extra --order playlist # --extra = сразу все доп. колонки + порядок как в источнике
|
||
```
|
||
|
||
## Полная очистка кэша
|
||
|
||
Если требуется начать с чистого листа (переполучить все данные с нуля), удалите папку `cache/`:
|
||
|
||
```
|
||
Remove-Item -Recurse -Force cache
|
||
```
|
||
|
||
Кэш состоит из шести файлов:
|
||
- `cache/tracks.json` — список лайкнутых треков и их метаданные (включая id артистов);
|
||
- `cache/artist_genres.json` — жанровые теги каждого артиста (может быть несколько на артиста);
|
||
- `cache/genre_catalog.json` — снимок дерева жанров Яндекса на момент последнего запуска;
|
||
- `cache/lyrics_lang.json` — определённые языки текстов для каждого трека;
|
||
- `cache/lyrics_text.json` — тексты песен русскоязычных треков;
|
||
- `cache/lyrics_analysis.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` (сокращённо):
|
||
|
||
## Разбор текстов (русскоязычные треки)
|
||
|
||
`lyrics` загружает текст песни, `analyze` читает его локальной моделью и пишет профиль каждого
|
||
трека: настроение, темы, эмоциональную арку, ракурс, тон, плюс короткое описание того, о чём
|
||
песня и чем она может цеплять.
|
||
|
||
```
|
||
.venv\Scripts\python -m sort_ym lyrics # тексты песен русскоязычных треков
|
||
.venv\Scripts\python -m sort_ym analyze # разбор локальной Ollama, часы локальных вычислений
|
||
.venv\Scripts\python -m sort_ym digest # out/lyrics_digest.md + агрегат в out/digest.md
|
||
```
|
||
|
||
Разбираются только русскоязычные треки. На иноязычном треке смысл передаётся музыкой и подачей
|
||
вокала, а не разобранными словами, поэтому буквальное чтение текста добавило бы сигнал, которого
|
||
слушатель при прослушивании не считывает.
|
||
|
||
Что нужно: запущенная локально Ollama с моделью из `config.toml`, блок `[ollama]` (проверить —
|
||
`ollama list`). Интернет не используется: `analyze` не обращается к Яндексу вообще.
|
||
|
||
Скорость — примерно 40 секунд на трек, около трёх часов на ~266 треков. Прогон прерываемый:
|
||
результат пишется после каждого трека, Ctrl+C стоит максимум один трек, повторный запуск
|
||
продолжает с места обрыва. Треки, упавшие по таймауту, повторяются при следующем запуске.
|
||
Увеличение `prompt_version` в `config.toml` обесценивает весь кэш и запускает разбор заново.
|
||
|
||
Пример `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)
|