fix: 优化架构

This commit is contained in:
Daniel
2026-03-25 19:35:37 +08:00
parent 34786b37c7
commit 508c28ce31
184 changed files with 2199 additions and 241 deletions

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,9 @@
from __future__ import annotations
from pathlib import Path
class BaseVideoGen:
def generate(self, image_path: str, prompt: dict, output_path: str | Path) -> str:
raise NotImplementedError

View File

@@ -0,0 +1,18 @@
from __future__ import annotations
from pathlib import Path
from engine.config import AppConfig
from .base import BaseVideoGen
class LTXVideoGen(BaseVideoGen):
def __init__(self, cfg: AppConfig):
self.cfg = cfg
def generate(self, image_path: str, prompt: dict, output_path: str | Path) -> str:
# Reserved for future: direct image->video generation (LTX / diffusion video).
# Current project keeps clip generation via MoviePy for stability.
raise NotImplementedError("LTXVideoGen is not implemented yet")

View 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)