mirror of
https://github.com/Horizontal42/Sort_YM.git
synced 2026-11-10 22:41:59 +03:00
document lyrics and analyze commands
This commit is contained in:
parent
d8f4e16f14
commit
2344889418
4 changed files with 157 additions and 6 deletions
|
|
@ -21,6 +21,10 @@ sort_ym/
|
||||||
genres.py — the real Yandex genre tree; classify_track — resolves track genre and artist
|
genres.py — the real Yandex genre tree; classify_track — resolves track genre and artist
|
||||||
genres into a single sub-genre (root_id); ROOT_BUCKET — sub-genre -> 11 top-level buckets
|
genres into a single sub-genre (root_id); ROOT_BUCKET — sub-genre -> 11 top-level buckets
|
||||||
language.py — hybrid language detection (lyrics API / genre hint / alphabet)
|
language.py — hybrid language detection (lyrics API / genre hint / alphabet)
|
||||||
|
lyrics.py — text lyrics of RU-tracks: track_supplement().lyrics.full_lyrics ->
|
||||||
|
cache/lyrics_text.json (incrementally, atomic-write)
|
||||||
|
analyze.py — lyric analysis via local Ollama (/api/chat, think + JSON Schema) ->
|
||||||
|
cache/lyrics_analysis.json (written after each track)
|
||||||
classify.py — (sub-genre | top-level bucket, language) -> playlist name; with_label — source
|
classify.py — (sub-genre | top-level bucket, language) -> playlist name; with_label — source
|
||||||
label suffix for apply --source --label
|
label suffix for apply --source --label
|
||||||
report.py — two-pass report row building, writes out/report.csv, summaries
|
report.py — two-pass report row building, writes out/report.csv, summaries
|
||||||
|
|
@ -34,12 +38,15 @@ sort_ym/
|
||||||
tests/
|
tests/
|
||||||
test_genres.py — classify_track, dominant, bucket_for, genre tree
|
test_genres.py — classify_track, dominant, bucket_for, genre tree
|
||||||
test_language.py — language detection
|
test_language.py — language detection
|
||||||
|
test_lyrics.py — lyrics text download, cache structure, resumability
|
||||||
|
test_analyze.py — local Ollama integration, JSON Schema parsing, cache atomicity
|
||||||
test_report.py — two-pass collapsing of small sub-genre groups
|
test_report.py — two-pass collapsing of small sub-genre groups
|
||||||
test_fetch.py, test_apply.py — track/artist cache, apply idempotency
|
test_fetch.py, test_apply.py — track/artist cache, apply idempotency
|
||||||
test_digest.py — artist/album aggregation, long-tail collapsing, digest size bounds
|
test_digest.py — artist/album aggregation, long-tail collapsing, digest size bounds
|
||||||
test_source.py — playlist URL parsing, matching client.tracks() response by id, handling
|
test_source.py — playlist URL parsing, matching client.tracks() response by id, handling
|
||||||
private/inaccessible playlists
|
private/inaccessible playlists
|
||||||
test_cli.py — --source/--label flag validation before any network access
|
test_cli.py, test_config.py — --source/--label flag validation before any network access;
|
||||||
|
config.toml reading including [ollama] block
|
||||||
|
|
||||||
cache/ — API cache (in .gitignore)
|
cache/ — API cache (in .gitignore)
|
||||||
out/ — reports (in .gitignore)
|
out/ — reports (in .gitignore)
|
||||||
|
|
@ -59,6 +66,9 @@ report → genres.load_or_fetch_catalog() → cache/genre_catalog.json
|
||||||
apply --yes → users_playlists_create / users_playlists_insert_track
|
apply --yes → users_playlists_create / users_playlists_insert_track
|
||||||
digest → genres.load_catalog() + language.load_lang_cache() (cache only, no network)
|
digest → genres.load_catalog() + language.load_lang_cache() (cache only, no network)
|
||||||
→ build_rows() → out/digest.md
|
→ build_rows() → out/digest.md
|
||||||
|
lyrics → ru_lyric_track_ids() → client.track_supplement() → cache/lyrics_text.json
|
||||||
|
analyze → cache/lyrics_text.json → Ollama /api/chat → cache/lyrics_analysis.json
|
||||||
|
digest → + cache/lyrics_analysis.json → out/digest.md (aggregate block) + out/lyrics_digest.md
|
||||||
|
|
||||||
report/apply/digest --source <url> → source.parse_playlist_url() → (user_id, kind)
|
report/apply/digest --source <url> → source.parse_playlist_url() → (user_id, kind)
|
||||||
→ client.users_playlists(kind, user_id=...) → TrackShort[] → client.tracks(batch)
|
→ client.users_playlists(kind, user_id=...) → TrackShort[] → client.tracks(batch)
|
||||||
|
|
@ -107,6 +117,36 @@ report/apply/digest --source <url> → source.parse_playlist_url() → (user_id,
|
||||||
3. An alphabet heuristic on the title/artist (Cyrillic vs Latin, `unicodedata`).
|
3. An alphabet heuristic on the title/artist (Cyrillic vs Latin, `unicodedata`).
|
||||||
4. Otherwise — `UNKNOWN` → a separate "Undetermined" playlist.
|
4. Otherwise — `UNKNOWN` → a separate "Undetermined" playlist.
|
||||||
|
|
||||||
|
- **Lyric analysis (`lyrics.py`, `analyze.py`)**: only Russian-language tracks are analyzed. The
|
||||||
|
reason is signal validity, not cost — on a foreign-language track the meaning arrives through
|
||||||
|
the music and the vocal delivery, not through parsed words, so an LLM reading the literal text
|
||||||
|
would add a signal the listener never perceives. Text fetching and analysis are **two separate
|
||||||
|
commands** on purpose: the fetch is network-bound against the deprecated `track_supplement`,
|
||||||
|
the analysis is a multi-hour local GPU job, and a prompt or model change must be re-runnable
|
||||||
|
without touching Yandex again. `analyze` and `digest` construct no `Client` at all.
|
||||||
|
|
||||||
|
The Ollama call (`/api/chat`) combines `think: true` with a full JSON Schema in `format`:
|
||||||
|
reasoning goes to a separate `message.thinking` field (never cached) while only the final
|
||||||
|
answer is grammar-constrained, so the model reasons freely and the result still parses.
|
||||||
|
`num_predict: -1` — a token cap would truncate mid-JSON and turn a slow answer into an invalid
|
||||||
|
one; the bound is the request timeout instead. `temperature: 0.65`, not 0: at 0 the narrative
|
||||||
|
fields flatten out.
|
||||||
|
|
||||||
|
`themes` are free-form but pattern-constrained to English snake_case
|
||||||
|
(`^[a-z][a-z0-9_]{2,29}$`) — in the pilot, an unconstrained field drifted between Russian and
|
||||||
|
English inside a single run, making the values impossible to aggregate; the pattern makes
|
||||||
|
Cyrillic mechanically unrepresentable at decode time. They are English for the same reason
|
||||||
|
genre slugs and `ROOT_BUCKET` keys are: they are aggregation keys. `mood` is a closed enum that
|
||||||
|
deliberately excludes irony/sarcasm — that axis is `stance`, and mixing the two produced
|
||||||
|
compound values like `"aggressive/triumphant"`.
|
||||||
|
|
||||||
|
Resumability: the cache is written atomically after **every single track** (not batched by 20
|
||||||
|
as in `language.py`) — inference costs tens of seconds, so a crash must cost at most one track.
|
||||||
|
An entry is reused only when `(model, prompt_version, lyrics_hash)` all match, so a corrected
|
||||||
|
lyrics text or a bumped prompt invalidates exactly what it should. A timeout or a broken
|
||||||
|
response is stored as an `error` marker and the batch continues; error entries are never
|
||||||
|
considered fresh, so the next run retries precisely those tracks.
|
||||||
|
|
||||||
- **Track identifiers**: a track has a composite `track_id` (`"id:album_id"`, used as the cache key
|
- **Track identifiers**: a track has a composite `track_id` (`"id:album_id"`, used as the cache key
|
||||||
and to check against tracks already in a playlist) and separate `id`/`album_id` (needed as
|
and to check against tracks already in a playlist) and separate `id`/`album_id` (needed as
|
||||||
separate parameters for `track_supplement`, `tracks_lyrics`, and `users_playlists_insert_track`).
|
separate parameters for `track_supplement`, `tracks_lyrics`, and `users_playlists_insert_track`).
|
||||||
|
|
@ -178,6 +218,12 @@ report/apply/digest --source <url> → source.parse_playlist_url() → (user_id,
|
||||||
`report --extra`-only purpose as `added_at` above.
|
`report --extra`-only purpose as `added_at` above.
|
||||||
- `cache/genre_catalog.json` — a flat snapshot of Yandex's genre tree (id -> title, root_id).
|
- `cache/genre_catalog.json` — a flat snapshot of Yandex's genre tree (id -> title, root_id).
|
||||||
- `cache/lyrics_lang.json` — lyrics language per track (id -> language code | null).
|
- `cache/lyrics_lang.json` — lyrics language per track (id -> language code | null).
|
||||||
|
- `cache/lyrics_text.json` — lyrics text per track (numeric id -> text | null, RU tracks only).
|
||||||
|
- `cache/lyrics_analysis.json` — per-track analysis record: `{track_id: {model, prompt_version,
|
||||||
|
lyrics_hash, mood, themes, emotional_arc, pov, tone, description, error?}}`.
|
||||||
|
- `out/lyrics_digest.md` — full per-track reading grouped by primary mood.
|
||||||
|
- `config.toml` — includes `[ollama]` block with `host`, `model`, `prompt_version`, `timeout`,
|
||||||
|
`keep_alive`.
|
||||||
- `out/report.csv` — a preview of the playlist breakdown before `apply` (including the
|
- `out/report.csv` — a preview of the playlist breakdown before `apply` (including the
|
||||||
`fine_bucket` column — the sub-genre before any collapsing into a top-level bucket).
|
`fine_bucket` column — the sub-genre before any collapsing into a top-level bucket).
|
||||||
`report --order playlist` keeps source track order instead of sorting by target playlist;
|
`report --order playlist` keeps source track order instead of sorting by target playlist;
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,10 @@ sort_ym/
|
||||||
genres.py — реальное дерево жанров Яндекса; classify_track — сведение жанра трека и жанров
|
genres.py — реальное дерево жанров Яндекса; classify_track — сведение жанра трека и жанров
|
||||||
артиста в один под-жанр (root_id); ROOT_BUCKET — под-жанр -> 11 крупных корзин
|
артиста в один под-жанр (root_id); ROOT_BUCKET — под-жанр -> 11 крупных корзин
|
||||||
language.py — гибридная детекция языка (API текста / жанр-подсказка / алфавит)
|
language.py — гибридная детекция языка (API текста / жанр-подсказка / алфавит)
|
||||||
|
lyrics.py — тексты песен RU-треков: track_supplement().lyrics.full_lyrics ->
|
||||||
|
cache/lyrics_text.json (инкрементально, atomic-write)
|
||||||
|
analyze.py — разбор текстов локальной Ollama (/api/chat, think + JSON Schema) ->
|
||||||
|
cache/lyrics_analysis.json (запись после каждого трека)
|
||||||
classify.py — (под-жанр | крупная корзина, язык) -> имя плейлиста; with_label — суффикс метки
|
classify.py — (под-жанр | крупная корзина, язык) -> имя плейлиста; with_label — суффикс метки
|
||||||
источника для apply --source --label
|
источника для apply --source --label
|
||||||
report.py — двухпроходная сборка строк отчёта, запись out/report.csv, сводки
|
report.py — двухпроходная сборка строк отчёта, запись out/report.csv, сводки
|
||||||
|
|
@ -34,13 +38,16 @@ sort_ym/
|
||||||
tests/
|
tests/
|
||||||
test_genres.py — classify_track, dominant, bucket_for, дерево жанров
|
test_genres.py — classify_track, dominant, bucket_for, дерево жанров
|
||||||
test_language.py — детекция языка
|
test_language.py — детекция языка
|
||||||
|
test_lyrics.py — загрузка текстов песен, структура кэша, резюмируемость
|
||||||
|
test_analyze.py — интеграция локальной Ollama, разбор JSON Schema, атомарность кэша
|
||||||
test_report.py — двухпроходное схлопывание мелких под-жанровых групп
|
test_report.py — двухпроходное схлопывание мелких под-жанровых групп
|
||||||
test_fetch.py, test_apply.py — кэш треков/артистов, идемпотентность применения
|
test_fetch.py, test_apply.py — кэш треков/артистов, идемпотентность применения
|
||||||
test_digest.py — агрегация по исполнителям и альбомам, схлопывание длинного хвоста,
|
test_digest.py — агрегация по исполнителям и альбомам, схлопывание длинного хвоста,
|
||||||
границы размера дайджеста
|
границы размера дайджеста
|
||||||
test_source.py — разбор ссылки на плейлист, сопоставление ответа client.tracks() по id,
|
test_source.py — разбор ссылки на плейлист, сопоставление ответа client.tracks() по id,
|
||||||
обработка приватных/недоступных плейлистов
|
обработка приватных/недоступных плейлистов
|
||||||
test_cli.py — валидация флагов --source/--label до обращения к сети
|
test_cli.py, test_config.py — валидация флагов --source/--label до обращения к сети;
|
||||||
|
чтение config.toml включая блок [ollama]
|
||||||
|
|
||||||
cache/ — кэш API (в .gitignore)
|
cache/ — кэш API (в .gitignore)
|
||||||
out/ — отчёты (в .gitignore)
|
out/ — отчёты (в .gitignore)
|
||||||
|
|
@ -60,6 +67,9 @@ report → genres.load_or_fetch_catalog() → cache/genre_catalog.json
|
||||||
apply --yes → users_playlists_create / users_playlists_insert_track
|
apply --yes → users_playlists_create / users_playlists_insert_track
|
||||||
digest → genres.load_catalog() + language.load_lang_cache() (только кэш, сеть не используется)
|
digest → genres.load_catalog() + language.load_lang_cache() (только кэш, сеть не используется)
|
||||||
→ build_rows() → out/digest.md
|
→ build_rows() → out/digest.md
|
||||||
|
lyrics → ru_lyric_track_ids() → client.track_supplement() → cache/lyrics_text.json
|
||||||
|
analyze → cache/lyrics_text.json → Ollama /api/chat → cache/lyrics_analysis.json
|
||||||
|
digest → + cache/lyrics_analysis.json → out/digest.md (агрегат) + out/lyrics_digest.md
|
||||||
|
|
||||||
report/apply/digest --source <url> → source.parse_playlist_url() → (user_id, kind)
|
report/apply/digest --source <url> → source.parse_playlist_url() → (user_id, kind)
|
||||||
→ client.users_playlists(kind, user_id=...) → TrackShort[] → client.tracks(batch)
|
→ client.users_playlists(kind, user_id=...) → TrackShort[] → client.tracks(batch)
|
||||||
|
|
@ -108,6 +118,35 @@ report/apply/digest --source <url> → source.parse_playlist_url() → (user_id,
|
||||||
3. Эвристика по алфавиту названия/исполнителя (кириллица vs латиница, `unicodedata`).
|
3. Эвристика по алфавиту названия/исполнителя (кириллица vs латиница, `unicodedata`).
|
||||||
4. Иначе — `UNKNOWN` → отдельный плейлист «Не определено».
|
4. Иначе — `UNKNOWN` → отдельный плейлист «Не определено».
|
||||||
|
|
||||||
|
- **Разбор текстов (`lyrics.py`, `analyze.py`)**: разбираются только русскоязычные треки.
|
||||||
|
Причина — валидность сигнала, не стоимость: на иноязычном треке смысл передаётся музыкой и
|
||||||
|
подачей вокала, а не разобранными словами, поэтому LLM, читающая буквальный текст, добавит
|
||||||
|
сигнал, который слушатель при прослушивании не считывает. Загрузка текста и анализ — это
|
||||||
|
**две отдельные команды** намеренно: загрузка привязана к сети и устаревшему `track_supplement`,
|
||||||
|
анализ — это многочасовая работа локального GPU, а смена prompt или модели должна быть
|
||||||
|
переиспользуема без нового обращения к Яндексу. `analyze` и `digest` вообще не создают `Client`.
|
||||||
|
|
||||||
|
Вызов Ollama (`/api/chat`) комбинирует `think: true` с полной JSON Schema в `format`:
|
||||||
|
рассуждение идёт в отдельное поле `message.thinking` (никогда не кэшируется), а финальный ответ
|
||||||
|
ограничивается грамматикой, так что модель рассуждает свободно, а результат всё равно парсится.
|
||||||
|
`num_predict: -1` — ограничение по токенам обрежет JSON и превратит медленный ответ в невалидный;
|
||||||
|
ограничение вместо этого — таймаут запроса. `temperature: 0.65`, не 0: при 0 нарративные поля
|
||||||
|
становятся одноообразными.
|
||||||
|
|
||||||
|
`themes` свободного формата, но ограничены паттерном англ. snake_case (`^[a-z][a-z0-9_]{2,29}$`) —
|
||||||
|
в пилоте неограниченное поле дрейфовало между русским и английским в одном прогоне, что сделало
|
||||||
|
значения невозможными для агрегации; паттерн делает кириллицу механически невозможной при разборе.
|
||||||
|
Они англ. по той же причине, что и слаги жанров, и `ROOT_BUCKET`: это ключи агрегации. `mood` —
|
||||||
|
закрытый enum, намеренно исключающий иронию/сарказм — это ось `stance`, и смешивание двух осей
|
||||||
|
дало составные значения типа `"aggressive/triumphant"`.
|
||||||
|
|
||||||
|
Резюмируемость: кэш пишется атомарно после **каждого трека** (не батчами по 20, как в
|
||||||
|
`language.py`) — инференс занимает десятки секунд, так что крах должен стоить максимум один трек.
|
||||||
|
Запись переиспользуется, только если совпадают `(model, prompt_version, lyrics_hash)`, поэтому
|
||||||
|
исправленный текст или bumped prompt инвалидирует ровно то, что надо. Таймаут или повреждённый
|
||||||
|
ответ сохраняются как `error` marker, прогон продолжается; error-записи никогда не считаются
|
||||||
|
свежими, так что следующий прогон повторит точно эти треки.
|
||||||
|
|
||||||
- **Идентификаторы треков**: у трека есть composite `track_id` (`"id:album_id"`, используется как
|
- **Идентификаторы треков**: у трека есть composite `track_id` (`"id:album_id"`, используется как
|
||||||
ключ кэша и для сверки с уже добавленными треками в плейлисте) и отдельные `id`/`album_id`
|
ключ кэша и для сверки с уже добавленными треками в плейлисте) и отдельные `id`/`album_id`
|
||||||
(нужны как раздельные параметры для `track_supplement`, `tracks_lyrics` и
|
(нужны как раздельные параметры для `track_supplement`, `tracks_lyrics` и
|
||||||
|
|
@ -177,6 +216,12 @@ report/apply/digest --source <url> → source.parse_playlist_url() → (user_id,
|
||||||
и `added_at` выше, только для `report --extra`.
|
и `added_at` выше, только для `report --extra`.
|
||||||
- `cache/genre_catalog.json` — плоский снимок дерева жанров Яндекса (id -> title, root_id).
|
- `cache/genre_catalog.json` — плоский снимок дерева жанров Яндекса (id -> title, root_id).
|
||||||
- `cache/lyrics_lang.json` — язык текста песни по треку (id -> language code | null).
|
- `cache/lyrics_lang.json` — язык текста песни по треку (id -> language code | null).
|
||||||
|
- `cache/lyrics_text.json` — текст песни по треку (numeric id -> текст | null, только RU-треки).
|
||||||
|
- `cache/lyrics_analysis.json` — запись анализа на трек: `{track_id: {model, prompt_version,
|
||||||
|
lyrics_hash, mood, themes, emotional_arc, pov, tone, description, error?}}`.
|
||||||
|
- `out/lyrics_digest.md` — полный построчный разбор, сгруппированный по основному настроению.
|
||||||
|
- `config.toml` — включает блок `[ollama]` с полями `host`, `model`, `prompt_version`, `timeout`,
|
||||||
|
`keep_alive`.
|
||||||
- `out/report.csv` — предпросмотр распределения по плейлистам перед `apply` (включая колонку
|
- `out/report.csv` — предпросмотр распределения по плейлистам перед `apply` (включая колонку
|
||||||
`fine_bucket` — под-жанр до возможного схлопывания в крупную корзину). `report --order playlist`
|
`fine_bucket` — под-жанр до возможного схлопывания в крупную корзину). `report --order playlist`
|
||||||
сохраняет исходный порядок треков вместо сортировки по целевому плейлисту; отдельные флаги
|
сохраняет исходный порядок треков вместо сортировки по целевому плейлисту; отдельные флаги
|
||||||
|
|
|
||||||
34
README.md
34
README.md
|
|
@ -42,6 +42,8 @@ For development (tests):
|
||||||
.venv\Scripts\python -m sort_ym apply --limit 20 --yes # dry run: create playlists and add the first 20 tracks
|
.venv\Scripts\python -m sort_ym apply --limit 20 --yes # dry run: create playlists and add the first 20 tracks
|
||||||
.venv\Scripts\python -m sort_ym apply --yes # create playlists and add all tracks (changes the account)
|
.venv\Scripts\python -m sort_ym apply --yes # create playlists and add all tracks (changes the account)
|
||||||
.venv\Scripts\python -m sort_ym digest # library summary (top artists, genres) to out/digest.md - to paste into an LLM chat; no network used
|
.venv\Scripts\python -m sort_ym digest # library summary (top artists, genres) to out/digest.md - to paste into an LLM chat; no network used
|
||||||
|
.venv\Scripts\python -m sort_ym lyrics # download the lyrics text of Russian-language tracks (cache/lyrics_text.json); does not change the account
|
||||||
|
.venv\Scripts\python -m sort_ym analyze # analyze those lyrics with a local Ollama model (cache/lyrics_analysis.json); no internet, runs for hours, resumable
|
||||||
```
|
```
|
||||||
|
|
||||||
On repeated runs `fetch` only downloads new tracks, and `apply` does not duplicate tracks already added.
|
On repeated runs `fetch` only downloads new tracks, and `apply` does not duplicate tracks already added.
|
||||||
|
|
@ -80,11 +82,13 @@ To start over from scratch (re-fetch everything), delete the `cache/` folder:
|
||||||
Remove-Item -Recurse -Force cache
|
Remove-Item -Recurse -Force cache
|
||||||
```
|
```
|
||||||
|
|
||||||
The cache consists of four files:
|
The cache consists of six files:
|
||||||
- `cache/tracks.json` — liked tracks and their metadata (including artist ids);
|
- `cache/tracks.json` — liked tracks and their metadata (including artist ids);
|
||||||
- `cache/artist_genres.json` — genre tags for each artist (an artist can have several);
|
- `cache/artist_genres.json` — genre tags for each artist (an artist can have several);
|
||||||
- `cache/genre_catalog.json` — a snapshot of Yandex's genre tree as of the last run;
|
- `cache/genre_catalog.json` — a snapshot of Yandex's genre tree as of the last run;
|
||||||
- `cache/lyrics_lang.json` — detected lyrics language per track.
|
- `cache/lyrics_lang.json` — detected lyrics language per track;
|
||||||
|
- `cache/lyrics_text.json` — lyrics text of Russian-language tracks;
|
||||||
|
- `cache/lyrics_analysis.json` — per-track lyric analysis from the local model.
|
||||||
|
|
||||||
Deleting `cache/` makes `fetch` and `report` re-fetch everything from scratch on the next run.
|
Deleting `cache/` makes `fetch` and `report` re-fetch everything from scratch on the next run.
|
||||||
|
|
||||||
|
|
@ -159,6 +163,32 @@ Digest saved: out/digest.md
|
||||||
|
|
||||||
Example `out/digest.md` (abridged):
|
Example `out/digest.md` (abridged):
|
||||||
|
|
||||||
|
## Lyric analysis (Russian tracks)
|
||||||
|
|
||||||
|
`lyrics` downloads the lyrics text, `analyze` reads it with a local model and writes a per-track
|
||||||
|
profile: mood, themes, emotional arc, point of view, tone, plus a short description of what the
|
||||||
|
song is about and why it might resonate.
|
||||||
|
|
||||||
|
```
|
||||||
|
.venv\Scripts\python -m sort_ym lyrics # lyrics text of Russian-language tracks
|
||||||
|
.venv\Scripts\python -m sort_ym analyze # analysis via local Ollama, hours of local compute
|
||||||
|
.venv\Scripts\python -m sort_ym digest # out/lyrics_digest.md + an aggregate block in out/digest.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Only Russian-language tracks are analyzed. On a foreign-language track the meaning is carried by
|
||||||
|
the music and the vocal delivery rather than by parsed words, so a literal reading of the text
|
||||||
|
would add a signal the listener never actually perceives.
|
||||||
|
|
||||||
|
Requirements: Ollama running locally with the model named in `config.toml` under `[ollama]`
|
||||||
|
(`ollama list` to check). No internet is used — `analyze` never talks to Yandex.
|
||||||
|
|
||||||
|
Expect roughly 40 seconds per track — about three hours for a ~266-track library. The run is
|
||||||
|
interruptible: results are written after every single track, so Ctrl+C costs at most one track,
|
||||||
|
and re-running continues where it stopped. Tracks that timed out are retried on the next run.
|
||||||
|
Raising `prompt_version` in `config.toml` invalidates the whole cache and re-analyzes everything.
|
||||||
|
|
||||||
|
Example `out/digest.md` (abridged):
|
||||||
|
|
||||||
```
|
```
|
||||||
## Genres (top-level buckets)
|
## Genres (top-level buckets)
|
||||||
- Indie & Alternative — 680 (34%): INT 571 / RU 108
|
- Indie & Alternative — 680 (34%): INT 571 / RU 108
|
||||||
|
|
|
||||||
34
README.ru.md
34
README.ru.md
|
|
@ -42,6 +42,8 @@ python -m venv .venv
|
||||||
.venv\Scripts\python -m sort_ym apply --limit 20 --yes # пробный запуск: создать плейлисты и добавить в них первые 20 треков
|
.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 apply --yes # создать плейлисты и добавить в них все треки (меняет аккаунт)
|
||||||
.venv\Scripts\python -m sort_ym digest # сводка библиотеки (топ-исполнители, жанры) в out/digest.md - чтобы вставить в чат с LLM; сеть не используется
|
.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` не дублирует уже добавленные треки.
|
При повторных запусках `fetch` докачивает только новые треки, а `apply` не дублирует уже добавленные треки.
|
||||||
|
|
@ -80,11 +82,13 @@ python -m venv .venv
|
||||||
Remove-Item -Recurse -Force cache
|
Remove-Item -Recurse -Force cache
|
||||||
```
|
```
|
||||||
|
|
||||||
Кэш состоит из четырёх файлов:
|
Кэш состоит из шести файлов:
|
||||||
- `cache/tracks.json` — список лайкнутых треков и их метаданные (включая id артистов);
|
- `cache/tracks.json` — список лайкнутых треков и их метаданные (включая id артистов);
|
||||||
- `cache/artist_genres.json` — жанровые теги каждого артиста (может быть несколько на артиста);
|
- `cache/artist_genres.json` — жанровые теги каждого артиста (может быть несколько на артиста);
|
||||||
- `cache/genre_catalog.json` — снимок дерева жанров Яндекса на момент последнего запуска;
|
- `cache/genre_catalog.json` — снимок дерева жанров Яндекса на момент последнего запуска;
|
||||||
- `cache/lyrics_lang.json` — определённые языки текстов для каждого трека.
|
- `cache/lyrics_lang.json` — определённые языки текстов для каждого трека;
|
||||||
|
- `cache/lyrics_text.json` — тексты песен русскоязычных треков;
|
||||||
|
- `cache/lyrics_analysis.json` — разбор текста каждого трека локальной моделью.
|
||||||
|
|
||||||
Удаление `cache/` заставит `fetch` и `report` переполучить всё с нуля при следующем запуске.
|
Удаление `cache/` заставит `fetch` и `report` переполучить всё с нуля при следующем запуске.
|
||||||
|
|
||||||
|
|
@ -160,6 +164,32 @@ $ python -m sort_ym digest
|
||||||
|
|
||||||
Пример `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
|
- Инди и альтернатива — 680 (34%): INT 571 / RU 108
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue