Files
grabber-ai/docs/plan.md
T
DenozordecandClaude Opus 5.5 dc19fe6255
docker / build (push) Successful in 1m18s
API: видео, тип релиза и субтитры в ответе релиза; Atmos и kHz у дорожек
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 <noreply@anthropic.com>
2026-11-02 22:26:57 +07:00

11 KiB
Raw Blame History

План доработки: модель озвучки для grabber

Цель

По ссылке на раздачу (rutracker / kinozal / nnm-club) или её HTML определить на уровне релиза: какие дорожки озвучки есть, кто их сделал (студия / переводчик), какого они типа (дубляж, многоголосый, …) и есть ли 18+. Модель должна быть маленькой и встраиваемой в grabber.

Что уже есть (анализ)

  • Multi-task RuBERT (тип 14 классов + BIO-спан автора + 18+), 3742 строки, joint exact 0.946 (base) / 0.915 (tiny2); ONNX-экспорт с parity 100%; FastAPI-сервис; клиенты Rust/TS/PowerShell; тесты и документация.
  • grabber (C:\Users\shats\Dev\grabber) — PHP 8.1 + Docker, интеграции Radarr/Sonarr/qBittorrent/Prowlarr; студии с kinozal разбирает регулярками (KinozalReleaseParser.php, ProwlarrReleaseEnricher.php), идёт Rust-порт. Естественный путь интеграции — HTTP-сервис (PHP ходит curl-ом), ONNX нужен для Rust-порта.

Пробелы относительно задачи

# Пробел Решение
1 Нет входа «по ссылке» (только text/html) url в CLI, сервисе, API: fetch (таймаут, UA, cp1251), определение трекера по домену
2 Ответ — плоский список дорожек Агрегат релиза: дедуп дорожек, уникальные студии с типами, is_18plus релиза; поле tracks не ломаем
3 Заголовок раздачи (/ ПМ (LostFilm) /) не используется fallback-парсинг title, если дорожек 0 или студии нет в дорожках
4 kinozal: авторы грузятся JS при работе по URL запросить вкладку озвучки напрямую (эндпоинт — из data/samples/kinozal*.html, не угадывать)
5 «Маленькая» модель: best = base (~440 МБ) tiny2 (30M) + ONNX + int8-квантизация; решение по цифрам parity/размера
6 Precision 0.83 на rutracker разобрать ложные срабатывания e2e — скорее всего регулярки сегментации
7 В git закоммичен examples/rust/*/target/ .gitignore + удалить из индекса

Порядок работ

  1. Baseline: tests + e2e на текущих моделях (цифры в конец документа).
  2. URL + агрегат релиза (src/fetch.py, src/release.py, эндпоинт POST /predict с url, CLI --url).
  3. Title-fallback в html_extract.py.
  4. kinozal-вкладка (если эндпоинт найден в сэмплах).
  5. Разбор false-positives сегментации.
  6. tiny2 + int8 ONNX, замер размера/parity.
  7. Тесты на каждый пункт, обновление docs/README.
  8. .gitignore + чистка target/.

Критерии готовности

  • python -m src.predict --url <ссылка> печатает агрегат релиза;
  • e2e precision/recall не хуже baseline;
  • малая модель < ~40 МБ (int8), parity ≥ 99%;
  • тесты зелёные, docs обновлены.

Baseline

Модель models/best (ruBert-base), tests/e2e_check.py:

  • rutracker: precision 0.826 / recall 0.984 (n=290);
  • kinozal: 0.107 / 0.107 (n=28) — в текстовых дампах нет авторов (JS), потолок данных;
  • smoke вне датасета: тип 9/10, автор 5/10, 18+ 10/10;
  • tests/run_tests.py: 34 ok.

Статус

Шаг Состояние
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-вкладка сделано: 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-клиентах этих полей нет

Открытые вопросы

  • Подтвердить, что grabber (PHP) интегрируется по HTTP (рекомендуется), а Rust-порт — через ONNX int8 (models/tiny2-onnx/model.int8.onnx).
  • tiny2 vs base: held-out joint exact 0.915 против 0.946 (reports/summary.md); int8 сверху теряет до ~0.8% по спану (parity). e2e на страницах для tiny2 не гонялся.
  • --url живьём не проверен: rutracker отдал 403 (бот-защита/гео), nnm — 404 на тестовый id. Проверено только на allowlist, декодировании и заглушках.
  • Шаг 5 (false positives сегментации) и kinozal без авторизации остаются открытыми.
  • Веса (models/, tiny2-onnx) в gitignore и лежат только в этом worktree/основном репозитории.

Итоги после переобучения (датасет 6864 строк, 7780 после подготовки)

было стало
held-out joint exact, base 0.946 0.950 (тест теперь 516 строк — сравнение условное)
held-out joint exact, tiny2 0.915 0.924
e2e rutracker precision / recall 0.826 / 0.984 0.841 / 0.973
smoke: автор вне датасета 5/10 7/10
tiny2 int8: размер, parity тип / спан / 18+ 29.3 МБ, 99.8 / 99.2 / 100 29.3 МБ, 100 / 98.8 / 100 (ниже порога 99%)

Для e2e есть страницы только старых релизов (data/pages), для новых строк их нет. Если порог 99% по спану критичен — для встраивания брать fp32 tiny2 (116 МБ).

Сегментация страницы: правила, а не нейросеть

Нейромодель получает уже вырезанный текст одной дорожки. Вырезает его src/html_extract.py — регулярками («Перевод N:», разделители, запятые). Поэтому ошибки вида «Дубляж, Продубляж» режется на две дорожки — это ошибки правил. Следующий шаг (не сделан): токен-классификатор по строкам страницы, который сам находит границы дорожек.