fix: 优化架构
This commit is contained in:
81
engine/adapters/video/moviepy_adapter.py
Normal file
81
engine/adapters/video/moviepy_adapter.py
Normal file
@@ -0,0 +1,81 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
from moviepy import AudioFileClip, VideoClip
|
||||
from PIL import Image
|
||||
|
||||
from engine.config import AppConfig
|
||||
|
||||
from .base import BaseVideoGen
|
||||
|
||||
|
||||
class MoviePyVideoGen(BaseVideoGen):
|
||||
def __init__(self, cfg: AppConfig):
|
||||
self.cfg = cfg
|
||||
|
||||
def generate(self, image_path: str, prompt: dict, output_path: str | Path) -> str:
|
||||
output_path = Path(output_path)
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Required prompt fields for shot rendering.
|
||||
duration_s = float(prompt.get("duration_s", 3))
|
||||
fps = int(prompt.get("fps", self.cfg.get("video.mock_fps", 24)))
|
||||
audio_path = prompt.get("audio_path")
|
||||
|
||||
# Clip resolution.
|
||||
size = prompt.get("size")
|
||||
if isinstance(size, (list, tuple)) and len(size) == 2:
|
||||
w, h = int(size[0]), int(size[1])
|
||||
else:
|
||||
mock_size = self.cfg.get("video.mock_size", [1024, 576])
|
||||
w, h = int(mock_size[0]), int(mock_size[1])
|
||||
|
||||
base_img = Image.open(image_path).convert("RGB")
|
||||
|
||||
def make_frame(t: float):
|
||||
progress = float(t) / max(duration_s, 1e-6)
|
||||
progress = max(0.0, min(1.0, progress))
|
||||
scale = 1.0 + 0.03 * progress
|
||||
new_w = max(w, int(w * scale))
|
||||
new_h = max(h, int(h * scale))
|
||||
frame = base_img.resize((new_w, new_h), Image.LANCZOS)
|
||||
left = (new_w - w) // 2
|
||||
top = (new_h - h) // 2
|
||||
frame = frame.crop((left, top, left + w, top + h))
|
||||
return np.array(frame)
|
||||
|
||||
video = VideoClip(make_frame, duration=duration_s, has_constant_size=True)
|
||||
|
||||
# Optional audio.
|
||||
if audio_path and os.path.exists(str(audio_path)):
|
||||
a = AudioFileClip(str(audio_path))
|
||||
video = video.with_audio(a)
|
||||
else:
|
||||
a = None
|
||||
|
||||
try:
|
||||
video.write_videofile(
|
||||
str(output_path),
|
||||
fps=fps,
|
||||
codec="libx264",
|
||||
audio_codec="aac",
|
||||
preset="veryfast",
|
||||
threads=2,
|
||||
)
|
||||
finally:
|
||||
try:
|
||||
video.close()
|
||||
except Exception:
|
||||
pass
|
||||
if a is not None:
|
||||
try:
|
||||
a.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return str(output_path)
|
||||
|
||||
Reference in New Issue
Block a user