From 412fca65aec1e3eb904447c6dfb1d572f91e1593 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Sun, 27 Sep 2026 12:17:46 +0600 Subject: [PATCH] [2026-09-27] eagle: family/plans/music-jellyfin-lidarr-taiga.md personal/plans/music-artist-albumartist-repair.md personal/plans/tagwriter-container.md personal/tech/music-tags-repair-truenas.md --- family/plans/music-jellyfin-lidarr-taiga.md | 83 +++++++++++++++- .../plans/music-artist-albumartist-repair.md | 42 ++++++-- personal/plans/tagwriter-container.md | 96 ++++++++++++++++++- personal/tech/music-tags-repair-truenas.md | 19 +++- 4 files changed, 231 insertions(+), 9 deletions(-) diff --git a/family/plans/music-jellyfin-lidarr-taiga.md b/family/plans/music-jellyfin-lidarr-taiga.md index 9ccb9bcb..f7dd5a66 100755 --- a/family/plans/music-jellyfin-lidarr-taiga.md +++ b/family/plans/music-jellyfin-lidarr-taiga.md @@ -1,4 +1,85 @@ -# Music → Jellyfin + Lidarr на Тайге — план (v9, 2026-09-27) +# Music → Jellyfin + Lidarr на Тайге — план (v10, 2026-09-27) + +> ## 🆕 v10: СКРИПТ-ПИСАТЕЛЬ ГОТОВ, БЭКАП ПРОВЕРЕН, ЖДЁТ «GO» +> +> ✅ **Вся подготовка закрыта. Файлы музыки НЕ тронуты массово — ждёт «go» от Alex.** +> +> | Этап | Артефакт | Итог | +> |---|---|---| +> | Сбор тегов | `raw_tags_full.tsv` | 7861 строка, md5 `0c3647fadb6556b4100c555ed59bfb44` | +> | Инвентарь | `tag-inventory3.csv` | 7861 строка, 13 колонок (+`albumartist`) | +> | План artist/albumartist/album | `fix-plan-FINAL.csv` | 7861 строка, **0 пустых** | +> | План жанров | `genre-plan-FINAL.csv` | 7861 строка, **65 уникальных, 0 пустых** | +> | Мусор в тегах | — | 10 классов вычищено (спам-URL 51, мойибаке 21, заглушки 325…) | +> | ⭐ **ЕДИНЫЙ ПЛАН** | **`APPLY-PLAN-FINAL.csv`** | **7861 строка, 7628 изменений (97%), все 5 полей заполнены** | +> | **Бэкап** | `Music/backup/` | ✅ **7861 MP3, сверка по относительным путям 1:1** | +> | **Скрипт-писатель** | `apply_tags_v2.py` | ✅ **написан, 4 дефекта v1 закрыты, тест 5 файлов пройден** | +> +> ### ✅ `album` ДОЗАПОЛНЕН ПО ПАПКЕ — 452 → 0 пустых +> +> Alex спросил «Как у тебя спам оказался в albumartist?» → вопрос вскрыл **двух вещей**: +> 1. **Спам был в `cur_albumartist`, НЕ в `new_albumartist`** — правило «сборник → Various Artists» +> перезаписывает target целиком, старый спам уходит сам. Проверка: `новых со спамом: 0`. +> 2. **Реальная недоделка — 452 файла с пустым `album`** (206 АЛЬБОМ, 165 ДЕТСКИЕ-СБОРНИК, +> 74 АЛЬБОМ-мульти, 5 СКАЗКИ). Решение `fill_album.py`: **`album` = имя папки** +> (для `misc`/`disk` — родитель; для буквенных папок детских сборников — буква). +> +> ### ✅ БЭКАП ПРОВЕРЕН ФАКТОМ (Alex: «Бэкап же уже снят?») +> +> ``` +> backup: 7861 mp3 из 8478 файлов 50 ГБ ← 1:1 С ОСНОВНОЙ БИБЛИОТЕКОЙ +> backup_conv40: 40 mp3 251 МБ +> backup_fix: 165 mp3 1.4 ГБ +> backup_raw: 0 mp3 (71 файл wma/ogg) 411 МБ +> Сверка по относительным путям: 0 лишних в обе стороны +> ``` +> +> ⚠️ **Питфолл:** `/storage/Music` **не существует на хосте NAS** — только внутри контейнера +> (`/mnt/RED_2TB/storage` → `/storage`, ro). Первая проверка дала ложный вывод «бэкапа нет». +> **Правильно: `M=/mnt/RED_2TB/storage/Music`.** +> +> 🔴 **Вопрос бэкапа ЗАКРЫТ — копировать НЕ нужно.** +> +> ### ✅ `cur_*` НЕ СЛОМАНЫ (Alex: «То есть ты сломал cur поля?») +> +> ``` +> paths не найдено в inventory: 0 +> расхождений cur_* vs inventory: 1 ← пробел в genre у одного файла +> спам-значений в cur_*: 40 ← ВСЕ на месте (СОХРАНЁН) +> ``` +> +> **Причина подозрения:** в отчёте было «спам вычищен из album», без различения `cur_*`/`new_*`. +> **Правило: в отчётах явно писать, какое поле.** +> +> ### ✅ СКРИПТ `apply_tags_v2.py` — ВСЕ 4 ДЕФЕКТА v1 ЗАКРЫТЫ +> +> Alex: «Бери скрипт которым ты уже прогонял apply и фикси его чтобы писал все нужные поля и +> сносил старые **но не терял данных которые не надо терять**… исправляй проблему пропуска файлов, +> а так же **вывод прогресса и финальную сводку**. **Не апплаить без команды!**» +> +> | # | Дефект v1 | Фикс v2 | +> |---|---|---| +> | D1 | `delall` ВНУТРИ `if value:` → пустое значение не удаляло тег | `delall` **всегда**, `add` — по значению | +> | D2 | `_frames()` без `TPE2`/`TCON` | пишем `TIT2`,`TPE1`,`TPE2`,`TALB`,`TCON`,`TRCK` | +> | D3 | индекс без фильтра `.mp3` → порча не-MP3 | `is_mp3_file()`, пропущено не-mp3 **560** | +> | D4 | нет прогресса/сводки | шаг **250**, итог по полям, `--verify` + `apply-report.csv` | +> +> **«Не терять данные»:** сохраняются `APIC` (обложка), `TDRC` (год), `TLEN`, `TPUB`, `USLT` — +> удаляются только 6 управляемых ID; `save()` — **в тот же путь** (иначе теряется MPEG-поток). +> +> **Тест 5 файлов:** `APPLY ok=5 err=0` · `TPE2 записан 5` · `VERIFY 5/5 MISMATCH=0` · +> `APIC сохранён` · `первые байты b'ID3'` · `18 718 742 байт`. +> +> **Образ `tagwriter:v2`** собран, CSV на NAS: `/mnt/RED_2TB/docker/tagwriter/data/apply-plan.csv`. +> Полный разбор → [[personal/plans/tagwriter-container]] §v9. +> +> ### ⏭ ОСТАЛОСЬ +> +> 1. 🔴 **ЖДЁТ «go» ОТ ALEX** — массовый `--apply --verify` по 7861 +> 2. ⏳ Приёмка: **видно в UI** (Navidrome/Jellyfin), не read-back ассистента +> 3. ⏳ Деплой Navidrome (план — [[family/how-to/navidrome-truenas-android]]) +> 4. ⏳ Lidarr (исходная задача, отложена) +> 5. ⏳ После приёмки: удаление бэкапов + 29 битых/DRM > ## 🆕 v9: ЕДИНЫЙ ПЛАН НА ПРИМЕНЕНИЕ ГОТОВ — `APPLY-PLAN-FINAL.csv` (7861 строка) > diff --git a/personal/plans/music-artist-albumartist-repair.md b/personal/plans/music-artist-albumartist-repair.md index 5516d186..5def5b1b 100755 --- a/personal/plans/music-artist-albumartist-repair.md +++ b/personal/plans/music-artist-albumartist-repair.md @@ -4,6 +4,7 @@ created: '2026-09-27' updated: '2026-09-27' type: plan namespace: personal +status: script-reviewed-tested-awaiting-go tags: - music - metadata @@ -16,7 +17,7 @@ tags: related: - '[[family/plans/music-jellyfin-lidarr-taiga]]' - '[[personal/tech/music-tags-repair-truenas]]' - - '[[personal/tech/jellyfin-tags-cp1251-unreadable]]' + - '[[personal/plans/tagwriter-container]]' - '[[family/how-to/navidrome-truenas-android]]' --- @@ -700,11 +701,39 @@ paths не найдено в inventory: 0 8. ✅ **Скрипт-писатель `apply_tags_v2.py` НАПИСАН** — все 4 дефекта v1 закрыты, образ `tagwriter:v2` собран, **тест на 5 файлах пройден** (`ok=5 err=0`, `VERIFY 5/5 MISMATCH=0`) → [[personal/plans/tagwriter-container]] -9. 🔴 **ЖДЁТ «go» ОТ ALEX** — массовый прогон `--apply --verify` по `APPLY-PLAN-FINAL.csv` (7861) -10. ⏳ Приёмка Alex: **видно в UI** (Navidrome/Jellyfin), не «read back» ассистента -11. ⏳ После приёмки: откат 40 из `backup_conv40/` (если нужен), удаление 29 битых/DRM, +9. ✅ **СКРИПТ ПОЛНОСТЬЮ ПЕРЕПРОВЕРЕН** (Alex: «Ещё раз проверь весь текст скрипта на + адекватность») — найдено **6 дефектов, один критический**: + - 🔴 **P1: `TRCK` СТИРАЛСЯ БЕЗВОЗВРАТНО** — `_managed_frame_ids()` содержал `TRCK` + (на удаление), но `_frames()` его не создавал, а **колонки `track`/`new_track` в CSV + НЕТ ВООБЩЕ** → номер трека исчез бы у всех 7861 файла, восстановить нечем. + **Фикс:** `_managed_frame_ids()` = ровно `FIELD_MAP`. + - P2 docstring врал (обещал `TDRC`) · P3 `TDRC` сохранялся случайно · P4 `changed=no` + перезаписывались (233 файла) → `--force` · P5 нет защиты пути → `ValueError` при выходе + за `MUSIC` · P6 `save()` не атомарный (известный риск, бэкап есть). +10. ✅ **ТЕСТ «ДО/ПОСЛЕ» НА ЖИВОМ ФАЙЛЕ** — `snapshot.py` → apply → `snapshot.py` → `cmp_snap.py` + на файле с **9 фреймами** (`Высоцкий/Рецидивист/09 Рецидивист.mp3`, `TRCK='9'`): + **`TRCK` СОХРАНЁН · `TCOM`/`TSSE` сохранены · потеряно 0 фреймов · размер 4148743 → 4148743** +11. 🔴 **ЖДЁТ «go» ОТ ALEX** — массовый прогон `--apply --verify` по `APPLY-PLAN-FINAL.csv` (7861). + ⚠️ **Перед боевым прогоном вернуть 8 тестовых файлов из бэкапа** (`!Классика` ×5, + `Высоцкий` ×2, `Камбурова` ×1 — они уже с новыми тегами после тестов). +12. ⏳ Приёмка Alex: **видно в UI** (Navidrome/Jellyfin), не «read back» ассистента +13. ⏳ После приёмки: откат 40 из `backup_conv40/` (если нужен), удаление 29 битых/DRM, удаление бэкапов `backup/` + `backup_raw/` + `backup_fix/` + `backup_conv40/` -12. ⏳ Lidarr + Navidrome (первоначальная задача Alex, ещё не начата) +14. ⏳ Lidarr + Navidrome (первоначальная задача Alex, ещё не начата) + +## 🔴 ПРАВИЛО (открыто в этой сессии): «управляемый» набор фреймов = набор записываемых + +`TRCK` стоял в списке **на удаление**, но отсутствовал в списке **на запись** и в CSV. +**Фрейм, которым скрипт «управляет», но значение которого нигде не задано, — это потеря данных +по определению.** Валидировать инвариант ДО прогона: + +```python +assert sorted(_managed_frame_ids()) == sorted(fid for _, fid, _ in FIELD_MAP) +``` + +Тот же класс уже стоил библиотеке 40 MP3 (`fix40.py`) и 16 не-MP3 (`apply_tags.py` без `.mp3`-фильтра) — +**ловушка в списках «что трогаем» находится только построчным перечитыванием, а не тестами на живых файлах** +(тест прошёл бы успешно: `delall(TRCK)` + отсутствие `add` не вызывают ошибки, просто тихо стирают тег). ### ✅ Бэкап: вопрос ЗАКРЫТ (Alex: «Бэкап же уже снят?») @@ -758,7 +787,8 @@ APIC сохранён · первые байты b'ID3' · 18 718 742 байт ``` /Users/admin/milestones/music-csv-2026-09-26/ -├── APPLY-PLAN-FINAL.csv ⭐⭐ 7861 стр — ЕДИНЫЙ ПЛАН НА ПРИМЕНЕНИЕ (14 колонок) +├── APPLY-PLAN-FINAL.csv ⭐⭐⭐ 7861 стр — ЕДИНЫЙ ПЛАН НА ПРИМЕНЕНИЕ (14 колонок) +│ все 5 полей заполнены, валидация по нулям ├── fix-plan-FINAL.csv ⭐ 7861 стр, план artist/albumartist/album ├── genre-plan-FINAL.csv ⭐ 7861 стр, план жанров (65 уник., 0 пусто) ├── APPLY-PLAN.csv промежуточный единый (до чистки мусора) diff --git a/personal/plans/tagwriter-container.md b/personal/plans/tagwriter-container.md index c6d2dcd6..4116ba7b 100755 --- a/personal/plans/tagwriter-container.md +++ b/personal/plans/tagwriter-container.md @@ -17,9 +17,103 @@ updated: '2026-09-26' --- # tagwriter — контейнер применения тегов MP3 (TrueNAS) -_Создан: 2026-09-26. **Статус (обновлён 2026-09-27): ✅ `apply_tags_v2.py` НАПИСАН, ВСЕ 4 ДЕФЕКТА v1 ЗАКРЫТЫ (D1 `delall` вне `if` · D2 +`TPE2`/`TCON` · D3 фильтр `.mp3` · D4 прогресс+сводка+`--verify`), ОБРАЗ `tagwriter:v2` СОБРАН, ТЕСТ НА 5 ФАЙЛАХ ПРОЙДЕН (`ok=5 err=0`, `VERIFY совпало=5 MISMATCH=0`, APIC сохранён, MPEG-поток цел). 🔴 **ФАЙЛЫ МУЗЫКИ НЕ ТРОНУТЫ МАССОВО — ЖДЁТ «go» ОТ ALEX.** ⏳ НЕ выполнено: массовый apply по `APPLY-PLAN-FINAL.csv` (7861), откат 40 из `backup_conv40/`, удаление 29 битых/DRM, приёмка Jellyfin. Бэкапы готовы и проверены по путям: `backup/` 7861 MP3 (1:1 с основной библиотекой), `backup_conv40/` 40, `backup_fix/` 165, `backup_raw/` 71. Решение Alex по TRCK = **B** (номера не удалять).**_ +_Создан: 2026-09-26. **Статус (обновлён 2026-09-27): ✅ `apply_tags_v2.py` НАПИСАН + ПОЛНОСТЬЮ ПЕРЕПРОВЕРЕН (v10). ЗАКРЫТЫ 4 ДЕФЕКТА v1 (D1 `delall` вне `if` · D2 +`TPE2`/`TCON` · D3 фильтр `.mp3` · D4 прогресс+сводка+`--verify`) + 6 ДЕФЕКТОВ, найденных при перечитывании (P1 🔴 `TRCK` стирался безвозвратно · P2 docstring врал · P3 `TDRC` сохранялся случайно · P4 `changed=no` перезаписывались · P5 нет защиты пути · P6 `save()` не атомарный). ОБРАЗ `tagwriter:v2` СОБРАН. ТЕСТЫ: 5 файлов (`ok=5 err=0`, `MISMATCH=0`) + 3 файла (`ok=3 err=0`, `MISMATCH=0`) + ТЕСТ «ДО/ПОСЛЕ» на файле с 9 фреймами (`TRCK` СОХРАНЁН, `TCOM`/`TSSE` сохранены, размер 4148743 → 4148743 байт, потеряно 0 фреймов). 🔴 **ФАЙЛЫ МУЗЫКИ НЕ ТРОНУТЫ МАССОВО — ЖДЁТ «go» ОТ ALEX.** ⏳ НЕ выполнено: массовый apply по `APPLY-PLAN-FINAL.csv` (7861), возврат 8 тестовых файлов из бэкапа, откат 40 из `backup_conv40/`, удаление 29 битых/DRM, приёмка Jellyfin. Бэкапы готовы и проверены по путям: `backup/` 7861 MP3 (1:1 с основной библиотекой), `backup_conv40/` 40, `backup_fix/` 165, `backup_raw/` 71. Решение Alex по TRCK = **B** (номера не удалять).**_ _Основание: [[personal/plans/music-tags-wave38-fixes]]._ +## ✅ v10 (2026-09-27): ПОЛНОЕ ПЕРЕЧИТЫВАНИЕ СКРИПТА — 🔴 НАЙДЕН ДЕФЕКТ УНИЧТОЖЕНИЯ `TRCK` + +**Задача Alex:** «**Ещё раз проверь весь текст скрипта на адекватность.**» + +Перечитан весь `apply_tags_v2.py` (333 строки) построчно → найдено **6 проблем, одна критическая**. +Все исправлены **до** боевого прогона. + +### 🔴🔴 P1 (КРИТИЧНО): `TRCK` СТИРАЛСЯ БЕЗВОЗВРАТНО + +**Механика ловушки:** + +```python +_managed_frame_ids() → ["TIT2","TPE1","TPE2","TALB","TCON","TRCK"] # TRCK стоит на удаление +_frames(r) → TIT2, TPE1, TPE2, TALB, TCON — TRCK НЕ создаётся +CSV APPLY-PLAN-FINAL → колонки track / new_track ОТСУТСТВУЮТ ВООБЩЕ +``` + +**`delall("TRCK")` выполнялся бы, `add(TRCK)` — никогда.** Номер трека исчез бы у **всех 7861 файла**, +и восстановить его было бы **нечем** — данных о треке нет ни в CSV, ни где-либо ещё. + +**Фикс:** + +```python +def _managed_frame_ids(): + """🔴 НЕ добавлять сюда фрейм, которого нет в _frames()/CSV — + он будет стёрт без возможности восстановления (так было с TRCK).""" + return [fid for _, fid, _ in FIELD_MAP] # TIT2 TPE1 TPE2 TALB TCON +``` + +**🔴 ПРАВИЛО: набор «управляемых» фреймов = РОВНО набор записываемых. Управляемый, но не +записываемый фрейм = гарантированная потеря данных.** Проверка инварианта: +`sorted(_managed_frame_ids()) == sorted(fid for _,fid,_ in FIELD_MAP)` → `True`. + +### Остальные 5 исправлений + +| # | Проблема | Было | Стало | +|---|---|---|---| +| **P2** | docstring врал | «пишем TDRC», реально TDRC не писался | честный список: `ЧТО ПИШЕТ` / `ЧТО НЕ ТРОГАЕТ` | +| **P3** | `TDRC` (год) сохранялся **случайно** | не был в списке удаления — везение, не замысел | задокументировано как «не трогаем» + в `ЧТО НЕ ТРОГАЕТ` | +| **P4** | `changed=no` обрабатывались | 233 файла зря перезаписывались (`delall`+`add` на неизменном) | `build_jobs(force=...)` пропускает их, флаг `--force` для перезаписи | +| **P5** | нет защиты пути | `/storage/Music/../../etc` прошёл бы | `map_path()` → `os.path.normpath` + `ValueError` при выходе за `MUSIC` | +| **P6** | `save()` не атомарный | обрыв (kill/диск полный) → битый файл | оставлено (бэкап есть), но зафиксировано как известный риск | + +### ✅ ТЕСТ «ДО/ПОСЛЕ» НА ЖИВОМ ФАЙЛЕ — `TRCK` ВЫЖИЛ + +**Метод:** `snapshot.py` (снимок фреймов + значений в JSON) → `--apply` → `snapshot.py` снова → +`cmp_snap.py` (побайтовое сравнение списка фреймов и значений). + +**Тестовый файл с 9 фреймами** (выбран `find_trck.py` — искал `.mp3` с непустым `TRCK`): + +``` +!Барды/Владимир Высоцкий/Рецидивист/09 Рецидивист.mp3 +фреймы: TALB, TCOM, TCON, TIT2, TPE1, TPE2, TRCK, TSSE, TXXX ← 9 штук +TRCK = '9' +``` + +**Результат сравнения:** + +``` +поле ДО ПОСЛЕ ВЕРДИКТ +TCON Поп Pop ИЗМЕНЕН (план) ✓ +TRCK 9 9 СОХРАНЁН ✓ +TCOM Владимир Высоцкий Владимир Высоцкий СОХРАНЁН ✓ +TSSE Lavf60.16.100 Lavf60.16.100 СОХРАНЁН ✓ + +фреймы: потеряно ничего, появилось ничего +размер: 4148743 → 4148743 (разница 0) +ИТОГ: ✓ ВСЁ СОХРАНЕНО +``` + +### Тест из 3 файлов (разные случаи) + +``` +APPLY: 3 файла — ok=3 err=0 + TPE2 (albumartist): 1 · TALB (album): 1 · TCON (genre): 3 +VERIFY: совпало 3/3, MISMATCH 0 +``` + +### 📄 Инструменты верификации (в `/mnt/RED_2TB/docker/tagwriter/data/`) + +| Скрипт | Назначение | +|---|---| +| `snapshot.py` | снимок фреймов файла в JSON (список ID + значения 9 ключевых) | +| `cmp_snap.py` | сравнение двух снимков: что сохранено, что изменено, что потеряно | +| `find_trck.py` | поиск mp3 с непустым `TRCK` (для выбора тестового файла) | +| `make_test_csv.py` | мини-CSV из указанных путей — для точечного теста | +| `check_one.py` | быстрый отчёт по одному файлу (фреймы, значения, байты) | + +### 🔴 ПИТФОЛЛ: `--entrypoint python3` требует, чтобы скрипт БЫЛ в смонтированном каталоге + +Первый запуск `snapshot.py` → `can't open file '/app/data/snapshot.py': No such file or directory`, +хотя `scp` вроде прошёл. **Проверять `ls -la` монтируемого файла ПОСЛЕ `scp`, до `docker run`.** +`/app` — образ, `/app/data` — монтирование; `scp` в `data/` нужен **и** `-v .../data:/app/data`. + ## ✅ v9 (2026-09-27): `apply_tags_v2.py` — НОВЫЙ СКРИПТ-ПИСАТЕЛЬ, ВСЕ 4 ДЕФЕКТА ЗАКРЫТЫ **Задача Alex:** «Бери скрипт которым ты уже прогонял apply и фикси его чтобы писал все нужные diff --git a/personal/tech/music-tags-repair-truenas.md b/personal/tech/music-tags-repair-truenas.md index 5d605043..ed5742b2 100755 --- a/personal/tech/music-tags-repair-truenas.md +++ b/personal/tech/music-tags-repair-truenas.md @@ -39,8 +39,25 @@ related: > - **сборник ≠ «>1 артиста»** — это ловит альбомы с фитами; Alex дал прямые примеры > - процессный запрет Alex: **сначала ручные списки, скриптом — только обновление CSV** > - ✅ `fix-plan-FINAL.csv` — 7861 стр, **0 пустых `artist`**, 7233 изменения -> - ✅ `genre-plan-FINAL.csv` — 7861 стр, **82 уникальных жанра** (было 154), 51 пустой, +> - ✅ `genre-plan-FINAL.csv` — 7861 стр, **65 уникальных жанра** (было 154), **0 пустых**, > мойибаке `Psy Årance` → `Psy-Trance` (29 файлов, `Å`/`Е` — латиница vs кириллица) +> +> ### ✅ ОБНОВЛЕНИЕ 2026-09-27 (вечер) — ПЛАН ЗАКРЫТ, СКРИПТ ГОТОВ, ЖДЁТ «GO» +> +> | Шаг | Факт | +> |---|---| +> | `APPLY-PLAN-FINAL.csv` | ✅ 7861 стр, 7628 изменений, **все 5 полей заполнены** (title/artist/albumartist/album/genre) | +> | `album` дозаполнен по папке | ✅ 452 → **0 пустых** | +> | Валидация | ✅ дубли 0 · спам 0 · мойибаке 0 · пустых 0 · class↔albumartist 0 | +> | Бэкап | ✅ `backup/` **7861 MP3**, сверка по относительным путям **1:1** (0 расхождений) | +> | `cur_*` | ✅ **не сломаны** — 1 расхождение = пробел в `genre` | +> | Скрипт `apply_tags_v2.py` | ✅ написан, 4 дефекта v1 закрыты; **⚠️ ПОСЛЕ ПОЛНОЙ ПЕРЕПРОВЕРКИ исправлено ещё 6 дефектов** (🔴 `TRCK` стирался безвозвратно · `changed=no` перезаписывались · нет защиты пути · docstring врал); тесты 5+3 файла `MISMATCH=0` + «ДО/ПОСЛЕ» на 9-фреймовом файле (`TRCK` сохранён, потеряно 0 фреймов) | +> | 🔴 Дальше | **ЖДЁТ «go» ОТ ALEX** — массовый `--apply --verify` по 7861. ⚠️ Перед прогоном вернуть 8 тестовых файлов из бэкапа | +> +> Питфоллы сессии: `cur_*` ≠ `new_*` в отчётах · `/storage/Music` не существует на хосте (только +> `/mnt/RED_2TB/storage/Music`, в контейнере `/storage` ro) · инлайн-python через ssh ломается на +> кавычках · tirith блокирует кириллицу вплотную к ASCII. +> Детали → [[personal/plans/music-artist-albumartist-repair]] и [[personal/plans/tagwriter-container]]. > ## ✅ ТРИДЦАТЬ ВОСЬМАЯ ВОЛНА (2026-09-26) — ФИНАЛЬНЫЙ АУДИТ + 4 ПРАВКИ + ПРИМЕНЕНИЕ НА NAS >