[2026-09-26] music tags: strip year prefixes from 504 albums, fix Knorkator umlauts, truncated titles
This commit is contained in:
1 parent
a56483983a
commit
b368c6e115
3 files changed
+141
-11
No files matched your search
@@ -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)
|
||||
|
||||
@@ -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`
|
||||
|
||||
Reference in new issue
Block a user