- таблица sections (дерево через parent_id), у видео section_id
- API: CRUD /sections, фильтры /videos (поиск, раздел с потомками, метод),
GET /videos/{id}/text, PATCH раздела, DELETE вместе с файлами
- pipeline: keep_video=false удаляет mp4 после расшифровки, section_id
- UI: панель разделов с вложенностью и счётчиками, поиск, фильтр по методу,
диалоги полного текста/выжимки, привязка к разделу, удаление,
переключатель «Сохранять видео на диске»
- compose: bind-mount ./web для правок без пересборки
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
83 lines
2.5 KiB
Python
83 lines
2.5 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,
|
||
) -> Video:
|
||
async with SessionLocal() as session:
|
||
v = Video(
|
||
uuid=uuid,
|
||
source_url=source_url,
|
||
section_id=section_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
|