innoai commited on
Commit
f2690e7
·
verified ·
1 Parent(s): 82a2134

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +102 -115
app.py CHANGED
@@ -1,141 +1,128 @@
1
  #!/usr/bin/env python
2
  # -*- coding: utf-8 -*-
3
  """
4
- SVG → PNG 在线批量转换(Inkscape 版)
5
  作者: ChatGPT 示例
6
- 运行环境: Python 3.9+ / Gradio 5.x / Inkscape ≥1.0
7
  """
8
 
9
- import os
10
- import shutil
11
- import subprocess
12
- import tempfile
13
- import zipfile
14
  from pathlib import Path
15
  from typing import List, Tuple
 
16
 
17
- import gradio as gr
18
- import cairosvg # 纯 Python 兜底渲染
19
 
20
- # --------------------------- 全局配置 ---------------------------
21
- DEFAULT_DPI = 300 # 默认导出分辨率 (DPI)
 
 
22
 
23
- # --------------------------- 工具函数 ---------------------------
24
- def find_inkscape() -> str:
25
- """在 PATH 中查找 inkscape 可执行文件;找不到则抛异常。"""
26
- exe = shutil.which("inkscape")
27
- if exe:
28
- return exe
29
- raise FileNotFoundError(
30
- "未检测到 Inkscape,请确认 packages.txt 已包含 `inkscape`,"
31
- "并在 Spaces 构建日志中检查安装是否成功。"
32
- )
33
 
34
-
35
- INKSCAPE = None # 运行期检测并缓存路径
36
-
37
-
38
- def svg_to_png(svg_path: Path, out_path: Path, dpi: int = DEFAULT_DPI):
39
  """
40
- Inkscape CLI 为主,将 SVG 转成 PNG
41
- - 优先使用新 CLI(--export-type);失败后自动尝试旧 CLI(--export-png)
42
- - 若两者均失败,则回退到 CairoSVG。
43
  """
44
- global INKSCAPE
45
- if INKSCAPE is None: # 首次调用时初始化
46
- INKSCAPE = find_inkscape()
47
-
48
- # Inkscape ≥1.0 推荐写法
49
- cmd_new = [
50
- INKSCAPE,
51
- str(svg_path),
52
- "--export-type=png",
53
- f"--export-filename={out_path}",
54
- f"--export-dpi={dpi}",
55
- ]
56
 
