Back to Ebook2audiobook

@title 🚀 Run ebook2audiobook!

Notebooks/colab_ebook2audiobook.ipynb

26.5.410.3 KB
Original Source

<a href="https://colab.research.google.com/github/DrewThomasson/ebook2audiobook/blob/main/Notebooks/colab_ebook2audiobook.ipynb" target="_parent"></a>

Welcome to the ebook2audiobook Google Colab!

Features

  • 🔧 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>

Want to run locally for free? ⬇

Check out the ebook2audiobook github!

python
# @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.")