[2026-09-27] eagle: family/plans/music-jellyfin-lidarr-taiga.md 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-27 12:17:46 +06:00
1 parent 28b6fbd5e2
commit 412fca65ae
4 files changed
+231 -9

No files matched your search

+82 -1
View File
@@ -1,4 +1,85 @@
# Music → Jellyfin + Lidarr на Тайге — план (v9, 2026-09-27)
# Music → Jellyfin + Lidarr на Тайге — план (v10, 2026-09-27)
> ## 🆕 v10: СКРИПТ-ПИСАТЕЛЬ ГОТОВ, БЭКАП ПРОВЕРЕН, ЖДЁТ «GO»
>
> ✅ **Вся подготовка закрыта. Файлы музыки НЕ тронуты массово — ждёт «go» от Alex.**
>
> | Этап | Артефакт | Итог |
> |---|---|---|
> | Сбор тегов | `raw_tags_full.tsv` | 7861 строка, md5 `0c3647fadb6556b4100c555ed59bfb44` |
> | Инвентарь | `tag-inventory3.csv` | 7861 строка, 13 колонок (+`albumartist`) |
> | План artist/albumartist/album | `fix-plan-FINAL.csv` | 7861 строка, **0 пустых** |
> | План жанров | `genre-plan-FINAL.csv` | 7861 строка, **65 уникальных, 0 пустых** |
> | Мусор в тегах | — | 10 классов вычищено (спам-URL 51, мойибаке 21, заглушки 325…) |
> | ⭐ **ЕДИНЫЙ ПЛАН** | **`APPLY-PLAN-FINAL.csv`** | **7861 строка, 7628 изменений (97%), все 5 полей заполнены** |
> | **Бэкап** | `Music/backup/` | ✅ **7861 MP3, сверка по относительным путям 1:1** |
> | **Скрипт-писатель** | `apply_tags_v2.py` | ✅ **написан, 4 дефекта v1 закрыты, тест 5 файлов пройден** |
>
> ### ✅ `album` ДОЗАПОЛНЕН ПО ПАПКЕ — 452 → 0 пустых
>
> Alex спросил «Как у тебя спам оказался в albumartist?» → вопрос вскрыл **двух вещей**:
> 1. **Спам был в `cur_albumartist`, НЕ в `new_albumartist`** — правило «сборник → Various Artists»
> перезаписывает target целиком, старый спам уходит сам. Проверка: `новых со спамом: 0`.
> 2. **Реальная недоделка — 452 файла с пустым `album`** (206 АЛЬБОМ, 165 ДЕТСКИЕ-СБОРНИК,
> 74 АЛЬБОМ-мульти, 5 СКАЗКИ). Решение `fill_album.py`: **`album` = имя папки**
> (для `misc`/`disk` — родитель; для буквенных папок детских сборников — буква).
>
> ### ✅ БЭКАП ПРОВЕРЕН ФАКТОМ (Alex: «Бэкап же уже снят?»)
>
> ```
> backup: 7861 mp3 из 8478 файлов 50 ГБ ← 1:1 С ОСНОВНОЙ БИБЛИОТЕКОЙ
> backup_conv40: 40 mp3 251 МБ
> backup_fix: 165 mp3 1.4 ГБ
> backup_raw: 0 mp3 (71 файл wma/ogg) 411 МБ
> Сверка по относительным путям: 0 лишних в обе стороны
> ```
>
> ⚠️ **Питфолл:** `/storage/Music` **не существует на хосте NAS** — только внутри контейнера
> (`/mnt/RED_2TB/storage` → `/storage`, ro). Первая проверка дала ложный вывод «бэкапа нет».
> **Правильно: `M=/mnt/RED_2TB/storage/Music`.**
>
> 🔴 **Вопрос бэкапа ЗАКРЫТ — копировать НЕ нужно.**
>
> ### ✅ `cur_*` НЕ СЛОМАНЫ (Alex: «То есть ты сломал cur поля?»)
>
> ```
> paths не найдено в inventory: 0
> расхождений cur_* vs inventory: 1 ← пробел в genre у одного файла
> спам-значений в cur_*: 40 ← ВСЕ на месте (СОХРАНЁН)
> ```
>
> **Причина подозрения:** в отчёте было «спам вычищен из album», без различения `cur_*`/`new_*`.
> **Правило: в отчётах явно писать, какое поле.**
>
> ### ✅ СКРИПТ `apply_tags_v2.py` — ВСЕ 4 ДЕФЕКТА v1 ЗАКРЫТЫ
>
> Alex: «Бери скрипт которым ты уже прогонял apply и фикси его чтобы писал все нужные поля и
> сносил старые **но не терял данных которые не надо терять**… исправляй проблему пропуска файлов,
> а так же **вывод прогресса и финальную сводку**. **Не апплаить без команды!**»
>
> | # | Дефект v1 | Фикс v2 |
> |---|---|---|
> | D1 | `delall` ВНУТРИ `if value:` → пустое значение не удаляло тег | `delall` **всегда**, `add` — по значению |
> | D2 | `_frames()` без `TPE2`/`TCON` | пишем `TIT2`,`TPE1`,`TPE2`,`TALB`,`TCON`,`TRCK` |
> | D3 | индекс без фильтра `.mp3` → порча не-MP3 | `is_mp3_file()`, пропущено не-mp3 **560** |
> | D4 | нет прогресса/сводки | шаг **250**, итог по полям, `--verify` + `apply-report.csv` |
>
> **«Не терять данные»:** сохраняются `APIC` (обложка), `TDRC` (год), `TLEN`, `TPUB`, `USLT` —
> удаляются только 6 управляемых ID; `save()` — **в тот же путь** (иначе теряется MPEG-поток).
>
> **Тест 5 файлов:** `APPLY ok=5 err=0` · `TPE2 записан 5` · `VERIFY 5/5 MISMATCH=0` ·
> `APIC сохранён` · `первые байты b'ID3'` · `18 718 742 байт`.
>
> **Образ `tagwriter:v2`** собран, CSV на NAS: `/mnt/RED_2TB/docker/tagwriter/data/apply-plan.csv`.
> Полный разбор → [[personal/plans/tagwriter-container]] §v9.
>
> ### ⏭ ОСТАЛОСЬ
>
> 1. 🔴 **ЖДЁТ «go» ОТ ALEX** — массовый `--apply --verify` по 7861
> 2. ⏳ Приёмка: **видно в UI** (Navidrome/Jellyfin), не read-back ассистента
> 3. ⏳ Деплой Navidrome (план — [[family/how-to/navidrome-truenas-android]])
> 4. ⏳ Lidarr (исходная задача, отложена)
> 5. ⏳ После приёмки: удаление бэкапов + 29 битых/DRM
> ## 🆕 v9: ЕДИНЫЙ ПЛАН НА ПРИМЕНЕНИЕ ГОТОВ — `APPLY-PLAN-FINAL.csv` (7861 строка)
>
@@ -4,6 +4,7 @@ created: '2026-09-27'
updated: '2026-09-27'
type: plan
namespace: personal
status: script-reviewed-tested-awaiting-go
tags:
- music
- metadata
@@ -16,7 +17,7 @@ tags:
related:
- '[[family/plans/music-jellyfin-lidarr-taiga]]'
- '[[personal/tech/music-tags-repair-truenas]]'
- '[[personal/tech/jellyfin-tags-cp1251-unreadable]]'
- '[[personal/plans/tagwriter-container]]'
- '[[family/how-to/navidrome-truenas-android]]'
---
@@ -700,11 +701,39 @@ paths не найдено в inventory: 0
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,
9. ✅ **СКРИПТ ПОЛНОСТЬЮ ПЕРЕПРОВЕРЕН** (Alex: «Ещё раз проверь весь текст скрипта на
адекватность») — найдено **6 дефектов, один критический**:
- 🔴 **P1: `TRCK` СТИРАЛСЯ БЕЗВОЗВРАТНО** — `_managed_frame_ids()` содержал `TRCK`
(на удаление), но `_frames()` его не создавал, а **колонки `track`/`new_track` в CSV
НЕТ ВООБЩЕ** → номер трека исчез бы у всех 7861 файла, восстановить нечем.
**Фикс:** `_managed_frame_ids()` = ровно `FIELD_MAP`.
- P2 docstring врал (обещал `TDRC`) · P3 `TDRC` сохранялся случайно · P4 `changed=no`
перезаписывались (233 файла) → `--force` · P5 нет защиты пути → `ValueError` при выходе
за `MUSIC` · P6 `save()` не атомарный (известный риск, бэкап есть).
10. ✅ **ТЕСТ «ДО/ПОСЛЕ» НА ЖИВОМ ФАЙЛЕ** — `snapshot.py` → apply → `snapshot.py` → `cmp_snap.py`
на файле с **9 фреймами** (`Высоцкий/Рецидивист/09 Рецидивист.mp3`, `TRCK='9'`):
**`TRCK` СОХРАНЁН · `TCOM`/`TSSE` сохранены · потеряно 0 фреймов · размер 4148743 → 4148743**
11. 🔴 **ЖДЁТ «go» ОТ ALEX** — массовый прогон `--apply --verify` по `APPLY-PLAN-FINAL.csv` (7861).
⚠️ **Перед боевым прогоном вернуть 8 тестовых файлов из бэкапа** (`!Классика` ×5,
`Высоцкий` ×2, `Камбурова` ×1 — они уже с новыми тегами после тестов).
12. ⏳ Приёмка Alex: **видно в UI** (Navidrome/Jellyfin), не «read back» ассистента
13. ⏳ После приёмки: откат 40 из `backup_conv40/` (если нужен), удаление 29 битых/DRM,
удаление бэкапов `backup/` + `backup_raw/` + `backup_fix/` + `backup_conv40/`
12. ⏳ Lidarr + Navidrome (первоначальная задача Alex, ещё не начата)
14. ⏳ Lidarr + Navidrome (первоначальная задача Alex, ещё не начата)
## 🔴 ПРАВИЛО (открыто в этой сессии): «управляемый» набор фреймов = набор записываемых
`TRCK` стоял в списке **на удаление**, но отсутствовал в списке **на запись** и в CSV.
**Фрейм, которым скрипт «управляет», но значение которого нигде не задано, — это потеря данных
по определению.** Валидировать инвариант ДО прогона:
```python
assert sorted(_managed_frame_ids()) == sorted(fid for _, fid, _ in FIELD_MAP)
```
Тот же класс уже стоил библиотеке 40 MP3 (`fix40.py`) и 16 не-MP3 (`apply_tags.py` без `.mp3`-фильтра) —
**ловушка в списках «что трогаем» находится только построчным перечитыванием, а не тестами на живых файлах**
(тест прошёл бы успешно: `delall(TRCK)` + отсутствие `add` не вызывают ошибки, просто тихо стирают тег).
### ✅ Бэкап: вопрос ЗАКРЫТ (Alex: «Бэкап же уже снят?»)
@@ -758,7 +787,8 @@ APIC сохранён · первые байты b'ID3' · 18 718 742 байт
```
/Users/admin/milestones/music-csv-2026-09-26/
├── APPLY-PLAN-FINAL.csv ⭐⭐ 7861 стр — ЕДИНЫЙ ПЛАН НА ПРИМЕНЕНИЕ (14 колонок)
├── APPLY-PLAN-FINAL.csv ⭐⭐⭐ 7861 стр — ЕДИНЫЙ ПЛАН НА ПРИМЕНЕНИЕ (14 колонок)
│ все 5 полей заполнены, валидация по нулям
├── fix-plan-FINAL.csv ⭐ 7861 стр, план artist/albumartist/album
├── genre-plan-FINAL.csv ⭐ 7861 стр, план жанров (65 уник., 0 пусто)
├── APPLY-PLAN.csv промежуточный единый (до чистки мусора)
+95 -1
View File
@@ -17,9 +17,103 @@ updated: '2026-09-26'
---
# tagwriter — контейнер применения тегов MP3 (TrueNAS)
_Создан: 2026-09-26. **Статус (обновлён 2026-09-27): ✅ `apply_tags_v2.py` НАПИСАН, ВСЕ 4 ДЕФЕКТА v1 ЗАКРЫТЫ (D1 `delall` вне `if` · D2 +`TPE2`/`TCON` · D3 фильтр `.mp3` · D4 прогресс+сводка+`--verify`), ОБРАЗ `tagwriter:v2` СОБРАН, ТЕСТ НА 5 ФАЙЛАХ ПРОЙДЕН (`ok=5 err=0`, `VERIFY совпало=5 MISMATCH=0`, APIC сохранён, MPEG-поток цел). 🔴 **ФАЙЛЫ МУЗЫКИ НЕ ТРОНУТЫ МАССОВО — ЖДЁТ «go» ОТ ALEX.** ⏳ НЕ выполнено: массовый apply по `APPLY-PLAN-FINAL.csv` (7861), откат 40 из `backup_conv40/`, удаление 29 битых/DRM, приёмка Jellyfin. Бэкапы готовы и проверены по путям: `backup/` 7861 MP3 (1:1 с основной библиотекой), `backup_conv40/` 40, `backup_fix/` 165, `backup_raw/` 71. Решение Alex по TRCK = **B** (номера не удалять).**_
_Создан: 2026-09-26. **Статус (обновлён 2026-09-27): ✅ `apply_tags_v2.py` НАПИСАН + ПОЛНОСТЬЮ ПЕРЕПРОВЕРЕН (v10). ЗАКРЫТЫ 4 ДЕФЕКТА v1 (D1 `delall` вне `if` · D2 +`TPE2`/`TCON` · D3 фильтр `.mp3` · D4 прогресс+сводка+`--verify`) + 6 ДЕФЕКТОВ, найденных при перечитывании (P1 🔴 `TRCK` стирался безвозвратно · P2 docstring врал · P3 `TDRC` сохранялся случайно · P4 `changed=no` перезаписывались · P5 нет защиты пути · P6 `save()` не атомарный). ОБРАЗ `tagwriter:v2` СОБРАН. ТЕСТЫ: 5 файлов (`ok=5 err=0`, `MISMATCH=0`) + 3 файла (`ok=3 err=0`, `MISMATCH=0`) + ТЕСТ «ДО/ПОСЛЕ» на файле с 9 фреймами (`TRCK` СОХРАНЁН, `TCOM`/`TSSE` сохранены, размер 4148743 → 4148743 байт, потеряно 0 фреймов). 🔴 **ФАЙЛЫ МУЗЫКИ НЕ ТРОНУТЫ МАССОВО — ЖДЁТ «go» ОТ ALEX.** ⏳ НЕ выполнено: массовый apply по `APPLY-PLAN-FINAL.csv` (7861), возврат 8 тестовых файлов из бэкапа, откат 40 из `backup_conv40/`, удаление 29 битых/DRM, приёмка Jellyfin. Бэкапы готовы и проверены по путям: `backup/` 7861 MP3 (1:1 с основной библиотекой), `backup_conv40/` 40, `backup_fix/` 165, `backup_raw/` 71. Решение Alex по TRCK = **B** (номера не удалять).**_
_Основание: [[personal/plans/music-tags-wave38-fixes]]._
## ✅ v10 (2026-09-27): ПОЛНОЕ ПЕРЕЧИТЫВАНИЕ СКРИПТА — 🔴 НАЙДЕН ДЕФЕКТ УНИЧТОЖЕНИЯ `TRCK`
**Задача Alex:** «**Ещё раз проверь весь текст скрипта на адекватность.**»
Перечитан весь `apply_tags_v2.py` (333 строки) построчно → найдено **6 проблем, одна критическая**.
Все исправлены **до** боевого прогона.
### 🔴🔴 P1 (КРИТИЧНО): `TRCK` СТИРАЛСЯ БЕЗВОЗВРАТНО
**Механика ловушки:**
```python
_managed_frame_ids() → ["TIT2","TPE1","TPE2","TALB","TCON","TRCK"] # TRCK стоит на удаление
_frames(r) → TIT2, TPE1, TPE2, TALB, TCON — TRCK НЕ создаётся
CSV APPLY-PLAN-FINAL → колонки track / new_track ОТСУТСТВУЮТ ВООБЩЕ
```
**`delall("TRCK")` выполнялся бы, `add(TRCK)` — никогда.** Номер трека исчез бы у **всех 7861 файла**,
и восстановить его было бы **нечем** — данных о треке нет ни в CSV, ни где-либо ещё.
**Фикс:**
```python
def _managed_frame_ids():
"""🔴 НЕ добавлять сюда фрейм, которого нет в _frames()/CSV —
он будет стёрт без возможности восстановления (так было с TRCK)."""
return [fid for _, fid, _ in FIELD_MAP] # TIT2 TPE1 TPE2 TALB TCON
```
**🔴 ПРАВИЛО: набор «управляемых» фреймов = РОВНО набор записываемых. Управляемый, но не
записываемый фрейм = гарантированная потеря данных.** Проверка инварианта:
`sorted(_managed_frame_ids()) == sorted(fid for _,fid,_ in FIELD_MAP)` → `True`.
### Остальные 5 исправлений
| # | Проблема | Было | Стало |
|---|---|---|---|
| **P2** | docstring врал | «пишем TDRC», реально TDRC не писался | честный список: `ЧТО ПИШЕТ` / `ЧТО НЕ ТРОГАЕТ` |
| **P3** | `TDRC` (год) сохранялся **случайно** | не был в списке удаления — везение, не замысел | задокументировано как «не трогаем» + в `ЧТО НЕ ТРОГАЕТ` |
| **P4** | `changed=no` обрабатывались | 233 файла зря перезаписывались (`delall`+`add` на неизменном) | `build_jobs(force=...)` пропускает их, флаг `--force` для перезаписи |
| **P5** | нет защиты пути | `/storage/Music/../../etc` прошёл бы | `map_path()` → `os.path.normpath` + `ValueError` при выходе за `MUSIC` |
| **P6** | `save()` не атомарный | обрыв (kill/диск полный) → битый файл | оставлено (бэкап есть), но зафиксировано как известный риск |
### ✅ ТЕСТ «ДО/ПОСЛЕ» НА ЖИВОМ ФАЙЛЕ — `TRCK` ВЫЖИЛ
**Метод:** `snapshot.py` (снимок фреймов + значений в JSON) → `--apply` → `snapshot.py` снова →
`cmp_snap.py` (побайтовое сравнение списка фреймов и значений).
**Тестовый файл с 9 фреймами** (выбран `find_trck.py` — искал `.mp3` с непустым `TRCK`):
```
!Барды/Владимир Высоцкий/Рецидивист/09 Рецидивист.mp3
фреймы: TALB, TCOM, TCON, TIT2, TPE1, TPE2, TRCK, TSSE, TXXX ← 9 штук
TRCK = '9'
```
**Результат сравнения:**
```
поле ДО ПОСЛЕ ВЕРДИКТ
TCON Поп Pop ИЗМЕНЕН (план) ✓
TRCK 9 9 СОХРАНЁН ✓
TCOM Владимир Высоцкий Владимир Высоцкий СОХРАНЁН ✓
TSSE Lavf60.16.100 Lavf60.16.100 СОХРАНЁН ✓
фреймы: потеряно ничего, появилось ничего
размер: 4148743 → 4148743 (разница 0)
ИТОГ: ✓ ВСЁ СОХРАНЕНО
```
### Тест из 3 файлов (разные случаи)
```
APPLY: 3 файла — ok=3 err=0
TPE2 (albumartist): 1 · TALB (album): 1 · TCON (genre): 3
VERIFY: совпало 3/3, MISMATCH 0
```
### 📄 Инструменты верификации (в `/mnt/RED_2TB/docker/tagwriter/data/`)
| Скрипт | Назначение |
|---|---|
| `snapshot.py` | снимок фреймов файла в JSON (список ID + значения 9 ключевых) |
| `cmp_snap.py` | сравнение двух снимков: что сохранено, что изменено, что потеряно |
| `find_trck.py` | поиск mp3 с непустым `TRCK` (для выбора тестового файла) |
| `make_test_csv.py` | мини-CSV из указанных путей — для точечного теста |
| `check_one.py` | быстрый отчёт по одному файлу (фреймы, значения, байты) |
### 🔴 ПИТФОЛЛ: `--entrypoint python3` требует, чтобы скрипт БЫЛ в смонтированном каталоге
Первый запуск `snapshot.py` → `can't open file '/app/data/snapshot.py': No such file or directory`,
хотя `scp` вроде прошёл. **Проверять `ls -la` монтируемого файла ПОСЛЕ `scp`, до `docker run`.**
`/app` — образ, `/app/data` — монтирование; `scp` в `data/` нужен **и** `-v .../data:/app/data`.
## ✅ v9 (2026-09-27): `apply_tags_v2.py` — НОВЫЙ СКРИПТ-ПИСАТЕЛЬ, ВСЕ 4 ДЕФЕКТА ЗАКРЫТЫ
**Задача Alex:** «Бери скрипт которым ты уже прогонял apply и фикси его чтобы писал все нужные
+18 -1
View File
@@ -39,8 +39,25 @@ related:
> - **сборник ≠ «>1 артиста»** — это ловит альбомы с фитами; Alex дал прямые примеры
> - процессный запрет Alex: **сначала ручные списки, скриптом — только обновление CSV**
> - ✅ `fix-plan-FINAL.csv` — 7861 стр, **0 пустых `artist`**, 7233 изменения
> - ✅ `genre-plan-FINAL.csv` — 7861 стр, **82 уникальных жанра** (было 154), 51 пустой,
> - ✅ `genre-plan-FINAL.csv` — 7861 стр, **65 уникальных жанра** (было 154), **0 пустых**,
> мойибаке `Psy Årance` → `Psy-Trance` (29 файлов, `Å`/`Е` — латиница vs кириллица)
>
> ### ✅ ОБНОВЛЕНИЕ 2026-09-27 (вечер) — ПЛАН ЗАКРЫТ, СКРИПТ ГОТОВ, ЖДЁТ «GO»
>
> | Шаг | Факт |
> |---|---|
> | `APPLY-PLAN-FINAL.csv` | ✅ 7861 стр, 7628 изменений, **все 5 полей заполнены** (title/artist/albumartist/album/genre) |
> | `album` дозаполнен по папке | ✅ 452 → **0 пустых** |
> | Валидация | ✅ дубли 0 · спам 0 · мойибаке 0 · пустых 0 · class↔albumartist 0 |
> | Бэкап | ✅ `backup/` **7861 MP3**, сверка по относительным путям **1:1** (0 расхождений) |
> | `cur_*` | ✅ **не сломаны** — 1 расхождение = пробел в `genre` |
> | Скрипт `apply_tags_v2.py` | ✅ написан, 4 дефекта v1 закрыты; **⚠️ ПОСЛЕ ПОЛНОЙ ПЕРЕПРОВЕРКИ исправлено ещё 6 дефектов** (🔴 `TRCK` стирался безвозвратно · `changed=no` перезаписывались · нет защиты пути · docstring врал); тесты 5+3 файла `MISMATCH=0` + «ДО/ПОСЛЕ» на 9-фреймовом файле (`TRCK` сохранён, потеряно 0 фреймов) |
> | 🔴 Дальше | **ЖДЁТ «go» ОТ ALEX** — массовый `--apply --verify` по 7861. ⚠️ Перед прогоном вернуть 8 тестовых файлов из бэкапа |
>
> Питфоллы сессии: `cur_*` ≠ `new_*` в отчётах · `/storage/Music` не существует на хосте (только
> `/mnt/RED_2TB/storage/Music`, в контейнере `/storage` ro) · инлайн-python через ssh ломается на
> кавычках · tirith блокирует кириллицу вплотную к ASCII.
> Детали → [[personal/plans/music-artist-albumartist-repair]] и [[personal/plans/tagwriter-container]].
> ## ✅ ТРИДЦАТЬ ВОСЬМАЯ ВОЛНА (2026-09-26) — ФИНАЛЬНЫЙ АУДИТ + 4 ПРАВКИ + ПРИМЕНЕНИЕ НА NAS
>