Skip to content

Instantly share code, notes, and snippets.

@kxrz
Last active October 28, 2025 08:12
Show Gist options
  • Select an option

  • Save kxrz/62587524adc5c96f8f670587de0c066b to your computer and use it in GitHub Desktop.

Select an option

Save kxrz/62587524adc5c96f8f670587de0c066b to your computer and use it in GitHub Desktop.
Markdown to ePub

Markdown to ePub Converter

Python script to convert your Markdown files to ePub with elegant and professional formatting.

Huge update here:

πŸ”—https://github.com/kxrz/md_to_epub

Code below still works fine.


πŸ“‹ Prerequisites

1. Install Pandoc

The script uses Pandoc for conversion:

macOS:

brew install pandoc

Linux (Ubuntu/Debian):

sudo apt install pandoc

Windows: Download the installer from pandoc.org

2. Make the script executable (Linux/macOS)

chmod +x md_to_epub.py

πŸš€ Usage

Convert a single file

python md_to_epub.py my_document.md

Convert all files in a directory

python md_to_epub.py --dir ./my_notes

Specify an output directory

python md_to_epub.py --dir ./my_notes --output ./my_epubs

Create a default CSS file

python md_to_epub.py --create-css

This creates a style.css file that you can customize.

Use custom CSS

python md_to_epub.py my_document.md --css my_style.css

Recursive conversion (includes subdirectories)

python md_to_epub.py --dir ./my_notes --recursive --output ./epubs

Add an author

python md_to_epub.py my_document.md --author "Your Name"

✨ Features

  • βœ… Automatic conversion from .md to .epub
  • βœ… Table of contents generated automatically
  • βœ… Customizable CSS for elegant rendering
  • βœ… Batch processing of multiple files
  • βœ… Metadata support (title, author, language)
  • βœ… Support for images, code, tables, etc.
  • βœ… Recursive search in subdirectories

🎨 Style Customization

The script creates a default CSS with:

  • Elegant serif font for text
  • Sans-serif font for headings
  • Syntax highlighting for code
  • Clean and readable layout
  • Support for tables, quotes, lists

You can modify style.css to match your preferences!

πŸ“ Supported Markdown Format

