DEV Community

Mate Technologies
Mate Technologies

Posted on

QuickEbook – Batch Ebook Converter Tutorial

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
    )
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

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 = {}
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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)