This commit is contained in:
Marek Lenczewski
2026-04-07 16:13:16 +02:00
parent 52c4e5f33d
commit 8f15f51bce
32 changed files with 212 additions and 196 deletions

0
backend/api/__init__.py Normal file
View File

View File

@@ -0,0 +1,13 @@
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from api.schemas import ProfileResponse
from database.database import get_db
from model.profile import Profile
router = APIRouter(prefix="/profiles", tags=["profiles"])
@router.get("", response_model=list[ProfileResponse])
def get_profiles(db: Session = Depends(get_db)):
return Profile.get_all(db)

47
backend/api/schemas.py Normal file
View File

@@ -0,0 +1,47 @@
from pydantic import BaseModel
class VideoCreate(BaseModel):
title: str
youtuber: str
thumbnail_url: str
youtube_url: str
profile_id: int | None = None
class VideoResponse(BaseModel):
id: int
title: str
youtuber: str
thumbnail_url: str
youtube_url: str
is_downloaded: bool
profile_ids: list[int]
class Config:
from_attributes = True
@classmethod
def from_model(cls, video):
return cls(
id=video.id,
title=video.title,
youtuber=video.youtuber,
thumbnail_url=video.thumbnail_url,
youtube_url=video.youtube_url,
is_downloaded=video.file_path is not None,
profile_ids=[p.id for p in video.profiles],
)
class CleanupRequest(BaseModel):
profile_id: int
exclude_ids: list[int] = []
class ProfileResponse(BaseModel):
id: int
name: str
class Config:
from_attributes = True

View File

@@ -0,0 +1,121 @@
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"}