Notebooks/colab_ebook2audiobook.ipynb
<a href="https://colab.research.google.com/github/DrewThomasson/ebook2audiobook/blob/main/Notebooks/colab_ebook2audiobook.ipynb" target="_parent"></a>
🔧 TTS Engines supported: XTTSv2, Bark, Fairseq, VITS, Tacotron2, Tortoise, GlowTTS, YourTTS- 📚 Convert multiple file formats: .epub, .mobi, .azw3, .fb2, .lrf, .rb, .snb, .tcr, .pdf, .txt, .rtf, .doc, .docx, .html, .odt, .azw, .tiff, .tif, .png, .jpg, .jpeg, .bmp
🔍 OCR scanning for files with text pages as images
🔊 High-quality text-to-speech from near realtime to near real voice
🗣️ Optional voice cloning using your own voice file
🌐 Supports 1158 languages (supported languages list)
💻 Low-resource friendly — runs on 2 GB RAM / 1 GB VRAM (minimum)
🎵 Audiobook output formats: mono or stereo aac, flac, mp3, m4b, m4a, mp4, mov, ogg, wav, webm
🧠 SML tags supported — fine-grained control of breaks, pauses, voice switching and more (see below)
🧩 Optional custom model using your own trained model (XTTSv2 only, other on request)
🎛️ Fine-tuned preset models trained by the E2A Team
<i>(Contact us if you need additional fine-tuned models, or if you'd like to share yours to the official preset list)</i>
# @title 🚀 Run ebook2audiobook!
import os
import subprocess
import time
import shutil
import sysconfig
# Emojis for logs
CHECK_MARK = "✅"
CROSS_MARK = "❌"
SCRIPT_DIR = "/content/ebook2audiobook"
# ── Environment variables (mirrors the bash script) ──────────────────────────
os.environ["PYTHONUTF8"] = "1"
os.environ["PYTHONIOENCODING"] = "utf-8"
os.environ["TTS_CACHE"] = f"{SCRIPT_DIR}/models"
os.environ["TESSDATA_PREFIX"] = f"{SCRIPT_DIR}/models/tessdata"
os.environ["TMPDIR"] = f"{SCRIPT_DIR}/tmp"
def display_loading_bar(total_steps):
print("\n--- LOADING... Total steps:", total_steps, " ---")
def update_progress(step, total_steps):
bar_length = 20
progress_percent = int((step / total_steps) * 100)
progress_filled = int(bar_length * step / total_steps)
bar = '=' * progress_filled + '>' + ' ' * max(bar_length - progress_filled - 1, 0)
print(f"--- PROGRESS: [{bar}] {progress_percent}% ({step}/{total_steps}) ---")
def run_command_with_log(command, description, step_progress, total_step_commands):
"""Runs a shell command and logs progress, outcome and duration."""
print(f"\n{step_progress}/{total_step_commands}: {description}...")
start_time = time.time()
try:
process = subprocess.Popen(command, shell=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = process.communicate()
duration = f"{time.time() - start_time:.2f}"
if process.returncode != 0:
print(f"{CROSS_MARK} Command failed: {description} (Took {duration}s)")
print(f" Command: {command}")
print(f" Error Output:\n{stderr.decode()}")
return False
else:
update_progress(step_progress, total_step_commands)
print(f"{CHECK_MARK} {step_progress}/{total_step_commands} completed: {description} (Took {duration}s)")
return True
except Exception as e:
duration = f"{time.time() - start_time:.2f}"
print(f"{CROSS_MARK} Error during: {description} (Took {duration}s) — {e}")
return False
# ── Step 1 : OS-level packages ────────────────────────────────────────────────
# Mirrors HOST_PROGRAMS from the bash script + Calibre's special installer
os_install_commands = [
("apt-get update -qq",
"Update package lists"),
("apt-get install -y -qq libxcb-cursor0 libegl1 libopengl0",
"Install Calibre display libraries (libxcb-cursor0, libegl1, libopengl0)"),
("sudo -v && wget -nv -O- https://download.calibre-ebook.com/linux-installer.sh | sudo sh /dev/stdin",
"Download & install Calibre"),
("apt-get install -y -qq ffmpeg",
"Install ffmpeg"),
("apt-get install -y -qq mediainfo",
"Install mediainfo"),
("apt-get install -y -qq nodejs",
"Install nodejs"),
("apt-get install -y -qq espeak-ng",
"Install espeak-ng"),
("apt-get install -y -qq sox",
"Install sox"),
("apt-get install -y -qq tesseract-ocr tesseract-ocr-eng",
"Install Tesseract OCR + English language pack"),
# mecab + unidic (for Japanese support, same as old notebook)
("apt-get install -y -qq mecab libmecab-dev mecab-ipadic-utf8",
"Install mecab (Japanese text analysis)"),
# Rust is required by some pip packages (e.g. tokenizers)
("curl -fsSL https://sh.rustup.rs | sh -s -- -y --quiet && "
"echo 'source $HOME/.cargo/env' >> ~/.bashrc",
"Install Rust (required by some Python packages)"),
]
# ── Step 2 : Git clone ────────────────────────────────────────────────────────
git_commands = [
("git clone --depth=1 https://github.com/DrewThomasson/ebook2audiobook.git /content/ebook2audiobook",
"Git clone ebook2audiobook"),
]
# ── Step 3 : Python packages (mirrors install_python_packages()) ─────────────
# llvmlite/numba must be installed first as pre-built binaries;
# then the rest of requirements.txt is installed line-by-line.
# unidic download closes out the step.
pip_commands = [
("pip install -q --upgrade pip setuptools wheel",
"Upgrade pip / setuptools / wheel"),
("pip install -q --upgrade llvmlite numba --only-binary=:all:",
"Install llvmlite & numba (pre-built binaries only)"),
("pip install -q --upgrade --no-cache-dir "
"-r /content/ebook2audiobook/requirements.txt",
"Install Python requirements from requirements.txt"),
("python -m unidic download",
"Download Unidic dictionary data"),
]
# ── Helper: sitecustomize hook (mirrors check_sitecustomized()) ───────────────
def install_sitecustomize():
src = f"{SCRIPT_DIR}/components/sitecustomize.py"
dst_dir = sysconfig.get_paths()["purelib"]
dst = os.path.join(dst_dir, "sitecustomize.py")
if not os.path.exists(src):
print(f"{CROSS_MARK} sitecustomize.py source not found at {src}, skipping.")
return False
if not os.path.exists(dst) or os.path.getmtime(src) > os.path.getmtime(dst):
shutil.copy2(src, dst)
print(f"{CHECK_MARK} Installed sitecustomize.py hook → {dst}")
else:
print(f"{CHECK_MARK} sitecustomize.py already up-to-date.")
return True
# ════════════════════════════════════════════════════════════════════════════
# EXECUTION
# ════════════════════════════════════════════════════════════════════════════
# --- Step 1: OS packages ---
print("\n--- Step 1: OS-Level Installations ---")
print("Installs system packages required by the bash script. (~3-5 min)")
display_loading_bar(len(os_install_commands))
step1_ok = True
for i, (cmd, desc) in enumerate(os_install_commands):
if not run_command_with_log(cmd, desc, i + 1, len(os_install_commands)):
step1_ok = False
break
# Activate Rust for the current session after installation
cargo_env = os.path.expanduser("~/.cargo/env")
if os.path.exists(cargo_env):
os.environ["PATH"] = os.path.expanduser("~/.cargo/bin") + ":" + os.environ.get("PATH", "")
if step1_ok:
print(f"\n{CHECK_MARK} Step 1: OS-Level Installations — Completed Successfully!")
else:
print(f"\n{CROSS_MARK} Step 1: OS-Level Installations — Failed. See errors above.")
# --- Step 2: Git Clone ---
print("\n--- Step 2: Git Clone ---")
display_loading_bar(len(git_commands))
step2_ok = True
for i, (cmd, desc) in enumerate(git_commands):
if not run_command_with_log(cmd, desc, i + 1, len(git_commands)):
step2_ok = False
break
if step2_ok:
print(f"\n{CHECK_MARK} Step 2: Git Clone — Completed Successfully!")
else:
print(f"\n{CROSS_MARK} Step 2: Git Clone — Failed. See errors above.")
# --- Step 3: Python packages ---
print("\n--- Step 3: Python Package Installation ---")
print("Mirrors install_python_packages() from the bash script. (~3-5 min)")
display_loading_bar(len(pip_commands))
step3_ok = True
for i, (cmd, desc) in enumerate(pip_commands):
if not run_command_with_log(cmd, desc, i + 1, len(pip_commands)):
step3_ok = False
break
if step3_ok:
print(f"\n{CHECK_MARK} Step 3: Python Package Installation — Completed Successfully!")
else:
print(f"\n{CROSS_MARK} Step 3: Python Package Installation — Failed. See errors above.")
# --- Step 4: sitecustomize hook ---
print("\n--- Step 4: Installing sitecustomize.py hook ---")
install_sitecustomize()
# --- Step 5: Create required directories ---
print("\n--- Step 5: Creating required directories ---")
for d in ["models", "models/tessdata", "tmp", "run", "audiobooks", "ebooks", "voices"]:
os.makedirs(f"{SCRIPT_DIR}/{d}", exist_ok=True)
os.chmod(f"{SCRIPT_DIR}/tmp", 0o777)
print(f"{CHECK_MARK} Directories ready.")
# --- Step 6: Run App ---
print("\n--- Step 6: Launch ebook2audiobook ---")
print("Starting the Gradio web interface with a public share link...")
try:
# script_mode=native matches the NATIVE branch of the bash script;
# --share creates the public Gradio tunnel link.
get_ipython().system(
"cd /content/ebook2audiobook && "
"python -u app.py --script_mode native --share"
)
except Exception as e:
print(f"{CROSS_MARK} Error starting app.py: {e}")
print("\n--- All Steps Completed ---")
print("Check the output above for the public Gradio URL.")