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

This commit is contained in:
Alexey Martemyanov committed 2026-09-27 01:20:21 +06:00
1 parent 1b4e2c8600
commit 0d9ab9d3ba
4 files changed
+149 -28

No files matched your search

+1
View File
@@ -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-файлы]]
---
+102 -20
View File
@@ -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/` | анализаторы расхождений |
+12
View File
@@ -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/`
+34 -8
View File
@@ -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]` →