- таблица 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>
85 lines
2.6 KiB
Python
85 lines
2.6 KiB
Python
from sqlalchemy import select
|
||
|
||
from api.db.connection import SessionLocal
|
||
from api.models.video import Video
|
||
|
||
|
||
async def create_video(
|
||
*,
|
||
uuid: str,
|
||
source_url: str,
|
||
title: str,
|
||
video_path: str,
|
||
text_full_path: str,
|
||
text_summary_path: str,
|
||
transcription_method: str,
|
||
section_id: int | None = None,
|
||
job_id: str | None = None,
|
||
) -> Video:
|
||
async with SessionLocal() as session:
|
||
v = Video(
|
||
uuid=uuid,
|
||
source_url=source_url,
|
||
section_id=section_id,
|
||
job_id=job_id,
|
||
title=title or "",
|
||
video_path=video_path or "",
|
||
text_full_path=text_full_path or "",
|
||
text_summary_path=text_summary_path or "",
|
||
transcription_method=transcription_method or "",
|
||
)
|
||
session.add(v)
|
||
await session.commit()
|
||
await session.refresh(v)
|
||
return v
|
||
|
||
|
||
async def get_video(video_uuid: str) -> Video | None:
|
||
async with SessionLocal() as session:
|
||
res = await session.execute(select(Video).where(Video.uuid == video_uuid))
|
||
return res.scalars().first()
|
||
|
||
|
||
async def list_videos(
|
||
*,
|
||
q: str | None = None,
|
||
section_ids: list[int] | None = None,
|
||
method: str | None = None,
|
||
) -> list[Video]:
|
||
"""Каталог с фильтрами: поиск по названию/URL, разделы (уже с потомками), метод."""
|
||
stmt = select(Video)
|
||
if q:
|
||
pattern = f"%{q}%"
|
||
stmt = stmt.where(Video.title.ilike(pattern) | Video.source_url.ilike(pattern))
|
||
if section_ids is not None:
|
||
stmt = stmt.where(Video.section_id.in_(section_ids))
|
||
if method:
|
||
stmt = stmt.where(Video.transcription_method.ilike(f"{method}%"))
|
||
stmt = stmt.order_by(Video.created_at.desc())
|
||
async with SessionLocal() as session:
|
||
res = await session.execute(stmt)
|
||
return list(res.scalars().all())
|
||
|
||
|
||
async def set_video_section(video_uuid: str, section_id: int | None) -> Video | None:
|
||
async with SessionLocal() as session:
|
||
res = await session.execute(select(Video).where(Video.uuid == video_uuid))
|
||
v = res.scalars().first()
|
||
if not v:
|
||
return None
|
||
v.section_id = section_id
|
||
await session.commit()
|
||
await session.refresh(v)
|
||
return v
|
||
|
||
|
||
async def delete_video(video_uuid: str) -> bool:
|
||
async with SessionLocal() as session:
|
||
res = await session.execute(select(Video).where(Video.uuid == video_uuid))
|
||
v = res.scalars().first()
|
||
if not v:
|
||
return False
|
||
await session.delete(v)
|
||
await session.commit()
|
||
return True
|