PDF to MOBI · 5 min read · May 17, 2026

PDF to MOBI in Python: A Clean Conversion Workflow

Turning a PDF into a MOBI file in Python sounds simple at first. In reality, it is one of those tasks that looks easy on paper and becomes interesting the moment you try to preserve formatting, headings, images, paragraph spacing, and readable flow.

HA

Hassan Agmir

Author at Filenewer

Share:
PDF to MOBI in Python: A Clean Conversion Workflow

Turning a PDF into a MOBI file in Python sounds simple at first. In reality, it is one of those tasks that looks easy on paper and becomes interesting the moment you try to preserve formatting, headings, images, paragraph spacing, and readable flow. PDFs are designed to look fixed and identical everywhere. MOBI files, on the other hand, are designed for reflowable reading on e-readers. That means you are not just changing one file extension into another. You are translating a page-based document into a reading experience that must adapt to screen size, font settings, and Kindle-style behavior.

That difference is the heart of this tutorial.

If you have ever tried to feed a PDF directly into an e-reader, you already know the problem. Some PDFs display beautifully on a computer monitor but feel cramped on a Kindle device. Others contain scanned pages, multi-column layouts, headers and footers, embedded fonts, or decorative spacing that make direct conversion messy. A good conversion workflow does not pretend those issues do not exist. Instead, it handles them carefully, step by step.

In this article, we will build a Python-based approach for converting PDF files into MOBI files in a way that is practical, understandable, and useful in real projects. We will cover the conversion strategy, the libraries you can use, the limitations you should expect, and a complete code example that you can adapt for your own files.

This is not just about getting a file to “work.” It is about getting a file to read well.

Why PDF to MOBI Is Not a Trivial Conversion

A PDF is usually a fixed-layout document. Every page has a defined size, and text, images, and drawings are placed at exact coordinates. This makes PDFs excellent for print-like consistency. They are great for reports, brochures, manuals, contracts, and anything that needs a stable visual layout.

MOBI is different. It is meant for reflowable content, which means the text can adapt to the screen. Line breaks are not sacred. Page sizes are not fixed in the same way. The goal is readability, not visual rigidity. This is why a direct PDF-to-MOBI conversion can produce poor results if you do not plan carefully.

The biggest challenge is that PDFs often contain structure that is visual rather than semantic. A human can easily look at a page and understand which line is a heading, which paragraph belongs to the introduction, and which figure should come next. A computer has to infer all of that. When the PDF is simple and text-based, conversion can be fairly smooth. When the PDF is complex, scanned, or heavily formatted, the output needs cleanup.

That is why a good workflow usually follows this idea:

  1. Extract text and images from the PDF.

  2. Reconstruct the content into a more semantic format such as HTML or EPUB.

  3. Convert that intermediate file into MOBI.

This is much more reliable than trying to force a PDF straight into MOBI with no preparation.

What You Need Before You Start

Before writing code, it helps to understand the tools involved.

Python alone can extract text from a PDF, but generating a high-quality MOBI file is easier when Python works together with a conversion tool such as Calibre. Calibre is widely used for ebook management and conversion. One of its most useful command-line tools is ebook-convert, which can convert EPUB files into MOBI and many other formats.

A practical stack often includes:

  • pypdf or pdfplumber for extracting text from PDF files

  • Pillow for image handling, if needed

  • ebooklib for creating EPUB files in Python

  • Calibre’s ebook-convert command for final MOBI conversion

There are other possible combinations, of course. Some people use OCR tools for scanned PDFs. Others create HTML directly and then convert it. The point is not to chase the “one true library.” The point is to create a workflow that is predictable and maintainable.

When This Method Works Best

A Python conversion workflow works best when the PDF is:

  • text-based, not scanned

  • relatively clean and well structured

  • made of chapters, headings, and paragraphs

  • not too dependent on precise page positioning

  • intended for reading on an e-reader rather than print fidelity

It still can work for more complex PDFs, but the more layout-heavy the document is, the more cleanup you will need.

For example, a textbook with tables, sidebars, footnotes, and multi-column pages will require more processing than a novel or a report with plain chapters. A scanned document may need OCR before text extraction even becomes useful. A brochure with artistic layout may convert into awkward reading order unless you intervene manually.

