11
This commit is contained in:
165
scripts/cover/cover_preview_generator.py
Normal file
165
scripts/cover/cover_preview_generator.py
Normal file
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
实时封面预览生成器(移植版:调用 cover_generator.py,不依赖 subtitle_cover_generator_simple)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import hashlib
|
||||
import tempfile
|
||||
import subprocess
|
||||
from typing import Dict, Any
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
if SCRIPT_DIR not in sys.path:
|
||||
sys.path.insert(0, SCRIPT_DIR)
|
||||
|
||||
COVER_GENERATOR = os.path.join(SCRIPT_DIR, "cover_generator.py")
|
||||
|
||||
STYLE_CONFIGS = {
|
||||
"default": {
|
||||
"titlePosition": "top",
|
||||
"titleColor": "#FFFFFF",
|
||||
"titleFontSize": 72,
|
||||
"titleStrokeWidth": 0,
|
||||
},
|
||||
"blur-bg": {
|
||||
"titlePosition": "top",
|
||||
"titleColor": "#FFFFFF",
|
||||
"titleFontSize": 72,
|
||||
"backgroundBlurEnabled": True,
|
||||
},
|
||||
"outline": {
|
||||
"titlePosition": "top",
|
||||
"titleColor": "#FFFFFF",
|
||||
"titleFontSize": 72,
|
||||
"titleStrokeWidth": 4,
|
||||
"titleStrokeColor": "#000000",
|
||||
},
|
||||
"gradient": {
|
||||
"titlePosition": "top",
|
||||
"titleColor": "#FFFFFF",
|
||||
"titleFontSize": 72,
|
||||
},
|
||||
"split": {
|
||||
"titlePosition": "center",
|
||||
"titleColor": "#FFFFFF",
|
||||
"titleFontSize": 64,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class CoverPreviewGenerator:
|
||||
def __init__(self):
|
||||
self.temp_dir = os.path.join(tempfile.gettempdir(), "aiclient-cover-previews")
|
||||
os.makedirs(self.temp_dir, exist_ok=True)
|
||||
self.preview_cache = {}
|
||||
self.COVER_STYLES = list(STYLE_CONFIGS.keys())
|
||||
|
||||
def get_video_hash(self, video_path: str) -> str:
|
||||
h = hashlib.md5()
|
||||
with open(video_path, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(4096), b""):
|
||||
h.update(chunk)
|
||||
return h.hexdigest()[:16]
|
||||
|
||||
def _run_cover_generator(self, video_path: str, title: str, output_path: str, style: str) -> bool:
|
||||
if not os.path.isfile(COVER_GENERATOR):
|
||||
print(f"Missing {COVER_GENERATOR}", file=sys.stderr)
|
||||
return False
|
||||
config = dict(STYLE_CONFIGS.get(style, STYLE_CONFIGS["default"]))
|
||||
python_exe = os.environ.get("AICLIENT_PYTHON_PATH") or sys.executable
|
||||
cmd = [
|
||||
python_exe,
|
||||
COVER_GENERATOR,
|
||||
"--video",
|
||||
video_path,
|
||||
"--title",
|
||||
title,
|
||||
"--output",
|
||||
output_path,
|
||||
"--config",
|
||||
json.dumps(config, ensure_ascii=False),
|
||||
"--timestamp",
|
||||
"1",
|
||||
]
|
||||
env = os.environ.copy()
|
||||
env["PYTHONIOENCODING"] = "utf-8"
|
||||
try:
|
||||
r = subprocess.run(
|
||||
cmd,
|
||||
cwd=SCRIPT_DIR,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120,
|
||||
env=env,
|
||||
)
|
||||
if r.returncode != 0:
|
||||
print(r.stderr or r.stdout, file=sys.stderr)
|
||||
return r.returncode == 0 and os.path.isfile(output_path)
|
||||
except Exception as e:
|
||||
print(f"cover_generator failed: {e}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
def generate_preview(self, video_path: str, style: str, output_path: str) -> bool:
|
||||
return self._run_cover_generator(
|
||||
video_path,
|
||||
"封面预览",
|
||||
output_path,
|
||||
style,
|
||||
)
|
||||
|
||||
def generate_all_previews(self, video_path: str) -> Dict[str, Any]:
|
||||
if not os.path.exists(video_path):
|
||||
return {"error": f"Video file not found: {video_path}"}
|
||||
|
||||
video_hash = self.get_video_hash(video_path)
|
||||
if video_hash in self.preview_cache:
|
||||
return self.preview_cache[video_hash]
|
||||
|
||||
previews = []
|
||||
errors = []
|
||||
for style in self.COVER_STYLES:
|
||||
output_path = os.path.join(self.temp_dir, f"preview_{video_hash}_{style}.jpg")
|
||||
ok = self.generate_preview(video_path, style, output_path)
|
||||
if ok:
|
||||
previews.append({"style": style, "path": output_path})
|
||||
else:
|
||||
errors.append(style)
|
||||
|
||||
response = {
|
||||
"previews": previews,
|
||||
"videoHash": video_hash,
|
||||
"tempDir": self.temp_dir,
|
||||
}
|
||||
if errors:
|
||||
response["errors"] = errors
|
||||
self.preview_cache[video_hash] = response
|
||||
return response
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Generate cover style previews")
|
||||
parser.add_argument("video_path", help="Path to the video file")
|
||||
parser.add_argument("--output-dir", help="Output directory for previews")
|
||||
args = parser.parse_args()
|
||||
|
||||
if not os.path.exists(args.video_path):
|
||||
print(json.dumps({"error": f"not found: {args.video_path}"}, ensure_ascii=False))
|
||||
sys.exit(1)
|
||||
|
||||
gen = CoverPreviewGenerator()
|
||||
if args.output_dir:
|
||||
gen.temp_dir = args.output_dir
|
||||
os.makedirs(gen.temp_dir, exist_ok=True)
|
||||
|
||||
result = gen.generate_all_previews(args.video_path)
|
||||
print(json.dumps(result, ensure_ascii=False))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user