From ae931acd155dbf37985fdf15e2d64c70905ce039 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Sun, 27 Sep 2026 00:19:39 +0600 Subject: [PATCH] [2026-09-27] eagle: personal/plans/tagwriter-container.md personal/tech/music-tags-repair-truenas.md --- personal/plans/tagwriter-container.md | 102 +++++++++++++++++++-- personal/tech/music-tags-repair-truenas.md | 7 ++ 2 files changed, 103 insertions(+), 6 deletions(-) diff --git a/personal/plans/tagwriter-container.md b/personal/plans/tagwriter-container.md index b4090c88..298e440c 100755 --- a/personal/plans/tagwriter-container.md +++ b/personal/plans/tagwriter-container.md @@ -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, операция продолжается «осиротевшей».
Два независимых долгих процесса можно вести **параллельно** (ЧТЕНИЕ тегов + КОПИРОВАНИЕ файлов не конфликтуют) — Alex это ожидает. ## Связано diff --git a/personal/tech/music-tags-repair-truenas.md b/personal/tech/music-tags-repair-truenas.md index 2dcac340..b1ec7213 100755 --- a/personal/tech/music-tags-repair-truenas.md +++ b/personal/tech/music-tags-repair-truenas.md @@ -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`).