So the best mindset is this: use automation for the heavy lifting, then clean the output where needed.

The Overall Workflow

Here is the general workflow we will use in this article:

  • Read the PDF page by page.

  • Extract text content.

  • Clean up line breaks and spacing.

  • Build an EPUB file from the extracted text.

  • Convert the EPUB into MOBI using Calibre.

Why EPUB first? Because EPUB is a much better intermediate format for ebook generation. It is structured, flexible, and easier to generate programmatically. MOBI is then created as the final Kindle-compatible output.

This approach is much more practical than trying to create MOBI directly in Python from scratch.

Installing the Required Tools

You will need Python packages and, ideally, Calibre installed on your system.

Install the Python libraries:

pip install pypdf ebooklib

If you want better text cleanup or chapter detection, you may also use:

pip install pdfplumber beautifulsoup4 lxml

For the final conversion step, install Calibre. On many systems, this gives you the ebook-convert command.

On macOS, you can install Calibre from the official package.

On Linux, you may use your package manager or the official installer.

On Windows, the standard Calibre installer is usually the easiest route.

Once installed, check whether the command is available:

ebook-convert --version

If that works, the final conversion step will be possible from Python using subprocess.

A Simple Strategy for Text Extraction

Extracting text from PDFs is often the most important part of the process. If the extracted text is messy, the ebook will be messy too.

A good first pass is to extract one page at a time and combine the results. That gives you more control than trying to process the whole file at once. It also makes it easier to detect page breaks and section boundaries.

Here is a simple example using pypdf:

from pypdf import PdfReader

def extract_text_from_pdf(pdf_path):
    reader = PdfReader(pdf_path)
    pages_text = []

    for page_number, page in enumerate(reader.pages, start=1):
        text = page.extract_text()
        if text:
            pages_text.append((page_number, text))
        else:
            pages_text.append((page_number, ""))

    return pages_text

This is not yet enough for a polished ebook, but it gives you the raw material. From here, you can clean the text and turn it into chapters or sections.

Cleaning PDF Text for Ebook Reading

Raw PDF text often contains awkward line breaks. Some PDFs break lines at every visual line, which is disastrous for ebook reading because paragraphs become chopped into short fragments. Sometimes words are split by hyphenation at the end of lines. Sometimes there are repeated headers, footers, page numbers, or odd spacing.

That is why cleanup matters so much.

A simple cleanup function might:

  • remove repeated spaces

  • join broken lines

  • remove empty page fragments

  • normalize newlines

  • optionally detect headings

Here is an example of a basic cleanup function:

import re

def clean_pdf_text(text):
    if not text:
        return ""

    # Normalize line endings
    text = text.replace("\r\n", "\n").replace("\r", "\n")

    # Remove common hyphenation at line breaks
    text = re.sub(r'-\n([a-z])', r'\1', text)

    # Join single line breaks inside paragraphs
    text = re.sub(r'(?<!\n)\n(?!\n)', ' ', text)

    # Collapse multiple spaces
    text = re.sub(r'[ \t]+', ' ', text)

    # Collapse too many blank lines
    text = re.sub(r'\n{3,}', '\n\n', text)

    return text.strip()

This is a general-purpose cleanup function. It will not be perfect for every PDF, but it works well enough for many text-based documents. If your PDF has strong structure, you can refine it further.

Why EPUB Is the Best Intermediate Format

Creating MOBI directly is possible in some workflows, but EPUB is usually the better bridge. EPUB files are built from HTML-like content packaged in a standard structure. That makes them easier to generate programmatically. You can control chapters, headings, paragraphs, images, and metadata much more naturally.

Once the EPUB looks good, converting it to MOBI is straightforward with ebook-convert.

This gives you a powerful advantage: you can inspect and debug the EPUB before generating the final Kindle-compatible file. If the EPUB is broken, you can fix the issue before the last step.

That is a huge relief compared with trying to troubleshoot a final MOBI file with no intermediate structure.

Building an EPUB in Python

The ebooklib library makes EPUB creation easier. It lets you define a book, add metadata, add chapters, and write the final file.

Here is a complete example that takes extracted PDF text, cleans it, turns each page into a simple chapter-like section, and generates an EPUB file.

import os
import re
import subprocess
from pypdf import PdfReader
from ebooklib import epub


