[2026-09-27] eagle: personal/plans/music-artist-albumartist-repair.md personal/plans/tagwriter-container.md personal/tech/id3-tag-repair.md
This commit is contained in:
1 parent
8ce2b1e6a4
commit
6912c41874
3 files changed
+294
-5
No files matched your search
@@ -4,7 +4,7 @@ created: '2026-09-27'
|
||||
updated: '2026-09-27'
|
||||
type: plan
|
||||
namespace: personal
|
||||
status: script-v13-id3v1-logic-tested-awaiting-go
|
||||
status: csv-audited-20-artist-defects-awaiting-alex
|
||||
tags:
|
||||
- music
|
||||
- metadata
|
||||
@@ -23,6 +23,27 @@ related:
|
||||
|
||||
# Music `artist` + `albumartist` Repair — правила Alex и ход работ
|
||||
|
||||
> ## 🔴 ИМЕНА CSV ПЕРЕИМЕНОВАНЫ (2026-09-27) — актуальная раскладка
|
||||
>
|
||||
> Все упоминания `APPLY-PLAN-FINAL.csv` / `APPLY-PLAN-FINAL2.csv` **ниже по тексту устарели**.
|
||||
> Актуально (в `~/milestones/music-csv-2026-09-26/` и `~/Downloads/music-tags/`):
|
||||
>
|
||||
> | Было | Стало | Роль |
|
||||
> |---|---|---|
|
||||
> | `APPLY-PLAN-FINAL2.csv` | **`APPLY-PLAN.csv`** | ✅ **БОЕВОЙ, единственный источник истины** (7861 стр, 16 колонок, md5 `245efb55ab1de26cb5172f0754192c3d`) |
|
||||
> | `APPLY-PLAN-FINAL.csv` | `APPLY-PLAN-RAW.csv` | сырой план (track не нормализован) — **НЕ ПРИМЕНЯТЬ** |
|
||||
> | `APPLY-PLAN.csv` (12:02) | `archive/APPLY-PLAN-v0-20260927-1202.csv` | устаревший (нет колонки `track`) |
|
||||
> | `APPLY-PLAN2.csv` (12:03) | `archive/APPLY-PLAN-v0-20260927-1203.csv` | устаревший |
|
||||
>
|
||||
> Снимок каталога перед переименованием: `~/milestones/music-csv-BEFORE-RENAME-20260927-124542.tar.gz`.
|
||||
>
|
||||
> ## 🔴 АУДИТ `new_artist` ВЫЯВИЛ 20 ДЕФЕКТОВ — ПРАВКА ЖДЁТ ALEX
|
||||
>
|
||||
> `'Maria Makarova (organ)'` ×2 (исполнитель вместо композитора — должен быть **Бах**),
|
||||
> `'Классика в современной обработке'` ×3, `'Сказочный сборник'` ×15.
|
||||
> Подробно: [[personal/plans/tagwriter-container]] § 3.5.
|
||||
> **Боевой прогон заблокирован до правки этих 20 файлов.**
|
||||
|
||||
**Задача Alex (2026-09-27):**
|
||||
> «Артиста всем трём где он не указан или указан неправильно, включая детские песни ставишь
|
||||
> в реального артиста. Что не знаешь — ищешь в интернете. Для _всех_ треков.
|
||||
|
||||
@@ -4,7 +4,7 @@ created: '2026-09-26'
|
||||
updated: '2026-09-27'
|
||||
type: plan
|
||||
namespace: personal
|
||||
status: v13-csv-renamed-single-source-of-truth-awaiting-go
|
||||
status: v14-csv-audited-20-defects-awaiting-alex
|
||||
tags:
|
||||
- music
|
||||
- truenas
|
||||
@@ -19,11 +19,12 @@ related:
|
||||
---
|
||||
# tagwriter — применение тегов к MP3 (TrueNAS)
|
||||
|
||||
> 🔴 **СТАТУС: скрипт готов и протестирован. БОЕВОЙ ПРОГОН НЕ ЗАПУЩЕН — ЖДЁТ «go» от Alex.**
|
||||
> 🔴 **СТАТУС: скрипт готов и протестирован. Аудит CSV выявил 20 дефектов в `new_artist`
|
||||
> (§ 3.5) — правка НЕ начата, ждёт решения Alex. БОЕВОЙ ПРОГОН НЕ ЗАПУЩЕН.**
|
||||
|
||||
## 1. Что делает скрипт
|
||||
|
||||
Применяет `APPLY-PLAN-FINAL2.csv` (7861 строка) к MP3 на `/storage/Music`.
|
||||
Применяет `APPLY-PLAN.csv` (7861 строка) к MP3 на `/storage/Music`.
|
||||
Пишет через `mutagen`, **только ID3v2.3-блок** — MPEG-поток не пересжимается.
|
||||
|
||||
### Логика полей — граница по КОЛОНКАМ CSV
|
||||
@@ -119,6 +120,83 @@ changed=no: 213
|
||||
|
||||
MD5: `245efb55ab1de26cb5172f0754192c3d` — сверен между Mac и NAS.
|
||||
|
||||
## 3.5 🔴 АУДИТ CSV — 5 классов дефектов (2026-09-27)
|
||||
|
||||
Alex: «**ты нихуя не проверил csv. давай бэкапь ее и проверяй всю.**»
|
||||
Бэкап снят: `APPLY-PLAN.csv.bak-chk-20260927-124918`.
|
||||
|
||||
Скрипт аудита: `~/Automation/music-tags/audit_artist.py` (8 классов A–H).
|
||||
Результат — **2 реальных класса дефектов, 20 файлов**; остальные 6 классов оказались
|
||||
ложными срабатываниями детектора.
|
||||
|
||||
### A. 🔴 Исполнитель вместо композитора — 2 файла
|
||||
|
||||
```
|
||||
'Maria Makarova (organ)' Бах — Toccata and Fugue in D minor.mp3 ×2
|
||||
```
|
||||
**Мария Макарова — органистка, это ИСПОЛНИТЕЛЬ.** Должно быть `Иоганн Себастьян Бах`.
|
||||
Это ровно тот кейс, из-за которого в прошлой сессии искали источник бага Jellyfin #18162 —
|
||||
тогда нашли `Maria Makarova (organ)` в теге и не поправили.
|
||||
|
||||
### B. 🔴 Название сборника в поле артиста — 18 файлов
|
||||
|
||||
```
|
||||
'Классика в современной обработке' Моцарт — Турецкий марш.mp3 ×3 → по композитору из title
|
||||
'Сказочный сборник' Чебурашка, Котёнок Гав, Айболит, Щелкунчик … ×15
|
||||
```
|
||||
`'Сказочный сборник'` — не артист вообще. Для сказок нужно согласованное значение
|
||||
(кандидаты: `Сказки` — как `albumartist`; пусто; автор конкретной сказки).
|
||||
|
||||
### C / D — чисто ✓
|
||||
|
||||
```
|
||||
служебные значения (Various/Unknown/NA/…): 0
|
||||
URL/спам в артисте: 0
|
||||
```
|
||||
|
||||
### E, F, G, H — ЛОЖНЫЕ срабатывания (CSV корректен)
|
||||
|
||||
| класс | n | почему НЕ дефект |
|
||||
|---|---:|---|
|
||||
| E «артист и альбом пересекаются» | 204 | `artist='Винни-Пух'` + `album='Винни-Пух (2)'` — норма |
|
||||
| F «несколько артистов в одном поле» | 267 | фиты: `'Би-2 feat. Диана Арбенина'` — норма |
|
||||
| G «смешаны алфавиты» | 96 | `'Иванушки International'`, `'Flёur'` — норма |
|
||||
| H «artist == title» | 25 | `'Винни-Пух.mp3'` в сборнике «Винни-Пух» — норма |
|
||||
|
||||
🔴 **Правило: детектор по regex даёт много ложных срабатываний. Каждый класс разбирать
|
||||
по примерам вручную, прежде чем объявлять дефектом.** E/F/G/H — шум, править нельзя.
|
||||
|
||||
### Попутно: дубликаты файлов
|
||||
|
||||
`Моцарт — Турецкий марш.mp3` лежит в **двух** папках (`!Классика` и
|
||||
`z Другое, Enigma, Ленинград/Классика`). Проверено `cmp_audio.py` (хеш аудио без тег-блока):
|
||||
|
||||
```
|
||||
размер: 5993976 = 5993976
|
||||
ID3v2-блок: 87040 = 87040
|
||||
аудио-MD5: ac0f10ba114e196f9e49b99913bb1c27 (одинаков)
|
||||
→ АУДИО ИДЕНТИЧНО — это дубликат
|
||||
```
|
||||
Файлы различались только наличием `TRCK` в v2 (у одного 3 фрейма, у другого 4).
|
||||
План даёт им **разные** `album`/`genre` — для дубликата это дефект плана.
|
||||
|
||||
### Скрипт `cmp_audio.py` — сравнение аудио в обход тегов
|
||||
|
||||
Считает MD5 от аудио-потока: пропускает ID3v2-блок (`10 + synchsafe size`),
|
||||
отрезает ID3v1 (последние 128 байт при `TAG`). Отвечает на вопрос «это дубликат
|
||||
или разные записи», когда размеры совпадают, а MD5 файла различается.
|
||||
|
||||
## ⏳ ОТКРЫТЫЙ ВОПРОС: 20 файлов ждут решения Alex
|
||||
|
||||
| # | Что | Предложено |
|
||||
|---|---|---|
|
||||
| 1 | `Maria Makarova (organ)` ×2 | `Иоганн Себастьян Бах` (или `Бах`) |
|
||||
| 2 | `Классика в современной обработке` ×3 | по композитору из title: Моцарт ×2, Вивальди ×1 |
|
||||
| 3 | `Сказочный сборник` ×15 | `Сказки` / пусто / автор сказки — **нужно решение** |
|
||||
| 4 | `title='Вивальди А. - Зима (рок-обработка)'` | срезать префикс `Вивальди А. - `? |
|
||||
|
||||
Правка боевого CSV **не начата** — ждёт ответа по всем четырём пунктам.
|
||||
|
||||
## 4. Что скрипт НЕ трогает
|
||||
|
||||
`APIC` (обложка) · `TDRC` (год) · `TCOM` · `TXXX` · `USLT` · `TPUB` · `TLEN` ·
|
||||
@@ -215,13 +293,29 @@ docker start -a tw && docker rm -f tw
|
||||
|
||||
| # | Шаг | Статус |
|
||||
|---|---|---|
|
||||
| 1 | Боевой `--apply --verify` по 7861 | 🔴 **ЖДЁТ «go»** |
|
||||
| 0 | 🔴 **Аудит `new_artist` → 20 дефектов, правка CSV** | ⏳ **ЖДЁТ решения Alex (4 вопроса в § 3.5)** |
|
||||
| 1 | Боевой `--apply --verify` по 7861 | 🔴 **ЖДЁТ «go»** (после шага 0) |
|
||||
| 2 | Приёмка Alex в UI (Navidrome/Jellyfin) | ⏳ |
|
||||
| 3 | Откат 40 из `backup_conv40/` | ⏳ |
|
||||
| 4 | Удаление 29 битых/DRM ([[personal/tech/music-broken-toload]]) | ⏳ |
|
||||
| 5 | Удаление бэкапов (`backup/` `backup_raw/` `backup_fix/` `backup_conv40/`) | ⏳ после приёмки |
|
||||
| 6 | Lidarr + Navidrome | ⏳ не начато |
|
||||
|
||||
### Артефакты этой сессии
|
||||
|
||||
| Файл | Назначение |
|
||||
|---|---|
|
||||
| `~/Automation/music-tags/apply_tags_v2.py` | писатель тегов (566 стр, ID3v1-логика) |
|
||||
| `~/Automation/music-tags/normalize_track_csv.py` | вход `APPLY-PLAN-RAW.csv` → выход `APPLY-PLAN.csv` |
|
||||
| `~/Automation/music-tags/audit_artist.py` | аудит 8 классов дефектов в CSV |
|
||||
| `~/Automation/music-tags/test_logic.py` | тесты нормализатора и инвариантов |
|
||||
| `~/Automation/music-tags/snapshot.py` | снимок фреймов файла (ДО/ПОСЛЕ) |
|
||||
| `~/Automation/music-tags/cmp_audio.py` | сравнение аудио-потоков (поиск дубликатов) |
|
||||
| `~/Automation/music-tags/dump_v1.py` | дамп ID3v1 + ID3v2 для сравнения |
|
||||
| `~/Automation/music-tags/find_v1.py` | поиск mp3 с ID3v1 |
|
||||
| `~/Automation/music-tags/Dockerfile.v2` | python:3.11-slim + mutagen 1.47.0 |
|
||||
| `~/Downloads/music-tags/tagwriter/rename_apply_plan.py` | раскладка имён CSV (одноразовый) |
|
||||
|
||||
## 10. Правило процесса (выведено на этой задаче)
|
||||
|
||||
> **Договорённость, не записанная в файл, для агента не существует.**
|
||||
|
||||
@@ -141,6 +141,180 @@ os.replace(TMP, SRC)
|
||||
с combining `U+0306`. Перед сравнением/группировкой всегда
|
||||
`unicodedata.normalize("NFC", name)`.
|
||||
|
||||
## 🖊 Слой ЗАПИСИ — ID3v1 vs ID3v2 (добавлено 2026-09-27)
|
||||
|
||||
Зеркало правил W1–W15 скилла. Полные формулировки — в `id3-tag-repair/SKILL.md`.
|
||||
|
||||
### 🔴 W1 — «управляемый» фрейм, который писатель не умеет СОЗДАТЬ = гарантированная потеря
|
||||
|
||||
Если `TRCK` стоит в списке на удаление (`_managed_frame_ids()`), но ветки его создания
|
||||
в `_frames()` нет — `delall` выполнится, `add` никогда. **Номер трека исчезнет у всех файлов,
|
||||
и источника для восстановления нет** (колонки `track` в плане тоже не было).
|
||||
`delall` + отсутствующий `add` **не даёт ошибки**: apply рапортует успех, verify — `MISMATCH=0`.
|
||||
|
||||
```python
|
||||
assert sorted(_managed_frame_ids()) == sorted(fid for _, fid, _ in FIELD_MAP)
|
||||
```
|
||||
|
||||
### 🔴 W2 — из трёх способов `save()` два уничтожают аудио
|
||||
|
||||
```python
|
||||
tags = ID3(path); tags.save(path, v2_version=3) # ✅ в ТОТ ЖЕ путь
|
||||
m = MP3(path); m["TIT2"] = ...; m.save() # ✅ контейнер-осознанный
|
||||
tags = ID3(path); tags.save("/tmp/out.mp3"); os.replace(...) # 🔴 АУДИО СТЁРТО
|
||||
```
|
||||
|
||||
`ID3()` — парсер **тег-блока**, не файла: `save(other_path)` сериализует только фреймы.
|
||||
Файлы 2–10 МБ → ~1 КБ. Поймано на 40 файлах, отчёт был «40/40 записано, 0 ошибок».
|
||||
|
||||
### 🔴 W3 — ID3-писатель по не-MP3 разрушает контейнер
|
||||
|
||||
`ID3()` на `.m4a`/`.wma` → `ID3NoHeaderError` → `except: tags = ID3()` → запись ID3 **поверх**
|
||||
контейнера: первые байты `49 44 33` вместо `ftyp`/ASF → `moov atom not found`. Непоправимо
|
||||
(в `backup_raw/` те же байты — битые до сессии). Испорчено 16 файлов. Фильтровать `.mp3`.
|
||||
|
||||
### 🔴 W4 — `delall` ВНЕ проверки «значение не пусто»
|
||||
|
||||
```python
|
||||
# ❌ «очистить поле» ничего не делает при пустом новом значении
|
||||
if (r.get("new_artist") or "").strip(): out.append(TPE1(...))
|
||||
for fr in _frames(r): tags.delall(...); tags.add(fr) # пустой список → цикл не идёт
|
||||
# ✅ удаление безусловно, запись условна
|
||||
for fid in _managed_frame_ids(): tags.delall(fid)
|
||||
for fr in _frames(r): tags.add(fr)
|
||||
```
|
||||
|
||||
### 🔴 W5 — ID3v1 невидим для `ID3()` и переживает `save()`, перекрывая v2
|
||||
|
||||
`ID3()` читает **только** v2-блок в начале. ID3v1 — отдельная запись 128 байт в **конце**
|
||||
(`TAG` + title/artist/album по 30 + year 4 + comment 30 + genre 1). `save()` пишет v2 и
|
||||
**v1 не трогает**. В библиотеке — **1099 из 2004 файлов** с v1, её payload — latin-1 с обрезкой:
|
||||
|
||||
```
|
||||
v1 title = '????? ????????? ??????? (?????' ← кириллица уничтожена
|
||||
v2 TIT2 = 'Танец маленьких лебедей (Лебединое озеро)'
|
||||
v1 title = 'J.S. Bach: Toccata and Fugue (' ← обрезано на 30 байтах
|
||||
```
|
||||
|
||||
Плеер, читающий v1 первым, показывает мусор. **Фикс: `save(path, v1=0, v2_version=3)`.**
|
||||
|
||||
### 🔴 W6 — probe инварианта обязан проходить собственные валидаторы
|
||||
|
||||
```python
|
||||
probe = {k: "x" for k, _, _ in FIELD_MAP} # 🔴 "x" невалиден для номера трека
|
||||
# normalize_track("x") -> "" -> TRCK не создаётся -> ЛОЖНЫЙ ABORT
|
||||
```
|
||||
|
||||
Ложный ABORT отключают — и защита исчезает. Для числовых полей probe = `"1"`.
|
||||
|
||||
### 🔴 W7 — граница «из CSV» / «из файла» проходит по КОЛОНКАМ CSV
|
||||
|
||||
```
|
||||
колонки CSV → значение ТОЛЬКО из CSV. Пусто ⇒ УДАЛИТЬ тег. НЕ подставлять старое.
|
||||
фреймы вне CSV → из ID3v2; если там нет — из ID3v1
|
||||
ID3v1 → удалить после записи
|
||||
```
|
||||
|
||||
ID3v1 хранит 6 полей; **4 дублируют управляемые колонки** и игнорируются. Переносить стоит
|
||||
только **year → TDRC** и **comment → COMM**, и лишь когда в v2 их нет.
|
||||
|
||||
### 🔴 W8 — год в v1 бывает бинарным мусором
|
||||
|
||||
Реальное значение: `year = '\x00\x00\x000'`. Без валидации уедет в `TDRC` как есть.
|
||||
Чистить NUL/пробелы **с двух сторон**, отбрасывать управляющие символы,
|
||||
требовать `re.fullmatch(r"\d{4}", year)`.
|
||||
|
||||
### 🔴 W9 — нормализатору нужны тесты на ведущие нули
|
||||
|
||||
`^[1-9]\d{0,3}$` выглядит разумно и **молча отвергает `095`** → 133 файла получили бы
|
||||
**пустой** TRCK. Отбрасывать также `0`, `03/`, `/10`, `N/M` с нулём, спам-строки
|
||||
(в колонке трека был найден `P@X@Tu*HCK*u`). Верно: `^\d{1,4}(/\d{1,4})?$` + `int()`.
|
||||
|
||||
### 🔴 W10–W13 — процесс
|
||||
|
||||
| # | Правило |
|
||||
|---|---|
|
||||
| **W10** | Скрипт-фиксер НЕ должен перезаписывать файл, который сам же правит — повторный прогон затрёт правки. Разделять: CSV-нормализатор + точечные `patch`. |
|
||||
| **W11** | Тест обязан **импортировать** код, а не повторять его — копия `normalize_track()` продолжала проходить после фикса (2 ложных FAIL). |
|
||||
| **W12** | Образ содержит **свою** копию скрипта: `docker cp` в смонтированный каталог не обновляет `/app/script.py` внутри образа. Сигнатура: `AttributeError: no attribute '_read_id3v1'` после успешной заливки. |
|
||||
| **W13** | Договорённость только в чате **не существует** — вердикт Alex: «это неоднократно». Флаг, колонка, решение → в файл **в том же ходе**. |
|
||||
|
||||
### 🔴 W14 — MD5 всего файла бесполезен для поиска дубликатов; хешировать АУДИО
|
||||
|
||||
Две копии трека в разных папках: размер одинаков, **MD5 файла разный** (блоки тегов разные —
|
||||
у одной есть `TRCK`). Хешировать аудио-поток: пропустить v2-блок в начале, отрезать v1 в конце.
|
||||
|
||||
```python
|
||||
def audio_hash(path):
|
||||
size = os.path.getsize(path)
|
||||
with open(path, "rb") as f:
|
||||
head = f.read(10); start = 0
|
||||
if head[:3] == b"ID3": # пропуск v2 (synchsafe size)
|
||||
s = ((head[6] & 0x7F) << 21) | ((head[7] & 0x7F) << 14) | \
|
||||
((head[8] & 0x7F) << 7) | (head[9] & 0x7F)
|
||||
start = 10 + s
|
||||
end = size
|
||||
f.seek(size - 128)
|
||||
if f.read(3) == b"TAG": end = size - 128 # отрезать v1
|
||||
f.seek(start); h, rem = hashlib.md5(), end - start
|
||||
while rem > 0:
|
||||
chunk = f.read(min(1 << 20, rem))
|
||||
if not chunk: break
|
||||
h.update(chunk); rem -= len(chunk)
|
||||
return h.hexdigest()
|
||||
```
|
||||
|
||||
Проверено: `ac0f10ba114e196f9e49b99913bb1c27` для **обеих** копий → истинный дубликат.
|
||||
**Для плана это важно:** дубликат, получающий **разные** `album`/`genre` из плана — дефект плана.
|
||||
|
||||
### 🔴 W15 — аудит `artist` требует РУЧНОГО разбора по классам
|
||||
|
||||
Regex-детектор по `new_artist` дал 8 классов / 628 попаданий; **606 — ложные**. Реальных — два:
|
||||
|
||||
| Класс | Реально? | Пример |
|
||||
|---|---|---|
|
||||
| Исполнитель вместо композитора | ✅ | `'Maria Makarova (organ)'` на файле Баха → должно быть `Иоганн Себастьян Бах` |
|
||||
| Название сборника в `artist` | ✅ | `'Сказочный сборник'` ×15, `'Классика в современной обработке'` ×3 |
|
||||
| `artist` и `album` пересекаются | ❌ 204 | `artist='Винни-Пух'` + `album='Винни-Пух (2)'` — норма |
|
||||
| Несколько имён в поле | ❌ 267 | `'Би-2 feat. Диана Арбенина'` — фит |
|
||||
| Смешаны алфавиты | ❌ 96 | `'Иванушки International'`, `'Flёur'` — норма |
|
||||
| `artist == title` | ❌ 25 | `'Винни-Пух.mp3'` внутри сборника «Винни-Пух» |
|
||||
|
||||
**Печатать значения по классу и смотреть глазами.** Счётчик класса — не список дефектов:
|
||||
объявить 628 дефектов при 20 реальных = потеря доверия (реакция Alex: «ты нихуя не проверил csv»).
|
||||
|
||||
🔴 **Паттерн «инструмент в скобках» — самая ценная проверка:** `'(organ)'`, `'(piano)'`,
|
||||
`'(vocals)'`, `'(guitar)'` в `artist` означает, что поле держит **исполнителя**; композитора
|
||||
брать из title/имени файла. Это ровно тот дефект, из-за которого Jellyfin показывал мусор.
|
||||
|
||||
### Приёмка записи
|
||||
|
||||
`snapshot.py` ДО и ПОСЛЕ + diff. Критерий: управляемые фреймы совпадают с CSV, **каждый
|
||||
неуправляемый фрейм на месте** (`APIC`, `TDRC`, `COMM`, `TCOM`, `TSSE`, `WCOP`, `WXXX`, `TKEY`,
|
||||
`TCOP`…), `ID3v1` исчез, **размер в байтах не изменился** (равный размер = MPEG-поток не тронут).
|
||||
Отчёт `ok=3 err=0` не доказывает ничего из этого.
|
||||
|
||||
### Запуск длинных задач без апрува и без сети
|
||||
|
||||
```bash
|
||||
docker create --name tw <image> # без промпта
|
||||
docker cp script.py tw:/app/script.py # АКТУАЛЬНЫЙ скрипт внутрь
|
||||
docker start -a tw && docker rm -f tw # foreground, видно stdout
|
||||
```
|
||||
|
||||
### 🔴 W16 — имена рабочих CSV: один боевой файл
|
||||
|
||||
Раскладка, к которой пришли (после путаницы `APPLY-PLAN-FINAL` vs `APPLY-PLAN-FINAL2`):
|
||||
|
||||
```
|
||||
APPLY-PLAN.csv — БОЕВОЙ, единственный источник истины
|
||||
APPLY-PLAN-RAW.csv — сырой (до нормализации), НЕ применять
|
||||
archive/APPLY-PLAN-v0-*.csv — устаревшие версии
|
||||
```
|
||||
|
||||
Суффикс `2` в имени файла-плана — источник ошибок: непонятно, какой применять. Один файл
|
||||
без суффикса, остальные явно помечены. Перед переименованием — `tar czf` снимок каталога.
|
||||
|
||||
## ⚪ Что НЕ является багом
|
||||
|
||||
- **Рукописная транслитерация:** `КняZz`, `EMINЭM`, `Flёur`, `изBESTняк`, `Скаzки`, `Глюкоzа`,
|
||||
|
||||
Reference in new issue
Block a user