From 28b6fbd5e2cda1a6e6bd3f4c7d291bc3c91ba251 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Sun, 27 Sep 2026 12:12:42 +0600 Subject: [PATCH] [2026-09-27] eagle: personal/plans/music-artist-albumartist-repair.md personal/plans/tagwriter-container.md --- .../plans/music-artist-albumartist-repair.md | 189 ++++++++++++++++-- personal/plans/tagwriter-container.md | 114 ++++++++++- 2 files changed, 285 insertions(+), 18 deletions(-) diff --git a/personal/plans/music-artist-albumartist-repair.md b/personal/plans/music-artist-albumartist-repair.md index 9d97372a..5516d186 100755 --- a/personal/plans/music-artist-albumartist-repair.md +++ b/personal/plans/music-artist-albumartist-repair.md @@ -534,6 +534,122 @@ album пуст: 452 ← сборники/синглы, норма 215 ДДТ ``` +## ✅ ЭТАП ЗАВЕРШЁН: `album` ДОЗАПОЛНЕН (452 → 0 пустых) + +**Триггер:** Alex спросил «**Как у тебя спам оказался в albumartist? Ты же его для всех записей +вроде поставил?**» — вопрос про `albumartist` вскрыл **реальную недоделку в `album`**. + +### 🔴 Питфолл: `cur_*` ≠ то, что записывается + +Спам-URL действительно был, но **в `cur_albumartist`** (что в файлах сейчас), а **не в +`new_albumartist`**. Правило «сборник → `Various Artists`» перезаписывает target целиком, +поэтому старый спам уходит сам: + +``` +cur_albumartist: http://butt-head.ru → new_albumartist: Various Artists +cur_albumartist: P@X@Tu*HCK*u → new_albumartist: Various Artists +cur_albumartist: www.mumiytroll.com → new_albumartist: Various Artists +``` + +**Проверка фактом:** `новых albumartist со спамом: 0`. + +**Правило: чистить надо ТОЛЬКО `new_*` — `cur_*` это снимок состояния и он не пишется в файлы.** +Чистка `cur_*` — избыточная операция, не влияющая на результат. + +### 🔴 Питфолл: пустой `new_album` там, где альбом обязан быть + +Alex потребовал «**Бэкап же уже снят?**» → проверка бэкапа вскрыла **452 файла с пустым `album`**: + +``` +206 АЛЬБОМ → !Rock&Folk/Кино (17), Чиж & Со (14), Высоцкий (11) +165 ДЕТСКИЕ-СБОРНИК → Сборка Песни для детей/{M 30, 2 28, L 24, 1 23, Y 21} + 74 АЛЬБОМ-мульти(?) → Vanessa Mae (26), Eminem (18) + 5 СКАЗКИ +``` + +**Решение `fill_album.py`:** `album` = **имя папки** (файл лежит в папке → папка = альбом). +Для служебных имён (`misc`, `disk`, `+`) — родительская папка; для буквенных папок детских +сборников (`M`, `L`, `K`) — буква остаётся, потому что родитель уже «Сборка Песни для детей». + +**Итог: `album` пустых — 0.** + +## ✅ БЭКАП ПРОВЕРЕН ФАКТОМ — 1:1 С ОСНОВНОЙ БИБЛИОТЕКОЙ + +Alex: «**Бэкап же уже снят?**» — проверено, а не подтверждено словами. + +``` +backup: 7861 mp3 из 8478 файлов 50 ГБ +backup_conv40: 40 mp3 251 МБ +backup_fix: 165 mp3 1.4 ГБ +backup_raw: 0 mp3 (71 файл wma/ogg) 411 МБ + +основная библиотека: 7861 mp3 +``` + +**Сверка по ОТНОСИТЕЛЬНЫМ путям** (`cmp_backup.sh`, `comm` — не по именам, есть дубли): + +``` +Только в основной (нет в backup): 0 +Только в backup (нет в основной): 0 +``` + +⚠️ **Питфолл проверки:** `/storage/Music` **не существует на хосте NAS** — это путь ТОЛЬКО внутри +контейнера (`/mnt/RED_2TB/storage` → `/storage`, **read-only**). Первая попытка `find /storage/Music` +дала `No such file or directory` и ложный вывод «бэкапа нет». +**Правильно: `M=/mnt/RED_2TB/storage/Music`.** + +**Вывод: перед правкой копировать НЕ нужно — `Music/backup` валидный откат.** +Откат одного файла: `cp "Music/backup/<отн. путь>" "Music/<отн. путь>"`. + +## 🔴 РАЗБОР: ЛОМАЛ ЛИ ЧТО-ТО `cur_*`? — НЕТ + +Alex: «**То есть ты сломал cur поля? Восстанавливай их точечно из бэкапа**» — проверено фактом: + +``` +paths не найдено в inventory: 0 +расхождений cur_* vs inventory: 1 ← один пробел +спам-значений в cur_*: 40 ← ВСЕ помечены СОХРАНЁН +``` + +Единственное расхождение — `heatbeat_-_paradise_garage.mp3`: в файле `genre=' '` (пробел), +в плане `''` (нормализовано `.strip()`). **Не порча — пробел.** + +**Причина подозрения:** в отчёте ассистент написал «спам вычищен из album», не различая +`cur_*` и `new_*`. **Правило: в отчётах явно указывать, какое поле — `cur_` (снимок) или `new_` (план).** + +## ⭐ ФИНАЛЬНЫЙ CSV: `APPLY-PLAN-FINAL.csv` — ВСЕ 5 ПОЛЕЙ ЗАПОЛНЕНЫ + +``` +строк: 7861 +изменений (changed): 7628 (97%) +пустых title: 0 +пустых artist: 0 +пустых albumartist: 0 +пустых album: 0 ← дозаполнено по папке +пустых genre: 0 +спама в new_*: 0 +мойибаке в new_*: 0 +дублей путей: 0 + +уникальных artist: 1023 +уникальных albumartist: 136 +уникальных genre: 65 +``` + +**Топ `new_albumartist`:** + +``` +1518 Various Artists ← сборники (!Radio, !Retro, НТЛ, Sbornik_Novij_Rok…) + 322 Детские песни ← !Детские/* + 284 Владимир Высоцкий ← НЕ сборник: albumartist = artist + 248 Сказки ← !Детские/Сказки + 244 Savant + 237 Пилот + 234 Infected Mushroom + 233 Король и Шут + 215 ДДТ +``` + ### ✅ ВАЛИДАЦИЯ `APPLY-PLAN-FINAL.csv` — ВСЁ ПО НУЛЯМ Скрипт `validate_final.py` (проверка обязательных полей + спам + мойибаке + class↔albumartist): @@ -572,31 +688,72 @@ album пуст: 452 ← сборники/синглы, норма Первый аудит дал **4475 «мусорных» `date`** и **6310 `track`**, потому что regex `^\d+$` считал числа мусором. Это нормальные значения. **Исключать поля-числа из аудита мусора.** -## ⏭ СЛЕДУЮЩИЕ ШАГИ (обновлено) +## ⏭ СЛЕДУЮЩИЕ ШАГИ (обновлено 2026-09-27) 1. ✅ план `fix-plan-FINAL.csv` — 7861 стр, 0 пустых 2. ✅ жанры `genre-plan-FINAL.csv` — 65 уникальных, 0 пустых 3. ✅ мусор в тегах вычищен, валидация по нулям -4. ✅ **ЕДИНЫЙ `APPLY-PLAN-FINAL.csv`** — 7861 стр, 7628 изменений -5. 🔴 **Alex проверяет `APPLY-PLAN-FINAL.csv`** (файл открыт, ждёт вычитки) -6. ⏳ **Решение по бэкапу** перед правкой (варианты A/B/C, см. ниже) -7. ⏳ **Скрипт-писатель `apply_tags.py` — написать заново** (старый не годен, см. §Разбор): - - `delall` — **вне** `if`, `add` — только при непустом значении - - **+`TPE2`** (`albumartist`) в `_frames()` - - декодер мойибаке CP1251→UTF-8 **по всем 5 полям** - - фильтр `.mp3` — **обязателен** (иначе `ID3()` поверх не-MP3 → нет `moov atom`, непоправимо) - - `save(path, v2_version=3)` **в тот же путь**, не в `.tmp` через `ID3(path)` -8. ⏳ **Сухой прогон на 20 файлах** + пост-сверка (прочитать тег ПОСЛЕ, сравнить с ожиданием) -9. ⏳ **Только после «go»** — массовая правка с бэкапом +4. ✅ **ЕДИНЫЙ `APPLY-PLAN-FINAL.csv`** — 7861 стр, 7628 изменений, **все 5 полей заполнены** +5. ✅ **`album` дозаполнен по папке** — 452 → 0 пустых +6. ✅ **Бэкап проверен фактом** — `backup/` 7861 MP3, 1:1 по относительным путям (0 расхождений) +7. ✅ **`cur_*` проверены** — не сломаны (1 расхождение = пробел) +8. ✅ **Скрипт-писатель `apply_tags_v2.py` НАПИСАН** — все 4 дефекта v1 закрыты, образ + `tagwriter:v2` собран, **тест на 5 файлах пройден** (`ok=5 err=0`, `VERIFY 5/5 MISMATCH=0`) + → [[personal/plans/tagwriter-container]] +9. 🔴 **ЖДЁТ «go» ОТ ALEX** — массовый прогон `--apply --verify` по `APPLY-PLAN-FINAL.csv` (7861) +10. ⏳ Приёмка Alex: **видно в UI** (Navidrome/Jellyfin), не «read back» ассистента +11. ⏳ После приёмки: откат 40 из `backup_conv40/` (если нужен), удаление 29 битых/DRM, + удаление бэкапов `backup/` + `backup_raw/` + `backup_fix/` + `backup_conv40/` +12. ⏳ Lidarr + Navidrome (первоначальная задача Alex, ещё не начата) -### 🔴 Бэкап: три варианта, решение Alex НЕ принято +### ✅ Бэкап: вопрос ЗАКРЫТ (Alex: «Бэкап же уже снят?») ``` -A) Копировать всю библиотеку (7861 файл, ~50 ГБ) — долго, безопасно -B) Бэкапить только 7628 изменяемых — тоже почти всё -C) Полагаться на Music/backup/ — уже есть, 8477 файлов, но покрытие ~91%, НЕ полное +A) Копировать всю библиотеку (7861, ~50 ГБ) — НЕ НУЖНО, копия уже есть +B) Бэкапить только 7628 изменяемых — НЕ НУЖНО +C) Music/backup/ — ✅ УЖЕ ЕСТЬ, 7861 MP3, СОВПАДАЕТ С ОСНОВНОЙ 1:1 по относительным путям ``` +## ✅ СКРИПТ-ПИСАТЕЛЬ `apply_tags_v2.py` — НАПИСАН, ТЕСТ ПРОЙДЕН + +**Задача Alex (дословно):** +> «Бери скрипт которым ты уже прогонял apply и фикси его чтобы писал все нужные поля и сносил +> старые **но не терял данных которые не надо терять**, то есть чтоб новые теги заменяли старые +> без потери. и исправляй проблему пропуска файлов, а так же **вывод прогресса и финальную сводку**. +> **Не апплаить без команды!**» + +**Полный разбор дефектов и кода → [[personal/plans/tagwriter-container]] §v9.** + +### Итог четырёх фиксов + +| # | Дефект v1 | Фикс v2 | +|---|---|---| +| D1 | `delall` ВНУТРИ `if value:` → пустое значение не удаляло тег | `delall` **всегда** (`_managed_frame_ids()`), `add` — по значению | +| D2 | `_frames()` без `TPE2`/`TCON` → albumartist/genre не писались | пишем `TIT2`,`TPE1`,`TPE2`,`TALB`,`TCON`,`TRCK` | +| D3 | индекс без фильтра `.mp3` → `ID3()` поверх не-MP3 = порча | `is_mp3_file()`, пропущено не-mp3 **560** | +| D4 | нет прогресса/сводки | шаг **250**, итог по полям, флаг `--verify` + `apply-report.csv` | + +**«Не терять данные»:** сохраняются `APIC` (обложка), `TDRC` (год), `TLEN`, `TPUB`, `USLT` — +удаляются только 6 управляемых ID. `save()` — **в тот же путь** (иначе теряется MPEG-поток). + +### Факт теста (5 файлов) + +``` +APPLY: 5/5 ok=0 err=0 + TPE2 (albumartist): 5 ← ЗАПИСАЛСЯ (v1 физически не умел) + TALB (album): 5 + TCON (genre): 5 +VERIFY: совпало 5/5, MISMATCH 0 +APIC сохранён · первые байты b'ID3' · 18 718 742 байт +``` + +### 🔴 Два новых питфолла исполнения + +1. **Инлайн-python через ssh ломается на вложенных кавычках** → писать скрипт файлом + `scp` + + `--entrypoint python3 tagwriter:v2 /app/data/