[2026-09-27] eagle: personal/plans/tagwriter-container.md personal/tech/music-broken-toload.md personal/tech/music-tags-repair-truenas.md
This commit is contained in:
1 parent
9493ae5514
commit
607fc8ba2a
3 files changed
+200
-16
No files matched your search
@@ -4,7 +4,7 @@ namespace: personal
|
||||
related:
|
||||
- '[[personal/plans/music-tags-wave38-fixes]]'
|
||||
- '[[personal/tech/music-tags-repair-truenas]]'
|
||||
status: applied-delta-closed
|
||||
status: converted-tags-lost-pending-restore
|
||||
tags:
|
||||
- music
|
||||
- truenas
|
||||
@@ -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 осталось) + ✅ ПРОБА 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.**_
|
||||
_Создан: 2026-09-26. **Статус (обновлён 2026-09-26, финал сессии): ✅ APPLY 7041 + ✅ ДЕЛЬТА-ПРАВКИ 165/165 + ✅ МУСОР `._*` ВЫЧИЩЕН (29) + ✅ КОНВЕРТАЦИЯ 41 (41/41) + ✅ ПРОБА 70 не-MP3 (41 ok · 13 drm · 16 corrupt) + ✅ ТЕГИ 40 НОВЫХ MP3 ЗАПИСАНЫ (`fix40.py`, 40/40, решение Alex «Нормально делать» → номер срезать). 🔴🔴 **КРИТИЧЕСКИЙ ДЕФЕКТ `fix40.py`: 40 MP3 СХЛОПНУЛИСЬ В ~1 КБ** — `ID3(p).save(tmp)` пишет ТОЛЬКО тег-блок, MPEG-поток не копируется. Аудио уничтожено. **ВОССТАНОВИМО:** `backup_conv40/` (40/40 целых, до записи тегов) + `backup_raw/` (41/41 оригиналов ogg/flac). Откат готов, ждёт «откат» от Alex. Решение Alex по TRCK = **B** (номера не удалять). Бэкапы: `backup/` 7041 · `backup_raw/` 70 · `backup_fix/` 165 · `backup_conv40/` 40. Образ с ffmpeg 8.1.2. ⏳ НЕ выполнено: удаление 29 битых/DRM, правка дыры в `apply_tags.py` (нет `.mp3`-фильтра), приёмка Jellyfin.**_
|
||||
_Основание: [[personal/plans/music-tags-wave38-fixes]]._
|
||||
|
||||
## 🆕 СОСТОЯНИЕ НА КОНЕЦ 2026-09-26 (последние шаги сессии)
|
||||
@@ -37,8 +37,10 @@ _Основание: [[personal/plans/music-tags-wave38-fixes]]._
|
||||
| **🔴 ПРИЧИНА «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 |
|
||||
| **Конвертация 41 (`convert41.py`)** | ✅ **ВЫПОЛНЕНО: 41/41, 0 ошибок** (`convert-result.json` — все 41 статус `CONVERTED`). Скрипт удаляет оригинал ogg/flac **сразу после успешной валидации** → оригиналов на диске больше нет |
|
||||
| **🔴 НОВЫЙ ДЕФЕКТ: теги новых MP3 ПУСТЫЕ** | 🔴 **40 из 41 без тегов** (`check_conv_tags.py` → `conv-tags-report.json`). Причина: **ffmpeg НЕ переносит Vorbis comments (ogg) → ID3**, `-map_metadata 0` работает только для уже-ID3-источников. Только 1 файл (flac) сохранил тег |
|
||||
| **Восстановление тегов 40** | ⏳ **ждёт решения Alex** по заголовкам Кирпичей (в плане `<N> - <title>`, у Muse/Бутусова заголовок чистый). Скрипт `fix40.py` ещё не написан |
|
||||
| **Удаление 29 битых/DRM** | ⏳ **не выполнено** — ждёт команды (список в [[personal/tech/music-broken-toload]] уже сохранён) |
|
||||
| **Приёмка в Jellyfin** | ⏳ ждёт Alex |
|
||||
| **Обновление доков** | ✅ `personal/plans/tagwriter-container.md` + `personal/tech/music-tags-repair-truenas.md` + `personal/tech/music-broken-toload.md` |
|
||||
|
||||
@@ -208,10 +210,152 @@ docker run --rm -u 0 -v /mnt/RED_2TB/docker:/docker alpine:latest sh -c \
|
||||
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`.
|
||||
20. ✅ **Конвертация 41 (`convert41.py --apply`)** — **41/41, 0 ошибок**, оригиналы ogg/flac удалены.
|
||||
21. 🔴 **Проверка тегов новых MP3** — **40 из 41 ПУСТЫЕ** (см. § ПИТФОЛЛ: теги теряются при конвертации ogg → MP3).
|
||||
22. ✅ **Решение Alex по Кирпичам** — «Нормально делать» = ведущий `<N> - ` **срезать**.
|
||||
23. ✅ **`fix40.py` написан** (ID3v2.3 UTF-16, правило B, срезка номера, бэкап `backup_conv40/`) → **DRY-RUN 40/40, 0 ошибок**.
|
||||
24. ✅ **`fix40.py --apply`** → отчёт «40/40 записано, 0 ошибок» — 🔴 **ЛОЖНАЯ ПРИЁМКА: аудио уничтожено** (см. § КРИТИЧЕСКИЙ ДЕФЕКТ).
|
||||
25. 🔴 **Диагностика** (`verify40.py`, `probe40b.py`, `damage_check.py`): 40 MP3 = огрызки ~1.1 КБ; `backup_conv40/` 40/40 целых; `backup_raw/` 41/41 целых.
|
||||
26. ⏳ **Откат 40 из `backup_conv40/` + перезапись тегов правильным способом** — ждёт «откат» от Alex.
|
||||
27. ⏳ **Удаление 29 битых/DRM** — список в [[personal/tech/music-broken-toload]], команды не было.
|
||||
28. ⏳ **Правка дыры в `apply_tags.py`** (нет `.mp3`-фильтра, `build_disk_index` индексирует всё) — Alex сказал «правь», не выполнено.
|
||||
29. ⏳ **Приёмка в Jellyfin** (глазами Alex).
|
||||
30. ✅ **Обновить доки** — этот файл + `personal/tech/music-tags-repair-truenas.md` + `personal/tech/music-broken-toload.md`.
|
||||
|
||||
### 🔴 ОТКРЫТЫЙ ДЕФЕКТ `apply_tags.py`: НЕТ ФИЛЬТРА `.mp3`
|
||||
|
||||
**Симптом (обнаружен по mtime битых m4a/wma):** 16 не-MP3 файлов получили mtime 10:20–10:32 —
|
||||
время `--apply` волны 38. Причина: `build_disk_index()` (стр. 47-48) индексирует **все** файлы,
|
||||
`cmd_apply` для m4a/wma: `ID3(disk)` бросает `ID3NoHeaderError` → `except: tags = ID3()` (пустой) →
|
||||
`tags.save(disk)` пишет ID3 **поверх контейнера** → `moov atom not found`.
|
||||
|
||||
```python
|
||||
# ❌ ДЫРА (apply_tags.py, ~стр. 47-48):
|
||||
for root, dirs, files in os.walk(MUSIC):
|
||||
if "/backup" in root: continue
|
||||
for name in files: # ← НЕТ проверки расширения!
|
||||
...
|
||||
|
||||
# ✅ ФИКС: фильтровать только .mp3 + защита в записи
|
||||
if not name.lower().endswith(".mp3"): continue
|
||||
# и в cmd_apply перед save():
|
||||
if not disk.lower().endswith(".mp3"): continue
|
||||
```
|
||||
|
||||
⚠️ Это тот же класс дефекта, что и порча 16 файлов: **запись ID3 в контейнер другого формата.**
|
||||
Текущие скрипты (`apply_fix165.py`, `convert41.py`, `fix40.py`) работают по явным спискам MP3 — безопасны.
|
||||
Дыра остаётся в `apply_tags.py`, без фикса повторный прогон снова испортит не-MP3.
|
||||
|
||||
### 🔴🔴 ПИТФОЛЛ: ТЕГИ ТЕРЯЮТСЯ ПРИ КОНВЕРТАЦИИ OGG → MP3 (40 из 41)
|
||||
|
||||
**Симптом:** конвертация прошла чисто (41/41, 0 ошибок), аудио валидно (`codec=mp3`, длительность ≥ 98 %),
|
||||
но **40 новых MP3 имеют пустые `TIT2`/`TPE1`/`TALB`/`TRCK`**. Только 1 файл (источник — `.flac`) тег сохранил.
|
||||
|
||||
**Причина:** `-map_metadata 0` в ffmpeg переносит метаданные **только между совместимыми контейнерами**.
|
||||
У ogg метаданные лежат в **Vorbis comments** (`ARTIST=`, `TITLE=`), у MP3 — в **ID3v2** (`TPE1`, `TIT2`).
|
||||
ffmpeg этот маппинг **не делает** → пишет пустой ID3-блок. У flac-источника метаданные уже в формате,
|
||||
близком к ID3, поэтому единственный flac тег пронёс.
|
||||
|
||||
```bash
|
||||
# НЕ РАБОТАЕТ для ogg → mp3 (теги теряются):
|
||||
ffmpeg -i src.ogg -vn -acodec libmp3lame -q:a 0 -map_metadata 0 -id3v2_version 3 out.mp3
|
||||
```
|
||||
|
||||
**Рабочие варианты:**
|
||||
1. **Восстановить теги из плана** (выбран) — `tag-fix-plan.csv` содержит целевые `new_*` для всех 40 путей.
|
||||
Матч по **basename без расширения** + NFC (в плане путь с `.ogg`/`.flac`, на диске теперь `.mp3`).
|
||||
2. `ffmpeg ... -metadata title="..." -metadata artist="..."` — явный маппинг, но данные всё равно берём из плана.
|
||||
3. `mutagen` — прочитать `OggVorbis`/`FLAC` до конвертации и записать `ID3` после (сложнее, не нужно: план есть).
|
||||
|
||||
**🔴 ПРАВИЛО: при конвертации между форматами с разными системами тегов (`ogg`→`mp3`, `flac`→`mp3`)
|
||||
`-map_metadata 0` НЕ гарантирует перенос. Проверять теги у выхода ОБЯЗАТЕЛЬНО —
|
||||
и предпочтительно писать теги из плана, а не надеяться на ffmpeg.**
|
||||
Проверка: `check_conv_tags.py` → `conv-tags-report.json` (ключ `empty` — список пустых).
|
||||
|
||||
### 🔴🔴🔴 КРИТИЧЕСКИЙ ДЕФЕКТ `fix40.py`: АУДИО УНИЧТОЖЕНО (`ID3().save()` пишет только тег-блок)
|
||||
|
||||
**Симптом:** `fix40.py --apply` отработал «успешно» — **40/40 записано, 0 ошибок**. Но независимая
|
||||
проверка (`verify40.py`, `probe40b.py`) показала: `mutagen.mp3.MP3()` падает
|
||||
`can't sync to MPEG frame`, `ffprobe` → `Failed to find two consecutive MPEG audio frames`,
|
||||
`Invalid data found when processing input`. Файлы на диске стали **~1.1 КБ** вместо 2–10 МБ.
|
||||
|
||||
**Причина — корневая, ловится один раз на всю жизнь:**
|
||||
|
||||
```python
|
||||
# ❌ СЛОМАНО (fix40.py):
|
||||
tags = ID3(p) # ID3-объект, НЕ привязанный к файлу
|
||||
tags.save(tmp, v2_version=3) # mutagen пишет ТОЛЬКО ID3-заголовок → MPEG-поток НЕ копируется
|
||||
os.replace(tmp, p) # оригинал перезаписан огрызком
|
||||
```
|
||||
|
||||
`ID3(path)` — это **парсер тег-блока**, он не знает про аудио-поток. `save()` у него сериализует
|
||||
только фреймы ID3. Чтобы аудио сохранилось, нужен объект файла, знающий про MPEG-поток:
|
||||
|
||||
```python
|
||||
# ✅ ПРАВИЛЬНО (вариант 1 — тег пишется в тот же файл, поток сохраняется):
|
||||
from mutagen.id3 import ID3
|
||||
tags = ID3(p)
|
||||
tags.save(p, v2_version=3) # save() В СУЩЕСТВУЮЩИЙ путь → поток перекопируется корректно
|
||||
|
||||
# ✅ ПРАВИЛЬНО (вариант 2 — через MP3-контейнер):
|
||||
from mutagen.mp3 import MP3
|
||||
m = MP3(p)
|
||||
m["TIT2"] = TIT2(encoding=1, text=[title])
|
||||
m.save(v2_version=3) # MP3.save() сохраняет и поток, и тег
|
||||
|
||||
# ✅ ПРАВИЛЬНО (вариант 3 — копируем оригинал целиком, потом правим тег):
|
||||
shutil.copy2(p, tmp) # сначала полная копия (аудио на месте)
|
||||
t = ID3(tmp); ...; t.save(tmp, v2_version=3) # правим тег в копии
|
||||
os.replace(tmp, p)
|
||||
```
|
||||
|
||||
⚠️ **Разница незаметна на MP3, который ещё не читался:** `ID3(p)` + `save(tmp)` создаёт валидный
|
||||
ID3-заголовок, mutagen/`ffprobe` не ругаются на **запись**, ошибка вылезает только на **чтении аудио**.
|
||||
Поэтому «40/40, 0 ошибок» в отчёте — **ложная приёмка**. Проверять надо `MP3(p).info.length`, а не факт `save()`.
|
||||
|
||||
**Ключевое правило: `save()` у mutagen-ID3 без привязки к потоку = перезапись файла одним тег-блоком.**
|
||||
|
||||
### ✅ УЩЕРБ ЛОКАЛИЗОВАН И ОТКАТЫВАЕТСЯ В НОЛЬ (40 файлов)
|
||||
|
||||
| Источник | Состояние |
|
||||
|---|---|
|
||||
| 40 MP3 на диске (`fix40-result.json`, все `WROTE`) | 🔴 огрызки ~1.1–1.2 КБ, `Failed to find two consecutive MPEG audio frames` |
|
||||
| `/music/backup_conv40/` — копии **ДО** записи тегов | ✅ **40/40 целых** (2.08 МБ – 10.0 МБ, проверено `damage_check.py`) |
|
||||
| `/music/backup_raw/` — оригиналы ogg/flac | ✅ **41/41 целых**, 0 отсутствующих |
|
||||
|
||||
Единственный выживший MP3 — Infected Mushroom *Bust A Move* (18.8 МБ, кодек mp3 549 с): он не попал
|
||||
в запись (тег был изначально), потому и цел.
|
||||
|
||||
**План отката (3 шага, ~2 мин):**
|
||||
1. `restore40.py` — копирование 40 файлов из `backup_conv40/` на место (`.tmp` → проверка размера ≥1 МБ → `os.replace`).
|
||||
2. Проверка `ffprobe` по 40 → все `codec_name=mp3`, 40/40.
|
||||
3. Запись тегов **правильным способом** (см. варианты выше) + readback через `MP3(p).info`.
|
||||
|
||||
⚠️ `backup_conv40/` — **копии после конвертации, до записи тегов**: аудио там уже MP3 V0 (валидное),
|
||||
теги пустые/частичные. Этого достаточно: теги берём из плана (`fix40_source.json`, матч 40/40).
|
||||
|
||||
### 📋 МАТЧ 40 ПУСТЫХ MP3 ↔ ПЛАН (2026-09-26)
|
||||
|
||||
**Матч по basename без расширения — 40/40 найдено.** ⚠️ Первая попытка дала **0 совпадений**:
|
||||
сравнивал полный путь с расширением (`.mp3` на диске vs `.ogg`/`.flac` в плане).
|
||||
**Правило: ключ матча = `NFC(путь) без расширения` + маппинг `/storage/Music/` → `/music/`.**
|
||||
|
||||
| Группа | Файлов | `new_title` в плане | `new_track` |
|
||||
|---|---:|---|---|
|
||||
| Бутусов & Ю-Питер — *Имя рек* | 8 | чистый (`Сердце камня`, `Мера`, …) | 1–8 ✅ |
|
||||
| Muse — *Absolution* | 14 | чистый (`Hysteria`, `Intro`, …) | 1–14 ✅ |
|
||||
| Кирпичи — *let's rock!* | 13 | ⚠️ **`<N> - <title>`** (`3 - squaw`, `1 - в загон!`) | 3, 1, … |
|
||||
| Синглы (Жуки, Дискотека Авария, Мумий Тролль, Би-2, Pushnoy) | 5 | чистый | пусто |
|
||||
|
||||
**✅ ОТВЕТ ALEX ПОЛУЧЕН (2026-09-26, «Нормально делать»):** у Кирпичей ведущий `<N> - ` из `new_title`
|
||||
**СРЕЗАЕТСЯ** (единообразие с Muse/Бутусовым, номер остаётся в `TRCK`). Реализовано в `fix40.py`:
|
||||
срезается только если префикс совпадает с `new_track` (`<N> - ` или `<N в 2 знака> - `).
|
||||
Результат: `3 - squaw` → `squaw`, `1 - в загон!` → `в загон!`, `9 - let's rock!` → `let's rock!`.
|
||||
|
||||
**Механизм (`fix40.py`, написан и запущен — `--apply` 40/40):** список `fix40_source.json`
|
||||
(40 пар `{mp3, plan row}`) → запись ID3v2.3 UTF-16 (`TIT2`/`TPE1`/`TALB`/`TRCK`) по правилу **B**
|
||||
(пустой `new_track` → `TRCK` не трогать). Бэкап: `backup_conv40/` (40 копий ДО записи).
|
||||
🔴 **НО: реализация записи дефектная — аудио уничтожено, см. § КРИТИЧЕСКИЙ ДЕФЕКТ `fix40.py` выше.**
|
||||
|
||||
### 🔧 `convert41.py` — конвертация 41 файла (создан 2026-09-26)
|
||||
|
||||
@@ -264,8 +408,10 @@ Alex трижды переспросил «ты снимал бэкап до п
|
||||
| **🔴 ПРИЧИНА `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 |
|
||||
| **КОНВЕРТАЦИЯ 41 (`convert41.py`)** | ✅ **выполнено: 41/41, 0 ошибок**, оригиналы ogg/flac удалены (`convert-result.json`) |
|
||||
| **🔴 ТЕГИ НОВЫХ MP3** | 🔴 **40 из 41 пустые** (`check_conv_tags.py`) — `-map_metadata 0` не переносит Vorbis comments → ID3 |
|
||||
| **ВОССТАНОВЛЕНИЕ ТЕГОВ 40** | ⏳ `fix40_source.json` собран (40/40 матч), `fix40.py` не написан — ждёт решения Alex по заголовкам Кирпичей |
|
||||
| **УДАЛЕНИЕ 29 битых/DRM** | ⏳ не выполнено — список сохранён, команды не было |
|
||||
|
||||
### Результат независимой сверки `verify_tags.py` (2026-09-26, ПОЛНЫЙ лог)
|
||||
|
||||
@@ -529,7 +675,16 @@ 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`** с валидацией + удаление оригиналов после успеха |
|
||||
| `convert41.py` | NAS | **конвертация только 41 `ok`** с валидацией + удаление оригиналов после успеха ✅ **выполнено 41/41** |
|
||||
| `check_conv_tags.py` | NAS | проверка тегов + аудио у 41 новых MP3 → `conv-tags-report.json` (**выявил 40 пустых**) |
|
||||
| `fix40.py` | NAS | 🔴 **запись тегов 40 новых MP3 — ДЕФЕКТНА** (`ID3(p).save(tmp)` уничтожил аудио, 40 файлов → ~1 КБ) |
|
||||
| `verify40.py` | NAS | независимая проверка 40: `MP3(p).info` — выявил `can't sync to MPEG frame` у всех 40 |
|
||||
| `probe40.py`, `probe40b.py` | NAS | `ffprobe -v verbose` по 41 файлу → `Failed to find two consecutive MPEG audio frames` |
|
||||
| `damage_check.py` | NAS | сверка размеров диск ↔ `backup_conv40/` ↔ `backup_raw/` (доказал: ущерб локализован, откат возможен) |
|
||||
| `inspect_data.py` | NAS | инвентарь `/app/data` + содержимое отчётов (выявил перезапись `conv-tags-report.json` 2-м прогоном) |
|
||||
| `empty_match_check.py`, `empty_match2.py` | NAS | матч 40 пустых против плана (v1 дал 0 — питфолл расширения; v2 фикс) |
|
||||
| `why_zero.py` | NAS | диагностика нулевого матча: расширения в плане + сырые байты путей |
|
||||
| `dump40.py` | NAS | полный дамп плана по 40 файлам → `fix40_source.json` (источник для `fix40.py`) |
|
||||
| `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 |
|
||||
@@ -613,6 +768,9 @@ docker compose --profile tools run --rm \
|
||||
- 🔴🔴 **WMA бывает с Windows Media DRM — конвертация НЕВОЗМОЖНА.** Признак: stderr ffmpeg содержит `License_URL : ...&sbp=DRM` и `Decode error rate 0.99... exceeds maximum`. Файл НТЛ `wma/Track 1–13.wma` — такие. **Правило: перед конвертацией любого формата — probe каждого файла** (`probe_convert.py`), конвертировать только класс `ok`; `drm` оставить как есть. Не трактовать DRM-ошибку как сбой скрипта и не пытаться «дожать» повторными прогонами.
|
||||
- ⚠️ **Лог ffmpeg забивается баннером** (`libav*/62.12.102`) — при разборе ошибок грепать маркеры (`License_URL`, `Decode error rate`, `Conversion failed`), а не листать хвост.
|
||||
- 🔴 **Пустой `new_track` в плане = «не задано», а НЕ «удалить тег».** Генерировать `action` на удаление тега нельзя: Alex подтвердил («это косяк плана, не надо удалять номер трека»). Реализация: при пустом `new_track` **исключать** `TRCK` из сравнения целиком (иначе 75 файлов Наутилуса потеряли бы номера).
|
||||
- 🔴🔴 **КОНВЕРТАЦИЯ МЕЖДУ РАЗНЫМИ СИСТЕМАМИ ТЕГОВ ТЕРЯЕТ ТЕГИ.** `ffmpeg -map_metadata 0` переносит метаданные только между совместимыми контейнерами: у **ogg** теги в **Vorbis comments** (`ARTIST=`, `TITLE=`), у **MP3** — в **ID3v2** (`TPE1`, `TIT2`). ffmpeg этот маппинг **не делает** → на выходе **пустой ID3**. Проверено: 40/41 новых MP3 без тегов (единственный выживший — `.flac`-источник). **Правило: после любой конвертации формата ОБЯЗАТЕЛЬНО проверять теги у выхода** (`check_conv_tags.py`), и предпочитать **запись тегов из плана**, а не надежду на ffmpeg. Не считать «0 ошибок ffmpeg» доказательством корректного результата — ошибок нет, а данные потеряны.
|
||||
- 🔴 **Матч CSV ↔ диск при смене расширения: ключ = путь БЕЗ расширения + NFC.** В плане пути записаны с исходным расширением (`.ogg`/`.flac`), на диске после конвертации — `.mp3`. Сравнение полного пути даёт **0 совпадений** (ложное «нет в плане»). Правило: `os.path.splitext(nfc(p))[0]` + маппинг `/storage/Music/` → `/music/`.
|
||||
- ⚠️ **`convert41.py` удаляет оригинал СРАЗУ после успешной валидации** — это заложено в скрипт, отдельного подтверждения не спрашивает. Учитывать: после прогона оригиналов ogg/flac на диске нет, восстановление тегов возможно только из **плана** или из `backup_raw/`.
|
||||
- ⚠️ macOS-мусор `._*` (AppleDouble) попадает в `find` как «m4a» — **исключать** (`if f.startswith("._"): continue`), иначе счёт файлов завышен вдвое (26 vs 13).
|
||||
- 🔴 **Длительные операции на NAS (бэкап/apply) — только в фоне** (`background=true` + `notify_on_complete`): foreground-таймаут 600 с обрывает ssh, операция продолжается «осиротевшей».<br>Два независимых долгих процесса можно вести **параллельно** (ЧТЕНИЕ тегов + КОПИРОВАНИЕ файлов не конфликтуют) — Alex это ожидает.
|
||||
- 🔴 **Апрув-таймаут ≠ «не смог».** Если команда заблокирована («Approval request timed out — do NOT retry»), это значит **Alex не увидел промпт**, а не запрет. Ассистент обязан: (1) сообщить точную формулировку блокировки, (2) **не повторять вслепую**, (3) попросить подтверждение. Alex реагирует жёстко на «почему не сделал?» — нужно сразу объяснять механику блокировки, а не оправдываться.
|
||||
@@ -620,6 +778,11 @@ docker compose --profile tools run --rm \
|
||||
- 🔴 **НЕ переизобретать работу, которую Alex уже дал.** Если данные/список уже есть (лог verify, CSV) — **взять их**, а не писать новый массовый пересчёт. Alex: «У тебя же уже есть этот список зачем пересчет». Лишний шаг = потеря времени + раздражение.
|
||||
- 🔴 **Вопрос «Чини» = план + выполнение, не новые вопросы.** Alex сказал «Чини» — значит надо чинить. Но перед **деструктивной частью** (удаление TRCK, удаление файлов) — план и подтверждение обязательны (правило стопа/безопасности выше по приоритету, чем краткость).
|
||||
- 🔴 **Не списывать расхождения «всё ок» без разбора.** Alex ловит такие выводы: «ты только что говоришь что там была неправильная кодировка и неверные теги но тут же говоришь что это ок». Если в данных есть mojibake/неверные значения — это **дефект**, а не норма. Сначала классификация по группам и файлам, потом вывод.
|
||||
- 🔴🔴🔴 **`ID3(path).save(other_path)` УНИЧТОЖАЕТ АУДИО.** `ID3()` — парсер **тег-блока**, он не знает про MPEG-поток: `save()` сериализует **только фреймы ID3**, а `os.replace` затирает оригинал → файл становится ~1 КБ. Проверено: `fix40.py` убил 40 MP3 (2–10 МБ → 1.1 КБ), `ffprobe` → `Failed to find two consecutive MPEG audio frames`. **Правильно:** `ID3(p).save(p)` в тот же путь, либо `MP3(p).save()`, либо `shutil.copy2(p, tmp)` перед правкой. **Ключевое: `save()` в СУЩЕСТВУЮЩИЙ путь** — тогда mutagen копирует поток. Спасение в этом кейсе: `backup_conv40/` (копии до записи) + `backup_raw/` (оригиналы ogg/flac).
|
||||
- 🔴 **«40/40 записано, 0 ошибок» — НЕ приёмка.** Отчёт `save()` говорит только о записи тега, **не о целостности аудио**. Единственная приёмка: `MP3(p).info.length > 0` (mutagen) **и** `ffprobe -select_streams a:0 -show_entries stream=codec_name` → `mp3`. Валидировать **выход**, а не факт выполнения. Отсутствие этой проверки = потеря 40 файлов, выявленная только через независимый скрипт.
|
||||
- 🔴 **`ffprobe -of csv=p=0` + `-show_entries stream=...` возвращает ПУСТО** (баг в `check_conv_tags.py`): проверка `"mp3" not in stdout` даёт ложное «аудио-проблемы» на **всех** файлах. Использовать `-of json` и парсить `streams[0].codec_name` — тогда ошибка видна реально.
|
||||
- 🔴 **Скрипт-чекер ПЕРЕЗАПИСЫВАЕТ свой отчёт** — `check_conv_tags.py` при втором прогоне заменил `conv-tags-report.json`: ключ `empty` (40) стал пустым, те же 40 ушли в `bad`. Далее `probe40.py` читал уже пустой список → «файлов: 0», ложная пустота. **Правило: при разборе смотреть, когда отчёт создан и не запускать чекер повторно «для верности»; источник истины — `convert-probe.json` (`ok: 41`) и `fix40-result.json`.**
|
||||
- 🔴 **Проверять факт ДО вывода «данные потеряны»:** ущерб 40 файлов был бы «невосстановим», если бы не `backup_conv40/`. Правило: **перед любой массовой правкой тегов — бэкап копией файлов** (не только ID3-дампа), именно в отдельную папку `backup_<что>`. Это спасло сессию.
|
||||
|
||||
## Связано
|
||||
|
||||
|
||||
Reference in new issue
Block a user