Convert Discourse threads to multi-voice audio using TTS
Project description
DPO Reader
Turn Discourse threads into podcasts. Each author gets their own voice.
I got tired of skimming long forum threads. DPO Reader converts them to audio so I can listen while doing other things. Point it at any Discourse thread and it'll synthesize the whole thing with different voices for each participant.
Install
# uv (recommended)
uv tool install dpo-reader
# Or run directly without installing
uvx dpo-reader listen "https://discuss.python.org/t/your-thread"
# pipx works too
pipx install dpo-reader
Usage
# Basic: generate audio and play it
dpo-reader listen "https://discuss.python.org/t/your-thread"
# Interactive TUI with read-along highlighting
dpo-reader listen "https://discuss.python.org/t/your-thread" --ui
# Start from a specific post (the /50 in the URL does this automatically)
dpo-reader listen "https://discuss.python.org/t/your-thread/12345/50" --ui
# Or explicitly with -s
dpo-reader listen "https://discuss.python.org/t/your-thread" -s 50
# Export without playing
dpo-reader export "https://discuss.python.org/t/your-thread" -o thread.wav
# See what you're getting into before generating
dpo-reader info "https://discuss.python.org/t/your-thread"
dpo-reader preview "https://discuss.python.org/t/your-thread"
Options
-o, --output PATH Output file (default: output.wav)
-e, --engine ENGINE openai | bark | piper
-s, --start-post INT Start from this post number
-n, --max-posts INT Limit number of posts
--ui Interactive TUI with controls
--no-attribution Skip "Author says:" prefix
--no-play Don't auto-play after generating
-c, --cache-dir PATH Cache audio chunks (useful if generation crashes)
-p, --pause FLOAT Seconds between posts (default: 1.5)
-f, --file PATH Load from local JSON (testing)
TTS Engines
| Engine | Quality | Speed | Notes |
|---|---|---|---|
| OpenAI | Best | Fast | Needs API key, costs money |
| Bark | Excellent | Slow (~10s/sentence) | Local, wants a GPU |
| Piper | Good | Fast (~0.1s/sentence) | Local, CPU-only |
Bark is the default. It runs locally and produces natural-sounding speech with good intonation. A GPU helps a lot but isn't strictly required.
OpenAI (-e openai) sounds the best if you have an API key. Get one at platform.openai.com/api-keys and set OPENAI_API_KEY in your environment or a .env file.
Piper (-e piper) is the lightweight option. Install with uv pip install dpo-reader[piper]. Good for batch processing or machines without GPUs.
TUI Controls
When using --ui:
| Key | Action |
|---|---|
| Space | Play/Pause |
| ←/→ | Skip 5 seconds |
| ↑/↓ | Speed up/down |
| n/p | Next/Previous post |
| l | Toggle logs |
| q | Quit |
Requirements
Python 3.10-3.13. The onnxruntime dependency doesn't support 3.14 yet.
Development
git clone https://github.com/JacobCoffee/dpo-reader.git
cd dpo-reader
make dev
make lint # Linting
make type-check # Type checking
make test # Tests
make ci # All of the above
# Test TTS locally
make test-listen # Piper
make test-bark # Bark
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dpo_reader-0.1.0.tar.gz.
File metadata
- Download URL: dpo_reader-0.1.0.tar.gz
- Upload date:
- Size: 26.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8328ef1a412f0156e8004bf64b5a584f17e2044673ef2f69ef2f3dda0823d6ac
|
|
| MD5 |
fe8b65a152259da0252598f4360b1712
|
|
| BLAKE2b-256 |
ae574939e2731d4a02abb1f45dce1d82e5a106de93eb4a7db2aec23a986986a5
|
Provenance
The following attestation bundles were made for dpo_reader-0.1.0.tar.gz:
Publisher:
publish.yml on JacobCoffee/dpo-reader
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dpo_reader-0.1.0.tar.gz -
Subject digest:
8328ef1a412f0156e8004bf64b5a584f17e2044673ef2f69ef2f3dda0823d6ac - Sigstore transparency entry: 833857836
- Sigstore integration time:
-
Permalink:
JacobCoffee/dpo-reader@89baf6f556eaaa2f9cb035bdb9f12bc8f32e4695 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/JacobCoffee
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@89baf6f556eaaa2f9cb035bdb9f12bc8f32e4695 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file dpo_reader-0.1.0-py3-none-any.whl.
File metadata
- Download URL: dpo_reader-0.1.0-py3-none-any.whl
- Upload date:
- Size: 29.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
274b2459fad463526281ebfb66bc34696652ce437ee411a464307c7a7c2c600f
|
|
| MD5 |
6ee4e39a8a701e7bff166fbdc137feb7
|
|
| BLAKE2b-256 |
ffda54bf3b6a9ecb3f22e74e9a015ae0d0ffd5ca69403d5e772455a44569fd5f
|
Provenance
The following attestation bundles were made for dpo_reader-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on JacobCoffee/dpo-reader
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dpo_reader-0.1.0-py3-none-any.whl -
Subject digest:
274b2459fad463526281ebfb66bc34696652ce437ee411a464307c7a7c2c600f - Sigstore transparency entry: 833857840
- Sigstore integration time:
-
Permalink:
JacobCoffee/dpo-reader@89baf6f556eaaa2f9cb035bdb9f12bc8f32e4695 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/JacobCoffee
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@89baf6f556eaaa2f9cb035bdb9f12bc8f32e4695 -
Trigger Event:
workflow_dispatch
-
Statement type: