D144: 新增SVG转PNG渲染工具·解决FFmpeg无SVG解码器问题
- 新增 tools/svg-to-png.py:CairoSVG渲染工具,支持单文件/批量SVG→PNG转换 - 新增 tools/README-SVG-TO-PNG.md:工具使用文档 - 更新 ENTRY.hdlp:工具链增加 svg-to-png.py 条目 - 依赖:pip install cairosvg Pillow + brew install cairo - 路径:video-ai-system/tools/svg-to-png.py
This commit is contained in:
parent
71da21924e
commit
c80d804bf9
@ -177,6 +177,7 @@ video-ai-system/
|
||||
| video-api-adapter.js (Seedance提交) | 自研 | ✅ (preflightCheck预校验) |
|
||||
| video-composer.js (FFmpeg拼接) | 自研 | ✅ (溶解过渡·faststart) |
|
||||
| image-api-adapter.js (Seedream生图) | 自研 | ✅ (苏白人设图已生成) |
|
||||
| svg-to-png.py (SVG→PNG渲染) | 自研 | ✅ (D144·CairoSVG·解决FFmpeg无SVG解码器) |
|
||||
| MoviePy v2.x | MIT | ⏳ 待评估 |
|
||||
| WhisperX + Subaligner | MIT | ⏳ |
|
||||
| Edge-TTS (一期) → GPT-SoVITS MIT (二期) | MIT | ⏳ |
|
||||
|
||||
113
video-ai-system/tools/README-SVG-TO-PNG.md
Normal file
113
video-ai-system/tools/README-SVG-TO-PNG.md
Normal file
@ -0,0 +1,113 @@
|
||||
# SVG to PNG 渲染工具
|
||||
|
||||
## 功能
|
||||
|
||||
将 SVG 矢量图文件渲染为 PNG 位图,解决本机缺少 SVG 渲染工具、FFmpeg 无 SVG 解码器的问题。
|
||||
|
||||
## 依赖
|
||||
|
||||
- Python 3.8+
|
||||
- CairoSVG 2.9.0+
|
||||
- Pillow 12.0.0+
|
||||
- 系统 cairo 库(macOS: `brew install cairo`)
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
# 1. 安装系统 cairo 库(macOS)
|
||||
brew install cairo
|
||||
|
||||
# 2. 安装 Python 依赖
|
||||
pip install cairosvg Pillow
|
||||
```
|
||||
|
||||
## 用法
|
||||
|
||||
### 单文件转换
|
||||
|
||||
```bash
|
||||
# 基本用法(自动读取 SVG 原始尺寸)
|
||||
python tools/svg-to-png.py input.svg output.png
|
||||
|
||||
# 指定输出宽度(高度等比缩放)
|
||||
python tools/svg-to-png.py input.svg output.png --width 1920
|
||||
|
||||
# 指定输出高度(宽度等比缩放)
|
||||
python tools/svg-to-png.py input.svg output.png --height 1080
|
||||
|
||||
# 指定缩放因子(基于 SVG 原始尺寸放大/缩小)
|
||||
python tools/svg-to-png.py input.svg output.png --scale 2
|
||||
|
||||
# 透明背景
|
||||
python tools/svg-to-png.py input.svg output.png --background none
|
||||
```
|
||||
|
||||
### 批量转换
|
||||
|
||||
```bash
|
||||
# 转换目录下所有 SVG 文件
|
||||
python tools/svg-to-png.py ./svg-dir/ ./png-dir/ --batch
|
||||
|
||||
# 批量转换并指定缩放因子
|
||||
python tools/svg-to-png.py ./svg-dir/ ./png-dir/ --batch --scale 2
|
||||
```
|
||||
|
||||
### 作为模块导入
|
||||
|
||||
```python
|
||||
from tools.svg_to_png import convert_svg_to_png
|
||||
|
||||
# 转换单个文件
|
||||
convert_svg_to_png("input.svg", "output.png", scale=2)
|
||||
|
||||
# 指定宽度(高度等比)
|
||||
convert_svg_to_png("input.svg", "output.png", width=1920)
|
||||
```
|
||||
|
||||
## 路径
|
||||
|
||||
```
|
||||
video-ai-system/tools/svg-to-png.py
|
||||
```
|
||||
|
||||
## 视频处理流水线集成
|
||||
|
||||
在视频处理流水线中,使用该工具将 SVG 字幕/特效渲染为 PNG 序列,再通过 FFmpeg 合成到视频中:
|
||||
|
||||
```bash
|
||||
# 1. 渲染 SVG 字幕为 PNG 序列
|
||||
python tools/svg-to-png.py ./subtitles/ ./subtitles-png/ --batch --width 1920
|
||||
|
||||
# 2. 用 FFmpeg 将 PNG 序列合成到视频
|
||||
ffmpeg -i input.mp4 -i ./subtitles-png/frame-%04d.png -filter_complex overlay output.mp4
|
||||
```
|
||||
|
||||
## 故障排查
|
||||
|
||||
### OSError: no library called "cairo-2" was found
|
||||
|
||||
**原因**:Python 找不到系统 cairo 库。
|
||||
|
||||
**解决**:
|
||||
|
||||
```bash
|
||||
# macOS(Apple Silicon)
|
||||
export DYLD_LIBRARY_PATH="/opt/homebrew/lib:$DYLD_LIBRARY_PATH"
|
||||
|
||||
# macOS(Intel)
|
||||
export DYLD_LIBRARY_PATH="/usr/local/lib:$DYLD_LIBRARY_PATH"
|
||||
|
||||
# 然后重新运行
|
||||
python tools/svg-to-png.py input.svg output.png
|
||||
```
|
||||
|
||||
### 建议在 `~/.zshrc` 中添加:
|
||||
|
||||
```bash
|
||||
# video-ai-system SVG 渲染工具依赖
|
||||
export DYLD_LIBRARY_PATH="/opt/homebrew/lib:$DYLD_LIBRARY_PATH"
|
||||
```
|
||||
|
||||
## 更新记录
|
||||
|
||||
- 2026-06-23:初始版本,支持单文件/批量 SVG→PNG 转换
|
||||
189
video-ai-system/tools/svg-to-png.py
Normal file
189
video-ai-system/tools/svg-to-png.py
Normal file
@ -0,0 +1,189 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
SVG to PNG Renderer
|
||||
==========
|
||||
将 SVG 文件渲染为 PNG 图片。
|
||||
|
||||
依赖:
|
||||
pip install cairosvg Pillow
|
||||
|
||||
用法:
|
||||
# 基本用法(自动读取 SVG 尺寸)
|
||||
python svg-to-png.py input.svg output.png
|
||||
|
||||
# 指定输出宽度(高度等比缩放)
|
||||
python svg-to-png.py input.svg output.png --width 1920
|
||||
|
||||
# 指定输出高度(宽度等比缩放)
|
||||
python svg-to-png.py input.svg output.png --height 1080
|
||||
|
||||
# 指定缩放因子(基于 SVG 原始尺寸放大 / 缩小)
|
||||
python svg-to-png.py input.svg output.png --scale 2
|
||||
|
||||
# 批量转换目录下的所有 SVG
|
||||
python svg-to-png.py ./svg-dir/ ./png-dir/ --batch
|
||||
|
||||
# 作为模块导入
|
||||
from svg_to_png import convert_svg_to_png
|
||||
convert_svg_to_png("input.svg", "output.png", scale=2)
|
||||
|
||||
路径:
|
||||
video-ai-system/tools/svg-to-png.py
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
import cairosvg
|
||||
from PIL import Image
|
||||
except ImportError as e:
|
||||
print(f"[ERROR] 缺少依赖:{e}")
|
||||
print("请先安装:pip install cairosvg Pillow")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def convert_svg_to_png(
|
||||
svg_path: str,
|
||||
png_path: str,
|
||||
width: int = None,
|
||||
height: int = None,
|
||||
scale: float = None,
|
||||
background: str = "white",
|
||||
) -> bool:
|
||||
"""
|
||||
将单个 SVG 文件转换为 PNG。
|
||||
|
||||
:param svg_path: 输入 SVG 文件路径
|
||||
:param png_path: 输出 PNG 文件路径
|
||||
:param width: 输出宽度(px),为 None 时保留 SVG 原始宽度
|
||||
:param height: 输出高度(px),为 None 时保留 SVG 原始高度
|
||||
:param scale: 缩放因子,基于 SVG 原始尺寸等比缩放
|
||||
:param background: 背景色,默认白色;设为 None 保留透明
|
||||
:return: 是否转换成功
|
||||
"""
|
||||
if not os.path.isfile(svg_path):
|
||||
print(f"[ERROR] SVG 文件不存在:{svg_path}")
|
||||
return False
|
||||
|
||||
# 确保输出目录存在
|
||||
os.makedirs(os.path.dirname(os.path.abspath(png_path)), exist_ok=True)
|
||||
|
||||
try:
|
||||
# 构造 cairosvg 参数
|
||||
kwargs = {
|
||||
"url": svg_path,
|
||||
"write_to": png_path,
|
||||
}
|
||||
|
||||
if background:
|
||||
kwargs["background_color"] = background
|
||||
|
||||
if scale is not None:
|
||||
kwargs["scale"] = scale
|
||||
else:
|
||||
if width is not None:
|
||||
kwargs["output_width"] = width
|
||||
if height is not None:
|
||||
kwargs["output_height"] = height
|
||||
|
||||
cairosvg.svg2png(**kwargs)
|
||||
|
||||
# 验证输出文件
|
||||
if os.path.isfile(png_path):
|
||||
file_size = os.path.getsize(png_path)
|
||||
print(f"[OK] {svg_path} → {png_path} ({file_size // 1024} KB)")
|
||||
return True
|
||||
else:
|
||||
print(f"[ERROR] 输出文件未生成:{png_path}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR] 转换失败 {svg_path}:{e}")
|
||||
return False
|
||||
|
||||
|
||||
def batch_convert(
|
||||
svg_dir: str,
|
||||
png_dir: str,
|
||||
width: int = None,
|
||||
height: int = None,
|
||||
scale: float = None,
|
||||
) -> tuple:
|
||||
"""
|
||||
批量转换目录下的所有 SVG 文件。
|
||||
|
||||
:param svg_dir: 输入目录(包含 SVG 文件)
|
||||
:param png_dir: 输出目录
|
||||
:return: (成功数, 总数)
|
||||
"""
|
||||
if not os.path.isdir(svg_dir):
|
||||
print(f"[ERROR] 输入目录不存在:{svg_dir}")
|
||||
return 0, 0
|
||||
|
||||
os.makedirs(png_dir, exist_ok=True)
|
||||
|
||||
svg_files = sorted(Path(svg_dir).glob("*.svg"))
|
||||
if not svg_files:
|
||||
print(f"[WARN] 目录中没有找到 SVG 文件:{svg_dir}")
|
||||
return 0, 0
|
||||
|
||||
print(f"[INFO] 找到 {len(svg_files)} 个 SVG 文件,开始批量转换...")
|
||||
success = 0
|
||||
total = len(svg_files)
|
||||
|
||||
for svg_file in svg_files:
|
||||
png_file = os.path.join(png_dir, svg_file.stem + ".png")
|
||||
if convert_svg_to_png(
|
||||
str(svg_file), png_file,
|
||||
width=width, height=height, scale=scale
|
||||
):
|
||||
success += 1
|
||||
|
||||
print(f"\n[INFO] 批量转换完成:{success}/{total} 成功")
|
||||
return success, total
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="SVG to PNG 渲染工具(基于 CairoSVG)",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
示例:
|
||||
python svg-to-png.py input.svg output.png
|
||||
python svg-to-png.py input.svg output.png --width 1920
|
||||
python svg-to-png.py ./svg/ ./png/ --batch --scale 2
|
||||
"""
|
||||
)
|
||||
parser.add_argument("input", help="输入 SVG 文件或目录")
|
||||
parser.add_argument("output", help="输出 PNG 文件或目录")
|
||||
parser.add_argument("--width", type=int, default=None, help="输出宽度(px)")
|
||||
parser.add_argument("--height", type=int, default=None, help="输出高度(px)")
|
||||
parser.add_argument("--scale", type=float, default=None, help="缩放因子")
|
||||
parser.add_argument("--batch", action="store_true", help="批量转换模式")
|
||||
parser.add_argument("--background", default="white", help="背景色(默认 white,设为 'none' 保留透明)")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.batch or os.path.isdir(args.input):
|
||||
# 批量模式
|
||||
success, total = batch_convert(
|
||||
args.input, args.output,
|
||||
width=args.width, height=args.height, scale=args.scale
|
||||
)
|
||||
sys.exit(0 if success == total else 1)
|
||||
else:
|
||||
# 单文件模式
|
||||
bg = None if args.background.lower() == "none" else args.background
|
||||
ok = convert_svg_to_png(
|
||||
args.input, args.output,
|
||||
width=args.width, height=args.height,
|
||||
scale=args.scale, background=bg
|
||||
)
|
||||
sys.exit(0 if ok else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
x
Reference in New Issue
Block a user