In this tutorial, we’ll walk through creating QuickEbook v4.3.0, a Python desktop app for batch ebook conversion. You can convert PDFs, EPUBs, MOBIs, and AZW3 files, with drag & drop, per-format selection, and real-time logs.
💻 Download EXE:
https://github.com/rogers-cyber/QuickEbook/releases
🛠 Source Code / Clone:
git clone https://github.com/rogers-cyber/QuickEbook.git
1️⃣ Importing Required Libraries
First, we import all the Python modules needed for the app.
import os
import sys
import threading
import time
import traceback
from queue import Queue, Empty
from tkinter import filedialog, messagebox
import ttkbootstrap as tb
import tkinter as tk
from tkinterdnd2 import DND_FILES, TkinterDnD
import subprocess
import shutil
Explanation:
tkinter & ttkbootstrap: for GUI elements
tkinterdnd2: drag & drop support
subprocess, shutil: run external ebook conversion commands
threading, queue: handle background tasks without freezing the UI
2️⃣ Application Configuration
We define the app name, version, and initialize the main Tkinter window.
APP_NAME = "QuickEbook - Batch Format Selector"
APP_VERSION = "4.3.0"
app = TkinterDnD.Tk()
app.title(f"{APP_NAME} v{APP_VERSION}")
app.geometry("1150x700")
tb.Style("darkly") # Dark theme
Explanation:
TkinterDnD.Tk() enables drag & drop
ttkbootstrap.Style("darkly") gives a modern dark theme
3️⃣ Utility Functions
We create helper functions to handle file paths, logging errors, and showing “About” info.
def resource_path(file_name):
base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
return os.path.join(base_path, file_name)
def log_error():
with open("error.log", "a", encoding="utf-8") as f:
f.write(traceback.format_exc() + "\n")
def show_about():
messagebox.showinfo(
f"About {APP_NAME} v{APP_VERSION}",
f"{APP_NAME} v{APP_VERSION}\nBatch ebook converter with drag & drop, batch selection, and real-time logs.\n© 2026 Mate Technologies"
)
Explanation:
resource_path: works with PyInstaller EXE files
log_error: saves errors for debugging
show_about: displays a pop-up info box
4️⃣ Menu Bar Setup
We add a menu bar with a Help menu.
menubar = tb.Menu(app)
help_menu = tb.Menu(menubar, tearoff=0)
help_menu.add_command(label="About", command=show_about)
menubar.add_cascade(label="Help", menu=help_menu)
app.config(menu=menubar)
Explanation:
Users can view app info anytime via “Help → About”
5️⃣ Flags, Queue, and Main Labels
stop_flag = False
pause_flag = False
ui_queue = Queue()
tb.Label(app, text=APP_NAME, font=("Segoe UI", 22, "bold")).pack(pady=(10, 2))
tb.Label(
app,
text="Batch Ebook Converter – Pick target format per input batch or apply to all",
font=("Segoe UI", 10, "italic"),
foreground="#9ca3af"
).pack(pady=(0, 10))
Explanation:
stop_flag & pause_flag: control conversion
ui_queue: updates UI safely from background threads
6️⃣ Ebook Selection Frame
frame1 = tb.Labelframe(app, text="Ebook Selection & Controls", padding=10)
frame1.pack(fill="x", padx=10, pady=6)
ebook_list = []
output_path = tb.StringVar()
batch_target_formats = {}
Explanation:
A section for adding ebooks and controlling output
ebook_list stores selected files
batch_target_formats stores per-format target selection
7️⃣ Adding & Clearing Ebooks
def group_by_format(files):
groups = {}
for f in files:
ext = os.path.splitext(f)[1].lower().replace(".", "")
groups.setdefault(ext, []).append(f)
return groups
def add_ebooks():
files = filedialog.askopenfilenames(title="Select Ebooks")
if not files:
return
for f in files:
if f not in ebook_list:
ebook_list.append(f)
ui_queue.put(("add", f))
setup_batch_format_selection()
def clear_ebooks():
ebook_list.clear()
ui_queue.put(("clear", None))
batch_target_formats.clear()
for widget in batch_frame.winfo_children():
widget.destroy()
Explanation:
group_by_format: groups ebooks by extension
add_ebooks: allows file selection
clear_ebooks: clears all selections and UI elements
8️⃣ File List & Output Controls
list_frame = tb.Frame(frame1)
list_frame.pack(fill="x", pady=6)
ebook_listbox = tk.Listbox(list_frame, height=6, selectmode="extended")
ebook_listbox.pack(side="left", fill="x", expand=True)
scroll = tk.Scrollbar(list_frame, command=ebook_listbox.yview)
scroll.pack(side="right", fill="y")
ebook_listbox.config(yscrollcommand=scroll.set)
tb.Button(frame1, text="Add Ebooks", command=add_ebooks, bootstyle="success").pack(side="left", padx=4)
tb.Button(frame1, text="Clear List", command=clear_ebooks, bootstyle="danger-outline").pack(side="left", padx=4)
tb.Label(frame1, text="Output Folder:", width=13).pack(side="left", padx=(12,0))
tb.Entry(frame1, textvariable=output_path, width=40).pack(side="left", padx=6)
tb.Button(frame1, text="Browse", command=lambda: output_path.set(filedialog.askdirectory() or output_path.get())).pack(side="left", padx=4)
Explanation:
Lists ebooks with scroll support
Buttons for adding, clearing, and selecting output folder
9️⃣ Conversion Controls
convert_btn = tb.Button(frame1, text="📚 Convert All Batches", bootstyle="success")
pause_btn = tb.Button(frame1, text="⏸ Pause", bootstyle="warning-outline", state="disabled")
stop_btn = tb.Button(frame1, text="🛑 Stop", bootstyle="danger-outline", state="disabled")
convert_btn.pack(side="left", padx=10, pady=4)
pause_btn.pack(side="left", padx=10, pady=4)
stop_btn.pack(side="left", padx=10, pady=4)
Explanation:
Buttons control batch conversion
Pause and Stop are disabled initially
🔟 Batch Format Selection
batch_frame = tb.Labelframe(app, text="Batch Target Format Selection", padding=8)
batch_frame.pack(fill="x", padx=10, pady=6)
def setup_batch_format_selection():
for widget in batch_frame.winfo_children():
widget.destroy()
grouped = group_by_format(ebook_list)
for ext, files in grouped.items():
tb.Label(batch_frame, text=f"{ext.upper()} Batch ({len(files)} files):", width=25).pack(side="left", padx=6, pady=4)
var = tb.StringVar(value=format_suggestion.get(ext,"epub"))
batch_target_formats[ext] = var
tb.Combobox(batch_frame, textvariable=var, values=["epub","mobi","pdf","azw3"], width=6).pack(side="left", padx=6, pady=4)
# Auto-Apply
tb.Label(batch_frame, text="Apply one target format to all:", width=25).pack(side="left", padx=6)
apply_var = tb.StringVar(value="epub")
tb.Combobox(batch_frame, textvariable=apply_var, values=["epub","mobi","pdf","azw3"], width=6).pack(side="left", padx=4)
tb.Button(batch_frame, text="Apply to All", bootstyle="info", command=lambda: apply_to_all(apply_var.get())).pack(side="left", padx=4)
Explanation:
Assign target formats per batch
Option to apply one format to all
1️⃣1️⃣ Progress & Log Panels
# Progress
frame2 = tb.Labelframe(app, text="Progress", padding=8)
frame2.pack(fill="x", padx=10)
progress_var = tb.IntVar()
tb.Progressbar(frame2, variable=progress_var, maximum=100, length=600).pack(side="left", padx=10)
status_lbl = tb.Label(frame2, text="Status: Ready")
status_lbl.pack(side="left", padx=10)
# Log
frame3 = tb.Labelframe(app, text="Conversion Log", padding=8)
frame3.pack(fill="both", expand=True, padx=10, pady=6)
log_text = tk.Text(frame3, height=10)
log_text.pack(side="left", fill="both", expand=True)
log_scroll = tk.Scrollbar(frame3, command=log_text.yview)
log_scroll.pack(side="right", fill="y")
log_text.config(yscrollcommand=log_scroll.set, state="disabled")
Explanation:
Progress bar shows conversion percentage
Log panel shows real-time messages
1️⃣2️⃣ Conversion Logic
def convert_ebooks():
global stop_flag, pause_flag
stop_flag = pause_flag = False
convert_btn.config(state="disabled")
pause_btn.config(state="normal")
stop_btn.config(state="normal")
ebook_convert_path = shutil.which("ebook-convert")
if not ebook_convert_path:
messagebox.showerror("Error", "ebook-convert not found! Install Calibre.")
convert_btn.config(state="normal")
pause_btn.config(state="disabled")
stop_btn.config(state="disabled")
return
Explanation:
Checks for ebook-convert (Calibre)
Initializes control flags
The full conversion function includes loops per batch, progress updates, pause/stop handling, and console-suppressed subprocess.run() calls (omitted here for brevity in tutorial).
1️⃣3️⃣ Threaded UI Updates
def process_ui_queue():
try:
while True:
cmd, data = ui_queue.get_nowait()
if cmd == "add":
ebook_listbox.insert("end", data)
elif cmd == "clear":
ebook_listbox.delete(0,"end")
elif cmd == "progress":
progress_var.set(data)
elif cmd == "log":
log_text.config(state="normal")
log_text.insert("end", data+"\n")
log_text.see("end")
log_text.config(state="disabled")
elif cmd == "complete":
progress_var.set(100)
status_lbl.config(text=f"Status: {data}")
convert_btn.config(state="normal")
pause_btn.config(state="disabled")
stop_btn.config(state="disabled")
except Empty:
pass
app.after(100, process_ui_queue)
Explanation:
Thread-safe UI updates from background conversion threads
1️⃣4️⃣ Buttons & Drag & Drop
convert_btn.config(command=lambda: threading.Thread(target=convert_ebooks, daemon=True).start())
pause_btn.config(command=toggle_pause)
stop_btn.config(command=stop_conversion)
# Drag & Drop
def drop(event):
files = app.tk.splitlist(event.data)
for f in files:
if os.path.isfile(f) and f not in ebook_list:
ebook_list.append(f)
ui_queue.put(("add", f))
setup_batch_format_selection()
app.drop_target_register(DND_FILES)
app.dnd_bind('<<Drop>>', drop)
Explanation:
Start conversion in a background thread
Pause/Stop buttons control flags
Drag & Drop adds files to the list
1️⃣5️⃣ Start the Application Loop
app.after(100, process_ui_queue)
app.mainloop()
Explanation:
Starts the Tkinter event loop
Processes UI updates continuously
✅ With this tutorial, beginners can follow each step, understand the purpose of each section, and build their own QuickEbook v4.3.0 desktop application.

Top comments (0)