Files
meijiaka-zy/python-api/app/services/material_service.py
T
小鱼开发 bc724810a6 feat: 视频创作流程全链路优化
- 后端: Vidu Provider、System API、Upload API、素材服务更新
- 前端: 字幕压制、视频生成、配音、本地存储、类型定义优化
- Rust: FFmpeg 命令、视频合成、语音命令、库注册更新
- Store: 项目状态、语音状态管理优化
- 新增: 对口型替换文档、健康检查器、字幕 API 模块、音频对齐工具
- 删除: 废弃的 polish 提示词模板
2026-04-26 21:24:42 +08:00

104 lines
3.2 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, exclude_urls: list[str] | None = None, strict: bool = False) -> dict | None:
"""
根据场景描述和所需时长匹配空镜素材
策略:
1. 严格匹配分类(scene 必须完全匹配 keywords 中的关键词)
2. 过滤掉时长小于 required_duration 的素材
3. 从剩余素材中排除已使用的,随机选取
Args:
scene: 分镜场景描述
required_duration: 所需时长(秒)
exclude_urls: 已使用的素材 URL 列表(避免重复)
Returns:
匹配到的素材 {url, duration},无匹配返回 None
"""
exclude_urls = exclude_urls or []
for keyword, slug in _keywords.items():
if keyword == scene:
all_materials = _materials.get(slug, [])
if not all_materials:
return None
# 1. 过滤掉时长小于 required_duration 的素材
matching = [m for m in all_materials if m["duration"] >= required_duration]
if not matching:
return None
# 2. 排除已使用的,从中随机选
unused = [m for m in matching if m["url"] not in exclude_urls]
if unused:
return random.choice(unused)
# 3. 严格模式下不允许返回已排除的素材
if strict:
return None
# 4. 非严格模式:全部用完则允许重复
return random.choice(matching)
return None