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

MethodRequiredDescription
iterate_all()yesYield every book in the catalogue
iterate_popular()noDefaults 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.


← HTTP Transport · Home