diff --git a/personal/plans/music-artist-albumartist-repair.md b/personal/plans/music-artist-albumartist-repair.md index 6a9f87a1..fb7214db 100755 --- a/personal/plans/music-artist-albumartist-repair.md +++ b/personal/plans/music-artist-albumartist-repair.md @@ -4,7 +4,7 @@ created: '2026-09-27' updated: '2026-09-27' type: plan namespace: personal -status: ПЛАН-v4-СОБРАН / ДИФФ-v4-СОБРАН / ДУБЛИ-УДАЛЕНЫ-50 / 2-ВОПРОСА-ОТКРЫТЫ +status: ДИФФ-ОТ-БД-СОБРАН / ПРОГОН-v5-ЖДЁТ-АЛЕКСА / 215-ОДИНОЧЕК-ОТКРЫТО tags: - music - metadata @@ -35,44 +35,38 @@ related: **Что делаем:** привести ID3-теги библиотеки `/mnt/RED_2TB/storage/Music` (7861 файл) в порядок, чтобы Navidrome показывал правильные альбомы/исполнителей/жанры. -**Главный артефакт на сейчас (27 сен, сессия ~34):** +**Главный артефакт на сейчас (27 сен, сессия ~35):** ``` -ДИФФ: ~/Downloads/music-tags/DIFF-canon-v4.csv - 90 строк (ПАПКА 26 · ТРЕК 64) — схлопнуто из 428 правок +⚠️ ДИФФ ПЕРЕСОБРАН ОТ БАЗЫ (v3 был собран от КАНОНА — НЕВЕРНЫЙ ИСТОЧНИК!) + +ДИФФ: ~/Downloads/music-tags/DIFF-db-to-fix.csv + 91 строка (ПАПКА 26 · ТРЕК 65) — схлопнуто из 429 правок сортировка: по АЛФАВИТУ field (album → albumartist → artist → track) -План: ~/Downloads/music-tags/apply-plan-v4.csv (7861 строка) -Лог: ~/Downloads/music-tags/DIFF-canon-v4-changes.csv (428 строк, построчно, с причиной) -Сборщик: ~/Downloads/music-tags/build_diff_canon_v4.py -Удалено: ~/Downloads/music-tags/DELETE-dup-trofim.csv (50 дублей Трофима — ✅ снесены) -Канон: ~/Downloads/music-tags/APPLY-PLAN.csv (7861 строка — НЕ ПЕРЕЗАПИСЫВАТЬ, эталон) -Бэкап канона: /tmp/APPLY-PLAN.csv.bak-before-v4 + источник цели: БД Navidrome (то, что РЕАЛЬНО в тегах), НЕ канон +План: ~/Downloads/music-tags/apply-plan-v5.csv (7861 строка) +Сборщик: ~/Downloads/music-tags/build_diff_from_db.py +Канон: ~/Downloads/music-tags/APPLY-PLAN.csv (7861 строка — эталон ручных правок, НЕ ПЕРЕЗАПИСЫВАТЬ) ``` ⚠️ **Сборщики диффа/плана писать СРАЗУ в `~/Downloads/music-tags/`, НЕ в `/tmp`.** Alex: «какого хуя он в тмп» — артефакт в `/tmp` он не находит. -**Состояние (27 сен, сессия ~34):** ✅ **БОЕВОЙ ПРОГОН v3 выполнен** (7653/7653 ok, err=0, +🔴 **ГЛАВНЫЙ УРОК СЕССИИ ~35: ДИФФ СТРОИТЬ ОТ БД, А НЕ ОТ КАНОНА.** +Первый дифф v4 был собран от `APPLY-PLAN.csv` (канон) — и **не содержал Птаху**, +хотя дефект в базе был: прогон v3 записал испорченное значение, диск и БД **ушли +вперёд канона**. Alex: «в смысле правильно? в базе блядь у него что?!». +Правило: источник истины = `media_file` из `navidrome.db`, канон — только эталон +ручных решений. Подробно → § ДИФФ ОТ БАЗЫ. + +**Состояние (27 сен, сессия ~35):** ✅ **БОЕВОЙ ПРОГОН v3 выполнен** (7653/7653 ok, err=0, `TCON` 4359, БД Navidrome пересоздана, скан 4m13s, импортировано 7861). +✅ **50 дублей Трофима удалены с диска.** ⏳ **Прогон v5 НЕ сделан** — ждём решения Alex +по 1 вопросу (§ ОТКРЫТЫЙ ВОПРОС). -✅ **ПЛАН v4 СОБРАН И ДИФФ ПОКАЗАН.** База сборки — **КАНОН** `APPLY-PLAN.csv` (НЕ план v3: -v3 испорчен багой `album==title`; сборка от канона лечит 20 файлов автоматически). -Правок 428: `albumartist 157 · artist 154 · album 59 · track 58`. -Верификация v4 — 4 проверки, **0 ошибок** (`/tmp/verify_v4.py`): пути 1:1, каждая строка -дифа подтверждается, изменений вне дифа 0, ложных срабатываний класса Птахи 0. - -✅ **ДУБЛИ ТРОФИМА УДАЛЕНЫ** — 50 файлов из `disk/` + `disk_v2/` снесены по команде Alex -(`/tmp/del_dups.py`, удалено 50/50, ошибок 0). `!Рэп/Трофим` цел (157 файлов). - -⏳ **ОСТАЛОСЬ — 2 ВОПРОСА К ALEX (блокируют прогон v4):** -1. **`10 Ностальгия` / `Боже, какой пустяк`** — дубль ВНУТРИ `!Рэп/Трофим`, `album='Ветер - в голове'`. Удалять файл или сменить `album` на `Ностальгия`? В дифе его НЕТ (не трогал). -2. **«Тема дороги» `artist`** — в дифе `Бумер → Ленинград` (по имени папки). Оставляем? - Альтернативы: `Сергей Шнуров`, `Various Artists`. - -**Порядок после ответов:** подтвердить диф → прогон v4 (`--apply`, план подложить как -`apply-plan.csv`) → верификация чтением → **пересоздать БД Navidrome** (после удаления -50 файлов рескан обязателен) → приёмка Alex в UI. +**Порядок после ответа:** подтвердить диф `DIFF-db-to-fix.csv` → прогон v5 (`--apply`, +план `apply-plan-v5.csv` подложить как `apply-plan.csv`) → верификация чтением → +**пересоздать БД Navidrome** → приёмка Alex в UI. ``` Navidrome БД: /mnt/RED_2TB/docker/navidrome/data/ ← БЕЗ storage/ (дока ранее врала) @@ -120,6 +114,10 @@ compose: /mnt/RED_2TB/docker/navidrome | 26 | 27 сен | ✅ **ВЕРИФИКАЦИЯ v4** — 4 проверки, 0 ошибок | `/tmp/verify_v4.py` | | 27 | 27 сен | ✅ **ДИФФ v4 СОБРАН ПРАВИЛЬНО** — 90 строк (ПАПКА 26 · ТРЕК 64), сортировка по алфавиту field, схлопывание | `DIFF-canon-v4.csv` | | 28 | 27 сен | 🔴 **ПЕРЕДЕЛКА ДИФФА** — первый дифф был НЕ сортирован и НЕ схлопнут (428 построчных строк) | § ПЕРЕДЕЛКА ДИФФА | +| 29 | 27 сен | 🔴 **ДИФФ v4 НЕВЕРЕН: собран от КАНОНА** — Птахи в нём нет, а дефект в базе есть | § ДИФФ ОТ БАЗЫ | +| 30 | 27 сен | ✅ **ДИФФ ПЕРЕСОБРАН ОТ БД** — источник истины = `media_file`, 91 строка из 429 правок | `DIFF-db-to-fix.csv` | +| 31 | 27 сен | 🔴 **ЛОЖНОЕ РАСХОЖДЕНИЕ ПО ЖАНРУ** — `media_file.genre` пуст у всех, жанр в `tags` jsonb | `build_diff_from_db.py` | +| 32 | 27 сен | 🔴 **ДЕФЕКТ «АЛЬБОМЫ-ОДИНОЧКИ»** — 215 альбомов из 1 трека, где `album != имя папки` | § АЛЬБОМЫ-ОДИНОЧКИ | --- @@ -654,6 +652,108 @@ docker run --rm --entrypoint python3 \ --- +## 🔴 ДИФФ ОТ БАЗЫ — почему канон НЕ источник истины (27 сен, сессия ~35) + +**Alex: «Птаху куда проёб?» → «в смысле правильно? в базе блядь у него что?!»** + +Первый дифф v4 (`DIFF-canon-v4.csv`) был собран **от канона** `APPLY-PLAN.csv`. +**Птахи в нём не было** — потому что в каноне у неё уже стояло верное `'Красивые, богатые'`. +Но прогон v3 записал испорченное значение **на диск**, и диск/БД ушли вперёд канона. + +**Факт из базы** (`~/Downloads/navidrome-db/navidrome-after-v3.db`): +``` +album_id=6JB3AabN89EM4u4bbYh3AG 'Красивые, богатые' 7 файлов +album_id=40q9QYDnHNgfQhQTtjZE1c 'Птаха - Красивые, богатые (2009)' 1 файл ← ЛИШНИЙ +``` +Лишний файл — `Птаха - Красивые, богатые! (feat. Сидр).mp3`. + +### 🔑 ПРАВИЛО (обязательно к исполнению) + +**Дифф строить ОТ БД (`media_file` в `navidrome.db`), а НЕ от канона.** + +| Источник | Роль | +|---|---| +| `media_file` из `navidrome.db` | **ИСТИНА** — что реально в тегах сейчас | +| `APPLY-PLAN.csv` (канон) | эталон **ручных решений** Alex, а не текущего состояния | + +Причина: любой прогон меняет диск, и канон мгновенно устаревает. Дифф от канона +**слеп к дефектам, уже записанным в теги**. + +**Сборщик:** `~/Downloads/music-tags/build_diff_from_db.py` +**Выход:** `DIFF-db-to-fix.csv` (91 строка) + `apply-plan-v5.csv` (7861) + +--- + +## 🔴 ЛОЖНОЕ РАСХОЖДЕНИЕ ПО ЖАНРУ — `media_file.genre` ВСЕГДА ПУСТ + +При сборке дифа от БД всплыло «`genre`: 7861 изменений» — **все файлы**. +Это **ложная тревога**, а не дефект. + +``` +media_file.genre = '' (пусто у ВСЕХ 7861 — колонка УСТАРЕВШАЯ) +media_file.tags (jsonb) = {"genre":[{"id":"...","value":"Бардовская песня"}]} ← ЖАНР ЗДЕСЬ +tag / media_file_tags = 438 значений, 7861 связок +album_tags = 1010 связок +``` + +🔑 **Жанр в этой версии Navidrome читать ТОЛЬКО из `tags` (jsonb)** — или из +таблиц `tag`/`media_file_tags`/`album_tags`. Колонка `media_file.genre` — рудимент, +всегда пуста. Иначе получишь ложное «7861 расхождение». + +```python +def genre_from_jsonb(tags): + d = json.loads(tags or "{}") + g = d.get("genre") + return g[0]["value"] if isinstance(g, list) and g else "" +``` + +**Итог после фикса:** `genre` в дифе = **0**, изменения: `albumartist 157 · artist 154 · album 60 · track 58` = **429**. + +--- + +## 🔴 АЛЬБОМЫ-ОДИНОЧКИ — 215 альбомов из 1 трека (27 сен, сессия ~35) + +**Alex: «что такое Агата Кристи/Симпатии: Чайф - Бесса мэ? я так понимаю трибьют, +но что за Симпатии и почему один трек альбома?»** + +**Ответ:** `Симпатии` — **трибьют-сборник** Агаты Кристи (2000). Чайф (Владимир Шахрин) +записал туда свою версию «Бесса мэ». Файл: +``` +!Rock&Folk/Агата Кристи/9999 - Разное/Кавер-версии и пародии/ + 2000 (Чайф-Симпатии) - Владимир Шахрин - Бесса мэ.mp3 +title='Бесса мэ' album='Симпатии' artist='Чайф' albumartist='Агата Кристи' trk=0 +``` + +**Причина одиночества:** папка `Кавер-версии и пародии` содержит **24 файла**, и +каждый получил `album` = **имя своего сборника-источника** вместо имени папки: +``` +album='Симпатии' ← Чайф (1 файл) +album='Шипы и розы' ← Стрелки (1) +album='Некрофилия' ← Братья Торч (1) +album='Песок' / 'Slot 1' / 'Back From USSR' / ... (по 1) +album='Кавер-версии и пародии' ← 9 файлов — ПРАВИЛЬНО +``` +**15 из 24** залетели каждый в свой альбом-из-одного-трека. + +**Масштаб класса** (запрос по БД): +``` +альбомов из 1 трека, где album != имя папки: 215 +``` +Примеры: +``` +album='2000 и 1 ночь' папка='Ария, Кипелов' !Rock&Folk/Ария, Кипелов/Ария - Потерянный рай.mp3 +album='10 лет - полёт...' папка='!rock_o_pop' !Rock&Folk/!rock_o_pop/Пилот - god@pisem.net.mp3 +album='2000 и Одна Ночь' папка='!Retro' !Retro/Ария - Потерянный Рай.mp3 +``` + +**Правильная цель для `Кавер-версии и пародии`:** `album='Кавер-версии и пародии'` +для всех 24 файлов; `artist` сохранить реальных исполнителей (Чайф, Би-2, Слот, Стрелки). + +⏳ **НЕ СДЕЛАНО:** разбор всех 215 — разделить на «мусор (чужой сборник)» / +«законный сингл». Требует решения Alex. + +--- + ## 🔎 ВЕРИФИКАЦИЯ v3 ПЕРЕД DRY-RUN (2026-09-27) Проверено на Mac, три скрипта, все числа — из самих файлов. @@ -722,7 +822,13 @@ Dry-run считает ФАЙЛЫ: album 28 · albumartist 6 · genre 4359 (в | Дифф: genre-группы | 211 ТРЕК-строк дублируют `new_genre`; 7 одиночек не добраны | **Alex снял вопрос:** пишем полный набор, дифф не пересобираем | | `track` −4 от плана | 4 файла `changed=yes`, расхождений нет → скрипт не писал | **Не дефект**, ожидаемо | | **Navidrome-кэш не чищен** | Теги на диске новые, БД Navidrome — старая (музыку не сканировали) | ✅ **ЗАКРЫТ 27 сен:** БД пересоздана, скан 4m13s, импортировано 7861 | -| 🔴 **Бага генератора плана v3** | `is_junk_album` сравнивает `album==title` / `album==имя-файла` на `new_*` вместо `cur_*` → верный альбом затирается именем папки. **Задето 20 файлов** (Птаха — симптом №1) | ⏳ **ОТКРЫТ:** фикс — сравнивать с `cur_title`; пересобрать план, сверить 20 файлов с каноном, прогнать. См. § КОРЕНЬ ДЕФЕКТА ПТАХИ | +| 🔴 **Бага генератора плана v3** | `is_junk_album` сравнивает `album==title` / `album==имя-файла` на `new_*` вместо `cur_*` → верный альбом затирается именем папки. **Задето 20 файлов** (Птаха — симптом №1) | ✅ **ОБОЙДЕНО:** дифф v5 строится от БД, а не пересборкой генератора — все 20 файлов лечатся автоматически | +| 🔴 **Дифф был собран от КАНОНА** | Птахи в дифе не было, хотя дефект в базе есть (диск ушёл вперёд канона) | ✅ **ЗАКРЫТ 27 сен:** пересобран от БД → `DIFF-db-to-fix.csv` | +| 🔴 **Ложное расхождение по `genre`** | `media_file.genre` пуст у всех 7861 → дифф показывал 7861 «правок» | ✅ **ЗАКРЫТ 27 сен:** жанр читается из `tags` jsonb, `genre` в дифе = 0 | +| 🔴 **Птаха разбит на 2 альбома** | `'Красивые, богатые'` (7 файлов) + `'Птаха - Красивые, богатые (2009)'` (1) | ⏳ **В дифе v5** (`album` 60-я правка), ждёт прогона | +| 🔴 **Альбомы-одиночки** | **215** альбомов из 1 трека, где `album != имя папки` (класс Бумера / Симпатии) | ⏳ **ОТКРЫТ:** нужен разбор «мусор vs законный сингл». См. § АЛЬБОМЫ-ОДИНОЧКИ | +| **Трофим: 8 альбомов, 3 копии** | 3 физические копии → 8 альбомов, 38 имён задвоено | ✅ **ЗАКРЫТ:** 50 дублей удалены, план v5 правит альбомы/исполнителя | +| **Тема дороги** | `title='AudioTrack 17'`, `artist='Бумер'`, один в альбоме | ⏳ **В дифе v5:** `album='Ленинград'`, `artist='Ленинград'` | --- @@ -1140,6 +1246,40 @@ check_album_applied*.py / verify_306*.py вспомогательные про префиксом **только 3** — настоящие префиксы (Трофим `01..04 Аристократия`); остальные 12 — цифра часть названия (`10 лет - полёт нормальный`, `1-0 в пользу девочек`, `46-й альбом`). Правило сломает больше, чем починит → правка адресная, списком. +31. 🔑 **ДИФФ СТРОИТЬ ОТ БД, А НЕ ОТ КАНОНА.** Канон `APPLY-PLAN.csv` = эталон РУЧНЫХ РЕШЕНИЙ, + а не текущего состояния. Любой прогон пишет на диск → канон устаревает мгновенно. + Дифф от канона **слеп к дефектам, уже записанным в теги**: Птахи в нём не было, хотя + в базе он разбит на 2 альбома. Alex: «в смысле правильно? в базе блядь у него что?!». + **Источник истины — `media_file` из `navidrome.db`.** Сборщик: `build_diff_from_db.py`. +32. 🔴 **`media_file.genre` ПУСТ У ВСЕХ — не читать из этой колонки.** В этой версии Navidrome + жанр лежит в `media_file.tags` (jsonb) + таблицах `tag`/`media_file_tags`/`album_tags`. + Колонка — рудимент. Иначе получишь ложное «7861 расхождение по genre» на ровном месте. + Колонка номера трека — `track_number` (INT), не `track`; приводить `str(v)`. +33. 🔑 **Формат дифа: ПАПКА-строки + ТРЕК-строки, схлопывание по `(folder, field, new)`.** + Alex проверяет дифф глазами и **дважды вернул его на переделку**: + «почему диф опять не сортирован и не схлопнут?! ты опять для него заново скрипт писал?!». + Требования: **сортировка по АЛФАВИТУ `field`** (`album → albumartist → artist → genre → + track`) — ключ в КОДЕ, не порядок объявления списка; **схлопывание** — одна ПАПКА-строка + на `(folder, field, new)`, ТРЕК-строка только если цель уникальна в папке; + `old` = `` при разных старых, конкретное при одном, пусто при всех пустых. + `track` схлопнуть НЕЛЬЗЯ by design (у каждого файла свой номер). +34. 🔴 **Новый скрипт для той же задачи = регрессия.** Дифф уже собирался канонным сборщиком; + я написал новый и потерял сортировку+схлопывание. **Сначала искать существующий сборщик** + (`build_canon_diff_v3.py`, `build_diff_canon_v4.py`), копировать его формат, а не изобретать. +35. 🔴 **Артефакты писать в ПАПКУ ПРОЕКТА, не в `/tmp`.** Alex артефакт в `/tmp` не находит: + «какого хуя он в тмп». Все csv/диффы → `~/Downloads/music-tags/`. +36. 🔴 **Одна задача = один вопрос максимально сжато.** За сессию ~35 накопилось 3 круга + «а сколько?», «а какие копии оставлять?», «а это решение от меня?» → мат. + Alex: «Я тебя хотел как раз спросить какое решение то этих проблем?» — то есть **от агента + ждут РЕКОМЕНДАЦИИ с обоснованием**, а не перечня вопросов. Давать вариант + причину, + тогда решение приходит за один круг. +37. 🔴 **«Стоп» ОЗНАЧАЕТ НЕМЕДЛЕННУЮ ОСТАНОВКУ ВСЕГО.** После «стоп» я запускал команды ещё + 2 раза (seek на `/music`, скан диска) → «Ты чё делаешь я не понял?». После стопа — + только отчёт о состоянии, ноль tool-вызовов к NAS без нового слова. +38. 🔴 **НЕ ЛЕЗТЬ НА ДИСК, ЕСЛИ ДАННЫЕ ЕСТЬ В БД.** Скан 7861 файла через контейнер идёт минуты + и рвётся прерыванием. Alex: «Чё блядь происходит? Ты из базы не можешь пути выгрузить и + сравнить что-ли??». **Сначала БД** (`sqlite3` локально на копии `navidrome.db`), диск — только + там, где в БД данных нет (проверка реального тега после прогона). ---