diff --git a/personal/tech/id3-tag-repair.md b/personal/tech/id3-tag-repair.md index 4f62a9ee..bbf40a47 100755 --- a/personal/tech/id3-tag-repair.md +++ b/personal/tech/id3-tag-repair.md @@ -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а`, diff --git a/personal/tech/music-tags-final-review.md b/personal/tech/music-tags-final-review.md index 92ccc97d..16eb4547 100755 --- a/personal/tech/music-tags-final-review.md +++ b/personal/tech/music-tags-final-review.md @@ -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 от суммы треков предыдущих дисков). Все — без дыр и дублей: diff --git a/personal/tech/music-tags-repair-truenas.md b/personal/tech/music-tags-repair-truenas.md index be7cb8c5..f81c833e 100755 --- a/personal/tech/music-tags-repair-truenas.md +++ b/personal/tech/music-tags-repair-truenas.md @@ -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-библиотеки