Files
grabber-ai/docs/integration.md
T
DenozordecandClaude Sonnet 5.5 8cbd800ec7 Релиз целиком: вход по ссылке, агрегат студий/типов/18+, малая int8-модель
- src/fetch.py: загрузка по URL (allowlist трекеров, защита от SSRF, cp1251)
- src/release.py: агрегат релиза, студии из заголовка раздачи (ПМ (LostFilm))
- predict --url, POST /predict {"url"}; ответ расширен, tracks сохранён
- export_onnx --quantize: tiny2 int8 29 МБ, parity 99.2-100%
- тесты (52 ok), docs/plan.md, docs; target/ убран из git

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-27 17:02:04 +07:00

8.7 KiB
Raw Blame History

Интеграция из Rust / TypeScript

Два пути. А (HTTP) — самый простой: Python-процесс один раз загружает модель, любой язык ходит по REST. Б (ONNX) — без Python в рантайме: модель запускается локально в вашем процессе.

Путь А: HTTP-сервис Путь Б: ONNX
Зависимости рантайма запущенный Python-процесс onnxruntime (+ токенизатор) в вашем языке
Сегментация HTML да, на стороне сервиса нет — в примерах обрабатывается одна дорожка; сегментацию нужно вызвать отдельно или портировать (html_extract.py ~60 строк)
Канонизация имён да, на стороне сервиса по aliases.json (простой словарь)
Скорость сеть + инференс (~мс) локальный инференс (~мс)
Кому подходит сервис/бэкенд CLI/десктоп/edge, где Python нельзя

Путь А: HTTP-сервис

Запуск (из корня проекта):

python -m src.service --model models/best --port 8000
# или открыто наружу: python -m src.service --host 0.0.0.0 --port 8000

API:

  • GET /health → {"status": "ok", "model": "...", "device": "...", "classes": [...]}
  • POST /predict — ровно одно из полей (url принимает опциональный cookie; ответ для html/url — агрегат релиза, см. docs/inference.md):
    • {"url": "https://rutracker.org/forum/viewtopic.php?t=…"} → страница загружается сервисом;
    • {"text": "Перевод: Дублированный | MovieDalen"} → одна дорожка;
    • {"html": "<html>…вся страница…"} → все дорожки релиза.

Ответ: {"tracks": [ …контракт из docs/inference.md… ]}.

curl:

curl -X POST http://127.0.0.1:8000/predict -H "Content-Type: application/json" \
     -d '{"text": "Перевод: Дублированный | MovieDalen"}'

Windows/Git Bash: кириллица в аргументе -d может искажаться кодировкой консоли — используйте --data-binary @req.json с файлом в UTF-8.

Rust (examples/rust/http_client)

cd examples/rust/http_client
cargo run -- --text "Перевод: Дублированный | MovieDalen"
cargo run -- --html page.html
DUB_URL=http://host:8000 cargo run -- --text "..."

Ядро клиента:

let resp: PredictResponse = client
    .post("http://127.0.0.1:8000/predict")
    .json(&serde_json::json!({ "html": page_html }))
    .send().await?
    .json().await?;
for t in &resp.tracks {
    println!("{} — {}{}", t.translation_type,
             t.studio.clone().unwrap_or_else(|| "—".into()),
             if t.is_18plus { " 18+" } else { "" });
}

TypeScript / Node.js (examples/typescript)

Без зависимостей (Node >= 18, глобальный fetch):

node examples/typescript/predict.mjs --text "Одноголосый закадровый — Дмитрий Есарев"
node examples/typescript/predict.mjs --html page.html
const res = await fetch("http://127.0.0.1:8000/predict", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ text: "Одноголосый закадровый — Дмитрий Есарев" }),
});
const { tracks } = await res.json();

Типизированная версия — examples/typescript/predict.ts (запуск: npx tsx predict.ts --text "...").

PowerShell (examples/powershell/predict.ps1)

Скрипт (работает и в Windows PowerShell 5.1, и в PowerShell 7):

