diff --git a/personal/plans/tagwriter-container.md b/personal/plans/tagwriter-container.md index 8cf3630c..fb23b5b8 100755 --- a/personal/plans/tagwriter-container.md +++ b/personal/plans/tagwriter-container.md @@ -494,7 +494,7 @@ docker start -a twdry; docker rm -f twdry | Что | Правильно | Дока говорила | |---|---|---| | Образ | **`tagwriter:latest`** | ❌ `tagwriter:v2` (устаревший) | -| Скрипт | **монтируется** через `--entrypoint python3 /app/data/apply_tags_v2.py` | ❌ «нужен `docker cp`» | +| Скрипт | **`/app/apply_tags_v2.py`** (В ОБРАЗЕ), монтируется папка данных | ❌ «нужен `docker cp`» | | Музыка (боевой) | `/music` **без** `:ro` | ✅ верно | | Музыка (сухой) | `/music:ro` | ✅ верно | | `save(v1=0)` | ⚠️ ID3v1-комментарий **уцелел** в тесте | ❌ «сносит ID3v1 всегда» | diff --git a/personal/tech/music-tags-repair-truenas.md b/personal/tech/music-tags-repair-truenas.md index 9740a167..261df20c 100755 --- a/personal/tech/music-tags-repair-truenas.md +++ b/personal/tech/music-tags-repair-truenas.md @@ -51,6 +51,202 @@ related: > «с нуля», теряя сортировку, схлопывание и правило `album`. > **Сборщик живёт в папке проекта (`~/Downloads/music-tags/`), НЕ в `/tmp`.** +> ## ✅ СЕССИЯ 23 (2026-09-27): ВОЛНА v6 — СБОРНИКИ + ТРОФИМ + ПРОВЕРКА БД. ПРИМЕНЕНО 282/282 +> +> **Триггер:** Alex: «поехали применять APPLY-PLAN-v3-changes… потом dry run который должен +> совпасть по цифрам» → далее «делай диф от текущей бд версии». +> +> ### 🔴 ГЛАВНЫЙ УРОК: ПРАВИЛО СБОРНИКОВ ДЕЙСТВУЕТ И БЕЗ `!` В ИМЕНИ ПАПКИ +> +> Первый сборщик этой сессии (`build_diff_from_db.py`) правил папки **только по хардкоду трёх +> путей** (`!Рэп/Трофим/`, `!Рэп/Птаха…`, `Тема дороги.mp3`). Правила сборников в нём +> **не было вообще** — отсюда дифф на 91 запись, где 26 строк — Трофим. +> +> **Alex (несколько раз, пока не дошло):** правило `album` = имя папки-владельца действует +> для ВСЕХ сборников, включая папки без `!` (`Infected Mushroom/Unreleased/Remixes, Mixes`, +> `Other Track's`). Я сузил его до папок с `!` — это была ошибка. +> +> **Корневая модель тегов (Alex):** +> ``` +> album = имя папки-владельца (папка релиза), БЕЗ ведущих '!', БЕЗ года, БЕЗ артиста +> albumartist= владелец папки; для СБОРНИКОВ = 'Various Artists' (уже так, НЕ трогать) +> artist = реальный исполнитель трека -- НЕ ТРОГАТЬ (кроме явных указаний) +> track = номер из имени файла, если в теге пусто +> ``` +> 🔴 **Алекс про сборники, дословно:** «для сборников типа rock_o_pop **artist = 'Various Artists'**! +> АЛЬБОМ БЛЯДЬ rock_o_pop, не albumartist!» — то есть `album` = имя папки, `albumartist` = `Various Artists`. +> +> ### 🔴 ПРОХОД v3 БЫЛ ПО ФАЙЛАМ, А НЕ ПО ПАПКАМ — 18 ПАПОК ИЗ 23 ЗАКРЫТЫ ЧАСТИЧНО +> +> Замер по `APPLY-PLAN-v3-changes.csv` против плана: +> ``` +> !Pop, Club, Любэ/!ZabuGor 1 из 15 ЧАСТИЧНЫЙ +> !Pop, Club, Любэ/Green Apelsin 1 из 38 ЧАСТИЧНЫЙ +> !Pop, Club, Любэ/Алексин 1 из 14 ЧАСТИЧНЫЙ +> !Retro/Забугр 3 из 32 ЧАСТИЧНЫЙ +> !Rock&Folk/!rock_o_pop 2 из 57 ЧАСТИЧНЫЙ +> !Rock&Folk/Ария, Кипелов 3 из 12 ЧАСТИЧНЫЙ +> !Рэп/Птаха - Красивые… 1 из 8 ЧАСТИЧНЫЙ +> ``` +> **Причина:** правило срабатывало только если `album == title` или `album == имя файла`. +> Файлы с ЧУЖИМ альбомом в теге проскакивали мимо. Полных проходов — 6 (все мелкие: `Little Big`, +> `Руки вверх`, `Хабиб`, `Юрий Антонов`, `Юрий Никулин`, `Серьга`). +> 🔴 **Правило проверки: после любой волны сверять «сколько файлов правилось» против +> «сколько файлов в папке». Расхождение = незакрытый проход, а не норма.** +> +> ### АРТЕФАКТЫ v6 (файлы в `~/Downloads/music-tags/`) +> +> | Файл | Что | md5 / число | +> |---|---|---| +> | `build_diff_v6.py` | сборщик (правило сборников + Трофим + Птаха + Ленинград + Т9) | — | +> | `DIFF-v6.csv` | 103 строки, 554 правки, **282 файла** | — | +> | `apply-plan-v6.csv` | 7861 строка | `60819ea387be6f98c3fdcb9da601e9c8` | +> +> **Разложение 282 файлов:** `album` 183 · `artist` 155 · `albumartist` 158 · `track` 58. +> +> ### 🔴 RED FLAG: ПУТЬ В ПЛАНЕ ОБЯЗАН НАЧИНАТЬСЯ С `/storage/Music/` +> +> Первый dry-run дал **`к обработке: 0`, 282 MISSING, «путь вне /music»**. +> Причина: в моём сборщике `path` писался **без префикса** `/storage/Music/`. +> Канонный `APPLY-PLAN.csv` префикс имеет; `apply_tags_v2.py` через `map_path()` снимает его, +> и без префикса файл уходит в MISSING. +> **Фикс:** `plan_rows.append({"path": "/storage/Music/" + rel, ...})`. +> 🔴 **Любой новый сборщик плана ОБЯЗАН писать путь с префиксом `/storage/Music/`.** +> +> ### БОЕВОЙ ПРОГОН v6 — ФАКТ +> +> ```bash +> ssh … 'docker create --name twapply6 --entrypoint python3 \ +> -v /tmp/tw306:/app/data \ +> -v /mnt/RED_2TB/storage/Music:/music \ +> tagwriter:latest /app/apply_tags_v2.py --apply --verify' +> ``` +> ``` +> строк в плане: 7861 | не найдено на диске: 0 | к обработке: 282 +> === APPLY: 282 файлов === ok=282 err=0 +> TPE1 155 · TPE2 158 · TALB 183 · TRCK 58 · TIT2 0 · TCON 0 +> === VERIFY: совпало 282, MISMATCH 0 === +> ``` +> **Приёмка с диска (mutagen, `verify_applied_v6.py`):** 282/282 совпало, расхождений 0. +> Точечная проверка 11 файлов (`spot_check_v6.py`) — все классы верны. +> +> ### 🔴 ДЕФЕКТ, НАЙДЕННЫЙ ПРИЁМКОЙ: `albumartist` у Т9 остался мусорным +> +> `Т9 - Вдох-Выдох`: я правил `album`/`artist`, но НЕ `albumartist` → в теге остался +> `TPE2 = 'Вдох-Выдох'` (мусор из старого artist). Дописан в сборщик, пересобран план +> (`albumartist` 157 → 158), повторный прогон — `aa='Т9'` ✅. +> 🔴 **Правило: правя `artist`, всегда проверять `albumartist` — он часто дублирует старое +> значение artist и остаётся мусором.** Детектор: `check_aa_stale.py` (ищет файлы, где artist +> менялся, а albumartist не менялся и равен старому artist). В библиотеке найден ровно **1**. +> +> ### КОНТЕКСТ ДЛЯ ВОССТАНОВЛЕНИЯ (Alex): Сирано — реальный альбом Т9 «Вдох-выдох» (2008) +> +> `!Рэп/03. Т9 - Вдох-Выдох (Ода Нашей Любви).mp3` (файл в КОРНЕ `!Рэп`): +> `album='Вдох-выдох'`, `artist='Т9'`, `albumartist='Т9'`. Подтверждено веб-поиском (Discogs 2008). +> +> ### СНОС БД NAVIDROME И ПЕРЕСБОР — ВЫПОЛНЕНО +> +> ```bash +> D=/mnt/RED_2TB/docker/navidrome/data +> mkdir -p "$D/navidrome.db.bak-$(date +%Y%m%d-%H%M%S)"; cp "$D"/navidrome.db* "$D/navidrome.db.bak-$TS"/ +> cd /mnt/RED_2TB/docker/navidrome && docker compose stop +> rm -v "$D"/navidrome.db "$D"/navidrome.db-shm "$D"/navidrome.db-wal +> docker compose up -d +> ``` +> **До сноса:** `media_file` 7861 · `album` 727. **После полного скана:** `media_file` 7811 · `album` 718. +> ⚠️ `media_file` 7811 ≠ 7861 — часть файлов Navidrome не импортировал (не-mp3 / битые; +> при dry-run: `mp3 на диске: 7811, пропущено не-mp3: 562`). Для проверок считать по 7811. +> +> ### ✅ ЧТО ПОДТВЕРЖДЕНО В БД ПОСЛЕ ПЕРЕСБОРА +> +> ``` +> Трофим: 11 альбомов, aa='Сергей Трофимов' (Аристократия помойки 1-4, Война и мир, +> Я рождаюсь вновь, Бард-авангард, Я скучаю по тебе, Ветер в голове, Ностальгия, +> Следующая остановка) -- ВЕРНО +> rock_o_pop: 57 треков ОДНИМ альбомом -- ВЕРНО +> ZabuGor: 2 альбома (13 + 2) -- второй = файл в корне, album='Radio' +> !Retro: 2 альбома (157 + 1) -- ОДИН ФАЙЛ ОТВАЛИЛСЯ +> !Классика: 2 альбома (34 + 1) -- ОДИН ФАЙЛ ОТВАЛИЛСЯ +> ``` +> +> ### 🔴 ОТКРЫТЫЙ КЛАСС ДЕФЕКТА (не закрыт): ПАПКИ БЕЗ `!`, СОСТОЯЩИЕ ИЗ ОДИНОЧЕК +> +> `Infected Mushroom` (51 папка, 244 файла) — в БД распался на ~35 альбомов-одиночек: +> `Becoming Insane (Chemical Trolls Remix)`, `Birthday (DJ Goni Peles Remix)`, `CDS`, `Promo`, +> `Elation Station`, `Magnet`, `Deck Wizards…`, `Unreleased Tracks` ×29 и др. +> В моём правиле их не было: я обрабатывал только папки со `!` и `Разное`. +> +> ### 🔴 ПРАВИЛО ALEX ДЛЯ ОДИНОЧЕК (получено в конце сессии, дословно разобрано) +> +> ``` +> папка с 1 файлом -> album = имя РОДИТЕЛЬСКОЙ папки (Remixes, Mixes) +> папка с 2+ файлами -> album = имя папки БЕЗ года и исполнителя +> ('(2001) Infected Mushroom - Wider (Domestic RMX)' +> -> 'Wider (Domestic RMX)') +> albumartist -> 'Infected Mushroom' +> artist -> как в треке, НЕ трогать +> ``` +> **Исключения, названные Alex:** +> - `2016 - Liquid Smoke (Single)` → `album='Liquid Smoke'` (сингл, без «WEB») +> - `2016 - Nutmeg (Single)` → `album='Nutmeg'` +> - `Unreleased/Other/… Other Track's` (23 файла) → `album='Other Tracks'` (**без апострофа**) +> - `Unreleased/Other/… Compilations Tracks` (12) и `Unreleased/Albums/*` — **уже верны, не трогать** +> - **ПАПКИ-АРТИСТЫ С 1 ФАЙЛОМ НЕ ТРОГАТЬ** (`Little Big`, `Хабиб`, `Юрий Антонов`, +> `Андрей Миронов`, `Агата Кристи` в SD-card) — Alex: «не надо ломать то что уже сделали». +> Правило «1 файл → родитель» к ним НЕ применять. +> +> ### ❌ `!A Not sorted/SD-card-2026-05` — Alex: УДАЛИТЬ ПАПКУ +> +> 3 файла, 3 разных артиста (`Пуля` — Агата Кристи, `Гагарин, я вас любила` — Андервуд, +> `Генералы песчаных карьеров` — Несчастный случай). **Дублей в дереве не найдено** — +> это 3 разные песни. Alex сказал «снеси папку, там только дубли вроде». +> ⚠️ **Требует подтверждения перед удалением** (расхождение: дублей нет). +> +> ### 📁 АРТЕФАКТЫ СЕССИИ 23 (все в `~/Downloads/music-tags/`) +> +> ``` +> build_diff_v6.py сборщик v6 (правило сборников + адресные) +> DIFF-v6.csv 103 строки, 554 правки, 282 файла +> apply-plan-v6.csv 7861 строка, md5 60819ea387be6f98c3fdcb9da601e9c8 +> verify_applied_v6.py приёмка с диска (mutagen), 282/282 +> spot_check_v6.py точечная проверка 11 классов +> check_aa_stale.py детектор устаревшего albumartist +> check_db_v6.py проверка БД Navidrome (альбомы/artists) +> db_final_rule.py список одиночек по правилу Alex +> db_find_single_albums.py папки, где album == title +> sanity/ diag_single_track.py, db_classB.py, db_singles.py, db_im_struct.py +> NAS: /tmp/tw306/apply-plan.csv, dryrun-v6.log, verify_v6.py, spot_v6.py, check_aa.py +> ``` +> +> ### 🔴 ПИТФОЛЛ: НЕ ЛОПАТИТЬ ДИСК, ЕСЛИ ДАННЫЕ ЕСТЬ В БД +> +> Alex: «**Ты базу проверяешь или ты назуя тотфайлуху пошел лопатить?!**» +> Я пошёл обходить `/music` через `os.walk` для проверки альбомов — медленно (таймаут 180 s) +> и не нужно: **Navidrome читает из своей БД**, все проверки группировки делать SQL-запросом +> к копии `navidrome.db`. Диск трогать только когда данные в БД отсутствуют. +> +> ### 🔴 ПИТФОЛЛ: `backup*/` ВНУТРИ `/music` ЛОВИТ `os.walk` +> +> Первый скан `Infected Mushroom` нашёл `backup_fix/!Pop, Club, Любэ/Infected Mushroom` +> (бэкап!) и выдал его за живое дерево. **Любой обход диска ОБЯЗАН отсеивать `backup*`.** +> +> ### 🔴 ПИТФОЛЛ: `--plan` НЕ СУЩЕСТВУЕТ, `CSV_IN` — КОНСТАНТА +> +> Запуск `apply_tags_v2.py --plan /app/data/apply-plan.csv` → `unrecognized arguments`. +> Путь зашит: `CSV_IN = "/app/data/apply-plan.csv"`. Свой план подкладывать **под этим именем** +> в отдельной папке данных (`/tmp/tw306:/app/data`). +> +> ### 🔴 ПИТФОЛЛ: `--entrypoint python3` + путь к скрипту +> +> Рабочая форма (проверена): +> ```bash +> docker create --name twX --entrypoint python3 \ +> -v /tmp/tw306:/app/data:ro -v /mnt/RED_2TB/storage/Music:/music:ro \ +> tagwriter:latest /app/apply_tags_v2.py --dry-run +> ``` +> ⚠️ В доке `personal/plans/tagwriter-container` было `/app/data/apply_tags_v2.py` — **неверно**, +> скрипт лежит в `/app/apply_tags_v2.py` (в образе). Dока исправлена ниже. + > ## 🔴 ДЕФЕКТЫ `build_fix_plan2.py` (найдены 27 сен, не исправлены в боевом файле) > > | # | Дефект | Где | Последствие |