From 8ad2c774351fa8ece4563a343bdb98fc265ed858 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Mon, 28 Sep 2026 00:32:09 +0600 Subject: [PATCH] [2026-09-28] eagle: personal/plans/music-artist-albumartist-repair.md personal/plans/tagwriter-container.md personal/tech/music-tags-repair-truenas.md --- .../plans/music-artist-albumartist-repair.md | 5 +- personal/plans/tagwriter-container.md | 34 +++-- personal/tech/music-tags-repair-truenas.md | 131 +++++++++++++++++- 3 files changed, 157 insertions(+), 13 deletions(-) diff --git a/personal/plans/music-artist-albumartist-repair.md b/personal/plans/music-artist-albumartist-repair.md index 29fc7951..4cc4c020 100755 --- a/personal/plans/music-artist-albumartist-repair.md +++ b/personal/plans/music-artist-albumartist-repair.md @@ -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='Ленинград'` | diff --git a/personal/plans/tagwriter-container.md b/personal/plans/tagwriter-container.md index 90519704..8cf3630c 100755 --- a/personal/plans/tagwriter-container.md +++ b/personal/plans/tagwriter-container.md @@ -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. Инварианты (падают на старте, до записи) diff --git a/personal/tech/music-tags-repair-truenas.md b/personal/tech/music-tags-repair-truenas.md index 33807a9a..9740a167 100755 --- a/personal/tech/music-tags-repair-truenas.md +++ b/personal/tech/music-tags-repair-truenas.md @@ -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-/` (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-/ бэкап БД +> ``` +> ✅ **Бэкап ФАЙЛОВ музыки отдельно НЕ создавался** — Alex: «Бэкап файлы уже есть». + +> ## 🔴 АКТУАЛЬНОЕ СОСТОЯНИЕ (2026-09-27, сессия 38) — АРХИВ > > 🔴🔴 **`apply` НЕ ЗАПУСКАТЬ БЕЗ ПРЯМОГО ПОДТВЕРЖДЕНИЯ ALEX.** Дословно: «**без подтверждения > никакого apply блядь**». Дифф и dry-run — можно, запись — только «go».