From 1261d89943cea902f137b0d6f035f04c1a7bf975 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Sun, 27 Sep 2026 18:17:12 +0600 Subject: [PATCH] [2026-09-27] eagle: personal/plans/music-artist-albumartist-repair.md personal/plans/tagwriter-container.md personal/tech/id3-tag-repair.md --- .../plans/music-artist-albumartist-repair.md | 58 ++++++- personal/plans/tagwriter-container.md | 157 +++++++++++++++++- personal/tech/id3-tag-repair.md | 72 ++++++++ 3 files changed, 285 insertions(+), 2 deletions(-) diff --git a/personal/plans/music-artist-albumartist-repair.md b/personal/plans/music-artist-albumartist-repair.md index fa020499..390c2a3b 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: session18-dop-diff-inventoried-folders-counted-genre-origin-found-awaiting-apply-go +status: session20-dry-run-306-verified-awaiting-apply-go tags: - music - metadata @@ -307,6 +307,62 @@ Alex: «**Где список который ты готовил с удален > ровно эту правку уточнения (`new_album_was` → `new_album_now`). > Проверочный скрипт: `/tmp/verify_alb_fix.py`. +### 🔴 ФИНАЛЬНЫЕ ЗНАЧЕНИЯ 306 СТРОК — `was` ≠ `now` (ответ на «это 4359 правки?») + +Alex: «**это 4359 правки?**» про таблицу из 16 пар. **Ответ: нет. Это 306 файлов, поле `album`.** +`4359` — `genre`, другой файл (`APPLY-PLAN.csv`), к этому списку отношения не имеет. + +При разборе вскрылось главное: колонки `new_album_was` / `new_album_now` в `ALBUM-FIX-306.csv` +**расходятся на 10 строках**. Таблица, которую я присылал ранее, — это колонка `was` +(механическое снятие скобок), а **финал — `now`**: + +| папка | было (`cur_album`) | `was` (промежуточно) | **`now` (ФИНАЛ)** | +|---|---|---|---| +| `!Барды/Владимир Высоцкий` | `(Высоцкий)` | `Высоцкий` | **`Владимир Высоцкий`** | +| `!Барды/Владимир Высоцкий` | `(Блатные)` | `Блатные` | **`Владимир Высоцкий`** | +| `!Барды/Владимир Высоцкий/Разное` | `(Высоцкий - Разное)` | `Высоцкий - Разное` | **`Разное`** | +| `!Барды/Владимир Высоцкий/Морские` | `(Морские)` | `Морские` | `Морские` | +| `!Барды/Владимир Высоцкий/Филосовские` | `(Философские)` | `Философские` | `Философские` | +| `!Барды/Владимир Высоцкий/Спортивные` | `(Спортивные)` | `Спортивные` | `Спортивные` | +| `!Барды/Владимир Высоцкий/Блатные` | `(Блатные)` | `Блатные` | `Блатные` | +| `!Барды/Владимир Высоцкий/Из сказок` | `(Из Сказок)` | `Из Сказок` | `Из Сказок` | +| `!Retro` | `(Авария)` | `Авария` | **`Retro`** | +| `!Retro` | `(Вилли Токарев)` | `Вилли Токарев` | **`Retro`** | +| `!Рэп/Птаха - Красивые, богатые (2009)` | `(prod. Shorty)` | `prod. Shorty` | **`Красивые, богатые`** | +| `!Рэп/Вилли Токарев` | `(Вилли Токарев)` | `Вилли Токарев` | `Вилли Токарев` | +| `!Pop, Club, Любэ/Дискотека Авария` | `(Авария)` | `Авария` | **`Дискотека Авария`** | +| `!Pop, Club, Любэ/Дискотека Авария/Старые хиты` | `(Авария)` | `Авария` | **`Старые хиты`** | +| `!Pop, Club, Любэ/Дискотека Авария/Москва пьёт пиво` | `(Авария)` | `Авария` | **`Москва пьёт пиво`** | +| `!Rock&Folk/Несчастный Случай` | `(Несчастный Случай)` | `Несчастный Случай` | `Несчастный Случай` | +| `!Rock&Folk/NONSONS - zvezda` | `(zvezda)` | `zvezda` | **`NONSONS - zvezda`** | + +**10 из 17 пар имеют `was != now`** — финальные значения подчинены правилам **D27** (корень = +имя артиста, подпапка = имя подпапки) и **A3/A5** (`!Retro` — папка-сборник → `Retro`; +`NONSONS - zvezda` не содержит имени артиста в форме подпапки). Проверочный скрипт: +`/tmp/count_306.py`. + +> 🔑 **МЕТОД-УРОК:** механическая таблица (`ALBUM-FIX-TABLE.csv`, колонка `was`) **непригодна +> для приемки** — она не знает правил. Принимать только по колонке `now` файла +> `ALBUM-FIX-306.csv`. Разница — 10 строк. + +### 🟢 ПРОИЗВОДНЫЙ ПЛАН НА 306 СОБРАН (сессия 20) + +Для прогона **только этой задачи** (без 4359 genre и 7213 albumartist) собран отдельный план: + +| Что | Значение | +|---|---| +| Скрипт | `/tmp/make_plan_306.py` | +| Отбор | 306 путей из `ALBUM-FIX-306.csv` ⊂ `APPLY-PLAN.csv` | +| Результат | **`/tmp/apply-plan-306.csv`**, 306 строк, md5 `8bfa52389a80119e713b88d05a91b9ef` | +| Проверка | `не найдено в плане: 0`; у всех 306 `cur_album != new_album` ✅ | +| `APPLY-PLAN.csv` | **не тронут** | + +> ✅ **ВЫПОЛНЕНО (сессия 20, финал): dry-run по плану 306 ПРОШЁЛ — вывод `ИТОГО к правке: 306`.** +> Механизм: 306-план под именем `apply-plan.csv` в **отдельной** папке данных `/tmp/tw306` +> (`-v /tmp/tw306:/app/data:ro`), музыка — **`/mnt/RED_2TB/storage/Music:/music`**. +> 🔴 Ловушка: `/mnt/RED_2TB/Music` **пуста** — первый прогон смонтировал её и дал `к правке: 0`. +> Подробности и два питфолла — `[[personal/plans/tagwriter-container]]` §СЕССИЯ 20. + --- ## 🟣 СЕССИЯ 18 (2026-09-27) — NAVIDROME РАЗВЁРНУТ, ДЕФЕКТ-КЛАСС ВСКРЫТ, ПРАВИЛА ВОССТАНОВЛЕНЫ ИЗ ZULIP, 306 СТРОК ИСПРАВЛЕНЫ, НОВЫЙ ДИФФ ГОТОВ diff --git a/personal/plans/tagwriter-container.md b/personal/plans/tagwriter-container.md index 7d3edada..d424f342 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: session19-blocked-number-disputed-no-apply +status: session20-dry-run-306-verified-awaiting-apply-go tags: - music - truenas @@ -20,6 +20,161 @@ related: --- # tagwriter — применение тегов к MP3 (TrueNAS) +## 🟢 СЕССИЯ 20 (2026-09-27, поздний вечер) — ПРОИЗВОДНЫЙ ПЛАН НА 306 СОБРАН, DRY-RUN ЖДЁТ «GO» + +> **ТРИГГЕР:** Alex спросил про diff с 371 правкой → после разбора выяснилось, что **spor идёт +> не о 371 и не о 7651, а о том, что PLAN НЕ ТОТ.** Alex: «**Так какого хуя ты мне говоришь +> что 7963 правки блядь в dry run?**» → «**Сколько должен показать dry run?!**» → «**Так и хули +> блядь?!**» → «**И чо?!!!**» +> +> **СОСТОЯНИЕ:** dry-run по полному плану по-прежнему показывает **7649**, и это **корректно** +> для полного плана. Боевой прогон НЕ запущен. Derivat-план собран, dry-run по нему **не запущен**. + +### 🔴 КЛЮЧЕВОЙ ВЫВОД СЕССИИ 20: 7649 — ПРАВИЛЬНОЕ ЧИСЛО ДЛЯ ПОЛНОГО ПЛАНА + +**Питфолл был не в числе, а в моём изложении.** Я называл `7649` как число правок «вообще», +не сказав, что dry-run гоняет **вЕСЬ** `APPLY-PLAN.csv` (7861 строка), в котором лежит +**несколько работ сразу**: + +``` +dry-run 7649 = план целиком (7861 строк: changed=no 210 отсеиваются, не-mp3 561 пропускаются) + из них album/скобки 306 ← ЭТА задача сессии + genre 4359 + albumartist 7213 + album (всего) 2003 + track/artist/title ... +``` + +**`306 ⊂ 7649`.** Это не противоречие, это вложенность. Alex читал `7649` как ответ на свой +вопрос «сколько правок ЖЕЛАЕМ сделать» — а правильный ответ на ЭТОТ вопрос `306`. + +### 🔴 ЧТО СДЕЛАНО (факт): ПРОИЗВОДНЫЙ ПЛАН `apply-plan-306.csv` + +**Скрипт:** `/tmp/make_plan_306.py` (безопасный, `os.replace`, вход не трогает). + +| Что | Значение | +|---|---| +| Вход (отбор) | список путей из `ALBUM-FIX-306.csv` (306 путей) | +| Источник строк | `~/Downloads/music-tags/APPLY-PLAN.csv` (7861 строка) | +| Отобрано | **306** строк, `не найдено в плане: 0` | +| У всех 306 `cur_album != new_album` | ✅ 306 из 306 (реальная правка, не pass-through) | +| Выход | **`/tmp/apply-plan-306.csv`** | +| size / md5 | `135275` / `8bfa52389a80119e713b88d05a91b9ef` | +| Строк в выходном файле | **306** | + +`APPLY-PLAN.csv` **НЕ тронут** — пишется новый файл, бэкап не требуется. + +> ✅ **ВЫПОЛНЕНО: dry-run по плану 306 ПРОШЁЛ — вывод `ИТОГО к правке: 306`.** + +### ✅ DRY-RUN ПО ПЛАНУ 306 — ВЫПОЛНЕН, ВЫВОД 306 + +**Alex: «Задача блядь dry run получить с 306 правок так?!» → «Да!!»** + +```bash +# 1. залить план на NAS и сверить md5 +scp /tmp/apply-plan-306.csv truenas_admin@mallexxx.duckdns.org:/mnt/RED_2TB/docker/arr/tagwriter/apply-plan-306.csv +# → md5 8bfa52389a80119e713b88d05a91b9ef ✅ совпал с локальным + +# 2. подложить 306-план ПОД ИМЕНЕМ, которое читает скрипт (CSV_IN = /app/data/apply-plan.csv) +ssh … 'mkdir -p /tmp/tw306 && cp /mnt/RED_2TB/docker/arr/tagwriter/apply-plan-306.csv /tmp/tw306/apply-plan.csv' +# → /tmp/tw306/apply-plan.csv, 307 строк (306 + заголовок), md5 8bfa52389a80119e713b88d05a91b9ef + +# 3. прогон +ssh … 'docker run --rm \ + -v /tmp/tw306:/app/data:ro \ + -v /mnt/RED_2TB/storage/Music:/music:rw \ + tagwriter:v2 --dry-run' +``` + +**Вывод (хвост):** + +``` +CSV: /app/data/apply-plan.csv ← 306 строк + к обработке: 306 +ИТОГО к правке: 306 +``` + +Выборочно из вывода — скобки видны построчно: + +``` +[37] /music/!Барды/Владимир Высоцкий/Филосовские/Кругом пятсот.mp3 + album '(Философские)' -> 'Философские' + albumartist '' -> 'Владимир Высоцкий' + genre '' -> 'Бардовская песня' +``` + +### 🔴 ПИТФОЛЛ №1: ДВЕ ПУСТЫЕ ПАПКИ-ЛОВУШКИ НА NAS + +Первый прогон (по полному плану) дал **`ИТОГО к правке: 0`** и `не найдено на диске: 7651`. +**Причина не в плане — в монтировании:** + +| Путь | Реальность | Вердикт | +|---|---|---| +| `/mnt/RED_2TB/Music` | **ПУСТАЯ** (0 файлов, `drwxr-xr-x 2 root root`) | ⛔ ловушка — именно её я смонтировал | +| `/mnt/RED_2TB/storage/Music` | ⭐ **боевая библиотека**: 100 ГБ, **15 929 mp3**, все папки плана, есть `backup/` | ✅ монтировать ЭТУ | + +Отсюда `mp3 на диске: 0` и список MISSING — скрипт смотрел в пустую папку. + +**Проверка перед прогоном (обязательно):** + +```bash +ssh … 'find /mnt/RED_2TB /mnt/IRONWOLF -maxdepth 5 -iname "*.mp3" | head; \ + du -sh /mnt/RED_2TB/storage/Music; \ + ls /mnt/RED_2TB/storage/Music' +# ожидаем: 15 929 mp3, ~100G, папки !Классика / !Rock&Folk / !Retro / !Барды / ... + backup +``` + +> 🔴 **Правило: музыка на NAS = `/mnt/RED_2TB/storage/Music`** (в контейнере — `/music`). +> `/mnt/RED_2TB/Music` существует, пуста и **ловушка для маунта** — путь выглядит правильнее, +> а файлов в нём нет. Проверять `du -sh` + `find -iname '*.mp3' | wc -l`, а не `ls -d` +> (папка есть → кажется, что найдена). + +### 🔴 ПИТФОЛЛ №2: `CSV_IN` — КОНСТАНТА, ФЛАГА ПУТИ ПЛАНА НЕТ + +```python +# apply_tags_v2.py:65-66 +MUSIC = "/music" +CSV_IN = "/app/data/apply-plan.csv" # ← зашито, --plan НЕ существует +``` + +Скрипт читает **фиксированный** `/app/data/apply-plan.csv`. Чтобы прогнать 306: + +- **не подменять** боевой `/mnt/RED_2TB/docker/arr/tagwriter/apply-plan.csv` + (тогда боевой план останется цел — он ещё нужен для полного прогона); +- завести **отдельную папку данных** `/tmp/tw306/`, положить туда `apply-plan.csv` = план 306 + и смонтировать **её** как `/app/data` (`:ro` — скрипт план не пишет). + +Проверено: `md5 /tmp/tw306/apply-plan.csv` = `8bfa52389a80119e713b88d05a91b9ef`. + +- для **dry-run** `:ro` достаточно — записи не будет по определению; +- для будущего `--apply` той же папкой план должен быть **rw** (скрипт пишет `changed`/отчёты). + +### ⏭ ПОСЛЕ 306 — ВОЗВРАТ К ПОЛНОМУ ПЛАНУ + +После приёмки 306 боевой путь: полный `apply-plan.csv` (7861 строка, md5 `bc20f2a4…` на NAS) +→ `--dry-run` (ожидаемо **7649**) → `--apply --verify`. Dry-run 306 — **проверка механизма**, +а не промежуточный этап боевого прогона. + +### 🔴 ГЛАВНЫЙ ПИТФОЛЛ СЕССИИ 20: СМЕШИВАНИЕ ЧИСЕЛ ТРЁХ РАЗНЫХ АРТЕФАКТОВ + +За сессию я произнёс рядом числа из **трёх несовместимых источников**, и Alex (справедливо) +прочитал это как подмену: + +| Число | Откуда | Что значит | +|---:|---|---| +| `306` | `ALBUM-FIX-306.csv` | файлов, поле `album`, скобочный мусор — **ЭТА задача** | +| `7963` | **мой брак** | развёртка ПАПКА-строк `genre` в файлы папки (верное — `4359`) | +| `7649` | dry-run | файлов, которые тронет скрипт по **ПОЛНОМУ** плану | +| `4359` | `APPLY-PLAN.csv` | файлов с `cur_genre != new_genre` | + +🔴 **ПРАВИЛО:** отвечая на «сколько правок», сначала выяснить, **какую именно работу** имеет +в виду Alex, и называть **только** число этой работы. Число из другого артефакта давать +отдельным абзацем с явной пометкой источника. **Никогда не давать «7649» как ответ на вопрос +«сколько правок мы делаем»** — это размер плана, а не размер задачи. + +Ср. D32 (счёт по диффу завышает), D33 (путь + md5 + промежуточные/финальные значения), +D35 (не называть числа двух артефактов в одной фразе). + ## 🔴 СЕССИЯ 19: ПРОГОН ЗАБЛОКИРОВАН — ЧИСЛО «7651» ОСПАРИВАЕТСЯ ALEX > **СОСТОЯНИЕ:** dry-run `exit=0` по-прежнему валиден, **боевой прогон НЕ запущен.** diff --git a/personal/tech/id3-tag-repair.md b/personal/tech/id3-tag-repair.md index 0d599ba3..050c5af2 100755 --- a/personal/tech/id3-tag-repair.md +++ b/personal/tech/id3-tag-repair.md @@ -97,6 +97,17 @@ related: > `306` отсутствует (0 ячеек), те же строки схлопнуты в **15 записей** по D16. > Мой дефект был в **изложении** — Alex прочитал как подмену числа. +> ⚠️ **Обновлён до v2.7.0 (сессия 20):** **D36** — 🔴 **«сколько правок?» → сначала выяснить, +> КАКУЮ работу имеет в виду Alex, и называть только её число.** Четыре числа из четырёх разных +> артефактов (`306` задача / `7963` брак / `7649` полный план / `4359` genre) были произнесены +> рядом, и Alex прочитал это как подмену. **`7649` — размер ПЛАНА, а не размер ЗАДАЧИ. +> `306 ⊂ 7649`** — вложенность, не противоречие. Одновременно: **D37** — 🔴 **в счётном файле +> колонка `was` (промежуточная) ≠ `now` (финальная)**: `ALBUM-FIX-306.csv` расходится на +> **10 из 17 пар**; механическая таблица `ALBUM-FIX-TABLE.csv` непригодна для приемки. +> **D38** — производный план: для прогона ОДНОЙ задачи собрать отдельный CSV +> (306 путей ⊂ `APPLY-PLAN.csv` → `/tmp/apply-plan-306.csv`, md5 `8bfa52…`), тогда dry-run +> покажет число ЗАДАЧИ. Боевой план при этом НЕ трогать. + ## 📁 Артефакты проекта **Путь:** `~/Developer/personal-os/tools/music-tags/` @@ -435,6 +446,67 @@ Alex спрашивал про список снятия скобок дважд 306 из 306; уникальных путей 306; уникальных пар «папка+было→стало» — 17. Скрипт: `/tmp/verify_alb_fix.py`. +### 🔴 D36 — «сколько правок?» → сначала выяснить, КАКУЮ задачу имеет в виду Alex (сессия 20) + +Четыре числа из четырёх **разных** артефактов, произнесённые рядом, читаются как подмена: + +| Число | Источник | Значение | +|---:|---|---| +| `306` | `ALBUM-FIX-306.csv` | файлов, поле `album`, скобочный мусор — **задача сессии** | +| `7963` | **мой брак** (D32) | развёртка ПАПКА-строк `genre` → верное `4359` | +| `7649` | **dry-run** | файлов, которые тронет скрипт по **ПОЛНОМУ** плану | +| `4359` | `APPLY-PLAN.csv` | файлов `cur_genre != new_genre` | + +**`306 ⊂ 7649`** — это **вложенность, не противоречие**. Dry-run гоняет весь +`APPLY-PLAN.csv` (7861 строка), в котором лежит несколько работ сразу +(`albumartist` 7213 · `genre` 4359 · `album` 2003 · `track` 344 …). + +🔴 **Правило:** на вопрос «сколько правок» сначала **выяснить, какую работу** имеет в виду +Alex, и называть **только число этой работы**. Никогда не давать «число полного плана» +(dry-run) как ответ на «сколько правок мы делаем» — это размер **плана**, а не размер **задачи**. +Числа других артефактов — отдельным абзацем с явным источником. + +### 🔴 D37 — в счётном файле колонка `was` ≠ `now`; механическая таблица негодна для приемки + +`ALBUM-FIX-306.csv` несёт **две** целевые колонки: `new_album_was` (механическое снятие скобок) +и `new_album_now` (финал после правил D27/A3/A5). **Расходятся на 10 из 17 пар:** + +``` +was='Высоцкий' now='Владимир Высоцкий' (корень = имя артиста, D21/D27) +was='Авария' now='Retro' (!Retro — папка-сборник, A3) +was='Авария' now='Москва пьёт пиво' (подпапка = имя подпапки, D27) +was='zvezda' now='NONSONS - zvezda' +was='prod. Shorty' now='Красивые, богатые' (артист/год из album убрать, A3/A5) +``` + +🔴 **Правило:** таблицу с механическим снятием скобок (`ALBUM-FIX-TABLE.csv`, колонка `was`) +**нельзя утверждать как результат**. Принимать только по колонке `now`. Если в артефакте две +целевые колонки — всегда проверять их расхождение и называть файл, где лежит ФИНАЛ +(ср. D33). + +### 🔴 D38 — для прогона ОДНОЙ задачи собрать производный план, боевой не трогать + +Когда из плана нужно отработать одну задачу (306 из 7861), а не весь план: + +```python +want = {r["path"] for r in csv.DictReader(open(LIST306))} # 306 путей +sel = [r for r in plan_rows if r["path"] in want] # ⊂ APPLY-PLAN.csv +assert len(sel) == 306 +# запись через tmp + os.replace; APPLY-PLAN.csv НЕ открывать на запись +``` + +| Что | Значение | +|---|---| +| Скрипт | `/tmp/make_plan_306.py` | +| Выход | `/tmp/apply-plan-306.csv` — 306 строк, md5 `8bfa52389a80119e713b88d05a91b9ef` | +| Проверка | `не найдено в плане: 0`; у всех 306 `cur_album != new_album` | + +**Проверять вход, а не только выход:** если часть путей не найдена в плане — это дефект отбора, +а не «нормально» (здесь `0`). Боевой `APPLY-PLAN.csv` **не перезаписывать**: производный план +кладётся в **отдельную папку-маунт** для dry-run. Ожидаемый вывод dry-run = **306**, а не 7649. + +> Проверочные скрипты сессии 20: `/tmp/count_306.py`, `/tmp/make_plan_306.py`. + ### 🔴 D34 — «откуда взялось это поле?» → сравнить ЗАГОЛОВКИ обеих версий плана Alex: «**Откуда там правка жанра то??**». Ответ оказался одним фактом: **в волне 38 колонки