diff --git a/docs/api-reference.md b/docs/api-reference.md index b4e6d48..7d8b2b1 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -54,7 +54,7 @@ | `series` | object \| null | сезон, выпущенные серии, «какая студия какие серии» (для Sonarr); `null` — сведений нет | | `title` | string \| null | заголовок раздачи, если найден | | `segmenter` | `"model"` \| `"rules"` | чем страница разбита на строки дорожек: обученным сегментатором или запасными правилами | -| `tracker` | string | только для `url`: `rutracker`, `nnmclub` или `kinozal` | +| `tracker` | string | только для `url`: `rutracker`, `nnm` или `kinozal` | `series`: `season` — номер сезона строкой или `null`; `episodes` — выпущенные серии («1-9»); `total` — всего серий; `by_studio` — `{студия: серии}`; `full_release` — студии без указанных серий @@ -86,6 +86,22 @@ ### translation_type: 14 классов +Точные строки (регистр, пробелы и точки после префикса — как есть; сортировка — по алфавиту, так их +возвращает `GET /health` → `classes`): + +```json +["авт. двухголосый", "авт. многоголосый", "авт. одноголосый", "двухголосый", "люб. двухголосый", + "люб. многоголосый", "люб. одноголосый", "многоголосый", "неизвестно", "одноголосый", + "проф. двухголосый", "проф. дубляж", "проф. многоголосый", "проф. одноголосый"] +``` + +Для клиентов, которые сводят класс к своим типам (Dub / MVO / DVO / AVO), надёжно сопоставление по +**подстроке** нижнего регистра: `дубляж` → дубляж; `многоголос` → многоголосый; `двухголос` → +двухголосый; `одноголос` → одноголосый; ровно `неизвестно` → тип не определён (такие дорожки обычно +пропускают). Подстрока `дублирован` в классах **не встречается** (дублированные страницы сводятся к +`проф. дубляж`). Префикс (`проф.`/`люб.`/`авт.`/нет) — отдельный признак. Если набор классов в будущей +модели изменится, ориентируйтесь на `/health` → `classes`, а неизвестное значение не считайте ошибкой. + Класс — составная метка «кто делал × сколько голосов». Других значений сервис не возвращает. | Значение | Кто озвучивал | Вид | @@ -113,6 +129,72 @@ доверять осторожно. Состав и названия классов задаёт обучение; актуальный список для конкретной модели — `GET /health` → `classes`. +## Схема полей и допустимые null + +Дорожка (`tracks[]`), нотация TypeScript. Все ключи **присутствуют всегда** (значение может быть +`null`, но ключ не пропускается), кроме помеченных «только в ответе релиза». + +```ts +type Track = { + segment: string; // никогда null + translation_type: string; // один из 14 классов, никогда 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" | null; // null вместе с lang + role: "перевод" | "оригинал" | "комментарии"; // никогда null + official: boolean; + audio: { codec: string | null; channels: number | null; bitrate_kbps: number | null } | 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; is_18plus: boolean; episodes: string | null }[]; + translation_types: string[]; + is_18plus: boolean; + series: { + season: string | null; // "2" + episodes: string | null; // выпущенные серии, "1-9" + total: number | null; // всего серий + by_studio: Record; // { "LostFilm": "1-4" }, может быть {} + full_release: string[]; // студии без указанных серий + } | null; // null — ни сезона, ни серий в раздаче нет + title: string | null; + segmenter: "model" | "rules"; + tracker?: "rutracker" | "nnm" | "kinozal"; // только для 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 или форму входа без FlareSolverr/`cookie`; ошибка 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 или