From ba364607e2d4c75685a64d9b5706669c2b5cb750 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Mon, 28 Sep 2026 01:27:53 +0600 Subject: [PATCH] [2026-09-28] eagle: personal/tech/music-tags-repair-truenas.md --- personal/tech/music-tags-repair-truenas.md | 185 ++++++++++++++++++++- 1 file changed, 184 insertions(+), 1 deletion(-) diff --git a/personal/tech/music-tags-repair-truenas.md b/personal/tech/music-tags-repair-truenas.md index 30eed3d5..7c734143 100755 --- a/personal/tech/music-tags-repair-truenas.md +++ b/personal/tech/music-tags-repair-truenas.md @@ -8,8 +8,16 @@ wave: 38 diff_v6_md5: a2a777f65eb1b6c0729f16bbafe0acc6 diff_v6_status: OK — 394 файла / 398 правок, покрытие ПОЛНОЕ (папок с разным album после дифа: 0; путей вне БД: 0). НЕ ПРИМЕНЁН, dry-run не запускался, апрува нет. diff_v6_last_good: DIFF-v6.csv (394/398); история бэкапов в блоке «СЕССИЯ 28» +diff_v7_md5: 7047e5c4c320a839fe1568b2bf5761a9 +diff_v7_status: Собран 28 сен 01:15 через build_diff_v7.py. 204 строки (203 правки). НЕ ПРИМЕНЁН. Переработка v6 — расширен охват, схлопывание по (folder, field, old, new). +diff_v7_vs_v6: "v6 требовал aa=='Various Artists' у ВСЕХ файлов папки → папки-сборники без этого тега не видел. v7 убрал это условие: берёт ЛЮБЫЕ папки >=2 файлов с разным album. Поэтому Sbornik_Novij_Rok перешёл из scope=outlier в scope=folder." +diff_v7_open_defect: 🔴 ОДИНОЧКИ НЕ ПОКРЫТЫ. build_diff_v7.py правило A содержит `if len(fl) < 2: continue` → папки с 1 файлом пропускаются. Правило B правит только albumartist. Итог: 244 файла / 51 папка Infected Mushroom (распались на ~35 альбомов-одиночек) в v7 НЕ входят — тот же открытый класс, что и в v6. +v6_coverage_measured: "398 правок / 394 файла / 116 папок; scope: outlier 86 · orphan 173 · infected 139; field: album 394 · albumartist 4. Пустых old/new = 0, old==new = 0, путей вне БД = 0 — файл чистый." +v6_not_closed: 🔴 v6 покрывает 394 файла из 7808 в БД (5%). Не закрыто: (а) одиночки вне IM — 37 папок, где album != имя папки; (б) 109 файлов IM в 14 папках; (в) 492 однородные папки — НЕ пропуск (там old==new). +single_folders_open: "37 папок-одиночек с расхождением album != имя папки (вычислено precise9.py по navidrome-fresh.db). НЕ внесены в DIFF-v6.csv — ждут решения Alex по правилу (A или B, см. блок ниже)." +waits_decision: 🔴 ПРАВИЛО ДЛЯ ОДИНОЧЕК НЕ ВЫБРАНО. A: album = имя папки как есть (с годом и артистом) → 37 строк. B: album = имя папки БЕЗ года/артиста, а для одиночек album = имя РОДИТЕЛЬСКОЙ папки (как в правиле Alex для IM) → другой список и другое число. Запись в DIFF-v6.csv заблокирована до ответа. last_session: 28 -blocker: НЕТ apply-plan под DIFF-v6.csv (дифф собран из БД, не из плана). apply_tags_v2.py читает только /app/data/apply-plan.csv (14 колонок, путь с /storage/Music/). Нужен сборщик build_applyplan_v6.py в ~/Downloads/music-tags/ и план apply-plan-v7.csv (7807 строк). Затем апрув Alex → dry-run → apply. +blocker: НЕТ apply-plan под DIFF-v6.csv (дифф собран из БД, не из плана). apply_tags_v2.py читает только /app/data/apply-plan.csv (14 колонок, путь с /storage/Music/). Нужен сборщик build_applyplan_v6.py в ~/Downloads/music-tags/ и план apply-plan-v7.csv (7807 строк). Затем апрув Alex → dry-run → apply. 🔴 УТОЧНИТЬ: apply-plan собирать от v6 или от v7 (v7 новее и шире — вероятно от v7). done_session28: удалена папка !Rock&Folk/Король и Шут/Король и шут-1 (17 файлов, 59 МБ); база navidrome.db НЕ тронута. tags: - music @@ -110,6 +118,53 @@ related: > > **Разложение 282 файлов:** `album` 183 · `artist` 155 · `albumartist` 158 · `track` 58. > +> ### 📌 СБОРЩИКИ ДИФФА: `build_diff_v6.py` vs `build_diff_v7.py` (сессия 28) +> +> Оба лежат в `~/Downloads/music-tags/`. Оба читают **`navidrome-fresh.db`** (снимок БД, источник +> истины = текущее состояние тегов), НЕ план и НЕ диск. Фильтр `path NOT LIKE '%backup%'`. +> Формат вывода одинаков: `scope,folder,path,field,old,new`. Пути **БЕЗ** префикса `/storage/Music/` +> (префикс добавляет только сборщик плана). +> +> **`build_diff_v7.py` — докстринг, 7 правил (единственное место, где они записаны):** +> ``` +> 1. album = имя папки-владельца (ведущий '!' срезается; год/артист в имени не трогаем) +> 2. albumartist = Infected Mushroom (в папке Infected Mushroom) +> 3. artist = НЕ ТРОГАЕМ +> 4. track = НЕ ТРОГАЕМ +> 5. Правило применяется ТОЛЬКО к файлам, выбивающимся из папки: +> - папка >=2 файлов и album разный -> выравниваем на имя папки +> - папка ==1 файл -> НЕ ТРОГАЕМ (норма, папка-артист) +> 6. Пути ровно как в БД (без префикса /storage/Music/) +> 7. Схлопывание по (folder, field, old, new); сортировка по алфавиту полей +> ``` +> +> | | v6 | **v7** | +> |---|---|---| +> | Триггер | `aa == 'Various Artists'` у **ВСЕХ** файлов папки + album разный | **любые** папки ≥2 файлов с разным album | +> | Infected Mushroom | `album` (разбор имени папки) + `albumartist` | только `albumartist` | +> | `artist` / `track` | не трогает | не трогает | +> | Схлопывание | ❌ нет | ✅ `(folder, field, old, new)` | +> | Строк | 399 (398 правок, 394 файла) | 204 (203 правки) | +> | md5 | `a2a777f65eb1b6c0729f16bbafe0acc6` | `7047e5c4c320a839fe1568b2bf5761a9` | +> +> **Почему v7 шире:** v6 требовал `Various Artists` у всех файлов папки → папки-сборники **без** +> этого тега он не видел вовсе. v7 снял условие. Следствие: `!A Not sorted/Sbornik_Novij_Rok` +> перешёл из `scope=outlier` в `scope=folder`. +> +> 🔴 **ГЛАВНОЕ ПРО v7: ОН НЕ ЗАКРЫВАЕТ ЗАДАЧУ ПОЛНОСТЬЮ.** Правило 5 (`if len(fl) < 2: continue`) +> пропускает **папки с одним файлом**, а правило B у одиночек правит только `albumartist`, +> не `album`. Значит **244 файла / 51 папка `Infected Mushroom`**, распавшиеся на ~35 альбомов- +> одиночек (`Becoming Insane (Chemical Trolls Remix)`, `CDS`, `Promo`, `Unreleased Tracks` ×29 и др.), +> в v7 **не входят** — это ровно тот же незакрытый класс дефекта, что зафиксирован выше по v6 +> (блок «ОТКРЫТЫЙ КЛАСС ДЕФЕКТА»). Правило Alex для одиночек записано ниже, строка «ПРАВИЛО ALEX +> ДЛЯ ОДИНОЧЕК»: папка с 1 файлом → `album` = имя **РОДИТЕЛЬСКОЙ** папки. +> +> **Следствие для плана:** `apply-plan` надо собирать **от v7** (новее и шире), но перед этим +> решить, добавлять ли в v7 правило для одиночек. Если добавлять — сборщик должен обходить +> **ВСЕ** папки, включая `len(fl) == 1`, беря `album` из имени родительской папки. +> ⚠️ Память по сборникам: правило «доминанта ≥60%» и `SKIP`-исключения **удалены** (Alex 27 сен) — +> обходить все папки ≥2 файлов, `album` = имя папки-владельца без `!`. +> > ### 🔴 RED FLAG: ПУТЬ В ПЛАНЕ ОБЯЗАН НАЧИНАТЬСЯ С `/storage/Music/` > > Первый dry-run дал **`к обработке: 0`, 282 MISSING, «путь вне /music»**. @@ -6133,6 +6188,134 @@ z Другое…/Pushnoy.ru/старые cur 'старые' -> new 'Pus > ⚠️ **mutagen на хосте NAS НЕТ** — читать теги с диска только внутри контейнера > `tagwriter:latest` (монтирование `${MUSIC}:/music:ro`). +## 31-я волна — ЗАМЕР ПОКРЫТИЯ v6/v7 И ПРАВИЛО ДЛЯ ОДИНОЧЕК (2026-09-28, сессия 28) + +### Триггер + +Alex: «по diff v6/v7 скоуп работ понятен? задача в доке?» → серия уточнений → +**«в diff v6 реальные данные? они закрывают задачу?»** → **«что не закрыто? конкретно»** → +**«добавь эти 9 файлов в v6 список. вручную»**. + +### ✅ v6 — данные РЕАЛЬНЫЕ, файл чистый + +Замер (`/usr/bin/python3` + `csv.DictReader`, `encoding="utf-8-sig"`): + +``` +DIFF-v6.csv 399 строк / 398 правок / 394 файла / 116 папок +scope: outlier 86 · orphan 173 · infected 139 +field: album 394 · albumartist 4 +пустых old: 0 | пустых new: 0 | old==new: 0 | путей вне БД: 0 +``` + +Сверка с `navidrome-fresh.db` (7808 файлов) — расхождений нет. **Файл годен.** + +### 🔴 НО задачу v6 НЕ закрывает — 394 из 7808 файлов (5%) + +| Срез | Всего | Покрыто v6 | +|---|---:|---| +| Файлов в БД | 7808 | **394** | +| Папок всего | 628 | — | +| Папок 2+ файлов | 575 | — | +| Папок 2+ с **разным** album | 83 | **83** ✅ | +| Папок 2+ однородных | 492 | 0 — **НЕ пропуск** (old==new) | +| Папок-одиночек | 53 | 30 (частично) | +| Файлов Infected Mushroom | 244 | 135 | +| Папок IM (одиночек 27) | 51 | 37 | + +**Вывод: v6 закрывает 83 неоднородные папки полностью. Незакрыто — одиночки.** + +### 🔴 37 ПАПОК-ОДИНОЧЕК с расхождением `album != имя папки` + +Вычислено `/tmp/precise9.py` (`mode=ro`), полный список: + +``` +IM (27 папок, правила Alex из блока «ПРАВИЛО ALEX ДЛЯ ОДИНОЧЕК»): + (2001) Infected Mushroom - Wider (Domestic RMX) TALB='Wider (Domestic RMX)' + (2003) Fly Agaric - I See Myself (…Remix) TALB='Unreleased Tracks' + (2003) Hallucinogen - Gamma Goblins (…remix) TALB='Unreleased Tracks' + (2003) Infected Mushroom - Birthday (DJ Goni Peles) TALB='Birthday (DJ Goni Peles Remix)' + (2003) Infected Mushroom - PGM (Delirious Remix) TALB='P.G.M. (Delirious Remix)' + (2004) Astrix - Coolio (…Remix) TALB='Unreleased Tracks' + (2004) IM - Elation Station (Beat Hackers Remix) TALB='Elation Station' + (2004) IM - I Wish (Skazi Remix) TALB='I Wish [skazi Remix] CDS' + (2004) IM - Shakawkaw (Vibe Tribe Rmx) TALB='Shakawkaw (Vibe Tribe Rmx)' + (2005) IM - Chaplin (Eskimo Rmx) TALB='Chaplin (Eskimo Rmx)' + (2005) IM - I'm The Superviz'a (Toxical Rmx) TALB='CDS' + (2005) IM - In The Mix (N-Joy) TALB='In The Mix (N-Joy) 10-12' + (2005) IM - Symphonatic (Silent Hill Rmx) TALB='Promo' + (2005) IM - Wider (Astrix Rmx) TALB='Promo' + (2005) Yahel And IM - Electro Panic (Psycraft Remix) TALB='Electro Panic (PsyCraft Remix)' + (2006) IM - Im the Supervisor (Shift Rmx) TALB='Im The Supervisor Shift Rmx (CDS)' + (2007) IM - Becoming Insane (Chemical Trolls Rmx) TALB='Becoming Insane (Chemical Trolls Remix)' + (2007) IM - Bust a Move (Aya Remix) TALB='Unreleased Tracks' + (2007) IM - Dancing With Kadafi (Future Prophecy) TALB='Dancing With Kadafi (…)' + (2007) IM - Deeply Disturbed (Astrix Mix) TALB='Deeply Disturbed (Astrix Mix)' + (2007) IM - Institute of Science (Multi System Rmx) TALB='Institute Of Science (…)' + (2007) IM - Suliman (RAZ Rmx) TALB='Suliman (RAZ Rmx)' + (2009) IM - DJ Times Shortlist TALB='DJ Times Shortlist-SAT-18-11' + (2012) IM – U r so fucked (GANDI Remix) TALB='U r so fucked (GANDI Remix)' + IM Vs. The Prodigy - Supervized Firestarter TALB='Supervized Firestarter (…)' + ИСКЛЮЧЕНИЯ Alex: 2016 - Liquid Smoke (Single) → 'Liquid Smoke' (без WEB) + 2016 - Nutmeg (Single) → 'Nutmeg' + +НЕ-IM одиночки с расхождением (10): + !Pop, Club, Любэ TALB='Radio' (файл в КОРНЕ категории) + !Pop, Club, Любэ/!Radio/z Старое/disk/4 INSTRUMENTAL TALB='RADIO 4 INSTRUMENTAL' + !Pop, Club, Любэ/!Radio/z Старое/disk_v2/4 INSTRUMENTAL TALB='RADIO 4 INSTRUMENTAL' + !Retro/Андрей Миронов TALB='Лучшие песни… Александра Зацепина к 100-летию' + !Retro/Тамара Миансарова TALB='Храбрый Зайчик. Песни для детей' + !Rock&Folk/Otava Yo TALB='Ой, Дуся, ой, Маруся' + !Rock&Folk/Александр Пушной TALB='Лето прошло...' + !Rock&Folk/Наутилус Помпилиус TALB='Золотой век' ← ВНИМАНИЕ: папка НЕ одиночка + !Рэп TALB='Вдох-выдох' (файл в КОРНЕ категории) + !Рэп/Eminem,50cent/The Slim Shady LP TALB='The Slim Shady LP CD1' +``` + +> 🔴 **ПИТФОЛЛ: `!Rock&Folk/Наутилус Помпилиус` — НЕ одиночка.** 80 файлов, 9 подпапок +> (`De Luxe collection` 20 · `Подъём` 12 · `Атлантида` 11 · `Gold Age` 10 · `Раскол` 10 · +> `1998 Яблокитай` 9 · `1989 …Столицы` 4 · `2000 - песни` 3). Одиночка — только +> `Гудбай Америка.mp3` в корне папки (`TALB='Золотой век'`). Вывод «одиночка» из паттерна +> пути **без подсчёта файлов** — ошибка; всегда считать `len(files)` в папке. + +### 🔴 РАЗНОГЛАСИЕ ПРАВИЛ — запись в DIFF-v6.csv НЕ выполнена + +Alex потребовал добавить одиночек в `DIFF-v6.csv` вручную. Измерение вскрыло, что +**два разных правила дают два разных ответа**, и выбор — не моё решение: + +| | Правило | Результат на `(2001) Infected Mushroom - Wider (Domestic RMX)` | +|---|---|---| +| **A** | `album` = имя папки **как есть** (с годом и артистом) | `2001) Infected Mushroom - Wider (Domestic RMX` | +| **B** | `album` = имя папки **БЕЗ года и артиста**; для папок с 1 файлом — имя **РОДИТЕЛЬСКОЙ** папки | `Wider (Domestic RMX)`, а для IM → `Remixes` | + +Правило **B** — это то, что записано в доке в блоке «ПРАВИЛО ALEX ДЛЯ ОДИНОЧЕК» (строка 188): +`папка с 1 файлом -> album = имя РОДИТЕЛЬСКОЙ папки (Remixes, Mixes)`. + +Правило **A** — то, что даёт автоматический проход `album = clean(имя папки)`. + +**Число строк зависит от правила:** A → 37, B → другой список и другое число. + +> 🔴 **БЛОКЕР: без ответа Alex (A или B) строки в `DIFF-v6.csv` НЕ вписаны.** +> Вписать наугад = создать новый класс дефекта. + +### 🔴 ПИТФОЛЛ: обрезанный вывод `head -N` принят за полный список + +Первый проход дал «9 файлов брака» — это был результат `head -80` по списку 27 одиночек. +Полный пересчёт тем же критерием дал **37**. **Правило: прежде чем называть Alex число — +убедиться, что вывод не обрезан; считать длину в скрипте, а не глазами по хвосту.** + +### 🛠 ИНСТРУМЕНТЫ ЭТОЙ ВОЛНЫ + +| Файл | Назначение | +|---|---| +| `/tmp/read_9.py` | чтение 9 папок из БД (`mode=ro`) — `title/artist/album/album_artist/track_number` | +| `/tmp/precise9.py` | ✅ **точный замер**: одиночки с `album != имя папки` + разбор Наутилуса по подпапкам | +| `~/Downloads/music-tags/navidrome-fresh.db` | источник истины (локальный снимок БД Navidrome, 7808 файлов) | + +Приём доступа к БД (без TCC-промптов и без записи в рабочую базу): +```python +con = sqlite3.connect("file:%s?mode=ro" % DB, uri=True) +``` + ## Связанные заметки - [[family/how-to/jellyfin-config]] — живой инстанс Jellyfin на Taiga, план добавления Music-библиотеки