[2026-09-27] eagle: personal/plans/music-artist-albumartist-repair.md personal/plans/tagwriter-container.md personal/tech/id3-tag-repair.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-27 18:17:12 +06:00
1 parent 23a5da004b
commit 1261d89943
3 files changed
+285 -2

No files matched your search

@@ -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 СТРОК ИСПРАВЛЕНЫ, НОВЫЙ ДИФФ ГОТОВ
+156 -1
View File
@@ -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` по-прежнему валиден, **боевой прогон НЕ запущен.**
+72
View File
@@ -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 колонки