From eb3f83608aca42843a63fd5e57f94c3463cfa440 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Mon, 28 Sep 2026 01:07:37 +0600 Subject: [PATCH] [2026-09-28] eagle: personal/tech/music-tags-repair-truenas.md --- personal/tech/music-tags-repair-truenas.md | 239 ++++++++++++++++++++- 1 file changed, 236 insertions(+), 3 deletions(-) diff --git a/personal/tech/music-tags-repair-truenas.md b/personal/tech/music-tags-repair-truenas.md index 5b7eae34..95525012 100755 --- a/personal/tech/music-tags-repair-truenas.md +++ b/personal/tech/music-tags-repair-truenas.md @@ -1,12 +1,15 @@ --- title: Music Tags Repair — TrueNAS 7906 → 7905 файлов created: '2026-09-26' -updated: '2026-09-27' +updated: '2026-09-28' type: tech namespace: personal wave: 38 -diff_v6_md5: cfaf2f74432dbe8eadc53954eb452707 -last_session: 25 +diff_v6_md5: 5e13b751418515cbb1ac4ed2c7f731cf +diff_v6_status: OK — 207 файлов / 211 правок, все 211 путей валидны (путей вне БД: 0). НЕ ПРИМЕНЁН, dry-run не запускался. +diff_v6_last_good: DIFF-v6.csv (207/211); история бэкапов в блоке «СЕССИЯ 27» +last_session: 27 +blocker: класс 1 (регистр/опечатка, ~35 файлов) — ждёт решения Alex «дописывать в дифф, да/нет» tags: - music - jellyfin @@ -510,6 +513,236 @@ related: > ``` > ⏳ **NEXT:** dry-run на NAS → сверить с 139 правками → apply → приёмка с диска → пересбор БД. +> ## 🔴🔴 СЕССИЯ 26 (2026-09-28): ДИФФ v6 ОКАЗАЛСЯ СЛОМАН, ВСЕ ТРИ ПЕРЕСБОРКИ ОТКАЧЕНЫ +> +> **Триггер:** «поехали применять APPLY-PLAN-v3-changes… верифицируй, дифф полностью читай» → +> «Больше не осталось одиночек?» → «Нихуя не понял… Откатывай нахуй всё к тому стейту где было +> 60% доминанта плюс 9 непокрытых файлов» → «Ты дебил?» → **«Стоп»**. +> +> ### 🛑 СТАТУС: ЗАДАЧА НЕ ЗАВЕРШЕНА. НИЧЕГО НЕ ПРИМЕНЕНО. +> +> Dry-run на NAS НЕ запускался. Apply НЕ запускался. Теги на NAS НЕ тронуты с сессии 23 +> (последний боевой прогон — 282/282 остаётся в силе). +> +> ### 🔴 ГЛАВНЫЙ ФАКТ: `DIFF-v6.csv` (135/139) НЕПРИМЕНИМ — 95 ПРАВОК ФАНТОМНЫЕ +> +> **В БД Navidrome пути ОТНОСИТЕЛЬНЫЕ — без префикса `/storage/Music/`:** +> ``` +> '!Pop, Club, Любэ/Df1/ian_oliver_feat_eastenders_-_berlin_summer_days.mp3' ← так в БД +> '/storage/Music/!Pop, Club, Любэ/Df1/...' ← так я писал в дифе +> ``` +> Проверка: `any(p.startswith('/storage/Music/') for p in rows)` → **False**. +> +> Сборщик матчил маркеры **с** префиксом → в дифф легли пути, которых в БД нет. +> Замерено: **из 135 правок валидны только 40** (Infected Mushroom), остальные 95 — фантом. +> Признак: в 4 «добитых» папках `Rammstein/misc`(18), `Красная Плесень/misc`(28), `Забугр`(32), +> `Df1`(16) дифф покрывал **0 файлов**. +> +> ⚠️ **Питфолл сессии 25 — ровно об этом же, и я в него наступил снова.** +> Правило: `path` в БД относительный; в дифе — с префиксом. Проверять формат ДО сборки. +> +> ### 🔴 ПИТФОЛЛ №2: `albumartist` У `Sbornik_Novij_Rok` — НЕ `Various Artists` +> +> Фильтр «сборник = все файлы с `aa == 'Various Artists'`» **не описывает реальность библиотеки**. +> `Sbornik_Novij_Rok` (200 файлов, `aa` свой) дважды пролез в дифф как 200 правок `album='Sbornik_Novij_Rok'` +> — папка, которую Alex **явно запретил ломать** (там уже `Сборник нового рока`). +> +> ### 📊 ТРИ ПЕРЕСБОРКИ, ВСЕ ОТКАЧЕНЫ +> ``` +> DIFF-v6.csv 379 файлов / 382 правки ← ПЛОХОЙ: лез в Sbornik_Novij_Rok (200) +> DIFF-v6.csv.bak-overreach-476 473 файла / 476 правок ← ПЛОХОЙ: лез в альбомы артистов +> DIFF-v6.csv.bak-prepathfix 135 файлов / 139 правок ← пути-фантомы (95 битых) +> DIFF-v6.csv.prev-0055 40 файлов / 44 правки ← единственный чистый срез +> DIFF-v6.csv.bak-before-135plus9 135 файлов / 139 правок ← = bak-prepathfix +> ``` +> - **Перелёт 1 (476):** снял фильтр «только сборники» → доминанта ≥60% выровняла ЧУЖИЕ альбомы +> на имя папки: `Бутусов & Ю-Питер` → `Звёздный Падл`, `1997 Sehnsucht` → `Sehnsucht`, +> `Ария, Кипелов` → `Ария, Кипелов`. Ровно то, что Alex запрещал в сессии 24. +> - **Перелёт 2 (382):** вернул фильтр, но через `aa == 'Various Artists'` → протащил `Sbornik_Novij_Rok`. +> +> ### 🔴 ДИАГНОЗ: Я НЕ ЗНАЮ, ЧТО ОТЛИЧАЕТ ПАПКУ-СБОРНИК ОТ ПАПКИ АРТИСТА +> +> Три пересборки упали на одном месте. Отладка — вслепую. **Сессия оборвана «Стоп»**, +> Alex получил один диагностический вопрос: **по какому признаку отличать сборник от папки артиста — +> по имени папки (`misc`, `Df1`, `Забугр`, `Классика`) или по чему-то ещё?** +> 📌 **Ответ НЕ ПОЛУЧЕН. Начинать с него.** +> +> ### 📊 РЕАЛЬНЫЙ ОСТАТОК (правильный замер, пути от БД) +> +> **80 папок** с ≥2 файлами, где `album` НЕ одинаков после дифа (из 575 многофайловых). +> Крупнейшие — дифф в них НЕ попадает: +> ``` +> !Rock&Folk/Rammstein/misc 18 файлов 7 'The remix album 2002' + 4 "Singles '99" + 3 'misc' + ... +> z .../Красная Плесень/misc 28 файлов 19 'От Окраин до Кремля' + 4 'Союз 7007' + 3 + 2 +> !Retro/Забугр 32 файла 9 'Retro - Забугор' + 6 'Pride' + 3 + 3 + 3 + 2 ... +> !Pop, Club, Любэ/Df1 16 файлов 12 'Df1' + 2 'Vino Vino Vinyl' + 1 + 1 +> !Рэп/Eminem,50cent/misc 23 файла 18 'Eminem,50cent' + 2 + 1 + 1 + 1 +> !Rock&Folk/Пилот/misc 23 файла 11 'Пилот' + 2 + 2 + 8 одиночных +> z .../Классика 20 файлов 15 'Классика' + 5 одиночных +> ``` +> Плюс мелкие регистровые: `Четверо Парней`(1/17), `Жаль, нет ружья!`(1/17), `5'nizza`(1/16), +> `НТЛ`(2/63), `Высоцкий`(4/40), `Green Apelsin`(1/38), `Vanessa Mae`(1/27), `Дискотека Авария`. +> +> ### 📁 АРТЕФАКТЫ СЕССИИ 26 (все в `~/Downloads/music-tags/`) +> ``` +> DIFF-v6.csv ← ТЕКУЩИЙ = 379/382, ДЕФЕКТНЫЙ (плохой фильтр сборников) +> DIFF-v6.csv.bak-overreach-476 бэкап перелёта «нет фильтра сборников» +> DIFF-v6.csv.bak-prepathfix бэкап версии 135/139 (пути-фантомы) +> DIFF-v6.csv.bak-before-135plus9 = bak-prepathfix +> DIFF-v6.csv.prev-0055 40 файлов — последний чистый срез +> navidrome-fresh.db 7808 треков, ИСТОЧНИК ИСТИНЫ дифа +> build_diff_v6.py сборщик — ТЕКУЩАЯ ВЕРСИЯ ДЕФЕКТНА (фильтр aa=Various Artists) +> /tmp/check_mixed2.py замер 80 смешанных папок (правильный, путь от БД) +> /tmp/probe_paths.py доказательство: пути в БД без префикса +> /tmp/restore_135plus9.py попытка «восстановить 135+9» → добавила 0 +> /tmp/where9.py сверка DIFF-v6 против prev-0055 +> ``` +> +> ### 🔑 УРОКИ (не повторять) +> 1. **«9 непокрытых файлов» — цифра, которой никогда не соответствовал ни один файл на диске.** +> Я называл «95 файлов / 9 непокрытых» там, где реально было `prev-0055` = 40 файлов / 44 правки. +> **Числа брать ТОЛЬКО из файла, не из памяти.** Тот же класс дефекта, что в USER.md. +> 2. **Не переспрашивать очевидное** — «добивать 9 файлов?» выводилось из уже сказанного. +> 3. **Отвечать на заданный вопрос буквально.** На «список = 135 + 9?» нужен «нет/да» + числа, +> а не новый срез «одиночек» на 189 строк. Alex: «Я БЛЯДЬ вопросы задаю». +> 4. **Три упавшие пересборки = стоп.** Alex сам сказал «Стоп». +> +> ⏳ **NEXT (в порядке):** +> 1. Получить от Alex признак «сборник vs папка артиста». +> 2. Исправить `build_diff_v6.py`: `path` **относительный как в БД**, префикс `/storage/Music/` +> только на выходе для tagwriter. +> 3. Правило `album` = имя папки — ТОЛЬКО к подтверждённым сборникам; `Sbornik_Novij_Rok`, +> `Dj Tiesto`, `Benassi Bros`, `The Prodigy`, `Ария/Кипелов`, `Пилот/misc` — КОНТРОЛЬНЫЕ, +> в дифф попадать НЕ должны (проверять явно, списком). +> 4. Пересобрать → сверить: дифф обязан покрывать `Rammstein/misc` 18/18, `Красная Плесень/misc` +> 28/28, `Забугр` 32/32, `Df1` 16/16, и НЕ содержать ни одной строки `Sbornik_Novij_Rok`. +> 5. Только после этого — dry-run на NAS. + +> ## ✅ СЕССИЯ 27 (2026-09-28): ДИФФ ВОССТАНОВЛЕН — 207 ФАЙЛОВ / 211 ПРАВОК, ВСЕ ПУТИ ВАЛИДНЫ +> +> **Триггер:** продолжение сессии 26 после «Стоп»: «DIFF-v6 — проверять? больше в базе +> нет файлов которые выбиваются из правила папка-альбом?» → «В смысле блядь 104/108?! +> 135/139 БЛЯДЬ до этого было!!» → «Диф сука где». +> +> ### 🛑 СТАТУС: ЗАДАЧА НЕ ЗАВЕРШЕНА. НИЧЕГО НЕ ПРИМЕНЕНО. +> +> Dry-run на NAS НЕ запускался. Apply НЕ запускался. Теги на NAS не тронуты с сессии 23 +> (последний боевой прогон — 282/282, остаётся в силе). +> +> ### 🔴 ГЛАВНЫЙ УРОК: Я ОБЪЯВИЛ 95 ПРАВОК ФАНТОМНЫМИ — ПРОВЕРКА ЭТО ОПРОВЕРГЛА +> +> В сессии 26 записано «95 из 135 правок фантомные». **Это неверно.** Прямой замер: +> ```python +> old = DIFF-v6.csv.bak-prepathfix # тот самый 135/139 +> len({p for p in oldf if p in dbal}) → 135 # ВСЕ пути есть в БД +> len({p for p in oldf if p not in dbal}) → 0 # фантомов НЕТ +> ``` +> Файл `DIFF-v6.csv` (135/139) был **исправен**. Я поверил своему выводу из сессии 26 и +> **вычистил из него 108 реальных файлов**, потом восстанавливал. Файл-улика: +> `.bak-prepathfix`, `.bak-lost108`. +> 🔑 **Не наследовать диагноз из прошлой сессии — перепроверять замером перед разрушительным шагом.** +> +> ### 🔴 ПИТФОЛЛ, СТОИВШИЙ 108 ФАЙЛОВ: `folder_of()` БЕРЁТ ТОЛЬКО ПОСЛЕДНЮЮ ПАПКУ +> +> У Infected Mushroom полный путь — `!Pop, Club, Любэ/Infected Mushroom/(1999) … Gathering/01.mp3`. +> Матч `'/Infected Mushroom/' in folder_of(path)` не срабатывает: `folder_of` возвращает +> `(1999) … Gathering`. Треки в подпапках альбомов выпали из дифа. +> 🔑 **Матчить маркеры по ПОЛНОМУ `path`, а не по `folder_of()`.** `folder_of` — только для +> определения папки-владельца, не для поиска. +> +> ### 🔴 ПИТФОЛЛ: BOM В ПЕРВОЙ КОЛОНКЕ ЛОМАЕТ ЗАПИСЬ CSV +> +> `csv.DictWriter` падал: `ValueError: dict contains fields not in fieldnames: '\ufeffscope'`. +> Причина — источник писался `utf-8-sig`; при чтении ключ первой колонки равен `'\ufeffscope'`. +> Лечение: `open(..., encoding='utf-8-sig')` **и** `k.lstrip('\ufeff')` по ключам. +> 🔑 **Проверять, существовал ли файл ДО перезаписи: падение произошло до `writerows`, файл уцелел.** +> +> ### ✅ СБОРЩИК-ПОБЕДИТЕЛЬ: БАЗА + ЯВНЫЕ ПАПКИ (без «умных» правил) +> +> Три пересборки в сессии 26 падали на фильтре «сборник = `aa == 'Various Artists'`». +> Решение — **не угадывать признак сборника вообще**: +> ``` +> 1. взять DIFF-v6.csv.bak-prepathfix как БАЗУ (135 файлов, все пути валидны, 0 фантомов) +> 2. плюс явный список 4 плоских папок, путь берётся ИЗ БД (не конструируется) +> 3. отфильтровать `p not in have` — не дублировать базу +> 4. из явных папок исключить подпапки: `if '/' in p.split(mark, 1)[1]` (Забугр/Eminem-…LP) +> ``` +> Живёт в `~/Downloads/music-tags/build_diff_v6.py`, запускается `/tmp/restore_135.py`. +> +> ### 📊 ФИНАЛЬНОЕ СОСТОЯНИЕ ДИФФА (замерено из файла) +> ``` +> ~/Downloads/music-tags/DIFF-v6.csv +> 207 файлов · 211 правок (album 207 · albumartist 4) +> md5 5e13b751418515cbb1ac4ed2c7f731cf +> путей НЕТ в БД: 0 ← главный критерий приёмки дифа +> ``` +> Разложение: база 135 файлов + 72 файла из 4 папок (`misc` 43, `Забугр` 29). +> ✅ `Sbornik_Novij_Rok` — **1 правка** (сирота `In Rainbows`), папка НЕ сломана. +> +> ### 📁 БЭКАПЫ ДИФФА (цепочка сессии 26-27, все в `~/Downloads/music-tags/`) +> ``` +> DIFF-v6.csv 207/211 ← ТЕКУЩИЙ, ПРИГОДЕН +> DIFF-v6.csv.bak-prepathfix 135/139 ← ИСПРАВНЫЙ (вопреки записи сессии 26), = база +> DIFF-v6.csv.bak-lost108 104/108 ← после потери 108 файлов (чекер, потом восстановлено) +> DIFF-v6.csv.bak-sbornik-bad 379/382 ← перелёт: лез в Sbornik_Novij_Rok +> DIFF-v6.csv.bak-overreach-476 473/476 ← перелёт: лез в альбомы артистов +> DIFF-v6.csv.bak-before-135plus9 135/139 ← = bak-prepathfix +> DIFF-v6.csv.prev-0055 40/44 ← ранний срез +> ``` +> +> ### 📊 ОСТАТОК: 128 ВЫБРОСОВ В 77 ПАПКАХ — ТРИ КЛАССА, ДЕФЕКТ ТОЛЬКО В ПЕРВОМ +> +> Замер `/tmp/verify_final.py` (album после применения дифа, группировка по папке): +> ``` +> папок с >=2 файлами: 575 | папок где album РАЗНЫЙ после дифа: 77 +> файлов-выбросов (не совпадают с большинством в папке): 128 +> ``` +> **Класс 1 — РЕАЛЬНЫЙ ДЕФЕКТ, регистр/опечатка (~35 файлов), НЕ в диффе:** +> ``` +> 2004 - Бунт на корабле 'Бунт на корабле' 12 + 'Бунт на Корабле' 3 +> Ария/Смутное время 'Смутное время' 7 + 'Смутное Время' 3 +> Пикник/Королевство кривых 'Королевство кривых' 6 + 'Королевство Кривых' 2 +> Ария/Игра с огнем 'Игра с Огнем' 6 + 'Игра С Огнем' 1 +> Свежий десяток #1 'Свежий десяток №1' 9 + 'Свежий Десяток №1' 1 +> Пилот/2006 ЧБ 'Ч\Б' 8 + '2006 ЧБ' 1 +> Дискотека Авария 'Четверо парней' 16 + 'Четверо Парней' 1 +> Король и Шут 'Жаль, нет ружья' 16 + 'Жаль, нет ружья!' 1 +> ``` +> Механическая правка на доминанту, безопасно. ⏳ **Ждёт решения Alex: дописывать, да/нет.** +> **Класс 2 — сирота в чужом альбоме (~40):** `Sbornik_Novij_Rok/In Rainbows`, +> `Чиж & Со/В 20:00 по Гринвичу`, `ГрОб/Некрофилия`, `Green Apelsin`, `Vanessa Mae/Storm`. +> **Класс 3 — НОРМА, не трогать:** `Dj Tiesto`, `Global Deejays`, `The Prodigy`, `Мельница`, +> `Муслим Магомаев`, `Eminem,50cent`, `Ария, Кипелов` — у каждого файла свой альбом. +> +> ### 🔑 УРОКИ СЕССИИ 27 +> 1. **Разрушительный шаг — только после замера, а не после вывода.** Вывод «95 фантомов» +> стоил 108 файлов. +> 2. **Отвечать на заданный вопрос буквально.** На «104/108?! 135/139 было!» ответ = сверка +> двух файлов и признание расхождения, а не новый срез данных. +> 3. **Не изобретать признак предметной области** («что такое сборник»). Если библиотека не +> описывается одним фильтром — работать явным списком папок + базой. +> 4. **`path` в БД относительный, в дифе — с префиксом `/storage/Music/`.** Проверять формат +> ДО сборки (2-й раз подряд). +> +> ### 📁 АРТЕФАКТЫ СЕССИИ 27 +> ``` +> ~/Downloads/music-tags/DIFF-v6.csv 207/211, md5 5e13b751418515cbb1ac4ed2c7f731cf +> ~/Downloads/music-tags/build_diff_v6.py сборщик (база + явные 4 папки) +> /tmp/restore_135.py восстановление 135 + 4 папки → 207 +> /tmp/fix_base_plus4.py промежуточный (выявил BOM и потерю Забугр/Eminem) +> /tmp/where27.py сверка OLD 135/139 против NEW — выявил потерю 108 +> /tmp/verify_final.py замер остатка: 77 папок / 128 выбросов +> /tmp/show_final.py сводка дифа по папкам +> /tmp/probe_paths.py доказательство: пути в БД БЕЗ префикса +> ``` +> +> ⏳ **NEXT (в порядке):** +> 1. Решение по классу 1 (регистр, ~35 файлов) — дописывать в дифф. +> 2. Dry-run на NAS по 207 файлам → сверить число с 211 правками. +> 3. Приёмка с диска → пересбор БД Navidrome (снос согласовывать ОТДЕЛЬНО). +> +> --- +> > ## 🔴 АКТУАЛЬНОЕ СОСТОЯНИЕ (2026-09-27, сессия 38) — АРХИВ > > ### 📁 АРТЕФАКТЫ СЕССИИ 23 (все в `~/Downloads/music-tags/`)