"""Endpoint for splitting with a tracklist file upload.""" import json from pathlib import Path from typing import Optional from fastapi import APIRouter, UploadFile, File, Form, HTTPException, BackgroundTasks from backend.config import settings from backend.services.task_manager import task_manager from backend.services.splitter import run_split_task from backend.models.request import TracklistEntry from backend.models.response import SplitResponse, TaskStatus router = APIRouter(prefix="/api", tags=["split"]) from audio_splitter.tracklist import parse_format, read_tracklist @router.post("/split-file", response_model=SplitResponse) async def split_from_file( background_tasks: BackgroundTasks, task_id: str = Form(...), tracklist_file: UploadFile = File(...), # New fields container: Optional[str] = Form(None), audio_codec: str = Form("copy"), video_codec: str = Form("copy"), subtitle_codec: str = Form("copy"), video_quality: Optional[int] = Form(None), drop_video: bool = Form(False), drop_subs: bool = Form(False), number_tracks: bool = Form(False), replace_bad_chars: bool = Form(False), replacement_char: str = Form("_"), bad_chars: str = Form(r'!@#№$;:%^&?*(){}[]\/<>+=~`\' '), skip_existing: bool = Form(False), output_template: str = Form("%an-%tn.%ext"), album: Optional[str] = Form(None), comment: Optional[str] = Form(None), no_comment: bool = Form(False), comment_stream: Optional[int] = Form(None), merge_comments: bool = Form(False), comment_separator: str = Form("; "), options: str = Form("{}"), # backward-compatible, but we now use explicit fields tracklist_format: str = Form("%ts %tn - %an"), ): # Validate task exists if not task_manager.has_task(task_id): raise HTTPException(status_code=404, detail=f"Task {task_id} not found") status = task_manager.get_status(task_id) if status and status["status"] in (TaskStatus.PROCESSING, TaskStatus.DONE): raise HTTPException( status_code=409, detail=f"Task {task_id} is already {status['status']}" ) if not tracklist_file.filename.endswith(('.txt', '.text')): raise HTTPException( status_code=400, detail="Tracklist file must be a text file (.txt or .text)" ) try: content = await tracklist_file.read() text = content.decode('utf-8') except UnicodeDecodeError: raise HTTPException( status_code=400, detail="Tracklist file must be UTF-8 encoded" ) if not text.strip(): raise HTTPException( status_code=400, detail="Tracklist file is empty" ) # Parse tracklist using CLI logic try: tokens = parse_format(tracklist_format) temp_tracklist_path = settings.temp_dir / task_id / "tracklist.txt" temp_tracklist_path.parent.mkdir(parents=True, exist_ok=True) temp_tracklist_path.write_text(text, encoding='utf-8') tracklist_dicts = read_tracklist(str(temp_tracklist_path), tokens) except Exception as e: raise HTTPException( status_code=400, detail=f"Failed to parse tracklist: {str(e)}" ) if not tracklist_dicts: raise HTTPException( status_code=400, detail="No valid tracks found in tracklist file" ) # Convert dicts to TracklistEntry objects try: tracklist_entries = [TracklistEntry(**entry) for entry in tracklist_dicts] except Exception as e: raise HTTPException( status_code=400, detail=f"Invalid tracklist data: {str(e)}" ) # Build options dict from explicit fields (ignore `options` parameter) options = { "container": container, "audio_codec": audio_codec, "video_codec": video_codec, "subtitle_codec": subtitle_codec, "video_quality": video_quality, "drop_video": drop_video, "drop_subs": drop_subs, "number_tracks": number_tracks, "replace_bad_chars": replace_bad_chars, "replacement_char": replacement_char, "bad_chars": bad_chars, "skip_existing": skip_existing, "output_template": output_template, "album": album, "comment": comment, "no_comment": no_comment, "comment_stream": comment_stream, "merge_comments": merge_comments, "comment_separator": comment_separator, } # Update task status task_manager.update_task( task_id, status=TaskStatus.PROCESSING, progress=0, message="Preparing to split..." ) background_tasks.add_task( run_split_task, task_id, tracklist_entries, options ) return SplitResponse( task_id=task_id, status=TaskStatus.PROCESSING )