Compare commits

...
14 Commits
Author SHA1 Message Date
DenozordecandClaude Opus 5.5 dc19fe6255 API: видео, тип релиза и субтитры в ответе релиза; Atmos и kHz у дорожек
docker / build (push) Successful in 1m18s
src/techinfo.py — правила по строкам «Видео:», «Тип релиза:»/«Качество»,
«Контейнер:», «Продолжительность:», «Субтитры:» и заголовку: поля video
(кодек, размер кадра, класс разрешения, fps, битрейт, битность, HDR,
профиль DV), release (тип, нормализованный источник, контейнер,
продолжительность), subtitles (языки с forced/full/SDH, формат, вшитые).
audio дорожек и original: khz и atmos (вход модели не меняется).
title_from_html больше не рвёт заголовок rutracker пробелами
(<span>/<wbr> на каждом слове). Покрытие на 600 страницах каждого
трекера: видео 90/93/100%, e2e озвучки без изменений. Тесты 275 ok.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 22:26:57 +07:00
DenozordecandClaude Opus 5.5 89ffe4375b Документация: кодек-строки, аудит дорожек, текущие метрики
docker / build (push) Successful in 39s
README: метрики модели 02.11.2026 (test 4 377 строк, e2e по трекерам), размер
датасета, kinozal-вкладки. dataset.md: размер снимка, колонки studio_source и
audio_raw. architecture.md: кодек-строки как позитивы сегментатора, E2E_MODEL.
collecting.md: tools/audit_tracks.py. training-windows.md: доразметка, сравнение
кандидата и постановка в models/best и Docker. plan.md: шаг «аудит дорожек».

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 21:55:43 +07:00
DenozordecandClaude Opus 5.5 588b1f1579 Модель для Docker: base, обученная со студиями из кодек-строк
docker / build (push) Successful in 4m43s
models/runs/20261102_190555_basecodec -> models/best -> docker-models
(tools/pack_models.py --stage). Тестовая выборка (4 377 строк): joint
exact 0.891 -> 0.906, span exact 0.905 -> 0.922, type macro F1 0.893 ->
0.903; исправлено 244 строки, сломано 179 (p = 0.002). Релизы (по 400
страниц, P/R): kinozal 0.745/0.850 -> 0.766/0.879, rutracker 0.866/0.859
-> 0.877/0.870, nnm 0.845/0.829 -> 0.838/0.825. Сегментатор прежний.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 20:30:54 +07:00
DenozordecandClaude Opus 5.5 e3af778068 Студии из кодек-строк: codec_line_rows, доразметка, аудит дорожек
tools/audit_tracks.py и reports/tracks_audit.md — аудит всех дорожек
техданных по data/raw с планом. Пункт 1 плана: harvest_core.codec_line_rows
добавляет студии из строк «Аудио 2: AC3 … (MVO, LostFilm)», «Аудио Rus: … |
TVShows» (studio_source=codec), которых нет среди строк релиза; тот же шаг
в tools/relabel_studios.py (повторный запуск ничего не добавляет).
labels.csv: +6 428 строк в 2 862 релизах (kinozal 3 444, nnm 1 609,
rutracker 1 375), со студией 29 331 -> 35 759. tests/e2e_segmenter.py:
модель через E2E_MODEL.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 20:30:52 +07:00
DenozordecandClaude Opus 5.5 63878f7874 Модель для Docker: base, обученная на доразмеченных студиях
docker / build (push) Successful in 1m21s
models/runs/20261102_152919_base_relabel -> models/best -> docker-models
(tools/pack_models.py --stage). Тестовая выборка (5 235 строк): joint
exact 0.880 -> 0.953, span exact 0.880 -> 0.955; исправлено 541 строка,
сломано 157. Релизы (по 400 страниц, P/R пар тип+студия): nnm
0.583/0.590 -> 0.853/0.859, rutracker 0.891/0.921 -> 0.887/0.911,
kinozal 0.779/0.926 -> 0.781/0.930. Сегментатор прежний.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 15:43:31 +07:00
DenozordecandClaude Opus 5.5 666caa60d5 Отчёт аудита данных после доразметки студий
docker / build (push) Successful in 1m27s
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 15:38:55 +07:00
DenozordecandClaude Opus 5.5 83bab13893 Доразметка студий: nnm «Перевод: … (Студия)», kinozal из заголовка раздачи
* harvest_core.nnm_part_studio: студия из части строки «Перевод:» nnm
  («Дублированный (Невафильм)», «… - Jaskier»); раньше такие строки шли с
  «(не указана)», и модель училась не извлекать студию даже из
  «Дублированный (Пифагор)». tech-строки со студиями больше не
  выбрасываются, если на странице нет строк «Аудио N» с именами.
* harvest_core.kz_title_rows: kinozal, студии из заголовка «ПМ (LostFilm,
  HDrezka Studio)», которых нет на странице; вход — сегмент как у сервиса,
  строка «Аудио Rus: … | Студия» — в audio_raw.
* tools/relabel_studios.py — то же по готовому labels.csv без пересборки
  страниц; применено к data/labels.csv: со студией 22 061 -> 29 331 строк
  (nnm +3 051, kinozal +4 219 строк в 3 007 релизах); prepare_data:
  39 418 строк, спан студии 24 004 -> 31 214.
* e2e: эталон как aggregate — «тип без студии» убирается, если у типа есть
  студия.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 15:20:03 +07:00
DenozordecandClaude Opus 5.5 f91f6cb48e Сегментатор: -1 только для кодек-строк с типом или студией
Пустая кодек-строка («Аудио 1: AC3, 6 ch, 384 kbps - Русский») снова
негатив: с -1 модель разучилась её отбрасывать и на nnm подменяла ею
строку «Перевод: …».

