diff --git a/personal/index.md b/personal/index.md index 73c73a00..f512a2a3 100755 --- a/personal/index.md +++ b/personal/index.md @@ -144,6 +144,7 @@ Hub: [[personal/projects/media-pipeline-tracker-parsers|Media Pipeline Roadmap]] - [[personal/spotify-library|Spotify Library]] - [[personal/yandex-music-library|Яндекс Музыка — библиотека]] - [[personal/tech/yandex-music-api|🎵 Yandex Music API — выгрузка библиотеки]] +- [[personal/tech/music-broken-toload|🗑 Аудио на перезагрузку — битые и DRM-файлы]] --- diff --git a/personal/plans/tagwriter-container.md b/personal/plans/tagwriter-container.md index 17fa869f..56271985 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. **Статус (обновлён 2026-09-26, вечер): ✅ APPLY 7041 + ✅ ДЕЛЬТА-ПРАВКИ 165/165 (readback 0 расхождений) + ✅ МУСОР `._*` ВЫЧИЩЕН (29 удалено, 0 осталось) + ⏳ КОНВЕРТАЦИЯ не-MP3 ЗАБЛОКИРОВАНА DRM (28 файлов WMA защищены Windows Media DRM, `License_URL ... DRM`, decode error rate 0.99). Решение Alex по TRCK = **B** (номера не удалять). Бэкапы: `backup/` 7041 файл 42 ГБ · `backup_raw/` 70 файлов 386 МБ · `backup_fix/` 165 файлов. Образ пересобран с ffmpeg 8.1.2.**_ +_Создан: 2026-09-26. **Статус (обновлён 2026-09-26, вечер): ✅ APPLY 7041 + ✅ ДЕЛЬТА-ПРАВКИ 165/165 (readback 0 расхождений) + ✅ МУСОР `._*` ВЫЧИЩЕН (29 удалено, 0 осталось) + ✅ ПРОБА 70 не-MP3 разобрана (41 ok · 13 drm · 16 corrupt) + 🔴 БИТЫЕ m4a/wma — контейнер разрушен ID3-заголовком, решения Alex: битые/DRM **удалять**, список в [[personal/tech/music-broken-toload]]. ⏳ Конвертация 41 (`convert41.py`, DRY-RUN) ждёт апрува. Решение Alex по TRCK = **B** (номера не удалять). Бэкапы: `backup/` 7041 файл 42 ГБ · `backup_raw/` 70 файлов 386 МБ · `backup_fix/` 165 файлов. Образ с ffmpeg 8.1.2.**_ _Основание: [[personal/plans/music-tags-wave38-fixes]]._ ## 🆕 СОСТОЯНИЕ НА КОНЕЦ 2026-09-26 (последние шаги сессии) @@ -32,13 +32,51 @@ _Основание: [[personal/plans/music-tags-wave38-fixes]]._ | **READBACK 165** | ✅ **совпало 165/165, расхождений 0** | | **Удаление `._*` мусора (`del_junk.py --apply`)** | ✅ **29 удалено, ошибок 0, `._*` в Music осталось 0** | | **Пересборка образа с ffmpeg** | ✅ `apk add --no-cache ffmpeg` → ffmpeg **8.1.2**, mutagen **1.47.0** | -| **DRY-RUN конвертации 70 не-MP3** | ⚠️ **28 ошибок** — WMA c **Windows Media DRM** | -| **Диагностика `probe_convert.py`** | ⏳ запущена: классификация 70 файлов → ok / drm / corrupt | -| **APPLY конвертации** | ⛔ **НЕ ЗАПУСКАТЬ** до разбора DRM-файлов | +| **DRY-RUN конвертации 70 не-MP3** | ⚠️ **28 ошибок** — WMA c **Windows Media DRM** + битые контейнеры | +| **Проба `probe_convert.py`** | ✅ **70 файлов разобраны: `ok` 41 (40 ogg + 1 flac) · `drm` 13 (НТЛ wma) · `corrupt` 16 (13 m4a + 3 wma)** → `/app/data/convert-probe.json` | +| **🔴 ПРИЧИНА «corrupt»** | **ID3-заголовок записан поверх структуры контейнера** (`moov atom not found`). Файлы битые **до сессии** — в `backup_raw/` те же байты. Восстановление невозможно | +| **Список битых/DRM** | ✅ **[[personal/tech/music-broken-toload]]** — 29 файлов на перезагрузку (рядом с `yandex-music-api.md`) | +| **Решение Alex по битым/DRM** | ✅ **удалять** («какая разница, они все равно не играют, так что сноси») | +| **Конвертация 41 (`convert41.py`)** | ⏳ **скрипт готов, DRY-RUN ждёт апрува** (блокирован таймаутом апрува) | +| **Удаление 29 битых/DRM** | ⏳ после конвертации 41 | | **Приёмка в Jellyfin** | ⏳ ждёт Alex | -| **Обновление доков** | ✅ этот документ + `personal/tech/music-tags-repair-truenas.md` | +| **Обновление доков** | ✅ `personal/plans/tagwriter-container.md` + `personal/tech/music-tags-repair-truenas.md` + `personal/tech/music-broken-toload.md` | -### 🔴 БЛОКЕР КОНВЕРТАЦИИ: Windows Media DRM в `.wma` +### 🔴 ИТОГОВАЯ ПРОБА 70 НЕ-MP3 (`probe_convert.py`) — 41 / 13 / 16 + +| Класс | Кол-во | Форматы | Действие | +|---|---:|---|---| +| **`ok`** | **41** | 40 `.ogg` + 1 `.flac` | ✅ конвертировать (`convert41.py`) | +| **`drm`** | **13** | 13 `.wma` (НТЛ `wma/Track 1–13`) | ⛔ снять DRM невозможно → **удалить** | +| **`corrupt`** | **16** | 13 `.m4a` (Infected Mushroom) + 3 `.wma` | ⛔ контейнер разрушен → **удалить** | + +**Состав `ok`:** Muse — *Absolution* (14 ogg), Кирпичи — *let's rock!* (13), Бутусов — *Имя рек* (8), +синглы (Жуки, Мумий Тролль, Би-2, Дискотека Авария, Pushnoy.ru) + 1 flac (Infected Mushroom — *Bust A Move*). + +**Состав `corrupt`:** 13 m4a `(2012) Army of Mushrooms` (~130 МБ) + `НТЛ/NTL - ВЕРТИТЕ ЗАДОМ_old.wma` + +`Агата Кристи/Ближе.wma` + `Ундервуд/02 - Гагарин, я вас любила.wma`. + +### 🔴🔴 КОРНЕВАЯ ПРИЧИНА «corrupt»: ID3-ЗАГОЛОВОК ПОВЕРХ КОНТЕЙНЕРА + +Первые байты всех 16 «битых» файлов: `49 44 33 03 …` = **`ID3`** — там, где обязан быть +`ftyp` (m4a) или `30 26 B2 75` (ASF/wma). Отсюда `moov atom not found` / `Invalid data found`. + +``` +01 Never Mind.m4a → 49443303000000006f62544954320000 («ID3….obTIT2…») +NTL - ВЕРТИТЕ ЗАДОМ_old.wma → 4944330300000000167a544954320000 («ID3…TIT2») +``` + +**Вывод:** ID3-заголовок был записан **поверх структуры формата** предыдущей массовой правкой тегов — +контейнер (m4a/wma) уничтожен. **Восстановлению не подлежит.** + +⚠️ **Бэкап не спасает:** сравнение `backup_raw/` ↔ диск — **байты и размеры идентичны** +(`49443303…` в обоих), т.е. файлы были битые **до** снятия бэкапа, до всей сессии. + +🔴 **ПРАВИЛО: НИКОГДА не применять массовые ID3-правки к не-MP3 (m4a/wma/ogg/flac).** +Именно это разрушает контейнер. Теги не-MP3 писать только родными инструментами формата +(mutagen: `MP4` для m4a, `OggVorbis` для ogg, `ASF` для wma). + +### 🔒 БЛОКЕР КОНВЕРТАЦИИ: Windows Media DRM в `.wma` (13 файлов) Первый dry-run `convert_nonmp3.py` дал **28 FFMPEG_FAIL**. Причина из stderr: @@ -49,15 +87,18 @@ Conversion failed! ``` Файлы НТЛ `wma/Track 1–13.wma` **защищены Windows Media DRM**: декодер получает мусор -(99.5 % ошибок) вместо аудио. **Это не сбой конвейера — контент защищён, конвертация технически -невозможна без снятия DRM.** Оригиналы в `backup_raw/` есть, ничего не потеряно. +(99.5 % ошибок) вместо аудио. **Контент защищён, конвертация технически невозможна.** -**Проверка форматов (по логу dry-run):** ошибки затронули `.wma`; `.ogg`/`.m4a`/`.flac` в списке -ошибок не фигурировали так массово — точная разбивка даётся `probe_convert.py` -(классы `ok` / `drm` / `corrupt` / `other`, отчёт `/app/data/convert-probe.json`). +**Про DRM (проверено по документации Microsoft, 2026-09-26):** +- Если лицензии нет на текущей машине — **официального способа снять DRM не существует**. +- Microsoft **закрыл инфраструктуру лицензирования** Windows Media DRM — лицензии не выдаются. +- Единственные шансы: машина/профиль, где файлы **когда-то играли** (лицензии в + `%USERPROFILE%\DRM\` и `C:\Documents and Settings\All Users\DRM\`), либо бэкап лицензий. +- **Практический вывод Alex:** файлы всё равно не играют → **удалить, скачать заново** + (НТЛ — российский рэп, есть в открытом доступе). -**Правило:** перед конвертацией формата с DRM — сначала probe каждого файла, потом конвертация -только класса `ok`. DRM-файлы оставить как есть (или по решению Alex — удалить/перекодировать иначе). +**Правило:** перед конвертацией формата — сначала `probe` каждого файла, конвертировать +только класс `ok`. DRM/битые — в список на перезагрузку. ### 📄 `final-update.csv` — 165 правок (решение B) @@ -120,7 +161,7 @@ docker run --rm -u 0 -v /mnt/RED_2TB/docker:/docker alpine:latest sh -c \ ## Решение: контейнер `tagwriter` -- **Образ:** `python:3.12-alpine` + `pip install mutagen==1.47.0` +- **Образ:** `python:3.12-alpine` + `apk add --no-cache ffmpeg` (**8.1.2**) + `pip install mutagen==1.47.0` - **Пользователь:** `user: "950:950"` (владелец Music; иначе запись невозможна) - **Монтирование:** `/mnt/RED_2TB/storage/Music` → `/music` (rw), `/mnt/RED_2TB/docker/tagwriter/data` → `/app/data` - **Теги:** ID3v2.3, `encoding=1` (**UTF-16**), `v2_version=3` — только тег-блок, аудио не пересжимается @@ -132,7 +173,7 @@ docker run --rm -u 0 -v /mnt/RED_2TB/docker:/docker alpine:latest sh -c \ |---|---| | `metrics/apply_tags.py` (в проекте — `apply_tags.py`) | `--backup` / `--dry-run` / `--apply` / `--readback` / `--limit N` | | `docker-compose.yml` | сервис `tagwriter`, профиль `tools`, монтирование Music | -| `Dockerfile` | `python:3.12-alpine` + mutagen, `USER 950:950` | +| `Dockerfile` | `python:3.12-alpine` + **ffmpeg 8.1.2** + mutagen 1.47.0, `USER 950:950`, `chmod 755 /app` | Локальные исходники: `~/Downloads/music-tags/tagwriter/`. @@ -163,10 +204,42 @@ docker run --rm -u 0 -v /mnt/RED_2TB/docker:/docker alpine:latest sh -c \ 13. ✅ **Бэкап 165** → `/music/backup_fix/` + **APPLY 165** + **READBACK 165/165, 0 расхождений**. 14. ✅ **Чистка `._*`** — `del_junk.py --apply`: **29 удалено, 0 осталось**. 15. ✅ **Образ пересобран с ffmpeg 8.1.2** (`apk add --no-cache ffmpeg`). -16. ⚠️ **DRY-RUN конвертации: 28 ошибок — WMA с Windows Media DRM.** `probe_convert.py` запущен. -17. ⛔ **APPLY конвертации** — ждёт разбора DRM-файлов (класс `ok` конвертируем, `drm`/`corrupt` — нет). -18. ⏳ **Приёмка в Jellyfin** (глазами Alex). -19. ✅ **Обновить доки** — `personal/plans/tagwriter-container.md` + `personal/tech/music-tags-repair-truenas.md`. +16. ✅ **DRY-RUN конвертации: 28 ошибок** — WMA DRM + битые контейнеры. +17. ✅ **`probe_convert.py`** — 70 файлов разобраны: **`ok` 41 · `drm` 13 · `corrupt` 16**. +18. 🔴 **Диагноз `corrupt`** — ID3-заголовок поверх контейнера; `backup_raw/` идентичен диску → битые до сессии. +19. ✅ **Список 29 битых/DRM → [[personal/tech/music-broken-toload]]**; Alex: **удалять**. +20. ⏳ **Конвертация 41 (`convert41.py`)** — скрипт готов, DRY-RUN ждёт апрува. +21. ⏳ **Удаление 29 битых/DRM** — после конвертации 41. +22. ⏳ **Приёмка в Jellyfin** (глазами Alex). +23. ✅ **Обновить доки** — `personal/plans/tagwriter-container.md` + `personal/tech/music-tags-repair-truenas.md` + `personal/tech/music-broken-toload.md`. + +### 🔧 `convert41.py` — конвертация 41 файла (создан 2026-09-26) + +Локально `/tmp/convert41.py`, на NAS `/mnt/RED_2TB/docker/tagwriter/convert41.py`. +Берёт **только пути из `convert-probe.json` → ключ `ok`** (41 файл); `drm`/`corrupt` не трогает. + +Предохранители (по требованию Alex «конвертируй и проверяй теги, потом сноси»): +1. `assert "/backup" not in nfc(src)` — защита от захвата бэкапов; +2. проверка `backup_raw/` + совпадение размера перед конвертацией; +3. `ffmpeg -q:a 0 -map_metadata 0 -id3v2_version 3` → `.tmp.mp3`; +4. валидация: `codec=mp3` и длительность ≥ 98 % оригинала; +5. `os.replace(tmp, out)` — атомарно; +6. **удаление оригинала только после успешной валидации**; +7. без `--apply` — dry-run (файлы не пишутся, оригиналы не удаляются). + +### 🔴 ПИТФОЛЛ: ПОРЯДОК БЭКАП ↔ ПРАВКА (проверка Alex) + +Alex трижды переспросил «ты снимал бэкап до правки?» — **правильный порядок: +`backup` → `apply`, а не наоборот.** Проверка фактом: + +- времена файлов в `backup/`: **09:03–09:38**; MP3 на диске после apply: **11:35** → бэкап **раньше**; +- `backup_fix/` vs диск для 165 файлов: **различаются 165, одинаковых 0** — в бэкапе старые + значения (`Rebekka`, `Nautilus Pompilius`, `Ãðÿçíàÿ êðîâü`), на диске новые (`Ребекка`, + `Наутилус Помпилиус`, `Грязная кровь`). + +**Правило: бэкап — ДО любой правки; доказывать фактом (mtime + diff тегов), а не словами.** +Скрипт проверки: `prove_backup_before.py`. Три бэкапа на диске: `backup/` (7041 MP3), +`backup_raw/` (70 не-MP3), `backup_fix/` (165 MP3) — все сняты **до** соответствующих правок. ## ✅ СОСТОЯНИЕ НА 2026-09-26 (факты) @@ -187,7 +260,12 @@ docker run --rm -u 0 -v /mnt/RED_2TB/docker:/docker alpine:latest sh -c \ | **БЭКАП 165** | ✅ **165 файлов → `/music/backup_fix/`** | | **МУСОР `._*`** | ✅ **29 удалено, 0 осталось** (`del_junk.py --apply`) | | **ПЕРЕСЧЁТ `action` ПО ДИСКУ** | ✅ **не понадобился** — заменён коротким путём (212 из verify → факт-чек → 165). **Правило Alex: есть список — не пересчитывать план.** | -| **КОНВЕРТАЦИЯ не-MP3 → MP3** | ⛔ **ЗАБЛОКИРОВАНА DRM**: 28 WMA защищены Windows Media DRM. Идёт `probe_convert.py` | +| **ПРОБА 70 НЕ-MP3** | ✅ **`ok` 41 · `drm` 13 · `corrupt` 16** (`convert-probe.json`) | +| **🔴 ПРИЧИНА `corrupt` (16)** | ID3-заголовок (`49443303`) поверх контейнера → `moov atom not found`. **Битые до сессии**, `backup_raw/` идентичен диску | +| **СПИСОК НА ПЕРЕЗАГРУЗКУ** | ✅ [[personal/tech/music-broken-toload]] — 29 файлов (16 битых + 13 DRM) | +| **РЕШЕНИЕ Alex: битые/DRM** | ✅ **удалять** | +| **КОНВЕРТАЦИЯ 41 (`convert41.py`)** | ⏳ **скрипт готов (NAS), DRY-RUN ждёт апрува** | +| **УДАЛЕНИЕ 29 битых/DRM** | ⏳ после конвертации 41 | ### Результат независимой сверки `verify_tags.py` (2026-09-26, ПОЛНЫЙ лог) @@ -451,6 +529,10 @@ docker compose --profile tools run --rm \ | `del_junk.py` | NAS | удаление `._*` по списку `junk-files.json` (dry-run без `--apply`) | | `convert_nonmp3.py` | NAS | конвертация не-MP3 → MP3 (VBR V0, `-map_metadata 0`, ID3v2.3), dry-run/`--apply` | | `probe_convert.py` | NAS | классификация 70 не-MP3: `ok`/`drm`/`corrupt`/`other` → `convert-probe.json` | +| `convert41.py` | NAS | **конвертация только 41 `ok`** с валидацией + удаление оригиналов после успеха | +| `check_backup_healthy.py` | NAS | сверка байтов/размеров диск ↔ `backup_raw/` (доказал: битые до сессии) | +| `prove_backup_before.py` | NAS | доказывает, что `backup_fix/` снят **до** правок (mtime + diff тегов) | +| `diag_m4a.py` | NAS | дамп первых байтов m4a/wma → выявил `ID3` поверх `ftyp`/ASF | | `recompute_action.py` | NAS | полный пересчёт `action` по диску (не понадобился — заменён коротким путём) | | `deep_classify.py`, `find_false_nochange.py`, `action_check.py`, `classify2.py`, `build_update_plan.py`, `fact_check_212.py` | локально `/tmp/` | анализаторы расхождений | diff --git a/personal/tech/music-broken-toload.md b/personal/tech/music-broken-toload.md index 3c8e65b2..aa448503 100644 --- a/personal/tech/music-broken-toload.md +++ b/personal/tech/music-broken-toload.md @@ -34,6 +34,18 @@ related: personal/tech/yandex-music-api, personal/tech/music-tags-repair-truenas `moov atom not found` / `Invalid data found`. Структура контейнера затёрта. **Восстановлению не подлежит.** +**Доказательство (первые байты):** +``` +01 Never Mind.m4a → 49 44 33 03 … («ID3….obTIT2…») ожидалось: ftyp +NTL - ВЕРТИТЕ ЗАДОМ_old.wma → 49 44 33 03 … («ID3…TIT2») ожидалось: 30 26 B2 75 (ASF) +``` + +**Корень:** ID3-заголовок записан **поверх структуры формата** предыдущей массовой правкой тегов. +⚠️ **Бэкап не помогает:** `backup_raw/` идентичен диску по байтам — порча случилась раньше бэкапа. + +🔴 **ПРАВИЛО: НИКОГДА не применять массовые ID3-правки к не-MP3 (m4a/wma/ogg/flac).** +Теги не-MP3 писать только родными инструментами (`mutagen.MP4`, `OggVorbis`, `ASF`). + ### Infected Mushroom — (2012) Army of Mushrooms (13 m4a, ~130 МБ) Папка: `!Pop, Club, Любэ/Infected Mushroom/(2012) Army of Mushrooms/` diff --git a/personal/tech/music-tags-repair-truenas.md b/personal/tech/music-tags-repair-truenas.md index 94eea287..109f443a 100755 --- a/personal/tech/music-tags-repair-truenas.md +++ b/personal/tech/music-tags-repair-truenas.md @@ -36,16 +36,25 @@ related: > | **APPLY 165 + READBACK** | ✅ **165/165, ошибок 0, расхождений 0** | > | Чистка `._*` (`del_junk.py --apply`) | ✅ **29 удалено, 0 осталось** | > | Образ с ffmpeg | ✅ пересобран, ffmpeg **8.1.2** + mutagen 1.47.0 | -> | **DRY-RUN конвертации не-MP3** | ⛔ **28 ошибок — WMA с Windows Media DRM** (`License_URL … sbp=DRM`, `Decode error rate 0.99`) | -> | Диагностика `probe_convert.py` | ⏳ классифицирует 70 файлов: `ok`/`drm`/`corrupt`/`other` | -> | APPLY конвертации | ⛔ **НЕ ЗАПУСКАТЬ** — конвертировать только класс `ok` | +> | **DRY-RUN конвертации не-MP3** | ⚠️ **28 ошибок** — WMA с Windows Media DRM + битые контейнеры | +> | ✅ **Проба `probe_convert.py`** | **70 файлов: `ok` 41 (40 ogg + 1 flac) · `drm` 13 (НТЛ wma) · `corrupt` 16 (13 m4a + 3 wma)** | +> | 🔴 **ПРИЧИНА `corrupt`** | **ID3-заголовок (`49443303`) записан поверх структуры контейнера** → `moov atom not found`. Файлы битые **до сессии** (в `backup_raw/` те же байты) — восстановление невозможно | +> | ✅ Список на перезагрузку | [[personal/tech/music-broken-toload]] — **29 файлов** (16 битых + 13 DRM) | +> | ✅ Решение Alex | битые/DRM **удалять** («какая разница, они все равно не играют») | +> | ⏳ Конвертация 41 | `convert41.py` готов, DRY-RUN ждёт апрува | +> | ⏳ Удаление 29 битых/DRM | после конвертации 41 | > | Приёмка в Jellyfin | ⏳ ждёт Alex | > > **🔴 НОВЫЙ КЛАСС ПРОБЛЕМЫ: зашифрованный контент.** Часть `.wma` (НТЛ `wma/Track 1–13.wma`) -> защищена **Windows Media DRM** — ffmpeg не может их декодировать (99.5 % ошибок). Это не сбой -> скрипта и не потеря файлов (оригиналы в `backup_raw/`). Обходного пути без снятия DRM нет. +> защищена **Windows Media DRM** — ffmpeg не может их декодировать (99.5 % ошибок). Лицензии +> Microsoft больше не выдаются → **снять DRM невозможно**. Не сбой скрипта (оригиналы в `backup_raw/`). > **Правило: перед конвертацией формата — probe каждого файла, конвертировать только валидные.** > +> **🔴🔴 НОВЫЙ КЛАСС ПРОБЛЕМЫ: контейнер разрушен массовой ID3-правкой.** 16 файлов (13 m4a + 3 wma) +> начинаются с `49 44 33 03` (**`ID3`**) вместо `ftyp`/ASF → `moov atom not found`. **Правило: +> НИКОГДА не применять массовые ID3-правки к не-MP3 (m4a/wma/ogg/flac)** — теги не-MP3 писать +> только родными инструментами формата (`mutagen.MP4`, `OggVorbis`, `ASF`). +> > **Итог по MP3: план закрыт.** 7041 (основной apply) + **165 (дельта)** = все MP3 приведены к плану. > Остались только не-MP3 (70 файлов), из которых часть конвертируема, часть — DRM. > @@ -228,11 +237,28 @@ related: > ⏭ **Осталось:** (1) ✅ полный разбор 83 MISSING + 212 MISMATCH — **сделан, вскрыл ДЕФЕКТ ПЛАНА > (353 строки ложного `no_change`) + факт-чек (165 реальных правок)**; > (2) ✅ **ответ Alex по TRCK = B** → **apply дельта-правок (165): 165/165, 0 расхождений**; -> (3) ⛔ конвертация не-MP3 → MP3 — **заблокирована Windows Media DRM** (28 WMA); идёт `probe_convert.py`, -> конвертировать только класс `ok`; -> (4) приёмка в UI Jellyfin (глазами Alex); (5) после приёмки — удалить бэкапы +> (3) ✅ **проба 70 не-MP3**: `ok` 41 · `drm` 13 · `corrupt` 16; битые/DRM → список +> [[personal/tech/music-broken-toload]], Alex: **удалять**; +> (4) ⏳ конвертация 41 `ok` (`convert41.py`, DRY-RUN ждёт апрува); +> (5) ⏳ удаление 29 битых/DRM; +> (6) ⏳ приёмка в UI Jellyfin (глазами Alex); (7) после приёмки — удалить бэкапы > (`backup/`, `backup_raw/`, `backup_fix/`). > +> ### 🔴 ПИТФОЛЛ: БЭКАП СНИМАТЬ ДО ПРАВКИ, ДОКАЗЫВАТЬ ФАКТОМ +> +> Alex переспросил трижды («ты снимал бэкап до правки?»). **Правильный порядок — `backup` → `apply`.** +> Доказательство фактом, не словами: времена файлов `backup/` **09:03–09:38** vs MP3 на диске после +> apply **11:35**; `prove_backup_before.py` — `backup_fix/` vs диск: **различаются 165, одинаковых 0** +> (в бэкапе `Rebekka`/`Nautilus Pompilius`/`Ãðÿçíàÿ êðîâü`, на диске `Ребекка`/`Наутилус Помпилиус`/`Грязная кровь`). +> **Три бэкапа, все сняты ДО соответствующих правок:** `backup/` (7041 MP3), `backup_raw/` (70 не-MP3), +> `backup_fix/` (165 MP3). +> +> ### 🔴 ПИТФОЛЛ: битые m4a/wma не восстановить из бэкапа +> +> `check_backup_healthy.py` доказал: `backup_raw/` и диск для битых файлов **идентичны по байтам** +> (`49443303…`) — порча случилась **до** снятия бэкапа, до всей сессии. Соответственно «проверь +> в бэкапе, вдруг живые» — не сработало. **Урок: бэкап защищает только от правок ПОСЛЕ него.** +> > ### 🔴 ПИТФОЛЛ: sentinel-группа собирается по КОРНЮ сборника, НЕ по ближайшей папке > > Первая версия правки группировала пустой `artist` по `parts[-2]` →