podcast-chapter-tools
Extract and transform podcast chapter information between PodcastIndex (PCI) chapters, Podlove Simple Chapters (PSC), description embeds (timestamps in show notes), and ID3v2 CHAP frames.
Installation
pip install podcast-chapter-tools
# with ID3 chapter support (mutagen)
pip install 'podcast-chapter-tools[id3]'
Usage
Command line
# Show-notes timestamps -> PodcastIndex chapters JSON
podcast-chapters from-description shownotes.txt --to pci
# PSC chapters from a feed (file or URL) -> description text
podcast-chapters from-psc feed.xml --guid "episode-guid" --to description
# PodcastIndex chapters JSON (file or URL) -> PSC XML
podcast-chapters from-pci https://example.com/chapters.json --to psc
# ID3 CHAP frames from an MP3 (requires the [id3] extra)
podcast-chapters from-id3 episode.mp3
All subcommands accept --to {pci,psc,description}, --normalize (sort,
dedupe, strip HTML from titles), and --output FILE.
Library
Every extractor returns list[Chapter] | None, where Chapter is a named
tuple of (start, title, url, image) with the start time in seconds:
from podcast_chapter_tools import (
extract_description_chapters,
extract_psc_chapters_from_file,
get_and_extract_pci_chapters,
chapters_to_pci_json,
normalize_chapters,
)
from pathlib import Path
# From show notes (plain text or HTML)
chapters = extract_description_chapters("0:00 Intro\n2:30 Interview\n")
# [Chapter(start=0, title='Intro', ...), Chapter(start=150, title='Interview', ...)]
# From a feed's Podlove Simple Chapters
chapters = extract_psc_chapters_from_file(Path("feed.xml"), guid="episode-guid")
# From a PodcastIndex chapters URL
chapters = get_and_extract_pci_chapters("https://example.com/chapters.json")
# Clean up and convert
chapters = normalize_chapters(chapters, strip_titles=True)
print(chapters_to_pci_json(chapters, indent=2))
Other useful entry points:
extract_all_psc_chapters_from_file(feed)- chapters for every episode, keyed by GUIDextract_psc_chapters_from_url(feed_url, guid)- fetch the feed for youfind_pci_chapters_url(feed, guid)- discover the<podcast:chapters>URL declared in a feedextract_id3_chapters(Path("episode.mp3"))- ID3v2 CHAP frames (requires the[id3]extra)chapters_to_psc_xml(chapters)/chapters_to_description(chapters)- other writersts_to_secs("1:02:03")/secs_to_ts(3723)- timestamp helpers
Development
git clone git@github.com:hbmartin/podcast-chapter-tools.git
cd podcast-chapter-tools
python3 -m venv venv
source venv/bin/activate
pip install -e '.[lint,test]'
pytest
ruff check .
ruff format --check .
mypy podcast_chapter_tools
The ai/ directory contains experimental scripts for generating chapters
from transcripts with LLMs; its dependencies live in ai/requirements.txt
and are not part of the published package.
See also
- https://github.com/hbmartin/podcast-transcript-convert
- https://github.com/Podcastindex-org/podcast-namespace/blob/main/transcripts/transcripts.md
- https://github.com/hbmartin/overcast-to-sqlite
- https://support.spotify.com/us/podcasters/article/enabling-podcast-chapters/
Authors
- Harold Martin - harold.martin at gmail
- Icon courtesy of Vecteezy.com
Metadata
Release files for podcast-chapter-tools 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| podcast_chapter_tools-0.2.0.tar.gz | 27.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| podcast_chapter_tools-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 48.8 kB
Release files / podcast_chapter_tools-0.2.0.tar.gz
| Download URL | podcast_chapter_tools-0.2.0.tar.gz |
|---|---|
| Size | 27.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b74074fc27e7c32b5d34c4486b0742ae3dd000c682e5c25a46414642e4cda157
|
|
BLAKE2b-256 checksum How to use checksums |
c03c6749fd6affff8067369e2b7063b771ceaa37f771ad48ee923a1cc3416f40
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 7, 2026.
Transparency logRelease files / podcast_chapter_tools-0.2.0-py3-none-any.whl
| Download URL | podcast_chapter_tools-0.2.0-py3-none-any.whl |
|---|---|
| Size | 21.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4c817624e16917e4a46de5e08949a7c5490992d7516d5f1149a075f66eef2cd6
|
|
BLAKE2b-256 checksum How to use checksums |
a7d4f0b1077f2f9d2798821473c368c4343dc51ccd95291d3de40def6a71b6d1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 7, 2026.
Transparency log