129 lines
3.4 KiB
Python
129 lines
3.4 KiB
Python
import json
|
|
import shutil
|
|
from pathlib import Path
|
|
from typing import Optional, List, Dict, Any
|
|
|
|
from loguru import logger
|
|
|
|
from app.core.config import settings
|
|
|
|
|
|
BGM_EXTENSIONS = {".wav", ".mp3", ".m4a", ".aac", ".flac", ".ogg", ".webm"}
|
|
|
|
|
|
def _style_file_path(style_type: str) -> Path:
|
|
return settings.ASSETS_DIR / "styles" / f"{style_type}.json"
|
|
|
|
|
|
def _load_style_file(style_type: str) -> List[Dict[str, Any]]:
|
|
style_path = _style_file_path(style_type)
|
|
if not style_path.exists():
|
|
return []
|
|
try:
|
|
with open(style_path, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
if isinstance(data, list):
|
|
return data
|
|
except Exception as e:
|
|
logger.error(f"Failed to load style file {style_path}: {e}")
|
|
return []
|
|
|
|
|
|
def list_styles(style_type: str) -> List[Dict[str, Any]]:
|
|
return _load_style_file(style_type)
|
|
|
|
|
|
def get_style(style_type: str, style_id: Optional[str]) -> Optional[Dict[str, Any]]:
|
|
if not style_id:
|
|
return None
|
|
for item in _load_style_file(style_type):
|
|
if item.get("id") == style_id:
|
|
return item
|
|
return None
|
|
|
|
|
|
def get_default_style(style_type: str) -> Optional[Dict[str, Any]]:
|
|
styles = _load_style_file(style_type)
|
|
if not styles:
|
|
return None
|
|
for item in styles:
|
|
if item.get("is_default"):
|
|
return item
|
|
return styles[0]
|
|
|
|
|
|
def list_bgm() -> List[Dict[str, Any]]:
|
|
bgm_root = settings.ASSETS_DIR / "bgm"
|
|
if not bgm_root.exists():
|
|
return []
|
|
|
|
items: List[Dict[str, Any]] = []
|
|
for path in bgm_root.rglob("*"):
|
|
if not path.is_file():
|
|
continue
|
|
if path.suffix.lower() not in BGM_EXTENSIONS:
|
|
continue
|
|
rel = path.relative_to(bgm_root).as_posix()
|
|
items.append({
|
|
"id": rel,
|
|
"name": path.stem,
|
|
"ext": path.suffix.lower().lstrip(".")
|
|
})
|
|
|
|
items.sort(key=lambda x: x.get("name", ""))
|
|
return items
|
|
|
|
|
|
def resolve_bgm_path(bgm_id: str) -> Optional[Path]:
|
|
if not bgm_id:
|
|
return None
|
|
bgm_root = settings.ASSETS_DIR / "bgm"
|
|
candidate = (bgm_root / bgm_id).resolve()
|
|
try:
|
|
candidate.relative_to(bgm_root.resolve())
|
|
except ValueError:
|
|
return None
|
|
if candidate.exists() and candidate.is_file():
|
|
return candidate
|
|
return None
|
|
|
|
|
|
def prepare_style_for_remotion(
|
|
style: Optional[Dict[str, Any]],
|
|
temp_dir: Path,
|
|
prefix: str
|
|
) -> Optional[Dict[str, Any]]:
|
|
if not style:
|
|
return None
|
|
|
|
prepared = dict(style)
|
|
font_file = prepared.get("font_file")
|
|
if not font_file:
|
|
return prepared
|
|
|
|
source_font = (settings.ASSETS_DIR / "fonts" / font_file).resolve()
|
|
try:
|
|
source_font.relative_to((settings.ASSETS_DIR / "fonts").resolve())
|
|
except ValueError:
|
|
logger.warning(f"Font path outside assets: {font_file}")
|
|
return prepared
|
|
|
|
if not source_font.exists():
|
|
logger.warning(f"Font file missing: {source_font}")
|
|
return prepared
|
|
|
|
temp_dir.mkdir(parents=True, exist_ok=True)
|
|
ext = source_font.suffix.lower()
|
|
target_name = f"{prefix}{ext}"
|
|
target_path = temp_dir / target_name
|
|
|
|
try:
|
|
shutil.copy(source_font, target_path)
|
|
prepared["font_file"] = target_name
|
|
if not prepared.get("font_family"):
|
|
prepared["font_family"] = prefix
|
|
except Exception as e:
|
|
logger.warning(f"Failed to copy font {source_font} -> {target_path}: {e}")
|
|
|
|
return prepared
|