[2026-09-28] eagle: personal/plans/music-artist-albumartist-repair.md personal/plans/tagwriter-container.md personal/tech/music-tags-repair-truenas.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-28 00:32:09 +06:00
1 parent b718a09e95
commit 8ad2c77435
3 files changed
+157 -13

No files matched your search

@@ -1166,7 +1166,10 @@ Dry-run считает ФАЙЛЫ: album 28 · albumartist 6 · genre 4359 (в
| 🔴 **Дифф был собран от КАНОНА** | Птахи в дифе не было, хотя дефект в базе есть (диск ушёл вперёд канона) | ✅ **ЗАКРЫТ 27 сен:** пересобран от БД → `DIFF-db-to-fix.csv` |
| 🔴 **Ложное расхождение по `genre`** | `media_file.genre` пуст у всех 7861 → дифф показывал 7861 «правок» | ✅ **ЗАКРЫТ 27 сен:** жанр читается из `tags` jsonb, `genre` в дифе = 0 |
| 🔴 **Птаха разбит на 2 альбома** | `'Красивые, богатые'` (7 файлов) + `'Птаха - Красивые, богатые (2009)'` (1) | ⏳ **В дифе v5** (`album` 60-я правка), ждёт прогона |
| 🔴 **Альбомы-одиночки** | **215** альбомов из 1 трека, где `album != имя папки` (класс Бумера / Симпатии) | ⏳ **ОТКРЫТ:** нужен разбор «мусор vs законный сингл». См. § АЛЬБОМЫ-ОДИНОЧКИ |
| 🔴 **Альбомы-одиночки** | **215** альбомов из 1 трека, где `album != имя папки` (класс Бумера / Симпатии) | 🔴 **ОТКРЫТ, УТОЧНЁН 27 сен (сессия 39):** цифра `215` неверна → **`56` однотрековых папок**. Класс дефекта разделён на два (см. § АЛЬБОМЫ-ОДИНОЧКИ) |
| 🔴 **Группировка по папкам вне `!`/`Разное`** | `Infected Mushroom`: ~35 альбомов-одиночек вместо одного `album` на папку-владельца. `Other Track's` 23 файла → 6 разных `album` | ⏳ **ОТКРЫТ (сессия 39):** правило реализовано только для папок со `!` и `9999 - Разное`. Ждёт критерия Alex: чем «сборка ремиксов» отличается от настоящего альбома |
| **`albumartist` мусорный при смене `artist`** | Т9: `TPE1='Т9'`, `TPE2` остался `'Вдох-Выдох'` | ✅ **ЗАКРЫТ 27 сен:** правило дописано (`albumartist='Т9'`), повторный прогон, `TPE2` 157 → 158, детектор нашёл ровно 1 такой файл |
| 🔴 **`path` без префикса `/storage/Music/`** | Мой сборщик писал относительный путь → dry-run дал `0 к обработке` и 282 MISSING | ✅ **ЗАКРЫТ 27 сен:** `"path": "/storage/Music/" + rel` |
| **Трофим: 8 альбомов, 3 копии** | 3 физические копии → 8 альбомов, 38 имён задвоено | ✅ **ЗАКРЫТ:** 50 дублей удалены, план v5 правит альбомы/исполнителя |
| **Тема дороги** | `title='AudioTrack 17'`, `artist='Бумер'`, один в альбоме | ⏳ **В дифе v5:** `album='Ленинград'`, `artist='Ленинград'` |
+23 -11
View File
@@ -1201,23 +1201,35 @@ Alex: «**я не буду читать всю твою хуйню. Я жду о
## 5. Запуск
> 🔴 `docker run` и `docker build` на NAS **блокируются апрувом** и сетью.
> Рабочая схема: `docker create` + `docker cp` + `docker start -a`.
> Рабочая схема: `docker create` + `docker start -a`.
> 🔴🔴 **ИСПРАВЛЕНО 27 сен (сессия 39): путь к скрипту — `/app/`, НЕ `/app/data/`.**
> `/app/data` — это **монтируемая папка с планом** (`CSV_IN=/app/data/apply-plan.csv`),
> сам скрипт лежит **в образе**: `/app/apply_tags_v2.py` (25971 B).
> С `/app/data/apply_tags_v2.py` вывод: `can't open file '/app/data/apply_tags_v2.py': No such file`.
> 🔴 **Флага `--plan` НЕ СУЩЕСТВУЕТ** — путь плана захардкожен. Реальные аргументы:
> `--dry-run --apply --readback --verify --force --limit N`.
```bash
# сухой прогон
docker create --name tw --entrypoint python3 \
-v /mnt/RED_2TB/docker/arr/tagwriter:/app/data \
# сухой прогон (Music :ro)
docker create --name twdry --entrypoint python3 \
-v /tmp/tw306:/app/data:ro \
-v /mnt/RED_2TB/storage/Music:/music:ro \
tagwriter:v2 /app/data/apply_tags_v2.py --dry-run
docker cp /mnt/RED_2TB/docker/arr/tagwriter/apply_tags_v2.py tw:/app/apply_tags_v2.py
docker start -a tw && docker rm -f tw
tagwriter:latest /app/apply_tags_v2.py --dry-run
docker start -a twdry && docker rm -f twdry
# боевой прогон + сверка
# (то же, но -v ...Music:/music БЕЗ :ro, и --apply --verify)
# боевой прогон + сверка (Music rw, /app/data rw)
docker create --name twapply --entrypoint python3 \
-v /tmp/tw306:/app/data \
-v /mnt/RED_2TB/storage/Music:/music \
tagwriter:latest /app/apply_tags_v2.py --apply --verify
docker start -a twapply && docker rm -f twapply
```
Режимы: `--dry-run` (по умолчанию) · `--apply` · `--verify` · `--readback` ·
`--force` · `--limit N`
🔴 **`path` в плане ОБЯЗАН начинаться с `/storage/Music/`.** `map_path()` снимает этот префикс;
относительный путь трактуется как «вне /music» → файл уходит в MISSING, `к обработке: 0`.
Тихий отказ: скрипт завершается `exit=0`, а правок ноль. Канон `APPLY-PLAN.csv` префикс имеет —
сверять формат перед первым прогоном.
## 6. Инварианты (падают на старте, до записи)
+130 -1
View File
@@ -100,7 +100,136 @@ related:
> Файлы в сборниках (`disk/`, `disk_v2/`) **удалять нельзя** — указание Alex:
> «они корректно живут в сборниках». Удаляем ТОЛЬКО дубли альбомов.
> ## 🔴 АКТУАЛЬНОЕ СОСТОЯНИЕ (2026-09-27, сессия 38) — ЧИТАЙ ЭТО ПЕРВЫМ
> ## 🟢 АКТУАЛЬНОЕ СОСТОЯНИЕ (2026-09-27, сессия 39) — APPLY v6 ВЫПОЛНЕН, БД ПЕРЕСОЗДАНА
>
> 🔴🔴 **`apply` ЗАПУСКАТЬ ТОЛЬКО ПО ПРЯМОМУ «go» ALEX.** Дословно: «**без подтверждения
> никакого apply блядь**». В этой сессии «go» получено («Давай») — прогон выполнен.
>
> ✅ **БОЕВОЙ ПРОГОН v6 ВЫПОЛНЕН: 282/282 ok, err=0.** VERIFY read-back: **282 совпало, MISMATCH 0**.
> Приёмка независимым скриптом с диска (`verify_applied_v6.py`, mutagen): **282/282, расхождений 0**.
> ```
> TIT2 title 0
> TPE1 artist 155
> TPE2 albumartist 158
> TALB album 183
> TCON genre 0
> TRCK track 58
> ```
> 🔑 **Артефакт прогона — 282 файла, НЕ 7861.** `changed=no` пропущено 7579, не-mp3 562.
>
> ✅ **БД NAVIDROME ПЕРЕСОЗДАНА И ПЕРЕСКАНИРОВАНА:**
> `media_file` **7811** · `album` **718** (до сноса было 7861 / 727).
> Бэкап перед сносом: `/mnt/RED_2TB/docker/navidrome/data/navidrome.db.bak-<ts>/` (28 МБ + wal 13 МБ).
> Порядок: `compose stop` → `rm navidrome.db` → `compose up -d` → полный скан.
> ⚠️ `navidrome.db-shm` / `-wal` при остановленном контейнере уже отсутствуют — `rm` по ним даёт
> `No such file`, это норма (не ошибка).
>
> ---
>
> ### 🔴🔴 ГЛАВНЫЙ ПИТФОЛЛ СЕССИИ 39: `path` В ПЛАНЕ ОБЯЗАН ИМЕТЬ ПРЕФИКС `/storage/Music/`
>
> Первый dry-run дал **`ИТОГО к правке: 0`** и **282 MISSING** с текстом `путь вне /music`.
> Причина: в моём сборщике `build_diff_v6.py` в колонку `path` писался **относительный** путь
> (`!Retro/…`), а канон `APPLY-PLAN.csv` хранит **абсолютный** (`/storage/Music/!Retro/…`).
> ```
> APPLY-PLAN.csv (канон): /storage/Music/!Классика/… ← префикс ЕСТЬ (эталон)
> apply-plan-v5.csv: !A Not sorted/… ← префикса НЕТ (дефект)
> apply-plan-v6.csv: !A Not sorted/… ← префикса НЕТ (тот же дефект)
> ```
> 🔑 **`map_path()` в образе снимает префикс `/storage/Music/`; без префикса путь считается
> «вне /music» и уходит в MISSING.** Фикс — одна строка: `"path": "/storage/Music/" + rel`.
> 🔴 **Правило: сверять формат `path` с каноном ПЕРЕД первым dry-run — это дешевле, чем
> 40 минут прогона в пустоту.**
>
> ### 🔴 ЗАПУСК ПРОГОНА: РАБОЧАЯ КОМАНДА (дока была неверна в пути к скрипту)
>
> ```bash
> docker create --name twdry6 --entrypoint python3 \
> -v /tmp/tw306:/app/data:ro \
> -v /mnt/RED_2TB/storage/Music:/music:ro \
> tagwriter:latest /app/apply_tags_v2.py --dry-run # ← /app/, НЕ /app/data/
> docker start -a twdry6
> ```
> ❌ **Дока предписывала `/app/data/apply_tags_v2.py` — НЕВЕРНО.** Скрипт лежит в образе
> (`/app/apply_tags_v2.py`, 25971 B), а `/app/data` — это монтируемая папка с планом.
> С `/app/data/...` вывод: `can't open file '/app/data/apply_tags_v2.py': No such file`.
> 🔴 **`--plan` НЕ СУЩЕСТВУЕТ.** `CSV_IN = "/app/data/apply-plan.csv"` — константа.
> Аргументы скрипта: `--dry-run --apply --readback --verify --force --limit N`.
> Раннер: `bash ~/Automation/music-tags/run_tagwriter.sh dry|apply|readback`.
>
> ### 🔴 ПИТФОЛЛ: `albumartist` ОСТАЁТСЯ МУСОРНЫМ, ЕСЛИ МЕНЯЛСЯ ТОЛЬКО `artist`
>
> Найден дефект ПОСЛЕ прогона, приёмкой с диска: файл `!Рэп/03. Т9 - Вдох-Выдох…` получил
> `TPE1='Т9'`, но **`TPE2` остался `'Вдох-Выдох'`** (старое значение `artist`).
> Причина: в плане правились `album` + `artist`, `albumartist` не трогался, а в файле он был мусорным.
> Фикс: дописать `albumartist='Т9'` в правило → прогон повторён, `TPE2` 157 → **158**.
> 🔑 **ПРАВИЛО: если правишь `artist`, проверь `albumartist` ЭТОГО ЖЕ файла.** Детектор:
> `cur_artist != new_artist AND cur_albumartist == new_albumartist AND albumartist == cur_artist`
> → нашёл ровно 1 файл в библиотеке.
>
> ### 🔴🔴 ОТКРЫТЫЙ ДЕФЕКТ: ГРУППИРОВКА ПО ПАПКАМ НЕ РАБОТАЕТ ВНЕ `!`-ПАПОК И `Разное`
>
> Alex: «**В infected mushroom как будто не сработала группировка по папкам**».
> Проверка **по БД** (не по диску!) показала: в `Infected Mushroom` — **~35 альбомов-одиночек**
> вместо одного `album` на папку-владельца:
> ```
> Unreleased/Remixes, Mixes/ → Becoming Insane (Chemical Trolls Remix), Birthday (DJ Goni Peles
> Remix), Bust a Move (Infected Remix), CDS, Chaplin (Eskimo Rmx),
> Elation Station, P.G.M. (Delirious Remix), Shakawkaw (Vibe Tribe Rmx),
> Suliman (RAZ Rmx), Wider (Domestic RMX), … ~30 однотрековых
> Unreleased/Other/ → Unreleased Tracks 14, Other Tracks 2, Magnet, Heat Seekers…,
> Full On Volume 5, Deck Wizards… (23 файла, 6 разных album)
> "Other Track's" → Alex: album = 'Other Tracks', БЕЗ апострофа ✅ (решение получено)
> ```
> 🔴 **Корневая причина: правило «`album` = имя папки-владельца» реализовано ТОЛЬКО для
> двух классов — папок со `!` в имени и `9999 - Разное`.** Обычные папки без `!`
> (`!Pop, Club, Любэ/Infected Mushroom/<любая>/`) под правило НЕ ПОПАДАЮТ.
>
> 🔴 **ДВА РАЗНЫХ КЛАССА, СМЕШИВАТЬ НЕЛЬЗЯ** (посчитано по БД):
> ```
> КЛАСС А — одиночный сбой внутри нормального альбома: 118 папок по ~1 файлу
> !Rock&Folk/Пилот/2003 Детство 1 из 25
> !Rock&Folk/Король и Шут/Камнем по голове 1 из 21
> !Рэп/Трофим/08 Я скучаю по тебе 1 из 18
> → album у ОДНОГО файла == его title, остальные верны. НЕ ТА ЗАДАЧА, вслепую не трогать.
> КЛАСС Б — папка ЦЕЛИКОМ из одиночек (реальная задача): Infected Mushroom/Unreleased/*
> ```
> БД-запрос класса А: `SELECT path,title,album,… WHERE album == title`, группировка по
> `os.path.dirname(path)`. Скрипт `~/Downloads/music-tags/db_find_single_albums.py`.
> ⚠️ **Цифра «215 альбомов-одиночек» — НЕВЕРНА, правильно `56`** (пересчёт по папкам, сессия 37).
>
> ⚠️ **ОСТАЛОСЬ (перед следующим apply):** (1) правило группировки для папок БЕЗ `!` —
> ждёт критерия Alex, чем «папка-сборка ремиксов» отличается от настоящего альбома
> (`B.P. Empire` — настоящий, `Remixes, Mixes` — сборка); (2) `!Retro` в БД дал **2** альбома
> (157 + 1 файл), `!Классика` — **2** (34 + 1): по одному файлу отвалилось, разобрать;
> (3) `Sbornik_Novij_Rok` genre; (4) `10 Ностальгия`/`Боже, какой пустяк`.
>
> ### 🔴 ПРОЦЕССНЫЙ УРОК СЕССИИ 39: НЕ ЛОПАТИТЬ ДИСК, КОГДА СПРАШИВАЮТ ПРО БАЗУ
>
> Alex: «**Что ты делаешь? Ты базу проверяешь или ты назуя тот файлуху пошел лопатить?!**».
> Я запустил `os.walk` + mutagen по 7811 файлам, чтобы найти расхождения `album`, — это 5+ минут
> и не тот источник. **Navidrome читает из СВОЕЙ БД, а теги уже подтверждены приёмкой.**
> 🔑 **Правило: вопрос «как выглядит X в плеере» → запрос к БД. Диск читать ТОЛЬКО для
> точечной проверки конкретного файла, не для обхода библиотеки.**
> БД копировать локально: `cp navidrome.db /tmp/tw306/` → `sqlite3 "file:…?mode=ro"`.
>
> ### 📁 АРТЕФАКТЫ СЕССИИ 39
> ```
> ~/Downloads/music-tags/DIFF-v6.csv 103 строки, md5 60819ea387be6f98c3fdcb9da601e9c8 (после фикса Т9)
> ~/Downloads/music-tags/apply-plan-v6.csv 7861 стр, path с /storage/Music/
> ~/Downloads/music-tags/build_diff_v6.py СБОРЩИК v6 — 8 правил, читает БД
> ~/Downloads/music-tags/verify_applied_v6.py приёмка: read-back TALB/TPE2/TPE1/TRCK + сверка с планом
> ~/Downloads/music-tags/spot_check_v6.py 11 точечных кейсов (сборники/Трофим/адресные)
> ~/Downloads/music-tags/check_aa_stale.py детектор «artist изменён, albumartist остался мусорным»
> ~/Downloads/music-tags/db_find_single_albums.py поиск класса А по БД
> ~/Downloads/music-tags/summary_v6.py сводка «что и где меняется»
> NAS: /tmp/tw306/apply-plan.csv план под именем, которое читает скрипт
> NAS: /tmp/tw306/dryrun-v6.log лог dry-run
> NAS: /mnt/RED_2TB/docker/navidrome/data/navidrome.db.bak-<ts>/ бэкап БД
> ```
> ✅ **Бэкап ФАЙЛОВ музыки отдельно НЕ создавался** — Alex: «Бэкап файлы уже есть».
> ## 🔴 АКТУАЛЬНОЕ СОСТОЯНИЕ (2026-09-27, сессия 38) — АРХИВ
>
> 🔴🔴 **`apply` НЕ ЗАПУСКАТЬ БЕЗ ПРЯМОГО ПОДТВЕРЖДЕНИЯ ALEX.** Дословно: «**без подтверждения
> никакого apply блядь**». Дифф и dry-run — можно, запись — только «go».