from fastapi import FastAPI, UploadFile, File, HTTPException, BackgroundTasks, Query from fastapi.responses import FileResponse, JSONResponse from fastapi.middleware.cors import CORSMiddleware import os import shutil import uuid import time from typing import Dict, List, Optional import threading import logging from pathlib import Path # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Import processing functions and variables from processing_logic import ( processing_status, uploaded_mp4s, log_message, process_hf_files_background, UPLOAD_DIRECTORY, MP4_OUTPUT_FOLDER, hf_api, DEFAULT_RAR_LIMIT ) app = FastAPI(title="MP4 Processing API", description="API for uploading, processing, and downloading MP4 files") # Configure CORS app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Define MP4_UPLOAD_FOLDER if not imported from processing_logic MP4_UPLOAD_FOLDER = os.path.join(UPLOAD_DIRECTORY, "uploads") if 'UPLOAD_DIRECTORY' in globals() else "uploads" os.makedirs(MP4_UPLOAD_FOLDER, exist_ok=True) os.makedirs(MP4_OUTPUT_FOLDER, exist_ok=True) processing_thread = None # ==== HELPER FUNCTIONS ==== def save_file(uploaded_file: UploadFile, save_path: str): os.makedirs(os.path.dirname(save_path), exist_ok=True) with open(save_path, "wb") as f: shutil.copyfileobj(uploaded_file.file, f) def log_request(endpoint: str, params: dict = None): """Log API requests for debugging""" logger.info(f"API Request: {endpoint} - Params: {params}") # === ROUTES === @app.get("/") async def root(): """API root endpoint""" return { "message": "MP4 Processing API", "version": "1.0.0", "status": "running", "endpoints": { "upload": "POST /upload - Upload MP4 file", "download": "GET /download?course={course}&file={file} - Download MP4 file", "list": "GET /list - List uploaded files", "courses": "GET /courses - List all course folders", "images": "GET /images/{course_folder:path} - List MP4s in course", "debug": "GET /debug/structure - Debug file structure" } } @app.get("/courses") async def get_courses(): """List all top-level course folders.""" try: courses = [d.name for d in Path(MP4_OUTPUT_FOLDER).iterdir() if d.is_dir()] return {"courses": courses, "total": len(courses)} except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to list courses: {e}") @app.get("/images/{course_folder:path}") async def get_mp4_list(course_folder: str): """List all MP4 files within a specific course folder.""" course_path = Path(MP4_OUTPUT_FOLDER) / course_folder if not course_path.is_dir(): raise HTTPException(status_code=404, detail="Course folder not found") try: mp4_files = [f.name for f in course_path.iterdir() if f.is_file() and f.suffix.lower() == ".mp4"] return mp4_files except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to list MP4s: {e}") @app.get("/download") async def download_mp4(course: str, file: str): """Download a specific MP4 file from a course folder.""" file_path = Path(MP4_OUTPUT_FOLDER) / course / file if not file_path.is_file(): raise HTTPException(status_code=404, detail="File not found") return FileResponse(path=file_path, media_type="video/mp4", filename=file) @app.get("/debug/structure") async def debug_structure(): """Debug endpoint to inspect the file structure and sizes.""" mp4_output_folder_path = Path(MP4_OUTPUT_FOLDER) structure = {} total_size_bytes = 0 total_mp4_files = 0 if not mp4_output_folder_path.exists(): return JSONResponse(content={ "mp4_output_folder": str(mp4_output_folder_path), "folder_exists": False, "total_mp4_files": 0, "total_size_bytes": 0, "structure": {} }) for root, dirs, files in os.walk(mp4_output_folder_path): current_path = Path(root) relative_path = str(current_path.relative_to(mp4_output_folder_path)) if relative_path == ".": relative_path = "/" structure[relative_path] = { "directories": [d for d in dirs], "mp4_files": [], "other_files": [] } for file in files: file_full_path = current_path / file file_size = file_full_path.stat().st_size total_size_bytes += file_size if file.lower().endswith(".mp4"): structure[relative_path]["mp4_files"].append({"name": file, "size": file_size}) total_mp4_files += 1 else: structure[relative_path]["other_files"].append({"name": file, "size": file_size}) return { "mp4_output_folder": str(mp4_output_folder_path), "folder_exists": mp4_output_folder_path.exists(), "total_mp4_files": total_mp4_files, "total_size_bytes": total_size_bytes, "structure": structure } @app.on_event("startup") async def startup_event(): """Run the processing loop in the background when the API starts""" global processing_thread logger.info("Starting up MP4 Processing API...") if not (processing_thread and processing_thread.is_alive()): logger.info("🚀 Starting background processing thread...") processing_thread = threading.Thread(target=process_hf_files_background) processing_thread.daemon = True processing_thread.start() if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)