From 68356f16778c29703cb3c6064dfb7a462633fe87 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Sun, 27 Sep 2026 21:34:55 +0600 Subject: [PATCH] [2026-09-27] eagle: personal/plans/music-artist-albumartist-repair.md personal/tech/music-tags-repair-truenas.md --- .../plans/music-artist-albumartist-repair.md | 147 ++++++++++++++++-- personal/tech/music-tags-repair-truenas.md | 39 ++++- 2 files changed, 166 insertions(+), 20 deletions(-) diff --git a/personal/plans/music-artist-albumartist-repair.md b/personal/plans/music-artist-albumartist-repair.md index 5f370dad..29aeeb5a 100755 --- a/personal/plans/music-artist-albumartist-repair.md +++ b/personal/plans/music-artist-albumartist-repair.md @@ -4,7 +4,7 @@ created: '2026-09-27' updated: '2026-09-27' type: plan namespace: personal -status: v3-verified-3-discrepancies / dry-run-blocked-on-alex-decision +status: dry-run-done / boevoy-zapusk-pravilnyy-cmd-ne-podtverzhden tags: - music - metadata @@ -46,12 +46,17 @@ related: Канон: ~/Downloads/music-tags/APPLY-PLAN.csv (7861 строка — НЕ ПЕРЕЗАПИСЫВАТЬ) ``` -**Состояние:** артефакты v3 проверифицированы 27 сен. **Dry-run НЕ запущен** — -блокирует решение Alex: дифф считает группы (genre 911), dry-run считает файлы -(genre 4359). См. § ВЕРИФИКАЦИЯ v3. +**Состояние (27 сен, сессия ~30):** ✅ **dry-run ВЫПОЛНЕН УСПЕШНО** — `7653 к правке`, +`mp3 на диске 7861`, `не найдено 0`. Требование Alex «совпасть по цифрам с +APPLY-PLAN-v3-changes» **снято самим Alex** («нихуя не понял… очевидно что полный набор»): +дифф считает группы, dry-run — файлы, совпадение недостижимо by design. Решение Alex: +**пишем полный набор полей** (не только `TCON`). -**Открытый дефект:** жанры (`TCON`) не записаны в теги — прогон шёл старым скриптом. -Скрипт уже исправлен, **прогон не сделан**. +🔴 **БОЕВОЙ ПРОГОН НЕ СОСТОЯЛСЯ — запущен с неверным аргументом.** См. § БОЕВОЙ ПРОГОН, +немедленно читать перед повтором. + +**Открытый дефект:** жанры (`TCON`) в тегах отсутствуют. Скрипт исправлен, dry-run доказал, +что `TCON` пишется. Боевой прогон — ждёт корректной команды. **Доступ к NAS:** `ssh -i ~/.ssh/id_rsa truenas_admin@mallexxx.duckdns.org` ⚠️ `192.168.1.100` — ЧУЖАЯ подсеть, не использовать. Подробнее → [[family/how-to/truenas-access]] @@ -71,6 +76,8 @@ related: | 7 | 27 сен | `.ndignore` — 6 бэкапов исключены, Navidrome их не видит | `.ndignore` | | 8 | 27 сен | **Канонный дифф v3 собран** — сортировка по `field`, схлопывание, шум убран | `DIFF-base-vs-applyplan-v3.csv` | | 9 | 27 сен | **Верификация артефактов v3 перед dry-run** — 3 расхождения вскрыты, см. § ВЕРИФИКАЦИЯ | `/tmp/verify_v3*.py` | +| 10 | 27 сен | **Dry-run выполнен: 7653 к правке, не найдено 0** — `TCON` пишется | `/tmp/dryrun.log` | +| 11 | 27 сен | 🔴 **Боевой прогон не состоялся** — `CMD=--dry-run` образа подставился | `/tmp/tw306/run.log` | --- @@ -123,16 +130,33 @@ python3 /tmp/make_applyplan_v3.py # → /tmp/apply-plan-v3.csv ``` -### Применить теги (образ tagwriter) +### Применить теги (образ tagwriter) — 🔴 ЧИТАТЬ `CMD` ОБРАЗА ПЕРЕД ЗАПУСКОМ ```bash -# сухой прогон +# 1. ВСЕГДА сначала посмотреть, что в CMD образа: +docker inspect tagwriter:latest --format 'CMD={{json .Config.Cmd}} ENTRY={{json .Config.Entrypoint}}' +# ФАКТ 27 сен: CMD=["--dry-run"] — то есть --dry-run ПОДСТАВЛЯЕТСЯ АВТОМАТИЧЕСКИ. + +# 2. СУХОЙ ПРОГОН — скрипт лежит в /app/, НЕ в /app/data/: +docker rm -f twdry >/dev/null 2>&1 docker create --name twdry --entrypoint python3 \ -v /tmp/tw306:/app/data:ro -v /mnt/RED_2TB/storage/Music:/music:rw \ - tagwriter:latest /app/data/apply_tags_v2.py --dry-run && docker start -a twdry + tagwriter:latest /app/apply_tags_v2.py --dry-run +docker start -a twdry + +# 3. БОЕВОЙ ПРОГОН — CMD переопределить явно, иначе будет dry-run (см. ПИТФОЛЛ 13): +docker rm -f twrun >/dev/null 2>&1 +docker create --name twrun --entrypoint python3 \ + -v /tmp/tw306:/app/data:ro -v /mnt/RED_2TB/storage/Music:/music:rw \ + tagwriter:latest /app/apply_tags_v2.py --apply # ← ФЛАГ ПОДТВЕРДИТЬ В СКРИПТЕ +docker start -a twrun ``` -- Образ `tagwriter:latest` (НЕ `v2`), uid 950:950, amd64 -- План читается ТОЛЬКО как `/app/data/apply-plan.csv` -- Скрипт: `/app/apply_tags_v2.py` — пишет `TIT2 TPE1 TPE2 TALB TCON TRCK` +- Образ `tagwriter:latest` (НЕ `v2`), uid 950:950, amd64 · 25971 байт `apply_tags_v2.py` +- **План читается ТОЛЬКО как `/app/data/apply-plan.csv`** → подкладывать + `/tmp/apply-plan-v3.csv` под этим именем +- **Скрипт лежит в `/app/`**, план — в `/app/data/`. Путать нельзя (см. ПИТФОЛЛ 14) +- Скрипт пишет `TIT2 TPE1 TPE2 TALB TCON TRCK` (все 6 полей сразу, режима «только genre» нет) +- Строки скрипта: 76 `("new_genre","TCON","genre")` · 206 `TCON(...)` · 351 MP3-ветка · + 515 верификация читает `TCON` обратно ### Пересканить Navidrome после правки тегов ```bash @@ -183,6 +207,8 @@ backup_raw /tmp/verify_v3.py верификация: лог vs план /tmp/verify_v3b.py верификация: ПАПКА/ТРЕК-строки диффа из плана /tmp/verify_v3c.py верификация: аномалии (genre-дубли, Sbornik, пропуск пути) +/tmp/parse_dry.py разбор лога dry-run по полям (только показанные 40 файлов!) +/tmp/dryrun.log вывод успешного dry-run (копия с NAS) ~/Developer/personal-os/tools/music-tags/build_fix_plan2.py генератор плана (пофикшен) ``` @@ -253,10 +279,77 @@ Dry-run считает ФАЙЛЫ: album 28 · albumartist 6 · genre 4359 (в | Дефект | Факт | Статус | |--------|------|--------| -| **Жанры не в тегах** | В БД `genre` пусто у всех 7861; прогон 27 сен не писал `TCON` | Скрипт исправлен, **прогон нужен** | +| **Жанры не в тегах** | В БД `genre` пусто у всех 7861; прогон 27 сен не писал `TCON` | Скрипт исправлен, dry-run доказал `TCON`. **Боевой прогон нужен** | +| 🔴 **Боевой прогон сорвался** | Запущен без переопределения `CMD` → подставился `--dry-run` образа | **Ничего не записано. Повтор с явным CMD** | | Жанры в диффе | 427 строк / 911 групп, покрытие 7861 — не «правка», а повторная запись | Учтено | | Лог v3 устарел | 3 строки + 1 путь расходятся с планом после `fix_ruki`/`fix_rockopop` | Решено: план — эталон, лог не пересобираем | -| Дифф: genre-группы | 211 ТРЕК-строк дублируют `new_genre`; 7 одиночек не добраны | **Ждём решения Alex** | +| Дифф: genre-группы | 211 ТРЕК-строк дублируют `new_genre`; 7 одиночек не добраны | **Alex снял вопрос:** пишем полный набор, дифф не пересобираем | + +--- + +## 🚀 DRY-RUN и БОЕВОЙ ПРОГОН (27 сен, сессия ~30) + +### ✅ DRY-RUN — ВЫПОЛНЕН УСПЕШНО + +```bash +scp -i ~/.ssh/id_rsa /tmp/apply-plan-v3.csv \ + truenas_admin@mallexxx.duckdns.org:/tmp/tw306/apply-plan.csv +# md5 на NAS = 6b9e49b2baa3f1d1708e4566972266a9 (= локальному) +``` +Вывод (`/tmp/dryrun.log`, 21955 B; на NAS `/tmp/tw306/dryrun.log`): +``` +CSV: /app/data/apply-plan.csv +строк в плане: 7861 + changed=yes требующих правки, но помеченных no: 4 +mp3 на диске: 7861 пропущено не-mp3: 562 +пропущено changed=no: 208 +не найдено на диске: 0 ← ни одного потерянного пути +к обработке: 7653 +=== DRY-RUN — ничего не записано (7653 файлов) === +ИТОГО к правке: 7653 +``` + +**`TCON` доказан рабочим** — живой пример из лога: +``` +!Барды/Владимир Высоцкий/Кони привередливые.mp3 + albumartist 'Vladimir Vysockij' -> 'Владимир Высоцкий' + album '(Высоцкий)' -> 'Владимир Высоцкий' + genre 'Разное' -> 'Бардовская песня' +``` + +⚠️ **Лог печатает только первые 40 файлов** из 7653 (`... и ещё 7613`), **итога по полям +в логе НЕТ** — только `ИТОГО к правке: 7653`. Не считать счётчики по 40 показанным +файлам: получается ложное «genre меняется у 37», хотя в плане genre меняется у **4359**. + +**Итог по полям берётся ИЗ ПЛАНА, а не из лога:** +``` +genre 4359 · albumartist 7213 · album 2031 · track 441 · artist 381 · title 8 +plan.changed=yes 7653 +``` + +### 🔴 БОЕВОЙ ПРОГОН — НЕ СОСТОЯЛСЯ (запущен с неверным аргументом) + +Запущено: +```bash +docker create --name twrun --entrypoint python3 \ + -v /tmp/tw306:/app/data:ro -v /mnt/RED_2TB/storage/Music:/music:rw \ + tagwriter:latest /app/apply_tags_v2.py # ← CMD НЕ переопределён +docker start -a twrun +``` +Результат: `exit=0`, но в `/tmp/tw306/run.log` строка +`=== DRY-RUN — ничего не записано (7653 файлов) ===`. + +**Причина:** у `tagwriter:latest` в `CMD` уже прописан `--dry-run`: +``` +ENTRYPOINT = ["python3", "/app/apply_tags_v2.py"] +CMD = ["--dry-run"] +``` +При `docker create` без своего CMD образ подставил свой → скрипт получил `--dry-run`. + +**✅ Ничего не записано, музыка не тронута, бэкап не потребовался.** + +**Осталось перед повтором:** подтвердить в скрипте флаг боевого режима (в доках он +не зафиксирован — проверялись только `--dry-run`), затем `docker create` **с явным CMD**. --- @@ -273,10 +366,23 @@ Dry-run считает ФАЙЛЫ: album 28 · albumartist 6 · genre 4359 (в genre 911 · album 28 · albumartist 6 Дифф на 100%: 7861 файл сопоставлен, не найдено 0 Лог v3: 31 строка +Dry-run 27 сен: план 7861 · mp3 на диске 7861 · не найдено 0 · к обработке 7653 + (пропущено changed=no 208, не-mp3 562) · лог печатает 40 файлов + changed=yes но расхождений нет: 4 файла (Infected Mushroom ×3, Пушной) +Боевой прогон: НЕ СОСТОЯЛСЯ — подставился `CMD=--dry-run` образа ``` --- +## 📁 ФАЙЛЫ НА NAS (рабочие, `/tmp/tw306/`) +``` +apply-plan.csv md5 6b9e49b2baa3f1d1708e4566972266a9 (= /tmp/apply-plan-v3.csv) +dryrun.log 21955 B — вывод успешного dry-run (есть копия на Mac /tmp/dryrun.log) +run.log вывод сорвавшегося боевого прогона (снова DRY-RUN) +check_album_applied*.py / verify_306*.py вспомогательные проверки + +--- + ## 🧨 ПИТФОЛЛЫ 1. **`192.168.1.100` — чужая подсеть.** NAS: `mallexxx.duckdns.org`. Агент объявлял @@ -302,6 +408,19 @@ Dry-run считает ФАЙЛЫ: album 28 · albumartist 6 · genre 4359 (в совпадающее с `cur_genre` (дубль групповой строки). Не считать их списком правок. 12. **Лог правок пересобирать ПОСЛЕ последнего фикса плана.** Иначе лог расходится с планом (так и вышло: `fix_ruki`/`fix_rockopop` прошли после генерации лога). +13. 🔴 **`CMD` ОБРАЗА ПОДСТАВЛЯЕТСЯ АВТОМАТИЧЕСКИ.** У `tagwriter:latest` прописан + `CMD=["--dry-run"]` → `docker create` БЕЗ своего CMD запускает dry-run, а не боевой + прогон. Внешне выглядит как успех (`exit=0`), в логе — «ничего не записано». + **Всегда:** `docker inspect --format 'CMD={{json .Config.Cmd}}'` ПЕРЕД `create` + и явный флаг в `create`. Проверять в логе строку про DRY-RUN, не только `exit=0`. +14. 🔴 **Скрипт в `/app/`, план в `/app/data/`.** `/app/data/apply_tags_v2.py` → + `[Errno 2] No such file or directory`. Только `/app/data/` — это точка монтирования, + скрипт туда не кладётся. +15. **Лог dry-run неполный (40 файлов из 7653) и без итогов по полям.** Цифры по полям + брать из плана (`cur_* != new_*`), не считать по обрезанному логу. +16. **Не предлагать Alex выбор, которого он не делал.** «Только жанры или полный набор?» + при живом плане = лишний круг и раздражение: набор очевиден из плана. Молчаливые + умолчания не выдумывать, но и очевидное не переспрашивать. --- diff --git a/personal/tech/music-tags-repair-truenas.md b/personal/tech/music-tags-repair-truenas.md index 681ad943..211a5327 100755 --- a/personal/tech/music-tags-repair-truenas.md +++ b/personal/tech/music-tags-repair-truenas.md @@ -23,18 +23,45 @@ related: # Music Tags Repair — TrueNAS (7906 → 7905 файлов) -> ## 🔴 АКТУАЛЬНОЕ СОСТОЯНИЕ (2026-09-27, сессия 29-ОКТИ) — ЧИТАЙ ЭТО ПЕРВЫМ +> ## 🔴 АКТУАЛЬНОЕ СОСТОЯНИЕ (2026-09-27, сессия ~30) — ЧИТАЙ ЭТО ПЕРВЫМ +> +> ✅ **DRY-RUN ВЫПОЛНЕН УСПЕШНО.** План `/tmp/apply-plan-v3.csv` (md5 +> `6b9e49b2baa3f1d1708e4566972266a9`) скопирован на NAS как `/tmp/tw306/apply-plan.csv` +> (md5 совпал). Вывод — `/tmp/dryrun.log` / NAS `/tmp/tw306/dryrun.log`: +> ``` +> строк в плане 7861 · mp3 на диске 7861 · не найдено 0 · к обработке 7653 +> пропущено changed=no 208 · не-mp3 562 · DRY-RUN, ничего не записано +> ``` +> **`TCON` доказан рабочим** — пример: `Кони привередливые.mp3` `genre 'Разное' → 'Бардовская песня'`. +> +> 🔴 **БОЕВОЙ ПРОГОН НЕ СОСТОЯЛСЯ — НИЧЕГО НЕ ЗАПИСАНО, музыка не тронута.** +> Причина: у `tagwriter:latest` в образе прописан `CMD=["--dry-run"]` → +> `docker create` без своего CMD запустил dry-run (`exit=0`, в логе снова DRY-RUN). +> **Перед повтором:** `docker inspect tagwriter:latest --format 'CMD={{json .Config.Cmd}}'` +> + явный флаг в `create`. См. § ПИТФОЛЛЫ доки плана. +> +> **Решение Alex (снят вопрос о диффе):** пишем **ПОЛНЫЙ НАБОР полей** (не только `TCON`), +> дифф НЕ пересобираем. Требование «dry-run должен совпасть с `APPLY-PLAN-v3-changes`» +> снято самим Alex: дифф считает ГРУППЫ, dry-run — ФАЙЛЫ, совпадение недостижимо by design. > > ✅ **КАНОННЫЙ ДИФФ v3 СОБРАН И ЗАКРЫТ:** `~/Downloads/music-tags/DIFF-base-vs-applyplan-v3.csv` -> — **945 записей** (`ПАПКА` 711 · `ТРЕК` 234), `md5 bdb20c6ae00e7dfab4da2f76653fe084`. -> Поля: `album` 28 · `albumartist` 6 · **`genre` 911 групп / 427 уникальных имён**. -> `track` — **0** (весь блок убран как шум, см. ниже). +> — **945 записей**, `md5 bdb20c6ae00e7dfab4da2f76653fe084`. Поля: `album` 28 · +> `albumartist` 6 · `genre` 911 (ГРУПП, не файлов). `track` — **0** (убран как шум). +> ⚠️ **`genre` в диффе НЕ выдержан по `scope`:** в папке `!Radio/w 2008-07` дифф даёт +> 5 значений, в плане 11 — одиночки отброшены правилом «ПАПКА, если ≥2 файла». +> **Числа полей брать ИЗ ПЛАНА** (`cur_* != new_*`): `genre 4359 · albumartist 7213 · +> album 2031 · track 441 · artist 381 · title 8`. > База 7861 ↔ план 7861, сопоставлено 7861, **не найдено 0** (фильтр `path NOT LIKE 'backup%'`). > Источник: `/tmp/apply-plan-v3.csv` (7861, из `new_*` `APPLY-PLAN.csv` + правило корня; -> **31 правка**, лог с колонкой `reason` — `~/Downloads/music-tags/APPLY-PLAN-v3-changes.csv`). +> **31 правка**, лог с колонкой `reason` — `~/Downloads/music-tags/APPLY-PLAN-v3-changes.csv`, +> ⚠️ **лог УСТАРЕЛ**: 3 строки расходятся с планом после `fix_ruki`/`fix_rockopop`). > Сборщик: `/tmp/build_canon_diff_v3.py`. > -> 🔴 **ВЕРИФИКАЦИЯ v3 (2026-09-27) — dry-run НЕ ЗАПУЩЕН, 3 расхождения.** Полный разбор — +> 🔴 **ЛОГ DRY-RUN НЕПОЛНЫЙ** — печатает 40 файлов из 7653, итогов по полям НЕТ. +> Не считать «genre меняется у 37» — это 40 показанных файлов, а не итог. +> +> ✅ **ВЕРИФИКАЦИЯ v3 (2026-09-27) — ВЫПОЛНЕНА, 3 расхождения.** dry-run с тех пор ЗАПУЩЕН +> и ПРОШЁЛ. Полный разбор — > [[personal/plans/music-artist-albumartist-repair]] § ВЕРИФИКАЦИЯ v3 ПЕРЕД DRY-RUN. > Коротко: (1) **лог `APPLY-PLAN-v3-changes.csv` УСТАРЕЛ** — писался до `fix_ruki.py`/ > `fix_rockopop_track.py`, 3 строки врут (`Руки` вместо `Руки вверх`, `!rock_o_pop` вместо