def clean_pdf_text(text):
    if not text:
        return ""

    text = text.replace("\r\n", "\n").replace("\r", "\n")
    text = re.sub(r'-\n([a-z])', r'\1', text)
    text = re.sub(r'(?<!\n)\n(?!\n)', ' ', text)
    text = re.sub(r'[ \t]+', ' ', text)
    text = re.sub(r'\n{3,}', '\n\n', text)

    return text.strip()


def extract_text_from_pdf(pdf_path):
    reader = PdfReader(pdf_path)
    pages_text = []

    for page_number, page in enumerate(reader.pages, start=1):
        text = page.extract_text()
        cleaned = clean_pdf_text(text or "")
        pages_text.append((page_number, cleaned))

    return pages_text


def create_epub_from_pdf_text(pdf_path, epub_path, title="Converted Book", author="Unknown"):
    pages_text = extract_text_from_pdf(pdf_path)

    book = epub.EpubBook()
    book.set_identifier("pdf-to-mobi-demo")
    book.set_title(title)
    book.set_language("en")
    book.add_author(author)

    chapters = []

    for page_number, content in pages_text:
        if not content.strip():
            continue

        chapter = epub.EpubHtml(
            title=f"Page {page_number}",
            file_name=f"page_{page_number}.xhtml",
            lang="en"
        )

        html_content = f"""
        <html>
          <head>
            <title>Page {page_number}</title>
          </head>
          <body>
            <h2>Page {page_number}</h2>
            {''.join(f'<p>{p.strip()}</p>' for p in content.split('\\n\\n') if p.strip())}
          </body>
        </html>
        """

        chapter.content = html_content
        book.add_item(chapter)
        chapters.append(chapter)

    # Add default stylesheet
    style = '''
    body {
        font-family: serif;
        line-height: 1.6;
    }
    h2 {
        text-align: center;
        margin-top: 1em;
        margin-bottom: 1em;
    }
    p {
        margin: 0 0 1em 0;
    }
    '''
    nav_css = epub.EpubItem(
        uid="style_nav",
        file_name="style/nav.css",
        media_type="text/css",
        content=style
    )
    book.add_item(nav_css)

    # Define spine and table of contents
    book.toc = tuple(chapters)
    book.spine = ['nav'] + chapters

    # Add navigation files
    book.add_item(epub.EpubNcx())
    book.add_item(epub.EpubNav())

    epub.write_epub(epub_path, book)


def convert_epub_to_mobi(epub_path, mobi_path):
    cmd = [
        "ebook-convert",
        epub_path,
        mobi_path
    ]
    subprocess.run(cmd, check=True)


if __name__ == "__main__":
    pdf_file = "input.pdf"
    epub_file = "output.epub"
    mobi_file = "output.mobi"

    create_epub_from_pdf_text(
        pdf_path=pdf_file,
        epub_path=epub_file,
        title="My Converted PDF",
        author="PDF Converter"
    )

    convert_epub_to_mobi(epub_file, mobi_file)

    print(f"Created: {mobi_file}")

This script is intentionally simple so the logic is easy to follow. It is not trying to be clever. It takes the extracted text, wraps it in EPUB chapters, and then uses Calibre to produce the MOBI file.

Understanding the Script Step by Step

Let’s walk through the script in a human way, because the details matter more than the syntax here.

The clean_pdf_text() function prepares raw PDF text for ebook formatting. It removes awkward line breaks, joins hyphenated words, and normalizes spacing. This matters because PDFs often store lines based on visual position rather than actual paragraph structure.

The extract_text_from_pdf() function reads each page one by one. That page-by-page approach gives you room to do page-specific processing later if needed. For example, you might choose to skip a cover page, ignore a copyright page, or handle chapter pages differently from body pages.

The create_epub_from_pdf_text() function builds an EPUB book. Each page becomes a chapter-like HTML file. That is a very simple strategy, and it will not be ideal for every document, but it is a solid starting point. For books and reports, you may later replace “one page per chapter” with real chapter detection.

The CSS section makes the output more readable. Even a small amount of styling can improve the reading experience significantly.

Finally, convert_epub_to_mobi() calls Calibre’s command-line converter. That step transforms the EPUB into MOBI.

This separation is useful because the conversion logic remains easy to understand and debug. If something goes wrong, you know whether the issue happened during extraction, EPUB generation, or the final conversion.

