[2026-09-27] eagle: personal/plans/tagwriter-container.md personal/tech/music-tags-repair-truenas.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-27 00:29:45 +06:00
1 parent ae931acd15
commit 627d06c07f
2 files changed
+263 -46

No files matched your search

+179 -25
View File
@@ -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, операция продолжается «осиротевшей».<br>Два независимых долгих процесса можно вести **параллельно** (ЧТЕНИЕ тегов + КОПИРОВАНИЕ файлов не конфликтуют) — 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/неверные значения — это **дефект**, а не норма. Сначала классификация по группам и файлам, потом вывод.
## Связано
+84 -21
View File
@@ -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-группа собирается по КОРНЮ сборника, НЕ по ближайшей папке
>