- таблица jobs + очередь с воркером: POST /jobs сразу отвечает, элементы
плейлиста обрабатываются последовательно, есть отмена и живой прогресс
(скачивание %, минуты распознанного Vosk-аудио)
- expand_url: плейлист раскрывается в список видео
- keep_video=false теперь качает только аудио (для 4-5ч лекций)
- настройки (LLM-ключ, модель, длина выжимки, хранение видео, путь Vosk)
хранятся в Postgres: переживают перезапуск и перезагрузку страницы
- длина выжимки прокидывается в LLM-промпт ({n} предложений)
- UI: вкладки «Главная»/«Настройки», карточка задач с автообновлением
каждые 3 сек, прогресс-бар, отмена, библиотека обновляется по мере
готовности элементов
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
81 lines
2.5 KiB
Python
81 lines
2.5 KiB
Python
"""Синхронный запуск пайплайна для одного видео (для длинных видео и
|
|
плейлистов используйте /jobs — этот эндпоинт держит HTTP-соединение открытым).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
from pydantic import BaseModel
|
|
|
|
from api.lib.llm import LLMConfig
|
|
from api.lib.processing import PipelineError, process_single_video
|
|
|
|
|
|
router = APIRouter(prefix="/pipeline", tags=["pipeline"])
|
|
|
|
|
|
class LLMOverride(BaseModel):
|
|
base_url: str
|
|
api_key: str = ""
|
|
model: str
|
|
timeout: int = 120
|
|
max_tokens: int = 1000
|
|
temperature: float = 0.3
|
|
extra_headers: dict[str, str] = {}
|
|
system_prompt: str | None = None
|
|
user_template: str | None = None
|
|
|
|
|
|
class ProcessRequest(BaseModel):
|
|
url: str
|
|
model_path: str | None = None # None → из настроек
|
|
summary_max_sentences: int | None = None
|
|
keep_video: bool | None = None
|
|
section_id: int | None = None
|
|
# Если задано — используется этот LLM; иначе сохранённые настройки/env;
|
|
# иначе встроенная экстрактивная выжимка.
|
|
llm: LLMOverride | None = None
|
|
|
|
|
|
class ProcessResponse(BaseModel):
|
|
uuid: str
|
|
title: str
|
|
source_url: str
|
|
video_path: str
|
|
text_full_path: str
|
|
text_summary_path: str
|
|
transcription_method: str
|
|
summary_preview: str
|
|
|
|
|
|
@router.post("/process", response_model=ProcessResponse)
|
|
async def process_video(req: ProcessRequest):
|
|
llm_cfg = None
|
|
sys_p = user_t = None
|
|
if req.llm is not None:
|
|
llm_cfg = LLMConfig(
|
|
base_url=req.llm.base_url,
|
|
api_key=req.llm.api_key,
|
|
model=req.llm.model,
|
|
timeout=req.llm.timeout,
|
|
max_tokens=req.llm.max_tokens,
|
|
temperature=req.llm.temperature,
|
|
extra_headers=req.llm.extra_headers,
|
|
)
|
|
sys_p = req.llm.system_prompt
|
|
user_t = req.llm.user_template
|
|
|
|
try:
|
|
result = await process_single_video(
|
|
req.url,
|
|
model_path=req.model_path,
|
|
summary_max_sentences=req.summary_max_sentences,
|
|
keep_video=req.keep_video,
|
|
section_id=req.section_id,
|
|
llm_cfg=llm_cfg,
|
|
system_prompt=sys_p,
|
|
user_template=user_t,
|
|
)
|
|
except PipelineError as e:
|
|
raise HTTPException(status_code=e.status, detail=str(e))
|
|
return ProcessResponse(**result)
|