Improving the Quality of the Output

A basic conversion works, but a good conversion feels polished. Here are some ways to improve the output.

Detect Real Chapter Boundaries

Using one page per section is convenient, but it is not always the best reading experience. A better method is to detect chapter headings from the text itself. Common patterns include:

  • lines beginning with “Chapter”

  • uppercase headings

  • numbered titles

  • larger font clues from PDF metadata, if available

If you can identify chapter boundaries, the ebook will feel much more natural.

Remove Repeated Headers and Footers

Many PDFs repeat the same title, author name, or page number on every page. These repeated elements should usually be removed. If you leave them in, the reader will see distracting noise on every page.

A practical solution is to compare the first and last few lines of each page and remove text that appears repeatedly across pages.

Handle Scanned PDFs with OCR

If the PDF is scanned images rather than selectable text, pypdf will not give useful output. In that case, OCR is required. A common approach is to use Tesseract OCR through pytesseract, often after converting each page to an image.

That workflow is more complex, but it is sometimes necessary. Without OCR, a scanned PDF is effectively just pictures of text.

Keep Images in Mind

If the PDF contains important diagrams, figures, or illustrations, you may want to extract and reinsert them into the EPUB. This is more advanced, but it can make a big difference for educational or technical material.

A text-only conversion may be acceptable for novels or essays, but not always for textbooks or manuals.

A More Advanced Version with Chapter Detection

If you want a stronger structure than page-based sections, you can build a simple chapter detector. Suppose the PDF text contains headings such as “Chapter 1,” “Chapter 2,” and so on. You can split the content accordingly.

Here is a simplified example:

def split_into_chapters(all_text):
    chapters = []
    current_title = "Introduction"
    current_content = []

    lines = all_text.split("\n")

    chapter_pattern = re.compile(r'^(chapter\s+\d+|chapter\s+one|chapter\s+two|chapter\s+three)', re.IGNORECASE)

    for line in lines:
        stripped = line.strip()
        if not stripped:
            current_content.append("")
            continue

        if chapter_pattern.match(stripped):
            if current_content:
                chapters.append((current_title, "\n".join(current_content).strip()))
            current_title = stripped
            current_content = []
        else:
            current_content.append(stripped)

    if current_content:
        chapters.append((current_title, "\n".join(current_content).strip()))

    return chapters

This is just a basic pattern. Real documents may need more sophisticated parsing, but even a simple chapter detector can dramatically improve the ebook structure.

Handling Errors Gracefully

Any real-world script should fail politely. Files may be missing. Calibre may not be installed. The PDF may be corrupted. Text extraction may return empty strings. These issues are common enough that your script should anticipate them.

Here is a slightly improved conversion wrapper:

import os
import subprocess

def safe_convert_epub_to_mobi(epub_path, mobi_path):
    if not os.path.exists(epub_path):
        raise FileNotFoundError(f"EPUB file not found: {epub_path}")

    try:
        subprocess.run(
            ["ebook-convert", epub_path, mobi_path],
            check=True,
            capture_output=True,
            text=True
        )
    except FileNotFoundError:
        raise RuntimeError("ebook-convert was not found. Make sure Calibre is installed and available in PATH.")
    except subprocess.CalledProcessError as e:
        raise RuntimeError(f"Conversion failed: {e.stderr}")

This kind of defensive coding saves time later. It is much easier to debug a clear error message than to guess why a conversion failed silently.

A Complete Script You Can Use as a Starting Point

Below is a more complete end-to-end script. It is still intentionally readable, but it includes better structure than the earlier snippet.

import os
import re
import subprocess
from pypdf import PdfReader
from ebooklib import epub


def clean_pdf_text(text):
    if not text:
        return ""

    text = text.replace("\r\n", "\n").replace("\r", "\n")
    text = re.sub(r'-\n([a-z])', r'\1', text)
    text = re.sub(r'(?<!\n)\n(?!\n)', ' ', text)
    text = re.sub(r'[ \t]+', ' ', text)
    text = re.sub(r'\n{3,}', '\n\n', text)

    return text.strip()


