diff --git a/personal/plans/music-artist-albumartist-repair.md b/personal/plans/music-artist-albumartist-repair.md index 4c675f07..4d1132a1 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: session29quater-ndignore-FIXED-live-music-7861-generator-FOUND-build_fix_plan2-NON_ARTIST-EXACT-MATCH-defect-AWAITING-ALEX +status: session29sexies-GENERATOR-FIXED-root-rule-643-rows tags: - music - metadata @@ -21,6 +21,238 @@ related: - '[[family/how-to/navidrome-truenas-android]]' # Music `artist` + `albumartist` Repair — правила Alex и ход работ +## 🔴 СЕССИЯ 29-СЕКСИ (2026-09-27) — ГЕНЕРАТОР ПОФИКШЕН, `apply-plan-v2.csv` СОБРАН, ДИФ К БАЗЕ СДЕЛАН + +> **ТРИГГЕР (последовательность):** «**нет. зайчика и "песни" мы руками не ловим. там было правило +> про корень папки исполнителя. оно должно было песни Никулина поместить в альбом Юрий Никулин. +> почему этого не произошло а остались "Песни"?**» → «**да. и готовь новый apply-plan - не замещая +> старый. и новый дифф от состояния базы к новому apply-plan в том же формате диффа что и тогда.**» +> → «**не забыть был еще apply который фиксил скобки в альбомах, его надо накатить на изначальный +> тоже**» → «**дифф сделал?**» → «**все готово? проверять?**» + +### 🔑 ГЛАВНЫЙ ВЫВОД: КОСЯК БЫЛ НЕ В `NON_ARTIST`, А В ОБРЕЗКЕ КАТЕГОРИИ + +Alex **отверг** мою гипотезу («править `NON_ARTIST` / список слов») и указал точно: +**должно работать правило корня папки исполнителя**. Он был прав — дефект оказался +структурным, в `derive_from_path()`, а не в списках слов. + +**Механика провала (доказано `/tmp/prove_root_rule.py` на живом импорте модуля):** + +```python +# build_fix_plan2.py, path_parts() — строка 454 +if dirs and norm(dirs[0]) in CATEGORY_DIRS: # '!retro' ∈ CATEGORY_DIRS + dirs = dirs[1:] # ← срезается В path_parts, до derive_from_path +``` + +Для `!Retro/Юрий Никулин/Если бы я был султан.mp3`: + +| Шаг | Значение | +|---|---| +| `path_parts(rel)` | `['!Retro', 'Юрий Никулин']` | +| после среза категории | **`['Юрий Никулин']` — ОДИН элемент** | +| ветка `if len(dirs) >= 2:` (стр. 497) — **та, что ставит `album = dirs[-1]`** | ❌ **НЕ выполняется** | +| падение в `len(dirs) == 1` (стр. 508) → `return only, ""` | **album ВСЕГДА пустой** | +| далее в main: `if not new_al and d_album:` | ❌ `new_al = 'Песни'` не пуст → правило не срабатывает | + +**🔑 КОРЕНЬ: правило «файл прямо в корне артиста → album = имя папки» в генераторе +ОТСУТСТВОВАЛО ВООБЩЕ.** Было только «если album ПУСТОЙ — взять из папки». +А `repair('Песни')` = `'Песни'` (не пусто) → мусор оставался навсегда. + +**Это одна и та же причина для всех шести случаев** (все в категорийных папках): + +| Файл | `dirs` после среза | album было | album стало | +|---|---|---|---| +| `!Retro/Юрий Никулин/…` | `['Юрий Никулин']` | `Песни` | **`Юрий Никулин`** | +| `!Retro/Тамара Миансарова/…` | `['Тамара Миансарова']` | `Храбрый Зайчик. Песни для детей` | **`Тамара Миансарова`** | +| `!Pop, Club, Любэ/Руки вверх/…` | `['Руки вверх']` | `Песни` | **`Руки вверх`** | +| `!Pop, Club, Любэ/Little Big/…` | `['Little Big']` | `Песни` | **`Little Big`** | +| `!Rock&Folk/Браво/…` | `['Браво']` | `[muzmo.ru]` | **`Браво`** | +| `!Rock&Folk/Агата Кристи/…` | `['Агата Кристи']` | — | **`Агата Кристи`** | + +Контроль (НЕ сломан): `!Rock&Folk/Любэ/1991 - Атас/…` → `len=2` → `album='1991 - Атас'` ✅ + +### ✅ ФИКС ГЕНЕРАТОРА (3 правки в `build_fix_plan2.py`) + +**Бэкап ДО правки:** `build_fix_plan2.py.bak-before-rootfix-20260927-203300` (md5 исходника `c1ae3fcb9820326be1e83f27b4e24bb0`). + +**Правка 1 — `path_parts()` отдаёт флаг среза категории:** +```python +def path_parts(rel, with_flag=False): + ... + stripped = False + if dirs and norm(dirs[0]) in CATEGORY_DIRS: + dirs = dirs[1:] + stripped = True # ← НОВОЕ + if with_flag: + return dirs, stripped # ← НОВОЕ + return dirs +``` + +**Правка 2 — `derive_from_path()` возвращает `(only, only)` когда категория срезана:** +```python +dirs, cat_stripped = path_parts(rel, with_flag=True) +stripped = cat_stripped +... +if stripped: + return only, only # ← папка = И артист, И альбом +``` + +**Правка 3 — правило в блоке album (главное):** +```python +is_artist_root = bool(d_album) and d_artist == d_album +if is_artist_root: + if new_al != d_album: + new_al = d_album + src.append("album<-artist-root") # ← НОВЫЙ source-токен +elif not new_al and d_album: + new_al = d_album +``` +🔑 **Правило ПЕРЕОПРЕДЕЛЯЕТ мусорный `album`, а не только заполняет пустой** — прежнее +условие `if not new_al` было слишком узким. + +### 🔴 ПРОЦЕССНЫЙ ПРОВАЛ (2-й подряд): ЖДАЛ РАЗРЕШЕНИЯ НА ФИКС ПОСЛЕ НАЗВАННОГО ДЕФЕКТА + +Выдал анализ + вопрос «Писать фикс?». Alex ответил одним словом **«да»** (в предыдущем блоке +это уже было «какой нахуй план б» / «делай»). **Дефект назван Alex + указано, где он → фиксить +и генерировать в том же ходе.** + +Также **сам себя обманул дважды при чтении результата**: смотрел `awk -F','` по полям, но путь +в кавычках → колонки съехали → выдал «Little Big и Руки вверх не починены», хотя они были +**починены** (`album<-artist-root` в `source`). **Читать CSV парсером, не `awk` по запятым** — +тот же питфолл, что `grep -c ',yes$'` в сессии 19. + +### ✅ АРТЕФАКТ 1: `apply-plan-v2.csv` (СТАРЫЙ ПЛАН НЕ ТРОНУТ) + +**Вход** — `APPLY-PLAN.csv`, его `new_*` взяты как текущее состояние (они применены прогоном +7861/7861). Конвертер: `/tmp/make_inventory_from_plan.py` → `inventory-from-applyplan.csv` +(7861 строк, формат `tag_inventory2.py`: `path,title,artist,album,album_artist,genre,date,track, +status_title,status_artist,verdict,source`; `verdict='ok'` всегда — теги на диске валидны). + +**Генерация:** +```bash +python3 build_fix_plan2.py ~/Downloads/music-tags/inventory-from-applyplan.csv /tmp/apply-plan-v2.csv "" +``` + +| Метрика | Значение | +|---|---:| +| Строк | **7861** | +| `fix` | **5079** | +| `no_change` | 2782 | +| `album<-artist-root` (правило корня) | **643** | +| `album` изменился всего | **671** | + +**🔑 `new_albumartist` = `''` У ВСЕХ 7861 СТРОК** — генератор это поле не заполняет +(в `PAIRS`/выходных колонках его нет: `path,current_verdict,cur_title,cur_artist,cur_album, +new_title,new_artist,new_album,new_track,action,source`). **Это исход эпохи 38-й волны: +`albumartist`/`genre` в плане появились правками ПОСЛЕ генерации.** + +### ✅ АРТЕФАКТ 2: `DIFF-base-vs-applyplan-v2.csv` (канонный формат) + +**Скрипт:** `/tmp/diff_base_vs_v2.py` · **Формат:** `scope,folder,path,field,old_38,new_apply_plan` +(как `diff_new_vs_38_v2.py`) · `old_38` = что фактически в БД **сейчас**. + +| Метрика | Значение | +|---|---:| +| Файлов БД (живая музыка) | **7861** | +| Строк в дифе | **1270** (907 ПАПКА + 363 ТРЕК) | +| Покрыто схлопыванием | 11977 треков | +| Контроль | `11977 + 363 = 12340` = всего правок; `assert` прошёл ✅ | +| Файлов плана, которых нет в БД | **0** | + +**Расхождения база vs план v2 (трек-уровень):** + +| Поле | Файлов | +|---|---:| +| `albumartist` | **7861 (ВСЕ)** | +| `track` | 3505 | +| `album` | 687 | +| `artist` | 189 | +| `title` | 98 | +| ✅ `genre` | **0** | + +### 🔴🔴 ДЕФЕКТ ДИФА №1 (ОТКРЫТ): `albumartist` 7861 — `old_38` ПУСТОЕ У ВСЕХ + +`old_38 = ''` во всех записях → либо читаю не ту колонку БД, либо прогон поле не писал. +**Причина-кандидат (сильная):** `build_fix_plan2.py` **не заполняет `new_albumartist` вообще** +(его нет в выходных колонках) → в `apply-plan-v2.csv` поле пустое у всех 7861 → +сравнение «`''` vs `album_artist` из БД» даёт расхождение на каждом файле. +**Это артефакт генератора v2, а не факт о базе. Не проверено до конца.** + +### 🔴🔴 ДЕФЕКТ ДИФА №2 (ОТКРЫТ): РЕГРЕССИЯ `Sbornik_Novij_Rok` — ВЕРНУЛСЯ ТОТ ЖЕ КОСЯК + +`DIFF-base-vs-applyplan-v2.csv` содержит: +``` +ПАПКА,!A Not sorted/Sbornik_Novij_Rok,,album,<2 разных>,Sbornik_Novij_Rok +``` +**Правильное значение `Сборник нового рока` снова затёрто именем папки** — ровно то, за что +Alex ругал в сессии 29-бис («**Sbornik_Novij_Rok — да ты охуел блядь!!!**»). + +Тот же класс: `Df1`, `Dj Tiesto`, `5'nizza` (это **реальный артист**, проверил БД: +`album_artist='5'nizza'`), `Морские`. + +**Причина:** мой `album<-artist-root` срабатывает на **любую** папку, где `d_artist == d_album`. +Папка-сборник `Sbornik_Novij_Rok` проходит, потому что: +- `sbornik_novij_rok` **нет** в `CATEGORY_DIRS` (там `backup`, но не `sbornik`) +- `sbornik_novij_rok` **нет** в `NON_ARTIST` (там `сборник`, но не `sbornik_novij_rok`) +- → `is_release_folder` = false → отдаётся `(only, only)` → правило перебивает верный album + +**🔑 ПРЕДЛОЖЕННЫЙ КРИТЕРИЙ (не реализован, ждёт Alex): сверять с тегом `artist`, а не с именем +папки.** У `Sbornik_Novij_Rok` тег `artist='Фактор Страха'` ≠ имя папки → **это НЕ папка +артиста, правило применять нельзя**. У Никулина `artist='Юрий Никулин'` == папка → применять. +Три условия перед `return only, only`: +1. папка не в `CATEGORY_DIRS` +2. папка не содержит слов-сборников (`sbornik`, `сборник`, `сборка`, `сборники`) +3. **тег `artist` совпадает с именем папки** (главное условие) + +### 🔴 ДЕФЕКТ ДИФА №3 (ОТКРЫТ): `track` 3505 — БАЗА ХРАНИТ `0`, ПЛАН ПУСТО + +`track_number = 0` в Navidrome = «номер не проставлен». План пишет `''`. +Скорее всего **представление, а не реальное расхождение** — требует нормализации при сравнении +(`0` и `''` считать равными). **Не исправлено.** + +### ⏳ НЕЗАКРЫТОЕ УКАЗАНИЕ ALEX (записано дословно, чтобы не потерять) + +> «**не забыть был еще apply который фиксил скобки в альбомах, его надо накатить на изначальный тоже**» + +Это прогон, от которого остались `ALBUM-FIX-306.csv` (306 путей) и `/tmp/make_plan_306.py`. +**Порядок наката не выбран** (обсуждались A: скобки на `APPLY-PLAN.csv` → перегенерить v2; +B: скобки прямо на `apply-plan-v2.csv`). **Не сделано.** + +### 🛠 СКРИПТЫ И АРТЕФАКТЫ ЭТОГО БЛОКА + +``` +~/Developer/personal-os/tools/music-tags/build_fix_plan2.py ⭐ ГЕНЕРАТОР — ПОФИКШЕН (3 правки) +~/Developer/personal-os/tools/music-tags/build_fix_plan2.py.bak-before-rootfix-20260927-203300 +/tmp/prove_root_rule.py ⭐ ДОКАЗАТЕЛЬСТВО дефекта: импорт модуля + 7 кейсов до/после +/tmp/why_littlebig.py 🔍 проверка is_artist_root по 4 папкам +/tmp/find_junk_miss.py 🔍 5 значений / 44 файла, не попавших в NON_ARTIST +/tmp/make_inventory_from_plan.py ⭐ вход для генератора: APPLY-PLAN new_* → inventory +/tmp/diff_base_vs_v2.py ⭐ канонный диф: БД (missing=0, без backup) vs план v2 + +/tmp/apply-plan-v2.csv ⭐ 7861 строк (fix 5079), старый НЕ тронут +~/Downloads/music-tags/inventory-from-applyplan.csv ⭐ 7861, вход генератора +~/Downloads/music-tags/DIFF-base-vs-applyplan-v2.csv ⭐ 1270 строк (907 ПАПКА + 363 ТРЕК) +``` + +### ✅ СТАТУС БЛОКА + +✅ **Сделано:** дефект генератора найден **по указанию Alex** (не по моей гипотезе о NON_ARTIST) +и исправлен; правило корня заработало (**643 строки**, 671 album-правок); `apply-plan-v2.csv` +сгенерирован (**старый `APPLY-PLAN.csv` не тронут**); канонный диф к базе собран и **проверен +на 4 целевых случаях** (Никулин, Миансарова, Руки вверх, Little Big — все ✅). + +🔴 **Открыто (4):** +1. **Регрессия `Sbornik_Novij_Rok`** — правило корня перебивает верный album; нужен критерий + сверки с тегом `artist`. **Это блокер: диф нельзя показывать Alex в текущем виде.** +2. **`albumartist` 7861** — генератор v2 не заполняет поле; надо решить, откуда брать значение. +3. **`track` 3505** — нормализовать `0` ↔ `''` при сравнении. +4. **Скобочный apply** — накатать на изначальный (указание Alex, порядок не выбран). + +⚠️ **НЕ ГОТОВО К ПРОВЕРКЕ.** На вопрос Alex «**все готово? проверять?**» дан честный ответ: +диф сделан, но **содержит `Sbornik_Novij_Rok`** — показывать нельзя. + +--- + ## 🔴 СЕССИЯ 29 (2026-09-27, ночь) — НОВЫЙ ДИФ СО СХЛОПЫВАНИЕМ: `DIFF-new-vs-38-v6.csv` > **ТРИГГЕР:** «**чини блядина дифы**» → «**какой нахуй план б**» (я выкатил меню вопросов diff --git a/personal/tech/music-tags-repair-truenas.md b/personal/tech/music-tags-repair-truenas.md index 77951a9f..132508a6 100755 --- a/personal/tech/music-tags-repair-truenas.md +++ b/personal/tech/music-tags-repair-truenas.md @@ -23,7 +23,33 @@ related: # Music Tags Repair — TrueNAS (7906 → 7905 файлов) -> ## 🔴 АКТУАЛЬНОЕ СОСТОЯНИЕ (2026-09-27, сессия 28) — ЧИТАЙ ЭТО ПЕРВЫМ +> ## 🔴 АКТУАЛЬНОЕ СОСТОЯНИЕ (2026-09-27, сессия 29-секси) — ЧИТАЙ ЭТО ПЕРВЫМ +> +> 🔴 **ГЕНЕРАТОР ПЛАНА НАЙДЕН И ПОФИКШЕН: `~/Developer/personal-os/tools/music-tags/build_fix_plan2.py`** +> Вызов: `python3 build_fix_plan2.py INVENTORY.csv OUT.csv [HOST_ROOT]`. Ищется **только через +> Zulip DB** — локальный `grep` даёт 63 файла-потребителя и **ни одного генератора**. +> +> 🔑 **КОРЕНЬ ДЕФЕКТА «мусор в album не правится»** (`Песни` у Никулина, `Храбрый Зайчик…` +> у Миансарова) — **не в списках слов**, а в `derive_from_path()`: после среза категории +> (`!Retro`, `!Pop, Club, Любэ` — они в `CATEGORY_DIRS`) остаётся **ОДИН** компонент пути, +> ветка `if len(dirs) >= 2:` (та, что ставит `album`) **не выполняется**, падает в +> `len(dirs) == 1` → `return only, ""` → **album ВСЕГДА пустой**. Правило «файл в корне артиста +> → album = имя папки» в генераторе **отсутствовало вообще**. +> **Фикс (3 правки):** `path_parts(rel, with_flag=True)` отдаёт флаг среза; `derive_from_path` +> возвращает `(only, only)` при срезе; в блоке album добавлено `is_artist_root = bool(d_album) +> and d_artist == d_album` → `album<-artist-root`. Верификация: `/tmp/prove_root_rule.py`. +> +> ✅ **Сделано в сессии 29-секси:** `apply-plan-v2.csv` (**7861**, fix 5079, `album<-artist-root` +> **643**) — **старый `APPLY-PLAN.csv` НЕ тронут**; канонный диф к базе +> `DIFF-base-vs-applyplan-v2.csv` (**1270** = 907 ПАПКА + 363 ТРЕК). +> +> 🔴 **ОТКРЫТО (блокер):** `Sbornik_Novij_Rok` **регрессировал** — правило корня перебивает +> верный `Сборник нового рока` (тот же косяк, за который Alex ругал в 29-бис). Нужен критерий +> **сверки с тегом `artist`**, а не с именем папки. Плюс `albumartist` 7861 (`old_38=''` у всех — +> генератор это поле не заполняет) и `track` 3505 (`0` ↔ `''` нормализовать). +> **Диф НЕ готов к показу Alex.** +> +> Подробно — [[personal/plans/music-artist-albumartist-repair]] § СЕССИЯ 29-СЕКСИ. > > ⚠️ **НОВОЕ (сессия 28): детектор «файл в корне папки артиста» сравнивал на РАВЕНСТВО > и терял папки-огрызки.** `'Руки'` ≠ `'Руки Вверх'` → `!Retro/Руки` (8 файлов) и @@ -32,17 +58,17 @@ related: > (`RULE-CHECK.csv` → `RULE-CHECK-v2.csv`, 60 → 169 папок). Подробно — > [[personal/plans/music-artist-albumartist-repair]] § СЕССИЯ 28. > -> 🔑 **МЕТОД-УРОК (повторяется 3-й раз):** при сопоставлении имени папки с именем артиста +> 🔑 **МЕТОД-УРОК (повторяется 5-й раз):** при сопоставлении имени папки с именем артиста > **НИКОГДА не сравнивать на равенство** — только `norm()` + вхождение в обе стороны. > Иначе теряются ровно те случаи, ради которых правило писалось (папка-огрызок, -> `Забугр`/`Забугор`, разный регистр, `Руки`/`Руки Вверх`). +> `Забугр`/`Забугор`, разный регистр, `Руки`/`Руки Вверх`, `norm(new_al) in NON_ARTIST`). > > **Этот файл — исторический лог волны 38 (завершена). Актуальная работа идёт здесь:** > > | Документ | О чём | > |---|---| > | **[[personal/plans/tagwriter-container]]** | ⭐ скрипт-писатель `apply_tags_v2.py` (566 стр): логика полей, ID3v1-снос, тесты, 18 питфоллов, запуск | -> | **[[personal/plans/music-artist-albumartist-repair]]** | ⭐ **правила формирования плана, `APPLY-PLAN.csv`, диффы, сессии 1-28** | +> | **[[personal/plans/music-artist-albumartist-repair]]** | ⭐ **правила формирования плана, `APPLY-PLAN.csv`, диффы, сессии 1-29** | > | **[[personal/tech/music-broken-toload]]** | 29 битых/DRM файлов на перезагрузку | > > **Состояние:** план `APPLY-PLAN.csv` доведён до **0 пустых полей**. Диффы **ПЕРЕСОБРАНЫ