diff --git a/personal/tech/music-tags-repair-truenas.md b/personal/tech/music-tags-repair-truenas.md index 211a5327..2c6ef9c2 100755 --- a/personal/tech/music-tags-repair-truenas.md +++ b/personal/tech/music-tags-repair-truenas.md @@ -23,8 +23,103 @@ related: # Music Tags Repair — TrueNAS (7906 → 7905 файлов) +> ## 🔴🔴 КАК СТРОИЛСЯ `APPLY-PLAN.csv` И ДИФФ — ЧИТАТЬ ДО ЛЮБОЙ ПЕРЕСБОРКИ +> +> **`APPLY-PLAN.csv` собран НЕ `build_fix_plan2.py`.** Это главная путаница. +> Настоящая цепочка (восстановлено из Zulip DB 27 сен): +> ``` +> папка сборки: /Users/admin/milestones/music-csv-2026-09-26/ +> tag-inventory3.csv ← свежий сбор тегов с диска (6432 номера треков) +> APPLY-PLAN-FINAL.csv 12:18 — 6 полей, валидация чистая, НО new_track сырой +> APPLY-PLAN-FINAL2.csv 12:26 — ПРАВДА, мусор в track вычищен +> APPLY-PLAN.csv ← канон, = FINAL2 +> колонки: path, folder, class, cur_title/artist/albumartist/album/genre/track, +> new_title/artist/albumartist/album/genre/track, changed +> ``` +> 🔴 **FINAL и FINAL2 различимы только по `new_track` (142 строки) и `changed` (20).** +> Схема колонок в них **идентична** — сравнивать надо значения, не заголовки. +> +> 🔴 **`build_fix_plan2.py` — это ДРУГОЙ скрипт, для волны 38.** +> Он НЕ пишет `albumartist` вообще (0 упоминаний) и НЕ собирал канон. +> Подробности ниже — «ДЕФЕКТЫ build_fix_plan2.py». +> +> **Дифф к канону:** `DIFF-base-vs-applyplan-v3.csv` (945 строк) — +> собирался `/tmp/build_canon_diff_v3.py`, **не** `build_fix_plan2.py`. +> +> 🔑 **ПРАВИЛО ХРАНЕНИЯ ЗНАНИЙ:** любая пересборка плана/диффа обязана +> начинаться с чтения этого блока. Три раза подряд я писал новый сборщик +> «с нуля», теряя сортировку, схлопывание и правило `album`. +> **Сборщик живёт в папке проекта (`~/Downloads/music-tags/`), НЕ в `/tmp`.** + +> ## 🔴 ДЕФЕКТЫ `build_fix_plan2.py` (найдены 27 сен, не исправлены в боевом файле) +> +> | # | Дефект | Где | Последствие | +> |---|---|---|---| +> | 1 | `albumartist` НЕ пишется вообще | `plan.append([...])` стр. 795-800 | все сборники без albumartist | +> | 2 | `is_release_folder`: `n.startswith("!") → True` | стр. 569-570 | папки `!rock_o_pop`, `!Retro`, `!Рэп` объявлены релизом → `derive_from_path` даёт `('','')` → правило корня выключено | +> | 3 | файл ПРЯМО в категории → `dirs=[]` | стр. 504-505 | `!Retro/x.mp3` не получает `album='Retro'` | +> | 4 | `9999 - Разное` уходит в `artist` | стр. 523-530 | `artist='9999 - Разное'` вместо реального исполнителя | +> +> 🔑 **Рабочая копия для правок: `music-tags/tagwriter` (указание Alex).** +> Бэкапы: `build_fix_plan2.py.bak-before-rootfix-20260927-203300`. +> +> **ЦЕЛЕВАЯ МОДЕЛЬ (подтверждена Alex):** +> - `albumartist` = **папка-владелец** (по правилу корня): `Агата Кристи`, `Трофим` +> - `artist` = **реальный исполнитель трека** из тега: `Чайф`, `Би-2`, `Слот` +> - сборник ≠ схлопывание исполнителя: в `9999 - Разное` артистов СОХРАНЯТЬ +> - `album` = имя папки без цифрового префикса (`01 Аристократия помойки` → `Аристократия помойки`) + +> ## 🔴 ИСТОЧНИК ИСТИНЫ ПРИ ПЕРЕСБОРКЕ = БАЗА NAVidrome, НЕ КАНОН +> +> Канон `APPLY-PLAN.csv` описывает состояние **ДО** боевого прогона 27 сен. +> После прогона диск ушёл вперёд: теги на диске ≠ канон. +> **Дифф, собранный от канона, НЕ ВИДИТ уже записанных дефектов** +> (пример: Птаха — в каноне верно, в БД `album='Птаха - Красивые, богатые (2009)'`). +> +> ✅ **Правильно: дифф от БД.** БД читать так: +> ``` +> ~/Downloads/navidrome-db/navidrome-after-v3.db ← локальная копия +> sqlite3 через python3 (НЕ внутри контейнера — там нет sqlite3) +> 🔴 жанр НЕ в media_file.genre (всегда пусто!) — он в media_file.tags (jsonb): +> {"genre":[{"id":"...","value":"Бардовская песня"}]} +> либо в таблицах tag / media_file_tags / album_tags +> ``` +> 🔴 **Обязательный фильтр:** `WHERE path NOT LIKE 'backup%'`. +> 🔴 Поля: `track_number` (НЕ `track`), `album_artist` (НЕ `albumartist`). +> 🔴 `track_number = 0` при пустом TRCK → все треки слипаются в один. +> Navidrome показывает `AudioTrack N` когда `TIT2` пуст. + +> ## 📍 ФИЗИЧЕСКИЕ ДУБЛИ (удалено 27 сен) +> +> Трофим лежал в 3 местах; копии удалены, эталон `!Рэп/Трофим` (157 файлов): +> ``` +> !Pop, Club, Любэ/!Radio/z Старое/disk/3 SHANSON/Трофим 25 → УДАЛЕНО +> !Pop, Club, Любэ/!Radio/z Старое/disk_v2/3 SHANSON/Трофим 25 → УДАЛЕНО +> !Рэп/Трофим 157 → ЭТАЛОН +> ``` +> Файлы в сборниках (`disk/`, `disk_v2/`) **удалять нельзя** — указание Alex: +> «они корректно живут в сборниках». Удаляем ТОЛЬКО дубли альбомов. + > ## 🔴 АКТУАЛЬНОЕ СОСТОЯНИЕ (2026-09-27, сессия ~30) — ЧИТАЙ ЭТО ПЕРВЫМ > +> ✅ **БОЕВОЙ ПРОГОН ВЫПОЛНЕН:** 7653/7653 ok, err=0. +> `TCON 4359 · TPE2 7213 · TALB 2031 · TPE1 381 · TRCK 437 · TIT2 8`. +> **Проверено чтением файлов: `TCON` у 7861/7861, mismatches 0.** +> 🔑 **ПИТФОЛЛ ЗАПУСКА:** у образа `tagwriter:latest` в `CMD` прописан `--dry-run`. +> Явно передавать `--apply`, иначе прогон молча уходит в dry-run: +> `docker create ... tagwriter:latest /app/apply_tags_v2.py --apply` +> 🔑 Боевой прогон запускать ДВУМЯ шагами: `docker create` затем `docker start`. +> Составная команда прерывается по таймауту. +> +> ✅ **NAVIDROME БД ПЕРЕСОЗДАНА:** `stop → rm navidrome.db* → up -d`, скан 4м13с, +> импортировано **7861** трек. Бэкапы БД: `navidrome.db.bak-20260927-apply-v3`, +> `navidrome.db.bak-20260927-053238`. +> 🔴 **ПУТЬ БД: `/mnt/RED_2TB/docker/navidrome/data/`** — БЕЗ `storage/`! +> В доке ранее был неверный путь `/mnt/RED_2TB/storage/docker/navidrome/data`. +> Композ-проект: `/mnt/RED_2TB/docker/navidrome`. + +> ## 🔴 АКТУАЛЬНОЕ СОСТОЯНИЕ (сессия ~29) — АРХИВ +> > ✅ **DRY-RUN ВЫПОЛНЕН УСПЕШНО.** План `/tmp/apply-plan-v3.csv` (md5 > `6b9e49b2baa3f1d1708e4566972266a9`) скопирован на NAS как `/tmp/tw306/apply-plan.csv` > (md5 совпал). Вывод — `/tmp/dryrun.log` / NAS `/tmp/tw306/dryrun.log`: