Convert HTML books and PDF documents into high-quality MP3 audiobooks for Yoto MakeYourOwn cards.
Built with Bun + TypeScript, powered by ElevenLabs TTS for natural narration.
- Dual Format Support: Convert HTML books from URLs or local PDF files
- Smart Chapter Detection:
- HTML: Automatic h1/h2 tag detection with fallback strategies
- PDF: Table of contents parsing with page-based fallback
- High-Quality Audio: ElevenLabs TTS for natural narration
- Yoto Compatible: MP3 files optimized for Yoto MakeYourOwn constraints
- Progress Tracking: Visual spinners showing conversion progress
- Dry Run Mode: Preview chapters and estimate costs before converting
- Error Recovery: Failed chapters don't block other conversions
- Bun 1.x or later
- ElevenLabs API key (free tier available)
bun install- Copy the example environment file:
cp .env.example .env- Edit
.envand add your ElevenLabs API key:
ELEVENLABS_API_KEY=your_api_key_hereGet your API key from: https://elevenlabs.io/
bun run src/cli.ts convert https://basecamp.com/shapeupbun run src/cli.ts convert ~/Downloads/book.pdf# Specify output directory
bun run src/cli.ts convert https://basecamp.com/shapeup --output ~/yoto-books/shapeup
# Use specific voice
bun run src/cli.ts convert book.pdf --voice rachel
# Set bitrate (default: 128kbps)
bun run src/cli.ts convert book.pdf --bitrate 192
# Configure pages per chapter for PDFs without TOC
bun run src/cli.ts convert book.pdf --pages-per-chapter 15
# Skip cost confirmation prompt (for automation/CI)
bun run src/cli.ts convert book.pdf --yes# See chapter structure and cost estimate without converting
bun run src/cli.ts convert https://basecamp.com/shapeup --dry-runBy default, linkin-lark shows the estimated cost and requires confirmation before conversion:
# Normal mode - shows cost and requires --yes flag
bun run src/cli.ts convert https://basecamp.com/shapeup
# Output:
# 💰 Cost Estimate:
# Chapters: 14
# Characters: 145,230
# Estimated cost: $4.36 (approximate)
#
# ⚠ To proceed with conversion, add the --yes flag:
# linkin-lark convert "https://basecamp.com/shapeup" --yes
# Auto-confirm and proceed with conversion
bun run src/cli.ts convert https://basecamp.com/shapeup --yesNote: JSON mode (--format json) and dry-run mode automatically skip confirmation for automation compatibility.
# Get structured JSON output for programmatic parsing
bun run src/cli.ts convert https://basecamp.com/shapeup --format json
# Dry run with JSON output
bun run src/cli.ts convert book.pdf --dry-run --format jsonYou can import and use linkin-lark programmatically in your TypeScript/JavaScript applications:
import { parseInput, convertToSpeech, saveMp3File, ensureOutputDir } from 'linkin-lark';
async function convertBook() {
const input = 'https://example.com/book';
const outputDir = './output';
await ensureOutputDir(outputDir);
const result = await parseInput(input);
console.log(`Parsed ${result.chapters.length} chapters from ${result.source}`);
for (const [i, chapter] of result.chapters.entries()) {
const audio = await convertToSpeech(chapter.content, {
apiKey: process.env.ELEVENLABS_API_KEY!,
voiceId: process.env.ELEVENLABS_VOICE_ID || 'EXAVITQu4vr4xnSDxMaL'
});
console.log(`Chapter ${i + 1}: ${audio.characters} characters`);
const filePath = await saveMp3File(
audio.audio,
i,
result.chapters.length,
{ outputDir }
);
console.log(`Saved: ${filePath}`);
}
}
convertBook().catch(console.error);// Types
import type {
Chapter,
ConvertOptions,
TTSOptions,
ParserResult,
TTSResponse,
GeneratorOptions,
ConversionResult
} from 'linkin-lark';
// Parsing
import { parseInput } from 'linkin-lark';
// Text-to-Speech
import { convertToSpeech, getApiKey, getDefaultVoiceId } from 'linkin-lark';
// File Generation
import { saveMp3File, generateFileName, ensureOutputDir } from 'linkin-lark';
// HTML Cleaning
import { cleanHTMLContent } from 'linkin-lark';MP3 files are generated with the following constraints to ensure Yoto MakeYourOwn compatibility:
Per Track:
- Max file size: 100MB
- Max duration: 60 minutes
- Format: MP3 (128-192 kbps recommended)
Per Card:
- Max total size: 500MB
- Max total duration: 5 hours
- Max tracks: 100
File Naming: Files are named with zero-padded numbers (01.mp3, 02.mp3, etc.) to ensure correct playback order on Yoto.
- Fetches HTML content from URL
- Parses structure with Cheerio
- Detects chapters using h1/h2 tags containing keywords like "chapter", "section", "prologue"
- Cleans content (removes navigation, ads, scripts)
- Converts each chapter to audio via ElevenLabs
- Saves as sequentially named MP3 files
Fallback: If fewer than 5 chapters detected, treats entire book as single chapter.
- Reads local PDF file
- Extracts text with pdf-parse
- Parses table of contents/bookmarks if available
- Falls back to page-based splitting (10 pages per chapter by default)
- Converts each chapter to audio via ElevenLabs
- Saves as sequentially named MP3 files
# Convert Shape Up book
bun run src/cli.ts convert https://basecamp.com/shapeup
# Preview chapters and cost
bun run src/cli.ts convert https://basecamp.com/shapeup --dry-run
# Convert local PDF with custom settings
bun run src/cli.ts convert ~/Downloads/ruby-book.pdf \
--output ~/yoto-books/ruby \
--voice rachel \
--bitrate 128
# PDF without TOC (use page-based splitting)
bun run src/cli.ts convert manual.pdf --pages-per-chapter 20Create a .env file (copy from .env.example):
# Required
ELEVENLABS_API_KEY=your_api_key_here
# Optional
ELEVENLABS_VOICE_ID=rachel
ELEVENLABS_MODEL_ID=eleven_flash_v2_5Find available voice IDs at: https://elevenlabs.io/docs/voices
Default voice: EXAVITQu4vr4xnSDxMaL (Rachel)
eleven_flash_v2_5(default) - Fastest, ~75ms latencyeleven_multilingual_v2- Best quality, more expressive
ElevenLabs pricing (as of 2026):
- Free: 10k characters/month
- Starter: $5/mo, 30k characters
- Pro: $99/mo, 500k characters
Pay-as-you-go rate: $30 per 1M characters (used for cost estimates)
Average book: ~200k characters ≈ $6.00 at pay-as-you-go rate, or included in subscription tiers.
Cost preview: Normal conversions automatically display cost estimates and require --yes flag to proceed. Use --dry-run to preview costs without confirmation prompts.
# Preview cost (no confirmation needed)
bun run src/cli.ts convert book.pdf --dry-run
# Normal conversion (requires --yes after seeing cost)
bun run src/cli.ts convert book.pdf
# Shows cost estimate, then exits with instructions
# Confirm and proceed
bun run src/cli.ts convert book.pdf --yesMake sure ELEVENLABS_API_KEY is set in .env file. Get your key from https://elevenlabs.io/
You've hit ElevenLabs API rate limits. Wait or upgrade your plan. The tool will show retry-after time.
Try:
- Check if the book uses h1/h2 tags for chapters
- The tool falls back to treating the whole book as one chapter
- Future: custom selectors will be supported
Use --pages-per-chapter to split by page count:
bun run src/cli.ts convert book.pdf --pages-per-chapter 15# Run with Bun
bun run src/cli.ts convert <input>
# Compile to standalone executable
bun build ./src/cli.ts --compile --outfile linkin-larkMIT
Built with:
- Bun - Fast JavaScript runtime
- Commander.js - CLI framework
- Cheerio - HTML parsing
- pdf-parse - PDF text extraction
- ElevenLabs - Text-to-speech API
- Ora - Terminal spinners