diff --git a/README.md b/README.md index 3008332..1ea8de8 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ docs/ документация * [docs/architecture.md](docs/architecture.md) — устройство модели и пайплайна * [docs/dataset.md](docs/dataset.md) — схема датасета, семантика классов, аудит * [docs/training-windows.md](docs/training-windows.md) — обучение на Windows+GPU, troubleshooting +* [docs/api-reference.md](docs/api-reference.md) — **все значения ответа сервиса (14 классов `translation_type` и др.) и принцип работы; `labels.csv` для этого открывать не нужно** * [docs/inference.md](docs/inference.md) — CLI/API, JSON-контракт * [docs/integration.md](docs/integration.md) — Rust/TypeScript: HTTP-сервис и ONNX diff --git a/docs/README.md b/docs/README.md index 0ee8f1e..e733849 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,6 +6,7 @@ | [dataset.md](dataset.md) | Схема labels.csv, семантика 14 классов, словарь алиасов, находки аудита и как они обработаны | | [training-windows.md](training-windows.md) | Пошаговое обучение на Windows с GPU (RTX 5070 Ti / Blackwell), гиперпараметры, troubleshooting, переобучение на свежем датасете | | [inference.md](inference.md) | CLI, Python API, JSON-контракт (таблица полей), обработка HTML, ограничения | +| [api-reference.md](api-reference.md) | **Справочник ответа сервиса**: все 14 значений `translation_type`, все поля дорожки и релиза, принцип работы. Читать вместо `data/labels.csv` | | [integration.md](integration.md) | Интеграция из Rust и TypeScript: HTTP-сервис (путь А) и ONNX без Python (путь Б), трейдоффы | | [docker.md](docker.md) | Docker-образ сервиса, переменные окружения (в т.ч. FlareSolverr), API, модели в контейнере, CI/CD в Gitea, развёртывание на VPS | diff --git a/docs/api-reference.md b/docs/api-reference.md new file mode 100644 index 0000000..b4e6d48 --- /dev/null +++ b/docs/api-reference.md @@ -0,0 +1,158 @@ +# Справочник API: значения ответа и принцип работы + +Этот документ — единственное место, где перечислены все возможные значения полей ответа. **Заглядывать +в `data/labels.csv` для этого не нужно**: файл большой (десятки МБ), нужен только при обучении и в +работе сервиса не участвует. Список классов модели хранится в `models/best/label_maps.json` +(в образе — `/app/models/best/label_maps.json`) и отдаётся сервисом: `GET /health` → `classes`. + +Запросы, переменные окружения и запуск — в [docker.md](docker.md); формат вызова из кода — +в [integration.md](integration.md); CLI и Python API — в [inference.md](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`) + +```json +{ + "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](integration.md). + +## Дорожка (элемент `tracks`) + +| Поле | Тип | Значения / описание | +|---|---|---| +| `segment` | string | текст дорожки, по которому сделано предсказание | +| `translation_type` | string | **один из 14 классов** — [таблица ниже](#translation_type-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](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](dataset.md). Источник правды для набора классов во время работы — `label_maps.json` +рядом с весами модели. + +## Пример + +```bash +curl -s http://localhost:7654/predict -H "Authorization: Bearer <ТОКЕН>" \ + -H "Content-Type: application/json" \ + -d '{"text": "Профессиональный (многоголосый закадровый) | HDRezka Studio 18+"}' +``` + +```json +{"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*` в примере иллюстративны: они зависят от модели и текста.) diff --git a/docs/inference.md b/docs/inference.md index 0d56560..19eab04 100644 --- a/docs/inference.md +++ b/docs/inference.md @@ -61,6 +61,8 @@ model.predict_file("page.txt") ## JSON-контракт +Полный справочник значений (14 классов `translation_type`, `lang`, `role`, `audio`, агрегат релиза, принцип работы) — в [api-reference.md](api-reference.md); ниже краткий пример. + ```json { "tracks": [