Files
grabber-ai/docs/api-reference.md
T

15 KiB

Справочник API: значения ответа и принцип работы

Этот документ — единственное место, где перечислены все возможные значения полей ответа. Заглядывать в data/labels.csv для этого не нужно: файл большой (десятки МБ), нужен только при обучении и в работе сервиса не участвует. Список классов модели хранится в models/best/label_maps.json (в образе — /app/models/best/label_maps.json) и отдаётся сервисом: GET /health → classes.

Запросы, переменные окружения и запуск — в docker.md; формат вызова из кода — в integration.md; CLI и Python API — в inference.md.

Эндпоинты

Метод Путь Назначение
GET /health статус, модель, список классов classes, режим сегментатора, flaresolverr, auth. Токен не нужен
POST /predict предсказание. Тело — JSON с ровно одним из полей text, html, url
GET /docs интерактивная Swagger-документация FastAPI

Тело /predict:

Поле Тип Описание
text string одна дорожка (строка вида «Профессиональный (многоголосый закадровый) | LostFilm») → ответ {"tracks": [одна дорожка]}
html string HTML (или текст) страницы раздачи → ответ-агрегат релиза
url string ссылка на раздачу rutracker / nnmclub / kinozal; страницу сервис скачивает сам (при 403/503 — через FlareSolverr) → ответ-агрегат + tracker
title string, необязательно заголовок раздачи для html (во фрагменте вкладки его нет); для url берётся со страницы
cookie string, необязательно Cookie для url (kinozal требует авторизацию)

Коды ошибок: 400 — неверное тело, пустой текст, страница не разобрана или запрещённый хост; 401 — нет/неверный токен (если задан DUB_API_TOKEN); 502 — не удалось скачать страницу. Токен передаётся Authorization: Bearer <токен> или X-API-Key: <токен>.

Ответ релиза (html и url)

{
  "tracks": [ /* дорожки, см. ниже */ ],
  "studios": [{"studio": "LostFilm", "translation_type": "проф. многоголосый", "is_18plus": false, "episodes": "1-4"}],
  "translation_types": ["проф. многоголосый"],
  "is_18plus": false,
  "series": {"season": "2", "episodes": "1-9", "total": 10, "by_studio": {"LostFilm": "1-4"}, "full_release": []},
  "title": "Название раздачи",
  "segmenter": "model",
  "tracker": "rutracker"
}
Поле Тип Описание
tracks array дорожки релиза без дублей (дубль — одинаковые тип + студия)
studios array уникальные {studio, translation_type, is_18plus, episodes}
translation_types array of string отсортированный набор типов (значения — из таблицы классов ниже)
is_18plus bool true, если 18+ хотя бы у одной дорожки
series object | null сезон, выпущенные серии, «какая студия какие серии» (для Sonarr); null — сведений нет
title string | null заголовок раздачи, если найден
segmenter "model" | "rules" чем страница разбита на строки дорожек: обученным сегментатором или запасными правилами
tracker string только для url: rutracker, nnmclub или kinozal

series: season — номер сезона строкой или null; episodes — выпущенные серии («1-9»); total — всего серий; by_studio — {студия: серии}; full_release — студии без указанных серий (для сериала это значит «озвучены все вышедшие серии»). Подробности — в integration.md.

Дорожка (элемент tracks)

Поле Тип Значения / описание
segment string текст дорожки, по которому сделано предсказание
translation_type string один из 14 классов — таблица ниже
type_confidence float 0..1 вероятность выбранного класса
studio_span string | null фрагмент текста, распознанный как автор/студия
studio string | null каноническое имя автора из словаря алиасов; неизвестное имя возвращается как в тексте; null — автора нет
studio_confidence float | null средняя вероятность токенов спана
is_18plus bool дорожка помечена 18+ (по модели или по строке «Аудио … 18+» страницы)
age_confidence float 0..1 вероятность предсказанного класса 18+
lang string | null язык дорожки, ISO-подобный 3-буквенный код: RUS, UKR, ENG, также BEL, KAZ, POL, FRA, DEU, ITA, SPA, JPN, KOR, CHI, CZE, LAV, LIT, EST, GEO, ARM, AZE, UZB, TUR, POR, HIN; null — не определён
lang_confidence float | null уверенность модели; заполнено только при lang_source = "model"
lang_source "model" | "audio" | "text" | null откуда язык: нейросеть (классы ENG/RUS/UKR, порог 0.5), строка «Аудио» страницы или слова в тексте («Украинский дубляж»)
role string перевод, оригинал (оригинальная дорожка без озвучки) или комментарии (комментарии режиссёра и т. п.)
official bool в строке «Аудио» есть слова «официальный» / «лицензионный» / «licensed»
audio object | null {codec, channels, bitrate_kbps} из строки «Аудио» страницы (ac3, 6, 448); каждое поле может быть null; весь объект null, если строка не найдена
source "page" | "title" только в ответе релиза: дорожка найдена в описании или студия взята из заголовка раздачи
episodes string | null серии, которые озвучивает дорожка («1-4», «1,3-5»); null — серии не указаны (покрывает весь релиз)
episode_ranges array то же в виде [[1, 4]]; пусто, если серий нет

