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

This commit is contained in:
Alexey Martemyanov committed 2026-09-27 00:19:39 +06:00
1 parent 84fee3eea2
commit ae931acd15
2 files changed
+103 -6

No files matched your search

+96 -6
View File
@@ -17,7 +17,7 @@ updated: '2026-09-26'
---
# tagwriter — контейнер применения тегов MP3 (TrueNAS)
_Создан: 2026-09-26. **Статус: ✅ APPLY ВЫПОЛНЕН — 7041 файл обновлён, 0 ошибок. Сверка: 7610/7905 совпало (96.3%), 83 MISSING (9 WMA + 1 OGG + 73 не разобрано), 212 MISMATCH (разбор неполный — лог обрезан).**_
_Создан: 2026-09-26. **Статус: ✅ APPLY ВЫПОЛНЕН — 7041 файл обновлён, 0 ошибок. Сверка: 7610/7905 совпало (96.3%), 83 MISSING (9 WMA + 1 OGG + 73 не разобрано), 212 MISMATCH (разбор неполный — лог обрезан). Бэкап не-MP3 (70 файлов) сделан. Далее — конвертация WMA/OGG/FLAC/M4A → MP3.**_
_Основание: [[personal/plans/music-tags-wave38-fixes]]._
## Задача
@@ -93,9 +93,11 @@ docker run --rm -u 0 -v /mnt/RED_2TB/docker:/docker alpine:latest sh -c \
3. ✅ **Dry-run** — `--dry-run`, 0 записей, **MISSING 0** (выполнено).
4. ✅ **Apply** — `--apply`: **обновлено 7041, ошибок 0** (~17 мин, запуск в фоне).
5. ✅ **Read-back** — `--readback`: не найдено 0.
6. ⏳ **Независимая сверка** — `verify_tags.py`: 7610/7905 (96.3%); полный разбор 83 MISSING + 212 MISMATCH **не получен** (лог обрезан).
7. ⏳ **Приёмка в Jellyfin** (глазами Alex).
8. ⏳ **Обновить техдок** `personal/tech/music-tags-repair-truenas.md` после сверки.
6. ✅ **Бэкап не-MP3** — `backup_nonmp3.py`: **70 файлов, 386 МБ → `/music/backup_raw/`** (выполнено).
7. ⏳ **Независимая сверка (полный список)** — `verify_tags.py` перезапущен без обрезки лога (ждёт результата).
8. ⏳ **Конвертация не-MP3 → MP3** — план готов (ffmpeg в образ, VBR V0, удаление оригиналов после проверки бэкапа). **Ждёт `go`.**
9. ⏳ **Приёмка в Jellyfin** (глазами Alex).
10. ⏳ **Обновить техдок** `personal/tech/music-tags-repair-truenas.md` после сверки.
## ✅ СОСТОЯНИЕ НА 2026-09-26 (факты)
@@ -108,7 +110,10 @@ docker run --rm -u 0 -v /mnt/RED_2TB/docker:/docker alpine:latest sh -c \
| **БЭКАП** | ✅ **7041 файл, 42 ГБ → `/mnt/RED_2TB/storage/Music/backup/`** (файлов в backup: 7041, MP3: 6823) |
| **APPLY** | ✅ **обновлено 7041, ошибок 0** (`APPLY: обновлено 7041, ошибок 0`, ~17 мин) |
| **READ-BACK** | ✅ не найдено на диске 0 |
| **СВЕРКА С ПЛАНОМ** (`verify_tags.py`) | ✅ **7610/7905 = 96.3%**, MISSING 83, MISMATCH 212 — полный разбор **не получен**, см. ниже |
| **СВЕРКА С ПЛАНОМ** (`verify_tags.py`) | ✅ **7610/7905 = 96.3%**, MISSING 83, MISMATCH 212 — полный разбор **перезапущен** (лог без обрезки) |
| **БЭКАП НЕ-MP3** | ✅ **70 файлов, 386.1 МБ → `/mnt/RED_2TB/storage/Music/backup_raw/`** (40 ogg · 16 wma · 13 m4a · 1 flac) |
| **`/music/backup/` не тронут** | ✅ 42 ГБ, 7041 файл — проверено после `backup_raw` |
| **КОНВЕРТАЦИЯ не-MP3 → MP3** | ⏳ план готов, **ждёт `go`** (см. § ПЛАН конвертации) |
### Результат независимой сверки `verify_tags.py` (2026-09-26)
@@ -137,6 +142,85 @@ docker run --rm -u 0 -v /mnt/RED_2TB/docker:/docker alpine:latest sh -c \
нужен полный список (лог обрезан `первые 30`). Возможные источники: правки после apply, файлы
с дублями имён, битые исходные ID3 (mojibake не перезаписан).
### ✅ БЭКАП НЕ-MP3 (`backup_raw/`, 2026-09-26) — ВЫПОЛНЕНО
Alex: «Следом конвертируй в макс доступном качестве все wma/ogg (вообще все в music) в mp3,
оригиналы удаляй, убедившись что они остались в backup. Смотри чтобы сама папку backup
конвертация не затронула.» → уточнение: «Бэкап естественно сделать не-mp3!»
**Ключевой факт:** бэкап `backup/` собран **только по MP3 из плана** (7041) — не-MP3
файлы в него **не входили**. Правило «убедись, что остались в backup» для них было
невыполнимо без отдельного шага → сначала `backup_raw/`.
| | |
|---|---|
| Скрипт | `backup_nonmp3.py` (на NAS: `/mnt/RED_2TB/docker/tagwriter/`) |
| Найдено | **70** |
| Скопировано | **70** ✅ |
| Объём | **386.1 МБ** |
| Место | `/mnt/RED_2TB/storage/Music/backup_raw/` |
| Состав | 40 `.ogg` · 16 `.wma` · 13 `.m4a` · 1 `.flac` |
Структура путей сохранена относительно Music (`backup_raw/!Рэп/НТЛ/wma/Track 12.wma`).
**Оригиналы не удалялись** — скрипт только копирует. Запуск:
```bash
cd /mnt/RED_2TB/docker/tagwriter
docker compose --profile tools run --rm \
-v /mnt/RED_2TB/docker/tagwriter/backup_nonmp3.py:/app/backup_nonmp3.py:ro \
--entrypoint python tagwriter /app/backup_nonmp3.py
```
**Защита от захвата `backup/` (обязательна в любом скрипте по Music):**
1. `os.walk` пропускает папки `backup` и `backup_raw` (`dirs[:] = [d for d in dirs if not skip_dir(d)]`);
2. `assert "/backup/" not in nfc(src)` перед каждым копированием;
3. идемпотентность: файл с тем же размером пропускается;
4. проверка размера после копирования (`getsize(dst) != src_size` → exit 1).
✅ Проверено: `/music/backup/` после прогона — **не тронут** (42 ГБ, 7041 файл).
### 📋 ИНВЕНТАРЬ НЕ-MP3 НА ДИСКЕ (2026-09-26, без `backup/`)
| Формат | Кол-во | Где |
|---|---:|---|
| `.ogg` | **40** | Muse — *Absolution* (14), Бутусов — *Имя рек* (8), Кирпичи — *let's rock!* (13), синглы (Жуки, Мумий Тролль, Би-2, Дискотека Авария) |
| `.m4a` | **26** (13 + 13 `._*`) | Infected Mushroom — *Army of Mushrooms*; `._*` = AppleDouble-мусор macOS, **исключать** |
| `.wma` | **16** | НТЛ — `wma/Track 1–13` + `ВЕРТИТЕ ЗАДОМ_old`; Агата Кристи — *Ближе*; Ундервуд — *02 Гагарин* |
| `.flac` | **1** | Infected Mushroom — *Bust A Move (Infected Remix)* |
| **Итого к конвертации** | **70** (после выкидывания 13 `._*` → **57**) | |
**Всего файлов в Music без backup:** 7822 (индекс) — MP3 подавляющее большинство.
### 🚧 ПЛАН: конвертация не-MP3 → MP3 (ЖДЁТ ЗАПУСКА)
**Требование Alex:** максимально доступное качество, оригиналы удалить **после** подтверждения
бэкапа, папку `backup/` не затрагивать.
**Блокер инструмента:** `ffmpeg` на хосте NAS **нет** (`which ffmpeg` → not found).
`immich-server` имеет ffmpeg, но **музыку не видит** — не годится.
**Решение:** добавить `ffmpeg` в образ `tagwriter` (`apk add --no-cache ffmpeg`, ~+80 МБ) —
изоляция сохраняется, монтирование Music уже есть.
**Алгоритм (по одному файлу, атомарно):**
```bash
ffmpeg -i src.wma -vn -acodec libmp3lame -q:a 0 -map_metadata 0 -id3v2_version 3 out.mp3
```
- `-q:a 0` = **VBR V0, максимальное качество** (~245 kbps) — Alex требует «макс доступное»
- `-map_metadata 0` — перенос тегов из оригинала
- `-id3v2_version 3` — совместимо с остальной библиотекой (ID3v2.3)
- `-vn` — без видео-потока (защита от M4A/видео-контейнеров)
**Порядок на каждый файл:**
1. найти файл в `backup_raw/`, сверить размер;
2. `ffmpeg` → `out.mp3` во временный путь;
3. `ffprobe out.mp3` — аудио-поток есть, длительность ±1 сек от оригинала;
4. **только при успехе** — `os.replace(tmp, final)`, затем удалить оригинал;
5. `assert "backup" not in path` на каждом шаге.
**Статус:** план составлен, **не запущен** (ждёт `go` от Alex).
### 🔧 `verify_tags.py` — скрипт сверки (создан 2026-09-26)
Локально: `/tmp/verify_tags.py`, на NAS: `/mnt/RED_2TB/docker/tagwriter/verify_tags.py` (644).
@@ -213,7 +297,13 @@ docker compose --profile tools run --rm \
- ⚠️ `ffmpeg` для записи тегов **не нужен** — mutagen работает с тег-блоком напрямую.
- ⚠️ uid контейнера обязан быть `950`, иначе запись в Music упадёт.
- ⚠️ Команды на NAS требуют апрува — таймаут апрува = команда не выполнена (не повторять вслепую, **сообщить пользователю**).
- ⚠️ mutagen пишет **только ID3 (MP3)**; `.wma`/`.ogg` в плане останутся нетронутыми (9+1 файл).
- ⚠️ mutagen пишет **только ID3 (MP3)**; `.wma`/`.ogg` в плане останутся нетронутыми (9+1 файл). **Причина MISSING ≠ потеря файла.**
- 🔴 **`backup/` содержит только MP3 из плана.** Для не-MP3 нужен **отдельный** бэкап (`backup_raw/`), иначе правило «оригинал остался в бэкапе» невыполнимо.
- 🔴 **Защита от захвата `backup/`**: `os.walk` пропускает `backup`/`backup_raw`, `assert "/backup/" not in path` перед каждой записью/удалением. Проверять факт после прогона (`du -sh` + `find | wc -l`).
- 🔴 **Логи скриптов не обрезать** (`[:10]`/`[:30]` в print) — при разборе расхождений теряются данные, приходится перезапускать долгую сверку. Печатать всё.
- ⚠️ `ffmpeg` на хосте NAS **нет**; добавлять в контейнер (`apk add --no-cache ffmpeg`), не использовать чужие контейнеры.
- ⚠️ macOS-мусор `._*` (AppleDouble) попадает в `find` как «m4a» — **исключать** (`if f.startswith("._"): continue`), иначе счёт файлов завышен вдвое (26 vs 13).
- 🔴 **Длительные операции на NAS (бэкап/apply) — только в фоне** (`background=true` + `notify_on_complete`): foreground-таймаут 600 с обрывает ssh, операция продолжается «осиротевшей».<br>Два независимых долгих процесса можно вести **параллельно** (ЧТЕНИЕ тегов + КОПИРОВАНИЕ файлов не конфликтуют) — Alex это ожидает.
## Связано
@@ -78,6 +78,13 @@ related:
> ⏭ **Дальше:** применение тегов через контейнер `tagwriter` — [[personal/plans/tagwriter-container]].
> ✅ Контейнер собран, **dry-run прошёл (MISSING 0, к обработке 7041)**.
> ✅ **Бэкап выполнен: 7041 файл, 42 ГБ → `/mnt/RED_2TB/storage/Music/backup/`** (файлов в backup 7041, сходится с планом).
> ✅ **APPLY выполнен: 7041 файл обновлён, ошибок 0** (~17 мин, в фоне).
> ✅ **READ-BACK:** не найдено 0.
> ⚠️ **Независимая сверка `verify_tags.py`: 7610/7905 = 96.3%** — MISSING 83, MISMATCH 212.
> **MISSING 83 = 10 не-MP3 (9 wma + 1 ogg, mutagen их не пишет) + 73 не разобрано. MP3 из плана потеряно — 0.**
> Полный разбор перезапущен (лог был обрезан `первые 10`/`первые 30`).
> ✅ **Бэкап не-MP3: 70 файлов, 386 МБ → `/music/backup_raw/`** (40 ogg · 16 wma · 13 m4a · 1 flac);
> `/music/backup/` не тронут (42 ГБ, 7041). ⏳ Далее — конвертация не-MP3 → MP3 (ffmpeg в образ, VBR V0, удаление оригиналов после проверки бэкапа), **ждёт `go` Alex**.
> ✅ **APPLY ВЫПОЛНЕН: `APPLY: обновлено 7041, ошибок 0`** (~17 мин, запуск в фоне).
> ✅ **READ-BACK: не найдено на диске 0.**
> Блокер прав на `/mnt/RED_2TB/docker/` решён: каталог создан **из контейнера** (`docker run -u 0 -v /mnt/RED_2TB/docker:/docker alpine mkdir` + `chown 950:950`).