datasea/ffmpeg-video-render
0
1from fastapi import FastAPI, UploadFile, Form2from fastapi.responses import FileResponse3import uuid4import os5import subprocess6 7app = FastAPI()8 9# Overlay 和字体资源路径(固定)10FONT_PATH = "/app/assets/fonts/FonderLTHTCH.ttf"11OVERLAY_PATH = "/app/assets/overlays/dust_particles-1.mp4"12 13@app.post("/render")14async def render_video(15 image: UploadFile,16 audio: UploadFile,17 fps: int = Form(...),18 filter_complex: str = Form(...),19 preset: str = Form(...),20 filename: str = Form(...),21 video_path: str = Form(...),22 shortest: bool = Form(True),23):24 # 文件处理25 image_ext = image.filename.split(".")[-1]26 audio_ext = audio.filename.split(".")[-1]27 image_path = f"/tmp/{uuid.uuid4()}.{image_ext}"28 audio_path = f"/tmp/{uuid.uuid4()}.{audio_ext}"29 output_path = f"{video_path.rstrip('/')}/{filename}.mp4"30 31 # 保存上传的 image 和 audio32 with open(image_path, "wb") as f:33 f.write(await image.read())34 with open(audio_path, "wb") as f:35 f.write(await audio.read())36 37 # 构造 FFmpeg 命令38 cmd = [39 "ffmpeg", "-y", "-nostdin", "-hide_banner", "-loglevel", "error",40 "-loop", "1", "-framerate", str(fps), "-i", image_path,41 "-i", OVERLAY_PATH,42 "-i", audio_path,43 "-filter_complex", filter_complex,44 "-map", "[v]", "-map", "2:a",45 "-c:v", "libx264", "-preset", preset, "-pix_fmt", "yuv420p",46 "-c:a", "aac", "-b:a", "192k",47 "-movflags", "+faststart",48 "-metadata", f"title={filename}"49 ]50 51 if shortest:52 cmd.append("-shortest")53 54 cmd.append(output_path)55 56 # 确保目标路径存在(仅用于 Space /tmp 测试)57 os.makedirs(os.path.dirname(output_path), exist_ok=True)58 59 # 执行 FFmpeg60 subprocess.run(cmd, check=True)61 62 return FileResponse(output_path, media_type="video/mp4", filename=f"{filename}.mp4")