import threading from pathlib import Path from typing import Optional from fastapi import APIRouter, Depends, HTTPException, Query from fastapi.responses import FileResponse, StreamingResponse from sqlalchemy.orm import Session from api.schemas import CleanupRequest, VideoCreate, VideoResponse from database.database import SessionLocal, get_db from download.download_service import download_video from model.video import Video from notify.notify_clients import notify_clients from stream.stream_service import stream_video_live router = APIRouter(prefix="/videos", tags=["videos"]) @router.post("", response_model=list[VideoResponse]) async def create_videos(videos_data: list[VideoCreate], db: Session = Depends(get_db)): created_ids = [] profile_ids = set() for video_data in reversed(videos_data): video_id_match = video_data.youtube_url.split("v=")[-1].split("&")[0] Video.delete_by_youtube_id(db, video_id_match) data = video_data.model_dump(exclude={"profile_id"}) video = Video.create_from_dict(db, data, video_data.profile_id) created_ids.append(video.id) profile_ids.add(video_data.profile_id or 1) videos = [Video.get_by_id(db, vid) for vid in created_ids] if profile_ids: await notify_clients(list(profile_ids)) return [VideoResponse.from_model(v) for v in videos if v] @router.get("", response_model=list[VideoResponse]) def get_all_videos(profile_id: Optional[int] = Query(None), db: Session = Depends(get_db)): videos = Video.get_all(db, profile_id=profile_id) return [VideoResponse.from_model(v) for v in videos] @router.get("/downloaded", response_model=list[VideoResponse]) def get_downloaded_videos(profile_id: Optional[int] = Query(None), db: Session = Depends(get_db)): videos = Video.get_downloaded(db, profile_id=profile_id) return [VideoResponse.from_model(v) for v in videos] @router.post("/cleanup") def cleanup_videos(request: CleanupRequest, db: Session = Depends(get_db)): count = Video.delete_not_downloaded(db, request.profile_id, request.exclude_ids or None) return {"deleted": count} @router.post("/{video_id}/download") def trigger_download(video_id: int, db: Session = Depends(get_db)): video = Video.get_by_id(db, video_id) if not video: raise HTTPException(status_code=404, detail="Video nicht gefunden") if video.file_path: return {"status": "already_downloaded"} thread = threading.Thread(target=download_video, args=(video.id, video.youtube_url)) thread.start() return {"status": "download_started"} @router.get("/{video_id}/stream") def stream_video(video_id: int, db: Session = Depends(get_db)): video = Video.get_by_id(db, video_id) if not video: raise HTTPException(status_code=404, detail="Video nicht gefunden") if not video.file_path: def stream_and_save(): output_path = f"/videos/{video_id}.mp4" yield from stream_video_live(video_id, video.youtube_url) if Path(output_path).exists(): sdb = SessionLocal() try: Video.update_file_path(sdb, video_id, output_path) finally: sdb.close() return StreamingResponse(stream_and_save(), media_type="video/mp4") path = Path(video.file_path) if not path.exists(): raise HTTPException(status_code=404, detail="Videodatei nicht gefunden") return FileResponse(path, media_type="video/mp4") @router.get("/{video_id}/file") def download_file(video_id: int, db: Session = Depends(get_db)): video = Video.get_by_id(db, video_id) if not video: raise HTTPException(status_code=404, detail="Video nicht gefunden") if not video.file_path: raise HTTPException(status_code=404, detail="Video noch nicht heruntergeladen") path = Path(video.file_path) if not path.exists(): Video.update_file_path(db, video_id, None) raise HTTPException(status_code=404, detail="Video noch nicht heruntergeladen") return FileResponse(path, media_type="video/mp4", filename=f"{video.title}.mp4") @router.delete("/{video_id}/file") def delete_server_file(video_id: int, db: Session = Depends(get_db)): video = Video.get_by_id(db, video_id) if not video: raise HTTPException(status_code=404, detail="Video nicht gefunden") if video.file_path: path = Path(video.file_path) if path.exists(): path.unlink() Video.update_file_path(db, video_id, None) return {"status": "deleted"}