# Распознавание контента ## Задача По доступным сигналам определить: фильм или сериал; каноническое название и год; для сериала — сезон(ы) и соответствие файлов сериям; при включённых базах — провайдер и его id. На выходе — план раскладки, оценка уверенности и решение «авто или review». ## Сигналы - Имя торрента и структура каталогов. - Список файлов с размерами и расширениями. Абсолютный путь источника восстанавливаем как `save_path`/`content_path` из qBit (после трансляции `path_map`) + относительное имя файла; учитываем одно- и многофайловые торренты. - Текстовый контекст человека (+ накопленные подсказки из review). - Распарсенное сообщение торрент-бота (если через Telegram): название с годом, качество, переводы — см. пример в [BRIEF.md](../../BRIEF.md). **Все сигналы недоверенные** — имя торрента, сообщение бота и контекст управляются извне и могут содержать инъекции. Выход LLM не отвечает за безопасность: целевой путь всё равно санитизируется и проверяется на выход за пределы библиотеки (см. architecture.md → «Раскладка файлов»). ## Конвейер 1. **Пред-парс** имени релиза (`go-ptn`): черновые название/год/сезон/ серия и качество. Грубо, но бесплатно. 2. **LLM** (через провайдер-абстракцию, см. ниже): получает сигналы и пред-парс, возвращает структурированный план в нашей схеме. Хорошо берёт русские релиз-имена. Длинный список файлов усекаем/семплируем под контекст модели. 3. **Сверка с базой** (если включена TMDB/TVDB): ищем по названию+году, берём официальный id и каноническое имя, собираем кандидатов. 4. **Оценка уверенности** и решение: авто или review. ## Структура ответа LLM (предварительная) ``` type movie | series title каноническое название original_title оригинальное название (если есть) year год provider_hint строка для поиска в базе (НЕ итоговый id) files[] { src, role: main|episode|subtitle|extra|sample|ignore, season?, episode? } # season/episode — на файл confidence 0..1 — самооценка модели (вспомогательный сигнал) notes пояснения, неоднозначности ``` Сезон/серия — **на файле**: так выражаются мультисезонные паки, спецвыпуски и смешанные раскладки; отдельного скалярного `season` нет. `provider_hint` — только подсказка для поиска; итоговые `provider` (`tmdb|tvdb|none`) и `provider_id` появляются после сверки с базой и хранятся отдельно. ## Провайдер LLM Доступ к LLM — за интерфейсом; реализация выбирается полем `[llm].type` (дискриминатор). Это позволяет подключать локальные модели и сторонние (в т.ч. китайские) эндпоинты — ради экономии и независимости от вендора. - Первый и пока единственный тип — **`openai-compat`**: OpenAI-совместимый Chat Completions API (`base_url` + `api_key` + `model`). Подходят локальные серверы (LM Studio, llama.cpp, Ollama) и облачные совместимые провайдеры (DeepSeek, Qwen и др.). - **Структурированный вывод надёжно:** просим JSON по схеме (`response_format` со схемой где поддерживается; иначе json-режим или tool-call); на приёме срезаем ```-ограждения и извлекаем JSON, **валидируем в Go**, ретраим со схемой-в-промпте до `llm.max_retries`; если так и не распарсилось — уходим в **review** (не в `failed`) с причиной «ответ LLM не разобран». Серверы заметно различаются по поддержке строгих схем, особенно мелкие локальные модели. - Новые типы (напр. нативный `anthropic`) добавляются, не трогая `recognize`. ## Модель уверенности Авто-раскладка — только если выполнено **всё**: 1. **Подтверждённый матч в базе** — единственный сильный результат TMDB/TVDB по названию+году, давший `provider_id`. **Нет матча (или база выключена) → всегда review.** Это и закрывает основной кейс (рус/аниме часто отсутствуют в базах), и снимает риск «LLM придумал». 2. **Структурная валидация** без предупреждений: - фильм: ровно один основной видеофайл (семплы/экстра/ignore отброшены); - сериал: число серий бьётся с базой, нумерация S·E консистентна, без пропусков, дублей и неоднозначных спецвыпусков. 3. **Согласованность сигналов** — пред-парс (`go-ptn`) и LLM не противоречат по типу/названию/году. Самооценку LLM (`confidence`) учитываем как вспомогательный сигнал, но **не как единственный гейт**: она плохо откалибрована и поддаётся инъекции. Решают матч в базе и валидация. Иначе — **review** ([review-ux.md](review-ux.md)) с явной причиной. ## Что делаем с краёв - Семплы/«экстра»/мусор → роль `ignore` (эвристики размер/имя + LLM). - Внешние субтитры (`.srt`, `.ass`, пары VobSub `.idx`+`.sub`) привязываем к видео и именуем по Jellyfin (`*.ru.srt`). - Сезон-паки разбираем по сериям; смешанные паки, спецвыпуски (`Season 00`), двойные серии (`SxxEyy-Eyy`) — через per-file season/episode; любая неоднозначность → review. - Аниме с абсолютной нумерацией — отдельный крайний случай, см. [drafts/ideas.md](../drafts/ideas.md). ## На будущее `go-ptn` слабее питоновского `guessit`. Если точности пред-парса не хватит — завернуть `guessit` лёгким сервисом-спутником (один файл рядом с бинарём). См. [drafts/ideas.md](../drafts/ideas.md).