Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,40 @@ The application also supports converting multiple files at once in Batch Mode.
3. **Manage Tasks**: You can add multiple tasks to the list. If you need to remove a task, select it from the list and click "Delete Task".
4. **Start Batch Conversion**: Once all your tasks are added, click "Start Batch Conversion". The application will process each task sequentially. A log will show the progress for each file.

## For macOS Users: Running from Source

Since there is no pre-built macOS release, you can run the application from source. Python 3.10–3.12 is required (PaddlePaddle does not yet support 3.13+).

1. **Install Python 3.12 and tkinter** (via [Homebrew](https://brew.sh)):
```bash
brew install python@3.12 python-tk@3.12
```

2. **Clone the repository**:
```bash
git clone https://github.com/JuniverseCoder/MinerU2PPT.git
cd MinerU2PPT
```

3. **Create a virtual environment and install dependencies**:
```bash
/opt/homebrew/bin/python3.12 -m venv venv
source venv/bin/activate
pip install -r requirements-cpu.txt
```

4. **Run the application**:
- **GUI**:
```bash
python gui.py
```
- **CLI**:
```bash
python main.py --json <path_to_json> --input <path_to_pdf_or_image> --output <output.pptx>
```

> **Note:** Drag-and-drop may not work on macOS with Tcl/Tk 9.x (the default from Homebrew). Use the "Browse..." buttons instead.

## For Developers

This section provides instructions for running the application from source and packaging it for distribution.
Expand Down
57 changes: 46 additions & 11 deletions gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,16 @@
import os
import shutil
import webbrowser
import subprocess
import platform
import locale
from tkinterdnd2 import DND_FILES, TkinterDnD
try:
from tkinterdnd2 import DND_FILES, TkinterDnD
except Exception:
DND_FILES = None
TkinterDnD = None

_HAS_DND = False # resolved at App.__init__ time
from converter.generator import convert_mineru_to_ppt, TEXT_CLEANUP_MARGIN_RATIO
from converter.ocr_merge import OCR_FONT_DISTANCE_THRESHOLD, OCR_FONT_DISTANCE_THRESHOLD_LITE

Expand Down Expand Up @@ -185,15 +193,17 @@ def _create_widgets(self):
tk.Label(frame, text=self.i18n['input_file_label']).grid(row=0, column=0, sticky="w", pady=5)
input_entry = tk.Entry(frame, textvariable=self.input_path)
input_entry.grid(row=0, column=1, sticky="ew", padx=5)
input_entry.drop_target_register(DND_FILES)
input_entry.dnd_bind('<<Drop>>', lambda e: self._on_drop(e, self.input_path))
if _HAS_DND:
input_entry.drop_target_register(DND_FILES)
input_entry.dnd_bind('<<Drop>>', lambda e: self._on_drop(e, self.input_path))
tk.Button(frame, text=self.i18n['browse_button'], command=self._browse_input).grid(row=0, column=2, padx=5)

tk.Label(frame, text=self.i18n['json_file_label']).grid(row=1, column=0, sticky="w", pady=5)
json_entry = tk.Entry(frame, textvariable=self.json_path)
json_entry.grid(row=1, column=1, sticky="ew", padx=5)
json_entry.drop_target_register(DND_FILES)
json_entry.dnd_bind('<<Drop>>', lambda e: self._on_drop(e, self.json_path))
if _HAS_DND:
json_entry.drop_target_register(DND_FILES)
json_entry.dnd_bind('<<Drop>>', lambda e: self._on_drop(e, self.json_path))
tk.Button(frame, text=self.i18n['browse_button'], command=self._browse_json).grid(row=1, column=2, padx=5)

tk.Label(frame, text=self.i18n['output_file_label']).grid(row=2, column=0, sticky="w", pady=5)
Expand Down Expand Up @@ -338,9 +348,20 @@ def _toggle_ocr_advanced(self):
else:
frame.grid_remove()

class App(TkinterDnD.Tk):
class App(tk.Tk):
def __init__(self):
super().__init__()
global _HAS_DND
if TkinterDnD is not None:
try:
# Try to initialize with drag-and-drop support
TkinterDnD.Tk.__init__(self)
_HAS_DND = True
except Exception:
tk.Tk.__init__(self)
_HAS_DND = False
else:
tk.Tk.__init__(self)
_HAS_DND = False
self.i18n = TRANSLATIONS[get_language()]
self.title(self.i18n['app_title'])
self.geometry("700x600")
Expand Down Expand Up @@ -385,13 +406,15 @@ def _create_widgets(self):
tk.Label(self.single_mode_frame, text=self.i18n['input_file_label']).grid(row=0, column=0, sticky="w", pady=2)
input_entry = tk.Entry(self.single_mode_frame, textvariable=self.input_path, state="readonly")
input_entry.grid(row=0, column=1, sticky="ew", padx=5)
input_entry.drop_target_register(DND_FILES); input_entry.dnd_bind('<<Drop>>', lambda e: self._on_drop(e, self.input_path))
if _HAS_DND:
input_entry.drop_target_register(DND_FILES); input_entry.dnd_bind('<<Drop>>', lambda e: self._on_drop(e, self.input_path))
tk.Button(self.single_mode_frame, text=self.i18n['browse_button'], command=self._browse_input).grid(row=0, column=2, sticky="w")

tk.Label(self.single_mode_frame, text=self.i18n['json_file_label']).grid(row=1, column=0, sticky="w", pady=2)
json_entry = tk.Entry(self.single_mode_frame, textvariable=self.json_path, state="readonly")
json_entry.grid(row=1, column=1, sticky="ew", padx=5)
json_entry.drop_target_register(DND_FILES); json_entry.dnd_bind('<<Drop>>', lambda e: self._on_drop(e, self.json_path))
if _HAS_DND:
json_entry.drop_target_register(DND_FILES); json_entry.dnd_bind('<<Drop>>', lambda e: self._on_drop(e, self.json_path))
json_buttons_frame = tk.Frame(self.single_mode_frame)
json_buttons_frame.grid(row=1, column=2, sticky="w")
tk.Button(json_buttons_frame, text=self.i18n['browse_button'], command=self._browse_json).pack(side=tk.LEFT)
Expand Down Expand Up @@ -600,11 +623,23 @@ def _open_output_folder(self):
if not output_file:
messagebox.showinfo(self.i18n['info_title'], self.i18n['info_no_output']); return
output_dir = os.path.dirname(output_file)
if os.path.exists(output_dir): os.startfile(output_dir)
if os.path.exists(output_dir):
if platform.system() == "Darwin":
subprocess.Popen(["open", output_dir])
elif platform.system() == "Windows":
os.startfile(output_dir)
else:
subprocess.Popen(["xdg-open", output_dir])
else: messagebox.showerror(self.i18n['error_title'], self.i18n['error_dir_not_found'].format(output_dir))

def _open_debug_folder(self):
if os.path.exists(self.debug_folder_path): os.startfile(self.debug_folder_path)
if os.path.exists(self.debug_folder_path):
if platform.system() == "Darwin":
subprocess.Popen(["open", self.debug_folder_path])
elif platform.system() == "Windows":
os.startfile(self.debug_folder_path)
else:
subprocess.Popen(["xdg-open", self.debug_folder_path])
else: messagebox.showinfo(self.i18n['info_title'], self.i18n['info_debug_not_found'])

def _set_default_output_path(self, in_path):
Expand Down