[2026-09-26] eagle: personal/plans/balda-mm-phase4-agents.md personal/plans/music-tags-review/music-tags-review.md personal/projects/balda/balda-mm-delegation-plan.md personal/tech/music-tags-repair-truenas.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-26 15:22:34 +06:00
1 parent ca471c21af
commit 269f2b189a
4 files changed
+437 -15

No files matched your search

+125 -12
View File
@@ -16,13 +16,119 @@ related:
- '[[family/how-to/jellyfin-config]]'
- '[[family/projects/media-toolbox-taiga]]'
---
# Music Tags Repair — TrueNAS (7906 файлов)
> **Статус: ЭТАПЫ 1–2 ЗАВЕРШЕНЫ (2026-09-26). CSV v4 + ноты ревью отданы Alex. Правки файлов НЕ начаты.**
> **Статус: ЭТАПЫ 1–2 ЗАВЕРШЕНЫ (2026-09-26). CSV v5 + ноты ревью отданы Alex. Правки файлов НЕ начаты.**
> Следующий шаг — апрув Alex по нотам ревью, затем бэкап + применение.
> Прогонялись три волны проверок: полный аудит всех строк (`audit_plan.py` + `final_gate.py`) → 6 багов,
> затем третья волна по замечаниям Alex → 3 бага (en-dash + подстроковый поиск стоп-слов).
> Итог v4: 4777 `fix`, mojibake = 0, 80 тестов PASS, блок «требует решения» 296 → 28.
> Прогонялись **четыре волны** проверок: полный аудит всех строк (`audit_plan.py` + `final_gate.py`) → 6 багов,
> третья волна по замечаниям Alex → 3 бага (en-dash + подстроковый поиск стоп-слов),
> **четвёртая волна (ручной проход) → 10 багов (track-номера, disc-track, восклицательные знаки, регистр)**.
> Итог v5: 6773 `fix`, mojibake = 0, 121 тест PASS, блок «требует решения» 296 → 28.
## 🔴 ЧЕТВЁРТАЯ ВОЛНА: РУЧНОЙ ПРОХОД И TRACK-НОМЕРА (2026-09-26)
Alex: **«Ты блядь руками пройди и пофикси все что мне предлагал кроме того что неочевидно!
Чё за автообработка то!!»** + **«01 Hit The Road Jack (Kub vs Ray Charles), 06 Benassi I am a Dj
и другие подобные — в них тебе что не понятно? Почему ты вписал все включая трек намбер в тайтл?»**
Оба замечания верны. Автоматика выжата — дальше только глазами по всему файлу (8051 строка ревью).
### 🔴 ГЛАВНОЕ: ведущий номер трека ехал в title
Было: `title = "01 Hit The Road Jack (Kub vs Ray Charles)"`. Стало: `title = "Hit The Road Jack…"`, `track = 1`.
**Затронуто 5158 файлов** — это массовый случай, не единичный.
**Причина:** `PREFIX_RE = r"^\s*\d{1,3}\s*[-._)\]]\s*"` требовал **пунктуацию** после номера.
`01 Hit…` (пробел после номера) не матчился.
**Новое поле `new_track`** добавлено в CSV-план и в ноты ревью. Функция `split_track_number(stem) -> (track, title)`.
**Условия извлечения номера** (все проверены тестами `test_track_numbers.py`, 25 кейсов):
| Случай | Пример | Результат |
|---|---|---|
| Обычный ведущий номер | `01 Hit The Road Jack` | track=1, title=чисто |
| Номер в теге, не в имени | `24 Моцарт Турецкий марш` | track=24 |
| **Disc-track** | `1-01. Albibeno` | track=`01`, НЕ `1` (иначе весь диск = один номер) |
| **Ведущие нули** | `001 БУРАТИНО` | track=1 (3 цифры с нулём = позиция, не id плейлиста) |
| **Трёхзначный без нуля** | `159_Css`, `149_Thriving` | **не трогать** — это номер архива/плейлиста |
| Остаток — число | `05 48`, `11 140` | **не трогать** — это реальное название |
| 4 цифры = год | `1999 Жывой Концерррт` | **не трогать** |
| **Строчная после номера** | `50 cent - p.i.m.p` | **не трогать** — «50» часть имени артиста |
| `00` | `00-intro-family-affair` | **не трогать** — track 0 не существует в ID3 |
> 🔴 **ПИТФОЛЛ: `PREFIX_RE` НЕ должен срезать номер.** Сначала номер убрали из `PREFIX_RE`
> (чтобы `50 cent` не резался) — это **сломало** путь «отвергнутый `TrackNN` → `<Папка> <NN>`»,
> `numeric_title` подскочил 22 → 67. Решение: `title_from_name()` вызывает `split_track_number()`
> как единый источник истины по номерам, а не регекс.
### Остальные баги, найденные ручным проходом
| # | Баг | Пример | Фикс |
|---|---|---|---|
| 1 | Номер в **artist**, не title | `10 Armin Van Buuren feat. Jan Vayne - Serenity` → artist=`10 Armin…` | `split_track_number` перед `split_artist_title` в обеих ветках (`artist<-filename`, `artist<-filename(replaced-release)`) |
| 2 | `Track NN` оставался title | `11. Josh Wink - Higher State…` → title=`Track 11` | после срезания номера остаток `TrackNN` → брать имя файла |
| 3 | Disc-track схлопывался | `1-01..1-10` все → track=1 | `md = re.match(r"^\s*(\d{1,2})\s*[-–—]\s*(\d{1,2})…")` → track=MM |
| 4 | Восклицательные знаки в artist | `!!Dj_hottab_-_test…` → artist=`!!Dj hottab` | `clean_value`: `v.strip("!")`, `PREFIX_RE = r"^\s*!+\s*"` |
| 5 | Заглавные союзы из CP1251 | `Êîðîëü È Øóò` → `Король И Шут` | `normalise_case()` + `FUNCTION_WORDS` (29 файлов) |
| 6 | Ложный сплит в сборнике | `ДВАЖДЫ ДВА - ЧЕТЫРЕ` → artist=`ДВАЖДЫ ДВА` | не сплитить, если родитель в `NON_ARTIST` |
| 7 | `track = 0` | `00-intro`, `00 Geo…` | `if not num.strip("0"): return "", rest` |
**`normalise_case()` — узкое правило:** понижать русский служебный союз/предлог (`и`, `в`, `на`, `с`, `за`…)
только если (а) не первое слово, (б) значение **не в ALL-CAPS** (капс — стиль, `НЕ ХОДИТЕ В АФРИКУ` не трогаем),
(в) предыдущее слово не `&`/`+`/`and` (`Чиж & Со` — «Со» часть названия). 16 тестов.
### ⚪ Что ручной проход НЕ стал чинить (осознанно)
**Рукописный транслит — 126 файлов.** `EMINЭM`, `КняZz`, `Flёur`, `изBESTняк`, `Скаzки`, `ИNOМАРКИ`,
`Глюкоzа`, `Cо`, `WWWаленки`, `Закoлебал`. Это **оригинальное написание** (мода 2000-х) / реальные
псевдонимы (`КняZz`, `Flёur`), а **не порча байтов**. Заменять `Э`→`E` в произвольном имени = выдумывать.
Оставлено как есть, задокументировано как законное.
**`numeric_title` — 15 файлов, все законные:** `007` у Ленинграда, `48` у Пилота, `11 - 11`,
`Високосный год/06.mp3` (имени нет, взять негде).
**`album_equals_artist` — 101 файл, законные:** одноимённые альбомы (`Рубль/Рубль`, `Би-2/2001 Би-2`).
### 📊 Цифры четвёртой волны
| Метрика | v4 | **v5 (финал)** |
|---|---:|---:|
| `fix` | 4777 | **6773** |
| `no_change` | 3129 | 1133 |
| Ноты: «требует решения» | 28 | **28** |
| mojibake в выводе | 0 | **0** |
| Тесты | 80 | **80 + 41 новых = 121** |
| `bad_track` (track вне 1–99) | — | **0** |
| `no_visible_change` | 2301 | **0** (аудит теперь считает track) |
**Новые скрипты четвёртой волны:**
| Файл | Что делает |
|---|---|
| `manual_review.py` | Печать всех правок по папкам в читаемом виде (`T:`/`A:`/`L:`/`#:`). Аргументы `--dir`, `--limit`. Именно им пройден весь файл. |
| `test_track_numbers.py` | 25 тестов извлечения ведущего номера |
| `test_titlecase.py` | 16 тестов нормализации служебных слов |
| `test_separators.py` | 15 тестов разделителей artist/title (третья волна) |
**Обновлены:** `audit_plan.py` (класс `bad_track`, `track_not_canonical`; `no_visible_change` учитывает track),
`build_fix_plan2.py` (колонка `new_track`, `split_track_number`, `normalise_case`, `has_word`, `find_separator`).
### 📂 НОТЫ РЕВЬЮ РАЗБИТЫ ПО КАТЕГОРИЯМ
`2-medium-confidence.md` разросся до **1.1 МБ / 3932 чекбокса** — Obsidian тормозит.
`render_grouped_doc.py` теперь **рекурсивно** делит блок >1200 файлов по категориям
(глубина 1 → `!Rock&Folk`, глубина 2 → `!Rock&Folk/Пилот`), лимит `max_items=1200`.
Итог: **56 файлов**, крупнейший 317 КБ. `Rock&Folk` разложен по группам:
`Пилот` (281), `Ногу Свело` (153), `Номумий Тролль` (80), `Ночные Снайперы` (95), `Пикник` (83).
Функции: `note_body()`, `split_by_category(depth, max_items)`. Индекс `music-tags-review.md`
ссылается на все созданные ноты.
> ⚠️ **Собрано в `/tmp/rg3/` (56 файлов), в vault ЕЩЁ НЕ СКОПИРОВАНО.** Старая версия
> (296 «требует решения») лежит в `personal/plans/music-tags-review/`. Нужно: скопировать
> `/tmp/rg3/*.md` → vault, удалить старые `2-medium-confidence*.md`, синк.
## 📌 ПЛАН ТРЕТЬЕЙ ВОЛНЫ (2026-09-26, апрув Alex) — «сам проставь, потом на апрув»
@@ -503,11 +609,12 @@ if cyr < 2: # одна буква — совпадение
| # | Шаг | Статус |
|---|---|---|
| 1 | **Инвентаризация** read-only: `ffprobe` по 7906 файлам → CSV | ✅ **ГОТОВО (v2)** — `tag_inventory2.py`, `probe_failed`=0 |
| 2 | **CSV с предлагаемым исправлением** → Alex | ✅ **ГОТОВО (v4)** — `~/Downloads/music-tags/tag-fix-plan.csv`, 4777 `fix` / 3129 `no_change` |
| 2a | **Ноты для ревью в Obsidian** → Alex (запрос Alex) | ✅ **ГОТОВО (v4)** — `personal/plans/music-tags-review/`, пересинкнуто |
| 2 | **CSV с предлагаемым исправлением** → Alex | ✅ **ГОТОВО (v5)** — `~/Downloads/music-tags/tag-fix-plan.csv`, 6773 `fix` / 1133 `no_change`, +колонка `new_track` |
| 2a | **Ноты для ревью в Obsidian** → Alex (запрос Alex) | ✅ **ГОТОВО (v5)** — 56 файлов, разбиты по категориям; ⚠️ собрано в `/tmp/rg3/`, в vault НЕ скопировано |
| 2b | **Полный аудит всех строк** (`audit_plan.py` + `final_gate.py`) → 6 багов исправлено | ✅ **ГОТОВО** (запрос Alex «Сам проверил весь файл?») |
| 2c | **Третья волна** по замечаниям Alex: чекбоксы, en-dash, подстроковый поиск стоп-слов | ✅ **ГОТОВО** — 3 бага, блок «требует решения» 296→28 |
| 3 | **Апрув Alex по нотам ревью** → только после этого правки | ⏳ **ЖДЁМ** (v1–v3 отклонены, v4 + ноты отданы) |
| 2d | **Четвёртая волна (ручной проход)**: track-номера, disc-track, `!`-префиксы, регистр, ложный сплит | ✅ **ГОТОВО** — 10 багов, 5158 файлов получили track, 121 тест PASS |
| 3 | **Апрув Alex по нотам ревью** → только после этого правки | ⏳ **ЖДЁМ** (v1–v4 отклонены, v5 + ноты отданы) |
| 4 | **Бэкап** — копировать **только перезаписываемые** файлы в `Music/backup/` | ⏳ |
| 5 | **Ремонт** — перекодировка mojibake + восстановление из имён путей + чистка мусора | ⏳ |
| 6 | **Приёмка** — Alex слушает выборку | ⏳ |
@@ -568,20 +675,26 @@ if cyr < 2: # одна буква — совпадение
| `diagnose_probe_failures.py` | Анализ заголовков файлов, упавших в `probe_failed`: ID3-версия, `hdr_ok`, размеры |
| `test_encoding_guard.py` | **32 регресс-теста (v1).** Защита от порчи корректного UTF-8 (`Björk`, `Saint-Saëns`, `Noël`) + `split_artist_title` |
| `test_plan2.py` | **65 регресс-тестов (v3, актуальный).** Пословная конверсия смешанных строк, короткие токены (`7Á`→`7Б`), контейнеры не становятся артистом, release-папки, отказ сплита на event-именах |
| `render_grouped_doc.py` | **Генератор нот для Obsidian.** CSV → 3 блока по уверенности + обзор, группировка по папкам, чекбоксы, оглавление. **v4:** чекбоксы без префикса `> ` (иначе Obsidian парсит их как цитату и они не кликаются); `ROOT = /storage/Music/` (пути в CSV относительные от `/storage`, не от `/mnt/...`); блок «требует решения» = `нет artist И нет album` |
| `test_separators.py` | **15 регресс-тестов (v4).** Разделители `–`/`—`/приклеенный дефис; отказ сплита на `Рок-н-ролл`, `БИ-2`; event-имена (`НАШЕСТВИЕ 2009 - …`) не расщепляются; `VA - Title` и `Сказка - Теремок` возвращаются целиком. Зовётся `python3 test_separators.py` в `tools/music-tags/` |
| `test_separators.py` | **15 тестов (третья волна).** Разделители `-`/`–`/`—` в artist/title, `Doray- Apocalypse`, отказ на event-именах |
| `test_track_numbers.py` | **25 тестов (четвёртая волна).** `split_track_number`: disc-track `1-01`, ведущие нули `001`, трёхзначные `159_`, `05 48`, `50 cent`, год `1999` |
| `test_titlecase.py` | **16 тестов (четвёртая волна).** `normalise_case`: `Король И Шут`→`Король и Шут`, ALL-CAPS не трогается, `Чиж & Со` защищён |
| `manual_review.py` | **Ручной проход (четвёртая волна).** Печать всех правок по папкам: `T:`/`A:`/`L:`/`#:` префиксы, `--dir`, `--limit`. Именно им пройден весь файл глазами |
| `render_grouped_doc.py` | **Генератор нот для Obsidian.** CSV → блоки по уверенности + обзор. Рекурсивное деление блоков >1200 файлов по категориям (`note_body`, `split_by_category(depth, max_items)`). Чекбоксы БЕЗ `> ` |
| `final_gate.py` | **Гейт полноты.** Проверяет совпадение числа строк с инвентарём, отсутствие заглушек/пустых title/U+FFFD/правок без `source` |
| Артефакт | Где | Что |
|---|---|---|
| CSV-инвентарь v2 | `/mnt/RED_2TB/storage/tmp/tag-inventory2.csv` (NAS) → `~/Downloads/music-tags/tag-inventory.csv` (Mac) | 7906 строк, колонка `source`, `probe_failed`=0 |
| **CSV-план правок v4** | `~/Downloads/music-tags/tag-fix-plan.csv` (Mac, ~2.2 МБ) | **Отдан Alex 2026-09-26 (финал).** 4777 `fix` / 3129 `no_change` / 0 `probe_failed` |
| **CSV-план правок v5** | `~/Downloads/music-tags/tag-fix-plan.csv` (Mac, ~2.2 МБ) | **Отдан Alex 2026-09-26 (финал).** 6773 `fix` / 1133 `no_change` / 0 `probe_failed`, +`new_track` |
| Копия БД Jellyfin | `/tmp/jf-copy.db` (Mac) | Для чтения API-ключа (см. `[[family/how-to/jellyfin-config]]`) |
| Копия плана на NAS | `/tmp/tag-fix-plan.csv` (NAS) | Для запуска `id3_direct.py`/`check_ddt_album.py` с хостовым Python |
| **Ноты ревью в vault** | `personal/plans/music-tags-review/` (4 файла) | Обзор + 3 блока по уверенности; **вход для апрува Alex**, синкнуто |
| **Ноты ревью в vault** | `personal/plans/music-tags-review/` | Ранее 4 файла; **v5 — 56 файлов**, разбитых по категориям (Rock&Folk по группам). ⚠️ собрано в `/tmp/rg3/`, в vault ещё НЕ скопировано |
### Колонки `tag-fix-plan.csv`
`path | current_verdict | cur_title | cur_artist | cur_album | new_title | new_artist | new_album | action | source`
`path | current_verdict | cur_title | cur_artist | cur_album | new_title | new_artist | new_album | new_track | action | source`
**v5:** добавлена колонка **`new_track`** (1–99 или пусто). Заполнена у 5158 строк.
Последняя колонка `source` показывает, **откуда** взято значение — `title<-filename`,
`artist<-folder`, `artist<-filename`, `title<-folder+tracknum`, `translit artist tag; folder is Cyrillic`.