import os import shutil from fastapi import APIRouter, HTTPException from fastapi.responses import FileResponse from app.models.schemas import JobInfo from app.models.job_store import job_store from app.config import settings router = APIRouter() @router.get("/", response_model=list[JobInfo]) async def list_jobs(limit: int = 50): """List all jobs.""" return job_store.list_jobs(limit=limit) @router.get("/{job_id}", response_model=JobInfo) async def get_job(job_id: str): """Get job details.""" job = job_store.get_job(job_id) if not job: raise HTTPException(status_code=404, detail="Job not found") print(f"[API GET] Job {job_id}: status={job.status}, progress={job.progress}") return job @router.delete("/{job_id}") async def delete_job(job_id: str): """Delete a job and its files.""" job = job_store.get_job(job_id) if not job: raise HTTPException(status_code=404, detail="Job not found") # Delete associated files download_dir = os.path.join(settings.DOWNLOAD_DIR, job_id) processed_dir = os.path.join(settings.PROCESSED_DIR, job_id) if os.path.exists(download_dir): shutil.rmtree(download_dir) if os.path.exists(processed_dir): shutil.rmtree(processed_dir) job_store.delete_job(job_id) return {"message": f"Job {job_id} deleted"} @router.get("/{job_id}/download") async def download_output(job_id: str): """Download the processed video.""" job = job_store.get_job(job_id) if not job: raise HTTPException(status_code=404, detail="Job not found") if not job.output_path or not os.path.exists(job.output_path): raise HTTPException(status_code=404, detail="Output file not found") return FileResponse( path=job.output_path, media_type="video/mp4", filename=f"shorts_{job_id}.mp4" ) @router.get("/{job_id}/original") async def download_original(job_id: str): """Download the original video.""" job = job_store.get_job(job_id) if not job: raise HTTPException(status_code=404, detail="Job not found") if not job.video_path or not os.path.exists(job.video_path): raise HTTPException(status_code=404, detail="Original video not found") filename = os.path.basename(job.video_path) # Disable caching to ensure trimmed video is always fetched fresh return FileResponse( path=job.video_path, media_type="video/mp4", filename=filename, headers={ "Cache-Control": "no-cache, no-store, must-revalidate", "Pragma": "no-cache", "Expires": "0" } ) @router.get("/{job_id}/subtitle") async def download_subtitle(job_id: str, format: str = "ass"): """Download the subtitle file.""" job = job_store.get_job(job_id) if not job: raise HTTPException(status_code=404, detail="Job not found") if not job.video_path: raise HTTPException(status_code=404, detail="Video not found") job_dir = os.path.dirname(job.video_path) subtitle_path = os.path.join(job_dir, f"subtitle.{format}") if not os.path.exists(subtitle_path): # Try to generate from transcript if job.transcript: from app.services.transcriber import segments_to_ass, segments_to_srt if format == "srt": content = segments_to_srt(job.transcript, use_translated=True) else: content = segments_to_ass(job.transcript, use_translated=True) with open(subtitle_path, "w", encoding="utf-8") as f: f.write(content) else: raise HTTPException(status_code=404, detail="Subtitle not found") return FileResponse( path=subtitle_path, media_type="text/plain", filename=f"subtitle_{job_id}.{format}" ) @router.get("/{job_id}/thumbnail") async def download_thumbnail(job_id: str): """Download the generated thumbnail image.""" job = job_store.get_job(job_id) if not job: raise HTTPException(status_code=404, detail="Job not found") # Check for thumbnail in processed directory thumbnail_path = os.path.join(settings.PROCESSED_DIR, f"{job_id}_thumbnail.jpg") if not os.path.exists(thumbnail_path): raise HTTPException(status_code=404, detail="Thumbnail not found. Generate it first using /process/{job_id}/thumbnail") return FileResponse( path=thumbnail_path, media_type="image/jpeg", filename=f"thumbnail_{job_id}.jpg" ) @router.post("/{job_id}/re-edit") async def re_edit_job(job_id: str): """Reset job status to awaiting_review for re-editing.""" from app.models.schemas import JobStatus job = job_store.get_job(job_id) if not job: raise HTTPException(status_code=404, detail="Job not found") if job.status != JobStatus.COMPLETED: raise HTTPException( status_code=400, detail="Only completed jobs can be re-edited" ) # Check if transcript exists for re-editing if not job.transcript: raise HTTPException( status_code=400, detail="No transcript found. Cannot re-edit." ) # Reset status to awaiting_review job_store.update_job( job_id, status=JobStatus.AWAITING_REVIEW, progress=70, error=None ) return {"message": "Job ready for re-editing", "job_id": job_id}