innoai commited on
Commit
f97f2ac
·
verified ·
1 Parent(s): 4dad4a9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +81 -94
app.py CHANGED
@@ -1,154 +1,141 @@
1
  #!/usr/bin/env python
2
  # -*- coding: utf-8 -*-
3
  """
4
- SVG → PNG 在线批量转换(Gradio + ImageMagick 版)
5
  作者: ChatGPT 示例
6
- 环境: Python 3.9+ / Gradio 5.x / ImageMagick 6 或 7
7
  """
8
 
9
  import os
10
  import shutil
11
  import subprocess
12
- import sys
13
  import tempfile
14
  import zipfile
15
  from pathlib import Path
16
  from typing import List, Tuple
17
 
18
- import gradio as gr # pip install gradio>=5.0
19
-
20
- # ----------------------------------------------------------------------
21
- # 全局配置
22
- # ----------------------------------------------------------------------
23
- # 默认 DPI(决定 PNG 清晰度),与 Inkscape/浏览器相同:96
24
- DEFAULT_DPI = 300
25
- # ImageMagick 可执行文件名字 (Magick v7 用 "magick",v6 用 "convert")
26
- IMAGEMAGICK_CMD_CANDIDATES = ["magick", "convert"]
27
-
28
- # ----------------------------------------------------------------------
29
- # 辅助函数
30
- # ----------------------------------------------------------------------
31
- def find_imagemagick() -> str:
32
- """
33
- 检测系统中的 ImageMagick 可执行文件名称,并返回绝对路径或命令名。
34
- 若未找到则抛出异常。
35
- """
36
- for cmd in IMAGEMAGICK_CMD_CANDIDATES:
37
- if shutil.which(cmd):
38
- return cmd
39
- raise RuntimeError(
40
- "未检测到 ImageMagick。请确认已安装并将可执行文件加入 PATH。"
41
  )
42
 
43
 
44
- IM_CMD = find_imagemagick()
45
 
46
 
47
- def svg_to_png(svg_path: Path, out_path: Path, dpi: int = DEFAULT_DPI) -> None:
48
  """
49
- 调用 ImageMagick 将单个 SVG 转换为 PNG。
50
- 参数:
51
- svg_path: SVG 文件路径
52
- out_path: 目标 PNG 路径
53
- dpi: 渲染密度 (DPI),数值越大 PNG 越清晰,体积也越大
54
  """
55
- # ImageMagick 标准命令: magick -background none -density <dpi> input.svg PNG32:output.png
56
- command = [
57
- IM_CMD,
58
- "-background",
59
- "none", # 保留透明度&#8203;:contentReference[oaicite:6]{index=6}
60
- "-density",
61
- str(dpi), # 设置渲染 DPI
62
  str(svg_path),
63
- "PNG32:" + str(out_path), # PNG32 强制包含 alpha 通道
 
 
64
  ]
65
 
66
- # Windows 上若使用 magick,需要子命令 convert;直接传 PNG32:output 同样有效
67
  try:
68
- subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
69
- except subprocess.CalledProcessError as e:
70
- raise RuntimeError(
71
- f"转换失败:{svg_path.name}\n\n"
72
- f"ImageMagick 输出:\n{e.stderr.decode('utf-8', errors='ignore')}"
73
- ) from e
74
-
75
-
76
- def batch_convert(
77
- files: List[gr.File], dpi: int = DEFAULT_DPI
78
- ) -> Tuple[List[Tuple[str, str]], str]:
79
- """
80
- Gradio 回调函数:批量转换上传的 SVG。
81
- 返回:
82
- 1. [(PNG文件名, PNG路径)] 供 Gallery 展示
83
- 2. ZIP 文件路径 供用户下载
84
- """
 
 
 
 
 
 
 
 
 
85
  if not files:
86
- raise gr.Error("请先上传至少一个 SVG 文件。")
87
 
88
  tmp_dir = Path(tempfile.mkdtemp(prefix="svg2png_"))
89
  png_dir = tmp_dir / "png"
90
  png_dir.mkdir(exist_ok=True)
91
 
92
  gallery_items = []
93
- for file_obj in files:
94
- svg_path = Path(file_obj.name)
95
- out_path = png_dir / (svg_path.stem + ".png")
96
  svg_to_png(svg_path, out_path, dpi)
97
- # Gallery 接收 (display_name, file_path)
98
  gallery_items.append((out_path.name, str(out_path)))
99
 
100
- # 将所有 PNG 打包成 zip
101
  zip_path = tmp_dir / "result.zip"
102
  with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
103
- for png_item in png_dir.iterdir():
104
- zf.write(png_item, arcname=png_item.name)
105
 
106
  return gallery_items, str(zip_path)
107
 
108
-
109
- # ----------------------------------------------------------------------
110
- # Gradio UI
111
- # ----------------------------------------------------------------------
112
- with gr.Blocks(title="SVG 转 PNG 在线工具", theme="soft") as demo:
113
  gr.Markdown(
114
  """
115
- # 🌟 SVG → PNG 转换器
116
- - **支持批量上传**,透明背景,DPI 可调
117
- - 使用 **ImageMagick** 后端渲染,品质可靠
118
- - 点击「开始转换」后,可在下方画廊预览,也可整体打包下载
119
  """
120
  )
121
 
122
  with gr.Row():
123
  uploader = gr.File(
124
- label="上传 SVG(可多选)",
125
- file_count="multiple",
126
- file_types=[".svg"],
127
  )
128
  dpi_slider = gr.Slider(
129
- minimum=72,
130
- maximum=600,
131
- step=1,
132
- value=DEFAULT_DPI,
133
- label="输出 DPI(分辨率)",
134
- info="数值越大越清晰,文件体积越大",
135
  )
 
136
  convert_btn = gr.Button("🚀 开始转换")
137
- out_gallery = gr.Gallery(label="PNG 结果预览", show_label=True, height="auto")
138
- download_zip = gr.File(label="下载全部 PNG (ZIP)")
139
 
140
  convert_btn.click(
141
- fn=batch_convert,
142
  inputs=[uploader, dpi_slider],
143
- outputs=[out_gallery, download_zip],
144
  api_name="convert",
145
  queue=True,
146
  )
147
 
148
- # ----------------------------------------------------------------------
149
- # 入口
150
- # ----------------------------------------------------------------------
151
  if __name__ == "__main__":
152
- # 在本地启动: python svg2png_app.py
153
- # 部署到 Hugging Face Spaces / Docker 时,可根据需要修改 host/port
154
  demo.launch(share=False)
 
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)