Utility_Apps/Svg2PngPdf/Archived/SVG to PNG-PDF Converter-C.py

313 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
svg_to_png_gui.py
A standalone Python/Tkinter app to batch-convert SVG files to PNG using Inkscape.
Key Features:
-------------
1. Modern GUI (Tkinter).
2. Multiple File Selection:
- Add individual SVG files.
- Add entire folders containing SVG files (recursive).
3. Real-time Logging:
- Each conversion step logs info in the GUI.
- Logs shown for both success and failure.
4. Progress Bar and Statistics:
- Shows progress while converting multiple files.
- At the end, displays success/failure counts.
5. Customizable:
- DPI setting.
- Background color.
6. Automatic Inkscape Detection:
- Checks common installation paths.
- Allows manual override if not found.
7. Fully Cross-Platform:
- Windows, Mac, Linux (requires installed Inkscape).
8. Easy Packaging:
- No external dependencies.
- PyInstaller can build a single .exe or .app.
Author: ChatGPT
Author: Ryan Schultz
Author: Regis Nde Tene
"""
import os
import sys
import subprocess
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
class SVGtoPNGConverter:
def __init__(self, root):
self.root = root
self.root.title("SVG to PNG Converter")
self.root.geometry("600x450")
self.root.resizable(False, False)
# -------------- Variables --------------
self.selected_files = []
self.dpi_var = tk.StringVar(value="150")
self.bg_color_var = tk.StringVar(value="white")
self.inkscape_path_var = tk.StringVar(value=self.find_inkscape_path() or "")
self.success_count = 0
self.fail_count = 0
# -------------- Main Frames --------------
self.create_top_frame()
self.create_center_frame()
self.create_bottom_frame()
def create_top_frame(self):
""" Create the top frame with add files/folders and Inkscape path. """
top_frame = ttk.Frame(self.root, padding=(10, 10))
top_frame.pack(fill=tk.X)
# Add File Button
add_file_btn = ttk.Button(top_frame, text="Add SVG File(s)", command=self.add_files)
add_file_btn.pack(side=tk.LEFT, padx=5)
# Add Folder Button
add_folder_btn = ttk.Button(top_frame, text="Add Folder", command=self.add_folder)
add_folder_btn.pack(side=tk.LEFT, padx=5)
# Inkscape Path
inkscape_label = ttk.Label(top_frame, text="Inkscape Path:")
inkscape_label.pack(side=tk.LEFT, padx=(30, 2))
inkscape_entry = ttk.Entry(top_frame, textvariable=self.inkscape_path_var, width=35)
inkscape_entry.pack(side=tk.LEFT, padx=5)
browse_inkscape_btn = ttk.Button(top_frame, text="Browse...", command=self.browse_inkscape)
browse_inkscape_btn.pack(side=tk.LEFT, padx=(0,5))
def create_center_frame(self):
""" Create the center frame with a file list, config, and logging area. """
center_frame = ttk.Frame(self.root, padding=(10, 0, 10, 10))
center_frame.pack(fill=tk.BOTH, expand=True)
# -- File List --
file_list_frame = ttk.LabelFrame(center_frame, text="Selected Files")
file_list_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10), pady=5)
self.file_listbox = tk.Listbox(file_list_frame, width=35, height=15, selectmode=tk.EXTENDED)
self.file_listbox.pack(side=tk.LEFT, fill=tk.Y)
file_list_scroll = ttk.Scrollbar(file_list_frame, orient=tk.VERTICAL, command=self.file_listbox.yview)
file_list_scroll.pack(side=tk.RIGHT, fill=tk.Y)
self.file_listbox.config(yscrollcommand=file_list_scroll.set)
# -- Settings Panel --
settings_frame = ttk.LabelFrame(center_frame, text="Conversion Settings")
settings_frame.pack(side=tk.TOP, fill=tk.X, pady=5)
# DPI
dpi_label = ttk.Label(settings_frame, text="DPI:")
dpi_label.grid(row=0, column=0, padx=5, pady=5, sticky=tk.E)
dpi_entry = ttk.Entry(settings_frame, textvariable=self.dpi_var, width=8)
dpi_entry.grid(row=0, column=1, padx=5, pady=5, sticky=tk.W)
# Background Color
bg_label = ttk.Label(settings_frame, text="Background:")
bg_label.grid(row=1, column=0, padx=5, pady=5, sticky=tk.E)
bg_entry = ttk.Entry(settings_frame, textvariable=self.bg_color_var, width=8)
bg_entry.grid(row=1, column=1, padx=5, pady=5, sticky=tk.W)
# Progress Bar
self.progress = ttk.Progressbar(settings_frame, orient='horizontal', length=180, mode='determinate')
self.progress.grid(row=0, column=2, rowspan=2, padx=30, pady=5)
# -- Logging Panel --
log_frame = ttk.LabelFrame(center_frame, text="Conversion Log")
log_frame.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True)
self.log_text = tk.Text(log_frame, height=8, width=60, state=tk.DISABLED)
self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
log_scrollbar = ttk.Scrollbar(log_frame, orient=tk.VERTICAL, command=self.log_text.yview)
log_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.log_text.config(yscrollcommand=log_scrollbar.set)
def create_bottom_frame(self):
""" Create the bottom frame with Convert, Remove, and Clear buttons. """
bottom_frame = ttk.Frame(self.root, padding=(10, 10))
bottom_frame.pack(fill=tk.X)
remove_btn = ttk.Button(bottom_frame, text="Remove Selected", command=self.remove_selected)
remove_btn.pack(side=tk.LEFT, padx=5)
clear_btn = ttk.Button(bottom_frame, text="Clear All", command=self.clear_all)
clear_btn.pack(side=tk.LEFT, padx=5)
convert_btn = ttk.Button(bottom_frame, text="Convert to PNG", command=self.start_conversion)
convert_btn.pack(side=tk.RIGHT, padx=5)
def add_files(self):
""" Open file dialog for multiple SVG files and add them. """
filepaths = filedialog.askopenfilenames(
title="Select SVG File(s)",
filetypes=[("SVG Files", "*.svg"), ("All Files", "*.*")]
)
if not filepaths:
return
for f in filepaths:
if f not in self.selected_files:
self.selected_files.append(f)
self.file_listbox.insert(tk.END, f)
def add_folder(self):
""" Open directory dialog and add all SVG files in that folder (recursively). """
folder_path = filedialog.askdirectory(title="Select Folder with SVG Files")
if not folder_path:
return
for root, dirs, files in os.walk(folder_path):
for fname in files:
if fname.lower().endswith(".svg"):
full_path = os.path.join(root, fname)
if full_path not in self.selected_files:
self.selected_files.append(full_path)
self.file_listbox.insert(tk.END, full_path)
def remove_selected(self):
""" Remove selected files from the listbox and internal list. """
selected_indices = list(self.file_listbox.curselection())
selected_indices.reverse() # Remove from the end to not mess up indices
for index in selected_indices:
self.selected_files.pop(index)
self.file_listbox.delete(index)
def clear_all(self):
""" Clear the entire list of selected files. """
self.selected_files.clear()
self.file_listbox.delete(0, tk.END)
def browse_inkscape(self):
""" Let user manually select Inkscape executable if auto-detection fails. """
path = filedialog.askopenfilename(title="Select Inkscape Executable")
if path:
self.inkscape_path_var.set(path)
def find_inkscape_path(self):
"""
Try to auto-detect Inkscape in common locations or in PATH.
Return the path if found, else None.
"""
possible_locations = []
# Windows common installations
if os.name == 'nt':
possible_locations += [
r"C:\Program Files\Inkscape\bin\inkscape.exe",
r"C:\Program Files\Inkscape\inkscape.exe",
r"C:\Program Files (x86)\Inkscape\inkscape.exe",
]
# macOS brew / Applications
possible_locations += [
"/Applications/Inkscape.app/Contents/MacOS/inkscape",
"/opt/homebrew/bin/inkscape",
"/usr/local/bin/inkscape",
"/usr/bin/inkscape",
]
# Also check PATH
path_env = os.environ.get("PATH", "")
for p in path_env.split(os.pathsep):
possible_locations.append(os.path.join(p, "inkscape"))
possible_locations.append(os.path.join(p, "inkscape.exe"))
# Return first location that exists
for loc in possible_locations:
if os.path.isfile(loc) and os.access(loc, os.X_OK):
return loc
return None
def log(self, message, level="info"):
""" Append a log message to the text box. """
self.log_text.config(state=tk.NORMAL)
self.log_text.insert(tk.END, f"{message}\n")
self.log_text.see(tk.END)
self.log_text.config(state=tk.DISABLED)
def start_conversion(self):
""" Validate input and begin conversion process. """
if not self.selected_files:
messagebox.showerror("Error", "No SVG files selected.")
return
inkscape_path = self.inkscape_path_var.get().strip()
if not inkscape_path or not os.path.exists(inkscape_path):
messagebox.showerror("Error", "Inkscape executable not found.\nPlease specify the correct path.")
return
# Check DPI
dpi_val = self.dpi_var.get().strip()
if not dpi_val.isdigit():
messagebox.showerror("Error", "DPI must be a positive integer.")
return
# Lets do conversions in the main thread (simple approach).
# For big conversions, you might want to use threading or multiprocessing.
self.success_count = 0
self.fail_count = 0
self.log("Starting conversion...")
self.progress['value'] = 0
total_files = len(self.selected_files)
increment = 100 / total_files
for i, svg_file in enumerate(self.selected_files, start=1):
# Try converting
result = self.convert_file(
inkscape_path=inkscape_path,
svg_file=svg_file,
dpi=dpi_val,
bg_color=self.bg_color_var.get().strip(),
)
# Update progress bar
self.progress['value'] += increment
self.root.update_idletasks()
# Final stats
self.log(f"\nConversion finished. Success: {self.success_count}, Fail: {self.fail_count}")
self.progress['value'] = 100
def convert_file(self, inkscape_path, svg_file, dpi, bg_color):
""" Convert one SVG file to PNG using Inkscape. """
base, ext = os.path.splitext(svg_file)
output_png = base + ".png"
msg = f"Converting '{svg_file}' -> '{output_png}'"
self.log(msg)
cmd = [
inkscape_path,
svg_file,
"--export-type=png",
f"--export-filename={output_png}",
f"--export-dpi={dpi}",
f"--export-background={bg_color}"
]
try:
subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if os.path.exists(output_png):
self.log(" [OK] Conversion successful.")
self.success_count += 1
return True
else:
self.log(" [ERROR] Output PNG not found after conversion.", level="error")
self.fail_count += 1
return False
except subprocess.CalledProcessError as e:
self.log(f" [ERROR] Conversion failed for '{svg_file}'\n {e}", level="error")
self.fail_count += 1
return False
def main():
root = tk.Tk()
app = SVGtoPNGConverter(root)
root.mainloop()
if __name__ == "__main__":
main()