[2026-09-26] eagle: personal/tech/id3-tag-repair.md personal/tech/music-tags-final-review.md personal/tech/music-tags-repair-truenas.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-26 20:21:32 +06:00
1 parent d7a3e90631
commit 0aacbc1226
3 files changed
+140 -12

No files matched your search

+22 -1
View File
@@ -72,7 +72,6 @@ TPE1 enc=0 payload = 00 FF FE 53 74 65 65 6C 20 44 65 6C 75 78 65
Потеряно 343 файла. → Читать ID3 **напрямую из байтов** (`id3_direct.py`).
### 4. Ноты из CSV — односторонний канал
Правки человека в сгенерированной ноте **не возвращаются** в CSV. Следующая перегенерация их
**затирает**. Лечится только явным переносом (`apply_manual_decisions.py`, список `DECISIONS`).
@@ -120,6 +119,28 @@ os.replace(TMP, SRC)
Плюс: `album` = год (`2007`) или имя папки-плейсхолдера (`NEW (Remix Album)`, `Unreleased Tracks`)
— **это** дефекты (папка протекла в album).
### 7. 🔴 Неидемпотентный батч-скрипт молча портит данные
Сквозная нумерация треков по дискам (Диск N = offset от суммы треков предыдущих) в наивной
версии читала **текущий** `track` и прибавляла offset. Повторный запуск прибавлял снова:
```
прогон 1: Disc 4 track 10 → 60
прогон 2: Disc 4 track 60 → 110 ← ошибки нет, данные уже неверны
```
Три версии скрипта ушли в мусор, дважды откатывались из бэкапа.
**Правило:** цель считать **от исходного значения** (номер в **имени файла**), не от целевого поля.
Обязательная приёмка — **второй прогон = 0 изменений**. Номер писать как `str(int(x))`,
иначе вернутся `05` vs `5`. По деталям — `music-tags-repair-truenas.md` §«28-я волна».
### 8. 🔴 NFC vs NFD в именах папок — `==` ложь при визуально равных строках
`Принц и нищий (Диск 2)` распался на два альбома: одна папка была precomposed, другая —
с combining `U+0306`. Перед сравнением/группировкой всегда
`unicodedata.normalize("NFC", name)`.
## ⚪ Что НЕ является багом
- **Рукописная транслитерация:** `КняZz`, `EMINЭM`, `Flёur`, `изBESTняк`, `Скаzки`, `Глюкоzа`,
+55 -10
View File
@@ -18,23 +18,44 @@ related:
_Обновлено: 2026-09-26. Источник: `~/Downloads/music-tags/tag-fix-plan.csv`._
> ## ✅ ГОТОВО К АППРУВУ (28-я волна)
> **Построчное чтение завершено: 1–7906 (100%).** 27-я волна закрыла блок 6402–7906.
> 28-я волна добавила **сквозную нумерацию треков по дискам** (9 многодисковых альбомов).
> Все проверки пройдены. Жду слово Alex («go») для бэкапа и применения тегов.
> ## ⚠️ 28-я волна ЗАКРЫТА, 29-я В РАБОТЕ — правки по замечаниям Alex
> **Построчное чтение завершено: 1–7906 (100%).**
> 28-я волна: **сквозная нумерация треков по дискам** (9 многодисковых альбомов) — ✅ ГОТОВО.
> 29-я волна: **5 замечаний Alex** (Детский хит капсом, битая строка `яюY`, Benassi 23→2,
> Eminem album, формат CSV-сводок) + 597 треков без `new_track` при номере в имени файла.
> 👉 **Согласованный план 29-й волны ниже.** Подтверждено Alex, но НЕ применено.
> ⚠️ Открытый вопрос: 104 дубля `(album, track)` — копии релизов в разных папках (дедупликация).
> Полный контекст волн — `personal/tech/music-tags-repair-truenas.md`.
## Состав файлов
| Файл | Строк | Что это |
| Файл | Строк | Колонки |
|---|---|---|
| `tag-fix-plan.csv` | 7906 | Главный план правок, 11 колонок |
| `artists-single-track.csv` | 3196 | Разбивка: треки БЕЗ номера (artist/title/album/path) |
| `artists-multi-track.csv` | 4694 | Разбивка: треки С номером (artist/album/track/title/path) |
| `tag-fix-plan.csv` | 7906 | 11 колонок (главный план) |
| `artists-single-track.csv` | 3196 | `artist, title, album, path` ⚠️ см. ниже |
| `artists-multi-track.csv` | 4694 | `artist, album, track, title, path` |
| | **7890** | = сумма сводок (16 строк отфильтрованы: 15 skip + 1 намеренно пустая) |
_Сводки пересобраны в 28-й волне после сквозной нумерации (`rebuild_summaries.py`)._
### 🔴 Проблема формата сводок (замечание Alex, 29-я волна)
**Порядок колонок различается** — это дефект генератора `rebuild_summaries.py`:
в `single` стоит `title` перед `album`, в `multi` — `album` перед `track/title`.
Причина: две разные `writerow()` для одного и того же набора данных.
**Решение (согласовано):** единый порядок в обоих файлах —
`artist, album, track, title, path`. Треки без номера просто имеют пустой `track`.
### 🔴 «single-track» ≠ «трек без номера» (замечание Alex)
Метка `single` сейчас означает **пустой `new_track`**, а не отсутствие номера.
Из **3197** строк без `new_track` у **597** номер есть прямо в имени файла
(`189_3 Doors Down - It's Not My Time.mp3`, `Ace Of Base - Happy Nation.mp3`).
Поэтому в «single» попадали нормальные альбомные исполнители — Alex заметил это как «wtf».
Крупные группы: `Sbornik_Novij_Rok` (199), `Infected Mushroom` (60), `!Radio` (53),
Агата Кристи (41), Пилот (30), Savant (29), Захар Май (27), Benassi Bros (25).
**Решение:** извлечь номер из имени файла (в `track`), треки реально без номера оставить пустыми.
## Действия
@@ -86,7 +107,31 @@ _Сводки пересобраны в 28-й волне после сквозн
- 46 уникальных альбомов после снятия года — с официальными треклистами не сверялись
(кроме Pushnoy, где регрессия найдена и исправлена).
## Сквозная нумерация по дискам (28-я волна)
## 📋 План 29-й волны (согласован Alex 2026-09-26, НЕ применён)
Пять пунктов из замечаний Alex + находка про `new_track`.
| # | Что | Объём | Решение |
|---|---|---|---|
| **A** | `!Детские/Детский хит` — title капсом (`HOLLYWOOD` и т.п.) | 156 строк | привести title к регистру `Как в предложении` (только где `isupper()`) |
| **B** | Строка 3260: `new_album='яюY'` — мусор + пустой artist | 1 строка | album из пути (`Sbornik_Novij_Rok`), мусор `яюY` вычистить |
| **C** | Benassi разъехался на 23 написания | 23 → 2 | **BENASSI BROS** (feat. Sandy и др.) → `Benassi Bros`; **BENNY BENASSI** → `Benny Benassi` (сольные/`vs.`) |
| **D** | Eminem: 15 треков `NEW (Remix Album)` без album | 15 строк | album → **`The Remix Album`** (подтверждено Alex) |
| **E** | Формат CSV-сводок: разный порядок колонок | 2 файла | единый `artist, album, track, title, path` в обоих (`rebuild_summaries.py`) |
| **F** | 597 треков: номер в имени файла, но `new_track` пуст → падают в «single» | 597 строк | извлечь номер в `track` из имени файла |
### Пояснение по B (номер сборника)
В `Sbornik_Novij_Rok` (199 треков) номер в начале файла — это **номер сборника**,
не номер трека на альбоме. **Решение Alex: ставить его в `track`** (то есть `track` = позиция
в сборнике). Это общий случай для всех `Sbornik_*` папок.
### Питфолл E (не повторить)
Порядок колонок в сводках задаётся **двумя разными `writerow()`**. Правку делать **обеими**
ветками сразу, иначе рассинхрон вернётся при следующей пересборке.
## Сквозная нумерация по дискам (28-я волна, ✅ применено)
9 многодисковых альбомов перенумерованы непрерывно (Диск N = offset от суммы треков
предыдущих дисков). Все — без дыр и дублей:
+63 -1
View File
@@ -3232,7 +3232,6 @@ if cyr < 2: # одна буква — совпадение
> 🔴 **Решение Alex:** дубли **НЕ трогать** — разберёт сам. Папки не удалять, не переименовывать.
## ПЛАН (согласован Alex 2026-09-26)
| # | Шаг | Статус |
|---|---|---|
| 1 | **Инвентаризация** read-only: `ffprobe` по 7906 файлам → CSV | ✅ **ГОТОВО (v2)** — `tag_inventory2.py`, `probe_failed`=0 |
@@ -3441,6 +3440,69 @@ Alex (третья волна): **«Давай ка дорогой пройди
> а чтение глазами каждой строки — «очевидные», которые видны пользователю с первого взгляда.
> Все три уровня нужны.
## 28-я волна — сквозная нумерация по дискам (+ питфолл идемпотентности)
**Задача (Alex):** «там где ты убрал номера дисков из альбомов (сказки) теперь треки
дублированы, нужно для вторых дисков сквозную нумерацию».
Причина: снятие `(Диск N)` из `album` сводило многодисковые релизы в один альбом, но нумерация
треков в каждом диске начиналась с `1` → 179 дублированных пар `(album, track)`.
**Итог:** 9 альбомов перенумерованы непрерывно (Диск N = offset от суммы треков предыдущих
дисков), 119 изменений, без дыр и дублей:
`Final Fantasy VI` 1–74 · `изBESTняк` 1–40 · `Алиса в Стране Чудес` 1–34 (2 диска + транслит-папка) ·
`Трое из Простоквашино` 1–20 · `Лунапарк` 1–19 · `Live Aus Berlin` 1–18 ·
`Принц и нищий` 1–14 · `Малыш и Карлсон` 1–6 · `Остров сокровищ` 1–6.
### 🔴 Питфолл №1: неидемпотентный скрипт silently портит данные
Три версии скрипта подряд (`fix_disc_tracks.py`, `fix_disc_tracks2.py`, `fix_disc_auto.py`)
считали цель от **текущего** поля `track`:
```
прогон 1: Disc 4 track 10 → 60
прогон 2: Disc 4 track 60 → 110 ← молчаливая порча, ошибки нет
```
`fix_disc_tracks2.py` был запущен дважды в одной команде → `Disc 4` уехал на `101`.
Оба раза откат делался из бэкапа (`cp bak-201011-disc tag-fix-plan.csv`).
**Фикс (`fix_disc_final.py`):** цель считается **от исходного номера из имени файла**,
а не от текущего значения. **Приёмка: второй прогон = 0 изменений** ✓.
**Общее правило:** любой батч-скрипт обязан (а) считать цель от источника
(имя файла), не от целевого поля; (б) писать номер как `str(int(x))`, иначе вернутся `05` vs `5`;
(в) при повторном запуске выдавать «nothing to do».
### 🔴 Питфолл №2: NFC vs NFD в именах папок
`Принц и нищий (Диск 2)` разъехался на **два альбома**, потому что одна папка использовала
precomposed-символ, а другая — combining `U+0306`. Визуально идентичны, `==` даёт `False`.
Лечится `unicodedata.normalize("NFC", name)` перед сравнением/группировкой.
### Открытый вопрос (к Alex)
**104 пары `(album, track)`** — это НЕ диски, а одни и те же релизы в двух папках
(`Город дорог` 15, `Мат без электричества` 13, `Живая коллекция` 13, `Второй Магаданский` 11,
`Lies For The Liars` 11, `Ч\Б` 8, `Аристократия помойки 1–4`, `Война и мир`, `СМС`, `MCMXC A.D.`).
Решается **дедупликацией файлов**, не нумерацией. Ждём решения Alex.
## 29-я волна — замечания Alex (согласовано, НЕ применено)
Приёмка Alex по сводкам выявила 5 пунктов; детальная таблица (A–F) —
`personal/tech/music-tags-final-review.md` §«План 29-й волны». Кратко:
1. `!Детские/Детский хит` — 156 title капсом → привести к регистру.
2. Строка 3260 — `new_album='яюY'` (мусор), пустой artist, album брать из пути.
3. **Benassi 23 написания → 2:** `Benassi Bros` (feat. Sandy) и `Benny Benassi` (сольные/vs.).
4. **Eminem `NEW (Remix Album)`** (15 треков, папка протекла в album) → album `The Remix Album`.
5. **Формат сводок:** разный порядок колонок в `artists-single-track.csv` vs `multi` → единый
`artist, album, track, title, path`; править **обе** ветки `rebuild_summaries.py`.
Плюс находка: **597 строк** имеют номер в имени файла, но пустой `new_track` → падали в
«single-track». Номер сборника (`Sbornik_*`, 199 строк) **ставится в `track`** — решение Alex.
## Связанные заметки
- [[family/how-to/jellyfin-config]] — живой инстанс Jellyfin на Taiga, план добавления Music-библиотеки