powershell -NoProfile -ExecutionPolicy Bypass -File examples\powershell\predict.ps1 -Text "Перевод: Дублированный | MovieDalen"
pwsh -File examples\powershell\predict.ps1 -HtmlFile page.html -Url http://host:8000

Однострочник (POST с кириллицей; UTF-8 байтами — важно для PS 5.1, где строковый -Body уходит в ISO-8859-1):

$b = [Text.Encoding]::UTF8.GetBytes((@{ text = "Перевод: Дублированный | MovieDalen" } | ConvertTo-Json))
(Invoke-RestMethod -Uri http://127.0.0.1:8000/predict -Method Post -ContentType "application/json" -Body $b).tracks

Проверка живости: Invoke-RestMethod http://127.0.0.1:8000/health. Ответ сервис отдаёт с charset=utf-8, поэтому кириллица в ответе корректно декодируется и в PS 5.1.

grabber (PHP)

kinozal требует авторизации, поэтому grabber скачивает страницу сам (KinozalClient) и отправляет сервису готовый HTML:

$ch = curl_init('http://dub-ai:8000/predict');
curl_setopt_array($ch, [
    CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode(['html' => $html], JSON_UNESCAPED_UNICODE),
]);
$release = json_decode(curl_exec($ch), true);
// $release['studios'], $release['translation_types'], $release['is_18plus']

Путь Б: ONNX без Python

Экспорт (из корня проекта, после обучения):

python -m src.export_onnx            # -> models/best-onnx/ + reports/onnx_parity.md

Для встраивания — малая модель: python -m src.export_onnx --model models/tiny2 --quantize кладёт в models/tiny2-onnx/ ещё и model.int8.onnx (29 МБ вместо ~440 МБ у base; parity с PyTorch: тип 99.8%, спан 99.2%, 18+ 100% — reports/onnx_parity_tiny2_int8.md).

Состав models/best-onnx/: model.onnx (входы input_ids, attention_mask, token_type_ids int64 [B, seq]; выходы type_logits [B, 14], bio_logits [B, seq, 3], age_logits [B, 2]), tokenizer.json (HF fast-токенизатор), label_maps.json (классы), aliases.json (нормализация имён).

Эквивалентность PyTorch проверяется на всём held-out test (совпадение типа/спана/18+ ≥ 99%) — отчёт в reports/onnx_parity.md.

Клиентская постобработка (одинакова во всех языках):

  1. токенизировать дорожку (max_len 128, truncation);
  2. прогнать ONNX, взять argmax;
  3. спан: первый токен с меткой B, затем цепочка I; текст спана — по offsets токенизатора (или decode токенов спана);
  4. is_18plus = argmax(age_logits) == 1;
  5. канонизация автора по aliases.json.

Rust (examples/rust/onnx_local)

cd examples/rust/onnx_local && cargo build
ORT_DYLIB_PATH="$(find ../../.venv -name onnxruntime.dll | head -1)" \
  ./target/debug/dub-ai-onnx-local.exe ../../models/best-onnx \
  "Перевод: Дублированный | MovieDalen"

Крейты: ort 2.0.0-rc.15 + tokenizers (читает tokenizer.json как есть). Библиотека onnxruntime грузится динамически — DLL можно взять из pip-пакета onnxruntime (уже есть в .venv проекта) или из официального релиза; проверено: тип, спан автора и 18+ совпадают с Python-инференсом.

TypeScript (examples/typescript/onnx_local.mjs)

cd examples/typescript
npm init -y && npm i onnxruntime-node @huggingface/transformers
node onnx_local.mjs "../../models/best-onnx" "Перевод: Дублированный | MovieDalen"

@huggingface/transformers используется только как токенизатор; инференс — onnxruntime-node.

Замечание про сегментацию страниц в пути Б

Сервис (путь А) сам находит все дорожки в HTML. В пути Б примеры принимают одну дорожку; для полной обработки страницы нужно сначала получить сегменты (например, один вызов лёгкого HTTP-эндпоинта или порт html_extract.py — это регулярки без зависимостей, кроме BeautifulSoup для HTML).