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

This commit is contained in:
Alexey Martemyanov committed 2026-09-27 18:01:59 +06:00
1 parent fbba9c65f2
commit c486da75fa
2 files changed
+216 -5

No files matched your search

@@ -4,7 +4,7 @@ created: '2026-09-27'
updated: '2026-09-27'
type: plan
namespace: personal
status: RC-complete-defects-fixed
status: session18-dop-diff-inventoried-folders-counted-awaiting-apply-go
tags:
- music
- metadata
@@ -19,9 +19,190 @@ related:
- '[[personal/tech/music-tags-repair-truenas]]'
- '[[personal/plans/tagwriter-container]]'
- '[[family/how-to/navidrome-truenas-android]]'
status: session18-step2-done-dryrun-ok-awaiting-apply-go
# Music `artist` + `albumartist` Repair — правила Alex и ход работ
## 🟣 СЕССИЯ 18-ДОП (2026-09-27, вечер) — ИНВЕНТАРЬ ДИФФА ПО ФАКТУ, ПОСЛЕ ВОПРОСА ALEX «ГДЕ ДИФ?»
> **Триггер:** Alex — «**Где диф последний с 371 правкой альбомов/albumartist который ты готовил?**»
> → затем «**Какие там записи?**». Отвечено из **Zulip DB** (по скиллу `zulip-db-forensics`),
> затем проверено по самим файлам на диске.
### 📍 Местонахождение (факт с диска, mtime 2026-09-27 17:41)
| Файл | MD5 | Строк | Статус |
|---|---|---:|---|
| ⭐ `~/Downloads/music-tags/DIFF-new-vs-38-v2.csv` | `7bc222a1087787c390026d624af932f3` | **2854** | **АКТУАЛЬНЫЙ** (после 371 правки) |
| `~/Downloads/music-tags/DIFF-new-vs-38.csv` | `4e073fe50c191cc50015d3f15f6a6d28` | 2852 | устаревший (mtime 15:46) — **НЕ трогать** (указание Alex) |
| `~/Downloads/music-tags/milestones/RC-20260927/DIFF-RC-20260927-153717.csv` | — | — | снапшот RC |
Генератор — `~/Downloads/music-tags/diff_new_vs_38_v2.py` (17:32).
### 🧩 Состав `DIFF-new-vs-38-v2.csv` (факт, `csv.DictReader`)
Колонки: `scope, folder, path, field, old_38, new_apply_plan`.
Всего **2854** строки = **2768 правок** + **86 контроль**.
Правки = `ПАПКА` **1501** + `ТРЕК` **1267**.
| field | строк | что именно |
|---|---:|---|
| `genre` | 911 | снятие `!`-префиксов, `!Radio`→`Radio`, `!Rock&Folk`→`Rock&Folk` |
| `track` | 883 | `#2_04_`→`4`, ведущие нули, суффиксы `-bpm`/`-aQB`/`-1king` |
| `albumartist` | 634 | 🔴 `old_38 = '<КОЛОНКИ НЕ БЫЛО>'` → значение: колонки в 38-м **не существовало** (ср. **D3**) |
| `album` | 164 | скобочный мусор, склейки `Артист - Год`, `Нечетный`→`Нечётный`, заполнение пустых |
| `artist` | 134 | опечатки, регистр, лишние пробелы |
| `title` | 42 | дубли `album == title` |
Разбивка по scope × field: `ПАПКА` genre 700 · aa 577 · track 76 · album 113 · artist 35 ·
`ТРЕК` track 807 · genre 211 · artist 99 · title 42 · albumartist 57 · album 51.
**Контроль (86):** `ТОЛЬКО-В-38` — файлы `._*.m4a` (AppleDouble) в
`!Pop, Club, Любэ/Infected Mushroom/(2012) Army of Mushrooms/` и не-ID3 форматы.
Не правки, список полноты (ср. D-секцию «служебные секции в хвосте диффа»).
**Классы правок `album`** (примеры, полный дамп `/tmp/diff_v2_dump.txt`):
`(Авария)`→`Москва пьёт пиво`, `(Философские)`→`Философские`, `(zvezda)`→`zvezda`,
`(Несчастный Случай)`→`Несчастный Случай`, `Бард - Авангард`→`Бард-авангард`,
`Нечетный`→`Нечётный`, `Земфира (2000)`→`Земфира`, `'<N разных>'`→имя подпапки/корня,
сборники `!Retro`/`!Radio` → `Retro` + `Various Artists`.
> 🔴 **Число 371 ≠ число строк диффа.** `371` — это **строки `APPLY-PLAN.csv`, изменённые
> в сессии 18** (306 + 58 + 12). Дифф показывает **дельту плана против 38-го прогона**:
> 2768 записей, из них `album`+`albumartist` = **798 строк**, `album` = **164**.
> ⚠️ **Не выдавать число ЗАПИСЕЙ диффа за число правок сессии и наоборот** —
> две разные базы счёта (ср. правила счёта в [[personal/tech/id3-tag-repair]] § D-правила).
### 🛠 Скрипты проверки (созданы в этой сессии, `/tmp/`)
| Скрипт | Что делает |
|---|---|
| `/tmp/check_diff_v2.py` | заголовок, размер, MD5, разбивка по `scope` |
| `/tmp/check_diff_v2b.py` | матрица `scope × field` (правки/контроль раздельно) |
| `/tmp/dump_diff_v2.py` | полный дамп → `/tmp/diff_v2_dump.txt` (5626 строк до стопа на `head`) |
| `/tmp/dump_diff_v2c.py` | секции `albumartist` / `artist` построчно |
> 🔴 **ПИТФОЛЛ (повтор D4):** `python3 dump.py 2>&1 | head -160` → `TypeError` в ветке
> контрольной секции **дописался в лог, но файл `/tmp/diff_v2_dump.txt` остался от прошлого
> прогона**. Дамп писать в файл и смотреть файл, а не полагаться на пайп.
> Также: форматирование `None` через `{:12s}` падает — приводить `str(...)` для полей
> контрольной секции (у них `field`/`path` = `None`).
### 🗂 РАЗБИВКА ДИФФА ПО ПАПКАМ — 633 папки, с числом файлов
Alex: «**Все папки**» → «**С количеством файлов**».
Артефакт: **`/tmp/folders_report.txt`** (639 строк, генератор `/tmp/diff_v2_folders2.py`).
Колонки: `FOLDER · fld_edits · files_in_folder · files_changed · album · aa · artist · genre · track · title`.
**Верхний уровень — ровно 6 папок:**
```
!Pop, Club, Любэ
!Retro
!Детские
!Классика
!Рэп
z Другое, Enigma, Ленинград
```
Остальные **627 — вложенные** (артист/альбом). `!Rock&Folk` и `!Барды` затронуты
**только через вложенные** — верхнеуровневых строк у них нет.
**Топ-10 папок по числу файлов:**
| файлов | edits | папка |
|---:|---:|---|
| 203 | 8 | `!A Not sorted/Sbornik_Novij_Rok` |
| 179 | 44 | `!Retro` |
| 156 | 3 | `!Детские/Детский хит` |
| 97 | 3 | `!Барды/Владимир Высоцкий/Филосовские` |
| 71 | 18 | `!Rock&Folk/Агата Кристи` |
| 69 | 27 | `!Rock&Folk/!rock_o_pop` |
| 69 | 21 | `!Pop, Club, Любэ/!Radio/x 2008-04` |
| 63 | 4 | `!Рэп/НТЛ` |
| 61 | 31 | `!Pop, Club, Любэ/!Radio/z Старое/disk_v2/1 DRIVE` |
| 58 | 29 | `!Pop, Club, Любэ/!Radio/z Старое/disk/1 DRIVE` |
> 🔴 **ПИТФОЛЛ (счёт файлов по ПАПКА-строке):** ПАПКА-строка диффа разворачивается
> в **ВСЕ файлы папки**, включая те, у которых целевое значение уже совпадает с текущим.
> Это даёт **фантомные файлы**. Сумма по папкам → `8852`, тогда как по плану меняется `7651`,
> а по конкретному полю — см. ниже. **Число файлов из ПАПКА-строки ≠ число реально меняющихся файлов.**
### 🔴 ОШИБКА СЧЁТА `7963` ПО `genre` — ВСКРЫТА И ПРИЗНАНА
Alex: «**Чё за нахуй?! В смысле блядь 7963?!!!**»
**Что я сделал неверно:** в диффе 911 genre-строк, из них **700 — уровня ПАПКА**.
Каждую ПАПКА-строку я развернул во **все файлы папки** и сложил → `7752 + 211 = 7963`.
**Правильное число по плану — `4359`** (строк `APPLY-PLAN.csv`, где `cur_genre != new_genre`).
```
plan rows 7861
genre differing files 4359 ← правда
albumartist differing 7213
album differing 2003
artist differing 381
track differing 344
title differing 8
ANY field differing 7651
```
**Причина расхождения 7752 vs 4359 (3393 фантома):** ПАПКА-строка = «папка целиком меняет genre»,
но у части файлов genre **уже равен** целевому — скрипт пишет значение без изменения.
Косвенное подтверждение из плана: `distinct cur_genre` = **156** значений → `new_genre` = **64**;
пустой genre у **2950** файлов (их и заполняют).
> 🔑 **МЕТОД-УРОК (счёт файлов):** **ПАПКА-строка диффа разворачивается в файлы, которых
> правка не касается.** Для числа «реально меняющихся файлов» считать **по `APPLY-PLAN.csv`**
> (`cur_X != new_X`), а не по диффу. Дифф отвечает на вопрос «что изменилось против 38-го прогона»,
> план — «что реально отличается от целевого». **Две разные базы счёта** (ср. §Число 371 ≠ строки диффа).
> Проверочный скрипт: `/tmp/explain_genre.py`.
### 🧾 Список удаления скобок — ГДЕ ЛЕЖИТ (ответ на «Где список с удалением скобок»)
Alex: «**Где список который ты готовил с удалением скобок**»
**Два файла, оба mtime 2026-09-27 17:21–17:32:**
| Файл | MD5 | Строк | Назначение |
|---|---|---:|---|
| ⭐ `~/Downloads/music-tags/ALBUM-FIX-TABLE.csv` | `1597f2716ac5a2881efabaec13755d4c` | **306** | читаемая таблица: `folder, cur_album, new_album, path, title` |
| `~/Downloads/music-tags/ALBUM-FIX-306.csv` | `49ba645462964cc299da7dcffb55ca09` | **306** | + `new_album_was` / `new_album_now` (история уточнения значения) |
**Факт (`csv.DictReader`):** `cur_album` начинается с `(` — **306 из 306**, значение **целиком
в скобках** — **306 из 306**, уникальных путей **306**, уникальных пар «папка+было→стало» — **17**.
**17 пар (полный список):**
| папка | было | стало |
|---|---|---|
| `!Барды/Владимир Высоцкий` | `(Высоцкий)` | `Высоцкий` |
| `!Барды/Владимир Высоцкий/Морские` | `(Морские)` | `Морские` |
| `!Барды/Владимир Высоцкий/Филосовские` | `(Философские)` | `Философские` |
| `!Барды/Владимир Высоцкий/Спортивные` | `(Спортивные)` | `Спортивные` |
| `!Барды/Владимир Высоцкий/Блатные` | `(Блатные)` | `Блатные` |
| `!Барды/Владимир Высоцкий/Разное` | `(Высоцкий - Разное)` | `Высоцкий - Разное` |
| `!Барды/Владимир Высоцкий/Из сказок` | `(Из Сказок)` | `Из Сказок` |
| `!Retro` | `(Авария)` | `Авария` |
| `!Retro` | `(Вилли Токарев)` | `Вилли Токарев` |
| `!Рэп/Птаха - Красивые, богатые (2009)` | `(prod. Shorty)` | `prod. Shorty` |
| `!Рэп/Вилли Токарев` | `(Вилли Токарев)` | `Вилли Токарев` |
| `!Pop, Club, Любэ/Дискотека Авария` | `(Авария)` | `Авария` |
| `!Pop, Club, Любэ/Дискотека Авария/Старые хиты` | `(Авария)` | `Авария` |
| `!Pop, Club, Любэ/Дискотека Авария/Москва пьёт пиво` | `(Авария)` | `Авария` |
| `!Rock&Folk/Несчастный Случай` | `(Несчастный Случай)` | `Несчастный Случай` |
| `!Rock&Folk/NONSONS - zvezda` | `(zvezda)` | `zvezda` |
> ⚠️ **Важно для читателя списка:** значения в `ALBUM-FIX-TABLE.csv` — **промежуточные**
> (снятие скобок механически). По **финальным правилам** (§ФИНАЛЬНЫЕ РЕШЕНИЯ ПО 306 СТРОКАМ)
> в корне папки артиста `album` = **имя папки артиста**, а в подпапке — **имя подпапки**.
> Поэтому `(Авария)` → `Москва пьёт пиво`, а не `Авария`; `(Высоцкий)` → `Дискотека Авария`
> в одноимённой папке. Отличие `ALBUM-FIX-TABLE.csv` от `ALBUM-FIX-306.csv` фиксирует
> ровно эту правку уточнения (`new_album_was` → `new_album_now`).
> Проверочный скрипт: `/tmp/verify_alb_fix.py`.
---
## 🟣 СЕССИЯ 18 (2026-09-27) — NAVIDROME РАЗВЁРНУТ, ДЕФЕКТ-КЛАСС ВСКРЫТ, ПРАВИЛА ВОССТАНОВЛЕНЫ ИЗ ZULIP, 306 СТРОК ИСПРАВЛЕНЫ, НОВЫЙ ДИФФ ГОТОВ
> **СТАТУС СЕССИИ 18 (ФИНАЛ):** Navidrome ✅ развёрнут и работает (см. ниже +