def extract_pdf_pages(pdf_path):
    reader = PdfReader(pdf_path)
    pages = []

    for page_number, page in enumerate(reader.pages, start=1):
        raw_text = page.extract_text() or ""
        cleaned_text = clean_pdf_text(raw_text)
        pages.append({
            "page_number": page_number,
            "text": cleaned_text
        })

    return pages


def build_epub_from_pages(pages, epub_path, title="Converted PDF", author="Unknown"):
    book = epub.EpubBook()
    book.set_identifier("pdf-to-mobi-converter")
    book.set_title(title)
    book.set_language("en")
    book.add_author(author)

    chapter_items = []

    for item in pages:
        page_number = item["page_number"]
        text = item["text"]

        if not text:
            continue

        chapter = epub.EpubHtml(
            title=f"Page {page_number}",
            file_name=f"page_{page_number}.xhtml",
            lang="en"
        )

        paragraphs = [
            f"<p>{para.strip()}</p>"
            for para in text.split("\n\n")
            if para.strip()
        ]

        chapter.content = f"""
        <html>
          <head>
            <title>Page {page_number}</title>
          </head>
          <body>
            <h1>Page {page_number}</h1>
            {''.join(paragraphs)}
          </body>
        </html>
        """

        book.add_item(chapter)
        chapter_items.append(chapter)

    style = """
    body {
        font-family: serif;
        line-height: 1.7;
        margin: 5%;
    }
    h1 {
        text-align: center;
        margin-bottom: 1.5em;
        font-size: 1.4em;
    }
    p {
        margin-bottom: 1em;
        text-indent: 1.2em;
    }
    """

    css_item = epub.EpubItem(
        uid="main_css",
        file_name="style/main.css",
        media_type="text/css",
        content=style
    )
    book.add_item(css_item)

    book.toc = tuple(chapter_items)
    book.spine = ['nav'] + chapter_items
    book.add_item(epub.EpubNcx())
    book.add_item(epub.EpubNav())

    epub.write_epub(epub_path, book)


def convert_epub_to_mobi(epub_path, mobi_path):
    if not os.path.exists(epub_path):
        raise FileNotFoundError(f"EPUB file not found: {epub_path}")

    try:
        subprocess.run(
            ["ebook-convert", epub_path, mobi_path],
            check=True,
            capture_output=True,
            text=True
        )
    except FileNotFoundError:
        raise RuntimeError("Calibre's ebook-convert command is not available.")
    except subprocess.CalledProcessError as e:
        raise RuntimeError(f"ebook-convert failed:\n{e.stderr}")


def pdf_to_mobi(pdf_path, mobi_path, title="Converted PDF", author="Unknown"):
    base_name = os.path.splitext(mobi_path)[0]
    temp_epub = base_name + ".epub"

    pages = extract_pdf_pages(pdf_path)
    build_epub_from_pages(pages, temp_epub, title=title, author=author)
    convert_epub_to_mobi(temp_epub, mobi_path)

    return mobi_path


if __name__ == "__main__":
    input_pdf = "input.pdf"
    output_mobi = "output.mobi"

    result = pdf_to_mobi(
        pdf_path=input_pdf,
        mobi_path=output_mobi,
        title="My Ebook",
        author="Your Name"
    )

    print(f"Converted successfully: {result}")

This version is suitable as a base for a real project. From here, you can add better chapter detection, image handling, OCR, or command-line arguments.

Making the Script More Useful in the Real World

A conversion tool becomes much more valuable when it is easy to reuse. Instead of editing the file every time, you can turn it into a command-line script with arguments for input path, output path, title, and author. That makes it much easier to automate conversions across many documents.

You can also add:

  • batch processing for a whole folder of PDFs

  • logging to track failures

  • metadata extraction from the PDF

  • cover image support

  • custom output styles

  • language settings for multilingual books

That is where a simple tutorial script evolves into a useful internal tool.

Batch Conversion Example

If you have many PDFs to convert, automation saves a lot of time.

Here is a simple batch converter:

from pathlib import Path

def batch_convert_pdf_folder(input_folder, output_folder):
    input_path = Path(input_folder)
    output_path = Path(output_folder)
    output_path.mkdir(parents=True, exist_ok=True)

    for pdf_file in input_path.glob("*.pdf"):
        mobi_file = output_path / f"{pdf_file.stem}.mobi"
        try:
            pdf_to_mobi(
                pdf_path=str(pdf_file),
                mobi_path=str(mobi_file),
                title=pdf_file.stem,
                author="Unknown"
            )
            print(f"Converted: {pdf_file.name}")
        except Exception as e:
            print(f"Failed: {pdf_file.name} -> {e}")

