[2026-09-26] music tags: strip year prefixes from 504 albums, fix Knorkator umlauts, truncated titles

This commit is contained in:
Alexey Martemyanov committed 2026-09-26 19:47:54 +06:00
1 parent a56483983a
commit b368c6e115
3 files changed
+141 -11

No files matched your search

@@ -3,7 +3,7 @@ title: Music Tags — Review
type: task
namespace: personal
tags: [music, jellyfin, tags, review, truenas]
status: awaiting-approval
status: in-progress
updated: '2026-09-26'
---
@@ -11,12 +11,28 @@ updated: '2026-09-26'
**К правке:** 6773 файлов · **без изменений:** 1133
> **✅ 21-я волна (2026-09-26): CSV ГОТОВ. 7907 строк, 0 битых. Ждём «go» на применение.**
> Разобраны блоки `Pushnoy.ru`/`Александр Пушной` (13 строк cur_artist) и `Infected Mushroom`
> (7 реальных дефектов: обрезки `Deeply Disturbed (Kobi Shalti-`, `elation station (beat hackers`;
> title = имя файла `Infected Mushoom - Saeed/Red Filter`; `Eyal Shechter` → title `Raash`;
> `U R So Fucked (GANDI Remix)`). 14 файлов `._*` (AppleDouble) помечены `skip`.
> Детали — [[personal/tech/music-tags-repair-truenas|tech/music-tags-repair-truenas]], 21-я волна.
> **⚠️ 22-я волна (2026-09-26): CSV НЕ ГОТОВ.** Alex спросил: «Ты прошёл вручную через весь список
> и АБСОЛЮТНО всё корректно? Нет дублей с разным написанием?» — честный ответ был **НЕТ**. Проверка
> нашла ещё ~900 дефектных строк сверх «зелёных» чекеров.
>
> Исправлено: **43 группы дублей артистов** (`Король и шут`/`Король и Шут`, `Земфира`/`земфира`,
> `Чиж & Cо` с латинской C, `Руки Вверх`/`Руки в Верх`) — 85 правок, свелось к 0 групп;
> **220 строк** с артистом-префиксом в title (`Король и Шут - Лесник` → `Лесник`,
> `Muse / Dead Star` → `Dead Star`); `album` case (`Король и шут-1` → `Король и Шут`), 36 строк.
>
> 🔴 **Два урока:** (1) «файл прошёл чекеры» ≠ «файл готов» — чекеры не видят дубли/префиксы;
> (2) **скрипт правки ОБРЕЗАЛ CSV до 4640 строк** (открыл источник на запись, упал на середине).
> Восстановлено из бэкапа. Теперь только схема tmp-файл + ревалидация + `os.replace`.
>
> **⏭ Осталось до «go»:** `album='2007'` (10), `album`=папка-плейсхолдер `NEW (Remix Album)` /
> `Unreleased Tracks` (~109), глазами не пройдены ~5000 строк прочих исполнителей.
> Детали и детекторы — [[personal/tech/music-tags-repair-truenas|tech/music-tags-repair-truenas]], 22-я волна.
> **✅ 21-я волна (2026-09-26):** разобраны блоки `Pushnoy.ru`/`Александр Пушной` (13 строк cur_artist)
> и `Infected Mushroom` (7 реальных дефектов: обрезки `Deeply Disturbed (Kobi Shalti-`,
> `elation station (beat hackers`; title = имя файла `Infected Mushoom - Saeed/Red Filter`;
> `Eyal Shechter` → title `Raash`; `U R So Fucked (GANDI Remix)`). 14 файлов `._*` (AppleDouble)
> помечены `skip`.
> [!warning] Ничего не изменено
> Это план. Ставь галочки, что одобряешь. После «go» — бэкап в `Music/backup/` и применение.
+46 -1
View File
@@ -76,6 +76,50 @@ TPE1 enc=0 payload = 00 FF FE 53 74 65 65 6C 20 44 65 6C 75 78 65
Правки человека в сгенерированной ноте **не возвращаются** в CSV. Следующая перегенерация их
**затирает**. Лечится только явным переносом (`apply_manual_decisions.py`, список `DECISIONS`).
### 5. 🔴 Скрипт ОБРЕЗАЛ CSV до 4640 строк (22-я волна)
`open(SRC, "w")` + `writerows(rows)` → падение на середине на битой строке
(`ValueError: dict contains fields not in fieldnames: None`) → **файл потерял половину**.
Режим `w` обрезает при открытии, всё недописанное исчезло. Восстановлено из `bak-194449-prefix`.
**Обязательная схема правки CSV (tmp + ревалидация + `os.replace`):**
```python
with open(SRC, encoding="utf-8-sig", newline="") as f:
rd = csv.DictReader(f); fields = rd.fieldnames; rows = list(rd)
bad = [i for i, r in enumerate(rows, 2) if None in r or len(r) != len(fields)]
if bad: raise SystemExit(f"ABORT: malformed source {bad[:10]}") # валидация ВХОДА
for r in rows: transform(r)
TMP = SRC + ".tmp"
with open(TMP, "w", encoding="utf-8-sig", newline="") as f:
w = csv.DictWriter(f, fieldnames=fields, quoting=csv.QUOTE_MINIMAL, lineterminator="\r\n")
w.writeheader(); w.writerows(rows)
# ревалидация tmp ТЕМИ ЖЕ проверками, затем атомарная подмена
os.replace(TMP, SRC)
```
Перед каждым запуском: `cp tag-fix-plan.csv bak-$(date +%H%M%S)-<tag>`.
Битые строки — **симптом**, а не причина: незакавыченная запятая (`Танцуй, Рублёвка`)
или лишние пробелы (`Áàíä'Ýðîñ ,2005 `) создают поля сверх заголовка. Чинить руками ДО скриптов.
### 6. 🔴 «Чекеры зелёные» ≠ «файл готов» (22-я волна, ~900 строк)
0 битых строк + 0 битых кавычек прошли — а в файле оставалось **~900** дефектных строк:
43 группы дублей написания артиста (676 строк), 220 строк с артистом-префиксом в title,
36 album с чужой раскладкой. Нашлось только после вопроса Alex «а дубли написаний?».
Детекторы, которые ОБЯЗАТЕЛЬНО гонять перед заявлением «готово»:
| Детектор | Ловит | Нюанс |
|---|---|---|
| Дубли артистов | `Король и шут`/`Король и Шут`, `Земфира`/`земфира` | нормализовать: lowercase → `ё→е` → маппинг латиница→кириллица-омоглифы (`C→С`, `o→о`…) |
| Артист-префикс в title | `Король и Шут - Лесник`, `Muse / Dead Star` | срезать ТОЛЬКО если после артиста идёт разделитель `[-–—/]`, иначе съест `Жуки Комарики`, `Тест Сабвуфера №2` |
| case/disc в album | `Король и шут-1` при artist `Король и Шут` | явный `ALBUM_MAP` |
| `album == artist` | **в основном ЛЕГИТИМНО** | одноимённые альбомы; НЕ массово менять |
Плюс: `album` = год (`2007`) или имя папки-плейсхолдера (`NEW (Remix Album)`, `Unreleased Tracks`)
— **это** дефекты (папка протекла в album).
## ⚪ Что НЕ является багом
- **Рукописная транслитерация:** `КняZz`, `EMINЭM`, `Flёur`, `изBESTняк`, `Скаzки`, `Глюкоzа`,
@@ -84,7 +128,8 @@ TPE1 enc=0 payload = 00 FF FE 53 74 65 65 6C 20 44 65 6C 75 78 65
`КняZz` оставил).
- **`ÿþ` в середине слова:** `Òåðÿþ ñâåò → Теряю свет`, `Ïîçäðàâëÿþ → Поздравляю` — корректная
CP1251-конверсия. Ложное срабатывание grep. Различать **по позиции**: начало значения ≠ середина слова.
- **`album == artist`** (101 файл) — одноимённые альбомы (`Рубль/Рубль`, `Би-2/2001 Би-2`).
- **`album == artist`** (138 файлов) — одноимённые альбомы (`Рубль/Рубль`, `Би-2`, `Земфира` (1999),
`НТЛ`, `Краденое Солнце`). Законно — **не менять без сверки дискографии**.
- **`numeric_title`** (15 файлов) — `007` у Ленинграда, `48` у Пилота. Законно.
## 🚫 Запреты (жёсткие, от Alex)
+72 -3
View File
@@ -4,7 +4,7 @@ created: '2026-09-26'
updated: '2026-09-26'
type: tech
namespace: personal
wave: 21
wave: 22
tags:
- music
- jellyfin
@@ -20,8 +20,77 @@ related:
# Music Tags Repair — TrueNAS (7906 файлов)
> **Статус: ДВАДЦАТЬ ПЕРВАЯ ВОЛНА (2026-09-26). CSV ГОТОВ, 0 БИТЫХ СТРОК, 7907 строк.
> Ждём «go» на применение.**
> **Статус: ДВАДЦАТЬ ВТОРАЯ ВОЛНА (2026-09-26). CSV: 7906 строк, 0 БИТЫХ, 0 ДУБЛЕЙ АРТИСТОВ.
> НЕ ГОТОВ к применению — остались неразобранные классы (см. «ОСТАЛОСЬ»).**
> ✅ **НОВОЕ (22-я волна) — ДЕДУП АРТИСТОВ + АРТИСТ В TITLE + case в album. Поймано триггером
> Alex: «Ты прошел вручную через весь список и абсолютно все выглядит корректно??? Нет дублей
> с разным написанием?» — ответ был НЕТ, и проверка нашла ещё ~900 дефектных строк.**
> 🔴 **ГЛАВНЫЙ УРОК 22-й ВОЛНЫ: «файл прошёл чекеры» ≠ «файл готов».** Чекеры ловят кодировку и
> пустые поля, но НЕ ловят (а) дубли написаний артиста, (б) артиста-префикс в title,
> (в) капс/диск-суффиксы в album. Перед любым заявлением «всё готово» — прогонять эти 4 детектора
> (скрипты ниже), иначе Alex ловит это вопросом и это выглядит как враньё.
>
> **Что исправлено в 22-й волне:**
> | Класс | Строк | Скрипт | Результат |
> |---|---|---|---|
> | Дубли написания артиста, 43 группы | 85 правок | `dedup_artists.py` (карта `CANON`) | 0 групп дублей |
> | Артист-префикс в `new_title` (`Король и Шут - Лесник`) | 220 | `strip_artist_prefix.py` | 21 остаток = ложные |
> | `album` со старой раскладкой (`Король и шут`, `Нечетный воин`) | 36 | `fix_albums.py` (`ALBUM_MAP`) | исправлено |
> | `Король и шут-1` — диск-суффикс в album | 17 | тот же | → `Король и Шут` |
> | `Иванушки Int - Тополиный пух`, `Алексин и группа Колючки` | 2 | вручную `patch` | исправлено |
> | **Структурные дефекты CSV** (`Áàíä'Ýðîñ ,2005 ` — незакавыченные пробелы; `Танцуй, Рублёвка` — запятая без кавычек) | 2 | вручную | это они РОНЯЛИ скрипты |
>
> 🔴 **КРИТИЧНЫЙ УРОК: СКРИПТ ОБРЕЗАЛ CSV ДО 4640 СТРОК.** `strip_artist_prefix.py` (v1) открыл
> `tag-fix-plan.csv` **на запись** и упал на середине (`ValueError: dict contains fields not in
> fieldnames: None` — строка с лишними полями) → файл потерял половину. Восстановлено `cp` из
> `bak-194449-prefix`. **Правило: НИКОГДА не открывать `tag-fix-plan.csv` на запись напрямую.**
> Схема безопасного скрипта (v2, `strip_artist_prefix.py` / `fix_albums.py`):
> 1) `csv.DictReader` читает источник; 2) валидация входа (нет `None`-ключей, все строки = `len(fields)`);
> 3) запись в `SRC + ".tmp"`; 4) **ревалидация tmp** теми же проверками; 5) `os.replace(tmp, src)`.
> Перед каждым запуском — `cp tag-fix-plan.csv bak-$(date +%H%M%S)-<tag>`.
>
> **⏭ ОСТАЛОСЬ (НЕ разобрано, требует прохода):**
> - `album = '2007'` (год вместо альбома) — 10 строк, блок Pushnoy `2007/`.
> - `album` = имя папки-плейсхолдера: `NEW (Remix Album)` (16, Eminem/50cent),
> `Unreleased Tracks` (20+, Infected Mushroom), `The Rest, The Unreleased! THE`,
> `Disko Partizani` — всего ~109 строк.
> - title у ~5000 строк прочих исполнителей глазами НЕ проходился.
> - 138 строк `album == artist` — БОЛЬШИНСТВО ЛЕГИТИМНЫ (одноимённые альбомы: `Земфира` (1999),
> `Би-2` (дебютник), `НТЛ`, `Рубль`, `Краденое Солнце`) — НЕ трогать без проверки дискографии.
> - Ложные срабатывания детектора «артист в title» (21) — там нет разделителя-тире,
> это части названий: `Тест Сабвуфера №2`, `Жуки Комарики`, `Глюк'oza nostra`,
> `Savant Fuck Nexus`, `Земфира (DJ Shek Remix)`, `Ленинград (Bonus Track)`.
> - `allcaps` title в `!Детские/Детский хит` (156 строк) — **ЛОЖНОЕ срабатывание**: капс
> в исходных файлах, artist пустой НАМЕРЕННО (решение зафиксировано в
> `music-tags-review.md`, «Класс 3»), album `Детский Хит Сезона` остаётся.
>
> **📎 Детекторы (read-only, гонять перед заявлением «готово»):**
> ```bash
> cd ~/Downloads/music-tags
> # дубли артистов (нормализация латиница→кириллица-омоглифы)
> python3 - <<'EOF'
> import csv, collections
> rows=list(csv.DictReader(open('tag-fix-plan.csv',encoding='utf-8-sig',newline='')))
> def norm(s):
> s=s.lower().replace('ё','е')
> for x,y in [('fl','фл'),('f','ф'),('e','е'),('a','а'),('o','о'),('p','р'),
> ('c','с'),('x','х'),('y','у'),('k','к'),('m','м'),('t','т'),('h','н'),('b','в')]:
> s=s.replace(x,y)
> return ''.join(ch for ch in s if ch.isalnum())
> g=collections.defaultdict(list)
> for k,v in collections.Counter((r['new_artist'] or '').strip()
> for r in rows if (r['new_artist'] or '').strip()).items(): g[norm(k)].append(k)
> print(sum(1 for v in g.values() if len(v)>1), "групп дублей")
> EOF
> ```
> Плюс: `title` начинается с `artist` + разделитель `[-–—/]` → префикс;
> `album` в `ALBUM_MAP`-подобных вариантах; незакавыченные запятые/пробелы в полях.
>
> **Бэкапы 22-й волны:** `bak-194352-dedup`, `bak-194449-prefix`, `bak-<HHMMSS>-prefix2`,
> `bak-<HHMMSS>-albumfix` в `~/Downloads/music-tags/`.
>
> ---
>
> ✅ **НОВОЕ (21-я волна) — блок `Pushnoy.ru`/`Александр Пушной` + `Infected Mushroom`:**
> - `Pushnoy-ru_` в title — это НЕ сайт-вода, а **префикс-пространство имён файлов** (транслит
> имени файла); артист везде `Александр Пушной`. `Pushnoy.ru`/`Pushnoy-ru` в `cur_artist`