[2026-09-27] eagle: personal/plans/music-artist-albumartist-repair.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-27 23:41:33 +06:00
1 parent 891942e1d6
commit 2332ce79fb
1 file changed
+170 -30
+170 -30
View File
@@ -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` = `<N разных>` при разных старых, конкретное при одном, пусто при всех пустых.
`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`), диск — только
там, где в БД данных нет (проверка реального тега после прогона).
---