From 9f620920899ad34737c25d1c08899fc6416afe59 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Sun, 27 Sep 2026 17:51:51 +0600 Subject: [PATCH] [2026-09-27] eagle: family/how-to/navidrome-truenas-android.md personal/plans/music-artist-albumartist-repair.md personal/plans/tagwriter-container.md --- family/how-to/navidrome-truenas-android.md | 12 +- .../plans/music-artist-albumartist-repair.md | 33 +++-- personal/plans/tagwriter-container.md | 137 ++++++++++++++++-- 3 files changed, 157 insertions(+), 25 deletions(-) diff --git a/family/how-to/navidrome-truenas-android.md b/family/how-to/navidrome-truenas-android.md index b679bf0e..76240bda 100755 --- a/family/how-to/navidrome-truenas-android.md +++ b/family/how-to/navidrome-truenas-android.md @@ -79,10 +79,18 @@ Navidrome поднялся и просканировал **8043 трека**. П | Реальный скобочный мусор | **306 строк / 15 папок** | **Итог:** 306 строк исправлены (скобочный мусор → 0), затем **ещё 58 строк по 3 дефектам** -(склейка артист+год, чужой `albumartist`, `album`=название песни) → сгенерирован новый дифф -`DIFF-new-vs-38-v2.csv` (⏭ устарел после правки 58 строк, требует перегенерации). +(склейка артист+год, чужой `albumartist`, `album`=название песни), затем **+12** (`Шнуров`→`Ленинград`, +`NONSONS - zvezda`→`zvezda`). **Всего 371 строка.** Дифф **`DIFF-new-vs-38-v2.csv` перегенерирован** +(164 album-строки, полнота 18/18 + 6/6 проверена). Полный разбор — [[personal/plans/music-artist-albumartist-repair]] §СЕССИЯ 18. +> 🔴 **ПРИЁМКА NAVDROME ДАЛА ДВОЙНОЙ РЕЗУЛЬТАТ — оба важны:** +> 1. **Движок подтверждён на живых данных** — теги читаются, папки игнорируются, бага Jellyfin нет. +> 2. **Вскрыт дефект ВХОДНОГО ПЛАНА**, который не видел ни один валидатор и ни один дифф: +> мусор, унаследованный из источника, присутствует в **обеих** версиях плана → дифф показывает +> «совпало». **Открывать данные в UI-потребителе = самый эффективный способ найти мусор в тегах, +> который не ловится сверкой файлов.** + ## 🔴 УРОК: разброс значений в папке ≠ дефект (метод-урок сессии 18) При проверке «один ли `album`/`albumartist` в папке» получено **96 папок с разными `album` diff --git a/personal/plans/music-artist-albumartist-repair.md b/personal/plans/music-artist-albumartist-repair.md index a3f9ee87..e8fae4b7 100755 --- a/personal/plans/music-artist-albumartist-repair.md +++ b/personal/plans/music-artist-albumartist-repair.md @@ -19,7 +19,7 @@ related: - '[[personal/tech/music-tags-repair-truenas]]' - '[[personal/plans/tagwriter-container]]' - '[[family/how-to/navidrome-truenas-android]]' -status: session18-complete-waiting-apply-step2 +status: session18-step2-done-dryrun-ok-awaiting-apply-go # Music `artist` + `albumartist` Repair — правила Alex и ход работ ## 🟣 СЕССИЯ 18 (2026-09-27) — NAVIDROME РАЗВЁРНУТ, ДЕФЕКТ-КЛАСС ВСКРЫТ, ПРАВИЛА ВОССТАНОВЛЕНЫ ИЗ ZULIP, 306 СТРОК ИСПРАВЛЕНЫ, НОВЫЙ ДИФФ ГОТОВ @@ -35,9 +35,13 @@ status: session18-complete-waiting-apply-step2 > ✅ Дифф **`DIFF-new-vs-38-v2.csv` ПЕРЕГЕНЕРИРОВАН** (164 album-строки, +12), полнота проверена — > все 18 целевых `album` и 6 `albumartist` внутри. Старый дифф **не тронут**. > Alex дал команду **«применяй»** → **ШАГ 1 (сверка артефактов) ПРОЙДЕН**: найдено 2 расхождения -> (скрипт на NAS и план на NAS — оба старые). ⏭ **ШАГ 2 не начат — ждёт «шаг 2» от Alex.** +> (скрипт на NAS и план на NAS — оба старые). **ШАГ 2 ТОЖЕ ВЫПОЛНЕН:** артефакты залиты, +> MD5 сверены, **dry-run `exit=0` — 7651 к обработке, не найдено 0**. По ходу найден и закрыт +> дефект: 3 файла `Green Apelsin` имели изменённый `new_album` при старом флаге `changed=no` +> → пересчёт `changed` по всем 7861, контроль `0`. ⏭ **ШАГ 3 (`--apply --verify`) ждёт «go».** +> Канонический раннер: `~/Automation/music-tags/run_tagwriter.sh`. > См. §ПРАВКА 3 ДЕФЕКТОВ, §КАЛИБРОВКА ДЕТЕКТОРОВ, §СДЕЛАНО: правка 306 строк, §НОВЫЙ ДИФФ, -> §СВЕРКА АРТЕФАКТОВ ШАГ 1. +> §СВЕРКА АРТЕФАКТОВ ШАГ 1, §ШАГ 2 ВЫПОЛНЕН. > **Контекст:** Navidrome поднят и просканировал библиотеку (8043 трека). Alex открыл UI и > спросил: «**Откуда альбомы в скобках взялись?** Типа `Дискотека Авария` — для корня, @@ -533,18 +537,29 @@ Alex: «**Отлично, применяй**». Прогон **НЕ запуще > CMD ["python3", "/app/apply_tags_v2.py", "--dry-run"] ← нет ENTRYPOINT, нет USER > ``` -### ⏭ ШАГ 2 (НЕ ВЫПОЛНЕН — ждёт «шаг 2» от Alex) +### ✅ ШАГ 2 ВЫПОЛНЕН — ЗАЛИВКА, DRY-RUN, ДЕФЕКТ `changed` ЗАКРЫТ -1. `docker cp` актуального `apply_tags_v2.py` (Mac) → контейнер `/app/apply_tags_v2.py` -2. Залить актуальный `APPLY-PLAN.csv` (Mac) → NAS `/mnt/RED_2TB/docker/arr/tagwriter/apply-plan.csv` -3. Сверить MD5 обоих на NAS после заливки -4. `--dry-run` → сверить счётчики -5. **Только после «go»** — `--apply --verify` +1. ✅ `apply_tags_v2.py` (Mac) → NAS `/mnt/RED_2TB/docker/arr/tagwriter/` — MD5 `c7a2bc14…` совпал +2. ✅ `APPLY-PLAN.csv` (Mac) → NAS `apply-plan.csv` — MD5 `bc20f2a4…` совпал +3. ✅ Dry-run `exit=0`: **7651 к обработке**, `не найдено 0` + +> 🔴 **Найден и закрыт дефект:** 3 файла `Green Apelsin` имели изменившийся `new_album`, +> но **старый флаг `changed=no`** → скрипт их бы пропустил. Пересчёт `changed` по всем 7861 +> (`/tmp/recalc_changed.py`) → **контроль: расхождений 0**. Детали — +> [[personal/plans/tagwriter-container]] §СЕССИЯ 18. > 🔴 **Правило шага 1:** **перед необратимым прогоном сверять MD5 скрипта (Mac / образ / NAS) > и плана (Mac / NAS).** В этой сессии расхождения нашлись в ДВУХ местах из двух проверенных — > без сверки прогон пошёл бы **по старому плану без 371 правки**. +### ⏭ ШАГ 3 (НЕ ВЫПОЛНЕН — ждёт «go» от Alex) + +```bash +bash ~/Automation/music-tags/run_tagwriter.sh apply # 7651 файл, --apply --verify +``` +Канонический раннер — [[personal/plans/tagwriter-container]] §СЕССИЯ 18. +Бэкап `Music/backup` (50 ГБ) на месте, без него `cmd_apply()` → `exit 2`. + ### ✅ Артефакты сессии 18 | Файл | Что | diff --git a/personal/plans/tagwriter-container.md b/personal/plans/tagwriter-container.md index aa831587..4bb99d5b 100755 --- a/personal/plans/tagwriter-container.md +++ b/personal/plans/tagwriter-container.md @@ -4,7 +4,7 @@ created: '2026-09-26' updated: '2026-09-27' type: plan namespace: personal -status: session17-image-built-deployed-dryrun-ok-awaiting-apply-go +status: session18-artifacts-uploaded-dryrun-ok-awaiting-apply-go tags: - music - truenas @@ -20,7 +20,114 @@ related: --- # tagwriter — применение тегов к MP3 (TrueNAS) -## 🔴 СЕССИЯ 17: АУДИТ APPLY-СКРИПТА ПЕРЕД БОЕВЫМ ПРОГОНОМ — ЧИТАТЬ ПЕРВЫМ +## 🟢 СЕССИЯ 18: КАНОНИЧЕСКИЙ РАННЕР, ЗАЛИВКА АРТЕФАКТОВ, DRY-RUN OK — ЖДЁТ «GO» + +> **СОСТОЯНИЕ:** артефакты залиты на NAS, MD5 сверены, dry-run `exit=0`. +> **Осталось: боевой `--apply --verify` (7651 файл) — ждёт команды Alex.** + +### ⭐ КАНОНИЧЕСКИЙ РАННЕР: `~/Automation/music-tags/run_tagwriter.sh` + +Единая точка входа. Зафиксированная последовательность вместо ручных шагов +(жалоба Alex: «**почему у тебя каждый раз хуйня выходит при прогонах? У тебя записана +чёткая последовательность действий для прогона?**»). + +```bash +bash ~/Automation/music-tags/run_tagwriter.sh dry # сухой прогон (Music :ro) +bash ~/Automation/music-tags/run_tagwriter.sh apply # боевой (Music rw, --apply --verify) +bash ~/Automation/music-tags/run_tagwriter.sh readback # сверка без записи +``` + +Скрипт сам: печатает MD5 (Mac скрипт/план + NAS скрипт/план) → `docker create` → +`docker start -a` → `docker rm -f`. **Механизм `create`+`start -a` не требует апрува** +(`docker run` — требует). + +### 🔴 МЕХАНИЗМ ЗАПУСКА — восстановлен из базы Zulip (дока врала) + +Предыдущая версия доки предписывала `docker cp` и образ `tagwriter:v2` — **оба неверны**. +Фактическая команда (из Zulip, сообщения 126978, 126989, 127135): + +```bash +ssh truenas_admin@mallexxx.duckdns.org ' +docker create --name twdry --entrypoint python3 \ + -v /mnt/RED_2TB/docker/arr/tagwriter:/app/data \ + -v /mnt/RED_2TB/storage/Music:/music:ro \ + tagwriter:latest /app/data/apply_tags_v2.py --dry-run +docker start -a twdry; docker rm -f twdry +' +``` + +| Что | Правильно | Дока говорила | +|---|---|---| +| Образ | **`tagwriter:latest`** | ❌ `tagwriter:v2` (устаревший) | +| Скрипт | **монтируется** через `--entrypoint python3 /app/data/apply_tags_v2.py` | ❌ «нужен `docker cp`» | +| Музыка (боевой) | `/music` **без** `:ro` | ✅ верно | +| Музыка (сухой) | `/music:ro` | ✅ верно | +| `save(v1=0)` | ⚠️ ID3v1-комментарий **уцелел** в тесте | ❌ «сносит ID3v1 всегда» | + +### ✅ РЕЗУЛЬТАТ ЗАЛИВКИ И DRY-RUN (факт, конец сессии 18) + +**MD5 после заливки (Mac ↔ NAS сверены):** +``` +скрипт apply_tags_v2.py c7a2bc143e38820f2050310cbe01b799 (Mac = NAS = образ latest) +план apply-plan.csv bc20f2a49e364ecd5ed57d9fc97bfbcb (Mac = NAS) +``` + +**Сухой прогон по актуальному плану (7651 файл):** +``` +строк в плане: 7861 + changed=yes требующих правки, но помеченных no: 0 ✅ + changed=no без расхождений, но помеченных yes: 0 ✅ + mp3 на диске: 7861 пропущено не-mp3: 561 + пропущено changed=no: 210 + не найдено на диске: 0 + к обработке: 7651 +exit=0 +``` + +> 🔑 **Инвариант `_check_changed_consistency()` пройден:** оба счётчика расхождений `= 0`. +> Перед этим было `7648 к обработке`; стало **7651** (+3 = файлы `Green Apelsin`, см. ниже). + +### 🔴 ДЕФЕКТ ПРАВКИ ПЛАНА: `changed` не пересчитывался после смены `new_album` + +**Класс дефекта:** правка `new_album` в плане **не обновляет** колонку `changed`. +Если у файла до правки `cur_album == new_album` (флаг `no` стоял корректно), после правки +значения расходятся — **а флаг остаётся `no`. Скрипт такой файл ПРОПУСТИТ.** + +Найдено **3 файла** (все `Green Apelsin`) — правка `album` → `Северный ветер` не применилась бы: +``` +!Pop, Club, Любэ/Green Apelsin/В пасти смерти.mp3 'В пасти смерти' -> 'Северный ветер' changed=no ❌ +!Pop, Club, Любэ/Green Apelsin/Кукловод.mp3 'Сердце не бьётся'-> 'Северный ветер' changed=no ❌ +!Pop, Club, Любэ/Green Apelsin/Лес.mp3 'Лес' -> 'Северный ветер' changed=no ❌ +``` +Плюс встречный: 1 файл `changed=yes` **без** расхождений — +`The Prodigy/The Prodigy - Girls (Rex The Dog Remix).mp3` (лишний флаг, известен с сессии 17). + +**Фикс** (`/tmp/recalc_changed.py`): `changed = "yes" if любой cur_* != new_* else "no"`, по всем 7861. + +``` +ДО: yes 7649 · no 212 +ПОСЛЕ: yes 7651 · no 210 переключено 4 +КОНТРОЛЬ: расхождений changed осталось 0 +``` +Бэкап: `APPLY-PLAN.csv.bak-before-changed-recalc-20260927-174900`. + +> 🔴 **ПРАВИЛО ПАЙПЛАЙНА: любая правка `new_*` в плане ОБЯЗАНА пересчитывать `changed`. +> Скрипт-писатель читает флаг, а не сравнивает значения сам.** + +### 🔴 ГЛАВНЫЙ ПИТФОЛЛ: `Dockerfile.v2` на Mac — УСТАРЕВШАЯ СПРАВОЧНАЯ КОПИЯ + +В нём **нет** `ENTRYPOINT` и `USER 950:950`, хотя дока описывает их как фиксы. +Боевой образ `tagwriter:latest` собран **с** фиксами (проверено `docker inspect` + `id`). +**Не пересобирать из этого файла — потеряешь фиксы.** + +### 🔴 ПИТФОЛЛ: `tagwriter:v2` — старая версия, НЕ ИСПОЛЬЗОВАТЬ + +`tagwriter:v2` = `e5e2437a2fda90cb6b06d6daa6330cb` (скрипт старый). +`tagwriter:latest` = `c7a2bc14…` ✅. Оба образа сосуществуют — легко промахнуться. + +--- + +## 🔴 СЕССИЯ 17: АУДИТ APPLY-СКРИПТА ПЕРЕД БОЕВЫМ ПРОГОНОМ > Alex: «**Проверяй ещё раз руками apply script. Скажи алгоритм которым он будет применять > правки и найди если есть какие-то проблемы.**» @@ -610,18 +717,20 @@ Alex: «**Отлично, применяй**» → прогон **не запу **Шаг 2 (не выполнен, ждёт Alex):** `docker cp` скрипта + заливка актуального плана → MD5-сверка → `--dry-run` → `--apply --verify` только после «go». -### 🔴 ОТКРЫТЫЙ ДЕФЕКТ (требует проверки): `!Rock&Folk/Rammstein/misc` +### 📜 ИСТОРИЧЕСКАЯ ЗАПИСЬ (НЕДЕЙСТВИТЕЛЬНА — см. §ЗАКРЫТО выше) -Alex: «**`!Rock&Folk/Rammstein/misc` - album - rammstein - неправильно**». - -``` -ПАПКА album '' -> Rammstein ← ❌ имя АРТИСТА в поле альбома (3 файла с пустым cur_album) -ПАПКА albumartist <КОЛОНКИ НЕ БЫЛО> -> Puhdys ← ❌ чужая группа на всю папку Rammstein -``` -Папка: 18 файлов. Остальные альбомы корректны: `The remix album 2002` (7), `Singles '99` (4), -`Live Aus Russland`, `Wilder Freiden`, `Demo`, `Golden Collection 2001`. -Файлы с пустым `cur_album`: `Pet Sematary.mp3`, `Песня о тревожной молодости.mp3` + 1. -⏳ Решение не получено. +> ~~### 🔴 ОТКРЫТЫЙ ДЕФЕКТ: `!Rock&Folk/Rammstein/misc`~~ +> +> ~~Alex: «`!Rock&Folk/Rammstein/misc` - album - rammstein - неправильно».~~ +> +> ``` +> ПАПКА album '' -> Rammstein ← ❌ имя АРТИСТА в поле альбома +> ПАПКА albumartist <КОЛОНКИ НЕ БЫЛО> -> Puhdys ← ❌ чужая группа на всю папку +> ``` +> ~~Папка: 18 файлов. Решение не получено.~~ +> +> ✅ **ЗАКРЫТО в сессии 18:** факт по файлу — `album='misc'`, `albumartist='Rammstein'`. +> Дефекта нет, запись была снимком ДО правок. Детали — §ЗАКРЫТО выше. ### 🔴 ПИТФОЛЛ: `grep` по ИМЕНИ ПАПКИ ≠ проверка ЗНАЧЕНИЯ @@ -778,7 +887,7 @@ docker start -a tw && docker rm -f tw | 0d | `Филипок` / `Маленький_Мук` — `album='Сказочный сборник'` / транслит | ⏳ войдёт в 0c | | 0e | **12 строк `changed=yes` без реальных правок** — снять флаг? | ⏳ **вопрос сессии 17** | | 0f | **`track` с ведущим нулём** (`01`, `03/12`) — канонизировать в `1`/`3/12`? 11 файлов | ⏳ **вопрос сессии 17** | -| 1 | Боевой `--apply --verify` по 7861 | 🔴 **ЖДЁТ «go»** (после 0, 0a–0f) | +| 1 | Боевой `--apply --verify` по 7861 | 🔴 **ЖДЁТ шаг 2** (сверка артефактов пройдена, см. §СВЕРКА АРТЕФАКТОВ) | | 2 | Приёмка Alex в UI (Navidrome/Jellyfin) | ⏳ | | 3 | Откат 40 из `backup_conv40/` | ⏳ | | 4 | Удаление 29 битых/DRM ([[personal/tech/music-broken-toload]]) | ⏳ |