API Reference
August 2, 2026 · View on GitHub
AudioBook
from audiobooker.base import AudioBook
@dataclass
class AudioBook:
title: str = ""
description: str = ""
image: str = "" # cover art URL
language: str = "" # ISO 639-1 (normalised on init via normalize_language)
authors: List[BookAuthor] = field(default_factory=list)
tags: List[str] = field(default_factory=list)
streams: List[str] = field(default_factory=list) # direct audio URLs
narrator: Optional[AudiobookNarrator] = None
narrators: List[AudiobookNarrator] = field(default_factory=list)
chapters: List[AudioBookChapter] = field(default_factory=list)
genres: List[str] = field(default_factory=list)
year: int = 0
runtime: int = 0 # seconds (where available)
source: str = "" # set automatically by the scraper, e.g. "Librivox"
score: float = 0.0 # relevance score from last search (0..1)
codec: str = "" # audio codec of the primary stream, when known
bitrate: str = "" # bitrate of the primary stream, when known
external_ids: dict = field(default_factory=dict) # e.g. librivox_id, gutenberg_id
__post_init__ normalises language and reconciles the singular narrator
with the plural narrators list so callers can use either one.
stable_id()
book.stable_id() -> str
A deterministic SHA-256-derived hex digest of title + authors, truncated to
16 characters. Stable across processes and Python versions, unlike the
built-in hash(). Used as the cache directory name (see
cache.md) and the index deduplication key.
Equality and hashing
AudioBook.__hash__ and __eq__ are both derived from stable_id()
(title + authors), so two books with the same title and authors compare
equal regardless of source. Use a set to deduplicate across sources:
seen = set()
for book in results:
if book not in seen:
seen.add(book)
process(book)
has_live_streams()
book.has_live_streams() -> bool
Issues a HEAD request to each stream URL and returns True if at least one
responds with HTTP < 400. Uses the shared plain requests.Session
(AudioBookSource.session).
BookAuthor
from audiobooker.base import BookAuthor
a = BookAuthor(first_name="H. P.", last_name="Lovecraft")
Equality and hashing are case-insensitive.
AudiobookNarrator
from audiobooker.base import AudiobookNarrator
n = AudiobookNarrator(first_name="Frank", last_name="Muller")
AudioBookChapter
from audiobooker.base import AudioBookChapter
c = AudioBookChapter(title="Chapter 1", offset=0.0, runtime=612.0, stream="https://...")
A single chapter or section of an audiobook. offset is the start position
in seconds from the beginning of the book; runtime is the chapter duration
in seconds; stream is the per-chapter audio URL when known.
AudioBookSource
Base class for all scrapers.
from audiobooker.scrappers import AudioBookSource
Class-level shared session
AudioBookSource.session # plain requests.Session shared across all scrapers
Replace with your own session if you need caching, retries, or a custom adapter.
import requests
AudioBookSource.session = requests.Session()
source_name property
Returns self.__class__.__name__. Stamped into book.source via _tag().
Abstract / override
| Method | Required | Description |
|---|---|---|
iterate_all() | yes | Yield every book in the catalogue |
iterate_popular() | no | Defaults to iterate_all() |
All search_by_* methods have working default implementations in the base
class (fuzzy linear scan over iterate_all()). Override for native search.
Utilities
from audiobooker import score_book, iter_sitemap_urls, check_url_availability, normalize_language
score_book
score_book(query: str, book: AudioBook, method: str = "search") -> float
Returns a value from 0.0 to 1.0. method controls field weights: use the same
name as the search function ("search", "search_by_title", etc.).
See scoring.md.
iter_sitemap_urls
iter_sitemap_urls(url: str) -> Iterable[str]
Recursively walks a sitemap or sitemap index and yields every leaf URL.
Handles both <urlset> and <sitemapindex> transparently. Silently skips
URLs that fail to fetch or parse.
check_url_availability
check_url_availability(url: str, timeout: int = 5) -> bool
Returns True if a HEAD request to url returns HTTP < 400.
normalize_language
normalize_language(lang: str) -> str
Maps any language string to an ISO 639-1 two-letter code:
normalize_language("English") # → "en"
normalize_language("en-US") # → "en"
normalize_language("français") # → "fr"
normalize_language("de") # → "de"
Called automatically by AudioBook.__post_init__ on the language field.
HTTP transport
Every scraper inherits from AudioBookSource, which holds a class-level
requests.Session (with a randomised User-Agent) for backward
compatibility. You can also inject a per-instance session through the
constructor. This helps with testing, custom retries, proxies, or alternative
HTTP backends.
import requests
from audiobooker.scrappers.librivox import Librivox
s = requests.Session()
s.proxies = {"https": "http://localhost:8888"}
lv = Librivox(session=s)
[stealth] extra and AUDIOBOOKER_TRANSPORT
Some sites front their pages with bot-protection that fingerprints the
TLS handshake and blocks plain requests. Install the optional
[stealth] extra to pull in curl_cffi,
which impersonates a real browser's TLS fingerprint:
pip install audiobooker[stealth]
Then set AUDIOBOOKER_TRANSPORT=curl_cffi in the environment and call
audiobooker.transport.default_session() to obtain a curl_cffi-backed
session you can pass into any scraper:
import os
os.environ["AUDIOBOOKER_TRANSPORT"] = "curl_cffi"
from audiobooker.transport import default_session
from audiobooker.scrappers.librivox import Librivox
lv = Librivox(session=default_session())
If curl_cffi isn't importable, default_session() falls back
to a plain requests.Session without raising an error. Note that LibriVox's RSS
fetch uses feedparser, which goes through urllib internally. Injected
sessions do not apply to that call, only the User-Agent header is forwarded.