Sort_YM/README.ru.md
2026-08-16 19:58:28 +03:00

21 KiB
Raw Blame History

English

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      # Windows
.venv/bin/pip install -r requirements.txt          # Linux/macOS

Для разработки (тесты):

.venv\Scripts\pip install -r requirements-dev.txt  # Windows
.venv/bin/pip install -r requirements-dev.txt      # Linux/macOS

Все команды ниже используют путь Windows .venv\Scripts\... — на Linux/macOS вместо него .venv/bin/....

Что делает

Основные команды

.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  # Windows (PowerShell)
rm -rf cache                       # Linux/macOS

Кэш состоит из шести файлов:

  • 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  # Windows (PowerShell)
rm -rf out                       # Linux/macOS

Это просто результаты анализа, их удаление не повлияет на следующий запуск report/digest.

Важно: файл .token (OAuth-токен) не входит в кэш и не удаляется при очистке cache/. Очистка кэша вас не разлогирует. Если нужна повторная авторизация, удалите .token отдельно:

Remove-Item .token  # Windows (PowerShell)
rm .token           # Linux/macOS

Как определяется жанр и язык

  • Жанр определяется на под-жанровом уровне (Рок, Панк, Инди, Альтернатива, Метал, ..., не только 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

По умолчанию разбираются только русскоязычные треки — промпт и нарративные поля (summary/resonance/key_line) написаны на русском и проверены на пилотном прогоне по русской лирике; качество на других языках не проверялось. Флаг --all-languages у lyrics снимает фильтр и загружает/разбирает все треки с доступным текстом независимо от языка.

Что нужно: запущенная локально 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.

Credits

Andrew Pimenov

License

MIT License