This is the kind of small utility that can become very handy in a working environment. A folder full of reports or chapter PDFs can be transformed into e-reader files without manual repetition.

What About Direct PDF to MOBI Conversion?

Some people ask whether it is possible to skip EPUB entirely and generate MOBI directly from the PDF in Python.

The practical answer is: not usually in a clean, maintainable way.

You can certainly write a custom conversion pipeline, but the output quality will depend heavily on how much transformation you do yourself. Since MOBI is an ebook format, and PDF is a fixed-layout document, an intermediate semantic format is often the better choice. That extra step gives you control and better results.

So while direct conversion might sound faster, it often leads to more problems in the long run.

Common Problems and How to Think About Them

The output looks like broken lines

That usually means the PDF extraction preserved visual line wrapping. Try joining single line breaks inside paragraphs, as shown in the cleanup function.

The text order is wrong

This happens frequently with multi-column layouts or unusual page structures. A better extractor or a custom parsing rule may be necessary.

The file looks empty

The PDF may be scanned or protected. If it is scanned, OCR is needed. If it is protected, text extraction may be limited.

The MOBI file fails to generate

Check whether Calibre is installed correctly and whether ebook-convert is available in your system PATH.

Images are missing

The basic example does not extract or embed images. You will need a more advanced workflow for image preservation.

A Note About MOBI in Modern Workflows

It is worth saying something practical here: MOBI is still recognizable, but it is not the best long-term format for every Kindle workflow. In many modern publishing and reading setups, EPUB or AZW3 is often more flexible. That said, there are still cases where MOBI is requested, especially in older pipelines or compatibility-focused workflows.

So even if your end goal is MOBI, it is smart to think in terms of structured ebook generation rather than format conversion alone. That mindset will give you better results and less frustration.

Best Practices for Better Results

Here are a few habits that make a big difference:

  • Start with the cleanest possible PDF.

  • Test on short documents before converting large ones.

  • Inspect the EPUB before creating MOBI.

  • Preserve structure, not page appearance.

  • Remove repetitive noise such as headers and page numbers.

  • Keep a backup of the original PDF.

  • Use OCR for scanned documents.

  • Expect manual cleanup for complex layouts.

These are small habits, but they save a lot of time.

When You Should Not Expect Perfect Conversion

It is important to be honest about expectations. Some PDFs are simply not made for ebook conversion. If the document was designed as a poster, brochure, or visually rich magazine, the MOBI output may never feel elegant. In those cases, the goal is not perfect visual reproduction. The goal is readable content.

That distinction matters. A Kindle screen is not a printed page. A good ebook makes reading easy, even if it does not replicate every visual detail of the original PDF.

Human Touch Matters in Conversion Work

It may sound technical, but conversion work has a human side. People do not read files; they read stories, reports, notes, lessons, and ideas. If the conversion is clumsy, the message becomes harder to enjoy. If it is smooth, the document feels effortless.

That is why a thoughtful cleanup function, a clear structure, and a readable ebook format matter so much. You are not just moving bytes from one container to another. You are trying to preserve the way someone experiences the text.

That is a very human task, even when the code is doing most of the work.

Final Thoughts

Converting PDF to MOBI in Python is absolutely possible, but it works best when you stop thinking of it as a one-step conversion. The best results usually come from a pipeline:

  • extract text from the PDF

  • clean and structure the content

  • build an EPUB

  • convert the EPUB to MOBI

This approach gives you flexibility, better readability, and a much easier debugging process. It also leaves room for future improvements such as OCR, image extraction, chapter detection, metadata handling, and batch conversion.

The script in this article is a strong starting point. You can use it as-is for simple documents or expand it into a more advanced converter for real projects. Once you understand the workflow, you will find that the difficult part is not writing the code. The difficult part is respecting the shape of the source document and turning it into something that feels natural to read.

HA

Hassan Agmir

Author · Filenewer

Writing about file tools and automation at Filenewer.

Try It Free

Process your files right now

No account needed · Fast & secure · 100% free

Browse All Tools