from fastapi import FastAPI, UploadFile, Form from fastapi.responses import FileResponse import uuid import os import subprocess app = FastAPI() # Overlay 和字体资源路径(固定) FONT_PATH = "/app/assets/fonts/FonderLTHTCH.ttf" OVERLAY_PATH = "/app/assets/overlays/dust_particles-1.mp4" @app.post("/render") async def render_video( image: UploadFile, audio: UploadFile, fps: int = Form(...), filter_complex: str = Form(...), preset: str = Form(...), filename: str = Form(...), video_path: str = Form(...), shortest: bool = Form(True), ): # 文件处理 image_ext = image.filename.split(".")[-1] audio_ext = audio.filename.split(".")[-1] image_path = f"/tmp/{uuid.uuid4()}.{image_ext}" audio_path = f"/tmp/{uuid.uuid4()}.{audio_ext}" output_path = f"{video_path.rstrip('/')}/{filename}.mp4" # 保存上传的 image 和 audio with open(image_path, "wb") as f: f.write(await image.read()) with open(audio_path, "wb") as f: f.write(await audio.read()) # 构造 FFmpeg 命令 cmd = [ "ffmpeg", "-y", "-nostdin", "-hide_banner", "-loglevel", "error", "-loop", "1", "-framerate", str(fps), "-i", image_path, "-i", OVERLAY_PATH, "-i", audio_path, "-filter_complex", filter_complex, "-map", "[v]", "-map", "2:a", "-c:v", "libx264", "-preset", preset, "-pix_fmt", "yuv420p", "-c:a", "aac", "-b:a", "192k", "-movflags", "+faststart", "-metadata", f"title={filename}" ] if shortest: cmd.append("-shortest") cmd.append(output_path) # 确保目标路径存在(仅用于 Space /tmp 测试) os.makedirs(os.path.dirname(output_path), exist_ok=True) # 执行 FFmpeg subprocess.run(cmd, check=True) return FileResponse(output_path, media_type="video/mp4", filename=f"{filename}.mp4")