Поле lang описывает только язык дорожки; role и lang не зависят от translation_type.

translation_type: 14 классов

Класс — составная метка «кто делал × сколько голосов». Других значений сервис не возвращает.

Значение Кто озвучивал Вид
проф. дубляж профессиональная студия дубляж (речь актёров заменяет оригинал; в страницах «Дублированный»)
проф. многоголосый профессиональная студия закадровый, три и более голоса
проф. двухголосый профессиональная студия закадровый, два голоса
проф. одноголосый профессиональная студия закадровый, один голос
люб. многоголосый любительская озвучка закадровый, три и более голоса
люб. двухголосый любительская озвучка закадровый, два голоса
люб. одноголосый любительская озвучка закадровый, один голос
авт. многоголосый авторская (один автор / «авторский перевод») закадровый, три и более голоса
авт. двухголосый авторская закадровый, два голоса
авт. одноголосый авторская закадровый, один голос
многоголосый в тексте не сказано закадровый, три и более голоса
двухголосый в тексте не сказано закадровый, два голоса
одноголосый в тексте не сказано закадровый, один голос
неизвестно — тип по тексту определить нельзя

Как читать: префикс проф. / люб. / авт. — категория озвучивающего (на трекерах это слова «Профессиональный», «Любительский», «Авторский»); без префикса — раздача не указывает категорию. Для фильтров в приложении обычно достаточно сопоставить по префиксу и по хвосту (…дубляж, …многоголосый, …двухголосый, …одноголосый). Классы с единичными примерами в обучении распознаются хуже (см. dataset.md); при низком type_confidence (< 0.6) значению стоит доверять осторожно. Состав и названия классов задаёт обучение; актуальный список для конкретной модели — GET /health → classes.

Принцип работы

  1. Получение страницы. url скачивается самим сервисом: сначала напрямую, при 403/503 или Cloudflare-заглушке — через FlareSolverr (DUB_FLARESOLVERR_URL). Разрешены только rutracker, nnmclub и kinozal.
  2. HTML → текст (BeautifulSoup: пост rutracker, вкладка описания kinozal, иначе весь документ).
  3. Сегментация. Обученная модель-сегментатор (models/segmenter, rubert-tiny2) помечает строки страницы, в которых описана дорожка («Перевод: …», «Аудио 2: …», перечисления студий). Если сегментатора нет или он ничего не нашёл — запасные правила ("segmenter": "rules"). Многодорожечные строки («1. … 2. …», «A + B») разбиваются на отдельные дорожки.
  4. Контекст аудио. К тексту дорожки подмешивается подходящая строка «Аудио» страницы (кодек, каналы, битрейт, язык, 18+) в виде хвоста [RUS AC3 6ch 448kbps 18+].
  5. Модель-классификатор (DubTagger на основе ruBert-base, models/best) делает за один проход несколько предсказаний: translation_type (14 классов), спан автора (разметка BIO по токенам), флаг 18+ и язык (ENG/RUS/UKR).
  6. Постобработка. Автор канонизируется словарём алиасов; 18+ ставится также по строке «Аудио»; язык: нейросеть → строка «Аудио» → слова в тексте; определяются role, official, audio.
  7. Агрегат релиза. Дубли (тип + студия) склеиваются; студии из заголовка раздачи, которых нет в описании, добавляются с source: "title"; серии и сезон разбираются правилами (src/episodes.py).

Размеченные данные (data/labels.csv) используются только при обучении: на них учатся метки translation_type, спан студии, 18+ и язык. Схема файла и происхождение меток — в dataset.md. Источник правды для набора классов во время работы — label_maps.json рядом с весами модели.

Пример

curl -s http://localhost:7654/predict -H "Authorization: Bearer <ТОКЕН>" \
     -H "Content-Type: application/json" \
     -d '{"text": "Профессиональный (многоголосый закадровый) | HDRezka Studio 18+"}'
{"tracks": [{"segment": "Профессиональный (многоголосый закадровый) | HDRezka Studio 18+",
  "translation_type": "проф. многоголосый", "type_confidence": 0.99,
  "studio_span": "HDRezka Studio", "studio": "HDrezka", "studio_confidence": 0.97,
  "is_18plus": true, "age_confidence": 0.99,
  "lang": "RUS", "lang_confidence": null, "lang_source": "text",
  "role": "перевод", "official": false, "audio": null}]}

(значения lang* в примере иллюстративны: они зависят от модели и текста.)