Пробное обучение (2 эпохи, ~34 мин на 27.9k страниц) e2e по 400 тестовых
страниц, P/R пар (тип, студия), текущие веса -> новые: nnm 0.872/0.879 ->
0.836/0.860, rutracker 0.891/0.917 -> 0.883/0.910, kinozal 0.720/0.844 ->
0.724/0.845. Новые веса не лучше текущих, models/segmenter не заменён.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 14:10:32 +07:00
DenozordecandClaude Opus 5.5 6470f882f1 Сегментатор: текст kinozal из raw JSON, кодек-строки не как негатив; e2e по 400 страниц
Обучение:
* page_text/kinozal_text: текст kinozal = h1 + «Техданные» + «Релиз» из
  data/raw/kinozal/*.json (в pages/kz-*.txt нет заголовка и «Техданных»,
  откуда половина меток kinozal; сервис видит обе вкладки).
* label_page: позитив — raw или audio_raw; неразмеченная кодек-строка
  русской дорожки («Аудио 3: … Русский (MVO, X)», «Аудио Rus: … | X»)
  получает -1 и в обучение не идёт: как негатив она учила отбрасывать
  кодек-строки. Сводка «Аудио: Русский, английский (…)» остаётся негативом.

Инференс (release):
* model_segments -> (основные, кодек); выбранные моделью кодек-строки идут
  второстепенным источником вместе с правилом codec_segments; иноязычные
  («Казахский (Dub, …)», «English Dubbing», «Original») отбрасываются.
* merge_extra_tracks(adopt_type): кодек-дорожка с типом «неизвестно»
  получает тип единственной дорожки страницы без студии.

e2e_segmenter: тот же текст kinozal, не больше E2E_MAX (400) страниц на
трекер, train-страницы только с --train-pages (меню парсит только тест).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 12:05:20 +07:00
DenozordecandClaude Opus 5.5 099d33bdaa Слияние claude/advisor-opus-78114c: модели на пересобранном датасете, Docker, язык и серии
Конфликт в src/release.py: взяты original_info, lostfilm_release,
series_info и серии дорожек из advisor; сохранены same_studio,
merge_extra_tracks и codec_segments. Кодек-дорожкам тоже проставляются
серии.

Кодек-строки теперь подключаются, только если у какой-то дорожки страницы
нет студии: с новыми моделями безусловное подключение роняло precision
студий на rutracker (0.908 -> 0.793). С условием, test-сплит, по 300
релизов, студии P/R: rutracker 0.908/0.882 -> 0.840/0.900, nnm
0.875/0.915 -> 0.875/0.940, kinozal 0.816/0.845 -> 0.800/0.863. Тесты:
240 ok.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 11:50:44 +07:00
DenozordecandClaude Opus 5.5 666a266b2f Сегментатор: truncation longest_first — не падать на строке длиннее max_length
С only_second токенизатор бросал исключение, если сама строка (до 220
символов) не помещалась в MAX_LEN и обрезать было нечего, кроме контекста.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 11:44:18 +07:00
DenozordecandClaude Opus 5.5 1f9ee46a0d kinozal: догрузка вкладки «Релиз» по URL; кодек-строки «Аудио N» как источник студий
На kinozal во встроенном #tabs только «Техданные» («Перевод:» без студий),
а строки «Аудио N: … (MVO, LostFilm)» лежат во вкладке «Релиз», которую
страница грузит JS. Поэтому по URL определялись только типы без студий.

* fetch: для kinozal скачиваются вкладки showtab(id,N)
  (get_srv_details.php?id=…&pagesd=N, кроме «Техданных» и «Скриншотов»)
  и дописываются в #tabs; недоступная вкладка пропускается.
* release: кодек-строки с типом озвучки берутся правилом codec_segments
  (сегментатор их отбрасывает — в разметке rutracker это не raw) и идут
  второстепенным источником (source=codec / page+codec): только дорожки
  со студией, дубли студии (same_studio) пропускаются, студия вписывается
  в дорожку того же типа без студии. «English Dubbing» и т.п. не берутся.
* merge_title_tracks переведён на общий merge_extra_tracks.

e2e test rutracker без изменений (P 0.988 / R 0.982); train-страницы
P 0.975 -> 0.951. Тесты: 77 ok.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 11:33:01 +07:00
DenozordecandClaude Sonnet 5.5 6a87bc993c Слияние claude/advisor-opus-78114c: сегментатор страниц, URL/FlareSolverr, агрегат релиза
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-27 20:07:22 +07:00
Denozordec 2eb4f8b72e Merge claude/advisor-opus-78114c: релиз целиком, URL-вход, int8 tiny2, фикс сегментации, датасет 6864 2026-10-27 17:22:31 +07:00
25 changed files with 16248 additions and 3175 deletions

No files matched your search

+16 -14
View File
@@ -76,19 +76,21 @@ docs/ документация
## Качество
Held-out test (645 строк, группировка по релизам и текстам; полная таблица и
графики — [reports/summary.md](reports/summary.md)):
Held-out test (4 377 строк, группировка по релизам и текстам; модель
`models/runs/20261102_190555_basecodec`, обучена 02.11.2026 на `labels.csv` со студиями из
кодек-строк; сравнение моделей — `python -m src.runs`):
| модель | type acc | type macro-F1 | span exact | 18+ acc | joint exact |
|---|---|---|---|---|---|
| rubert-tiny2 | 0.991 | 0.970 | 0.918 | 1.000 | 0.915 |
| **ruBert-base (models/best)** | **0.994** | 0.966 | **0.950** | **1.000** | **0.946** |
| модель | type acc | type macro-F1 | span exact | 18+ acc | lang acc | joint exact |
|---|---|---|---|---|---|---|
| **ruBert-base (models/best)** | **0.978** | **0.903** | **0.922** | **1.000** | **0.997** | **0.906** |
* joint exact — строка распознана полностью (тип + автор + 18+).
* E2E на сохранённых страницах релизов: rutracker — precision 0.83 / recall 0.98
по парам (тип, автор); kinozal-текстовые дампы содержат только тех-вкладку
без авторов (автор подгружался на сайте через JS), поэтому там автор
недоступен в принципе.
* Тест-сплит пересобирается вместе с датасетом, поэтому цифры разных снимков напрямую
не сравниваются (ранние 0.946 — на тесте из 645 строк). rubert-tiny2 на текущем
датасете не переобучалась.
* E2E по релизам (`tests/e2e_segmenter.py`, по 400 тестовых страниц на трекер, пары
тип + студия, precision / recall): kinozal 0.766 / 0.879, rutracker 0.877 / 0.870,
nnm 0.838 / 0.825.
* ONNX-экспорт эквивалентен PyTorch на 100% строк test — [reports/onnx_parity.md](reports/onnx_parity.md).
* Smoke на формулировках, которых нет в датасете: тип 9/10, 18+ 10/10,
автор 5/10 (полное извлечение автора на непривычных форматах записи —
@@ -99,14 +101,14 @@ Held-out test (645 строк, группировка по релизам и т
* Метки датасета сгенерированы регулярками (пайплайн deepseek-harness) — метрики
измеряют согласование с этой разметкой; ценность модели — обобщение на новые
формулировки без сопровождения регулярок.
* Источники несбалансированы (rutracker 1658 / nnm 1549 / kinozal 77 строк).
* Датасет: 55 324 строки из 27 900 релизов (kinozal 21 766 / nnm 18 363 / rutracker 15 195).
* Извлечение автора уверенно работает на форматах записи, представленных в
датасете (имя после типа через разделитель, в скобках у AVO/MVO/DVO-дорожек,
после тире); на непривычных форматах («Одноголосый закадровый (Фамилия)»)
модель может не выделить имя — span остаётся пустым, тип при этом верный.
* kinozal-страницы: авторы озвучки подгружаются на сайте JavaScript-ом; в
статическом HTML и текстовых дампах их нет — модель их не увидит (это
ограничение данных, не модели).
* kinozal-страницы: авторы озвучки лежат во вкладках, которые сайт подгружает
JavaScript-ом. По `url` сервис скачивает их сам (`fetch_html`, `showtab`); в
присланном `html`/`text` без этих вкладок студий может не быть.
* Классы с 1–8 примерами почти не выучиваются (видно в macro-F1 и confusion
matrix); морфологическая аугментация (458 вариантов: «многоголосое» и т.п.)
частично закрывает этот пробел.
+13698 -3051
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.
+54 -5
View File
@@ -38,8 +38,16 @@
"studios": [{"studio": "LostFilm", "translation_type": "проф. многоголосый", "lang": "RUS", "is_18plus": false, "episodes": "1-4"}],
"translation_types": ["проф. многоголосый"],
"is_18plus": false,
"original": {"lang": "ENG", "codec": "E-AC3", "channels": 6, "bitrate_kbps": 768, "raw": "English E-AC3+Atmos / 5.1 / 48 kHz / 768 kbps |Original|"},
"original": {"lang": "ENG", "codec": "E-AC3", "channels": 6, "bitrate_kbps": 768, "khz": 48, "atmos": true, "raw": "English E-AC3+Atmos / 5.1 / 48 kHz / 768 kbps |Original|"},
"series": {"season": "2", "episodes": "1-9", "total": 10, "by_studio": {"LostFilm": "1-4"}, "full_release": []},
"video": {"codec": "HEVC", "width": 3840, "height": 2160, "resolution": "2160p", "fps": 23.976, "bitrate_kbps": 24800,
"bit_depth": 10, "hdr": ["Dolby Vision", "HDR10+", "HDR10"], "dv_profile": "8", "raw": "MPEG-H HEVC Video / 24,8 Mbps / 3840x2160 / …"},
"release": {"quality": "WEB-DL 2160p Dolby Vision", "source": "WEB-DL", "container": "MKV", "duration": "01:38:12", "duration_sec": 5892},
"subtitles": {"languages": ["RUS", "UKR", "ENG"],
"tracks": [{"lang": "RUS", "forced": true, "full": false, "sdh": false},
{"lang": "UKR", "forced": true, "full": true, "sdh": true},
{"lang": "ENG", "forced": false, "full": true, "sdh": true}],
"format": "softsub (SRT)", "hardsub": false, "raw": "Русские (Forced), Украинские (Forced, 2x Full, SDH), Английские (Full, SDH)"},
"title": "Название раздачи",
"segmenter": "model",
"tracker": "rutracker"
@@ -50,14 +58,39 @@
|---|---|---|
| `tracks` | array | дорожки релиза без дублей (дубль — одинаковые тип + студия) |
| `studios` | array | уникальные `{studio, translation_type, lang, is_18plus, episodes}`; `lang` — язык дорожки студии (как в `tracks[].lang`, может быть `null`). Одна студия с русским и украинским дубляжом даёт две записи |
| `original` | object \| null | оригинальная дорожка из строки «Аудио … \|Original\|»: `{lang, codec, channels, bitrate_kbps, raw}` (поля могут быть `null`); `null` — такой строки на странице нет |
| `original` | object \| null | оригинальная дорожка из строки «Аудио … \|Original\|»: `{lang, codec, channels, bitrate_kbps, khz, atmos, raw}` (поля могут быть `null`, `atmos` — bool); `null` — такой строки на странице нет |
| `translation_types` | array of string | отсортированный набор типов (значения — из таблицы классов ниже) |
| `is_18plus` | bool | `true`, если 18+ хотя бы у одной дорожки |
| `series` | object \| null | сезон, выпущенные серии, «какая студия какие серии» (для Sonarr); `null` — сведений нет |
| `video` | object \| null | видео из строки «Видео:» / «Video:» (у nnm — также «Видеокодек», «Размер кадра», «Битрейт видео»), см. ниже; `null` — строки нет |
| `release` | object \| null | тип релиза, контейнер, продолжительность, см. ниже; `null` — ничего из этого на странице нет |
| `subtitles` | object \| null | субтитры из строк «Субтитры:», «Субтитры 2:», «Язык субтитров:» (нет строк — языки из «Sub (Rus, Eng)» заголовка), см. ниже; `null` — о субтитрах ничего не сказано |
| `title` | string \| null | заголовок раздачи, если найден |
| `segmenter` | `"model"` \| `"rules"` | чем страница разбита на строки дорожек: обученным сегментатором или запасными правилами |
| `tracker` | string | только для `url`: `rutracker`, `nnm`, `kinozal` или `lostfilm` |
`video` (все поля, кроме `hdr` и `raw`, могут быть `null`):
`codec` — `HEVC`, `AVC`, `AV1`, `VP9`, `VC-1`, `MPEG-2`, `XviD`, `DivX`, `MPEG-4`;
`width`/`height` — размер кадра; `resolution` — класс по ширине (`2160p`, `1440p`, `1080p`, `720p`; кадр с
полосами 1920x800 — тоже `1080p`), для SD — высота кадра (`576p`, `400p`); `fps`; `bitrate_kbps`;
`bit_depth` — 8/10/12; `hdr` — массив из `Dolby Vision`, `HDR10+`, `HDR10`, `HLG` или просто `HDR`, если вид
не назван (ищется в строке видео, «HDR format», типе релиза и заголовке), пустой — HDR нет; `dv_profile` —
профиль Dolby Vision строкой («8», «5»); `raw` — строка видео целиком.
`release`: `quality` — значение «Тип релиза» / «Качество видео» / «Качество» как на странице (без
«\| Исходник: …»); `source` — нормализованный источник: `UHD BDRemux`, `BDRemux`, `WEB-DLRip`, `WEB-DL`, `WEBRip`,
`BDRip`, `HDRip`, `Blu-ray`, `DVDRip`, `DVD9`, `DVD5`, `HDTVRip`, `HDTV`, `SATRip`, `TVRip`, `DVB`, `VHSRip`,
`DCPRip`, `IPTV`, `CAMRip`, `DVD` (из «Качества», иначе из заголовка); `container` — `MKV`, `MP4`, `AVI`, `M2TS`,
`ISO`, `DVD-Video`, `TS`, `WEBM`, `MOV`, `WMV` (kinozal строку контейнера обычно не пишет — тогда `null`);
`duration` — «Продолжительность» как на странице («01:38:12», «18 x ~ 00:20:00»); `duration_sec` — секунды,
только если это одно значение ЧЧ:ММ:СС фильма (не серии и не список файлов). Любое поле может быть `null`.
`subtitles`: `languages` — коды языков по порядку (те же коды, что у `lang` дорожки); `tracks` — по записи
на язык: `{lang, forced, full, sdh}` (флаги «Forced/форсированные/надписи», «Full/полные», «SDH/для
слабослышащих» объединяются по всем строкам); `format` — «Формат субтитров» как на странице или `null`;
`hardsub` — субтитры вшиты в видео; `raw` — исходные строки через «; ». «Субтитры: нет/отсутствуют» даёт
объект с пустым `languages` — субтитров точно нет; `null` — страница о субтитрах молчит.
`series`: `season` — номер сезона строкой или `null`; `episodes` — выпущенные серии («1-9»);
`total` — всего серий; `by_studio` — `{студия: серии}`; `full_release` — студии без указанных серий
(для сериала это значит «озвучены все вышедшие серии»). Подробности — в [integration.md](integration.md).
@@ -79,7 +112,7 @@
| `lang_source` | `"audio"` \| `"model"` \| `"text"` \| null | откуда язык, в порядке приоритета: строка «Аудио» страницы (`Ukrainian E-AC3 …` → `UKR`, самый надёжный источник), нейросеть (классы `ENG`/`RUS`/`UKR`, порог 0.5; без строки «Аудио» ей приходится угадывать по тексту), слова в тексте («Украинский дубляж») |
| `role` | string | `перевод`, `оригинал` (оригинальная дорожка без озвучки) или `комментарии` (комментарии режиссёра и т. п.) |
| `official` | bool | в строке «Аудио» есть слова «официальный» / «лицензионный» / «licensed» |
| `audio` | object \| null | `{codec, channels, bitrate_kbps}` из строки «Аудио» страницы (`"E-AC3"`, `6`, `768`; `channels` — число каналов, «5.1» → 6); каждое поле может быть `null`; весь объект `null`, если строка не найдена |
| `audio` | object \| null | `{codec, channels, bitrate_kbps, khz, atmos}` из строки «Аудио» страницы (`"E-AC3"`, `6`, `768`, `48`, `true`; `channels` — число каналов, «5.1» → 6; `khz` — частота дискретизации, 44.1 / 48; `atmos` — в строке есть «Atmos», кодек при этом остаётся `E-AC3`/`TrueHD`); поля, кроме `atmos`, могут быть `null`; весь объект `null`, если строка не найдена |
| `source` | `"page"` \| `"title"` | только в ответе релиза: дорожка найдена в описании или студия взята из заголовка раздачи |
| `episodes` | string \| null | серии, которые озвучивает дорожка («1-4», «1,3-5»); `null` — серии не указаны (покрывает весь релиз) |
| `episode_ranges` | array | то же в виде `[[1, 4]]`; пусто, если серий нет |
@@ -157,7 +190,7 @@ type Track = {
lang_source: "model" | "audio" | "text" | "url" | null; // "url" — ссылка lostfilm.tv; null вместе с lang
role: "перевод" | "оригинал" | "комментарии"; // никогда null
official: boolean;
audio: { codec: string | null; channels: number | null; bitrate_kbps: number | null } | null;
audio: { codec: string | null; channels: number | null; bitrate_kbps: number | null; khz: number | null; atmos: boolean } | null;
// только в ответе релиза (html / url):
source: "page" | "title";
episodes: string | null; // "1-4", "1,3-5"; null — серии не указаны
@@ -167,7 +200,8 @@ type Track = {
type Release = {
tracks: Track[]; // может быть пустым массивом
studios: { studio: string | null; translation_type: string; lang: string | null; is_18plus: boolean; episodes: string | null }[];
original: { lang: string | null; codec: string | null; channels: number | null; bitrate_kbps: number | null; raw: string } | null;
original: { lang: string | null; codec: string | null; channels: number | null; bitrate_kbps: number | null;
khz: number | null; atmos: boolean; raw: string } | null;
translation_types: string[];
is_18plus: boolean;
series: {
@@ -177,6 +211,19 @@ type Release = {
by_studio: Record<string, string>; // { "LostFilm": "1-4" }, может быть {}
full_release: string[]; // студии без указанных серий
} | null; // null — ни сезона, ни серий в раздаче нет
video: {
codec: string | null; width: number | null; height: number | null; resolution: string | null;
fps: number | null; bitrate_kbps: number | null; bit_depth: number | null;
hdr: string[]; // [] — HDR нет
dv_profile: string | null; raw: string;
} | null;
release: { quality: string | null; source: string | null; container: string | null;
duration: string | null; duration_sec: number | null } | null;
subtitles: {
languages: string[]; // [] — «Субтитры: нет»
tracks: { lang: string; forced: boolean; full: boolean; sdh: boolean }[];
format: string | null; hardsub: boolean; raw: string | null;
} | null; // null — о субтитрах на странице ничего нет
title: string | null;
segmenter: "model" | "rules";
tracker?: "rutracker" | "nnm" | "kinozal" | "lostfilm"; // только для url
@@ -226,6 +273,8 @@ Cloudflare: FlareSolverr нестабилен.
по тексту; отдельно берётся `original`; определяются `role`, `official`, `audio`.
7. **Агрегат релиза.** Дубли (тип + студия) склеиваются; студии из заголовка раздачи, которых нет
в описании, добавляются с `source: "title"`; серии и сезон разбираются правилами (`src/episodes.py`).
8. **Техданные** (`video`, `release`, `subtitles`) — правилами по строкам «Видео:», «Тип релиза:»,
«Контейнер:», «Продолжительность:», «Субтитры:» и заголовку (`src/techinfo.py`), без нейросети.
Размеченные данные (`data/labels.csv`) используются только при обучении: на них учатся метки
`translation_type`, спан студии, 18+ и язык. Схема файла и происхождение меток — в
+21 -7
View File
@@ -73,13 +73,27 @@ BIO-разметка строится по символьным спанам ч
используются, если `models/segmenter` нет или модель не нашла ни одной строки;
режим можно форсировать: `predict_release(..., segmenter="rules")`.
* Обучение: `python -m src.segmenter --train` (~1 мин). Разметка строк — по
`raw` из `labels.csv` (362 страницы в `data/pages`); синтетика: `raw` всех
train-строк (в т.ч. nnm/kinozal без страниц) вставляются в контекст реальных
строк страниц. Страницы делятся по хешу `release_id` (≈20% в тест).
* Метрики: строки (test) P 0.936 / R 0.994; e2e на непросмотренных страницах
rutracker (n=64) — precision 0.904 → 0.946 против правил, recall 0.975 → 0.975
(`tests/e2e_segmenter.py`; выборка мала, разброс большой).
* Обучение: `python -m src.segmenter --train --out models/segmenter_v2` (или меню
`tools/collect.py` → «2»). Разметка строк — по `raw` и `audio_raw` из `labels.csv`
(страницы `data/pages`); синтетика: `raw` всех train-строк вставляются в контекст
реальных строк страниц. Страницы делятся по хешу `release_id` (≈20% в тест).
* Текст kinozal берётся из `data/raw/kinozal/<id>.json` (`h1` + «Техданные» +
«Релиз», `segmenter.page_text`): в `data/pages/kz-*.txt` нет ни заголовка, ни
«Техданных», откуда взята половина меток kinozal, а сервис (`fetch_html`)
видит обе вкладки.
* Кодек-строки со студией с 02.11.2026 размечены сами (`studio_source=codec`, см.
`docs/collecting.md`): их `raw`/`audio_raw` дают позитивы. Кодек-строка русской
дорожки («Аудио 3: AC3 … Русский (MVO, LostFilm)», «Аудио Rus: … | LostFilm»),
не попавшая в разметку, в обучение не идёт
(метка -1): у rutracker `raw` — строка «Перевод N:», и как негатив она учила
модель отбрасывать кодек-строки — а на kinozal студии только в них.
* В `predict_release` выбранные моделью кодек-строки — второстепенный источник
(`source: codec`): подключаются, только если у какой-то дорожки страницы нет
студии; если других строк нет (nnm), они и есть дорожки.
* e2e: `tests/e2e_segmenter.py [каталог]` — пары (тип, студия) на тестовых
страницах, не больше `E2E_MAX` (400) на трекер; `--train-pages` — ещё и train;
основная модель — `E2E_MODEL` (по умолчанию `models/best`), например
`set E2E_MODEL=models/runs/<запуск>` перед запуском, чтобы сравнить кандидата.
* Нарезка ВНУТРИ строки («Дублированный, многоголосый») в данных не размечена
(91 группа из 6735) — остаётся на правилах `_split_entries`, но только на
строках, выбранных моделью.
+30
View File
@@ -142,6 +142,36 @@ id разных трекеров не пересекались. Коллизий
повторяют логику правил. Для нарезки внутри строки («Дублированный,
многоголосый») нужна ручная проверка — режим ревью ещё не сделан.
Студии, которые парсеры раньше теряли (доразметка от 02.11.2026):
* **nnm, «Перевод: Дублированный (Невафильм)»** — строки «Перевод:» (tech) шли с
«(не указана)», хотя студия названа в той же части. Модель училась её не извлекать.
Теперь — `nnm_part_studio` (скобки или «… - Студия»; тип, «x2», «Лицензия», источник
вроде iTunes/Blu-ray — не студия). +3 051 студия.
* **kinozal, заголовок «… / ПМ (LostFilm, HDrezka Studio), СТ / …»** — во «Техданных»
только «Перевод: Профессиональный многоголосый», студии названы в заголовке и в
строках «Аудио Rus: … | LostFilm» без типа. Теперь — `kz_title_rows`: строка на каждую
студию из заголовка, которой нет на странице (`studio_source=title`, вход — тот же
сегмент, что строит сервис из заголовка; строка «Аудио» с этой студией — в
`audio_raw`). +4 219 строк в 3 007 релизах.
* **Студии только в кодек-строках** («Аудио 2: AC3 … (MVO, LostFilm)», «Аудио Rus: … | TVShows»,
«… • DUB | LeDoyen • UKR») — разметка шла по «Перевод:», кодек-строка была лишь `audio_raw`
у найденной дорожки. Теперь — `codec_line_rows`: строка на каждую студию русской/украинской
кодек-строки, которой нет среди меток релиза (`studio_source=codec`, вход — сегмент, как его
режет сервис; тип — из строки, иначе единственный тип релиза, иначе «неизвестно»). Студия —
из словаря меток (≥ 5 строк) или из скобок/`|…|`; раскладка, кодек, «поздний», «серии» — не
студия. +6 428 строк в 2 862 релизах (kinozal 3 444, nnm 1 609, rutracker 1 375). Аудит,
откуда взялась доработка: `reports/tracks_audit.md`.
Повторить аудит дорожек по `data/raw` (сколько студий из кодек-строк, «Перевод:»
и заголовков не попало в метки, мусор в колонке студий, форматы, которые не ловят регулярки):
`.venv\Scripts\python tools\audit_tracks.py` → `reports/tracks_audit_numbers.md`
(`--cache путь.pkl` ускоряет повторный запуск). Выводы и план — `reports/tracks_audit.md`.
Сборщик делает это при переиндексации. Для уже собранного `labels.csv` без пересборки
страниц: `.venv\Scripts\python tools\relabel_studios.py` (копия — `data/labels.bak_relabel_*.csv`;
`--dry-run` — только статистика; повторный запуск ничего не добавляет).
## Разнообразная выборка (режим balanced)
Списки разделов идут от новых к старым, поэтому чтение подряд даёт только свежие
+3 -1
View File
@@ -10,7 +10,7 @@
регулярками** — модель учится воспроизводить эту разметку и обобщать её на
новые формулировки.
Размер (на момент последней сборки): ~41 тыс. строк из ~28 тыс. релизов, 3 источника
Размер (снимок 02.11.2026): 55 324 строки из 27 900 релизов, 3 источника
(rutracker, nnm, kinozal); HTML-дамп страниц — `data/raw` (~32 тыс. файлов, ≈2.6 ГБ, в git не
хранится), разобранные тексты — `data/pages`. Точные числа показывает «Статистика датасета» в
`tools/collect.py` (пункт 3) и отчёт `reports/data_audit.md` после `prepare_data`.
@@ -29,6 +29,8 @@
| `studio` | автор («(не указана)» если нет) | **метка 2** (спан в тексте) |
| `age_18`, `note` | флаг/пометка 18+ | **метка 3** |
| `role_raw` | скобочная часть / конкретный тип из перечисления | вход для nnm tech-строк |
| `studio_source` | откуда взята строка: `release` / `tech` (описание релиза / техданные, в т.ч. nnm «Перевод:»), `codec` (кодек-строка «Аудио N: … (MVO, LostFilm)»), `title` (заголовок kinozal), `audio` (только строка «Аудио»), `manual` (ручная разметка) | не используется; `codec` не попадает в словарь студий `codec_line_rows` |
| `audio_raw` | кодек-строка той же дорожки целиком | разметка строк для сегментатора |
| остальные (`ru_title`, `codec`, …) | контекст релиза | не используются |
## Форматы входа по источникам
+6
View File
@@ -37,12 +37,18 @@ python -m src.predict --url https://kinozal.tv/details.php?id=1 --cookie "uid=..
| `studios` | уникальные `{studio, translation_type, is_18plus}` |
| `translation_types` | отсортированный список типов релиза |
| `is_18plus` | `true`, если 18+ хотя бы у одной дорожки |
| `original`, `series` | оригинальная дорожка; сезон и серии |
| `video` | кодек, разрешение, fps, битрейт, битность, HDR (Dolby Vision / HDR10+ / HDR10 / HLG) |
| `release` | тип релиза («WEB-DL», «BDRemux»…), контейнер, продолжительность |
| `subtitles` | языки субтитров с пометками forced / full / SDH, формат, вшитые ли |
| `title` | заголовок раздачи (если найден в HTML) |
| `tracker` | только для `--url` |
Дорожки дедуплицируются по (тип, студия). Студии из заголовка раздачи вида
`ПМ (LostFilm)`, которых нет в описании, добавляются с `source: "title"`
(у остальных `source: "page"`). `--text` по-прежнему возвращает только `tracks`.
Все поля и их значения — в [api-reference.md](api-reference.md); `video`, `release`, `subtitles`
считаются правилами (`src/techinfo.py`), без нейросети.
## Python API
+6 -1
View File
@@ -41,7 +41,12 @@ API:
| `lang`, `lang_confidence`, `lang_source` | язык дорожки (`RUS`, `UKR`, `ENG`, …); источник: `model` (языковая голова), `audio` (строка «Аудио»), `text` (слова в строке дорожки) или `null` |
| `role` | `перевод` / `оригинал` / `комментарии` (по строке «Аудио» и тексту, правилами) |
| `official` | в строке «Аудио» сказано «официальный» / «лицензионный» |
| `audio` | `{codec, channels, bitrate_kbps}` из строки «Аудио» (или `null`) |
| `audio` | `{codec, channels, bitrate_kbps, khz, atmos}` из строки «Аудио» (или `null`) |
Ответ релиза (`html`/`url`) кроме дорожек несёт `video`, `release` и `subtitles` — разрешение, HDR,
тип релиза, контейнер, продолжительность, языки субтитров; поля и значения — в
[api-reference.md](api-reference.md). ONNX-путь (Б) их не считает: это правила `src/techinfo.py`,
их нужно повторить на стороне клиента или ходить в HTTP-сервис.
Русский и украинский дубляж одной студии — две отдельные дорожки; так же раздельны обычная
дорожка студии и её дорожка 18+ (если на странице две строки «Аудио» с одной студией).
+3 -1
View File
@@ -65,12 +65,14 @@
| 1 baseline | сделано |
| 2 URL + агрегат релиза | сделано: `src/fetch.py`, `src/release.py`, `--url`, `POST /predict {"url"}` |
| 3 title-fallback | сделано (`ПМ (LostFilm)` → дорожка с `source: "title"`); заголовок берётся из HTML (`<h1>`/`<title>`), из текстового дампа (первая строка вида «… / год / …»), либо передаётся явно (`title`). Если на странице есть дорожка того же типа без студии, студия вписывается в неё (`source: page+title`), а не дублируется. **Эффект на e2e мал**: kinozal P/R 0.107 → 0.143, rutracker без изменений (0.826 / 0.984) — в разметке датасета студия из заголовка почти не нужна |
| 4 kinozal-вкладка | **не сделано** в сервисе: страница релиза требует авторизации, а `get_srv_details.php?id=…&pagesd=N` уже вызывает `KinozalClient.php` в grabber. Рекомендация: grabber скачивает вкладку сам и шлёт `{"html": …, "title": <заголовок раздачи>}` (во фрагменте вкладки заголовка нет) |
| 4 kinozal-вкладка | сделано: `fetch_html` для kinozal скачивает вкладки `showtab(id,N)` (`get_srv_details.php?id=…&pagesd=N`, кроме «Техданных» и «Скриншотов») и дописывает их в `#tabs`. Кодек-строки «Аудио N: … (MVO, LostFilm)» сегментатор отбрасывает (в разметке rutracker это не `raw`), поэтому они берутся правилом `codec_segments` как второстепенный источник (`source: codec` / `page+codec`): только дорожки со студией, студия-дубль (`same_studio`) пропускается. Кодек-строки подключаются, только если у какой-то дорожки страницы нет студии. На test-сплите модели 29.10 (по 300 релизов), студии P/R без → с кодек-строками: rutracker 0.908/0.882 → 0.840/0.900 (почти все «ложные» — реальные студии, которых нет в эталоне), nnm 0.875/0.915 → 0.875/0.940, kinozal (текст из raw JSON) 0.816/0.845 → 0.800/0.863 |
| 5 false-positives сегментации | сделано: починена резка «Авторский, одноголосый» по запятой; постобработка спана (`refine_span`: до границ слова, мусор вроде `L,C,R,l,r`/кодеков → null). Остаток — промахи модели на `kbps \| DUB Автор` и особенности разметки |
| 9 переобучение | датасет вырос 3286 → 6864 строк; обе модели переобучены (см. ниже) |
| 6 tiny2 + int8 | сделано: 29.3 МБ, parity 99.8% тип / 99.2% спан / 100% 18+ (`reports/onnx_parity_tiny2_int8.md`) |
| 7 тесты | `run_tests.py`: 57 ok (URL allowlist/SSRF, декодирование, заглушки Cloudflare/логина, title, слияние, агрегат) |
| 8 `target/` | сделано |
| 10 аудит дорожек | сделано 02.11.2026: `tools/audit_tracks.py` → `reports/tracks_audit.md` (план из 5 пунктов). Пункт 1 — студии из кодек-строк в разметке (`codec_line_rows`, +6 428 строк); модель переобучена: test joint exact 0.891 → 0.906, e2e P/R kinozal 0.745/0.850 → 0.766/0.879, rutracker 0.866/0.859 → 0.877/0.870, nnm 0.845/0.829 → 0.838/0.825. Пункты 2–5 и сегментатор на новой разметке — открыты |
| 11 техданные релиза | сделано 02.11.2026: в ответе релиза `video` (кодек, разрешение, fps, битрейт, битность, HDR, профиль DV), `release` (тип, контейнер, продолжительность), `subtitles` (языки, forced/full/SDH, формат, вшитые); у дорожек `audio.khz` и `audio.atmos` — правила `src/techinfo.py`. Покрытие на 600 сохранённых страницах каждого трекера: видео найдено на 90% rutracker / 93% nnm / 100% kinozal, кодек — у 91% / 92% / 97% из них. Заголовок rutracker больше не рвётся пробелами («Галактическо е»). В ONNX-клиентах этих полей нет |
## Открытые вопросы
+8
View File
@@ -84,6 +84,14 @@ copy /Y <путь>\labels.csv data\labels.csv
.venv\Scripts\python -m src.train
```
Если поменялась только логика разметки студий (без перекачки страниц), сначала
`.venv\Scripts\python tools\relabel_studios.py` (см. `docs/collecting.md`), затем
`prepare_data` и обучение. Кандидата из `models/runs/` перед заменой `models/best`
сравнить: `python -m src.runs --out runs.json --dirs models/best models/runs/<запуск>` (test-сплит) и
`E2E_MODEL=models/runs/<запуск>` + `tests/e2e_segmenter.py` (релизы). Поставить —
меню `tools/collect.py`, пункт 7, или `src.runs.promote`; для Docker затем
`python tools/pack_models.py --stage docker-models`.
## Troubleshooting
| Симптом | Причина | Решение |
+35 -23
View File
@@ -1,32 +1,44 @@
# Аудит датасета: data/labels.csv
строк в CSV: 44677
строк после подготовки: 35383 (train 31343 / test 4040)
источники: {'kinozal': 6382, 'rutracker': 14342, 'nnm': 14659}
уникальных нормализованных текстов: 18870
групп (релиз ∪ одинаковый текст): 6681
удалено: пустой текст 0, мусорный studio 155, не-озвучка (оригинал/субтитры) 6, противоречивые дубли 0 (групп-противоречий: 0), несколько дорожек в одной строке 20, лишние повторы (> 40 одинаковых) 16950
спан автора найден: 24004 (67.8%), не найден: 231
аугментировано морфологических вариантов: 7658
дорожек 18+: 701 (из них без упоминания «18» в тексте: 8)
язык размечен: 15282 из 35383; со строкой «Аудио» во входе: 11796; классы: {'RUS': 14673, 'UKR': 556, 'ENG': 53}; редкие (<20, не учатся): {'DEU': 7, 'FRA': 14, 'CHI': 3, 'KAZ': 10, 'GEO': 2, 'BEL': 2, 'KOR': 1, 'JPN': 4, 'LAV': 1, 'CZE': 1, 'ITA': 6, 'SPA': 8}
строк в CSV: 55324
строк после подготовки: 46083 (train 41706 / test 4377)
источники: {'kinozal': 14004, 'rutracker': 15825, 'nnm': 16254}
уникальных нормализованных текстов: 25056
групп (релиз ∪ одинаковый текст): 7354
удалено: пустой текст 0, мусорный studio 156, не-озвучка (оригинал/субтитры) 6, противоречивые дубли 83 (групп-противоречий: 73), несколько дорожек в одной строке 20, лишние повторы (> 40 одинаковых) 18218
спан автора найден: 37875 (82.2%), не найден: 240
аугментировано морфологических вариантов: 9062
дорожек 18+: 959 (из них без упоминания «18» в тексте: 8)
язык размечен: 25977 из 46083; со строкой «Аудио» во входе: 19805; классы: {'RUS': 24401, 'UKR': 1523, 'ENG': 53}; редкие (<20, не учатся): {'DEU': 7, 'FRA': 14, 'CHI': 3, 'KAZ': 10, 'GEO': 2, 'BEL': 2, 'KOR': 1, 'JPN': 4, 'LAV': 1, 'CZE': 1, 'ITA': 6, 'SPA': 8}
## Распределение translation_type
- проф. дубляж: 9936
- проф. многоголосый: 9831
- авт. одноголосый: 4265
- неизвестно: 2800
- проф. двухголосый: 2153
- одноголосый: 2126
- люб. многоголосый: 1381
- многоголосый: 824
- люб. одноголосый: 646
- люб. двухголосый: 551
- люб. дубляж: 380
- проф. одноголосый: 293
- двухголосый: 192
- проф. многоголосый: 13064
- проф. дубляж: 12127
- авт. одноголосый: 5756
- неизвестно: 4198
- проф. двухголосый: 2693
- одноголосый: 2623
- люб. многоголосый: 1778
- многоголосый: 1311
- люб. двухголосый: 752
- люб. одноголосый: 716
- люб. дубляж: 387
- проф. одноголосый: 354
- двухголосый: 319
- авт. двухголосый: 3
- авт. многоголосый: 2
## Примеры противоречий (один текст — разные типы)
- nnm:nnm-1104137 «ac-3, 48.0 khz, 1 ch, 192 kbps, cbr (русский, проф. многогол» -> ['многоголосый', 'проф. многоголосый']
- nnm:nnm-1116916 «ac3, 2 ch, 192 kbps, 48,0 khz (русский - двухголосый ''видео» -> ['двухголосый', 'проф. двухголосый']
- nnm:nnm-1116916 «dts, 2 ch, 768 kbps, 48,0 khz (русский - авторский ''андрей » -> ['авт. одноголосый', 'неизвестно']
- nnm:nnm-1129736 «ac3, 48 khz, ~192 kbps, 2ch, русский (любительский, многогол» -> ['люб. многоголосый', 'проф. многоголосый']
- nnm:nnm-1129736 «ac3, 48 khz, ~192 kbps, 2ch, русский (любительский, двухголо» -> ['люб. двухголосый', 'проф. двухголосый']
- nnm:nnm-1129736 «ac3, 48 khz, ~192 kbps, 2ch, русский (любительский, двухголо» -> ['люб. двухголосый', 'проф. двухголосый']
- nnm:nnm-1129736 «ac3, 48 khz, ~192 kbps, 2ch, русский (любительский, одноголо» -> ['люб. одноголосый', 'одноголосый']
- nnm:nnm-1131052 «ac3, 2 ch, 192 kbps (русский - авторский ''юрий живов'') [ru» -> ['авт. одноголосый', 'неизвестно']
- nnm:nnm-1131273 «ac3, 2 ch, 192 kbps (русский - авторский ''юрий живов'') - о» -> ['авт. одноголосый', 'неизвестно']
- nnm:nnm-1141333 «ac3, 2 ch, 192 kbps (русский - авторский ''андрей гаврилов''» -> ['авт. одноголосый', 'неизвестно']
## Спан не найден (примеры)
- [rutracker] studio=«datynet; : Куценко» text=«Одноголосый закадровый datynet; перевод: Куценко»
- [rutracker] studio=«datynet и Yuka_chan; :Куценко» text=«Любительский (двухголосый закадровый) datynet и Yuka_chan; перевод:Куц»
+126
View File
@@ -0,0 +1,126 @@
# Аудит дорожек в описаниях релизов (raw) — выводы и план
Вход: все сохранённые страницы `data/raw/{kinozal,nnm,rutracker}/*.json` (35 384 релиза:
kinozal 10 541, nnm 13 677, rutracker 11 166) против `data/labels.csv` (48 896 строк).
Текст как у сервиса: kinozal — «Техданные» + «Релиз», nnm/rutracker — пост.
Цифры и примеры: `reports/tracks_audit_numbers.md`, пересчёт:
.venv\Scripts\python tools\audit_tracks.py [--cache путь.pkl]
Строка дорожки = «Перевод N: / Озвучка: / Дубляж:» или кодек-строка («Аудио N: AC3 … (MVO, X)»)
русской/украинской дорожки; субтитры и сводки без типа/студии не считаются. Студии сравниваются
через `release.same_studio` («А. Гаврилов» = «Андрей Гаврилов»), словарь — студии меток (≥ 5 раз)
без мусора.
## Что нашлось
### 1. Студии из кодек-строк не попадают в метки (главное)
Студия названа в строке дорожки релиза, но её нет среди меток этого релиза:
| | релизов с метками | из них с недостающей студией | недостающих студий | из кодек-строк «Аудио» | из «Перевод:» | релизов, где у меток **нет ни одной** студии, а в кодек-строке есть |
|---|---|---|---|---|---|---|
| kinozal | 9 932 | 1 690 (17 %) | 3 736 | 3 240 | 496 | 1 129 |
| nnm | 10 085 | 1 295 (13 %) | 2 032 | 1 003 | 1 029 | 653 |
| rutracker | 7 742 | 972 (13 %) | 1 645 | 531 | 1 114 | 495 |
Топ недостающих: kinozal — HDrezka 362, TVShows 339, LostFilm 211, Red Head Sound 164, LeDoyen 117,
Jaskier 101, MovieDalen 89, Мосфильм-Мастер 80; nnm/rutracker — LeDoyen, Postmodern, НТВ+, HDrezka,
1+1, NewStation, Невафильм.
Причина: разметка строится по строкам «Перевод:», а кодек-строки идут только как `audio_raw`
к уже найденной дорожке (`studio_source=audio` — всего 992 строки). Типичный kinozal:
«Перевод: Профессиональный (многоголосый закадровый)» без студии + «Аудио 2: AC3 … (MVO, LostFilm)».
Это та же дыра, что закрыла доразметка nnm «Перевод:» (nnm 0.58 → 0.85 по релизам), только
крупнее. Обратное (студия в метках, которой нет на странице) почти не встречается: 1 / 22 / 14.
### 2. «Перевод N:» с несколькими студиями размечен частично (nnm, rutracker)
~1 000 студий на трекер — из строк вида «Перевод 3-6: Профессиональный (многоголосый) — Jaskier,
TVShows, HDrezka Studio, LostFilm» или «Перевод: Дублированный (Iyuno-SDI Group, Le Doyen),
Многоголосый (LostFilm, Jaskier, TVShows)»: в метках часть списка. Сводка prepare_data тоже
видит «несколько дорожек в одной строке».
### 3. Форматы кодек-строк, которые не ловят правила
Это правила разметки (`segmenter.RE_AUDIO_TRACK`) и запасной путь сервиса
(`release.codec_segments`), а не модель-сегментатор: промах здесь — дыра в метках сегментатора и
в фолбэке, не измеренная потеря предсказания.
| | кодек-строк | мимо RE_AUDIO_TRACK | мимо codec_segments |
|---|---|---|---|
| kinozal | 18 297 | 11 664 (из них 10 395 — «Аудио:» без номера) | 12 858 |
| nnm | 15 151 | 2 850 | 4 696 |
| rutracker | 11 471 | 2 496 | 5 058 |
* **мимо обоих**: «Аудио pic RUS:» (374), «Аудио #N Русский:» (175), «Аудио N Rus:» / «Аудио 3 Rus:» (~220),
«Аудио rus N:», «Звук N:», «Аудио #N (rus):», «• №N: Русский (АС3 …)», «Аудио 2. Русский:»,
nnm «Russian (Dolby AC3, 6 ch) …» без префикса (1 483), «Аудио (rus):».
* **«Аудио:» без номера** исключено намеренно (сводка «Аудио: Русский, английский (AC3…)»), но
~1 000 таких строк (kinozal 184, nnm 378, rt 463) — одна дорожка с типом/студией («Аудио: AC3, 6 ch, 448 kbps - Русский (MVO, Jaskier)»).
Нужно отличать сводку из нескольких языков от одиночной дорожки, а не снимать правило.
* **мимо codec_segments, но ловит RE_AUDIO_TRACK** (1 378 / 2 224 / 3 025): кодек-строка со студией,
но без тип-слова — «Аудио Rus: AC3 … |HDRezka|», «… | AlexFilm», «- Rus (HDRezka Studio)».
`RE_CODEC_TRACK` требует тип озвучки, и на kinozal это та же студия из п. 1.
* «АVO / МVO» с кириллической буквой — 99 строк, `\bAVO\b` их не видит.
### 4. Мусор в колонке studio меток
| вид | kinozal | nnm | rutracker |
|---|---|---|---|
| источник: Лицензия / Blu-ray CEE/EUR / iTunes / Netflix | 245 | 590 | 231 |
| «X по заказу Y» | 131 | 67 | 120 |
| язык-префикс «Русский, - AlexFilm», «Украинский, AMC» | 72 | — | 13 |
| тип-слово («дубляж», «полное дублирование») | 45 | 41 | 19 |
| каналы/кодек («6 ch», «AC3, 6ch, 448 кбит/с Ист-Вест») | 7 | 89 | 18 |
| язык вместо студии («Русский», «Украинский») | 7 | 26 | 18 |
| неру дорожка (казахский, «+ Английский + Комментарии») | 4 | 6 | 26 |
Всего ~1 800 строк (≈ 4 %, вместе с «субтитрами» в studio). Часть уже чистит prepare_data («мусорный studio 155»), остальное идёт в обучение.
### 5. Релизы без меток
| | без меток | нет строк дорожек | «не требуется»/немой | строки без типа | **есть тип озвучки** |
|---|---|---|---|---|---|
| kinozal | 609 | 15 | 186 | 407 (русские фильмы «Аудио: Русский (AC3…)») | 1 |
| nnm | 3 592 | 1 605 | 520 | 1 050 | **417** |
| rutracker | 3 424 | 1 537 | 535 | 790 | **562** |
legacy и `ok=False` среди них нет — все страницы обычные. ~980 релизов nnm/rutracker с
настоящими дорожками сборщик не разметил: «Перевод 1: Профессиональный (двухголосый закадровый)
Пётр Гланц и Евгения Лютая», «Перевод: Двуголосый (НТВ+)», аниме «Озвучка: … от StudioBand»,
«Аудио 1: AC-3, RUS(int) … | Озвучкa: Дубляж Reanimedia». «Строки без типа» — в основном
«Перевод: DreamersTeam» / «Озвучка: Persona99» (тип неизвестен, но студия есть).
## План (по ожидаемому эффекту на e2e)
Каждый пункт: сначала `--dry-run` со счётом строк, потом prepare → train, ворота — e2e
`tests/e2e_segmenter.py` (E2E_MAX=400) и joint exact на test-split против текущей `models/best`.
1. **Метки из кодек-строк** (kinozal 3 240, nnm 1 003, rt 531 студий; 2 277 релизов, где у меток нет
студий вовсе). В `harvest_core` + `tools/relabel_studios.py`: студия из кодек-строки русской
дорожки, которой нет среди меток, → заполнить studio-less строку того же типа или добавить строку
`studio_source=audio`, `raw` = кодек-строка. Ожидание: kinozal recall студий (сейчас 0.93 по
парам, но в gold этих студий просто нет — метрика тоже сдвинется, сравнивать на одном gold).
Dry-run: число строк по трекерам, выборка 50 глазами.
2. **Разбор списков студий в одной «Перевод:»** (~2 100 студий nnm+rt): одна дорожка на студию с
общим типом; «Дублированный (A, B), Многоголосый (C, D)» — тип у каждой группы свой.
3. **Доразметка ~980 релизов без меток с типом озвучки** (nnm 417, rt 562): найти, почему сборщик
их пропустил (вероятно, формат «Перевод 1:» в nnm и «Озвучка: … от X» в аниме), поправить
парсер, прогнать relabel.
4. **Форматы кодек-строк в правилах**: расширить `RE_AUDIO_TRACK` / `RE_AUDIO_PREFIX` на
«Аудио pic RUS», «Аудио N Rus:», «Аудио #N Русский:», «Звук N:», «• №N:», «Russian (…)»
без префикса; одиночное «Аудио:» с типом/студией; `RE_CODEC_TRACK` — студия из словаря без
тип-слова; кириллические «АVO/МVO». Тесты на каждый формат. Даёт метки сегментатору
(переобучение сегментатора — только если e2e лучше текущих весов) и покрытие фолбэка.
5. **Чистка мусорных студий** — после решений ниже; правила в prepare_data (мусор) и в сборщике
(язык-префикс, тип-слова, каналы).
## Решения за вами (не исправления)
* **Лицензия / Blu-ray CEE / iTunes / Netflix** (~1 070 строк): студия или источник дорожки?
Сейчас модель учится выдавать их как студию.
* **«X по заказу Y»** (~320): хранить целиком, только X, или X + заказчик отдельным полем?
* **«Русский, - AlexFilm»** (85): срезать язык-префикс — почти наверняка да, но это меняет метки.
* **Неру дорожки** (казахский Qazaqstan, «+ Английский + Комментарии», ~36): выкидывать из меток
или учить lang-голову (KAZ сейчас < 20 примеров и не учится).
+892
View File
@@ -0,0 +1,892 @@
# Аудит дорожек в raw-страницах (цифры)
Сгенерировано `tools/audit_tracks.py`; выводы и план — `reports/tracks_audit.md`.
## kinozal
**releases**: всего — 10541; с метками — 9932; с метками, есть недостающая студия — 1690; с метками без студий, студия есть в кодек-строке — 1129; без меток — 609
**строки**: audio — 18297; trans — 15072
**кодек-строки**: мимо RE_AUDIO_TRACK | мимо codec_segments — 11480; RE_AUDIO_TRACK | codec_segments — 5255; RE_AUDIO_TRACK | мимо codec_segments — 1378; мимо RE_AUDIO_TRACK | codec_segments — 184
**форматы мимо: seg codec**: аудио: — 10395; аудио pic rus: — 374; аудио #N русский: — 175; аудио N rus: — 62; аудио x rus: — 61; звук N: — 61; аудио #N украинский: — 41; аудио rus #N: — 33; аудио N · rus: — 20; аудио N#: — 19; аудио #N (rus): — 19; russian: — 17; • аудио №N: — 14; звук#N: — 13; русский (acN, N ch, N — 12; русская: — 11; • аудио #N русский: — 9; аудио-N: — 8; аудио #N russian: — 7; русский N (асN, N ch, — 6; аудио#N rus: — 6; • аудио N: — 5; аудио pic ukr: — 5; audio N (rus): — 5; аудио #N - (acN, N — 5
**недостающие студии: вид строки**: audio — 3240; trans — 496
**недостающие студии: топ**: HDrezka — 362; TVShows — 339; LostFilm — 211; Red Head Sound — 164; LeDoyen — 117; Jaskier — 101; MovieDalen — 89; Мосфильм-Мастер — 80; NewComers — 76; NewStudio — 71; Пифагор — 67; WinMedia — 64; Movie Dubbing — 63; Le-Production — 63; Postmodern — 61; Невафильм — 53; Кубик в Кубе — 48; Flarrow Films — 48; ViruseProject — 45; Дубляжная — 41; DniproFilm — 40; Синема УС — 35; SDI Media — 33; AlexFilm — 31; Custom — 31
**студии меток**: всего — 8462; нет в тексте/заголовке — 1
**форматы мимо: seg**: аудио: — 167; аудио : — 13; audio: — 4
**без меток**: обычный, есть строки дорожек — 594; обычный, нет строк дорожек — 15
**форматы мимо: codec**: аудио rus: — 662; аудио N: — 494; аудио #N: — 153; аудио ukr: — 26; аудиоN: — 9; аудио N. русский: — 7; аудио#N: — 6; аудио N.N: — 6; аудиодорожка N: — 6; аудио №N: — 3; аудио N. украински — 3; audio N: — 2; аудио N. украинский: — 1
**студии меток: нет в тексте, studio_source**: audio — 1
**мусор в studio меток**: источник: лицензия/Blu-ray/iTunes/Netflix — 245; «X по заказу Y» — 131; язык-префикс «Русский, - X» — 72; тип-слово — 45; каналы/кодек — 7; язык вместо студии — 7; субтитры — 7; неру дорожка — 4
<details><summary>примеры fmt:codec (1378)</summary>
- 2070202 · Аудио Rus: pic AC3, 48 kHz, 2 ch, 192 kbps \|HDRezka\|
- 1914764 · Аудио 06: Русский, AC-3, 48 kHz, 2 ch, 192 kb/s - Д. Пучков "Goblin" (2-5 сезоны)
- 2124461 · Аудио 7: Russian E-AC3 / 5.1 / 48 kHz / 1024 kbps \|Ю. Живов\| поздний
- 2158278 · Аудио Rus: AC3, 48 kHz, 2 ch, 192 kbps \|HDRezka\|
- 1630832 · Аудио #7: DTS-HD Master Audio Russian 7.1 / 48 kHz / 4232 kbps / 24-bit (DTS Core: 5.1 / 48 kHz / 1509 kbps / 24-bit) \| Юрий Немахов
- 1769927 · Аудио 3: AC3, 48 kHz, 2 ch, 192 kbps - Rus (HDRezka Studio)
- 2149720 · Аудио Rus: AC3, 48,0 kHz, 2 ch, 192 Кбит/сек \| AlexFilm
- 1795296 · Аудио #01: 48 kHz, DTS-HD MA, 5.1, 2239 Kbps \| Лицензия \| RUS \|
- 2105709 · Аудио Rus: AC3, 48,0 kHz, 6 ch, 384 Кбит/сек \|TVShows\|
- 2153009 · Аудио Rus: AC3, 48,0 kHz, 2 ch, 192 Кбит/сек \| Viju
- 1716051 · Аудио 4: Russian DTS / 5.1 / 48 kHz / 1509 kbps / 24 bits \|Ю. Живов\|
- 2145084 · Аудио Rus: AC3, 48,0 kHz, 2 ch, 192 Кбит/сек \| Red Head Sound
- 2005766 · Аудио 1: AC3, 2 ch, 192-384 kbps, русский (ПM HDRezka) (18+) без цензуры (1-10 серии)
- 1581690 · Аудио 4: Russian DTS-HD Master Audio / 7.1 / 48 kHz / 4829 kbps / 24-bit (DTS Core: 5.1 / 48 kHz / 1509 kbps / 24-bit) \|Ю. Сербин\|
- 1780718 · Аудио 6: Russian AC3 / 2.0 / 48 kHz / 192 kbps \|Д. Пучков aka Goblin\|
</details>
<details><summary>примеры fmt:seg (184)</summary>
- 2017336 · Аудио: E-AC3, 6 ch, 256 Kbps - Украинский Дублированный (Original)
- 2009954 · Аудио: AC3, 6 ch, 448 kbps \| русский (VO Александр Дасевич по переводу Groby)
- 1521470 · Аудио: Русский 48 kHz, (AC3), 6ch, 640 kbps [Профессиональный (полное дублирование)]
- 1991298 · Аудио: Е-AC3, 6 ch, 384 Kbps - Русский (DUB, LeDoyen)
- 1579711 · Аудио: Russian: 48 kHz, АС3, 2 ch, 192 kbps / 16-bit_\|Дубляж_[ТВ3]_\|_
- 2129647 · Аудио: Russian: AC3, 448 Kb/s (6 ch) \| DUB \| Марафон
- 2009954 · Аудио: AC3, 6 ch, 448 kbps \| русский (VO Вячеслав Замез по переводу Groby)
- 1481240 · Аудио: Russian / DTS Audio / 5.1 / 48 kHz / 768 kbps \| Дубляж, Blu-ray CEE
- 2138814 · Аудио: AC3, 48 kHz, 6 ch, ~384.00 kbps avg \| русский \| DUB \| WinMedia
- 1776297 · Аудио: 48 kHz, AC3, 5.1, 448 Kbps \| MVO \| RUS \|_Профессиональный (многоголосый, закадровый) \| HDRezka Studio_присутствует ненормативная лексика
- 1982638 · Аудио: Е-AC3, 6 ch, 640 Kbps Украинский профессиональный дублированный \| Postmodern (Netflix)- (8 из 8)
- 2158258 · Аудио: АC3, 48 kHz, 6 ch, ~640.00 kbps avg \| русский \| DVO \| ViruseProject \| 18+
- 2158258 · Аудио: АC3, 48 kHz, 2 ch, ~192.00 kbps avg \| русский \| DUB \| HDRezka Studio \| 18+
- 2149774 · Аудио: Dolby AC3 48000Hz 2ch 192kbps [rus DVO SDI Media]
- 2145264 · Аудио: АC3, 48 kHz, 2 ch, ~192.00 kbps avg \| русский \| DUB \| HDRezka Studio \| 18+
</details>
<details><summary>примеры fmt:seg codec (3000)</summary>
- 1704679 · Аудио: Русский, (AC3, 6 ch, 384 Кбит/с)
- 1504140 · Аудио: Русский (AC3, 6 ch, 640 Кбит/с), английский (DTS-HD MA, 8 ch, 2538 Кбит/с)
- 1046106 · Русский 2 (АС3, 6 ch, 448 Кбит/с) Профессиональный многоголосый [C.Р.И.]
- 1331558 · Аудио: Русский, английский (AC3, 6 ch, 448 Кбит/с)
- 1044555 · Аудио: Русский, английский (AC3, 2 ch, 192 Кбит/с), (AC3, 6 ch, 384 Кбит/с)
- 1658245 · Аудио: Русский (AAC, 2 ch, 192 Кбит/с)
- 1150580 · Аудио: Русский (АС3, 2 ch, 224 Кбит/с), английский (АС3, 2 ch, 192 Кбит/с)
- 1431453 · Аудио: Русский (AC3, 6 ch, 384 Кбит/с) английский (AC3, 2 ch, 192 Кбит/с)
- 1519187 · Аудио: Русский (AC3, 6 ch, 448 Кбит/с), английский (DTS-HD MA, 8 ch, 4090 Кбит/с)
- 1168919 · Аудио: Русский (AC3, 6 ch, 448 Кбит/с)
- 1634361 · Аудио: Русский (АС3, 6 ch, 384 Кбит/с), английский (английский, DTS-HD MA, 6 ch,1509 Кбит/с)
- 1122316 · Аудио: Русский, украинский, английский (АС3, 6 ch, 384 Кбит/с)
- 1681156 · Аудио: Русский (AC3, 2 ch, 192 Кбит/с), японский (AC3, 6 ch, 448 Кбит/с)
- 1449948 · Аудио: Русский (AC3, 6 ch, 448 Кбит/с), (DTS, 6 ch, 755 Кбит/с), украинский (AC3, 6 ch, 448 Кбит/с), английский (DTS, 6 ch, 1509 Кбит/с)
- 1663939 · Аудио: Русский (AC3, 2 ch, 192 Кбит/с), немецкий (AAC, 2 ch, 96 Кбит/с)
</details>
<details><summary>примеры ghost (1)</summary>
- 2010302 · TVShows · Russian E-AC3 / 5.1 / 48 kHz / 256 kbps \|MVO, TV Shows\|
</details>
<details><summary>примеры miss:audio (3240)</summary>
- 1899316 · Пифагор · Аудио #1 Русский: AC3, 48.0 kHz, 5.1 ch, 384 Kbps [Дубляж, Пифагор]
- 2149078 · LeDoyen · Аудио: AC3, 6 ch, 448 Kbps - Украинский (DUB, LeDoyen)
- 2147682 · Paragraph Media · Аудио #01: 48 kHz, AC3, 5.1, 384 Kbps \| Дублированный \| Paragraph Media \| RUS \|
- 2152575 · HTB · Аудио 2: AC3, 2 ch, 192 Kbps - русский (DVO HTB)
- 1981418 · HDrezka · Аудио RUS: АС3, 6 ch, 48 kHz, 448 kbps \|HDrezka Studio\|
- 2063569 · Jaskier · Аудио pic RUS: AC3, 6 ch, 48 kHz, 384 kbps \|Jaskier\|
- 1882053 · Ruscico · Аудио#1: Russian: 48 kHz, АС3, 2 ch, ~192.00 kbps avg \| Профессиональный (многоголосый, закадровый) [Сонотек, Ruscico]
- 2144862 · TVShows · Аудио 6: AC3, 6 ch, 384 Kbps - Русский (MVO, TVShows)
- 2157571 · Movie Dubbing · Аудио 1: 48.0 kHz, AC3, 6 ch, 384 Kbps - Русский (DUB, Movie Dubbing)
- 1811439 · AppleTV+ · Аудио #01: 48 kHz, E-AC3, 5.1, 768 Kbps \| Дублированный \| AppleTV+ (Пифагор) \| RUS \|
- 2146921 · Postmodern · Аудио 2: E-AC3, 6 ch, 640 Кбит/с - Украинский дубляж (Postmodern)
- 1810512 · Ю. Немахов · Аудио 10: Русский, АС3, 6 ch, 640 Кбит/с Одноголосый закадровый, Ю. Немахов
- 2151270 · СВЕТЛА · Аудио 1: AC3, 2 ch, 192 Kbps - русский (MVO Светла)
- 1984794 · DniproFilm · Аудио10: AC-3 • 5.1 • 384 kbps • MVO \| DniproFilm • UKR
- 2136518 · Red Head Sound · Аудио 6: AAC, 2 ch, 192 Kbps - Русский (MVO, Red Head Sound)
</details>
<details><summary>примеры miss:trans (496)</summary>
- 2138973 · AniStar · Озвучка: Любительский двухголосый \| AniStar (Mutsuko Air & Horie)
- 2094579 · Condor Films · Перевод: Дублированный \| Condor Films Studio (с цензурой)
- 2014244 · Кураж-Бамбей · Перевод #1: Профессиональный (одноголосый) (Кураж-Бамбей)
- 2153210 · SDI Media · Перевод #1. Профессиональный (многоголосый, закадровый) - SDI Media. Запись звука с т/к: василисса. Огромное спасибо soso4eg за релиз с переводом.
- 1937992 · HamsterStudio · Перевод: 1001cinema / HamsterStudio, присутствует ненормативная лексика.
- 2034410 · TVShows · Перевод: Профессиональный (многоголосый закадровый) (TVShows) (Flex)
- 2124828 · VSI Moscow · Перевод 1: Профессиональный (дублированный), русский (VSI Moscow)
- 2158761 · С. Визгунов · Перевод: Многоголосый профессиональный (РТР), авторский (С. Визгунов, В. Горчаков), одноголосый любительский (Сука-Падла)
- 2029926 · Невафильм · Перевод: Профессиональный (полное дублирование) [Невафильм]
- 1992132 · Инис · Перевод #2: Профессиональный двухголосый [Инис]
- 1914764 · HamsterStudio · Перевод 7: Русский, Профессиональный многоголосый закадровый - HamsterStudio
- 1860544 · Синема УС · Перевод: Любительский (многоголосый) ["Синема УС"]
- 2154508 · TVShows · Перевод: Дублированный (Red Head Sound), Многоголосый (NewComers, TVShows, LostFilm, HDRezka, Двухголосый (Кубик в кубе)
- 1805099 · Синема УС · Перевод: Любительский (двухголосый) ["Синема УС"]
- 2142169 · Megogo · Перевод №1 (русский): Дублированный \| Megogo
</details>
<details><summary>примеры noise:«X по заказу Y» (131)</summary>
- kz-2105683 · по заказу Амедиа · Перевод: Профессиональный многоголосый (Кравец-Рекордз), по заказу Амедиа (2025 год)
- kz-1796214 · Студия Кириллица по заказу Amedia · Перевод: Профессиональный многоголосый - Студия Кириллица по заказу Amedia.
- kz-2129097 · Дубляжная по заказу DEEP · Перевод: Дублированный (Дубляжная по заказу DEEP)
- kz-2158272 · AniLibria по заказу IVI · Перевод: Любительский (многоголосый закадровый) \| AniLibria по заказу IVI
- kz-2136502 · Force Media по заказу ИВИ · Перевод: Профессиональный (многоголосый закадровый) \| Force Media по заказу ИВИ \| Официальный
- kz-2150544 · Syncmer по заказу HBO · Перевод 1: Профессиональный (двухголосый закадровый) — Syncmer по заказу HBO
- kz-2127699 · Айгуль Шамсутдинова по заказу Digi Media · Озвучивание: Профессиональное (одноголосое) \| Айгуль Шамсутдинова по заказу Digi Media
- kz-1772489 · Greb & Creative по заказу Amedia · Перевод 1: профессиональный (многоголосый) Greb & Creative по заказу Amedia
- kz-1903750 · по заказу канала 2х2 · Перевод: Авторский (одноголосый) [Дмитрий Пучков] по заказу канала 2х2
- kz-2145848 · AniStar по заказу DEEP · Перевод: Любительский (многоголосый закадровый) \| AniStar по заказу DEEP \| Официальный
- kz-2006086 · студия Sigil по заказу Nickelodeon · Перевод: Дублированный \| студия «Sigil» по заказу Nickelodeon
- kz-1998690 · Novamedia по заказу Amedia · Перевод: Профессиональный (дублированный) Novamedia по заказу Amedia
- kz-2158114 · Дубляжная по заказу DEEP · Перевод: Дублированный \| Дубляжная по заказу DEEP
- kz-1475970 · По Заказу Дисней Россия · Озвучивание: Дублированное "Невафильм" (По Заказу Дисней Россия)
- kz-1791492 · Greb&CGC по заказу Amedia · Перевод: Профессиональный (многоголосый закадровый) - Greb&CGC по заказу Amedia.
</details>
<details><summary>примеры noise:источник: лицензия/Blu-ray/iTunes/Netflix (245)</summary>
- kz-2046269 · Лицензия · Перевод: Профессиональный многоголосый (Лицензия)
- kz-1868122 · Blu-ray RUS Лицензия · Перевод: Профессиональный (полное дублирование) \| Blu-ray RUS \| Лицензия
- kz-1876839 · iTunes · Перевод 1: Профессиональный (дублированный) iTunes
- kz-2083422 · лицензия BD · Перевод: Профессиональный (многоголосый закадровый) лицензия BD
- kz-2140559 · iTunes · Перевод 1: Профессиональный (дублированный) \| iTunes
- kz-2087781 · VSI Moscow по заказу Netflix · Перевод 1: Профессиональный (дублированный) VSI Moscow по заказу Netflix
- kz-1786227 · Пифагор по заказу Netflix · Перевод: Профессиональный (дублированный) Пифагор по заказу Netflix
- kz-1797177 · iTunes · Озвучивание: Дублированное \| iTunes
- kz-1990227 · Blu-ray CEE / Мост Видео · Перевод: Дублированный (Blu-ray CEE / Мост Видео)
- kz-1799697 · iTunes · Озвучивание: Дублированное \| iTunes
- kz-1901335 · iTunes · Озвучивание: Дублированное \| iTunes
- kz-1851685 · UHD Blu-Ray · Russian AC3 / 5.1 / 48 kHz / 640 kbps \|Dub, UHD Blu-Ray\|
- kz-1882179 · Akimbo Production iTunes · Перевод: Профессиональный (полное дублирование) \| Akimbo Production \| iTunes
- kz-2086101 · Пифагор по заказу Netflix · Перевод 1: Профессиональный (дублированный) Пифагор по заказу Netflix
- kz-1847720 · iTunes · Озвучивание: Дублированное \| iTunes
</details>
<details><summary>примеры noise:каналы/кодек (7)</summary>
- kz-1039053 · AC3, 6 ch, 448 Кбит/с · Перевод: Дублированный (AC3, 6 ch, 448 Кбит/с)
- kz-2156869 · AC3, 6 ch, 384 Кбит/с · Перевод 2: (AC3, 6 ch, 384 Кбит/с), Профессиональный многоголосый (TVShows)
- kz-2029179 · AC3, 6 ch, 448 Кбит/с · Озвучивание #1: Дублированное (неофициальное) [TVShows] - AC3, 6 ch, 448 Кбит/с
- kz-2029179 · AC3, 6 ch, 384 Кбит/с · Озвучивание #2: Профессиональное многоголосое [Jaskier \| 18 +] - AC3, 6 ch, 384 Кбит/с
- kz-2137517 · 192-224 kb/s, CBR VSI Moscow · Перевод: AC-3, 48.0 kHz, 2 ch (Stereo), 192-224 kb/s, CBR (Russian) \| Профессиональный (дублированный) \| VSI Moscow
- kz-2156869 · AC3, 2 ch, 192 Кбит/с · Перевод 1: (AC3, 2 ch, 192 Кбит/с), Профессиональный многоголосый (HDrezka Studio)
- kz-1824511 · DTS · Russian DTS-HD Master Audio / 5.1 / 48 kHz / 4265 kbps / 24-bit (DTS Core: 5.1 / 48 kHz / 1509 kbps / 24-bit) \|Дубляж, D
</details>
<details><summary>примеры noise:неру дорожка (4)</summary>
- kz-2124460 · Телеканал Qazaqstan · Kazakh AC3 / 1.0 / 48 kHz / 192 kbps \|Dub, Телеканал "Qazaqstan"\|
- kz-2124461 · Телеканал Qazaqstan · Kazakh AC3 / 1.0 / 48 kHz / 192 kbps \|Dub, Телеканал "Qazaqstan"\|
- kz-2035938 · Qazaqstan · Перевод 7: Профессиональный (дублированный) Qazaqstan
- kz-1081469 · Tannikka & 3opuH + Оригинальная английская дорожка · Перевод: Любительский (двухголосый, закадровый) Tannikka & 3opuH + Оригинальная английская дорожка
</details>
<details><summary>примеры noise:субтитры (7)</summary>
- kz-2070931 · Light Breeze по субтитрам tonyvika · Перевод: Любительский (двухголосый закадровый) Light Breeze по субтитрам tonyvika
- kz-2029956 · W³: voices по субтитрам ASIAN DRAGONS · Перевод 1: Любительский (двухголосый закадровый) W³: voices по субтитрам ASIAN DRAGONS
- kz-2024527 · Unicorn по субтитрам ФСГ Азалии · Перевод 1: Любительский (одноголосый закадровый) Unicorn по субтитрам ФСГ Азалии
- kz-1459417 · Расширенные места без русского озвучивания. Переведены русскими форсированными субтитрами. Включены по умолчанию · Озвучивание: Дублированное [iTunes] (Расширенные места без русского озвучивания. Переведены русскими форсированными субт
- kz-1633732 · О. Воротилин по субтитрам Zinovev · Перевод: Одноголосый закадровый О. Воротилин по субтитрам Zinov'ev
- kz-2058019 · den904 по субтитрам Катерины Яцук · Перевод: Одноголосый закадровый \| den904 по субтитрам Катерины Яцук
- kz-2156009 · Novamedia + субтитры на вырезанные цензурой места · Перевод: Профессиональный (многоголосый закадровый) Novamedia + субтитры на вырезанные цензурой места
</details>
<details><summary>примеры noise:тип-слово (45)</summary>
- kz-2097155 · полное дублирование · Перевод: Профессиональный (полное дублирование)
- kz-1577270 · полное дублирование · Перевод: Профессиональный (полное дублирование) [Лицензия]
- kz-2046404 · полное дублирование · Перевод: Профессиональный (полное дублирование)
- kz-1373383 · полное дублирование · Перевод: Профессиональный (полное дублирование)
- kz-1782714 · полное дублирование · Перевод: Профессиональный (полное дублирование) [iTunes]
- kz-1823534 · полное дублирование · Перевод: Профессиональный (полное дублирование) [iTunes]
- kz-1577726 · Дубляж · Перевод: Дубляж [Blu-ray RUS]
- kz-1806566 · полное дублирование · Перевод: Профессиональный (полное дублирование) [Пифагор]
- kz-2014726 · полное дублирование · Перевод: Профессиональный (полное дублирование) \| Официальный
- kz-2093319 · полное дублирование · Перевод: Профессиональный (полное дублирование)
- kz-2127996 · полное дублирование · Перевод: Профессиональный (полное дублирование) \| Официальный
- kz-1528391 · полное дублирование · Перевод: Профессиональный (полное дублирование) [Лицензия Blu-ray CEE]
- kz-1598461 · полное дублирование · Перевод: Профессиональный (полное дублирование)
- kz-2029926 · полное дублирование · Перевод: Профессиональный (полное дублирование) [Невафильм]
- kz-1748299 · полное дублирование · Перевод: Профессиональный (полное дублирование)
</details>
<details><summary>примеры noise:язык вместо студии (7)</summary>
- kz-2036420 · Русский · АС-3, 6 ch, 384 kbps \| Русский, MVO \| TVShows \|
- kz-1650921 · Английские · Перевод: Английские
- kz-1261226 · украинский · украинский профессиональный многоголосый [1+1
- kz-1872823 · Английский · Перевод 3: Английский дублированный
- kz-2124828 · русский · Перевод 1: Профессиональный (дублированный), русский (VSI Moscow)
- kz-2124828 · украинский · Перевод 2: Профессиональный (дублированный), украинский (Le Doyen)
- kz-1107366 · английский · Перевод: профессиональный многоголосый, R5 + оригинал (английский)
</details>
<details><summary>примеры noise:язык-префикс «Русский, - X» (72)</summary>
- kz-2136993 · Русский, - AlexFilm · Перевод 1: Русский, профессиональный многоголосый закадровый - AlexFilm
- kz-2136993 · Русский, - AlexFilm · Перевод 1: Русский, профессиональный многоголосый закадровый - AlexFilm
- kz-1986288 · Русский, - The Kitchen Russia · Перевод 2: Русский, дублированный - The Kitchen Russia
- kz-1986288 · Русский, - The Kitchen Russia · Перевод 2: Русский, дублированный - The Kitchen Russia
- kz-1914764 · Русский, - HDRezka · Перевод 5: Русский, Профессиональный многоголосый закадровый - HDRezka Studio
- kz-1986288 · Русский, - Good People · Перевод 1: Русский, профессиональный многоголосый закадровый - Good People
- kz-2136993 · Украинский, - Цікава Ідея · Перевод 3: Украинский, профессиональный многоголосый закадровый - Цікава Ідея
- kz-2136993 · Русский, - AlexFilm · Перевод 1: Русский, профессиональный многоголосый закадровый - AlexFilm
- kz-2136993 · Русский, - TVShows · Перевод 2: Русский, профессиональный многоголосый закадровый - TVShows
- kz-1202498 · Русские, английские · Перевод: Русские, английские
- kz-1996184 · Русский, - Red Head Sound · Перевод 1: Русский, дублированный - Red Head Sound
- kz-2136993 · Украинский, - Цікава Ідея · Перевод 3: Украинский, профессиональный многоголосый закадровый - Цікава Ідея
- kz-2081511 · Русские, английские · Перевод: Русские, английские
- kz-1914764 · Русский, - IdeaFilm · Перевод 6: Русский, Профессиональный многоголосый закадровый - IdeaFilm
- kz-1986288 · Русский, - TVShows · Перевод 3: Русский, профессиональный многоголосый закадровый - TVShows
</details>
<details><summary>примеры unlab (594)</summary>
- 2014296 · Гардемарины 1787 (Дилогия) / 2020-2023 / РУ, СТ / WEB-DLRip · Аудио: Русский (MP3, 2 ch, 192 Кбит/с)
- 1018580 · Орда / 2012 / РУ / BDRip (AVC) · Аудио: Русский (AC3, 6 ch, 448 Кбит/с)
- 1604585 · Большой (Расширенная ТВ-версия) (1-2 серии из 2) / 2016 / РУ / HDTV (1080i) · Аудио: Русский (MP2, 2 ch, 192 Кбит/с)
- 2148998 · Где моё кольцо? (Операция "Гүлдер") / 2020 / РУ / WEB-DL (1080p) · Аудио: Русский (AAC, 2 ch, 192 Кбит/с)
- 287608 · На краю стою (Гульфара) / 2008 / РУ / DVD-9 · Аудио: Русский (Dolby AC3, 6 ch), (DTS, 5 ch)
- 2128892 · Без предметов (Без объектов) / Sans objets / 2020 / БП / WEB-DL (1080p) · Перевод: Не требуется
- 861037 · Тихая семейная жизнь (2 серии из 2) / 2008 / РУ / DVD-9 · Аудио: Русский (Dolby AC3, 6 ch)
- 2088285 · Красный карандаш (Красный карандашик) / Sartulis / 2014 / БП / WEBRip · Перевод: Не требуется
- 1508276 · Взломать блогеров / 2016 / РУ / WEB-DL (720p) · Аудио: Русский (AC3, 6 ch, 384 Кбит/с)
- 2158475 · Третий удар (Южный узел) / 1948 / РУ, СТ / DVDRip · Аудио: Русский (AC3, 2 ch, 256 Кбит/с)
- 1206915 · Рихард Вагнер - Тангейзер (Большой театр Лисео) / Richard Wagner - Tannhauser (G · Перевод: Не требуется
- 2153202 · Тимур и его команда / 1940 / РУ, СТ / IPTV · Аудио: Русский (AAC, 2 ch, 125 Кбит/с)
- 715445 · Адмиралъ / 2008 / РУ / BDRip · Аудио: Русский (AC3, 6 ch, 448 Кбит/с)
- 2141802 · Марк и Галя (1 сезон: 1-5 серии из 5) / 2023-2024 / БП / WEB-DL (1080p) · Перевод: Не требуется
- 2142544 · Эпические приключения Морфа (1 сезон: 1-15 серии из 15) / The Epic Adventures of · Перевод: Не требуется
</details>
## nnm
**releases**: всего — 13677; с метками — 10085; без меток — 3592; с метками, есть недостающая студия — 1295; с метками без студий, студия есть в кодек-строке — 653
**строки**: audio — 15151; trans — 12541
**без меток**: обычный, есть строки дорожек — 1987; обычный, нет строк дорожек — 1605
**кодек-строки**: RE_AUDIO_TRACK | codec_segments — 10077; мимо RE_AUDIO_TRACK | мимо codec_segments — 2472; RE_AUDIO_TRACK | мимо codec_segments — 2224; мимо RE_AUDIO_TRACK | codec_segments — 378
**недостающие студии: вид строки**: trans — 1029; audio — 1003
**недостающие студии: топ**: LeDoyen — 69; Postmodern — 57; НТВ+ — 54; HDrezka — 53; 1+1 — 49; Так Треба Продакшн — 43; TVShows — 39; Советский дубляж — 37; ОРТ — 35; НТВ — 35; заКАДРЫ — 32; А. Михалёв — 29; А.Михалев — 28; СВ-Дубль — 28; Михалёв — 25; Ю.Живов — 24; DniproFilm — 24; LostFilm — 23; Юрий Живов — 22; Пифагор — 22; А.Гаврилов — 21; Андрей Гаврилов — 20; Киномания — 20; Le Doyen — 20; RHS — 20
**форматы мимо: seg codec**: russian (dolby acN, N — 1483; russian, acN, N/N (l, — 308; аудио: — 199; аудио (rus): — 68; ukrainian (dolby acN, N — 58; audio: — 53; russian (dts, N ch) — 35; russian acN, N khz, — 31; аудио N rus: — 25; аудио #N rus: — 21; русский: — 14; аудио »N: — 10; аудио#№N: — 9; аудио #N (rus): — 9; аудио (русский): — 9; аудио дорожка N: — 8; аудио rus(int) N: — 6; russian, dts, N/N (l, — 5; звук #N: — 5; звуковая дорожка N: — 5; аудио #N (русс): — 5; rus: — 4; ukrainian, acN, N/N (l, — 4; audio(N): — 4; аудиокодек: — 3
**форматы мимо: codec**: аудио N: — 1582; аудио #N: — 316; аудио#N: — 132; аудио rus: — 74; аудио №N: — 40; аудиоN: — 30; audio #N: — 20; аудиодорожка N: — 13; аудио # N: — 4; аудио N : — 4; audio N: — 4; аудио№N: — 2; аудио #N. acN, N ch — 2; аудио rus : — 1
**студии меток**: всего — 10003; нет в тексте/заголовке — 22
**форматы мимо: seg**: аудио: — 370; аудио N: — 8
**студии меток: нет в тексте, studio_source**: release — 20; tech — 2
**мусор в studio меток**: источник: лицензия/Blu-ray/iTunes/Netflix — 590; каналы/кодек — 89; «X по заказу Y» — 67; тип-слово — 41; язык вместо студии — 26; субтитры — 7; неру дорожка — 6
<details><summary>примеры fmt:codec (2224)</summary>
- 649043 · Аудио #3: MP3, 44.1 KHz, 2 ch, 128 Kbps, CBR (Русский)
- 626763 · Аудио #1: Русский (MP3, 2ch, 48 kHz, 128 kb/s)
- 1422226 · Аудио#1: AC3, 2 ch, 192 Kbps \| Русский
- 1728373 · Аудио #1: MP3, 2 ch, 128 Kbps (Русский) 18+ (Ненормативная лексика!)
- 655684 · Аудио 1: Dolby AC3, 48000Hz, 6ch, 384Kbps (русский)
- 1272634 · Аудио #3: AC3, 6 ch, 384 kbps - русский (АVO, Ю. Живов)
- 1735316 · Аудио 1: AC3, 2 ch, 192 Kbps, Русский, (Movie Dubbing / Bravo Records Georgia)
- 1292412 · Аудио #1: AC3, 2 ch, 192 Kbps \| Русский HDRezka
- 1771613 · Аудио 4: Е-AC3, 8 ch, 1536 Kbps, русский (АVO - В. Дохалов)
- 892406 · Аудио #1: AC3, 48.0 kHz, 2 ch, 192 kbps (Русский)
- 1608297 · Аудио 2: AAC, 48.0 KHz, 192 kbps, 2 ch (Русский, AniLibria.TV)
- 1439239 · Аудио1: AC3, 6 ch, 640 Kbps - Русский (LostFilm)
- 220287 · Аудио 1: (rus) 48 kHz, AC3 Dolby Digital, 1/0 (C) ch, 192 kbps avg
- 1222018 · Аудио #1: AC-3, 2 ch, 192 Kbps \| Русский
- 1418749 · Аудио#1: AC3, 2 ch, 192 Kbps \| Русский
</details>
<details><summary>примеры fmt:seg (378)</summary>
- 1009049 · Аудио: Русский: FLAC, 2 ch, 652 kbps, 48,0 КГц, 16 bit MVO Mediainfo:
- 482371 · Аудио: (Dolby AC3, 2 ch) ~192 kb/s \|Русский,одноголосый закадровый\|
- 1197476 · Аудио: 1 AC3, 2 ch, 192 kbps - Русский, многоголосый (ТК "Домашний")
- 453252 · Аудио: Dolby Digital Audio 5.1 / Русский / 640 Кбит/сек / 48 kHz (DUB)
- 1152863 · Аудио: AC3, 2 ch, 192 Kbps - русский, многоголосый закадровый Mediainfo:
- 1596114 · Аудио: AC3, 2 ch, 192 kbps - Русский (Дублированный)
- 553342 · Аудио: AC3, 6 ch, 384 kb/s - Русский, многоголосый
- 1054025 · Аудио: AC3, 6 ch, 640 kbps - Русский, Дубляж
- 1121293 · Аудио: 1 AC3, 6 ch, 384 kbps - Русский дубляж
- 1339316 · Аудио: MP3, 2 ch, 192 Kbps - Русский (MVO) MediaInfo:
- 1651071 · Аудио 5: TrueHD + Atmos, 8 ch, ~3661 Kbps - Українська (MVO, HDRezka Studio)
- 1196431 · Аудио: AC3, 6 ch, 384 kbps - Украинский, Дубляж
- 603650 · Аудио: (Dolby AC3, 2 ch) ~192 kb/s \|Русский, многоголосый закадровый\|
- 1432165 · Аудио: AC3, 6 ch, 384 Kbps - Русский, дубляж (iTunes) MediaInfo:
- 1564969 · Аудио: AC3, 2 ch, 192 Kbps - Русский (MVO, TVShows) MediaInfo:
</details>
<details><summary>примеры fmt:seg codec (2472)</summary>
- 961845 · Аудио #1 Rus: AC3, 2 канала, 384 Кбит/сек, CBR - многоголосый закадровый
- 464967 · Russian (Dolby AC3, 2 ch)
- 624704 · Russian (Dolby AC3, 6 ch)
- 1655275 · Russian, AC3, 2/0 (L,R) ch, 192 kbps, Delay 0 mSec
- 1617406 · Russian (Dolby AC3, 2 ch)
- 649787 · Russian (Dolby AC3, 2 ch)
- 597243 · Russian (Dolby AC3, 6 ch)
- 635903 · Russian (Dolby AC3, 2 ch)
- 643340 · Russian (Dolby AC3, 2 ch)
- 419146 · Russian (Dolby AC3, 2 ch)
- 1457385 · Russian (Dolby AC3, 2 ch)
- 1745806 · Аудио: AAC LC, 2 ch, 48.0 kHz, 192 kbps \| Звук: Русский
- 1514814 · Russian (Dolby AC3, 2 ch)
- 449635 · Russian, AC3, 2/0 (L,R) ch, 192 kbps, Delay 0 mSec
- 338450 · Аудио дорожка 1: 48,0 КГц, AC3, 192 Кбит/сек, 2 канала, русский, полное дублирование - СССР
</details>
<details><summary>примеры ghost (22)</summary>
- 1887058 · НТВ СТС · Аудио 4: AC3, 2 ch, 192 Kbps - русский (MVO НТВ \| СТС)
- 1701274 · ТК ОРТ · Аудио 07: AC3, 2 ch, 192 Kbps - Русский (MVO, ТК "ОРТ")
- 1887072 · НТВ СТС · Аудио 4: AC3, 2 ch, 192 Kbps - русский (MVO НТВ \| СТС)
- 1700762 · ТК СТС · Аудио 03: AC3, 2 ch, 192 Kbps - Русский (MVO, ТК "СТС")
- 1720458 · ТК НТВ+ · Аудио 5: AC3, 2 ch, 192 Kbps - Русский (MVO, ТК "НТВ+")
- 1671752 · TVShows · Аудио 7: AС3, 6 ch, 640 kbps - Русский, MVO (TV Shows)
- 1723189 · ТК СТС · Аудио 06: AC3, 2 ch, 192 Kbps - Русский (MVO, ТК "СТС")
- 1700762 · ТК НТВ · Аудио 06: AC3, 2 ch, 192 Kbps - Русский (MVO, ТК "НТВ")
- 1883291 · НТВ ТНТ Ren-TV · Аудио 2: AC3, 2 ch, 192 Kbps \| Русский, MVO (НТВ \| ТНТ \| Ren-TV)
- 1721934 · ТК ОРТ · Аудио 07: AC3, 2 ch, 192 Kbps - Русский (MVO, ТК "ОРТ")
- 1671752 · TVShows · Аудио 6: AС3, 6 ch, 384 kbps - Русский, MVO (TV Shows) - псевдо-дубляж
- 1723189 · ТК НТВ+ · Аудио 08: AC3, 2 ch, 192 Kbps - Русский (MVO, ТК "НТВ+")
- 1701066 · ТК ОРТ · Аудио 12: AC3, 2 ch, 192 Kbps - Русский (MVO, ТК "ОРТ")
- 1700762 · ТК ОРТ · Аудио 04: AC3, 2 ch, 192 Kbps - Русский (MVO, ТК "ОРТ")
- 1721934 · ТК РТР · Аудио 08: AC3, 2 ch, 192 Kbps - Русский (MVO, ТК "РТР")
</details>
<details><summary>примеры miss:audio (1003)</summary>
- 1763763 · TVShows · Аудио 2: AC-3, 2 ch, 48.0 kHz, 192 kbps \| Звук: Русский дубляж [TVShows]
- 1541528 · Инис · Аудио 2: AC3, 2 ch, 192 Kbps (русский, многоголосый - студия ИНИС)
- 1807127 · DniproFilm · Аудио 10: AC3, 6 ch, 640 Kbps - Украинский (MVO - DniproFilm)
- 1328029 · Film Prestige · Аудио 1: AC3, 2 ch, 224 Kbps (русский - Film Prestige)
- 1520154 · Королев · Аудио:5 DTS XLL, 8 ch, 3898 Kbps - Русский, AVO (В.Королёв)
- 857991 · П. Гланц и Т. Казакова · Аудио 2: Русский AC3 5.1, 384 Kbps, 48.0 KHz (Двуголосый) П. Гланц и Т. Казакова
- 1381272 · ОРТ · Аудио #2: AC3, 48 kHz, 6 ch, 448 kbps - Русский, Dub \| ОРТ
- 1771912 · 1+1 · Аудио5: AC3, 2 ch, 256 Kbps - Украинский, DVO (1+1)
- 1780225 · Postmodern · Аудио 5: Е-AC3, 2 ch, 256 Kbps - Украинский (DUB, Postmodern)
- 872505 · Кашкин · Аудио 5: Русский 48 kHz/24-bit, DTS-HD MA, 6 каналов ~4129 kbps \|Авторский, Кашкин (Первомайский)\|
- 1741989 · LeDoyen · Аудио 5: AC3, 6 ch, 384 Kbps - Украинский (DUB, LeDoyen)
- 1499836 · А. Михалёв · Аудио 7: DTS, 6 ch, 1509 Kbps (русский, одноголосый - А. Михалёв)
- 1888656 · LeDoyen · Аудио 6: E-AC3, 6 ch, 640 Kbps - Украинский (DUB, LeDoyen)
- 1872532 · Postmodern · Аудио 2: AC3, 6 ch, 640 Кbps - Украинский (Dub, Postmodern)
- 949884 · Л.Володарский · Аудио #2: AC-3, 48.0 KHz, 2 ch, 192 Kbps, CBR (Russian) - авторский Л.Володарский
</details>
<details><summary>примеры miss:trans (1029)</summary>
- 1887475 · НТВ+ · Перевод: профессиональный многоголосый (Светла), профессиональный двухголосый (НТВ+)
- 306718 · Союзмультфильм · Перевод: Дублированный [Союзмультфильм]
- 1801712 · ICTV · Перевод: дублированный неофициальный (Мост-Видео), профессиональный многоголосый (Novamedia, НТВ, Fox Crime, ICTV), авторский (Антон Карповский, Юрий Сербин, Дм
- 241721 · Гланц · Автор перевода: Петр Гланц
- 1891998 · СВ-Дубль · Перевод: двуголосый профессиональный (СВ-Дубль)
- 1896364 · Postmodern · Перевод: Дублированный (Movie Dubbing, укр. Postmodern)
- 897659 · СВЕТЛА · Перевод: Двуголосый (Светла), Одноголосый (Г.Либергал)
- 1181555 · LostFilm · Перевод: Многоголосый, профессиональный - NewStudio, LostFilm
- 1180186 · BadBajo · Перевод: Одноголосый авторский, А.Важенин (BadBajo)
- 541044 · Zamez · Перевод: Любительский (одноголосый) [zamez]
- 1888887 · Так Треба Продакшн · Перевод: Дублированный (Movie Dubbing, укр. Так Треба Продакшн)
- 434267 · Позитив · Перевод: Профессиональный (двухголосый, закадровый) НТВ+, профессиональный (многоголосый, закадровый) Позитив, авторский (одноголосый, закадровый) Алексей Михал
- 1884985 · AniLiberty · Перевод: AniLiberty, Sanae, Alvakarp
- 848518 · Нева-1 · Перевод: Советский Дубляж + Дубляж от "Нева-1" + Многоголосый
- 1872380 · Так Треба Продакшн · Перевод: Дублированный (Videofilm Int., укр. Так Треба Продакшн)
</details>
<details><summary>примеры noise:«X по заказу Y» (67)</summary>
- nnm-1867973 · по заказу Смотри кино · Аудио 2: AC3, 2 ch, 192 Kbps - Русский (MVO, по заказу "Смотри кино" 18+)
- nnm-1878037 · Paragraph Media по заказу Capella Film · Аудио 1: AC3, 2 ch, 192 Kbps - русский (MVO Paragraph Media по заказу Capella Film)
- 1875813 · СВ-Дубль по заказу ТК Культура · Аудио 04: AC3, 2 ch, 192 Kbps - Русский, DVO (СВ-Дубль по заказу ТК Культура)
- nnm-1379089 · студия Novamedia по заказу Amedia · Перевод: Многоголосый профессиональный (студия Novamedia по заказу Amedia), Aвторский (Ю. Сербин)
- 1229051 · студия Кравец по заказу Amedia · Перевод: Дублированный - студия "Кравец" по заказу Amedia
- 1854965 · студия TrueDubbing Studio по заказу Paramount Comedy · Аудио 3: AC3, 2 ch, 192 Kbps - Русский, MVO (студия "TrueDubbing Studio" по заказу Paramount Comedy)
- nnm-1499746 · по заказу Amedia · Аудио 1: AC3, 6 ch, 384 Kbps, русский (по заказу Amedia)
- 1865453 · студия TrueDubbing Studio по заказу Paramount Comedy · Аудио 2: AC3, 2 ch, 192 Kbps - Русский, MVO (студия "TrueDubbing Studio" по заказу Paramount Comedy)
- nnm-1087457 · 6 Канал, по заказу СТС · Аудио#3: AC3, 2 ch, 192 Kbps \| Русский - MVO (6 Канал, по заказу СТС)
- 1854965 · студия Нота по заказу РТР · Аудио 2: E-AC3, 2 ch, 224 Kbps - Русский, MVO (студия "Нота" по заказу РТР)
- nnm-806894 · студия NovaMedia по заказу Amedia · Перевод: Профессиональный, многоголосый закадровый - студия NovaMedia по заказу Amedia
- nnm-1797122 · Лексикон по заказу Гала Медиа · Аудио 1: AC3, 2 ch, 192 Kbps \| русский (MVO Лексикон по заказу Гала Медиа)
- nnm-1896805 · Селена Интернешнл по заказу ОРТ · Аудио 3: AC3, 2 ch, 192 Kbps - Русский, DVO (Селена Интернешнл по заказу ОРТ)
- nnm-1896063 · Велес по заказу CP Digital · Аудио 2: AC3, 2 ch, 224 Kbps - русский (DVO Велес по заказу CP Digital (2008 г., Станислав Войнич и Светлана Репетина))
- 1896444 · Студия Нота по заказу РТР · Аудио 04: AC3, 2 ch, 192 Kbps - Русский, MVO (Студия Нота по заказу РТР)
</details>
<details><summary>примеры noise:источник: лицензия/Blu-ray/iTunes/Netflix (500)</summary>
- nnm-1437447 · iTunes · Аудио 1: AC3, 6 ch, 384 kbps - Русский (DUB \| iTunes)
- nnm-1807653 · Blu-ray · Аудио 1: AC3, 6 ch, 640 Kbps русский ( DUB, Blu-ray)
- nnm-1525970 · iTunes · Аудио 1: AC3, 6 ch, 384 kbps - Русский (DUB, iTunes)
- nnm-1372998 · Blu-Ray · Аудио 01: DTS-HD MA, 8 ch, 3091 Kbps - Русский (DUB, Blu-Ray)
- nnm-1641984 · Лицензия · Аудио 1: AC3, 6 ch, 640 Kbps - Русский, Дубляж (Лицензия)
- nnm-1515035 · Blu-Ray CEE · Аудио 2: AC3, 6 ch, 448 Kbps - Русский (DUB \| Blu-Ray CEE)
- nnm-1026540 · iTunes · Аудио: AC3, 6 ch, 384 kbps, CBR, Русский (iTunes)
- nnm-1691690 · Blu-Ray CEE · Аудио 1: AC3, 6 ch, 640 Kbps - Русский (Dub, Blu-Ray CEE)
- nnm-1532438 · CPI Films, iTunes · Аудио 1: AC3, 6 ch, 384 Kbps - Русский (Dub CPI Films, iTunes)
- nnm-1098424 · iTunes · Аудио 1: AC3 5.1 384 Кбит/сек - Русский (iTunes)
- nnm-1404235 · BD CEE · Аудио 1: AC3, 6 ch, 640 kbps, Русский (DUB BD CEE)
- nnm-1635787 · BD CEE · Аудио 1: E-AC3, 6 ch, 448 kbps - Русский (Dub,BD CEE)
- nnm-1571635 · BD EUR · Аудио 1: AC3, 6 ch, 640 kbps - Русский (Dub, BD EUR)
- nnm-1607165 · BD EUR · Перевод: Дублированный (BD EUR)
- nnm-1312426 · Лицензия · Аудио 1: AC3, 6 ch, 640 Kbps - Русский (DUB, Лицензия)
</details>
<details><summary>примеры noise:каналы/кодек (89)</summary>
- nnm-511174 · L,R · Аудио 1: Русский: 48 kHz, AC3, 2/0 (L,R), ~384.00 kbps avg \|Дубляж, Видеосервис\|
- nnm-388165 · L, C, R, l, r · Аудио: Русский, AC3, 48 kHz, 6 каналов, 448 kbps, 16 bit, (L, C, R, l, r) + LFE \|Гоблинский, Многоголосный\|
- nnm-505307 · AC3, 2 ch, 192 kb/s · Аудио #3: Русский ( AC3, 2 ch, 192 kb/s ) (одноголосный)
- nnm-849036 · L,C,R,l,r · Аудио 2: Русский 48 kHz, AC3, 3/2 (L,C,R,l,r) + LFE ch, 640 kbps avg \| Многоголосый, Superbit
- nnm-471722 · L,C,R,l,r · Аудио#1: Русский: 48 kHz, AC3, 3/2 (L,C,R,l,r) + LFE ch, ~640 kbps avg \|Дубляж, Blu-ray CEE\|
- nnm-320358 · AC3, 6 ch, 448 kb/s · Аудио #1: Русский (AC3, 6 ch, 448 kb/s)
- nnm-492468 · L, R · Аудио 2: Русский, AC-3, 192 kbps, 2 канала, 16 bit, (L, R) - Многоголосый
- nnm-508334 · Dolby AC3, 6 ch · Аудио 2: Русский (Dolby AC3, 6 ch) 384 kb/s - Многоголосый,профессиональный - 20th Century Fox MI
- nnm-494281 · L, C, R, l, r · Аудио 1: Русский, DTS, 755 kbps, 6 каналов, 24 bit, (L, C, R, l, r) + LFE
- nnm-300455 · DTS, 6 ch, 755 kb/s · Аудио #3: Русский (DTS, 6 ch, 755 kb/s) одноголосый закадровый - Гаврилов
- nnm-374286 · AC3, 6 ch, 384 kb/s · Аудио #2: Русский ( AC3, 6 ch, 384 kb/s )
- nnm-380475 · L,C,R,l,r · Аудио#3: Русский: 48 kHz, AC3, 3/2 (L,C,R,l,r) + LFE ch, ~384.00 kbps avg \|Дубляж, R5\|
- nnm-356829 · AC3, 6 ch, 384 kb/s · Аудио #2: Русский ( AC3, 6 ch, 384 kb/s )
- nnm-1057579 · L,R · Аудио#1: Русский - 48 kHz, AC3, 2/0 (L,R) ch, 192 kbps avg
- nnm-369518 · AC3, 6 ch, 448 kb/s · Аудио #2: Русский (AC3, 6 ch, 448 kb/s)
</details>
<details><summary>примеры noise:неру дорожка (6)</summary>
- nnm-29325 · песни на английском языке · Перевод: Многоголосый профессиональный (песни на английском языке)
- nnm-1784983 · Казахфильм · Перевод: Дублированный (Казахфильм)
- nnm-1571754 · Cinema Tone Production, Казахстан · Аудио 1: AC-3, 2 ch, 192 Kbps - Русский (DUB \| Cinema Tone Production, Казахстан)
- nnm-1572189 · Cinema Tone Production, Казахстан · Аудио 1: AC-3, 2 ch, 192 Kbps - Русский (DUB \| Cinema Tone Production, Казахстан)
- nnm-51930 · слышен английский язык · Перевод: Профессиональный многоголосый (слышен английский язык)
- nnm-1450412 · Казахский язык · Перевод: Дублированный (Казахский язык)
</details>
<details><summary>примеры noise:субтитры (7)</summary>
- nnm-1843999 · Мика Бондарик, по субтитрам Lisok · Аудио 1: AC3, 2 ch, 192 Kbps \| русский (VO Мика Бондарик, по субтитрам Lisok)
- nnm-1894229 · Мика Бондарик, по субтитрам Lisok · Аудио 1: AC3, 2 ch, 192 Kbps - русский (VO Мика Бондарик, по субтитрам Lisok)
- nnm-1894835 · А. Наследников по субтитрам lobo · Аудио 1: AC3, 2 ch, 192 Kbps - русский (VO А. Наследников (MrRose) по субтитрам lobo)
- nnm-1894859 · А. Наследников по субтитрам lobo · Аудио 1: AC3, 2 ch, 192 Kbps - русский (VO А. Наследников (MrRose) по субтитрам lobo)
- nnm-1622631 · Andre1288 по субтитрам Антона Каптелова · Аудио 1: AC3, 2 ch, 192 kbps \| русский (VO Andre1288 по субтитрам Антона Каптелова)
- nnm-1874776 · Матвей Кенс по субтитрам Silken · Аудио 1: AC3, 2 ch, 192 Kbps - русский (VO Матвей Кенс по субтитрам Silken)
- nnm-1894210 · Мика Бондарик, по субтитрам Lisok · Аудио 1: AC3, 2 ch, 192 Kbps - русский (VO Мика Бондарик, по субтитрам Lisok)
</details>
<details><summary>примеры noise:тип-слово (41)</summary>
- nnm-857721 · Двуголосый · Аудио 4: Русский AC3 5.1, 384 Kbps, 48.0 KHz (Двуголосый) П. Гланц и Т. Казакова
- nnm-114327 · дубляж · Аудио1: Русский AC3 5.1, 48 KHz, 384 kbps (дубляж)
- nnm-389317 · Дубляж · Аудио 1: DTS Audio, Русский, 768 kbps, 5.1 / 48 kHz / 768 Кбит/сек / 24-bit (Дубляж)
- nnm-1365221 · Дубляж · Аудио 1: AC3, 6 ch, 448 Kbps - Русский (Дубляж)
- nnm-1457626 · дубляж · Аудио #1: AC3, 6 ch, 384 kbps - русский (дубляж)
- 1342511 · дубляж · Аудио #1: AC3, 6 ch, 640 kbps - русский (дубляж)
- nnm-1553559 · дубляж · Аудио1: AC3, 6 ch, 48.0 KHz, 448 Kbps \| русский (дубляж) [Студийная Банда]
- nnm-967458 · Двуголосый · Аудио 4: Русский AC3 5.1, 384 Kbps, 48.0 KHz (Двуголосый) Tycoon-Studio
- nnm-1650999 · дубляж · Аудио 1: AC3, 2 ch, 192 Kbps - русский (дубляж)
- nnm-1538061 · дубляж · Аудио #1: АС3, 6 ch, 384 kbps - русский (дубляж)
- nnm-312080 · полное дублирование · Аудио 2: DTS, 6 ch, 768Kbps, 48.0khZ Русский профессиональный (полное дублирование)
- nnm-821242 · Полное дублирование · Аудио 1: Русский AC3 5.1, 640 Kbps, 48.0 KHz (Полное дублирование)
- nnm-1181270 · дубляж · Аудио #1: AC3, 48.0 KHz, 6 ch, 384 Kbps, CBR - Русский (дубляж)
- nnm-997923 · Двуголосый · Аудио 3: Русский AC3 5.1, 384 Kbps, 48.0 KHz (Двуголосый) П. Гланц и Т. Казакова
- nnm-1578209 · Дубляж · Аудио #1: AC3, 6 ch, 448 Кбит/с, русский (Дубляж)
</details>
<details><summary>примеры noise:язык вместо студии (26)</summary>
- nnm-506684 · Испанский · (Dolby AC3, 2 ch) ~192 kb/s \|Испанский,дублированный\|
- nnm-268206 · английский · Перевод: Дублированный + Оригинальный (английский,отдельно)
- nnm-1074532 · украинский · Перевод: Дублированный (украинский)
- nnm-506333 · Испанский · (Dolby AC3, 2 ch) ~192 kb/s \|Испанский,дублированный\|
- nnm-528788 · Испанский · (Dolby AC3, 2 ch) ~192 kb/s \|Испанский, дублированный\|
- nnm-537011 · Французский · (Dolby AC3, 2 ch) ~192 kb/s \|Французский, дублированный\|
- nnm-534886 · Испанский · (Dolby AC3, 2 ch) ~192 kb/s \|Испанский, дублированный\|
- nnm-701414 · русский · Аудио 1: AC3; 192 Kbps; 48 kHz; 2 ch [Русский Репортаж (русский)]
- nnm-537011 · Испанский · (Dolby AC3, 2 ch) ~192 kb/s \|Испанский, дублированный\|
- nnm-649658 · Французский · (Dolby AC3, 2 ch) ~192 kb/s \|Французский, дублированный\|
- nnm-536934 · Французский · (Dolby AC3, 1 ch) ~192 kb/s \|Французский, дублированный\|
- nnm-452881 · Русский · (Dolby AC3, 6 ch) ~384 kb/s \|Русский,дублированный\|
- nnm-587730 · Украинский · Перевод: Дублированный (Русский) + Дублированный (Украинский)
- 1839838 · Украинский · Перевод: Профессиональный многоголосый (Syncmer), Дублированный (Украинский)
- nnm-372312 · английский · Перевод: Профессиональный (двухголосый, закадровый) R.U.S.C.I.C.O / Оригинальный (английский) + Профессиональный (многог
</details>
<details><summary>примеры unlab (1987)</summary>
- 429630 · Симпсоны / The Simpsons (1989 –2012) HDTVRip [720p], TVRip (23 сезона 510 серии · Перевод:
- 1396241 · Marmalade Boy \| Мальчик-мармелад [1994, TV, 76 из 76] BDRip 960p raw · Перевод: Жанна Тачёва, Tiona, KelendrosS
- 1849320 · Umibe no Etranger \| The Stranger by the Shore \| Незнакомец на берегу моря [2020, · Аудио 1: AC-3, RUS(int), 2.0 ch, 48.0 khz, 192 kbps \| Озвучкa: Закадр. (MVO) от StudioBand
- 1008316 · Тайное вторжение / The Secret Invasion (1964) HDTV [H.264/1080i-LQ] · Перевод: Двуголосый (НТВ+)
- 428151 · Fractale / Фрактал [2011, TV, 11] BDRip 720p raw + rus · Перевод: DreamersTeam
- 482868 · Ningen Kousaten / Human Crossing Point / Точки соприкосновения [2003, TV, 13 ep. · Перевод: Whizz Описание:
- 1365052 · Hanasaku Iroha \| Hanasaku Iroha: Blossoms for Tomorrow \| Азбука Цветов [2011, TV · Озвучка: Одноголосая, Двухголосая закадровая
- 1389210 · Море Солтона / The Salton Sea (2002) WEB-DL [H.264/720p] · Перевод 1: Профессиональный (двухголосый закадровый) Пётр Гланц и Евгения Лютая
- 1360458 · Sword Art Online \| Мастера Меча Онлайн [ТВ-1] [2012, TV, 25 ep] BDRip 1080p 10-b · Озвучка: Одноголосая, Двухголосая, Многоголосая закадровая
- 1753801 · Бог располагает / Gud raader (1912) WEBRip [H.264/720p] · Перевод: немой с интертитрами (В. Сачков)
- 1860621 · Дочь магазина / Magasinets Datter (1918) WEBRip [H.264/1080p] · Перевод: немой с интертитрами
- 253552 · Метрополис / Metropolis (1927) HDTV [H.264/720p] · Перевод: Немой с интертитрами
- 1331498 · Takarajima \| Treasure Island \| Остров сокровищ [1978, TV, 26 из 26] BDRip 1080p · Перевод: Gerale + Komugi-chan
- 1818600 · Evangelion Shin Gekijouban: Jo \| Evangelion: 1.0 You Are (Not) Alone \| Evangelio · Аудио 1: AC-3, RUS(int), 2.0, 48.0 khz, 192 kbps \| Озвучкa: Дубляж Reanimedia 2.0
- 1184789 · Taboo-Tattoo \| Запретная татуировка [2016, TV, 12 эп.] BDrip 1080p raw · Перевод: AOS
</details>
## rutracker
**releases**: всего — 11166; с метками — 7742; без меток — 3424; с метками, есть недостающая студия — 972; с метками без студий, студия есть в кодек-строке — 495
**строки**: trans — 15192; audio — 11471
**кодек-строки**: RE_AUDIO_TRACK | codec_segments — 5950; RE_AUDIO_TRACK | мимо codec_segments — 3025; мимо RE_AUDIO_TRACK | мимо codec_segments — 2033; мимо RE_AUDIO_TRACK | codec_segments — 463
**форматы мимо: seg codec**: аудио: — 1066; аудио N rus: — 161; аудио rus N: — 63; русская: — 59; аудио rus N-N: — 46; аудио #N русский, — 46; russian acN, N khz, — 31; russian: — 30; russian (dolby acN, N — 29; аудио #N (rus): — 28; аудио N ukr: — 23; аудио #N (внешняя) — 18; аудио : — 16; аудиопоток №N (rus): — 15; аудио rus#N (int.): — 13; аудио rus (ext): — 13; audio #N (rus): — 10; russian acN / N.N / N — 10; аудио №N (rus): — 9; звук (rus(int)): — 8; аудио N-N: — 8; russian, aac, N.N kh — 8; аудио rus N-N (ext): — 7; rus N (ext) aac, — 7; аудио #N (ext): — 7
**студии меток**: всего — 9107; нет в тексте/заголовке — 14
**без меток**: обычный, есть строки дорожек — 1887; обычный, нет строк дорожек — 1537
**недостающие студии: вид строки**: trans — 1114; audio — 531
**недостающие студии: топ**: LeDoyen — 60; NewStation — 48; HDrezka — 46; TVShows — 45; Невафильм — 40; Jaskier — 39; Киномания — 35; 2x2 — 31; LostFilm — 31; Кубик в Кубе — 31; СТС — 29; Red Head Sound — 25; Postmodern — 24; Гоблин — 22; А.Гаврилов — 22; Так Треба Продакшн — 21; Ю.Живов — 20; Андрей Гаврилов — 19; А. Михалёв — 19; Михалёв — 19; ICTV — 19; Пифагор — 17; ОРТ — 17; 1+1 — 17; Гланц — 15
**форматы мимо: seg**: аудио: — 437; аудио : — 20; аудио N: — 4; аудио #N: — 2
**форматы мимо: codec**: аудио N: — 1856; аудио rus: — 661; аудио #N: — 354; аудиоN: — 62; аудио#N: — 44; аудио №N: — 29; аудио ukr: — 10; audio #N: — 4; аудиодорожка N: — 2; audio#N: — 1; аудио N : — 1; аудио N.N: — 1
**студии меток: нет в тексте, studio_source**: release — 7; audio — 7
**мусор в studio меток**: источник: лицензия/Blu-ray/iTunes/Netflix — 231; «X по заказу Y» — 120; субтитры — 27; неру дорожка — 26; тип-слово — 19; каналы/кодек — 18; язык вместо студии — 18; язык-префикс «Русский, - X» — 13
<details><summary>примеры fmt:codec (3000)</summary>
- 6259831 · Аудио 4: Russian E-AC3 / 7.1 / 48 kHz / 1536 kbps \|Ю. Сербин\|
- 5711781 · Аудио 4: DTS-HD Master Audio Russian 3412 kbps 5.1 / 48 kHz / 3412 kbps / 24-bit (DTS Core: 5.1 / 48 kHz / 768 kbps / 24-bit) \| А. Михалев
- 6846635 · Аудио 8: Russian AAC (Advanced Audio Codec), 44.1 kHz, 2.0 ch (Stereo), 192 kb/s \| «LE-Vitation»
- 6887455 · Аудио #1: AAC (Advanced Audio Coding), 48.0 kHz, 2.0 ch (Stereo), ~128 kb/s (Russian) \| "Сыендук" (18+)
- 6899403 · Аудио Rus: AC3, 48 kHz, 2 ch, 384 kbps \|Lostfilm\|
- 6798816 · Аудио 9: Russian / Dolby Digital Audio, 256 kbps, 2.0 / 48 kHz / 256 kbps - НТВ+ (2014 / 2016 г.)
- 4767310 · Аудио #3: Russian (Dolby AC3, 6 ch) 384 Кбит/сек, 48,0 КГц
- 6902539 · Аудио Rus: AC3, 48,0 kHz, 6 ch, 640 Кбит/сек \| Red Head Sound \| Серии: 1-8
- 4186469 · Аудио RUS: АС3, 2 ch, 48 kHz, 192 kbps \|HDrezka\|
- 5921821 · Аудио 3: AC-3 384 kb/s 6 channels, 48.0 kH (Russian) [Flarrow Films]
- 5645820 · Аудио 1: 48 kHz, AC3 Dolby Digital, 3/2 (L,C,R,l,r) + LFE ch, ~448 kbps [русский]
- 6063299 · Аудио RUS: AAC, 48.0 kHz, 2.0 ch, 192 Kbps (в составе контейнера). Озвучка: многоголосая закадровая [KANSAI Studio]
- 3930926 · Аудио 6: Russian (Dolby AC3, 2 ch (2/0 (L,R) ch), 192 kbps)
- 4667342 · Аудио #2: [UKR] 48 kHz, AC3 Dolby Digital, 2/0 (L,R) Surround Encoded ch, ~192 kbps
- 6824956 · Аудио Rus: AC-3, 48 kHz, 6 ch, 384 kbps \|TVShows\|
</details>
<details><summary>примеры fmt:seg (463)</summary>
- 5838316 · Аудио: VBR ~128 Kbps, 44.1 kHz, 2ch Язык Русский; Озвучка: VO - Sahawk (AniMedia)
- 6425819 · Аудио: AC-3, 320 кбит/с, 48.0 кГц, 2 канала Язык Русский; Озвучка: Arrow Hell (NiksoN, n0active, Libra, Mufdkl) [Многоголосая закадровая]
- 2196723 · Аудио: Английский (Dolby AC3, 6 ch,448 Kbps), Русский (Dolby AC3, 6 ch, 448 Kbps) П. Гланц,И.Королёва, Русский (Dolby AC3, 6 ch,448 Kbps) дубляж, Китайский (Dol
- 2368674 · Аудио: Russian: DTS 5.1, 768 kbps (одноголосый, Немахов) (отдельно)
- 6691277 · Аудио: AC3 Dolby Digital, 48 kHz, 2/0 ch, 192 kbps, Russian MVO
- 2822758 · Аудио: russian: AC3 48000 6ch 384 kbps \|многоголосый, закадровый, студия Kravec (lostfilm)\|
- 5749545 · Аудио: Русский; AC-3; 192 Кбит/сек; 2 канала; 48,0 КГц (Студия "Кириллица" (По заказу Disney)) Дубляж
- 5053740 · Аудио: AC-3, 192kbps, 48,0Hz, 2ch Язык Русский (в составе контейнера) ; Озвучка: одноголосая женская Persona99
- 6440498 · Аудио: AC-3, 192 кбит/с, 48.0 кГц, 2 канала Язык Русский; Озвучка AniDub (Shaman, HelgardRay, Oriko, Лизавета) [Многоголосая закадровая]
- 6222916 · Аудио: Russian / Dolby Digital Audio / 5.1 / 48 kHz / 384 kbps \| Дубляж
- 6434506 · Аудио: AC-3, 320 кбит/с, 48.0 кГц, 2 канала Язык Русский; Озвучка SHIZA Project (Dancel, DroEd, Abe, Saylot, Kitsune, Bvia) [Многоголосая закадровая]
- 6292078 · Аудио: Russian AAC / 2.0 / 48 kHz / 192 kbps \| Дубляж Невафильм \|
- 3988042 · Аудио: AC3, 2/0 (L,R) ch, 192 kbps Язык Русский (Любительский одноголосый)
- 6648621 · Аудио: AC-3, 192 kbps, 48.0 kHz, 2 ch, Язык Русский; Озвучка: многоголосая закадровая, MiraiDuB
- 4811969 · Аудио: Russian (AC3, 2 ch, 192 kbps, 16 bits, 48.0 KHz) - VO
</details>
<details><summary>примеры fmt:seg codec (2033)</summary>
- 4952029 · Аудио: Russian: AC3, 2/0 (L,R) ch, 256 kbps
- 5343227 · Аудио: AC3, 2/0 (L,R) ch, 192 kbps, Delay 0 mSec (rus)
- 5454128 · русская: Dolby AC3 48000Hz --\|DualMono\|-- 192Kbps [Ren-TV] (Серии 01-13)
- 6789362 · Аудио 1 Rus: AC3, 48 kHz, 2 ch, 192 kbps (Dаблин)
- 5124321 · Аудио RUS 1: AC3, 48000Hz, ~192 Kbps, 2ch (внешними файлами)
- 5341902 · Аудио #4-8: AC3, 192 kbps, 2 ch (Русский, Греческий, Румынский, Турецкий, Чешский)
- 5169587 · Аудио: Rus, DTS, 768 kb/s (6 ch), 48 kHz
- 3874340 · Audio Русский (DTS-HD Master Audio / 5.1 / 48 kHz / 3957 kbps / 24-bit (DTS Core: 5.1 / 48 kHz / 1509 kbps / 24-bit) - Одноголосый закадровый [-Доцент-]
- 6392123 · Аудио: RUS \| AС-3, 48 кГц, ~192 кбит/с, 2 канала \| AniLibria
- 541355 · Аудио: Russian (Dolby AC3, 6 ch) Japanese (Dolby AC3, 6 ch), Russian (Dolby AC3, 2ch)
- 742076 · Audio:Russian (Dolby AC3, 2 ch 192Kbps)
- 735299 · Аудио: Japanese (Dolby AC3, 2 ch), Russian (Dolby AC3, 2 ch)
- 5220817 · Аудио: Rus, AC3, 640 kb/s (6 ch), 48 kHz
- 4224158 · Аудио: Dolby Digital Audio Russian 5.1 / 48 kHz / 384 kbps \| Попов
- 3969441 · Аудио : Русский (Dolby AC3, 2 ch, 192 кб/с, СТС)
</details>
<details><summary>примеры ghost (14)</summary>
- 6700775 · ТК 1+1 · Ukrainian AC3 / 2.0 / 48 kHz / 192 kbps \|MVO, ТК "1+1"\|
- 5854979 · RUS , UKR · Профессиональный (дублированный) \| RUS (BD 3D CEE), UKR (Ledoyen)
- 6698664 · ТК 1+1 · Ukrainian AC3 / 2.0 / 48 kHz / 192 kbps \|MVO, ТК "1+1"\|
- 5852176 · RUS , UKR · Профессиональный (дублированный) \| RUS ( iTunes), UKR (студія LeDoyen)
- 6641488 · ТК 1+1 · Ukrainian AC3 / 2.0 / 48 kHz / 192 kbps \|MVO, ТК "1+1"\|
- 5641267 · ТКТочкаТВ · Профессиональный (многоголосый закадровый) MVO ТК"ТочкаТВ";
- 6803833 · ТК 1+1 · Ukrainian AC3 / 2.0 / 48 kHz / 192 kbps \|MVO, ТК "1+1"\|
- 6821405 · ТК 1+1 · Ukrainian AC3 / 2.0 / 48 kHz / 192 kbps \|MVO, ТК "1+1"\|
- 6813764 · ТК 1+1 · Ukrainian AC3 / 2.0 / 48 kHz / 192 kbps \|DVO, ТК "1+1"\|
- 3376591 · эп. 1-9 , эп. 10-13 · эп. 1-9 [YakuSub Studio], эп. 10-13 [Advantage]
- 6891775 · ViruseProject · Любительский (двухголосый закадровый) Viruse Project
- 6821395 · ТК 1+1 · Ukrainian AC3 / 2.0 / 48 kHz / 192 kbps \|MVO, ТК "1+1"\|
- 6181833 · НТВ+ ТНТ · Профессиональный (двухголосый закадровый) - НТВ+ \| ТНТ
- 6396910 · ТО Дия · Профессиональный (дублированный) ТО "Дия"
</details>
<details><summary>примеры miss:audio (531)</summary>
- 3296081 · Королев · Аудио 3: Русский одноголосый Королёв DTS / 5.1 / 768 kbps / 48 kHz
- 5806891 · Shiza Project · Аудио: Russian (ext), AC-3, 48000Hz, ~192 Kbps, 2ch \| SHIZA Project
- 6053237 · Невафильм · Аудио 1: русский (профессиональный, дублированный, Невафильм) AC3 256 kbps 48kHz 2Ch
- 4496622 · Киномания · Аудио: Russian: 48 kHz, DTS, 3/2 (L,C,R,l,r) + LFE ch, ~1536.00 kbps avg \|Многоголосый закадровый, Киномания\|
- 5931625 · NewComers · Аудио2: AC3, 3/2 (L,C,R,SL,SR)+LFE ch, 384 kbps........................ Русский NewComers
- 1328322 · Tycoon · Аудио 3: Русский AC3, 6ch 448 Kbps (двухголосый, Tycoon)
- 5899190 · HDrezka · Аудио4: AC3, 3/2 (L,C,R,SL,SR)+LFE ch, 448 kbps.................................................................................................. Русский HDRezk
- 6130286 · Юрий Живов · Аудио 5: Russian \| AC3 / 2.0 Ch / 48 kHz / 224 kbps \| AVO \| Юрий Живов (с VHS)
- 6680266 · Garsu Pasaulis · Аудио 6: Russian \| FLAC / 2.0 Ch / 48 kHz / 24 bit / 1537 kbps \| DVO \| "Garsu Pasaulis" (Литва) [VHS Hi-Fi Stereo]
- 5148134 · Persona99 · Аудио 1: AC-3 48 КГц; 192 Кбит/сек , 2.0 Язык Русский (в составе контейнера) ; Озвучка: женская одноголосая: Persona99
- 5056191 · Нота · Аудио #03: Russian: 48 kHz, AC3, 2/0 (L,R) ch, ~192 kbps avg \|Многоголосый закадровый, Нота\|
- 6344693 · DniproFilm · Аудио 7: АС3, 6 ch, 48 kHz, 384 kbps Украинский MVO DniproFilm/HDRezka Studio
- 6096897 · AlexFilm · Аудио Rus: AC3, 48 kHz, 3/2 (L R C LFE Ls Rs) ch, 384 Кбит/сек \| AlexFilm
- 6680266 · Михаил Яроцкий · Аудио 11: Russian \| DTS-HD MA / 7.1 Ch / 48 kHz / 16 bit / 2951 kbps (core: DTS / 5.1 Ch / 48 kHz / 16 bit / 1509 kbps) \| VO \| Михаил Яроцкий aka kyberpunk
- 6086348 · Сэмпл · Аудио: AAC, 144 кбит/сек, 48 КГц, 2 канала; Язык: Русский (в составе контейнера); Озвучка: Dantes (мужской голос) & Yuki (женский) (Amazing Dubbing) Сэмпл
</details>
<details><summary>примеры miss:trans (1114)</summary>
- 4919872 · Невафильм · Перевод 1: Профессиональный (дублированный) \|Дубляж, Blu-ray CEE_[Невафильм]_2007г\|
- 6771413 · заКАДРЫ · Перевод #3: Любительский (многоголосый, закадровый) [заКАДРЫ]
- 3569656 · Видеосервис · Перевод: Профессиональный (двухголосый закадровый) x2 (ОРТ, ВидеоСервис)
- 6792103 · DniproFilm · Перевод 7: Профессиональный (многоголосый закадровый) HDRezka (Dniprofilm)
- 4431588 · 1+1 · Перевод 5: Профессиональный (двухголосый закадровый) [1+1]
- 6279898 · Невафильм · Перевод: Профессиональный (полное дублирование) [Невафильм]
- 6845439 · отсутствует · Перевод 3: Казахский, профессиональный (дублированный) (Cinema Tone Production) по заказу Nickelodeon (временно отсутствует в S02E07)
- 6713595 · Red Head Sound · Перевод 2-3: Профессиональный (псевдодублированный) — Red Head Sound, Jaskier
- 5007272 · ОРТ · Перевод #2: Профессиональный многоголосый закадровый (ОРТ)
- 6860022 · А.Михалев · Перевод 3-5: авторский (одноголосый закадровый) - В.Горчаков, А.Михалев, П.Прямостанов
- 2555358 · Persona99 · Перевод: Двухголосый (Persona99 & MaxDamage;Noir & Cuba77), Субтитры
- 6790851 · ИДДК · Перевод 3-6: профессиональный (двухголосый закадровый) - Мьюзик-Трейд, DVD Магия / Флагман, ИДДК, Дивайс
- 6100973 · Рудой · Перевод 6: Авторский (одноголосый, закадровый) [Е.Рудой]
- 5711781 · А. Михалёв · Перевод 4: Авторский (одноголосый закадровый) А. Михалев
- 2455771 · MC Entertainment · Перевод: Профессиональный (многоголосый, закадровый) [MC Entertainment]
</details>
<details><summary>примеры noise:«X по заказу Y» (120)</summary>
- rt-5389600 · СВ-Дубль по заказу ВГТРК · Профессиональный (Полное Дублирование) "СВ-Дубль" по заказу "ВГТРК" (Карусель)
- rt-5087462 · по заказу 2х2 · Профессиональный (многоголосый, закадровый) по заказу 2х2 (Быстров, Савина, Эльдаров)
- rt-6130327 · SDI Sun-Studio Russia по заказу ОРТ 2006 г · Профессиональный (дублированный) «SDI Sun-Studio Russia» по заказу ОРТ 2006 г
- rt-6572883 · VSI Moscow по заказу Nickelodeon · Русский, профессиональный (дублированный) \| «VSI Moscow» по заказу Nickelodeon
- rt-4820398 · Творческая студия работников дубляжа 1995 год по заказу Центр Скип Фильм · закадровый) Творческая студия работников дубляжа 1995 год по заказу Центр Скип Фильм
- rt-6124488 · SDI Sun-Studio Russia по заказу ОРТ 2006 г · Профессиональный (дублированный) «SDI Sun-Studio Russia» по заказу ОРТ 2006 г
- 5485340 · кинокомпания Эй Би Видео по заказу Первого канала · Профессиональный (многоголосый закадровый) кинокомпания Эй Би Видео по заказу Первого канала
- 3373172 · студия Кадр , по заказу РТР, 1995 г · Профессиональный (дублированный): студия "Кадр" (при уч. студии "Нота"), по заказу РТР, 1995 г
- rt-6895972 · Syncmer по заказу HBO · Профессиональный (двухголосый закадровый) — Syncmer по заказу HBO
- rt-6845439 · по заказу Nickelodeon · Украинский, профессиональный (дублированный) (1+1) по заказу Nickelodeon
- rt-4397160 · студия Кипарис по заказу 2х2 · Профессиональный (двухголосый закадровый) "студия "Кипарис" по заказу 2х2"
- rt-6878274 · Дубляж Reanimedia По заказу Amediateka · Дубляж Reanimedia [русский] По заказу Amediateka
- 6851700 · По заказу смотри кино · Профессиональный (многоголосый закадровый) \| По заказу "смотри кино \| 18+
- 5485351 · кинокомпания Эй Би Видео по заказу Первого канала · Профессиональный (многоголосый закадровый) кинокомпания Эй Би Видео по заказу Первого канала
- rt-4609190 · СВ-Дубль, по заказу МНВК в 1996 году · Профессиональный (дублированный) СВ-Дубль, по заказу МНВК в 1996 году
</details>
<details><summary>примеры noise:источник: лицензия/Blu-ray/iTunes/Netflix (231)</summary>
- 4659385 · iTunes Russia · Профессиональный (дублированный) \| iTunes Russia
- 3893431 · лицензия · Профессиональный (дублированный), лицензия
- rt-6292012 · Пифагор по заказу Netflix · Профессиональный (дублированный) Пифагор по заказу Netflix
- 5967155 · BD CEE со вставками Д. Есарева и Замеза · Профессиональный (дублированный) \| BD CEE со вставками Д. Есарева и Замеза
- rt-6239239 · Пифагор по заказу Netflix · Профессиональный (дублированный) Пифагор по заказу Netflix
- 5516896 · BluRay / По заказу Первого канала · Профессиональный (двухголосый закадровый) - BluRay / По заказу Первого канала
- rt-6426925 · iTunes · Ukrainian AC3 / 5.1 / 48 kHz / 384 kbps \|Dub, iTunes\|
- rt-5010737 · Blu-ray CEE · Профессиональный (дублированный) Blu-ray CEE
- rt-6591933 · BD CEE Невафильм · Профессиональный (дублированный) BD CEE \| Невафильм (2001 год)
- rt-6130327 · Blu-Ray CPIG 2010 г · Профессиональный (дублированный) Blu-Ray «CPIG» 2010 г
- 6320223 · Blu-ray CEE · Профессиональный (дублированный) - Blu-ray CEE
- rt-4822676 · Blu-ray CEE · Профессиональный (полное дублирование) Blu-ray CEE
- 5096972 · Blu-ray CEE · Профессиональный (дублированный) Blu-ray CEE
- rt-5727410 · RUS + UKR Дубляж iTunes · Профессиональный (дублированный) RUS + UKR Дубляж \| iTunes
- rt-5409044 · iTunes · #2: Ukrainian: 48 kHz, AAC, 2/0 (L,R) ch, ~160.00 kbps avg \|Дубляж, iTunes\|
</details>
<details><summary>примеры noise:каналы/кодек (18)</summary>
- rt-5521273 · AAC, 48.0 KHz, 2 ch, 317 Kbps · AAC, 48.0 KHz, 2 ch, 317 Kbps (Китайский)
- rt-4206667 · Shachiburi , внешним файломОзвучивание: Eladiel & Zendos, , внешним файлом Аудио, Japanese: AAC, 48000Hz, 2ch, 192 Kbps CBR · Shachiburi , внешним файломОзвучивание: Eladiel & Zendos, , внешним файлом[*]Аудио, Japanese: AAC, 48000Hz, 2ch, 192 Kbp
- 3163663 · AC3, 6ch , 448 кбит/с Blu-ray CEE + · Украинский (Дублированный), AC3, 6ch (L, C, R, Ls, Rs +LFE), 448 кбит/с \|Blu-ray CEE\| + (вставки из киномании)
- 3163663 · AC3, 6ch , 384 кбит/с Сербин · Русский (Профессиональный (один голос)), AC3, 6ch (L, C, R, Ls, Rs +LFE), 384 кбит/с \|Сербин\|
- 2579545 · AC3, 6ch , 448 кбит/с Ист-Вест * · Русский (Дублированный), AC3, 6ch (L, C, R, Ls, Rs +LFE), 448 кбит/с \|«Ист-Вест»\|*
- rt-4311341 · JAP AAC, 128 kbps, 44,1 Hz, Stereo · JAP AAC, 128 kbps, 44,1 Hz, Stereo
- rt-4311341 · RUS AAC, 256 kbps, 48,0 Hz, Stereo · RUS AAC, 256 kbps, 48,0 Hz, Stereo
- rt-5521273 · AAC, 44.1 KHz, 2 ch, 192 Kbps · AAC, 44.1 KHz, 2 ch, 192 Kbps (Китайский)
- rt-2985347 · Rus AC-3, 6Ch, 384 Kbps · Rus AC-3, 6Ch, 384 Kbps
- 3163663 · AC3, 6ch , 448 кбит/с Blu-ray CEE + · Русский (Дублированный), AC3, 6ch (L, C, R, Ls, Rs +LFE), 448 кбит/с \|Blu-ray CEE\| + (вставки из киномании)
- rt-5521273 · AAC, 48.0 KHz, 2 ch, 192 Kbps · AAC, 48.0 KHz, 2 ch, 192 Kbps (Русский)
- rt-4557862 · MPEG Audio, 48.0 KHz, 2 ch, ~192 Kbps · MPEG Audio, 48.0 KHz, 2 ch, ~192 Kbps
- 3163663 · AC3, 6ch , 384 кбит/с Гаврилов · Русский (Профессиональный (один голос)), AC3, 6ch (L, C, R, Ls, Rs +LFE), 384 кбит/с \|Гаврилов\|
- rt-3972129 · П.Гланц и И.Королёва , в составе контейнера Аудио, Japanese: AC3, 48000Hz, 5.1ch, 448 Kbps CBR · П.Гланц и И.Королёва , в составе контейнера[*]Аудио, Japanese: AC3, 48000Hz, 5.1ch, 448 Kbps CBR
- rt-5521273 · AAC, 44.1 KHz, 2 ch, 192 Kbps · AAC, 44.1 KHz, 2 ch, 192 Kbps (Русский)
</details>
<details><summary>примеры noise:неру дорожка (26)</summary>
- 6405953 · Qazaqstan казахский · Профессиональный (многоголосый закадровый) Qazaqstan казахский
- rt-6802844 · Телеканал Qazaqstan · Kazakh AC3 / 1.0 / 48 kHz / 192 kbps \|Dub, Телеканал "Qazaqstan"\|
- rt-5555650 · Ужастика с английского · Авторский (одноголосый закадровый) "Ужастика" с английского
- rt-6831331 · Qazaqstan · Профессиональный (дублированный) Qazaqstan
- rt-6146562 · Телеканал Qazaqstan · Kazakh AC3 / 2.0 / 48 kHz / 192 kbps \|Dub, Телеканал "Qazaqstan"\|
- 3842468 · + Английский + Комментарии · Авторский (В.Назаров / Ю.Живов) + Английский (оригинал) + Комментарии (оригинал)
- rt-6438249 · телеканал Qazaqstan · Kazakh AC3 / 2.0 / 48 kHz / 192 kbps \|MVO, телеканал "Qazaqstan"\|
- 4078156 · + Английский + Комментарии · Авторский (А.Гаврилов, В.Королев) + Английский (оригинал) + Комментарии (оригинал)
- rt-6274168 · Телеканал Qazaqstan · Kazakh AC3 / 2.0 / 48 kHz / 192 kbps \|MVO, Телеканал "Qazaqstan"\|
- rt-6304693 · Телеканал Qazaqstan · Kazakh AC3 / 2.0 / 48 kHz / 192 kbps \|MVO, Телеканал "Qazaqstan"\|
- rt-6804228 · Телеканал Qazaqstan · Kazakh AC3 / 1.0 / 48 kHz / 192 kbps \|Dub, Телеканал "Qazaqstan"\|
- rt-6274165 · Телеканал Qazaqstan · Kazakh AC3 / 2.0 / 48 kHz / 192 kbps \|MVO, Телеканал "Qazaqstan"\|
- rt-6808952 · Телеканал Qazaqstan · Kazakh AC3 / 1.0 / 48 kHz / 192 kbps \|Dub, Телеканал "Qazaqstan"\|
- rt-6688661 · Qazaqstan · Профессиональный (дублированный) Qazaqstan
- rt-649606 · + английская дорожка · Профессиональный (многоголосый, закадровый) + английская дорожка
</details>
<details><summary>примеры noise:субтитры (27)</summary>
- rt-5305351 · Субтитры · Любительский многоголосый закадровый; Субтитры (см. информацию о каждом томе)
- rt-1456998 · Субтитры · Русский закадровый (одноголосый) \| Субтитры
- 6283156 · Viruse Project по субтитрам StasSlender · Любительский (многоголосый закадровый) Viruse Project по субтитрам StasSlender
- 4325644 · den904 по субтитрам Janetta · Одноголосый (закадровый) den904 по субтитрам Janetta
- rt-2421299 · Субтитры · Профессиональный (полное дублирование, XL-Media), Субтитры
- 4429717 · Doctor Joker по субтитрам romero007 · Одноголосый закадровый Doctor Joker по субтитрам romero007
- rt-5360746 · + субтитры на французскую речь · Профессиональный (дублированный) (BD CEE) + субтитры на французскую речь
- 6926094 · Субтитры · Субтитры любительский
- rt-2555358 · Субтитры · Двухголосый (Persona99 & MaxDamage;Noir & Cuba77), Субтитры
- rt-4199725 · по субтитрам Bellena · Одноголосый закадровый (iDimo) по субтитрам Bellena
- rt-3923673 · den904 по субтитрам disluo · Одноголосый закадровый den904 по субтитрам disluo
- rt-4562795 · /Andi по субтитрам Gabo/ · одноголосый закадровый /Andi по субтитрам Gabo/
- 4322131 · den904 по субтитрам Janetta · Одноголосый (закадровый) den904 по субтитрам Janetta
- rt-6198714 · allecs2010 по субтитрам Nebëhr Gudahtt · Одноголосый закадровый allecs2010 по субтитрам Nebëhr Gudahtt
- rt-6858166 · Пифагор , дальше только оригинал + субтитры · Русский: Профессиональный (дублированный) Пифагор (1-2 сезоны) , дальше только оригинал + субтитры
</details>
<details><summary>примеры noise:тип-слово (19)</summary>
- rt-2343090 · полное дублирование · полное дублирование
- rt-2754519 · полное дублирование · полное дублирование
- rt-3174375 · Дубляж · Дубляж
- 1750614 · дубляж · дубляж
- rt-2288687 · полное дублирование · полное дублирование
- rt-4245673 · полное дублирование · Профессиональный полное дублирование
- rt-3502191 · дубляж · любительский дубляж
- rt-3280195 · Дубляж · Дубляж
- rt-5809379 · Дубляж · Дубляж
- rt-3163687 · Дубляж · Дубляж (Лексикон)
- rt-1239852 · полное дублирование · полное дублирование
- rt-2500434 · дубляж · дубляж
- rt-1024963 · дубляж · дубляж (с VHS
- rt-2985347 · дубляж · дубляж
- rt-4557862 · полное дублирование · полное дублирование [ НЕВАФИЛЬМ ]
</details>
<details><summary>примеры noise:язык вместо студии (18)</summary>
- rt-6561750 · украинский · Профессиональный (дублированный), украинский (Le Doyen)
- rt-6872631 · Украинский · Профессиональный (дублированный) \| [Cinema Sound] - Украинский
- rt-6561750 · русский · Профессиональный (дублированный), русский (VSI Moscow)
- rt-5448541 · русский · Профессиональный (дублированный) русский
- rt-4486630 · Украинский · Украинский, (многоголосый закадровый)
- rt-5801324 · Украинский · Профессиональный (дублированный) Украинский
- rt-4131293 · украинский · украинский многоголосный
- rt-6923497 · Французский · Профессиональный (дублированный) Французский
- rt-521554 · русский · Профессиональный русский(многоголосый, закадровый)
- rt-5705161 · Украинский · Профессиональный (дублированный) Украинский
- rt-3502191 · украинский · украинский многоголосный
- rt-6807308 · Украинский · Профессиональный (дублированный) \| [LeDoyen] - Украинский
- rt-5415702 · украинский · Профессиональный (дублированный) украинский
- 6287332 · Казахский · Профессиональный (дублированный) Казахский
- rt-2439828 · Русский · Русский многоголосый перевод
</details>
<details><summary>примеры noise:язык-префикс «Русский, - X» (13)</summary>
- 6105068 · Украинский, AMC · Профессиональный (многоголосый) \| Украинский, AMC
- 6008543 · Украинский, R5 · Профессиональный (дублированный) \| Украинский, R5
- rt-6887455 · Русский, - HBO · Русский, двухголосый закадровый - "HBO"
- rt-6887455 · Русский, - Сыендук · Русский, одноголосый закадровый - "Сыендук" (18+)
- rt-6887455 · Русский, - HDRezka · Русский, многоголосый закадровый - "HDRezka Studio"
- rt-6449707 · Украинский, Le Doyen / Netflix · Профессиональный (дублированный) Украинский, Le Doyen / Netflix
- rt-6887455 · Украинский, - HDRezka · Украинский, многоголосый закадровый - "HDRezka Studio"
- rt-6887455 · Русский, псевдодублированный - HDRezka · Русский, псевдодублированный - "HDRezka Studio" (18+)
- rt-6450195 · Украинский, MEGOGO · Профессиональный (многоголосый закадровый) Украинский, MEGOGO
- rt-6887455 · Русский, - HDRezka · Русский, многоголосый закадровый - "HDRezka Studio" (18+)
- rt-6887455 · Русский, - TVShows · Русский, многоголосый закадровый - "TVShows" \| (серии 1-8)
- rt-6887455 · Русский, - ТО Дубляжная по заказу ТВОЁ · Русский, многоголосый закадровый - "ТО Дубляжная" по заказу «ТВОЁ»
- rt-6887455 · Украинский, - HBO · Украинский, многоголосый закадровый - "HBO"
</details>
<details><summary>примеры unlab (1887)</summary>
- 242182 · Золотая коллекция любимых мультфильмов. Выпуск 2 (Про бегемота, который боялся п · Аудио: Russian (Dolby AC3, 2 ch)
- 5501688 · Смешарики. Пин-код. Гуманитарные технологии / Сезон: 3 / Серии: 1-20 из 20 (Алек · Перевод: Не требуется
- 4262044 · Связь сердец / Kokoro Connect / Единение сердец (Онума Син) [TV+Special] [13+4 и · Перевод:
- 4094721 · Драконий Жемчуг: Фильм второй / Dragon Ball Movie 2: Sleeping Princess in Devil' · Аудио: AC3, 2/0 (L,R) ch, 192 kbps Язык Русский (Любительский одноголосый)
- 4777768 · Мангака и все-все-все / Mangaka-san to Assistant-san to The Animation / The Comi · Перевод:
- 5441268 · Принцесса-шпионка / Princess Principal [TV] [12 из 12] [RUS(int), JAP+Sub] [2017 · Аудио 1: AC3, ~192 kbps, 48000 Hz, 2 ch., язык русский (в составе контейнера); озвучка: многоголосая Торгиль, Esther, LeeAnnushka, Demetra,
- 1078722 · Бернард: выпуски 1, 2, 3 / Bernard, Bernard 2, Bernard 3 (Клаудио Бьерн Ливирья, · Перевод: Не требуется
- 4735130 · Сломанный Меч / Break Blade [TV] [12 из 12] [RUS(int), JAP+Sub] [2014, приключен · Аудио 1: AC3, ~192 kbps, 48000 Hz, 2 ch., язык русский (в составе контейнера); озвучка: двухголосая (муж.+жен.) Absurd & Eladiel
- 5182067 · Попотан / Popotan [TV] [12 из 12] [RUS, ENG, JAP] & [5-8 из 12] [Sub] [2003, ком · Аудио 3: AC-3, 192 Kbps, 2 channels Язык Русский - мужская одноголосая от Гнус
- 5046410 · Ямада и семь ведьм / Yamada-kun to 7-nin no Majo [TV] [12 из 12] [RUS(int), JAP+ · Аудио: Русская / 2 ch / AC-3 / 44,1 kHz / 192 Kbps / RUS(int) [озвучка: Persona99]
- 5950622 · Владыка духовного меча / Ling Jian Zun / Spirit Sword Sovereign / Сезон: 1-3 / С · Аудио: AC3, 48000Hz, 2ch, 128kbps, Язык Русский (в составе контейнера)
- 5019993 · Бамбуковый Клинок / Bamboo Blade (Сайто Хисаси) [TV] [26 из 26] [RUS(ext),JAP+Su · Озвучка:
- 4629918 · Навсикая из Долины ветров / Kaze no Tani no Naushika / Nausicaa of the Valley of · Аудио 5: Русский [Сонотек/Ruscico (2007 г., дубляж)] Dolby Digital Audio 2.0 / 48 kHz / 192 kbps / DN -27dB
- 1932640 · Лучшее от Загребфильм / The Best of Zagreb Film (Dušan Vukotic,Zdenko Gašparovic · Перевод: Не требуется
- 6916333 · Абдукция / Сезон: 1 / Серии: 1-12 из 12 (Анастасия Бондаренко) [2025, Россия, му · Перевод: не требуется
</details>
+73 -11
View File
@@ -6,12 +6,19 @@
Публичный API:
detect_tracker(url) — 'rutracker' | 'kinozal' | 'nnm' | ValueError
fetch_html(url, ...) — скачать страницу, вернуть (html, tracker)
kinozal_tabs(html) — вкладки kinozal, которые грузятся JS (Релиз, …)
inline_tabs(html, t) — вписать скачанные вкладки в блок #tabs
decode_bytes(data) — байты -> текст (utf-8 -> cp1251, чинит «двойную»)
kinozal: описание многих раздач открывается и без входа (cookie необязателен). Если трекер
вернул форму входа, передайте cookie (`cookie="uid=...; pass=..."`) либо скачайте страницу сами
(grabber уже умеет) и отдайте сервису готовый `html`.
На kinozal во встроенном блоке #tabs только «Техданные» (там «Перевод:» без
студий), а «Релиз» со строками «Аудио N: … (MVO, LostFilm)» грузится JS:
`showtab(id, N)` -> `get_srv_details.php?id=…&pagesd=N`. fetch_html скачивает
эти вкладки и дописывает их в #tabs, чтобы описание разбиралось целиком.
Cloudflare / защита от ботов: если задан FlareSolverr (ваш собственный
экземпляр, как у Jackett/Prowlarr) — `flaresolverr="http://host:8191"` или
переменная окружения DUB_FLARESOLVERR_URL, — то при ответе 403/503 или
@@ -23,10 +30,13 @@ from __future__ import annotations
import json
import os
import re
import urllib.error
import urllib.request
from urllib.parse import urlparse
from bs4 import BeautifulSoup
from .html_extract import _fix_mojibake
TRACKER_HOSTS = {
@@ -127,32 +137,84 @@ def _fetch_flaresolverr(endpoint: str, url: str, timeout: float,
return data["solution"]["response"]
def fetch_html(url: str, *, timeout: float = 15.0, cookie: str | None = None,
flaresolverr: str | None = None) -> tuple[str, str]:
"""Скачивает страницу релиза. Возвращает (html, tracker).
Сначала прямой запрос; при 403/503 или странице-заглушке и заданном
FlareSolverr — повтор через него.
"""
tracker = detect_tracker(url)
flaresolverr = flaresolverr or os.environ.get("DUB_FLARESOLVERR_URL") or None
html, direct_error = None, None
def _get(url: str, timeout: float, cookie: str | None,
flaresolverr: str | None) -> str:
"""Прямой запрос; при 403/503 или странице-заглушке — через FlareSolverr."""
html = None
try:
html = _fetch_direct(url, timeout, cookie)
except urllib.error.HTTPError as e:
if e.code not in RETRY_CODES or not flaresolverr:
raise
direct_error = e
if html is not None and blocked_reason(html) and flaresolverr:
html = None
if html is None:
if not flaresolverr:
raise ValueError("страница недоступна")
html = _fetch_flaresolverr(flaresolverr, url, timeout, cookie)
return html
# «showtab(2155882,0)»; 100 — «Техданные», они уже встроены в страницу
RE_KZ_TAB = re.compile(r"showtab\((\d+)\s*,\s*(\d+)\)[^>]*>([^<]*)<")
KZ_INLINE_TAB = 100
KZ_SKIP_TABS = ("скриншот",)
def kinozal_tabs(html: str) -> list[tuple[str, int, str]]:
"""Вкладки описания kinozal, которые грузятся JS: [(id, N, название)]."""
out, seen = [], set()
for rid, n, label in RE_KZ_TAB.findall(html):
n = int(n)
if n == KZ_INLINE_TAB or n in seen or label.strip().lower().startswith(KZ_SKIP_TABS):
continue
seen.add(n)
out.append((rid, n, label.strip()))
return out
def inline_tabs(html: str, tabs: list[tuple[str, str]]) -> str:
"""Дописывает фрагменты вкладок [(название, html)] в конец блока #tabs."""
soup = BeautifulSoup(html, "lxml")
node = soup.select_one("#tabs")
if node is None or not tabs:
return html
for label, fragment in tabs:
box = soup.new_tag("div", attrs={"class": "dub-tab", "data-tab": label})
frag = BeautifulSoup(fragment, "lxml")
for child in list((frag.body or frag).contents):
box.append(child)
node.append(box)
return str(soup)
def fetch_html(url: str, *, timeout: float = 15.0, cookie: str | None = None,
flaresolverr: str | None = None) -> tuple[str, str]:
"""Скачивает страницу релиза. Возвращает (html, tracker).
Сначала прямой запрос; при 403/503 или странице-заглушке и заданном
FlareSolverr — повтор через него. Для kinozal дополнительно скачиваются
вкладки описания («Релиз», «Внимание»); недоступная вкладка пропускается.
"""
tracker = detect_tracker(url)
flaresolverr = flaresolverr or os.environ.get("DUB_FLARESOLVERR_URL") or None
html = _get(url, timeout, cookie, flaresolverr)
reason = blocked_reason(html)
if reason:
hint = "" if flaresolverr else " (задайте FlareSolverr: DUB_FLARESOLVERR_URL)"
raise ValueError(f"страница недоступна: {reason}{hint}")
if tracker == "kinozal":
parsed = urlparse(url)
tabs = []
for rid, n, label in kinozal_tabs(html):
tab_url = f"{parsed.scheme}://{parsed.netloc}/get_srv_details.php?id={rid}&pagesd={n}"
try:
frag = _get(tab_url, timeout, cookie, flaresolverr)
except (urllib.error.URLError, ValueError, OSError):
continue
if not blocked_reason(frag):
tabs.append((label, frag))
html = inline_tabs(html, tabs)
return html, tracker
+2 -4
View File
@@ -33,7 +33,7 @@ from .html_extract import (RE_LINE_PREFIX, extract_segments, html_to_text,
from .model import load_model, predict_with_lang
from .studio_norm import canonical
from .spans import MAX_LEN, decode_span
from .trackinfo import lang_of_text, model_text, role_of
from .trackinfo import audio_summary, lang_of_text, model_text, role_of
# мусор, который модель иногда принимает за автора: раскладка каналов, кодеки
@@ -166,9 +166,7 @@ class DubPredictor:
"lang_source": lang_src,
"role": role_of(info, seg),
"official": bool(info and info["official"]),
"audio": ({"codec": info["codec"] or None, "channels": int(info["channels"]) if info["channels"] else None,
"bitrate_kbps": int(info["kbps"]) if info["kbps"] else None}
if info else None),
"audio": audio_summary(info),
"_audio18": info["adult"] if info else None,
})
return results
+124 -31
View File
@@ -16,7 +16,8 @@ import re
from bs4 import BeautifulSoup
from .audio18 import apply_audio_18
from .trackinfo import audio_lines_all, match_audio
from .techinfo import tech_info
from .trackinfo import audio_lines_all, audio_summary, match_audio
from .episodes import format_ranges, merge_ranges, parse_episodes
from .html_extract import (RE_LINE_PREFIX, _split_entries, extract_segments,
html_to_text)
@@ -43,7 +44,9 @@ def title_from_html(html: str) -> str:
for sel in ("h1", "title"):
node = soup.select_one(sel)
if node and node.get_text(strip=True):
return re.sub(r"\s+", " ", node.get_text(" ", strip=True))
# без разделителя: rutracker оборачивает каждое русское слово в <span> и режет <wbr>
# («Галактическо<wbr>е»), пробелы между словами — отдельные текстовые узлы
return re.sub(r"\s+", " ", node.get_text()).strip()
return ""
@@ -149,14 +152,11 @@ def aggregate(tracks: list[dict]) -> dict:
}
def original_info(lines: list[dict]) -> dict | None:
"""Оригинальная дорожка из строки «Аудио … |Original|» (без студии и комментариев); нет — None."""
for ln in lines:
if ln["original"] and not ln["names"] and not ln["commentary"]:
return {"lang": ln["lang"] or None, "codec": ln["codec"] or None,
"channels": int(ln["channels"]) if ln["channels"] else None,
"bitrate_kbps": int(ln["kbps"]) if ln["kbps"] else None, "raw": ln["raw"]}
return {"lang": ln["lang"] or None, **audio_summary(ln), "raw": ln["raw"]}
return None
@@ -170,7 +170,7 @@ def lostfilm_release(title: str | None = None) -> dict:
"source": "url", "episode_ranges": [], "episodes": None}
result = aggregate([track])
result.update(original=None, series=series_info(title or "", result["tracks"]),
title=title or None, segmenter="rules")
**tech_info("", title or ""), title=title or None, segmenter="rules")
return result
@@ -198,17 +198,45 @@ def series_info(title: str, tracks: list[dict]) -> dict | None:
if t.get("studio") and not t.get("episodes")})}
def merge_title_tracks(tracks: list[dict], title_tracks: list[dict]) -> None:
"""Добавляет дорожки из заголовка к дорожкам страницы (на месте).
def _studio_tokens(name: str) -> set[str]:
return {w for w in re.findall(r"\w+", name.casefold()) if len(w) >= 4}
* студия уже есть на странице -> пропуск;
* на странице есть дорожка того же типа без студии -> студия берётся
из заголовка (source=page+title): это та же дорожка;
* иначе -> новая дорожка (source=title).
def same_studio(a: str | None, b: str | None) -> bool:
"""Одна студия в разном написании: «DVD» ~ «DVD Магия», «НТВ» ~ «НТВ+»,
«Михаил Яроцкий» ~ «М.Яроцкий/Kyberpunk» (общее слово ≥ 4 букв)."""
a, b = (a or "").casefold().strip(), (b or "").casefold().strip()
if not a or not b:
return False
return a in b or b in a or bool(_studio_tokens(a) & _studio_tokens(b))
def merge_extra_tracks(tracks: list[dict], extra: list[dict], source: str,
match=lambda a, b: (a or "").casefold() == (b or "").casefold(),
adopt_type: bool = False) -> None:
"""Добавляет дорожки второстепенного источника к дорожкам страницы (на месте).
* такая студия уже есть (по `match`) -> пропуск;
* есть дорожка того же типа без студии -> студия вписывается в неё
(source=page+<source>): это та же дорожка;
* иначе -> новая дорожка (source=<source>).
Дорожки без студии не берутся: тип без студии страница уже даёт, а в
кодек-строках он часто грубее («одноголосый» вместо «авт. одноголосый»).
Дорожка с типом «неизвестно» («Аудио Rus: AC3 … | LostFilm») берётся
только при `adopt_type` и если у страницы ровно один тип без студии
(«Перевод: Профессиональный многоголосый»): она получает этот тип.
"""
for t in title_tracks:
name = (t.get("studio") or "").casefold()
if any((x.get("studio") or "").casefold() == name for x in tracks):
bare = {x["translation_type"] for x in tracks
if not x.get("studio") and x["translation_type"] != UNKNOWN_TYPE}
for t in extra:
if not t.get("studio"):
continue
if t["translation_type"] == UNKNOWN_TYPE:
if not (adopt_type and len(bare) == 1):
continue
t["translation_type"] = next(iter(bare))
if any(match(x.get("studio"), t["studio"]) for x in tracks):
continue
same = next((x for x in tracks if not x.get("studio")
and x["translation_type"] == t["translation_type"]), None)
@@ -216,26 +244,75 @@ def merge_title_tracks(tracks: list[dict], title_tracks: list[dict]) -> None:
same.update(studio=t["studio"], studio_span=t["studio_span"],
studio_confidence=t["studio_confidence"],
is_18plus=same["is_18plus"] or t["is_18plus"],
source="page+title")
source=f"page+{source}")
else:
t["source"] = "title"
t["source"] = source
tracks.append(t)
def model_segments(segmenter, page_text: str) -> list[str]:
def merge_title_tracks(tracks: list[dict], title_tracks: list[dict]) -> None:
"""Дорожки из заголовка («ПМ (LostFilm)») -> к дорожкам страницы."""
merge_extra_tracks(tracks, title_tracks, "title")
# кодек-строка: «Аудио 3: AC3 …», «Аудио Rus: …», «Аудиодорожка 1: …», «Audio #2: …»
RE_AUDIO_PREFIX = re.compile(r"(?i)^\s*(?:Аудио\w*|Audio)\s*(?:№|#)?\s*(?:\d+|rus|ukr|eng)?\s*[:.]")
# с типом озвучки: «Аудио 3: AC3 … (MVO, HDrezka Studio)»; «English Dubbing» — не тип
RE_CODEC_TRACK = re.compile(
r"(?i)^\s*(?:Аудио|Audio)\s*(?:№|#)?\s*\d*\s*:.*(?:\b(?:MVO|DVO|AVO|DUB|VO)\b"
r"|дубл|многоголос|двухголос|одноголос|закадр|авторск)")
RE_RU_TRACK = re.compile(r"(?i)русск|russian|\brus\b|украин|ukrain|\bukr\b")
RE_FOREIGN_TRACK = re.compile(
r"(?i)казах|english|англ|француз|немец|немецк|итальян|испан|японск|корейск|китайск"
r"|\boriginal\b|оригинал|\beng\b")
def codec_ok(line: str) -> bool:
"""Кодек-строка русской (украинской) дорожки: «Казахский (Dub, TK Qazaqstan)»,
«English Dubbing», «Original» — нет."""
if RE_RU_TRACK.search(line):
return True
return not RE_FOREIGN_TRACK.search(line) and bool(RE_CODEC_TRACK.match(line))
def _entries(line: str) -> list[str]:
body = RE_LINE_PREFIX.sub("", line, count=1)
return [e for e in (re.sub(r"\s+", " ", x).strip() for x in _split_entries(body)) if e]
def codec_segments(page_text: str) -> list[str]:
"""Кодек-строки с типом озвучки («Аудио 2: AC3 … (MVO, LostFilm)») по правилу.
Запасной путь к тому, что выбирает сегментатор (`model_segments`): старые
веса сегментатора кодек-строки отбрасывают. Они нужны там, где студии есть
только в них (kinozal: вкладка «Релиз» против «Перевод:» без студий в
«Техданных»), и идут второстепенным источником, см. `merge_extra_tracks`.
"""
out = []
for line in (page_text or "").splitlines():
if RE_CODEC_TRACK.match(line) and codec_ok(line):
out += _entries(line)
return out
def model_segments(segmenter, page_text: str) -> tuple[list[str], list[str]]:
"""Дорожки страницы: строки выбирает обученный сегментатор.
Возвращает (основные, кодек): строки «Перевод/Озвучка …» и кодек-строки
«Аудио N: AC3 … (MVO, LostFilm)». Кодек-строки обычно дублируют основные
в другом написании, поэтому они второстепенный источник (`predict_release`);
если основных нет (nnm: дорожки только в «Аудио N:») — они и есть дорожки.
Нарезка внутри строки («Дублированный, многоголосый») в данных не
размечена и пока делается правилами `_split_entries`.
"""
out = []
main, codec = [], []
for line in segmenter.select_lines(page_text):
body = RE_LINE_PREFIX.sub("", line, count=1)
for entry in _split_entries(body):
entry = re.sub(r"\s+", " ", entry).strip()
if entry:
out.append(entry)
return out
if not RE_AUDIO_PREFIX.match(line):
main += _entries(line)
elif codec_ok(line):
codec += _entries(line)
return main, codec
def predict_release(predictor, *, html: str | None = None, text: str | None = None,
@@ -246,17 +323,19 @@ def predict_release(predictor, *, html: str | None = None, text: str | None = No
(если он загружен и нашёл хотя бы одну строку), иначе правила; "rules" —
только правила.
Заголовок раздачи используется как дополнительный источник: студии,
названные только там («ПМ (LostFilm)»), добавляются как дорожки с
пометкой source=title.
Кодек-строки («Аудио N: … (MVO, LostFilm)») и заголовок раздачи —
дополнительные источники: студии, названные только там, добавляются как
дорожки с пометкой source=codec / source=title.
"""
if (html is None) == (text is None):
raise ValueError("нужно ровно одно из: html, text")
page_text = html_to_text(html) if html is not None else text
segs_page, used = [], "rules"
segs_page, segs_codec, used = [], [], "rules"
seg_model = getattr(predictor, "segmenter", None)
if segmenter == "model" and seg_model is not None:
segs_page = model_segments(seg_model, page_text)
segs_page, segs_codec = model_segments(seg_model, page_text)
if not segs_page:
segs_page, segs_codec = segs_codec, []
used = "model" if segs_page else "rules"
if not segs_page:
segs_page = extract_segments(page_text)
@@ -272,6 +351,19 @@ def predict_release(predictor, *, html: str | None = None, text: str | None = No
ep = parse_episodes(t["segment"])
t["episode_ranges"] = ep["ranges"]
t["episodes"] = format_ranges(ep["ranges"]) or None
# кодек-строки — только если страница не назвала студию хотя бы у одной
# дорожки (kinozal: «Перевод: многоголосый» + «Аудио 3: … (MVO, LostFilm)»);
# иначе они дублируют «Перевод N:» в другом написании и дают ложные студии
seen = set(segs_page)
segs_codec = list(dict.fromkeys(x for x in segs_codec + codec_segments(page_text)
if x not in seen))
if segs_codec and (not tracks or any(not t.get("studio") for t in tracks)):
codec = predictor._predict_batch(segs_codec)
for t in codec:
ep = parse_episodes(t["segment"])
t["episode_ranges"] = ep["ranges"]
t["episodes"] = format_ranges(ep["ranges"]) or None
merge_extra_tracks(tracks, codec, "codec", match=same_studio, adopt_type=True)
if title is None:
title = title_from_html(html) if html is not None else title_from_text(text)
segs = title_segments(title or "")
@@ -283,6 +375,7 @@ def predict_release(predictor, *, html: str | None = None, text: str | None = No
for t in result["tracks"]:
t.pop("_audio18", None)
result["series"] = series_info(title or "", result["tracks"])
result.update(tech_info(page_text, title or ""))
result["title"] = title or None
result["segmenter"] = used
return result
+82 -8
View File
@@ -11,8 +11,10 @@ Python API:
seg = LineSegmenter("models/segmenter")
seg.select_lines(page_text) -> list[str] # строки-дорожки (префикс не снят)
Разметка: строка страницы положительна, если содержит `raw` строки датасета
этого релиза (или наоборот). Положительные примеры добавляются синтетически:
Разметка: строка страницы положительна, если содержит `raw` (или `audio_raw`)
строки датасета этого релиза (или наоборот); неразмеченные кодек-строки русских
дорожок в обучение не идут (см. `label_page`). Текст kinozal собирается из
data/raw/kinozal/*.json (`kinozal_text`). Положительные примеры добавляются синтетически:
`raw` всех train-строк датасета (включая nnm/kinozal, у которых нет страниц)
вставляются в контекст реальных строк страниц. Нарезка ВНУТРИ строки
(«Дублированный, многоголосый») в данных не размечена и остаётся на правилах.
@@ -69,8 +71,34 @@ def page_lines(text: str) -> list[str]:
# ---------------------------------------------------------------- данные
RAW_KINOZAL = Path("data/raw/kinozal")
def kinozal_text(release_id: str, raw_dir: Path = RAW_KINOZAL) -> str | None:
"""Текст страницы kinozal так, как его видит сервис: заголовок + «Техданные»
(«Перевод: …») + «Релиз» («Аудио N: … (MVO, LostFilm)»).
В data/pages/kz-*.txt нет ни заголовка, ни «Техданных», а метки kinozal
наполовину взяты именно оттуда, поэтому для kinozal берётся raw JSON.
"""
path = raw_dir / f"{str(release_id).removeprefix('kz-')}.json"
if not path.exists():
return None
j = json.loads(path.read_text(encoding="utf-8"))
return "\n".join(x for x in (j.get("h1"), j.get("tech_tab"), j.get("release_tab")) if x)
def page_text(path: Path) -> str:
"""Текст сохранённой страницы релиза (kinozal — из raw JSON, если он есть)."""
if path.stem.startswith("kz-"):
text = kinozal_text(path.stem, path.parent.parent / "raw" / "kinozal")
if text:
return text
return read_input(path)
def load_pages(pages_dir: Path = Path("data/pages")) -> dict[str, list[str]]:
return {p.stem: page_lines(read_input(p)) for p in sorted(pages_dir.glob("*.txt"))}
return {p.stem: page_lines(page_text(p)) for p in sorted(pages_dir.glob("*.txt"))}
def load_raws(csv_path: Path = Path("data/labels.csv")) -> list[dict]:
@@ -78,12 +106,53 @@ def load_raws(csv_path: Path = Path("data/labels.csv")) -> list[dict]:
return list(csv.DictReader(f))
# кодек-строка русской/украинской дорожки: «Аудио 2: AC3 … Русский (MVO, X)»,
# «Аудио Rus: AC3 … | LostFilm», «Аудиодорожка 1: … | Russian | TVShows»;
# сводка «Аудио: Русский, английский (AC3 …)» без номера — не дорожка
RE_AUDIO_TRACK = re.compile(
r"(?i)^\s*(?:(?:Аудио|Audio)\s*(?:rus|ukr)\s*[:.]"
r"|(?:Аудиодорожка\s*\d*|(?:Аудио|Audio)\s*(?:№|#)?\s*\d+)\s*[:.]"
r".*(?:русск|russian|\brus\b|украин|ukrain|\bukr\b))")
RE_TYPE_WORD = re.compile(
r"(?i)\b(?:MVO|DVO|AVO|DUB|VO)\b|дубл|многоголос|двухголос|одноголос|закадр|авторск")
# технические слова кодек-строки: всё, что после их удаления осталось, — имя студии/автора
RE_TECH_WORD = re.compile(
r"(?i)^(?:аудио\w*|audio|e?-?ac-?3|eac3|ac|dts|hd|ma|mpeg|mp3|aac|lc|opus|flac|pcm|lpcm|truehd"
r"|atmos|joc|dolby|digital|surround|stereo|mono|cbr|vbr|avg|constant|core|bit|ch|сh|channels?"
r"|каналов|канала|kbps|kb|кбит|бит|сек|khz|кгц|hz|lfe|dn|db|rus|ukr|eng|russian|русск\w*"
r"|украин\w*|english|англ\w*|l|c|r|sl|sr|ls|rs|lr|cbr|a)$")
def _informative(line: str) -> bool:
"""Кодек-строка называет тип озвучки или студию/автора, а не только параметры.
«Аудио 1: AC3, 6 ch, 384 kbps - Русский» — нет; «… | LostFilm»,
«… - Русский, Юрий Живов», «… (MVO)» — да.
"""
if RE_TYPE_WORD.search(line):
return True
body = re.sub(r"(?i)^\s*(?:Аудио\w*|Audio)\s*(?:№|#)?\s*(?:\d+|rus|ukr)?\s*[:.]", "", line)
words = [w for w in re.findall(r"[^\W\d_]+", body) if not RE_TECH_WORD.match(w)]
return any(len(w) >= 3 for w in words)
def label_page(lines: list[str], raws: list[str]) -> list[int]:
"""1, если строка содержит raw (или raw содержит строку ≥ 15 символов)."""
"""1 — строка содержит raw (или raw содержит строку ≥ 15 символов);
-1 — неразмеченная кодек-строка русской дорожки с типом или студией: в
обучение не идёт; пустая кодек-строка («AC3, 6 ch, 384 kbps - Русский») — 0.
Кодек-строка «Аудио N: … (MVO, LostFilm)» — дорожка, но у rutracker
`raw` — строка «Перевод N:», а кодек-строка в разметку не попадает. Как
негатив она учила модель отбрасывать такие строки (на kinozal студии
только в них), поэтому неразмеченные кодек-строки пропускаются.
"""
out = []
for line in lines:
hit = any(r and (r in line or (len(line) >= 15 and line in r)) for r in raws)
out.append(int(hit))
skip = RE_AUDIO_TRACK.match(line) and _informative(line)
out.append(1 if hit else (-1 if skip else 0))
return out
@@ -93,7 +162,10 @@ def build_examples(split: str, rng: random.Random, synth_per_page: int = 6) -> l
rows = load_raws()
raws_by_rel: dict[str, list[str]] = {}
for r in rows:
raws_by_rel.setdefault(r["release_id"], []).append(norm(r["raw"]))
# audio_raw — кодек-строка той же дорожки («AC3 … Русский (MVO, X)»), тоже позитив
for text in (r["raw"], r.get("audio_raw")):
if norm(text):
raws_by_rel.setdefault(r["release_id"], []).append(norm(text))
want_test = split == "test"
page_items = [] # (release_id, lines, labels)
@@ -105,7 +177,7 @@ def build_examples(split: str, rng: random.Random, synth_per_page: int = 6) -> l
# все raw train-релизов (без тестовых страниц) — материал для синтетики
test_pos = {l for rid, lines in pages.items() if is_test_release(rid)
for l, y in zip(lines, label_page(lines, raws_by_rel.get(rid, []))) if y}
for l, y in zip(lines, label_page(lines, raws_by_rel.get(rid, []))) if y == 1}
pool = sorted({norm(r["raw"]) for r in rows
if r["raw"] and not is_test_release(r["release_id"])
and norm(r["raw"]) not in test_pos})
@@ -113,13 +185,15 @@ def build_examples(split: str, rng: random.Random, synth_per_page: int = 6) -> l
ex: list[tuple] = []
for rid, lines, labels in page_items:
for i, (line, y) in enumerate(zip(lines, labels)):
if y < 0:
continue # неразмеченная кодек-строка: ни позитив, ни негатив
prev = lines[i - 1] if i else ""
nxt = lines[i + 1] if i + 1 < len(lines) else ""
if not y and not HINT.search(line) and rng.random() > 0.15:
continue # простые негативы прореживаем, трудные (с намёками) берём все
ex.append((*make_input(prev, line, nxt), y))
if not want_test:
negs = [l for l, y in zip(lines, labels) if not y]
negs = [l for l, y in zip(lines, labels) if y == 0]
for _ in range(synth_per_page if negs else 0):
raw = rng.choice(pool)
p, n = rng.choice(negs), rng.choice(negs)
+265
View File
@@ -0,0 +1,265 @@
"""Техданные релиза помимо озвучки: видео, тип релиза, контейнер, продолжительность, субтитры.
Только правила по строкам страницы («Видео: …», «Тип релиза: …», «Субтитры: …») и по
заголовку раздачи — нейросеть здесь не нужна, форматы у трекеров устойчивые.
video_info(text, title) -> {codec, width, height, resolution, fps, bitrate_kbps, bit_depth, hdr, dv_profile, raw} | None
release_info(text, title) -> {quality, source, container, duration, duration_sec} | None
subtitles_info(text, title) -> {languages, tracks, format, hardsub, raw} | None
tech_info(text, title) -> {"video": …, "release": …, "subtitles": …}
"""
from __future__ import annotations
import re
from .trackinfo import LANG_CODES, lang_of_text
# строка техданных «Ключ: значение»; ключ — в начале строки, точки-заполнители MediaInfo допустимы
def _field(text: str, key: str) -> list[str]:
rx = re.compile(rf"(?im)^[^\S\n]*(?:{key})[^\S\n.]*\.*[^\S\n]*:[^\S\n]*(?P<v>[^\n]*)$")
# nnm пишет несколько полей в одну строку через табуляцию или длинный пробел:
# «Отсутствуют Дата мировой премьеры: …» — значение до такого разрыва
return [re.split(r"\t| {3,}", m.group("v").strip(), maxsplit=1)[0].strip()
for m in rx.finditer(text or "")]
def _first(text: str, *keys: str) -> str:
for key in keys:
for v in _field(text, key):
if v:
return v
return ""
# ------------------------------------------------------------------ видео
VIDEO_CODECS = [ # (шаблон, каноническое имя); порядок важен: HEVC раньше MPEG-4
(r"HEVC|H\.?265|x265", "HEVC"),
(r"\bAV1\b", "AV1"),
(r"\bVP9\b", "VP9"),
(r"\bAVC\b|H\.?264|x264", "AVC"),
(r"VC-?1", "VC-1"),
(r"MPEG-?2|MPEG\s*Video\s*2", "MPEG-2"),
(r"XviD", "XviD"),
(r"DivX", "DivX"),
(r"MPEG-?4", "MPEG-4"),
]
RE_SIZE = re.compile(r"(?<!\d)(\d{3,4})\s*[xх×*]\s*(\d{3,4})(?!\d)")
RE_PLINE = re.compile(r"(?i)(?<!\d)(2160|1440|1080|720|576|480)[pi]\b")
RE_FPS = re.compile(r"(?i)(?<![\d.,])(\d{2,3}(?:[.,]\d{1,6})?)\s*(?:fps|кадр)")
# «8 200 Кбит/с» — пробел как разделитель тысяч, но не «640x360 1494 kb/s» (высота кадра перед числом)
RE_RATE = re.compile(r"(?i)(?<![\d.,xх×*])(?<![xх×*] )(\d{1,3}(?: \d{3})+|\d+)((?:[.,]\d+)?)\s*([kкmм])\s*(?:bps|bit|бит|b/[sсc])")
RE_DEPTH = re.compile(r"(?i)(?<![\d.,])(8|10|12)\s*-?\s*(?:bits?|бит)(?!\s*/)")
RE_DV_PROFILE = re.compile(r"(?i)dolby\s*vision[^/\n]{0,15}?profile\s*(\d+(?:\.\d+)?)|dvhe\.0?(\d+)")
HDR_FLAGS = [ # (шаблон, флаг) — что нашли, в этом порядке и отдаём
(r"dolby\s*vision|\bDoVi\b", "Dolby Vision"),
(r"HDR10\+|HDR10\s*plus", "HDR10+"),
(r"HDR10(?!\+|\s*plus)|SMPTE\s*ST\s*2086", "HDR10"),
(r"\bHLG\b", "HLG"),
]
RE_HDR_ANY = re.compile(r"(?i)\bHDR\b")
def _rate_kbps(s: str) -> int | None:
m = RE_RATE.search(s or "")
if not m:
return None
whole, frac, unit = m.group(1).replace(" ", ""), m.group(2), m.group(3).lower()
# «24,8 Mbps» — десятичная запятая; «1.050 kbps» — разделитель тысяч
if unit in "kк" and re.fullmatch(r"[.,]\d{3}", frac) and len(whole) <= 3:
whole, frac = whole + frac[1:], ""
val = float(whole + frac.replace(",", "."))
# «5763 Mбит/с», «16000 Mbps» — на деле килобиты: видео > 1 Гбит/с не бывает
return round(val * 1000) if unit in "mм" and val < 1000 else round(val)
def _resolution(width: int | None, height: int | None, line: str) -> str | None:
"""Класс разрешения: по ширине (кадр с полосами 1920x800 — всё равно 1080p), иначе «1080p» из текста."""
if width:
for w, name in ((3800, "2160p"), (2500, "1440p"), (1900, "1080p"), (1200, "720p")):
if width >= w:
return name
return f"{height}p" if height else None
m = RE_PLINE.search(line)
return f"{m.group(1)}p" if m else None
def hdr_flags(*texts: str) -> list[str]:
joined = "\n".join(t for t in texts if t)
out = [flag for rx, flag in HDR_FLAGS if re.search(rx, joined, re.I)]
if not out and RE_HDR_ANY.search(joined):
out.append("HDR")
return out
def video_info(text: str, title: str = "") -> dict | None:
"""Первая строка «Видео:»/«Video:» (MediaInfo-ключи nnm — запасной путь); нет — None."""
line = _first(text, r"Видео(?:\s*\d+)?", r"Video(?:\s*\d+)?")
extra = " ".join(x for x in (_first(text, r"Видео\s*кодек", r"Видеокодек"),
_first(text, r"Размер\s+кадра"),
_first(text, r"Битрейт\s+видео")) if x)
if not line and not extra:
return None
src = line or extra
codec = next((name for rx, name in VIDEO_CODECS if re.search(rx, src, re.I)), None)
if codec is None and line and extra:
codec = next((name for rx, name in VIDEO_CODECS if re.search(rx, extra, re.I)), None)
size = RE_SIZE.search(src) or RE_SIZE.search(extra)
width, height = (int(size.group(1)), int(size.group(2))) if size else (None, None)
fps = RE_FPS.search(src)
depth = RE_DEPTH.search(src)
dv = RE_DV_PROFILE.search(src) or RE_DV_PROFILE.search("\n".join(_field(text, r"HDR\s*format")))
hdr = hdr_flags(src, *_field(text, r"HDR\s*format"), release_quality(text), title)
return {
"codec": codec,
"width": width,
"height": height,
"resolution": _resolution(width, height, src + " " + release_quality(text) + " " + (title or "")),
"fps": float(fps.group(1).replace(",", ".")) if fps else None,
"bitrate_kbps": _rate_kbps(src) or _rate_kbps(_first(text, r"Битрейт\s+видео")),
"bit_depth": int(depth.group(1)) if depth else None,
"hdr": hdr,
"dv_profile": (dv.group(1) or dv.group(2)) if dv else None,
"raw": src,
}
# ------------------------------------------------------------------ тип релиза
SOURCES = [ # (шаблон, тег); порядок важен: Remux раньше Rip, Rip раньше DL
(r"UHD\s*BD\s*-?Remux|UHD\s*Blu-?ray\s*Remux", "UHD BDRemux"),
(r"BD-?Remux|Blu-?ray\s*Remux|REMUX", "BDRemux"),
(r"WEB-?DL\s*-?Rip", "WEB-DLRip"),
(r"WEB-?DL", "WEB-DL"),
(r"WEB-?Rip", "WEBRip"),
(r"BD-?Rip|Blu-?ray\s*Rip", "BDRip"),
(r"HD-?Rip", "HDRip"),
(r"UHD\s*Blu-?ray|Blu-?ray\s*Disc|\bBD(?:25|50|66|100)\b|\bBlu-?ray\b", "Blu-ray"),
(r"DVD-?Rip", "DVDRip"),
(r"\bDVD-?9\b", "DVD9"),
(r"\bDVD-?5\b", "DVD5"),
(r"HDTV-?Rip", "HDTVRip"),
(r"HDTV", "HDTV"),
(r"SAT-?Rip", "SATRip"),
(r"\bTV-?Rip\b", "TVRip"),
(r"DVB", "DVB"),
(r"VHS-?Rip|VHS", "VHSRip"),
(r"DCP-?Rip", "DCPRip"),
(r"\bIPTV\b", "IPTV"),
(r"CAM-?Rip|\bCAM\b|\bTS\b|Telesync", "CAMRip"),
(r"\bDVD\b", "DVD"),
]
CONTAINERS = [
(r"\bMKV\b|Matroska", "MKV"), (r"\bMP4\b", "MP4"), (r"\bAVI\b", "AVI"), (r"\bM2TS\b", "M2TS"),
(r"\bISO\b", "ISO"), (r"DVD[-\s]?Video", "DVD-Video"), (r"\bTS\b", "TS"), (r"\bWEBM\b", "WEBM"),
(r"\bMOV\b", "MOV"), (r"\bWMV\b", "WMV"),
]
RE_HMS = re.compile(r"(?<!\d)(\d{1,2}):(\d{2}):(\d{2})(?!\d)")
def release_quality(text: str) -> str:
"""«Тип релиза» / «Качество видео» / «Качество» — без «| Исходник: …» и подписей после него."""
v = _first(text, r"Тип\s+релиза", r"Качество\s+видео", r"Качество")
v = re.split(r"\s+\|\s+|\s+Исходник\s*:", v, maxsplit=1)[0]
return v.strip(" .;")
def release_info(text: str, title: str = "") -> dict | None:
quality = release_quality(text)
# заголовок — только если по «Качеству» тип не понять («Hybrid (2160p)», «Без хардсаба»)
source = next((tag for s in (quality, title or "") for rx, tag in SOURCES
if re.search(rx, s, re.I)), None)
# «Формат : AVC» у MediaInfo nnm — не контейнер: берём первое значение, похожее на контейнер
container = next((tag for key in (r"Контейнер", r"Формат\s+видео", r"Формат")
for v in _field(text, key)
for rx, tag in CONTAINERS if re.search(rx, v, re.I)), None)
duration = _first(text, r"Продолжительность", r"Продолжительность\s+серии", r"Длительность")
# секунды — только для одного фильма: не «18 x ~ 00:20:00», не «~00:22:30 серия», не список файлов
hms = RE_HMS.search(duration)
single = hms and len(RE_HMS.findall(duration)) == 1 and not re.search(r"(?i)\d\s*[xх×]|сери|эпизод", duration)
sec = int(hms.group(1)) * 3600 + int(hms.group(2)) * 60 + int(hms.group(3)) if single else None
if not (quality or source or container or duration):
return None
return {"quality": quality or None, "source": source, "container": container,
"duration": duration or None, "duration_sec": sec}
# ------------------------------------------------------------------ субтитры
SUB_KEYS = (r"Субтитры(?:\s*#?\s*\d+|\s+[A-Za-z]{3})?", r"Subtitles?(?:\s*#?\s*\d+)?",
r"Язык\s+(?:подключаемых\s+)?субтитров")
RE_SUB_NONE = re.compile(r"(?i)^\s*(?:нет|отсутству\w*|no|none|-+)\s*\.?$")
RE_HARDSUB = re.compile(r"(?i)вшит\w*|hardsub|встроенн\w*\s+в\s+видео")
RE_FORCED = re.compile(r"(?i)forced|форсирован\w*|на\s+надписи|надписи")
RE_FULL = re.compile(r"(?i)\bfull\b|полн\w*")
RE_SDH = re.compile(r"(?i)\bSDH\b|для\s+слабослыш\w*|closed\s+captions")
RE_LANG_CODE = re.compile(r"(?<![A-Za-z])([A-Z][A-Za-z]{2})(?![A-Za-z])")
RE_TITLE_SUB = re.compile(r"(?i)\bSub\w*\s*\(([^()]{2,80})\)")
def _split_top(s: str) -> list[str]:
"""Делит по запятым/«;» вне скобок: «Русские (Forced), Украинские (Forced, SDH)» -> 2 части."""
out, depth, cur = [], 0, []
for ch in s:
if ch in "([":
depth += 1
elif ch in ")]":
depth -= 1
if ch in ",;" and depth <= 0:
out.append("".join(cur))
cur = []
else:
cur.append(ch)
out.append("".join(cur))
return [x.strip() for x in out if x.strip()]
def _lang(part: str) -> str:
code = lang_of_text(part)
if code:
return code
for m in RE_LANG_CODE.finditer(part):
c = LANG_CODES.get(m.group(1).upper())
if c:
return c
return ""
def subtitles_info(text: str, title: str = "") -> dict | None:
"""Строки «Субтитры: …» (и «Субтитры 2:», «Язык субтитров:»); нет строк — языки из «Sub (Rus, Eng)» заголовка."""
values = [v for key in SUB_KEYS for v in _field(text, key)]
fmt = _first(text, r"Формат\s+субтитров") or None
tracks, raw = [], []
hardsub = False
for v in values:
if not v or RE_SUB_NONE.match(v):
continue
raw.append(v)
hardsub = hardsub or bool(RE_HARDSUB.search(v))
for part in _split_top(v):
lang = _lang(part)
if not lang:
continue
# «Украинские (Forced, 2x Full, SDH)» — одна запись на язык, флаги объединяются
tracks.append({"lang": lang, "forced": bool(RE_FORCED.search(part)),
"full": bool(RE_FULL.search(part)), "sdh": bool(RE_SDH.search(part))})
if not raw and title:
m = RE_TITLE_SUB.search(title)
if m:
raw.append(m.group(0))
for part in _split_top(m.group(1)):
lang = _lang(part)
if lang:
tracks.append({"lang": lang, "forced": False, "full": False, "sdh": False})
if not raw and not values:
return None
merged: dict[str, dict] = {}
for t in tracks:
cur = merged.setdefault(t["lang"], {"lang": t["lang"], "forced": False, "full": False, "sdh": False})
for k in ("forced", "full", "sdh"):
cur[k] = cur[k] or t[k]
return {"languages": list(merged), "tracks": list(merged.values()), "format": fmt,
"hardsub": hardsub, "raw": "; ".join(raw or [v for v in values if v]) or None}
def tech_info(text: str, title: str = "") -> dict:
return {"video": video_info(text, title), "release": release_info(text, title),
"subtitles": subtitles_info(text, title)}
+15
View File
@@ -34,6 +34,8 @@ RE_ADULT = re.compile(r"(?<!\d)18\s*\+")
RE_CODEC = re.compile(r"(?i)(E-?AC-?3|AC-?3|DTS-HD\s*MA|DTS-HD|DTS:X|DTS|TrueHD|FLAC|AAC|Opus|MP3|L?PCM)")
RE_CH = re.compile(r"(?i)(\d(?:\.\d)?)\s*(?:ch\b|канал)")
RE_KBPS = re.compile(r"(?i)(\d{2,5})\s*(?:k|к)(?:bps|бит)")
RE_KHZ = re.compile(r"(?i)(?<![\d.,])(\d{2}(?:[.,]\d{1,3})?)\s*(?:khz|кгц)|(?<!\d)(\d{5,6})\s*(?:hz|гц)")
RE_ATMOS = re.compile(r"(?i)\batmos\b")
RE_LAYOUT = re.compile(r"(?<![\d.])([1-9])\.([0-2])(?![\d.])") # раскладка «5.1» -> 6 каналов
RE_NAMES = re.compile(r"\|\s*([^|]{2,40}?)\s*\||\(([^()]{2,40})\)")
RE_ORIGINAL = re.compile(r"(?i)\boriginal\b|оригинал")
@@ -78,7 +80,11 @@ def parse_audio_line(line: str) -> dict:
for n in re.split(r"\s*[,;/]\s*", chunk) # «|Дубляж, Le Doyen|» -> тип и студия отдельно
if n.strip() and not _NOT_NAME.match(n.strip()) and not RE_CODEC.search(n)]
layout = RE_LAYOUT.search(fixed)
khz = RE_KHZ.search(fixed)
khz_val = (float(khz.group(1).replace(",", ".")) if khz.group(1) else int(khz.group(2)) / 1000) if khz else None
return {"raw": body, "lang": lang,
"khz": (int(khz_val) if khz_val == int(khz_val) else khz_val) if khz_val else None,
"atmos": bool(RE_ATMOS.search(body)),
"codec": re.sub(r"\s+", "", codec.group(1)).upper() if codec else "",
"channels": _channels(ch.group(1)) if ch else (_channels(layout.group(0)) if layout else ""), "kbps": kbps.group(1) if kbps else "",
"adult": bool(RE_ADULT.search(body)), "original": bool(RE_ORIGINAL.search(body)),
@@ -86,6 +92,15 @@ def parse_audio_line(line: str) -> dict:
"names": names}
def audio_summary(info: dict | None) -> dict | None:
"""Поле `audio` ответа: {codec, channels, bitrate_kbps, khz, atmos}; нет строки «Аудио» — None."""
if not info:
return None
return {"codec": info["codec"] or None, "channels": int(info["channels"]) if info["channels"] else None,
"bitrate_kbps": int(info["kbps"]) if info["kbps"] else None,
"khz": info.get("khz"), "atmos": bool(info.get("atmos"))}
def audio_lines_all(text: str) -> list[dict]:
out = []
for m in RE_AUDIO.finditer(text or ""):
+30 -12
View File
@@ -2,24 +2,31 @@
Метрика — пары (тип, автор) на уровне релиза против меток датасета, только по
страницам, которых сегментатор не видел при обучении (is_test_release).
Запуск: .venv/Scripts/python tests/e2e_segmenter.py
Текст kinozal — из data/raw/kinozal/*.json (как у сервиса), см. segmenter.page_text.
На трекер берётся не больше E2E_MAX страниц (по умолчанию 400, детерминированно);
train-страницы считаются только с флагом --train-pages. Основная модель — E2E_MODEL
(по умолчанию models/best).
Запуск: .venv/Scripts/python tests/e2e_segmenter.py [каталог_сегментатора] [--train-pages]
"""
from __future__ import annotations
import hashlib
import json
import os
import sys
from collections import defaultdict
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from src.html_extract import read_input # noqa: E402
from src.predict import DubPredictor # noqa: E402
from src.release import predict_release # noqa: E402
from src.segmenter import is_test_release # noqa: E402
from src.segmenter import is_test_release, page_text # noqa: E402
ROOT = Path(__file__).resolve().parents[1]
MAX_PER_SOURCE = int(os.environ.get("E2E_MAX", "400"))
MODEL_DIR = os.environ.get("E2E_MODEL", "models/best")
def run(predictor, rows, mode: str, test_only: bool) -> dict:
@@ -27,17 +34,25 @@ def run(predictor, rows, mode: str, test_only: bool) -> dict:
for r in rows:
by_rel[r["release_id"]].append(r)
per_src = defaultdict(list)
for page in sorted((ROOT / "data" / "pages").glob("*.txt")):
pages = [p for p in (ROOT / "data" / "pages").glob("*.txt")
if is_test_release(p.stem) == test_only and p.stem in by_rel]
pages.sort(key=lambda p: hashlib.md5(p.stem.encode()).hexdigest())
for page in pages:
rid = page.stem
if is_test_release(rid) != test_only or rid not in by_rel:
src = by_rel[rid][0]["source"]
if len(per_src[src]) >= MAX_PER_SOURCE:
continue
gold = {(r["translation_type"], r["studio"] or None) for r in by_rel[rid]}
res = predict_release(predictor, text=read_input(page), segmenter=mode)
# как aggregate: «тип без студии» не отдельная дорожка, если у типа есть студия
# (kinozal: «Перевод: многоголосый» + студии из заголовка)
named = {t for t, s in gold if s}
gold = {(t, s) for t, s in gold if s or t not in named}
res = predict_release(predictor, text=page_text(page), segmenter=mode)
pred = {(t["translation_type"], t["studio"]) for t in res["tracks"]}
tp = len(gold & pred)
p = tp / len(pred) if pred else 0.0
r = tp / len(gold) if gold else 0.0
per_src[by_rel[rid][0]["source"]].append((p, r))
per_src[src].append((p, r))
out = {}
for s, v in per_src.items():
out[s] = (len(v), sum(x[0] for x in v) / len(v), sum(x[1] for x in v) / len(v))
@@ -46,13 +61,16 @@ def run(predictor, rows, mode: str, test_only: bool) -> dict:
def main() -> None:
rows = json.loads((ROOT / "data" / "processed" / "dataset.json").read_text(encoding="utf-8"))
seg_dir = sys.argv[1] if len(sys.argv) > 1 else "models/segmenter"
predictor = DubPredictor(segmenter_dir=seg_dir)
print(f"сегментатор: {seg_dir}")
args = [a for a in sys.argv[1:] if not a.startswith("--")]
seg_dir = args[0] if args else "models/segmenter"
predictor = DubPredictor(MODEL_DIR, segmenter_dir=seg_dir)
print(f"модель: {MODEL_DIR}; сегментатор: {seg_dir}")
if predictor.segmenter is None:
sys.exit("нет models/segmenter: python -m src.segmenter --train")
for test_only, name in ((True, "тестовые страницы (не виделись сегментатору)"),
(False, "train-страницы (для справки, оптимистично)")):
blocks = [(True, "тестовые страницы (не виделись сегментатору)")]
if "--train-pages" in sys.argv:
blocks.append((False, "train-страницы (для справки, оптимистично)"))
for test_only, name in blocks:
print(f"== {name} ==")
for mode in ("rules", "model"):
res = run(predictor, rows, mode, test_only)
+166
View File
@@ -219,6 +219,74 @@ check("title: студия вписана в дорожку без студии"
check("title: дубль студии пропущен, новая добавлена",
len(_page) == 3 and _page[2]["source"] == "title")
from src.fetch import inline_tabs, kinozal_tabs # noqa: E402
from src.release import codec_segments, merge_extra_tracks, same_studio # noqa: E402
_kz = ('<ul><li><a onclick="showtab(7,100); return false;" href="#">Техданные</a></li>'
'<li><a onclick="showtab(7,0); return false;" href="#">Релиз</a></li>'
'<li><a onclick="showtab(7,2); return false;" href="#">Скриншоты</a></li></ul>'
'<div id="tabs"><b>Перевод:</b> Профессиональный многоголосый</div>')
check("kinozal: вкладки без «Техданных» и скриншотов", kinozal_tabs(_kz) == [("7", 0, "Релиз")])
_kz_text = html_to_text(inline_tabs(_kz, [("Релиз", "Аудио 1: AC3 - Русский (MVO, LostFilm)<br>")]))
check("kinozal: вкладка «Релиз» попадает в текст",
"Перевод: Профессиональный многоголосый" in _kz_text and "(MVO, LostFilm)" in _kz_text)
check("codec_segments: русская дорожка с типом, без иноязычной",
codec_segments("Аудио 3: AC3, 2.0 ch - Русский (MVO, HDrezka Studio)\n"
"Аудио 6: E-AC3, 5.1 ch, 640 Kbps - English Dubbing\n"
"Перевод: Профессиональный") == ["AC3, 2.0 ch - Русский (MVO, HDrezka Studio)"])
check("same_studio: варианты написания",
same_studio("DVD", "DVD Магия") and same_studio("Михаил Яроцкий", "М.Яроцкий/Kyberpunk")
and not same_studio("LostFilm", "HDrezka"))
_page2 = [dict(_t("проф. многоголосый", None), studio_span=None, studio_confidence=None)]
merge_extra_tracks(_page2, [
dict(_t("проф. многоголосый", "HDrezka"), studio_span="HDrezka Studio", studio_confidence=1),
dict(_t("проф. многоголосый", "LostFilm"), studio_span="LostFilm", studio_confidence=1),
dict(_t("одноголосый", None), studio_span=None, studio_confidence=None)],
"codec", match=same_studio)
check("codec: студия вписана, новая добавлена, без студии — нет",
[(x["studio"], x["source"]) for x in _page2]
== [("HDrezka", "page+codec"), ("LostFilm", "codec")])
_page3 = [dict(_t("проф. многоголосый", None), studio_span=None, studio_confidence=None)]
merge_extra_tracks(_page3, [
dict(_t("неизвестно", "LostFilm"), studio_span="LostFilm", studio_confidence=1),
dict(_t("неизвестно", "TVShows"), studio_span="TVShows", studio_confidence=1)],
"codec", match=same_studio, adopt_type=True)
check("codec: «Аудио Rus: … | LostFilm» получает тип дорожки страницы",
[(x["translation_type"], x["studio"]) for x in _page3]
== [("проф. многоголосый", "LostFilm"), ("проф. многоголосый", "TVShows")])
from src.release import codec_ok, model_segments # noqa: E402
from src.segmenter import kinozal_text, label_page # noqa: E402
class _FakeSeg:
def select_lines(self, text):
return text.splitlines()
_main, _codec = model_segments(_FakeSeg(), "Перевод: Профессиональный многоголосый\n"
"Аудио 3: AC3 - Русский (MVO, LostFilm)\n"
"Аудио 6: AC3 - Казахский (Dub, TK Qazaqstan)")
check("model_segments: основные и кодек-строки раздельно, иноязычная отброшена",
_main == ["Профессиональный многоголосый"] and _codec == ["AC3 - Русский (MVO, LostFilm)"])
check("codec_ok: «English Dubbing» и «Original» — не русская дорожка",
not codec_ok("Аудио 6: E-AC3 - English Dubbing") and not codec_ok("Аудио 5: E-AC3 - Original (французский)")
and codec_ok("Аудио Rus: AC3, 2 ch | LostFilm"))
check("label_page: raw — 1, неразмеченная кодек-строка — -1, сводка «Аудио:» — 0",
label_page(["Перевод: Дублированный", "Аудио 2: AC3 - Русский (MVO, X)",
"Аудио: Русский, английский (AC3)", "Размер: 6 ГБ"], ["Перевод: Дублированный"])
== [1, -1, 0, 0])
import json # noqa: E402
import tempfile as _tmp # noqa: E402
with _tmp.TemporaryDirectory() as _d:
Path(_d, "123.json").write_text(json.dumps({"h1": "X / Y / 2026 / ПМ (LostFilm)",
"tech_tab": "Перевод: Профессиональный многоголосый",
"release_tab": "Аудио Rus: AC3 | LostFilm"}), encoding="utf-8")
check("kinozal_text: заголовок + Техданные + Релиз из raw JSON",
kinozal_text("kz-123", Path(_d)).splitlines()
== ["X / Y / 2026 / ПМ (LostFilm)", "Перевод: Профессиональный многоголосый", "Аудио Rus: AC3 | LostFilm"]
and kinozal_text("kz-404", Path(_d)) is None)
_dump = ("Меню\nГангстерленд / MobLand / 2025 / ПМ (LostFilm) / WEB\n"
"Гангстерленд / 2026 / ДБ (X) / WEB-DL")
check("title_from_text: первая строка",
@@ -317,6 +385,58 @@ check("аудио: дорожка студии находит свою стро
_orig = original_info(_au)
check("оригинал: ENG, 768 kbps, 6 каналов", _orig and _orig["lang"] == "ENG" and _orig["bitrate_kbps"] == 768 and _orig["channels"] == 6, str(_orig))
check("оригинал: нет строки Original -> None", original_info(_au[:2]) is None)
check("оригинал: E-AC3+Atmos -> atmos, 48 kHz", _orig and _orig["atmos"] and _orig["khz"] == 48 and _orig["codec"] == "E-AC3", str(_orig))
from src.trackinfo import audio_summary, parse_audio_line
check("аудио: без Atmos -> atmos false", audio_summary(_au[0])["atmos"] is False)
check("аудио: 44.1 KHz и 48000 Гц", parse_audio_line("AC3, 2 ch, 44.1 KHz, 192 kbps")["khz"] == 44.1
and parse_audio_line("AAC 48000 Гц 2 ch 128 kbps")["khz"] == 48)
check("аудио: «48 kHz» не меняет вход модели (кодек без +Atmos)", parse_audio_line("English E-AC3+Atmos / 5.1 / 48 kHz / 768 kbps")["codec"] == "E-AC3")
print("== техданные: видео, тип релиза, субтитры ==")
from src.release import title_from_html
from src.techinfo import release_info, subtitles_info, video_info, _rate_kbps
_fx_html = open("data/samples/rt-topic-6870900.html", encoding="utf-8").read()
_fx_title = title_from_html(_fx_html)
check("заголовок rutracker без разрывов слов (<span>/<wbr>)",
_fx_title.startswith("Супер Марио: Галактическое кино / The Super Mario Galaxy Movie (Аарон Хорват"), _fx_title[:80])
_fx_text = html_to_text(_fx_html)
_v = video_info(_fx_text, _fx_title)
check("видео: HEVC 3840x2160 2160p 23.976 fps 24800 kbps 10 бит",
_v and (_v["codec"], _v["width"], _v["height"], _v["resolution"], _v["fps"], _v["bitrate_kbps"], _v["bit_depth"])
== ("HEVC", 3840, 2160, "2160p", 23.976, 24800, 10), str(_v))
check("видео: HDR — Dolby Vision, HDR10+, HDR10; профиль DV 8",
_v and _v["hdr"] == ["Dolby Vision", "HDR10+", "HDR10"] and _v["dv_profile"] == "8", str(_v and _v["hdr"]))
_r = release_info(_fx_text, _fx_title)
check("релиз: WEB-DL, MKV, 01:38:12 = 5892 с",
_r and (_r["source"], _r["container"], _r["duration"], _r["duration_sec"]) == ("WEB-DL", "MKV", "01:38:12", 5892), str(_r))
_s = subtitles_info(_fx_text, _fx_title)
check("субтитры: RUS forced, UKR forced+full+SDH, ENG full+SDH, softsub (SRT)",
_s and [(t["lang"], t["forced"], t["full"], t["sdh"]) for t in _s["tracks"]]
== [("RUS", True, False, False), ("UKR", True, True, True), ("ENG", False, True, True)]
and _s["format"] == "softsub (SRT)", str(_s))
check("видео: битрейт не склеивается с высотой кадра", _rate_kbps("Xvid 640x272 699 kb/s") == 699
and _rate_kbps("DivX 704 x 400 699 KB/s") == 699 and _rate_kbps("8 200 Кбит/сек") == 8200)
check("видео: «5763 Mбит/с» — на деле килобиты", _rate_kbps("MPEG-4 AVC, 5763 Mбит/с, 1280x720") == 5763)
check("видео: кадр 1920x800 — всё равно 1080p; fps 23.809523 целиком",
(lambda v: v["resolution"] == "1080p" and v["fps"] == 23.809523)(video_info("Видео: AVC, 1920x800, 23.809523 fps, 1776 Kbps")))
check("видео: строки «Видео» нет -> None", video_info("Аудио: AC3, 2 ch") is None)
check("релиз: «18 x ~ 00:20:00» и список файлов — без duration_sec",
release_info("Продолжительность: 18 x ~ 00:20:00")["duration_sec"] is None
and release_info("Продолжительность: 01:31:25, 01:25:23")["duration_sec"] is None)
check("релиз: «Формат : AVC» MediaInfo — не контейнер, «Формат: Matroska» — MKV",
release_info("Формат : AVC\nФормат : Matroska")["container"] == "MKV")
check("релиз: тип из заголовка, если «Качество» его не называет",
release_info("Качество: Hybrid (2160p)", "Фильм / Film [2024, BDRemux 2160p]")["source"] == "BDRemux")
_sn = subtitles_info("Субтитры: отсутствуют")
check("субтитры: «отсутствуют» — пустой список языков, а не None", _sn is not None and _sn["languages"] == [], str(_sn))
check("субтитры: nnm — поле до табуляции («Отсутствуют\\tДата…»)",
subtitles_info("Субтитры: Отсутствуют\tДата мировой премьеры: 2 апреля 2011")["languages"] == [])
check("субтитры: kinozal «Субтитры1: Full | iTunes • RUS»",
subtitles_info("Субтитры1: Full | iTunes • RUS\nСубтитры2: Forced | Official • UKR")["languages"] == ["RUS", "UKR"])
check("субтитры: вшитые -> hardsub", subtitles_info("Субтитры: русские, вшитые")["hardsub"] is True)
check("субтитры: строк нет — языки из «Sub (Rus, Eng)» заголовка",
subtitles_info("", "Фильм [2024] Dub + Sub (Rus, Eng)")["languages"] == ["RUS", "ENG"])
check("субтитры: нигде нет -> None", subtitles_info("Видео: AVC") is None)
_T = ["проф. дубляж", "проф. многоголосый", "люб. многоголосый", "многоголосый"]
check("тип: «Любительский (многоголосый …) Дубляжная» не дубляж",
guard_type("проф. дубляж", "Любительский (многоголосый закадровый) Дубляжная", "Дубляжная", _T) == "люб. многоголосый")
@@ -353,6 +473,7 @@ check("series_info: сезон, серии, total и «студия -> сери
print("== сборщик датасета (tools/collect.py) ==")
import csv as _csv # noqa: E402
import json as _json # noqa: E402
import json # noqa: E402
import tempfile as _tmp # noqa: E402
from pathlib import Path as _P # noqa: E402
@@ -857,6 +978,51 @@ check("prepare_data: у редкого языка метка пустая", [r["
print("== метки: тип, раскладка, строки из «Аудио», веса классов ==")
import harvest_core as _hc2 # noqa: E402
_ps = _hc2.nnm_part_studio
check("nnm «Перевод:»: студия из скобок и после тире",
_ps("Дублированный (Невафильм)") == "Невафильм" and _ps("Многоголосый профессиональный - Jaskier") == "Jaskier"
and _ps("Авторский (Живов") == "Юрий Живов")
check("nnm «Перевод:»: тип, кратность, лицензия, соседнее поле — не студия",
_ps("Двуголосый профессиональный") == "" and _ps("Одноголосый x2") == ""
and _ps("Дублированный (Лицензия BD CEE)") == ""
and _ps("одноголосый закадровый Дата мировой премьеры: 4 февраля 1983 (USA)") == "")
_kzr = _hc2.kz_title_rows(
{"id": "kz-1", "h1": "Что, если...? / What If...? / 2023 / ПМ (LostFilm, HDrezka Studio), СТ / WEB-DL"},
"Аудио Rus: AC3, 48 kHz, 2 ch, 384 Кбит/сек | LostFilm",
[{"studio": "(не указана)", "raw": "Перевод: Профессиональный многоголосый", "translation_type": "проф. многоголосый"}])
check("kinozal: студии из заголовка, строка «Аудио» — в audio_raw",
[(r["translation_type"], r["studio"], r["studio_source"], bool(r["audio_raw"])) for r in _kzr]
== [("проф. многоголосый", "LostFilm", "title", True), ("проф. многоголосый", "HDrezka", "title", False)])
check("kinozal: студия, уже названная на странице, из заголовка не дублируется",
_hc2.kz_title_rows({"id": "kz-1", "h1": "X / Y / 2023 / ПМ (LostFilm)"}, "",
[{"studio": "LostFilm", "raw": "Перевод: … LostFilm"}]) == [])
import re as _re # noqa: E402
_known = _re.compile(r"(?i)(?<![\w])(LostFilm|TVShows|HDrezka Studio|HDrezka|Дубляжная|1\+1)(?![\w])")
_kzrel = [{"source": "kinozal", "studio": "(не указана)", "raw": "Перевод: Профессиональный (многоголосый закадровый)",
"translation_type": "проф. многоголосый"}]
_cr = _hc2.codec_line_rows({"id": "kz-2"}, "\n".join([
"Аудио 1: AC3, 6 ch, 448 Кбит/с - Русский дубляж (Мосфильм-Мастер) - лицензия",
"Аудио Rus: AC3, 48,0 kHz, 6 ch, 384 Кбит/сек | TVShows",
"Аудио 3: AC3, 2 ch, 192 Kbps - Украинский (DVO, ТК \"1+1\")",
"Аудио 4: AC3, 2/0 (L,R) ch, 192 kbps, Delay 0 mSec Русский (MVO, LostFilm)",
"Аудио 5: AC3, 6 ch, 448 kbps - Английский (Original)",
"Аудио: Русский (AC3, 6 ch, 384 Кбит/с) Дубляжная, английский (AC3, 6 ch)",
]), _kzrel, known=_known)
check("кодек-строки: студия, тип из строки или единственный тип релиза, язык",
[(r["studio"], r["translation_type"], r["lang"], r["studio_source"]) for r in _cr]
== [("Мосфильм-Мастер", "проф. дубляж", "RUS", "codec"), ("TVShows", "проф. многоголосый", "RUS", "codec"),
("1+1", "проф. двухголосый", "UKR", "codec"), ("LostFilm", "проф. многоголосый", "RUS", "codec")],
str([(r["studio"], r["translation_type"]) for r in _cr]))
check("кодек-строки: вход — сегмент без префикса «Аудио N:», вся строка — в audio_raw",
_cr[0]["raw"].startswith("AC3, 6 ch") and _cr[0]["audio_raw"].startswith("Аудио 1:"))
check("кодек-строки: студия, уже размеченная, не дублируется (в т.ч. «А. Гаврилов» = «Андрей Гаврилов»)",
_hc2.codec_line_rows({"id": "x"}, "Аудио 2: AC3, 2 ch, 192 Kbps - Русский (AVO, А. Гаврилов)",
[{"studio": "Андрей Гаврилов", "translation_type": "авт. одноголосый"}], known=_known) == [])
check("кодек-строки: при нескольких типах релиза тип строки без тег-слова — «неизвестно»",
[r["translation_type"] for r in _hc2.codec_line_rows(
{"id": "x"}, "Аудио Rus: AC3, 2 ch, 192 Кбит/сек | HDrezka Studio",
[{"studio": "", "translation_type": "проф. дубляж"}, {"studio": "", "translation_type": "авт. одноголосый"}],
known=_known)] == ["неизвестно"])
_tt = _hc2.translation_type
check("тип: любительский дубляж — отдельный класс", _tt("Любительский (дублированный) Red Head Sound")[0] == "люб. дубляж")
check("тип: псевдодубляж -> люб. дубляж", _tt("Любительский (псевдодублированный)")[0] == "люб. дубляж")
+260
View File
@@ -0,0 +1,260 @@
"""Аудит дорожек в описаниях релизов: data/raw/* против data/labels.csv.
Читает текст каждой сохранённой страницы (kinozal: «Техданные» + «Релиз»;
nnm / rutracker: пост), находит строки русских/украинских дорожек
(«Перевод N:», «Озвучка:», «Аудио N: … (MVO, X)» и т. п., без субтитров) и считает:
* какие форматы строк встречаются и какие из них не ловят правила разметки
(segmenter.RE_AUDIO_TRACK) и запасной путь сервиса (release.codec_segments);
* студии из словаря меток, названные в строках дорожек, но отсутствующие
в метках релиза (сравнение через release.same_studio — «А. Гаврилов» = «Андрей Гаврилов»);
* мусор в колонке studio (каналы, языки, тип-слова, «Лицензия», «по заказу»);
* релизы без меток: legacy / ok / есть ли в них строки дорожек.
Результат — reports/tracks_audit_numbers.md (цифры и примеры); выводы и план —
reports/tracks_audit.md. Чтение raw ~2–3 мин; --cache сохраняет тексты в pickle.
.venv\\Scripts\\python tools\\audit_tracks.py [--cache путь.pkl]
"""
from __future__ import annotations
import argparse
import csv
import json
import os
import pickle
import random
import re
import sys
from collections import Counter, defaultdict
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from src.release import RE_CODEC_TRACK, codec_ok, same_studio # noqa: E402
from src.segmenter import RE_AUDIO_TRACK # noqa: E402
RAW = ROOT / "data" / "raw"
LABELS = ROOT / "data" / "labels.csv"
OUT = ROOT / "reports" / "tracks_audit_numbers.md"
SOURCES = (("kinozal", "kz-"), ("nnm", "nnm-"), ("rutracker", "rt-"))
NO_STUDIO = "(не указана)"
# строка дорожки: перевод/озвучка или кодек-строка
RE_TRANS_LINE = re.compile(
r"(?i)^\s*[#№*•\-\s]*(перевод\w*|озвуч\w*|дубляж\w*|автор перевода)\s*(№|#)?\s*[\d\-–,\s]*(\([^)]*\))?\s*[:.]")
RE_AUDIO_LINE = re.compile(
r"(?i)^\s*[#№*•\-\s]*(аудио\w*|audio\w*|звук\w*|аудиодорожк\w*|дорожк\w*|track\w*)\b")
RE_CODEC = re.compile(
r"(?i)\b(e-?ac-?3|ac-?3|ас3|dts|aac|truehd|flac|mp3|opus|l?pcm|mp2|dolby)\b|\d\s*(kbps|кбит|kb/s|khz|кгц)")
RE_RU = re.compile(r"(?i)русск|russian|\brus\b|украин|ukrain|\bukr\b|україн")
RE_NON_RU = re.compile(
r"(?i)english|\beng\b|англ|япон|japan|\bjap\b|казах|kazakh|корей|китай|немец|француз|итальян|испан|оригинал|original")
RE_TYPE = re.compile(
r"(?i)\b(MVO|DVO|AVO|DUB|VO)\b|дубл|многоголос|двухголос|двуголос|одноголос|закадр|авторск|любительск|профессиональн")
RE_SUBS = re.compile(r"(?i)субтитр")
# не студия: тех. параметры, языки, тип-слова, источник дорожки
JUNK = re.compile(
r"(?i)^(\d+\s*ch|.*\b(ac-?3|dts|aac|kbps|kb/s|ch)\b.*|l,.*|.*русск\w*.*|английск\w*|украинск\w*|французск\w*"
r"|испанск\w*|немецк\w*|итальянск\w*|японск\w*|казахск\w*|субтитр\w*|ukr|rus|eng|eur|cee|vhs|tv|ts"
r"|дубляж|полное дублирование|дублированный|дву(х)?голосый|многоголосый|одноголосый|закадровый"
r"|профессиональный|любительский|авторский|студийный|неизвестн\w*|оригинал\w*|original"
r"|.*лицензи\w*.*|звук.*|blu-?ray.*|bd.*|itunes.*|netflix|amazon|web-?dl|в составе контейнера"
r"|.*ненормативн\w*.*|18\+)$")
# виды мусора для отчёта (первое совпадение)
NOISE_KINDS = (
("каналы/кодек", r"(?i)\b(\d+\s*ch|ac-?3|dts|aac|kbps|kb/s)\b|^l,"),
("язык вместо студии", r"(?i)^(русск|английск|украинск|французск|испанск|казахск)\w*$"),
("язык-префикс «Русский, - X»", r"(?i)^(русск|украинск|english)\w*\s*,"),
("тип-слово", r"(?i)^(дубляж|полное дублирование|дву(х)?голосый|многоголосый|одноголосый|закадровый)$"),
("источник: лицензия/Blu-ray/iTunes/Netflix", r"(?i)лицензи|blu-?ray|^bd\b|itunes|netflix|amazon"),
("«X по заказу Y»", r"(?i)по заказу"),
("субтитры", r"(?i)субтитр"),
("неру дорожка", r"(?i)казах|qazaq|kazakh|английск|english"),
)
def nid(rid: str) -> str:
return re.sub(r"^(kz|nnm|rt)-", "", rid)
def norm(s: str) -> str:
return re.sub(r"\s+", " ", s).strip().lower().replace("ё", "е")
def load_texts(cache: Path | None) -> dict:
"""{(source, id): (title, text, legacy)}; kinozal — h1 и вкладки, nnm/rt — пост."""
if cache and cache.exists():
return pickle.loads(cache.read_bytes())
out = {}
for src, _ in SOURCES:
d = RAW / src
for n in sorted(os.listdir(d), key=lambda n: "legacy" in n):
if not n.endswith(".json"):
continue
rid = n.split(".")[0]
if (src, rid) in out:
continue
try:
rec = json.loads((d / n).read_text(encoding="utf-8"))
except (OSError, ValueError):
continue
if src == "kinozal":
title = rec.get("h1") or ""
text = "\n".join(x for x in (rec.get("tech_tab"), rec.get("release_tab")) if x)
else:
title, text = rec.get("title") or "", rec.get("post") or ""
out[(src, rid)] = (title, text, "legacy" in n, rec.get("ok", True))
if cache:
cache.write_bytes(pickle.dumps(out))
return out
def track_lines(text: str) -> list[tuple[str, str]]:
"""[(вид, строка)] строк русских/украинских дорожек; вид: trans | audio."""
out = []
for line in text.splitlines():
line = line.strip()
if not line or len(line) > 400 or RE_SUBS.search(line) and not RE_TYPE.search(line):
continue
if RE_TRANS_LINE.match(line):
if RE_NON_RU.search(line.split(":", 1)[0]) and not RE_RU.search(line):
continue
out.append(("trans", line))
elif (RE_AUDIO_LINE.match(line) or RE_RU.match(line)) and RE_CODEC.search(line):
if RE_RU.search(line) and (RE_TYPE.search(line) or "|" in line or "(" in line):
out.append(("audio", line))
return out
def line_prefix(line: str) -> str:
p = re.match(r"\s*\W*\w*[^:]{0,14}:?", line).group(0).strip().lower()
return re.sub(r"\s+", " ", re.sub(r"\d+", "N", p))
def studio_vocab(rows: list[dict]) -> tuple[re.Pattern, dict]:
cnt = Counter(r["studio"].strip() for r in rows if r["studio"] not in ("", NO_STUDIO))
vocab = [s for s, c in cnt.items() if c >= 5 and len(s) >= 3 and not JUNK.match(s)]
vocab.sort(key=len, reverse=True)
rx = re.compile(r"(?i)(?<![\w])(" + "|".join(map(re.escape, vocab)) + r")(?![\w])")
return rx, {norm(s): s for s in vocab}
def audit(texts: dict, rows: list[dict]) -> dict:
lab = defaultdict(list)
for r in rows:
lab[(r["source"], nid(r["release_id"]))].append(r)
voc_rx, canon = studio_vocab(rows)
R = defaultdict(lambda: defaultdict(Counter))
ex = defaultdict(list)
for (src, rid), (title, text, legacy, ok) in texts.items():
L = lab.get((src, rid), [])
lines = track_lines(text)
rel = R[src]["releases"]
rel["всего"] += 1
if not L:
rel["без меток"] += 1
key = ("legacy" if legacy else "обычный") + (", ok=False" if not ok else "") + \
(", есть строки дорожек" if lines else ", нет строк дорожек")
R[src]["без меток"][key] += 1
if lines and len(ex[(src, "unlab")]) < 3000:
ex[(src, "unlab")].append((rid, title[:80], lines[0][1][:140]))
labs = [r["studio"] for r in L if r["studio"] not in ("", NO_STUDIO) and not JUNK.match(r["studio"].strip())]
missing: dict[str, tuple[str, str]] = {}
for kind, line in lines:
R[src]["строки"][kind] += 1
if kind == "audio":
a = bool(RE_AUDIO_TRACK.match(line))
b = bool(RE_CODEC_TRACK.match(line) and codec_ok(line))
R[src]["кодек-строки"][("RE_AUDIO_TRACK " if a else "мимо RE_AUDIO_TRACK ") +
("| codec_segments" if b else "| мимо codec_segments")] += 1
if not a or not b:
miss = ("seg " if not a else "") + ("codec" if not b else "")
R[src]["форматы мимо: " + miss.strip()][line_prefix(line)] += 1
if len(ex[(src, "fmt:" + miss.strip())]) < 3000:
ex[(src, "fmt:" + miss.strip())].append((rid, line[:160]))
if not L:
continue
for m in voc_rx.finditer(line):
st = canon.get(norm(m.group(1)), m.group(1))
if any(same_studio(st, x) for x in labs) or any(same_studio(st, x) for x, _ in missing.values()):
continue
missing[norm(st)] = (st, kind)
if len(ex[(src, "miss:" + kind)]) < 5000:
ex[(src, "miss:" + kind)].append((rid, st, line[:160]))
if L:
rel["с метками"] += 1
rel["с метками, есть недостающая студия"] += bool(missing)
for st, kind in missing.values():
R[src]["недостающие студии: вид строки"][kind] += 1
R[src]["недостающие студии: топ"][st] += 1
# недостающие студии в релизе, где у меток вообще нет студий
if missing and not labs:
rel["с метками без студий, студия есть в кодек-строке"] += 1
full = norm(title + "\n" + text)
for r in L:
st = r["studio"].strip()
if st in ("", NO_STUDIO) or JUNK.match(st):
continue
R[src]["студии меток"]["всего"] += 1
words = [w for w in re.findall(r"\w{4,}", norm(st))]
if norm(st) not in full and not any(w in full for w in words):
R[src]["студии меток"]["нет в тексте/заголовке"] += 1
R[src]["студии меток: нет в тексте, studio_source"][r["studio_source"]] += 1
if len(ex[(src, "ghost")]) < 3000:
ex[(src, "ghost")].append((rid, st, r["raw"][:140]))
for r in rows:
st = r["studio"].strip()
for k, rx in NOISE_KINDS:
if re.search(rx, st):
R[r["source"]]["мусор в studio меток"][k] += 1
if len(ex[(r["source"], "noise:" + k)]) < 500:
ex[(r["source"], "noise:" + k)].append((r["release_id"], st, r["raw"][:120]))
break
return {"stats": R, "examples": ex}
def write_md(res: dict, path: Path) -> None:
R, ex = res["stats"], res["examples"]
rnd = random.Random(7)
out = ["# Аудит дорожек в raw-страницах (цифры)", "",
"Сгенерировано `tools/audit_tracks.py`; выводы и план — `reports/tracks_audit.md`.", ""]
for src, _ in SOURCES:
out += [f"## {src}", ""]
for block, cnt in R[src].items():
top = cnt.most_common(25)
out.append(f"**{block}**: " + "; ".join(f"{k} — {v}" for k, v in top))
out.append("")
for key in sorted(k for k in ex if k[0] == src):
v = ex[key]
out.append(f"<details><summary>примеры {key[1]} ({len(v)})</summary>\n")
for t in rnd.sample(v, min(15, len(v))):
out.append("- " + " · ".join(str(x).replace("|", "\\|") for x in t))
out += ["", "</details>", ""]
path.write_text("\n".join(out), encoding="utf-8")
def main() -> None:
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--cache", type=Path, help="pickle с текстами raw (создаётся, если нет)")
ap.add_argument("--out", type=Path, default=OUT)
args = ap.parse_args()
sys.stdout.reconfigure(encoding="utf-8")
with LABELS.open(encoding="utf-8-sig", newline="") as f:
rows = list(csv.DictReader(f))
res = audit(load_texts(args.cache), rows)
args.out.parent.mkdir(exist_ok=True)
write_md(res, args.out)
for src, _ in SOURCES:
print(f"== {src}")
for block, cnt in res["stats"][src].items():
print(f" {block}: " + "; ".join(f"{k} — {v}" for k, v in cnt.most_common(12)))
print(f"записано: {args.out.relative_to(ROOT)}")
if __name__ == "__main__":
main()
+196 -5
View File
@@ -1460,6 +1460,29 @@ def nnm_studio(name: str) -> tuple[str, str]:
return cleaned, " ".join(x for x in (note, extra) if x).strip()
# слова, которые в «Перевод: <тип> - <студия>» не студия: тип, кратность, язык
NNM_PART_TYPEISH = re.compile(
r"(?i)голос|дубл|закадр|авторск|любител|профессион|русск|оригинал|субтитр|лицензи"
r"|^\W*[xх]?\d+[xх]?\W*$")
def nnm_part_studio(part: str) -> str:
"""Студия из части строки «Перевод:» nnm: «Многоголосый профессиональный - Jaskier»,
«Дублированный (Невафильм)», «Авторский (Живов» -> имя; «Дублированный (Лицензия)»,
«Одноголосый x2», «Двуголосый профессиональный» -> ""."""
if len(part) > 90 or re.search(r"(?i)премьер|\bдата\b", part):
return "" # склеилось соседнее поле описания
m = re.search(r"\s[-–—:]\s*(.+)$", part)
cands = ([m.group(1)] if m else []) + [c for c in re.findall(r"\(([^()]*)\)?", part) if c]
for cand in cands:
studio, _ = clean_studio(cand)
studio, _ = canonical_studio(studio)
if len(studio) >= 2 and not NNM_PART_TYPEISH.search(studio) \
and studio.lower() not in SOURCE_LIKE:
return studio
return ""
def _nnm_parse_core(rec: dict) -> tuple[list[dict], dict]:
post = rec.get("post") or ""
title = rec.get("page_title") or rec.get("title") or ""
@@ -1539,20 +1562,21 @@ def _nnm_parse_core(rec: dict) -> tuple[list[dict], dict]:
if ttype in ("неизвестно", "оригинал", "субтитры"):
continue
perevod_types.append(part)
# без имён в скобках студия неизвестна, но тип перевода ценен
# студия — если названа в части («Дублированный (Невафильм)»); без неё тип всё равно ценен
rows.append({
"source": "nnm", "release_id": rec["id"], "ru_title": ru_title,
"year": year, "quality": quality, "is_series": is_series,
"studio": "(не указана)", "translation_type": ttype,
"studio": nnm_part_studio(part) or "(не указана)", "translation_type": ttype,
"professional": pro, "role_raw": part, "actors": "",
"note": mark_18("", has_18(line)), "age_18": int(has_18(line)),
"codec": "", "channels": "", "khz": "", "kbps": "",
"subtitles": "", "studio_source": "tech", "raw": line,
})
# если построчных «Аудио N» с именами не было — оставляем только типы
if any(r["studio"] != "(не указана)" for r in rows):
rows = [r for r in rows if r["studio"] != "(не указана)"]
# есть построчные «Аудио N» с именами — строки «Перевод:» (tech) не нужны; иначе
# оставляем tech-строки: типы и студии, названные в «Перевод:»
if any(r["studio"] != "(не указана)" and r["studio_source"] == "release" for r in rows):
rows = [r for r in rows if r["studio_source"] == "release" and r["studio"] != "(не указана)"]
# дорожка без имени и без типа («- Русский») ничего не даёт датасету
rows = [r for r in rows
@@ -1715,6 +1739,170 @@ def audio_only_rows(rec: dict, text: str, rows: list[dict]) -> list[dict]:
return out
def kz_title_rows(rec: dict, text: str, rows: list[dict]) -> list[dict]:
"""kinozal: студии из заголовка раздачи «… / ПМ (LostFilm, HDrezka Studio), ДБ (X) / …».
На kinozal студии часто названы только в заголовке и в строках «Аудио Rus: … | LostFilm»
(без типа), а «Перевод:» во «Техданных» — без студий. Строка-вход — тот же синтетический
сегмент, что строит сервис из заголовка (src.release.title_segments); строка «Аудио» с этой
студией (если есть) — в audio_raw (позитив для сегментатора).
"""
from src.release import RE_TITLE_TRACK, TITLE_TYPES
def key(x: str) -> str:
return re.sub(r"[\W_]+", "", (x or "").casefold())
title = (rec.get("h1") or rec.get("page_title") or "").split(" :: ")[0]
have = key(" ".join(((r.get("studio") or "") + " " + (r.get("raw") or "")) for r in rows))
lines = [ln for ln in audio_lines_all(text) if not ln["original"] and not ln["commentary"]]
base = rows[0] if rows else {}
info = kz_split_title(title)
out = []
for abbr, names in RE_TITLE_TRACK.findall(title):
for name in re.split(r"\s*[,;]\s*", names.strip()):
studio, note = clean_studio(name)
studio, extra = canonical_studio(studio)
k = key(studio)
if len(k) < 2 or k in have or studio.lower() in SOURCE_LIKE:
continue
kind, pro = translation_type(TITLE_TYPES[abbr])
a = next((ln for ln in lines if k in key(ln["raw"])), None)
out.append({
"source": "kinozal", "release_id": rec.get("id", ""),
"ru_title": base.get("ru_title") or info["ru_title"],
"year": base.get("year") or info["year"], "quality": base.get("quality", ""),
"is_series": base.get("is_series", bool(re.search(r"сезон|серии", title, re.I))),
"studio": studio, "translation_type": kind, "professional": pro,
"role_raw": TITLE_TYPES[abbr], "actors": "",
"note": mark_18(" ".join(x for x in (note, extra) if x).strip(), bool(a and a["adult"])),
"age_18": int(bool(a and a["adult"])),
"codec": a["codec"] if a else "", "channels": a["channels"] if a else "", "khz": "",
"kbps": a["kbps"] if a else "", "subtitles": "", "studio_source": "title",
"raw": f"{TITLE_TYPES[abbr]} | {name.strip()}",
"lang": (a["lang"] or "RUS") if a else "RUS", "audio_raw": a["raw"] if a else ""})
have += k
return out
# кодек-строка одной дорожки: «Аудио 2: …», «Аудио Rus: …», «Аудио pic RUS: …», «Звук #1: …»,
# «• №3: Русский (АС3 …)», «Russian (Dolby AC3, 6 ch) …»
RE_CODEC_LINE = re.compile(
r"(?i)^\s*[•#№*\-\s]*(?:(?:аудио\w*|audio|звук\w*)\b[^:\n]{0,24}:|№\s*\d+\s*:|(?:russian|русск\w*|украин\w*)\b)")
RE_CODEC_PARAM = re.compile(r"(?i)\b(?:e-?ac-?3|ac-?3|ас3|dts|aac|truehd|flac|mp3|opus|l?pcm|mp2)\b|\d\s*(?:kbps|кбит|kb/s)")
RE_LANG_WORD = re.compile(
r"(?i)русск|russian|\brus\b|украин|ukrain|\bukr\b|україн|англ|english|\beng\b|япон|japan|казах|kazakh"
r"|француз|french|немецк|german|итальян|испан|корей|китайск")
# не студия: язык, тех. параметры, тип, источник, пометки
CODEC_NOT_STUDIO = re.compile(
r"(?i)^(?:rus|ukr|eng|ru|ua|int|ext|pic|dub|mvo|dvo|avo|vo|лицензи\w*|ненормативн\w*.*|.*лексик\w*"
r"|присутствует.*|внешн\w*|в составе.*|серии.*|эп\w*\.?\s*\d.*|[\d\s.,:/\-]+|.*\b(?:kbps|ch|khz|кбит)\b.*"
r"|.*(?:русск|английск|украинск|russian|english|ukrainian|оригинал|original)\w*.*"
r"|.*(?:голос|дублир|закадр|перевод).*|.*(?<![а-яё])дубляж(?![а-яё]).*|звук.*"
r"|.*(?:blu-?ray|itunes|netflix|amazon|\bbd\b|\bdvd\b|web-?dl).*)$")
# имя из скобок кодек-строки, которого нет в словаре: «(L,C,R,l,r)», «(АС3 …)», «(1-8 серии)», «(поздний)»
CODEC_NAME_NOISE = re.compile(
r"(?i)^(?:[lrc]|sl|sr|ls|rs|lfe)\b|\b(?:[lrc]|lfe)\s*[,;]|^(?:cee|eur|vhs|cbr|vbr|lfe|stereo|custom"
r"|line|production|россия|сек|[aа]{1,2}[сc]\w?|мр\w?|front|side)\b|\bmono\b|dolby|поздн|ранн|отдельн|тифло|комментар"
r"|сезон|сери[ия]|эпизод|файл|контейнер|bits?\b|sound\s*track|sdh|forced|вставк|сэмпл|sample|неизвестн|unknown")
def _lang_key(word: str) -> str:
w = word.lower()
return "RUS" if w.startswith(("русс", "russ", "rus")) else "UKR" if w.startswith(("укр", "ukr")) else w[:4]
def _known_studios() -> re.Pattern | None:
"""Словарь студий из data/labels.csv (≥ 5 строк): имена, которые kinozal пишет без скобок
(«Аудио Rus: … | LostFilm», «… - Русский, LostFilm», «… MVO DniproFilm / HDRezka Studio»)."""
global _KNOWN_RX
if _KNOWN_RX is None:
path = _REPO / "data" / "labels.csv"
cnt: Counter = Counter()
if path.exists():
with path.open(encoding="utf-8-sig", newline="") as f:
for r in csv.DictReader(f):
if r.get("studio_source") == "codec": # словарь — без своих же строк: повторный прогон стабилен
continue
st = (r.get("studio") or "").strip()
if len(st) >= 3 and st != "(не указана)" and not CODEC_NOT_STUDIO.match(st) \
and st.lower() not in SOURCE_LIKE and not CODEC_NAME_NOISE.search(st):
cnt[st] += 1
names = sorted((s for s, c in cnt.items() if c >= 5), key=len, reverse=True)
_KNOWN_RX = re.compile(r"(?i)(?<![\w])(" + "|".join(map(re.escape, names)) + r")(?![\w])") \
if names else re.compile(r"(?!x)x")
return _KNOWN_RX
_KNOWN_RX: re.Pattern | None = None
def codec_line_rows(rec: dict, text: str, rows: list[dict], known: re.Pattern | None = None) -> list[dict]:
"""Студии из кодек-строк русских/украинских дорожек, которых нет среди строк разметки.
kinozal: «Перевод: Профессиональный (многоголосый закадровый)» без студий + «Аудио 2: AC3 …
(MVO, LostFilm)», «Аудио Rus: … | TVShows»; nnm/rutracker — те же кодек-строки рядом с
«Перевод N:» со списком, где этой студии нет. Строка-вход — сегмент кодек-строки, как его
режет сервис (`src.release._entries`); тип — из самой строки (MVO, «дубляж»), иначе
единственный тип релиза, иначе «неизвестно». Сводка из нескольких языков
(«Аудио: Русский (AC3…), украинский (AC3…)»), оригинал и комментарии — пропускаются.
"""
from src.release import _entries, same_studio
from src.trackinfo import parse_audio_line
known = known if known is not None else _known_studios()
have = [r["studio"] for r in rows if r.get("studio") and r["studio"] != "(не указана)"]
types = {r["translation_type"] for r in rows} - {"неизвестно", ""}
base = rows[0] if rows else {}
out: list[dict] = []
for line in (text or "").splitlines():
line = re.sub(r"\s+", " ", line).strip()
if len(line) > 300 or not RE_CODEC_LINE.match(line) or not RE_CODEC_PARAM.search(line):
continue
if len({_lang_key(m.group(0)) for m in RE_LANG_WORD.finditer(line)}) > 1:
continue
info = parse_audio_line(re.sub(r"(?i)^\s*[^:]{0,30}:\s*", "", line, count=1))
if info["original"] or info["commentary"] or info["lang"] not in ("RUS", "UKR", ""):
continue
if not info["lang"] and not re.search(r"(?i)\brus\b|\bukr\b", line):
continue
tag = NNM_TAG_RE.search(line) or NNM_TAG_RE.search(line.translate(CYR_TO_LAT)) # «АVO» кириллицей
kind, pro = nnm_tag_type(tag.group(1)) if tag else ("", "")
if not kind:
kind, pro = translation_type(line)
if kind.endswith("неизвестно"):
kind, pro = "", ""
if not kind:
kind = next(iter(types)) if len(types) == 1 else "неизвестно"
pro = kind.split()[0] if kind.split()[0] in ("проф.", "люб.", "авт.") else ""
cands = [(m.group(1), True) for m in known.finditer(line)] + [(n, False) for n in info["names"]]
for name, in_vocab in cands:
studio, note = clean_studio(name)
studio, extra = canonical_studio(studio)
if len(studio) < 3 or studio.lower() in SOURCE_LIKE or CODEC_NOT_STUDIO.match(studio):
continue
if CODEC_NAME_NOISE.search(studio) or not in_vocab and re.search(r"\d", studio):
continue
if any(same_studio(studio, x) for x in have):
continue
entries = _entries(line)
seg = next((e for e in entries if name.lower() in e.lower()), entries[0] if entries else line)
out.append({
"source": base.get("source") or rec.get("source", ""), "release_id": rec.get("id", ""),
"ru_title": base.get("ru_title", ""), "year": base.get("year", ""),
"quality": base.get("quality", ""), "is_series": base.get("is_series", False),
"studio": studio, "translation_type": kind, "professional": pro,
"role_raw": "", "actors": "",
"note": mark_18(" ".join(x for x in (note, extra) if x).strip(), info["adult"]),
"age_18": int(info["adult"]), "codec": info["codec"], "channels": info["channels"],
"khz": "", "kbps": info["kbps"], "subtitles": "", "studio_source": "codec",
"raw": seg, "lang": info["lang"] or ("UKR" if re.search(r"(?i)\bukr\b", line) else "RUS"),
"audio_raw": line})
have.append(studio)
return out
def _with_audio(core, texts):
"""Обёртка парсера: lang/audio_raw у строк; число переводов в rel — по итоговым строкам."""
def parse(rec: dict) -> tuple[list[dict], dict]:
@@ -1725,7 +1913,10 @@ def _with_audio(core, texts):
rows += audio_only_rows(rec, text, rows)
elif core is _kz_parse_core:
rows = kz_audio_only_rows(rec, text)
if core is _kz_parse_core:
rows += kz_title_rows(rec, text, rows)
if rows:
rows += codec_line_rows(rec, text, rows)
rel["n_translations"], rel["ok"] = len(rows), True
rel["studios"] = " | ".join(sorted({r["studio"] for r in rows}))
return rows, rel
+136
View File
@@ -0,0 +1,136 @@
"""Доразметка студий в data/labels.csv без пересборки страниц.
То же, что теперь делает сборщик (tools/harvest/harvest_core.py) при переиндексации,
но по готовому labels.csv и data/raw — минуты вместо полного разбора HTML:
* nnm, строки «Перевод:» (studio_source=tech) с «(не указана)»: студия, названная
в самой части строки («Дублированный (Невафильм)», «… - Jaskier») — nnm_part_studio;
* kinozal: студии из заголовка раздачи «ПМ (LostFilm, HDrezka Studio)», которых нет
среди строк релиза (studio_source=title) — kz_title_rows по data/raw/kinozal/<id>.json;
* все трекеры: студии из кодек-строк «Аудио 2: AC3 … (MVO, LostFilm)», «Аудио Rus: … | TVShows»,
которых нет среди строк релиза (studio_source=codec) — codec_line_rows по data/raw/<трекер>/<id>.json.
Ручные релизы (data/manual_ids.txt) не трогаются. Перед записью — копия
data/labels.bak_relabel_<время>.csv. Повторный запуск ничего не добавляет.
.venv\\Scripts\\python tools\\relabel_studios.py # применить
.venv\\Scripts\\python tools\\relabel_studios.py --dry-run # только статистика
"""
from __future__ import annotations
import argparse
import csv
import json
import re
import shutil
import sys
import time
from collections import Counter, defaultdict
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "tools" / "harvest"))
import harvest_core as H # noqa: E402
LABELS = ROOT / "data" / "labels.csv"
RAW = ROOT / "data" / "raw"
RAW_KZ = RAW / "kinozal"
MANUAL = ROOT / "data" / "manual_ids.txt"
NO_STUDIO = "(не указана)"
def relabel(rows: list[dict], raw_kz: Path = RAW_KZ, manual: set[str] | None = None) -> tuple[list[dict], Counter]:
"""rows -> (новые rows, статистика). Исходные dict меняются на месте."""
manual = manual or set()
stats: Counter = Counter()
for r in rows:
if r["source"] == "nnm" and r.get("studio_source") == "tech" and r["studio"] == NO_STUDIO \
and r["release_id"] not in manual:
studio = H.nnm_part_studio(r.get("role_raw") or "")
if studio:
r["studio"] = studio
stats["nnm: студия из «Перевод:»"] += 1
by_rel: dict[str, list[dict]] = defaultdict(list)
for r in rows:
if r["source"] == "kinozal":
by_rel[r["release_id"]].append(r)
added: list[dict] = []
for rid, rel_rows in by_rel.items():
if rid in manual:
continue
path = raw_kz / f"{rid.removeprefix('kz-')}.json"
if not path.exists():
stats["kinozal: нет raw JSON"] += 1
continue
rec = json.loads(path.read_text(encoding="utf-8"))
rec["id"] = rid
text = "\n".join(x for x in (rec.get("release_tab") or "", rec.get("tech_tab") or "") if x)
new = H.kz_title_rows(rec, text, rel_rows)
if new:
stats["kinozal: релизов со студиями из заголовка"] += 1
stats["kinozal: студий из заголовка"] += len(new)
stats["kinozal: из них со строкой «Аудио»"] += sum(bool(x["audio_raw"]) for x in new)
added += new
rows = rows + added
by_rel = defaultdict(list)
for r in rows:
by_rel[(r["source"], r["release_id"])].append(r)
codec: list[dict] = []
for (src, rid), rel_rows in by_rel.items():
if rid in manual:
continue
text = raw_text(raw_kz.parent, src, rid)
if text is None:
continue
new = H.codec_line_rows({"id": rid, "source": src}, text, rel_rows)
if new:
stats[f"{src}: релизов со студиями из кодек-строк"] += 1
stats[f"{src}: студий из кодек-строк"] += len(new)
codec += new
return rows + codec, stats
def raw_text(raw_dir: Path, src: str, rid: str) -> str | None:
"""Текст страницы, как у сборщика: kinozal — «Релиз» + «Техданные», nnm/rutracker — пост."""
num = re.sub(r"^(kz|nnm|rt)-", "", rid)
for name in (f"{num}.json", f"{num}.legacy.json"):
path = raw_dir / src / name
if path.exists():
rec = json.loads(path.read_text(encoding="utf-8"))
if src == "kinozal":
return "\n".join(x for x in (rec.get("release_tab") or "", rec.get("tech_tab") or "") if x)
return rec.get("post") or ""
return None
def main() -> None:
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--dry-run", action="store_true", help="только посчитать, файл не менять")
args = ap.parse_args()
with LABELS.open(encoding="utf-8-sig", newline="") as f:
reader = csv.DictReader(f)
fields = reader.fieldnames
rows = list(reader)
manual = set(MANUAL.read_text(encoding="utf-8").split()) if MANUAL.exists() else set()
before = sum(r["studio"] != NO_STUDIO for r in rows)
rows, stats = relabel(rows, manual=manual)
after = sum(r["studio"] != NO_STUDIO for r in rows)
for k, v in stats.items():
print(f"{k}: {v}")
print(f"строк: {len(rows)}; со студией: {before} -> {after}")
if args.dry_run:
return
backup = LABELS.with_name(f"labels.bak_relabel_{time.strftime('%Y%m%d_%H%M%S')}.csv")
shutil.copy(LABELS, backup)
with LABELS.open("w", encoding="utf-8-sig", newline="") as f:
w = csv.DictWriter(f, fieldnames=fields, extrasaction="ignore")
w.writeheader()
w.writerows(rows)
print(f"записано: {LABELS.relative_to(ROOT)}; копия: {backup.name}")
if __name__ == "__main__":
main()