The script supports all standard Markdown:

  • Headings (#, ##, etc.)
  • Numbered and bulleted lists
  • Bold and italic
  • Inline code and code blocks
  • Quotes
  • Links and images
  • Tables
  • And more!

πŸ’‘ Tips

Chapter Titles

Use # Title for main chapters and ## Subtitle for sections.

YAML Frontmatter

You can add metadata at the beginning of your files:

---
title: My Amazing Book
author: Your Name
lang: en
---

# Chapter 1
...

Images

Local images will be embedded in the ePub:

![Description](./images/photo.jpg)

πŸ”§ Complete Options

usage: md_to_epub.py [-h] [--dir DIR] [--output OUTPUT] [--css CSS]
                     [--create-css] [--recursive] [--author AUTHOR]
                     [input]

Options:
  input                 Markdown file or directory to convert
  --dir, -d            Directory containing .md files
  --output, -o         Output directory for ePub files
  --css, -c            Custom CSS file
  --create-css         Create a default CSS file
  --recursive, -r      Recursive search in subdirectories
  --author, -a         Author name

🎯 Practical Examples

Convert an entire notes library

python md_to_epub.py --dir ~/Documents/notes --output ~/Books/epubs --recursive

Create a book with custom style

# 1. Create the base CSS
python md_to_epub.py --create-css

# 2. Edit style.css to your liking

# 3. Convert with your style
python md_to_epub.py my_book.md --css style.css --author "Your Name"

πŸ“± For Your Xteink X4

Once the ePub files are generated, simply transfer them to your Xteink X4 via USB or the file management app!

Enjoy your Markdown content with professional formatting on your e-reader πŸ“š

❓ Help

To display help:

python md_to_epub.py --help

Happy reading! πŸŽ‰

#!/usr/bin/env python3
"""
Script to convert Markdown files to ePub with beautiful formatting.
Requires: pip install pypandoc markdown ebooklib
"""
import os
import sys
import argparse
from pathlib import Path
import subprocess
def check_pandoc():
"""Check if Pandoc is installed."""
try:
subprocess.run(['pandoc', '--version'], capture_output=True, check=True)
return True
except (subprocess.CalledProcessError, FileNotFoundError):
print("❌ Pandoc is not installed!")
print("Installation:")
print(" - macOS: brew install pandoc")
print(" - Linux: sudo apt install pandoc")
print(" - Windows: download from https://pandoc.org/installing.html")
return False
def convert_md_to_epub(md_file, output_dir=None, css_file=None, metadata=None):
"""
Convert a Markdown file to ePub.
Args:
md_file: Path to the .md file
output_dir: Output directory (optional)
css_file: Custom CSS file (optional)
metadata: Dictionary with title, author, etc.
"""
md_path = Path(md_file)
if not md_path.exists():
print(f"❌ File not found: {md_file}")
return False
# Determine output name
if output_dir:
output_path = Path(output_dir) / f"{md_path.stem}.epub"
output_path.parent.mkdir(parents=True, exist_ok=True)
else:
output_path = md_path.with_suffix('.epub')
# Build pandoc command
cmd = [
'pandoc',
str(md_path),
'-o', str(output_path),
'--toc', # Table of contents
'--toc-depth=3',
'--epub-chapter-level=2',
]
# Add CSS if provided
if css_file and Path(css_file).exists():
cmd.extend(['--css', css_file])
# Add metadata
if metadata:
if 'title' in metadata:
cmd.extend(['--metadata', f'title={metadata["title"]}'])
if 'author' in metadata:
cmd.extend(['--metadata', f'author={metadata["author"]}'])
if 'lang' in metadata:
cmd.extend(['--metadata', f'lang={metadata["lang"]}'])
if 'cover' in metadata and Path(metadata['cover']).exists():
cmd.extend(['--epub-cover-image', metadata['cover']])
# Execute conversion
try:
subprocess.run(cmd, check=True, capture_output=True)
print(f"βœ… Converted: {md_path.name} β†’ {output_path.name}")
return True
except subprocess.CalledProcessError as e:
print(f"❌ Error converting {md_path.name}")
print(e.stderr.decode())
return False
def create_default_css(css_path='style.css'):
"""Create a default CSS file for beautiful rendering."""
css_content = """/* Style for ePub generated from Markdown */
body {
font-family: "Georgia", "Times New Roman", serif;
font-size: 1.1em;
line-height: 1.6;
margin: 1em;
text-align: justify;
}
h1, h2, h3, h4, h5, h6 {
font-family: "Helvetica", "Arial", sans-serif;
font-weight: bold;
margin-top: 1.5em;
margin-bottom: 0.5em;
page-break-after: avoid;
}
h1 {
font-size: 2em;
border-bottom: 2px solid #333;
padding-bottom: 0.3em;
}
h2 {
font-size: 1.6em;
color: #444;
}
h3 {
font-size: 1.3em;
color: #666;
}
p {
margin: 0.8em 0;
text-indent: 1.5em;
}
/* No indentation after headings */
h1 + p, h2 + p, h3 + p, h4 + p, h5 + p, h6 + p {
text-indent: 0;
}
code {
font-family: "Courier New", monospace;
background-color: #f5f5f5;
padding: 0.2em 0.4em;
border-radius: 3px;
font-size: 0.9em;
}
pre {
background-color: #f5f5f5;
padding: 1em;
border-radius: 5px;
overflow-x: auto;
line-height: 1.4;
}
pre code {
background-color: transparent;
padding: 0;
}
blockquote {
border-left: 4px solid #ccc;
margin: 1em 0;
padding-left: 1em;
color: #666;
font-style: italic;
}
a {
color: #0066cc;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
ul, ol {
margin: 1em 0;
padding-left: 2em;
}
li {
margin: 0.5em 0;
}
img {
max-width: 100%;
height: auto;
display: block;
margin: 1em auto;
}
hr {
border: none;
border-top: 1px solid #ccc;
margin: 2em 0;
}
table {
border-collapse: collapse;
width: 100%;
margin: 1em 0;
}
th, td {
border: 1px solid #ddd;
padding: 0.5em;
text-align: left;
}
th {
background-color: #f5f5f5;
font-weight: bold;
}
"""
with open(css_path, 'w', encoding='utf-8') as f:
f.write(css_content)
print(f"βœ… CSS file created: {css_path}")
return css_path
def batch_convert(input_dir, output_dir=None, css_file=None, recursive=False):
"""Convert all .md files in a directory."""
input_path = Path(input_dir)
if not input_path.exists():
print(f"❌ Directory not found: {input_dir}")
return
# Find all .md files
if recursive:
md_files = list(input_path.rglob('*.md'))
else:
md_files = list(input_path.glob('*.md'))
if not md_files:
print(f"❌ No .md files found in {input_dir}")
return
print(f"πŸ“š {len(md_files)} Markdown file(s) found\n")
success_count = 0
for md_file in md_files:
# Extract title from first heading if possible
title = md_file.stem.replace('_', ' ').replace('-', ' ').title()
metadata = {
'title': title,
'lang': 'en'
}
if convert_md_to_epub(md_file, output_dir, css_file, metadata):
success_count += 1
print(f"\n✨ Conversion complete: {success_count}/{len(md_files)} files converted")
def main():
parser = argparse.ArgumentParser(
description='Convert Markdown files to ePub with beautiful formatting',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Convert a single file
python md_to_epub.py my_file.md
# Convert all .md files in a directory
python md_to_epub.py --dir ./my_notes --output ./epubs
# Create a default CSS file
python md_to_epub.py --create-css
# Use custom CSS
python md_to_epub.py my_file.md --css my_style.css
"""
)
parser.add_argument('input', nargs='?', help='Markdown file or directory to convert')
parser.add_argument('--dir', '-d', help='Directory containing .md files')
parser.add_argument('--output', '-o', help='Output directory for ePub files')
parser.add_argument('--css', '-c', help='Custom CSS file')
parser.add_argument('--create-css', action='store_true',
help='Create a default CSS file (style.css)')
parser.add_argument('--recursive', '-r', action='store_true',
help='Recursive search in subdirectories')
parser.add_argument('--author', '-a', help='Author name')
args = parser.parse_args()
# Special case: CSS creation
if args.create_css:
create_default_css()
return
# Check if Pandoc is installed
if not check_pandoc():
sys.exit(1)
# Determine operating mode
if args.dir:
# Batch mode: convert entire directory
batch_convert(args.dir, args.output, args.css, args.recursive)
elif args.input:
input_path = Path(args.input)
if input_path.is_dir():
# Input is actually a directory
batch_convert(args.input, args.output, args.css, args.recursive)
elif input_path.suffix == '.md':
# Convert a single file
metadata = {
'title': input_path.stem.replace('_', ' ').replace('-', ' ').title(),
'lang': 'en'
}
if args.author:
metadata['author'] = args.author
convert_md_to_epub(args.input, args.output, args.css, metadata)
else:
print("❌ File must have .md extension")
else:
parser.print_help()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment