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]>
302 lines
30 KiB
Markdown
302 lines
30 KiB
Markdown
# Справочник 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`. Ссылка на `lostfilm.tv` (и поддомены) страницу не загружает: сразу возвращается одна дорожка «проф. многоголосый, LostFilm, RUS» (`tracker: "lostfilm"`, `lang_source: "url"`, `source: "url"`) |
|
||
| `title` | string, необязательно | заголовок раздачи для `html` (во фрагменте вкладки его нет); для `url` берётся со страницы |
|
||
| `cookie` | string, необязательно | Cookie сессии (`uid=...; pass=...`) для `url`. **Для kinozal обычно не нужен**: описание многих раздач открывается и без входа (проверено на `details.php?id=2158906`). Передавайте, только если сервис вернул `400` про форму входа |
|
||
|
||
Коды ошибок: `400` — неверное тело, пустой текст, страница не разобрана или запрещённый хост;
|
||
`401` — нет/неверный токен (если задан `DUB_API_TOKEN`); `502` — не удалось скачать страницу.
|
||
Токен передаётся `Authorization: Bearer <токен>` или `X-API-Key: <токен>`.
|
||
|
||
## Ответ релиза (`html` и `url`)
|
||
|
||
```json
|
||
{
|
||
"tracks": [ /* дорожки, см. ниже */ ],
|
||
"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, "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"
|
||
}
|
||
```
|
||
|
||
| Поле | Тип | Описание |
|
||
|---|---|---|
|
||
| `tracks` | array | дорожки релиза без дублей (дубль — одинаковые тип + студия) |
|
||
| `studios` | array | уникальные `{studio, translation_type, lang, is_18plus, episodes}`; `lang` — язык дорожки студии (как в `tracks[].lang`, может быть `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).
|
||
|
||
## Дорожка (элемент `tracks`)
|
||
|
||
| Поле | Тип | Значения / описание |
|
||
|---|---|---|
|
||
| `segment` | string | текст дорожки, по которому сделано предсказание |
|
||
| `translation_type` | string | **один из 15 классов** — [таблица ниже](#translation_type-15-классов) |
|
||
| `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` | `"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, 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]]`; пусто, если серий нет |
|
||
|
||
Поле `lang` описывает только язык дорожки; `role` и `lang` не зависят от `translation_type`.
|
||
|
||
### translation_type: 15 классов
|
||
|
||
> Текущая модель знает 15 классов: к 14 из списка ниже добавлен `люб. дубляж` — любительский дубляж
|
||
> (см. [dataset.md](dataset.md)); модели, обученные раньше, его не возвращают и отдают 14 классов.
|
||
> Источник истины для конкретной модели — `GET /health` → `classes`; клиенту безопасно сопоставлять по подстроке
|
||
> (`дубляж` → дубляж), как описано ниже: `люб. дубляж` её содержит.
|
||
|
||
Точные строки (регистр, пробелы и точки после префикса — как есть; сортировка — по алфавиту, так их
|
||
возвращает `GET /health` → `classes`):
|
||
|
||
```json
|
||
["авт. двухголосый", "авт. многоголосый", "авт. одноголосый", "двухголосый", "люб. двухголосый",
|
||
"люб. дубляж", "люб. многоголосый", "люб. одноголосый", "многоголосый", "неизвестно", "одноголосый",
|
||
"проф. двухголосый", "проф. дубляж", "проф. многоголосый", "проф. одноголосый"]
|
||
```
|
||
|
||
Для клиентов, которые сводят класс к своим типам (Dub / MVO / DVO / AVO), надёжно сопоставление по
|
||
**подстроке** нижнего регистра: `дубляж` → дубляж; `многоголос` → многоголосый; `двухголос` →
|
||
двухголосый; `одноголос` → одноголосый; ровно `неизвестно` → тип не определён (такие дорожки обычно
|
||
пропускают). Подстрока `дублирован` в классах **не встречается** (дублированные страницы сводятся к
|
||
`проф. дубляж`). Префикс (`проф.`/`люб.`/`авт.`/нет) — отдельный признак. Если набор классов в будущей
|
||
модели изменится, ориентируйтесь на `/health` → `classes`, а неизвестное значение не считайте ошибкой.
|
||
|
||
Класс — составная метка «кто делал × сколько голосов». Других значений сервис не возвращает.
|
||
|
||
| Значение | Кто озвучивал | Вид |
|
||
|---|---|---|
|
||
| `проф. дубляж` | профессиональная студия | дубляж (речь актёров заменяет оригинал; в страницах «Дублированный») |
|
||
| `люб. дубляж` | любительская озвучка | дубляж/псевдодубляж; в текущей модели есть; в моделях, обученных раньше, его нет |
|
||
| `проф. многоголосый` | профессиональная студия | закадровый, три и более голоса |
|
||
| `проф. двухголосый` | профессиональная студия | закадровый, два голоса |
|
||
| `проф. одноголосый` | профессиональная студия | закадровый, один голос |
|
||
| `люб. многоголосый` | любительская озвучка | закадровый, три и более голоса |
|
||
| `люб. двухголосый` | любительская озвучка | закадровый, два голоса |
|
||
| `люб. одноголосый` | любительская озвучка | закадровый, один голос |
|
||
| `авт. многоголосый` | авторская (один автор / «авторский перевод») | закадровый, три и более голоса |
|
||
| `авт. двухголосый` | авторская | закадровый, два голоса |
|
||
| `авт. одноголосый` | авторская | закадровый, один голос |
|
||
| `многоголосый` | в тексте не сказано | закадровый, три и более голоса |
|
||
| `двухголосый` | в тексте не сказано | закадровый, два голоса |
|
||
| `одноголосый` | в тексте не сказано | закадровый, один голос |
|
||
| `неизвестно` | — | тип по тексту определить нельзя |
|
||
|
||
Как читать: префикс `проф.` / `люб.` / `авт.` — категория озвучивающего (на трекерах это слова
|
||
«Профессиональный», «Любительский», «Авторский»); без префикса — раздача не указывает категорию.
|
||
Для фильтров в приложении обычно достаточно сопоставить по префиксу и по хвосту (`…дубляж`,
|
||
`…многоголосый`, `…двухголосый`, `…одноголосый`). Классы с единичными примерами в обучении
|
||
распознаются хуже (см. [dataset.md](dataset.md)); при низком `type_confidence` (< 0.6) значению стоит
|
||
доверять осторожно. Состав и названия классов задаёт обучение; актуальный список для конкретной
|
||
модели — `GET /health` → `classes`.
|
||
|
||
## Схема полей и допустимые null
|
||
|
||
Дорожка (`tracks[]`), нотация TypeScript. Все ключи **присутствуют всегда** (значение может быть
|
||
`null`, но ключ не пропускается), кроме помеченных «только в ответе релиза».
|
||
|
||
```ts
|
||
type Track = {
|
||
segment: string; // никогда null
|
||
translation_type: string; // один из 15 классов, никогда null
|
||
type_confidence: number; // 0..1
|
||
studio_span: string | null; // null — автора в тексте нет
|
||
studio: string | null; // null вместе со studio_span
|
||
studio_confidence: number | null; // null вместе со studio_span
|
||
is_18plus: boolean;
|
||
age_confidence: number; // 0..1
|
||
lang: string | null; // 3-буквенный код или null
|
||
lang_confidence: number | null; // не null только при lang_source == "model"
|
||
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; khz: number | null; atmos: boolean } | null;
|
||
// только в ответе релиза (html / url):
|
||
source: "page" | "title";
|
||
episodes: string | null; // "1-4", "1,3-5"; null — серии не указаны
|
||
episode_ranges: [number, number][]; // [[1,4]]; пустой массив, если серий нет
|
||
};
|
||
|
||
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;
|
||
khz: number | null; atmos: boolean; raw: string } | null;
|
||
translation_types: string[];
|
||
is_18plus: boolean;
|
||
series: {
|
||
season: string | null; // "2"
|
||
episodes: string | null; // выпущенные серии, "1-9"
|
||
total: number | null; // всего серий
|
||
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
|
||
};
|
||
```
|
||
|
||
Ответ на `text` — `{"tracks": [Track]}` (без полей релиза и без `source`/`episodes`/`episode_ranges`).
|
||
Клиентам стоит читать ответ толерантно: неизвестные поля игнорировать, отсутствующие считать `null`.
|
||
|
||
## Ошибки
|
||
|
||
Тело любой ошибки — JSON `{"detail": "<сообщение>"}`; у `401` добавлен заголовок
|
||
`WWW-Authenticate: Bearer`.
|
||
|
||
| Код | Когда | Пример `detail` |
|
||
|---|---|---|
|
||
| 400 | передано не ровно одно из `text` / `html` / `url` | «нужно ровно одно поле: text (дорожка), html (страница) или url (ссылка на раздачу)» |
|
||
| 400 | пустой `text`; страница не разобрана; хост ссылки не из разрешённых (rutracker / nnmclub / kinozal); страница недоступна или слишком большая; трекер отдал заглушку Cloudflare или форму входа (для kinozal — раздача, которая без входа не открывается, тогда нужен `cookie`) или Cloudflare без FlareSolverr; ошибка FlareSolverr | текст причины на русском («неподдерживаемый хост: …», «страница недоступна: …», «FlareSolverr: …») |
|
||
| 401 | задан `DUB_API_TOKEN`, а токена нет или он неверный | «нужен токен: Authorization: Bearer <токен> или X-API-Key» |
|
||
| 422 | тело запроса не JSON-объект или поля не тех типов (валидация FastAPI; `detail` — массив) | `[{"loc": [...], "msg": "...", "type": "..."}]` |
|
||
| 502 | сетевая ошибка при скачивании страницы по `url` (соединение, DNS, таймаут) | «не удалось загрузить страницу: …» |
|
||
|
||
`GET /health` не требует токена и возвращает: `status`, `model`, `device`, `classes`, `segmenter`
|
||
(`"model"`/`"rules"`), `flaresolverr` (bool), `auth` (bool). Страница, на которой не найдено дорожек,
|
||
— не ошибка: ответ `200` с `"tracks": []`. Повторяйте запрос при `502` и при ответах `400` про
|
||
Cloudflare: FlareSolverr нестабилен.
|
||
|
||
## Принцип работы
|
||
|
||
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` (15 классов), спан автора (разметка BIO по токенам),
|
||
флаг 18+ и язык (`ENG`/`RUS`/`UKR`).
|
||
6. **Постобработка.** Автор канонизируется словарём алиасов; 18+ ставится также по строке «Аудио»;
|
||
язык: строка «Аудио» → нейросеть → слова в тексте; строка «Аудио» подбирается к дорожке по имени
|
||
студии из `|Дубляж, Le Doyen|` (слова-типы `Дубляж`/`MVO`/… отбрасываются); защита от имён вроде
|
||
«Дубляжная»: если дубляж назван только в имени студии, а в тексте указано число голосов, класс берётся
|
||
по тексту; отдельно берётся `original`; определяются `role`, `official`, `audio`.
|
||
7. **Агрегат релиза.** Дубли (тип + студия) склеиваются; студии из заголовка раздачи, которых нет
|
||
в описании, добавляются с `source: "title"`; серии и сезон разбираются правилами (`src/episodes.py`).
|
||
8. **Техданные** (`video`, `release`, `subtitles`) — правилами по строкам «Видео:», «Тип релиза:»,
|
||
«Контейнер:», «Продолжительность:», «Субтитры:» и заголовку (`src/techinfo.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*` в примере иллюстративны: они зависят от модели и текста.)
|