57
- try:
58
- subprocess.run(cmd_new, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
59
- except subprocess.CalledProcessError as e_new:
60
- # 兼容极旧版 (<1.0) 参数:--export-png
61
- cmd_old = [
62
- INKSCAPE,
63
- str(svg_path),
64
- f"--export-png={out_path}",
65
- f"-d={dpi}",
66
- ]
 
 
 
 
 
 
 
 
67
  try:
68
- subprocess.run(cmd_old, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
69
- except subprocess.CalledProcessError as e_old:
70
- # Inkscape 全部失败 ➜ 回退 CairoSVG
 
 
 
 
 
 
71
  try:
72
- cairosvg.svg2png(url=str(svg_path), write_to=str(out_path), dpi=dpi)
73
- except Exception as cs_err:
74
- # 抛出详细错误,方便前端提示
75
- raise RuntimeError(
76
- f"Inkscape 新旧命令均失败:\n{e_new.stderr.decode(errors='ignore')}\n"
77
- f"{e_old.stderr.decode(errors='ignore')}\n"
78
- f"CairoSVG 也失败:{cs_err}"
79
- ) from cs_err
80
-
81
-
82
- def batch_convert(files: List[gr.File], dpi: int = DEFAULT_DPI) -> Tuple[List[Tuple[str, str]], str]:
83
- """Gradio 回调:批量转换并打包下载。"""
 
 
 
 
 
 
 
 
84
  if not files:
85
  raise gr.Error("请先上传至少一个 SVG 文件!")
86
 
87
- tmp_dir = Path(tempfile.mkdtemp(prefix="svg2png_"))
88
- png_dir = tmp_dir / "png"
89
- png_dir.mkdir(exist_ok=True)
90
 
91
- gallery_items = []
92
  for f in files:
93
- svg_path = Path(f.name)
94
- out_path = png_dir / f"{svg_path.stem}.png"
95
- svg_to_png(svg_path, out_path, dpi)
96
- # Gallery 需要 (标题, 路径)
97
- gallery_items.append((out_path.name, str(out_path)))
98
-
99
- # 打包 ZIP
100
- zip_path = tmp_dir / "result.zip"
101
- with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
102
- for p in png_dir.iterdir():
103
- zf.write(p, arcname=p.name)
104
-
105
- return gallery_items, str(zip_path)
106
-
107
- # --------------------------- Gradio UI ---------------------------
108
- with gr.Blocks(title="SVG → PNG (Inkscape 版)", theme="soft") as demo:
109
- gr.Markdown(
110
- """
111
- # 🌈 SVG → PNG 在线转换器(Inkscape 引擎)
112
- - **批量上传** + **DPI 可调** + **透明背景**
113
- - 首选 Inkscape CLI,高质量矢量光栅化
114
- - 若 Inkscape 不可用自动回退 CairoSVG
115
- """
116
- )
117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  with gr.Row():
119
- uploader = gr.File(
120
- label="上传 SVG(可多选)", file_count="multiple", file_types=[".svg"]
121
- )
122
- dpi_slider = gr.Slider(
123
- 72, 600, step=1, value=DEFAULT_DPI,
124
- label="输出 DPI", info="数值越大越清晰,文件体积也越大"
125
- )
126
-
127
- convert_btn = gr.Button("🚀 开始转换")
128
- gallery = gr.Gallery(label="PNG 预览", show_label=True, height="auto")
129
- zip_file = gr.File(label="下载全部 PNG (ZIP)")
130
-
131
- convert_btn.click(
132
- batch_convert,
133
- inputs=[uploader, dpi_slider],
134
- outputs=[gallery, zip_file],
135
- api_name="convert",
136
- queue=True,
137
- )
138
 
139
  if __name__ == "__main__":
140
- # HF Spaces 中启动时会忽略 share 参数;本地调试可设 share=True
141
- demo.launch(share=False)
 
1
  #!/usr/bin/env python
2
  # -*- coding: utf-8 -*-
3
  """
4
+ SVG → PNG 批量转换(rsvg-convert ▸ Inkscape ▸ CairoSVG 三层保险版)
5
  作者: ChatGPT 示例
 
6
  """
7
 
8
+ import shutil, subprocess, tempfile, zipfile, gradio as gr
 
 
 
 
9
  from pathlib import Path
10
  from typing import List, Tuple
11
+ import cairosvg # 兜底
12
 
13
+ DEFAULT_DPI = 300 # 默认分辨率
 
14
 
15
+ # ───────────────────────── 工具检测 ─────────────────────────
16
+ def which(cmd: str) -> str | None:
17
+ """返回可执行文件完整路径,找不到则 None"""
18
+ return shutil.which(cmd)
19
 
20
+ RSVG = which("rsvg-convert")
21
+ INKSCAPE = which("inkscape")
 
 
 
 
 
 
 
 
22
 
23
+ # ──────────────────────── 渲染核心函数 ───────────────────────
24
+ def svg_to_png(svg: Path, png: Path, dpi: int = DEFAULT_DPI):
 
 
 
25
  """
26
+ 依次尝试 rsvg-convert Inkscape CairoSVG
27
+ 任一成功即返回;全部失败则抛 RuntimeError
 
28
  """
29
+ errs = []
 
 
 
 
 
 
 
 
 
 
 
30
 
31
+ # 1) rsvg-convert(最快最稳,支持大部分特性)
32
+ if RSVG:
33
+ cmd = [RSVG, "-d", str(dpi), "-p", str(dpi), "-f", "png",
34
+ "-o", str(png), str(svg)]
35
+ try:
36
+ subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
37
+ if png.stat().st_size > 2048: # 简单判空
38
+ return
39
+ except subprocess.CalledProcessError as e:
40
+ errs.append(f"[rsvg-convert] {e.stderr.decode(errors='ignore')}")
41
+
42
+ # 2) Inkscape CLI(加 export-area-drawing 防止空白)
43
+ if INKSCAPE:
44
+ cmd_new = [INKSCAPE, str(svg),
45
+ "--export-type=png",
46
+ f"--export-filename={png}",
47
+ f"--export-dpi={dpi}",
48
+ "--export-area-drawing"] # 关键参数
49
  try:
50
+ subprocess.run(cmd_new, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
51
+ if png.stat().st_size > 2048:
52
+ return
53
+ except subprocess.CalledProcessError as e_new:
54
+ # 极老版本 Inkscape (<1.0) 回退旧参数
55
+ cmd_old = [INKSCAPE, str(svg),
56
+ f"--export-png={png}",
57
+ f"-d={dpi}",
58
+ "--export-area-drawing"]
59
  try:
60
+ subprocess.run(cmd_old, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
61
+ if png.stat().st_size > 2048:
62
+ return
63
+ except subprocess.CalledProcessError as e_old:
64
+ errs.append(f"[Inkscape] {e_new.stderr.decode(errors='ignore')}\n"
65
+ f"[Inkscape-old] {e_old.stderr.decode(errors='ignore')}")
66
+
67
+ # 3) CairoSVG 兜底
68
+ try:
69
+ cairosvg.svg2png(url=str(svg), write_to=str(png), dpi=dpi)
70
+ if png.stat().st_size > 2048:
71
+ return
72
+ except Exception as cs_err:
73
+ errs.append(f"[CairoSVG] {cs_err}")
74
+
75
+ raise RuntimeError("三种渲染器均失败:\n" + "\n".join(errs))
76
+
77
+ # ───────────────────────── Gradio 回调 ───────────────────────
78
+ def batch_convert(files: List[gr.File], dpi: int = DEFAULT_DPI
79
+ ) -> Tuple[List[Tuple[str, str]], str]:
80
  if not files:
81
  raise gr.Error("请先上传至少一个 SVG 文件!")
82
 
83
+ tmp = Path(tempfile.mkdtemp(prefix="svg2png_"))
84
+ out_dir = tmp / "png"; out_dir.mkdir()
 
85
 
86
+ gallery, errs = [], []
87
  for f in files:
88
+ svg = Path(f.name)
89
+ out = out_dir / f"{svg.stem}.png"
90
+ try:
91
+ svg_to_png(svg, out, dpi)
92
+ gallery.append((out.name, str(out)))
93
+ except Exception as e:
94
+ errs.append(f"{svg.name} 转换失败:{e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
+ # 打包 zip
97
+ zip_path = tmp / "result.zip"
98
+ with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
99
+ for p in out_dir.iterdir():
100
+ zf.write(p, p.name)
101
+
102
+ if errs:
103
+ # 收敛为单条 Warning,避免打断流程
104
+ gr.Warning("\n".join(errs))
105
+
106
+ return gallery, str(zip_path)
107
+
108
+ # ────────────────────────── Gradio UI ────────────────────────
109
+ with gr.Blocks(title="SVG → PNG 在线转换", theme="soft") as demo:
110
+ gr.Markdown("""
111
+ # 🖼️ SVG → PNG 转换器
112
+ - **rsvg-convert ▸ Inkscape ▸ CairoSVG** 三层保险
113
+ - 支持批量上传,DPI 可调,默认导出实际绘图区域
114
+ """)
115
  with gr.Row():
116
+ uploader = gr.File(file_count="multiple", file_types=[".svg"],
117
+ label="上传 SVG 文件(可多选)")
118
+ dpi_slider = gr.Slider(72, 600, value=DEFAULT_DPI, step=1,
119
+ label="输出 DPI", info="分辨率越高越清晰")
120
+ btn = gr.Button("🚀 开始转换")
121
+ gallery = gr.Gallery(label="PNG 预览")
122
+ zip_out = gr.File(label="下载全部 PNG (ZIP)")
123
+
124
+ btn.click(batch_convert, [uploader, dpi_slider], [gallery, zip_out],
125
+ api_name="convert", queue=True)
 
 
 
 
 
 
 
 
 
126
 
127
  if __name__ == "__main__":
128
+ demo.launch(share=False) # 本地调试可改 True