66 lines
2.4 KiB
Python
66 lines
2.4 KiB
Python
import asyncio
|
|
from pathlib import Path
|
|
from fastapi import HTTPException
|
|
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class VideoService:
|
|
def __init__(self, upload_dir: str, max_size_mb: int, supported_formats: list[str]):
|
|
self.upload_dir = Path(upload_dir)
|
|
self.max_size_bytes = max_size_mb * 1024 * 1024
|
|
self.supported_formats = supported_formats
|
|
self.upload_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
def validate_video(self, filename: str | None, content_type: str | None, size_bytes: int) -> None:
|
|
if not filename:
|
|
raise HTTPException(status_code=400, detail="No file selected")
|
|
ext = Path(filename).suffix.lower()
|
|
if ext not in self.supported_formats:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Unsupported format: {ext}. Supported: {', '.join(self.supported_formats)}",
|
|
)
|
|
if size_bytes > self.max_size_bytes:
|
|
raise HTTPException(
|
|
status_code=413,
|
|
detail=f"File exceeds {self.max_size_bytes // 1024 // 1024}MB limit",
|
|
)
|
|
|
|
def get_video_path(self, video_id: str) -> Path:
|
|
candidates = list(self.upload_dir.glob(f"{video_id}.*"))
|
|
if not candidates:
|
|
raise HTTPException(status_code=404, detail="Video not found")
|
|
return candidates[0]
|
|
|
|
def delete_video(self, video_id: str) -> None:
|
|
for p in self.upload_dir.glob(f"{video_id}.*"):
|
|
p.unlink()
|
|
|
|
async def extract_audio(self, video_id: str) -> Path:
|
|
video_path = self.get_video_path(video_id)
|
|
output_path = self.upload_dir / f"{video_id}_audio.wav"
|
|
|
|
proc = await asyncio.create_subprocess_exec(
|
|
"ffmpeg", "-i", str(video_path),
|
|
"-vn", "-acodec", "pcm_s16le",
|
|
"-ar", "16000", "-ac", "1",
|
|
"-f", "wav", str(output_path),
|
|
stdout=asyncio.subprocess.PIPE,
|
|
stderr=asyncio.subprocess.PIPE,
|
|
)
|
|
stdout, stderr = await proc.communicate()
|
|
|
|
if proc.returncode != 0:
|
|
if output_path.exists():
|
|
output_path.unlink(missing_ok=True)
|
|
logger.error("ffmpeg failed for video_id=%s: %s", video_id, stderr.decode(errors="replace"))
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail=f"Audio extraction failed: {stderr.decode(errors='replace')[:200]}",
|
|
)
|
|
|
|
return output_path
|