diff --git a/personal/plans/tagwriter-container.md b/personal/plans/tagwriter-container.md index 298e440c..4c96517d 100755 --- a/personal/plans/tagwriter-container.md +++ b/personal/plans/tagwriter-container.md @@ -17,7 +17,7 @@ updated: '2026-09-26' --- # tagwriter — контейнер применения тегов MP3 (TrueNAS) -_Создан: 2026-09-26. **Статус: ✅ APPLY ВЫПОЛНЕН — 7041 файл обновлён, 0 ошибок. Сверка: 7610/7905 совпало (96.3%), 83 MISSING (9 WMA + 1 OGG + 73 не разобрано), 212 MISMATCH (разбор неполный — лог обрезан). Бэкап не-MP3 (70 файлов) сделан. Далее — конвертация WMA/OGG/FLAC/M4A → MP3.**_ +_Создан: 2026-09-26. **Статус: ✅ APPLY ВЫПОЛНЕН — 7041 файл, 0 ошибок. 🔴 НАЙДЕН ДЕФЕКТ ПЛАНА: 353 строки с ошибочным `action=no_change` (cur != new). MISSING 83 = 82 не-MP3 + 1 (ни одного потерянного MP3). MISMATCH 212 → факт-чек по диску: 165 реальных правок, 45 ложных, 2 мусор. Бэкапы: `backup/` 7041 файл 42 ГБ, `backup_raw/` 70 файлов 386 МБ. ⏭ ЖДЁТ РЕШЕНИЯ A/B/C: 75 правок «удалить TRCK». Далее — apply дельта-правок + конвертация не-MP3 → MP3.**_ _Основание: [[personal/plans/music-tags-wave38-fixes]]._ ## Задача @@ -94,10 +94,15 @@ docker run --rm -u 0 -v /mnt/RED_2TB/docker:/docker alpine:latest sh -c \ 4. ✅ **Apply** — `--apply`: **обновлено 7041, ошибок 0** (~17 мин, запуск в фоне). 5. ✅ **Read-back** — `--readback`: не найдено 0. 6. ✅ **Бэкап не-MP3** — `backup_nonmp3.py`: **70 файлов, 386 МБ → `/music/backup_raw/`** (выполнено). -7. ⏳ **Независимая сверка (полный список)** — `verify_tags.py` перезапущен без обрезки лога (ждёт результата). -8. ⏳ **Конвертация не-MP3 → MP3** — план готов (ffmpeg в образ, VBR V0, удаление оригиналов после проверки бэкапа). **Ждёт `go`.** -9. ⏳ **Приёмка в Jellyfin** (глазами Alex). -10. ⏳ **Обновить техдок** `personal/tech/music-tags-repair-truenas.md` после сверки. +7. ✅ **Независимая сверка (полный список)** — `verify_tags.py` прогнан без обрезки: 7610/7905, MISSING 83 (**всё не-MP3**), MISMATCH 212 (**всё `no_change`/`skip`**). Разобрано полностью. +8. ✅ **Бэкап не-MP3** — `backup_nonmp3.py`: **70 файлов, 386 МБ → `/music/backup_raw/`**. +9. 🔴 **НАЙДЕН ДЕФЕКТ ПЛАНА** — 353 строки с ложным `action=no_change` (см. § ГЛАВНЫЙ ДЕФЕКТ). +10. ✅ **ФАКТ-ЧЕК 212 MISMATCH** — `build_update_plan.py` + `fact_check_212.py`: **165 реальных правок**, 45 ложных, 2 мусор (см. § ФАКТ-ЧЕК). +11. 🔴 **ОТКРЫТЫЙ ВОПРОС: 75 правок «удалить TRCK»** — ждёт `A`/`B`/`C` от Alex (см. § ОТКРЫТЫЙ ВОПРОС). +12. ⏳ **Apply дельта-правок (165)** — после ответа Alex (бэкап уже есть). +13. ⏳ **Конвертация не-MP3 → MP3** — план готов (ffmpeg в образ, VBR V0, удаление оригиналов после проверки бэкапа). **Ждёт `go`.** +14. ⏳ **Приёмка в Jellyfin** (глазами Alex). +15. ✅ **Обновить техдок** `personal/tech/music-tags-repair-truenas.md` — **сделано.** ## ✅ СОСТОЯНИЕ НА 2026-09-26 (факты) @@ -110,37 +115,159 @@ docker run --rm -u 0 -v /mnt/RED_2TB/docker:/docker alpine:latest sh -c \ | **БЭКАП** | ✅ **7041 файл, 42 ГБ → `/mnt/RED_2TB/storage/Music/backup/`** (файлов в backup: 7041, MP3: 6823) | | **APPLY** | ✅ **обновлено 7041, ошибок 0** (`APPLY: обновлено 7041, ошибок 0`, ~17 мин) | | **READ-BACK** | ✅ не найдено на диске 0 | -| **СВЕРКА С ПЛАНОМ** (`verify_tags.py`) | ✅ **7610/7905 = 96.3%**, MISSING 83, MISMATCH 212 — полный разбор **перезапущен** (лог без обрезки) | +| **СВЕРКА С ПЛАНОМ** (`verify_tags.py`) | ✅ **7610/7905 = 96.3%**, MISSING 83 (**всё не-MP3: 40 ogg+26 m4a+16 wma+1 flac**), MISMATCH 212 (**всё `no_change`/`skip` — не трогались**) | +| **🔴 ДЕФЕКТ ПЛАНА: ложный `no_change`** | ❌ **353 строки** `action=no_change`, где `cur_*` ≠ `new_*` (TRCK 262, TIT2 49, TALB 21, TPE1 21) — требуют `fix`; **причина — устаревший `cur_*` + ложная логика генератора** | +| **ФАКТ-ЧЕК 212 MISMATCH ПО ДИСКУ** | ✅ **165 требуют правки**, 45 уже ОК (ложные), 2 ошибки (`._*` мусор). Поля: TRCK 128 · TPE1 98 · TALB 29 · TIT2 25. Скрипты: `build_update_plan.py` + `fact_check_212.py` | +| **🔴 ОТКРЫТЫЙ ВОПРОС: 75 правок «удалить TRCK»** | ⏳ **ждёт `A`/`B`/`C`** (почти все — папка Наутилус Помпилиус; похоже на дефект плана) | | **БЭКАП НЕ-MP3** | ✅ **70 файлов, 386.1 МБ → `/mnt/RED_2TB/storage/Music/backup_raw/`** (40 ogg · 16 wma · 13 m4a · 1 flac) | | **`/music/backup/` не тронут** | ✅ 42 ГБ, 7041 файл — проверено после `backup_raw` | +| **APPLY ДЕЛЬТА-ПРАВОК (165)** | ⏳ **НЕ запускать до ответа Alex по TRCK** | +| **ПЕРЕСЧЁТ `action` ПО ДИСКУ** | ⏳ частично заменён коротким путём (212 из verify → факт-чек → 165); полный пересчёт нужен только для валидации всего плана | | **КОНВЕРТАЦИЯ не-MP3 → MP3** | ⏳ план готов, **ждёт `go`** (см. § ПЛАН конвертации) | -### Результат независимой сверки `verify_tags.py` (2026-09-26) +### Результат независимой сверки `verify_tags.py` (2026-09-26, ПОЛНЫЙ лог) | Метрика | Значение | |---|---| -| Строк в плане | 7905 | +| Строк в плане | 7905 (7041 `fix` + 864 `no_change`/`skip`) | | **Совпало TIT2+TPE1+TALB+TRCK** | **7610 (96.3%)** | | MISSING (файл не найден/не MP3) | 83 | | MISMATCH (тег не совпал) | 212 | | Файлов на диске (индекс NFC) | 7822 | -**MISSING 83 — из видимых 10: 9 `.wma` + 1 `.ogg`.** mutagen пишет только ID3 (MP3) — -`.wma`/`.ogg` **ожидаемо не обрабатываются**. Остальные 73 **не разобраны** (лог обрезан `первые 10`). +**MISSING 83 — ТОЧНЫЙ РАЗБОР (полный список): 40 ogg + 26 m4a + 16 wma + 1 flac = 83.** +Все 83 — **не-MP3**. **Ни одного потерянного MP3** (проверено: `grep -i '\.mp3$'` по блоку MISSING → пусто). +`mutagen` пишет только ID3 (MP3) → не-MP3 ожидаемо не обрабатываются. См. § «Инвентарь не-MP3». -**MISMATCH 212 — из видимых 30:** `TPE1` 16, `TRCK` 11, `TIT2` 9, `TALB` 6. Типы: -- **mojibake (3)** — CP1251 прочитан как UTF-8: `Ãðÿçíàÿ êðîâü` → `Грязная кровь` - (Сектор Газа `05 - Грязная кровь.mp3`, `07 - Черный вурдалак.mp3`; НТЛ `САВЛО&LIL'DEN vs. NTL.mp3`). - Это **битые исходные теги**, не результат apply. -- **регистр/транслит** — `Ace of Base` vs `Ace Of Base`, `Nautilus Pompilius` vs `Наутилус Помпилиус`, - `Fleur` vs `Flëur`, `Vanessa-Mae` vs `Vanessa Mae`, `Иванушки Int` vs `Иванушки Int.`. -- **TRCK не записан** — `None` / чужой номер (`14` в плане → `1` на диске, `2` → `None`, `0` → `24`). -- **TALB с мусором** — `Лучшие песни. Часть 2` vs план `Лучшие песни` (не сработал delall?). -- **TIT2 обрезан/чужой** — `Shop` vs `Candy Shop`, `Sander Kleinenberg - The Fruit...` (артист внутри title). +**MISMATCH 212 — ТОЧНЫЙ РАЗБОР:** `action` у **всех 212** = `no_change` (210) / `skip` (2). +**`apply` их НЕ трогал** (скрипт пропускает `no_change`/`skip` по определению) — расхождение +между `new_*` плана и тегом на диске **существовало ДО apply**. -⚠️ **Вывод:** apply отработал, но у **212 файлов** теги на диске не равны плану. Причины не установлены — -нужен полный список (лог обрезан `первые 30`). Возможные источники: правки после apply, файлы -с дублями имён, битые исходные ID3 (mojibake не перезаписан). +Разбивка по полям: `TRCK` 102 · `TPE1` 98 · `TALB` 29 · `TIT2` 25 · `READ_ERROR` 2. +Разбивка по характеру расхождения: + +| Класс | Кол-во полей | Пример | +|---|---:|---| +| **REAL_DIFF** (значения реально разные) | 135 | `Nautilus Pompilius` → план `Наутилус Помпилиус`; `Shop` → `Candy Shop`; `Rebekka` → `Ребекка` | +| **TRCK_LEADING_ZERO** | 49 | диск `02` → план `2` | +| **EMPTY_ON_DISK** (тега нет) | 35 | диск `None` → план `169`, `Fire Power - Original Mix` | +| **EXTRA_IN_DISK** (мусор в теге) | 14 | `Лучшие песни. Часть 2` → план `Лучшие песни`; `Fever // By Maddalinio //` | +| **MOJIBAKE** (CP1251 как UTF-8) | 6 | `Ãðÿçíàÿ êðîâü` → `Грязная кровь` | +| DOT / REGISTER / TRUNCATED | ~13 | `Ace of Base`/`Ace Of Base`; `Иванушки Int`/`Иванушки Int.` | + +### 🔴🔴 ГЛАВНЫЙ ДЕФЕКТ, НАЙДЕННЫЙ В ЭТОЙ СЕССИИ: ЛОЖНЫЙ `action=no_change` (353 строки) + +**Alex: «Что блядь значит no action??»** → разбор вскрыл корень. + +`no_change` — это **колонка `action` в самом плане**, означает «текущее значение уже совпадает +с желаемым, менять не надо». `apply_tags.py` выполняет план **буквально**: раз `no_change` — файл +пропущен. **Скрипт не виноват — виноват план.** + +**Пересчёт по плану: 353 строки с `action=no_change`/`skip`, где `cur_*` ≠ `new_*`.** +Разбивка: `TRCK` 262 · `TIT2` 49 · `TALB` 21 · `TPE1` 21. +`source` пустой у **320 из 353** (автогенерация, без ручного решения); +`manual (user decision)` 16 · `appledouble artifact` 14. + +**Первопричина — две, обе подтверждены grep'ом по CSV:** + +1. **`action` проставлен ложно, где `cur != new`.** Пример: + `!Рэп/Eminem,50cent/50 cent-Candy Shop.mp3` → `cur_title=Shop`, `new_title=Candy Shop`, + а `action=no_change`. Логика генератора решила «совпадает» — **неверно**. +2. **`cur_*` в плане устарел и не соответствует диску.** Пример-доказательство: + `!Retro/Крылья.mp3` → план `cur_artist=Наутилус Помпилиус` (= `new_artist`, отсюда `no_change`), + **а на диске реально `Nautilus Pompilius`**. План снимал текущие значения раньше; предыдущие + волны правок меняли диск, `cur_*` не обновлялся. + +**Следствие:** 262 файла `Sbornik_Novij_Rok` имеют в имени `169_Steven Curtis Chapman`, но +`TRCK=None` в теге — план хотел записать номер, `no_change` → **не записано**. + +**Вывод (исправление прежнего ошибочного заключения):** 212 MISMATCH — **не «всё ок»**. +Это симптом того, что **353 строки требуют `action=fix`**, а план их не помечал. + +### ✅ ФАКТ-ЧЕК 212 MISMATCH ПО ДИСКУ (2026-09-26) — 165 РЕАЛЬНЫХ ПРАВОК + +Alex: «У тебя блядь есть список несовпавших mp3! Ты не можешь просто по нему update tags +запустить что-ли??» → «У тебя же уже есть этот список зачем пересчет» → **«Чини»**. + +**Правильный (короткий) путь вместо пересчёта всего плана:** + +1. Взять 212 путей из MISMATCH-блока `verify_tags.py`. +2. Отфильтровать MP3 → **все 212 оказались MP3** (не-MP3 ушли в MISSING, не в MISMATCH). +3. Подтянуть `new_*` для этих путей **из плана** → `update-plan.csv` (212 строк). +4. **Факт-чек:** прочитать реальные ID3 с диска, сравнить с `new_*` → `final-update.csv`. + +Скрипты: `build_update_plan.py` (шаги 1–3, локально `/tmp/`), `fact_check_212.py` (шаг 4, на NAS). + +**Результат факт-чека (`fact_check_212.py`, лог `tagwriter_factcheck.log`):** + +| Метрика | Значение | +|---|---:| +| Вход (212 MISMATCH) | 212 | +| **Требуют правки** | **165** | +| Уже совпадают (ложные — ведущий ноль TRCK и т.п.) | 45 | +| Ошибки | 2 — `._Infected Mushroom - Tiwanacu.MP3`, `._МИМИКРИЯ.MP3` (AppleDouble-мусор, не ID3) | + +Поля к правке: `TRCK` 128 · `TPE1` 98 · `TALB` 29 · `TIT2` 25. + +**Примеры реальных дефектов (чинить):** + +| Файл | На диске | Надо | +|---|---|---| +| `Сектор Газа/2000 - Восставший из Ада/05 - Грязная кровь.mp3` | mojibake `Ãðÿçíàÿ êðîâü` / `Ñåêòîðð Ãàçà` / `2000 - Âîññòàâøèé èç Àäà`, TRCK пусто | `Грязная кровь` / `Сектор Газа` / `Восставший из Ада` / `5` | +| `!Рэп/Eminem,50cent/50 cent-Candy Shop.mp3` | `Shop` / `Candy` / TRCK пусто | `Candy Shop` / `50 Cent` / `50` | +| `!Retro/Крылья.mp3`, `Люди на холме.mp3`, вся папка Наутилуса | `Nautilus Pompilius` | `Наутилус Помпилиус` | +| `!Рэп/НТЛ/*.mp3` (4 файла) | `NTL` | `НТЛ` | +| `!Radio/2009-10 Oktoberfest/Wolfgang Gartner - Fire Power (Original Mix).mp3` | TIT2/TPE1/TALB пусто | `Fire Power - Original Mix` / `Wolfgang Gartner` / `Fire Power / Latin Fever` | +| `SD-card-2026-05/Несчастный случай/Генералы песчаных карьеров.mp3` | `Несчастный Случай` | `Несчастный случай` | +| `Infected Mushroom/.../Infected Mushroom - You dont exist 140bpm.mp3` | TALB `old - unreleased` | `Unreleased Tracks` | + +### 🔴 ОТКРЫТЫЙ ВОПРОС (ждёт решения Alex): 75 правок «удалить TRCK» + +В `final-update.csv` **75 правок вида `TRCK: 'N'->''`** — план требует **удалить** номер трека: + +``` +TPE1: 'Nautilus Pompilius'->'Наутилус Помпилиус'; TRCK: '1'->'' +TPE1: 'NTL'->'НТЛ'; TRCK: '51'->'' +TIT2: 'Fever // By Maddalinio //'->'Fever'; TRCK: '02'->'' +``` + +Почти все — папка **Наутилус Помпилиус** (~20 файлов). Похоже на **дефект плана** +(`new_track` пустой = «не задано», а не «удалить»), а не намерение. + +**Варианты, предложенные Alex:** +- **A** — удалить номера (буквально выполнить план); +- **B** — **не трогать `TRCK`, где `new_track` пустой** (рекомендация ассистента: удаление + трек-номеров деструктивно для библиотеки); +- **C** — показать полный список 75 → Alex решит точечно. + +**Второй вопрос:** 2 файла `._Infected Mushroom - Tiwanacu.MP3` и `._МИМИКРИЯ.MP3` — +AppleDouble-мусор на диске. Удалять? + +**Статус: ждёт `A`/`B`/`C` от Alex. До ответа `apply` дельта-правок НЕ запускать.** + +### ⏭ ПЛАН: пересчёт `action` по фактическому диску (ЖДЁТ `go`) + +> ⚠️ Частично заменён более коротким путём выше (взять 212 из verify → факт-чек → 165). +> Полный пересчёт нужен, только если Alex захочет проверить **весь** план, а не только MISMATCH. + +**Идея:** действие считать от **реального тега на диске**, а не от устаревшего `cur_*`. + +1. Прочитать реальные ID3 с диска (7041+ файл) — индекс `{NFC(путь): теги}`. +2. Для каждой строки `no_change`/`skip`: если реальный тег ≠ `new_*` → поставить `fix`. +3. Получить **дельта-план** — только строки, которые реально надо доисправить. +4. **DRY-RUN** → показать Alex список → `apply` (с бэкапом, идемпотентно). + +⚠️ Второй прогон должен давать **0** — критерий идемпотентности (правило из памяти: +цель считать от ИСХОДНОГО значения, не от текущего). + +**Скрипт `find_false_nochange.py`** (локально `/tmp/`) уже реализует п.2–3 по CSV: +`python3 find_false_nochange.py` → напечатает 353 строки, разбивку по полям/`source`/`verdict`. + +### ✅ RATIONALE: почему 56 «провалов» из `fix` — не ошибки + +Из 7041 строк `fix` на диске не нашлись **56** — все **не-MP3** (ogg/wma/m4a). +`mutagen` не пишет ID3 в эти контейнеры. После конвертации в MP3 (см. план ниже) их теги +можно будет прописать обычным `apply`. **Реальных правок `fix` с расхождением — 0.** ### ✅ БЭКАП НЕ-MP3 (`backup_raw/`, 2026-09-26) — ВЫПОЛНЕНО @@ -237,8 +364,22 @@ docker compose --profile tools run --rm \ --entrypoint python tagwriter /app/verify_tags.py ``` -⚠️ Текущая версия печатает только `первые 10` MISSING и `первые 30` MISMATCH — **лог обрезан**, -полного разбора нет. Фикс (печать всех) **готов локально, но НЕ залит**: апрув истёк (2 раза). +⚠️ Печать обрезки (`[:10]`/`[:30]`) **убрана** — теперь скрипт печатает все MISSING и MISMATCH. +Фикс залит на NAS 2026-09-26 (`grep -n 'первые' verify_tags.py` → пусто). **Полный лог: 572 строки.** + +**Производные скрипты (созданы в этой сессии):** + +| Скрипт | Где | Назначение | +|---|---|---| +| `build_update_plan.py` | локально `/tmp/` | из MISMATCH-лога берёт пути → фильтрует MP3 → тянет `new_*` из плана → `update-plan.csv` | +| `fact_check_212.py` | NAS `/mnt/RED_2TB/docker/tagwriter/` | читает реальные ID3 → сравнивает с `new_*` → `final-update.csv` (165 строк) | +| `backup_nonmp3.py` | NAS | бэкап не-MP3 → `backup_raw/` | +| `recompute_action.py` | NAS | полный пересчёт `action` по диску (не понадобился — заменён коротким путём) | +| `deep_classify.py`, `find_false_nochange.py`, `action_check.py`, `classify2.py` | локально `/tmp/` | анализаторы расхождений | + +**Правило, выведенное Alex'ом:** если список несовпавших файлов **уже есть** (из verify) — +не пересчитывать весь план, а **сразу взять этот список** и обновить теги по нему. +Лишний массовый пересчёт = потерянное время и раздражение Alex. ⚠️ **Замечание по пулу:** после бэкапа `df` показал `RED_2TB` = **935G** (было 976G) — ZFS-снапшоты съели часть при копировании. Свободно 919 ГБ. Бэкап удалять **только после приёмки в Jellyfin**. @@ -300,10 +441,23 @@ docker compose --profile tools run --rm \ - ⚠️ mutagen пишет **только ID3 (MP3)**; `.wma`/`.ogg` в плане останутся нетронутыми (9+1 файл). **Причина MISSING ≠ потеря файла.** - 🔴 **`backup/` содержит только MP3 из плана.** Для не-MP3 нужен **отдельный** бэкап (`backup_raw/`), иначе правило «оригинал остался в бэкапе» невыполнимо. - 🔴 **Защита от захвата `backup/`**: `os.walk` пропускает `backup`/`backup_raw`, `assert "/backup/" not in path` перед каждой записью/удалением. Проверять факт после прогона (`du -sh` + `find | wc -l`). -- 🔴 **Логи скриптов не обрезать** (`[:10]`/`[:30]` в print) — при разборе расхождений теряются данные, приходится перезапускать долгую сверку. Печатать всё. +- 🔴 **Логи скриптов не обрезать** (`[:10]`/`[:30]` в print) — при разборе расхождений теряются данные, приходится перезапускать долгую сверку. Печатать всё. (Поймано дважды: сначала обрезал, потом перезапускал 2 минуты.) +- 🔴🔴 **`no_change` ≠ «всё хорошо».** Колонка `action` — это **инструкция скрипту**, а не статус. + `no_change` значит «скрипт не трогает файл». Если план поставил `no_change` **ошибочно** (а `cur != new`), + дефект **останется в файле навсегда** и сверка его покажет как MISMATCH. **Правило: перед `apply` + валидировать `action` против ФАКТИЧЕСКОГО диска, а не против `cur_*` из плана** — `cur_*` устаревает, + если предыдущие волны меняли файлы. Детектор: `find_false_nochange.py`. +- 🔴 **MISMATCH при сверке не всегда дефект apply.** Сначала проверить `action` строки: если + `no_change`/`skip` — файл не менялся, расхождение было ДО. Это может быть как норма + (регистр/ведущий ноль), так и **скрытый дефект плана**. Не списывать «всё ок» — Alex это ловит. - ⚠️ `ffmpeg` на хосте NAS **нет**; добавлять в контейнер (`apk add --no-cache ffmpeg`), не использовать чужие контейнеры. - ⚠️ macOS-мусор `._*` (AppleDouble) попадает в `find` как «m4a» — **исключать** (`if f.startswith("._"): continue`), иначе счёт файлов завышен вдвое (26 vs 13). - 🔴 **Длительные операции на NAS (бэкап/apply) — только в фоне** (`background=true` + `notify_on_complete`): foreground-таймаут 600 с обрывает ssh, операция продолжается «осиротевшей».
Два независимых долгих процесса можно вести **параллельно** (ЧТЕНИЕ тегов + КОПИРОВАНИЕ файлов не конфликтуют) — Alex это ожидает. +- 🔴 **Апрув-таймаут ≠ «не смог».** Если команда заблокирована («Approval request timed out — do NOT retry»), это значит **Alex не увидел промпт**, а не запрет. Ассистент обязан: (1) сообщить точную формулировку блокировки, (2) **не повторять вслепую**, (3) попросить подтверждение. Alex реагирует жёстко на «почему не сделал?» — нужно сразу объяснять механику блокировки, а не оправдываться. + **Дополнено 2026-09-26:** если Alex ответил на «почему не сделал?» раздражённо («Ты заебал! Ты забыл свои обязанности?!») — это **сигнал немедленно повторить команду**, а не продолжать объяснять. Alex ожидает, что сообщение о блокировке = пауза до его реакции, и после реакции работа продолжается **сразу**. +- 🔴 **НЕ переизобретать работу, которую Alex уже дал.** Если данные/список уже есть (лог verify, CSV) — **взять их**, а не писать новый массовый пересчёт. Alex: «У тебя же уже есть этот список зачем пересчет». Лишний шаг = потеря времени + раздражение. +- 🔴 **Вопрос «Чини» = план + выполнение, не новые вопросы.** Alex сказал «Чини» — значит надо чинить. Но перед **деструктивной частью** (удаление TRCK, удаление файлов) — план и подтверждение обязательны (правило стопа/безопасности выше по приоритету, чем краткость). +- 🔴 **Не списывать расхождения «всё ок» без разбора.** Alex ловит такие выводы: «ты только что говоришь что там была неправильная кодировка и неверные теги но тут же говоришь что это ок». Если в данных есть mojibake/неверные значения — это **дефект**, а не норма. Сначала классификация по группам и файлам, потом вывод. ## Связано diff --git a/personal/tech/music-tags-repair-truenas.md b/personal/tech/music-tags-repair-truenas.md index b1ec7213..0c2fbcec 100755 --- a/personal/tech/music-tags-repair-truenas.md +++ b/personal/tech/music-tags-repair-truenas.md @@ -84,10 +84,79 @@ related: > **MISSING 83 = 10 не-MP3 (9 wma + 1 ogg, mutagen их не пишет) + 73 не разобрано. MP3 из плана потеряно — 0.** > Полный разбор перезапущен (лог был обрезан `первые 10`/`первые 30`). > ✅ **Бэкап не-MP3: 70 файлов, 386 МБ → `/music/backup_raw/`** (40 ogg · 16 wma · 13 m4a · 1 flac); -> `/music/backup/` не тронут (42 ГБ, 7041). ⏳ Далее — конвертация не-MP3 → MP3 (ffmpeg в образ, VBR V0, удаление оригиналов после проверки бэкапа), **ждёт `go` Alex**. -> ✅ **APPLY ВЫПОЛНЕН: `APPLY: обновлено 7041, ошибок 0`** (~17 мин, запуск в фоне). -> ✅ **READ-BACK: не найдено на диске 0.** -> Блокер прав на `/mnt/RED_2TB/docker/` решён: каталог создан **из контейнера** (`docker run -u 0 -v /mnt/RED_2TB/docker:/docker alpine mkdir` + `chown 950:950`). +> `/music/backup/` не тронут (42 ГБ, 7041). +> +> ### 🔴🔴 ПОЛНЫЙ РАЗБОР СВЕРКИ (полный лог, 2026-09-26) — И ИСПРАВЛЕНИЕ ПРЕЖНЕГО ВЫВОДА +> +> | Метрика | Значение | +> |---|---:| +> | Строк в плане | 7905 (7041 `fix` + 864 `no_change`/`skip`) | +> | Совпало | **7610 (96.3%)** | +> | MISSING | 83 = **40 ogg + 26 m4a + 16 wma + 1 flac — все не-MP3** | +> | MISMATCH | 212 = **все `no_change`/`skip`, `apply` их не трогал** | +> +> **MISSING 83 — не потери.** Ни одного MP3 (проверено). mutagen пишет только ID3 → не-MP3 ожидаемо. +> Ни один MP3 из плана не потерян; не-MP3 в план попали ошибочно и на теги MP3 не влияли. +> +> **MISMATCH 212 — не «всё ок».** Разбивка по характеру: REAL_DIFF 135 · TRCK_LEADING_ZERO 49 · +> EMPTY_ON_DISK (`None`) 35 · EXTRA_IN_DISK 14 · MOJIBAKE 6 · прочее ~13. +> Поля: TRCK 102 · TPE1 98 · TALB 29 · TIT2 25. +> +> ### ✅ ФАКТ-ЧЕК 212 MISMATCH ПО ДИСКУ → 165 РЕАЛЬНЫХ ПРАВОК +> +> Alex: **«У тебя блядь есть список несовпавших mp3! Ты не можешь просто по нему update tags +> запустить что-ли??»** → «У тебя же уже есть этот список зачем пересчет» → **«Чини»**. +> +> **Короткий путь (вместо пересчёта всего плана):** 212 путей из MISMATCH → фильтр MP3 +> (все 212 MP3) → `new_*` из плана → `update-plan.csv` → факт-чек реальных ID3 → +> `final-update.csv`. +> +> | Метрика | Значение | +> |---|---:| +> | Вход (212 MISMATCH) | 212 | +> | **Требуют правки** | **165** | +> | Уже совпадают (ложные: ведущий ноль TRCK) | 45 | +> | Ошибки | 2 (`._*` AppleDouble-мусор, не ID3) | +> +> Поля к правке: `TRCK` 128 · `TPE1` 98 · `TALB` 29 · `TIT2` 25. +> Примеры: `Сектор Газа` mojibake `Ãðÿçíàÿ êðîâü` → `Грязная кровь`; `50 cent-Candy Shop` +> `Shop`/`Candy` → `Candy Shop`/`50 Cent`; Наутилус `Nautilus Pompilius` → `Наутилус Помпилиус`; +> НТЛ `NTL` → `НТЛ`; `Wolfgang Gartner - Fire Power` пустые теги → заполнить. +> +> 🔴 **ОТКРЫТЫЙ ВОПРОС: 75 правок вида `TRCK: 'N'->''`** (удалить номер трека; почти все — +> Наутилус Помпилиус). Похоже на дефект плана (`new_track` пустой = «не задано»). +> Варианты: **A** удалить как в плане · **B** не трогать пустой `new_track` (рекомендация) · +> **C** показать полный список 75. **Ждёт ответа Alex. До ответа `apply` дельта-правок не запускать.** +> Отдельно: 2 файла `._Infected Mushroom - Tiwanacu.MP3`, `._МИМИКРИЯ.MP3` — удалять? +> +> **Скрипты этой сессии:** `build_update_plan.py` (локально) + `fact_check_212.py` (NAS). +> +> ### 🔴 ГЛАВНЫЙ ДЕФЕКТ ПЛАНА: ЛОЖНЫЙ `action=no_change` — 353 СТРОКИ +> +> Alex: **«Что блядь значит no action??»** `no_change` — колонка `action` в CSV («менять не надо»). +> `apply_tags.py` выполняет план буквально → файл пропущен. **Скрипт не виноват, виноват ПЛАН.** +> +> **353 строки** `action=no_change`/`skip`, где `cur_*` ≠ `new_*` (TRCK 262 · TIT2 49 · TALB 21 · TPE1 21). +> `source` пустой у 320/353. **Две причины:** +> 1. **Ложная логика генератора** — `50 cent-Candy Shop.mp3`: `cur_title=Shop` vs `new_title=Candy Shop`, а `no_change`. +> 2. **Устаревший `cur_*`** — `!Retro/Крылья.mp3`: план `cur_artist=Наутилус Помпилиус` (= new), +> **на диске реально `Nautilus Pompilius`** → генератор решил «совпадает», поставил `no_change`. +> +> **Следствие:** 262 файла `Sbornik_Novij_Rok` (`169_Steven Curtis Chapman` в имени, `TRCK=None` в теге) — +> номер так и **не записан**. 6 mojibake (`Ãðÿçíàÿ êðîâü`) — **не перезаписаны**. И т.д. +> +> **Правило:** `action` валидировать против **фактического диска**, а не `cur_*` из плана. +> Детектор: `/tmp/find_false_nochange.py` (353 строки, разбивка по полю/`source`/`verdict`). +> +> ⏭ **Дальше:** (1) пересчитать `action` по факту диска → дельта-план → dry-run → apply; +> (2) конвертация не-MP3 → MP3 (ffmpeg в образ, VBR V0, удаление после проверки бэкапа); +> (3) приёмка в Jellyfin; (4) после приёмки — удалить бэкап. +> +> **Прогресс apply/бэкапа (выполнено ранее):** +> ✅ **APPLY: 7041 файл, ошибок 0** (~17 мин, в фоне). ✅ **READ-BACK: не найдено 0.** +> ✅ Бэкап 7041 файл / 42 ГБ → `/music/backup/`. ✅ Бэкап не-MP3 70 файлов / 386 МБ → `/music/backup_raw/`. +> Блокер прав на `/mnt/RED_2TB/docker/` решён: каталог создан **из контейнера** +> (`docker run -u 0 -v /mnt/RED_2TB/docker:/docker alpine mkdir` + `chown 950:950`). > > ### 📊 НЕЗАВИСИМАЯ СВЕРКА `verify_tags.py` (2026-09-26) — 96.3% > @@ -99,22 +168,13 @@ related: > | MISMATCH (тег ≠ план) | 212 | > | Файлов на диске (индекс NFC) | 7822 | > -> **MISSING 83:** из видимых 10 — **9 `.wma` + 1 `.ogg`** (mutagen пишет только ID3/MP3 → -> ожидаемо не обрабатываются). Остальные 73 **не разобраны** — скрипт печатал только первые 10. +> **MISSING 83:** ✅ РАЗОБРАНО ПОЛНОСТЬЮ — **40 ogg + 26 m4a + 16 wma + 1 flac, все не-MP3** +> (mutagen пишет только ID3/MP3 → ожидаемо). **Ни одного MP3 не потеряно.** См. § ПОЛНЫЙ РАЗБОР выше. > -> **MISMATCH 212:** из видимых 30 — `TPE1` 16, `TRCK` 11, `TIT2` 9, `TALB` 6. Классы: -> - **mojibake (3)** — CP1251 прочитан как UTF-8: `Ãðÿçíàÿ êðîâü` → `Грязная кровь` -> (`Сектор Газа/05 - Грязная кровь.mp3`, `07 - Черный вурдалак.mp3`, `НТЛ/САВЛО&LIL'DEN vs. NTL.mp3`). -> Это **битые исходные ID3**, не результат apply. ⚠️ mutagen их не перезаписал. -> - **регистр/транслит** — `Ace of Base` / `Nautilus Pompilius` / `Fleur` / `Vanessa-Mae` / `Иванушки Int`. -> - **TRCK пустой или чужой** — `None`, `14`→`1`, `2`→`None`, `0`→`24`. -> - **TALB с мусором** — `Лучшие песни. Часть 2` (план: `Лучшие песни`). -> - **TIT2 усечён/с артистом внутри** — `Shop` vs `Candy Shop`; `Sander Kleinenberg - The Fruit…`. -> -> ⚠️ **Статус разбора: НЕПОЛНЫЙ.** Лог обрезан (скрипт печатал `первые 10`/`первые 30`). -> Фикс (печать всех) готов локально в `/tmp/verify_tags.py`, **но НЕ залит** — апрув истёк дважды. -> Причины 212 MISMATCH **не установлены**: возможны правки после apply, дубли имён файлов, -> битые исходные теги. +> **MISMATCH 212:** ✅ РАЗОБРАНО ПОЛНОСТЬЮ — **все 212 строк с `action=no_change`/`skip`**, +> `apply` их не трогал, расхождение было ДО apply. **Корень: ложный `no_change` в плане (353 строки).** +> См. § ГЛАВНЫЙ ДЕФЕКТ ПЛАНА выше. Поля: TRCK 102 · TPE1 98 · TALB 29 · TIT2 25. +> Классы: REAL_DIFF 135 · TRCK_LEADING_ZERO 49 · EMPTY_ON_DISK 35 · EXTRA_IN_DISK 14 · MOJIBAKE 6. > > ### 🔴 ПИТФОЛЛ: скрипт в каталоге проекта НЕ попадает в образ > @@ -137,8 +197,11 @@ related: > ⚠️ **Пул:** после бэкапа `df` показал `RED_2TB` = 935G (было 976G) — ZFS-снапшоты. Свободно 919 ГБ. > Бэкап (`/music/backup/`, 7041 файл) удалять **только после приёмки в Jellyfin**. > -> ⏭ **Осталось:** (1) получить полный список 83 MISSING + 212 MISMATCH → разобрать причины; -> (2) приёмка в UI Jellyfin (глазами Alex); (3) после приёмки — удалить бэкап. +> ⏭ **Осталось:** (1) ✅ полный разбор 83 MISSING + 212 MISMATCH — **сделан, вскрыл ДЕФЕКТ ПЛАНА +> (353 строки ложного `no_change`) + факт-чек (165 реальных правок)**; +> (2) 🔴 **ответ Alex по TRCK (A/B/C) — 75 правок «удалить»** → затем **apply дельта-правок (165)** +> (бэкап уже есть); (3) конвертация не-MP3 → MP3 (**ждёт `go`**); +> (4) приёмка в UI Jellyfin (глазами Alex); (5) после приёмки — удалить бэкап. > > ### 🔴 ПИТФОЛЛ: sentinel-группа собирается по КОРНЮ сборника, НЕ по ближайшей папке >