[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:
1 parent
28b6fbd5e2
commit
412fca65ae
4 files changed
+231
-9
No files matched your search
@@ -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 промежуточный единый (до чистки мусора)
|
||||
|
||||
@@ -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 и фикси его чтобы писал все нужные
|
||||
|
||||
Reference in new issue
Block a user