Spaces:
Sleeping
Sleeping
| 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" | |
| 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") |