4e06f4abe2
- 后端: 空镜素材迁移到 config/materials.json,duration从文件名_{N}s_自动解析
- 后端: 新增 POST /api/v1/materials/match 接口,后端做关键词匹配
- 前端: VideoGeneration 空镜匹配改为调用后端接口
- 前端: 人物出镜素材改为本地文件选择器直接选取,不走素材库
- 前端: 视频生成流程简化,移除Vidu对口型和七牛云上传
- Rust: 视频合成支持从随机起始时间截取人物素材片段
- Rust: 修复ffprobe参数错误(添加-show_entries format=duration)
82 lines
2.3 KiB
Python
82 lines
2.3 KiB
Python
"""
|
|
空镜素材服务
|
|
============
|
|
|
|
从本地 JSON 配置文件加载素材库,提供匹配逻辑。
|
|
duration 从文件名 `_{N}s_` 中自动解析。
|
|
"""
|
|
|
|
import json
|
|
import random
|
|
import re
|
|
from pathlib import Path
|
|
|
|
# 正则:从文件名中提取时长,如 plumbing_10s_a23f8fcb.mp4 → 10
|
|
_DURATION_RE = re.compile(r"_(\d+)s_")
|
|
|
|
# 配置缓存(启动时加载,运行时只读)
|
|
_keywords: dict[str, str] = {}
|
|
_materials: dict[str, list[dict]] = {}
|
|
|
|
|
|
def _get_config_path() -> Path:
|
|
"""获取配置文件绝对路径"""
|
|
# 配置文件位于项目根目录的 config/ 下
|
|
return Path(__file__).resolve().parent.parent.parent / "config" / "materials.json"
|
|
|
|
|
|
def _parse_duration(url: str) -> float:
|
|
"""从 URL 文件名中解析时长(秒)"""
|
|
filename = url.split("/")[-1]
|
|
match = _DURATION_RE.search(filename)
|
|
if not match:
|
|
raise ValueError(f"无法从文件名解析时长: {filename}")
|
|
return float(match.group(1))
|
|
|
|
|
|
def load_config() -> None:
|
|
"""加载素材配置到内存缓存"""
|
|
global _keywords, _materials
|
|
|
|
config_path = _get_config_path()
|
|
if not config_path.exists():
|
|
raise FileNotFoundError(f"素材配置文件不存在: {config_path}")
|
|
|
|
with open(config_path, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
|
|
_keywords = data.get("keywords", {})
|
|
raw_materials = data.get("materials", {})
|
|
|
|
# 解析每个素材的 duration
|
|
_materials = {}
|
|
for slug, entries in raw_materials.items():
|
|
parsed = []
|
|
for entry in entries:
|
|
url = entry["url"]
|
|
duration = _parse_duration(url)
|
|
parsed.append({"url": url, "duration": duration})
|
|
_materials[slug] = parsed
|
|
|
|
|
|
def match_material(scene: str, required_duration: float) -> dict | None:
|
|
"""
|
|
根据场景描述和所需时长匹配空镜素材
|
|
|
|
Args:
|
|
scene: 分镜场景描述
|
|
required_duration: 所需时长(秒)
|
|
|
|
Returns:
|
|
匹配到的素材 {url, duration},无匹配返回 None
|
|
"""
|
|
for keyword, slug in _keywords.items():
|
|
if keyword in scene:
|
|
candidates = [
|
|
m for m in _materials.get(slug, [])
|
|
if m["duration"] >= required_duration
|
|
]
|
|
if candidates:
|
|
return random.choice(candidates)
|
|
return None
|