rss-pod

module
v0.12.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 30, 2026 License: MIT

README

rss-pod logo

rss-pod

Turn what you do not have time to read into podcasts for the road.

简体中文 · Quick start · Configuration

CI status GHCR container Go 1.26.2 MIT license

rss-pod is a self-hosted RSS-to-podcast app. It uses an OpenAI-compatible LLM to turn content into multi-speaker conversations, synthesizes audio with Edge TTS or Azure Speech, and provides a web player and podcast RSS feeds. Listen to the content you care about during your commute, drive, or walk.

rss-pod web player in English

Highlights

  • Content expansion through direct RSS, derived RSS, Jina, and Crawl4AI
  • Ordered LLM fallback and optional content screening before generation
  • Custom dialogue roles and voices, with Edge TTS, Azure Speech, and Azure MultiTalker
  • PostgreSQL + River for durable progress, automatic retries, and resumability
  • S3/MinIO storage for source content, scripts, and audio
  • English and Chinese player, with an optional admin page for episode visibility and screening results
  • Run with one Go binary or Docker container

How it works

flowchart LR
    RSS[RSS feeds] --> Content[Content expansion]
    Content --> Screening{Optional screening}
    Screening -->|allow or disabled| LLM[Dialogue script]
    Screening -->|skip| Skipped[Save screening result]
    LLM --> TTS[Synthesize audio]
    TTS --> Publish[Publish to object storage]
    Publish --> Player[Web player / podcast RSS]

Progress is stored in PostgreSQL. After a restart or temporary provider failure, episodes can continue from completed stages.

Quick start

1. Prepare services and configuration

You need PostgreSQL, S3-compatible storage, at least one OpenAI-compatible LLM, and Edge TTS or Azure Speech. The steps below use Docker, so Go is not required on the host. Provision the external services separately.

git clone https://github.com/synrise25/rss-pod.git
cd rss-pod
cp config.example.yaml config.yaml
cp .env.example .env

Edit the two local files:

  • .env: fill in connection settings and credentials for your database, storage, and chosen LLM.
  • config.yaml: confirm the database name and storage buckets. Create the rsspod-private and rsspod-media buckets, and make media under PUBLIC_MEDIA_BASE_URL publicly readable. Use a non-superuser database account.
  • Start with one LLM service, such as deepseek, and set defaults.llm to [deepseek]. You can keep the example service name while using any compatible provider's endpoint and model.
  • Start with the zhihu-daily source: replace feed.url with your RSS URL and set enabled: true. Its voices use Edge TTS, so Azure is not needed. Leave other sources disabled.

All example sources are disabled by default. check verifies the database, storage, and content services, LLMs, screening backends, and voice profiles used by enabled sources. Unused services do not receive external checks. Database and storage addresses must be reachable from the container; the example's 127.0.0.1 addresses do not point to the host from inside a container.

Need an LLM provider? You can register through this SiliconFlow referral link. You and the maintainer may receive credits when the platform's promotion conditions are met.

2. Check and start

Build from the current checkout so the image matches its configuration example:

docker build -t rss-pod:local .

docker run --rm \
  --env-file .env \
  --volume "$PWD/config.yaml:/app/config.yaml:ro" \
  rss-pod:local check

docker run --rm \
  --env-file .env \
  --volume "$PWD/config.yaml:/app/config.yaml:ro" \
  rss-pod:local migrate

docker run --detach \
  --name rss-pod \
  --restart unless-stopped \
  --publish 127.0.0.1:8080:8080 \
  --env-file .env \
  --volume "$PWD/config.yaml:/app/config.yaml:ro" \
  rss-pod:local run

Open http://localhost:8080. The player selects English or Chinese based on your browser's language. A fresh installation has no episodes until you trigger generation.

3. Generate your first episode

docker exec rss-pod rss-pod poll --sources zhihu-daily --limit 1
docker logs -f rss-pod

--sources takes a source's id; --limit 1 processes one RSS item. Generation runs asynchronously after enqueueing. Refresh the player when it finishes to listen. Subsequent polls follow the source's schedule.cron.

Other ways to run

Prebuilt images at ghcr.io/synrise25/rss-pod support linux/amd64 and linux/arm64, with version tags and latest. For a released image, use the README and configuration example from the matching release.

With Go 1.26.2 or a compatible newer toolchain installed, you can run directly after completing the configuration above:

go run ./cmd/rss-pod check
go run ./cmd/rss-pod migrate
go run ./cmd/rss-pod run

Keep run running and execute go run ./cmd/rss-pod poll --sources zhihu-daily --limit 1 in another terminal to generate your first episode.

Usage and optional features

Player

The player groups episodes by their task's edition date; overnight queueing and retries do not change it. Set the timezone with defaults.schedule.timezone. The player opens the latest date with playable episodes and preloads the next episode to reduce switching delays. Preloading lasts only for the current page.

Admin page

Sign in at /admin to hide or restore episodes and inspect recently skipped content. The page is disabled by default. To enable it, generate a secret:

python3 -c 'import base64, secrets; print(base64.b32encode(secrets.token_bytes(20)).decode())'

Set RSS_POD_ADMIN_TOTP_SECRET in .env to the result. Add the same secret to your authenticator using time-based SHA-1, six digits, and 30 seconds. Login requires only the dynamic code; sessions last 30 minutes. Restart the local process, or recreate the Docker container to load the new environment.

Hidden episodes disappear from the public player and podcast RSS. Their source content, scripts, and audio remain, so unexpired episodes can be restored. Hiding does not extend retention or revoke existing audio links, downloads, or client caches. If you lose your authenticator, replace the secret and restart the service; old sessions become invalid. Keep server clocks synchronized.

Content screening

Screening is disabled by default. To enable it, set enabled to true under defaults.screening or a source's screening. Set services to an independent, ordered chain such as [llm.deepseek] or [jev, llm.deepseek]. Sources can override the defaults; see config.example.yaml for fields.

A valid allow/skip decision ends screening. Provider failures try the next service; if all fail, the task retries. Skipped content produces no script or audio, and its reason is visible in the admin page. Jev uses the JEV_* connection settings in .env and skips when its probability reaches jev.skip_threshold, which defaults to 0.7. For long content, configure a fallback LLM with enough input capacity. Pin Jev model versions to avoid reusing cached decisions after a floating alias changes.

Player notice

Copy notice.example.md to notice.md and set runtime.http.notice_file: notice.md to show a Markdown notice. Empty or missing files produce no notice. Edits appear after a page refresh. Dismissed notices stay hidden until their content changes.

For Docker, add a read-only mount to the startup command:

--volume "$PWD/notice.md:/app/notice.md:ro" \

Commands and recovery

Command Purpose
check Validate configuration, shared infrastructure, and services used by enabled sources
migrate Apply database migrations
poll --sources <id> Poll selected sources; all selects every enabled source
run Run the web service, scheduler, and all job queues
serve Run only the web service and management API
worker Run job queues only; select them with --queues

Recover failed or interrupted episodes:

docker exec rss-pod rss-pod poll --sources zhihu-daily --resume-incomplete

Recovery covers the current RSS poll scope and reuses saved content, scripts, and audio segments. Published or skipped episodes and those with active jobs are not enqueued again.

Configuration

Customize roles and voices with dialogue_profiles and script templates with prompts/. Deployment-specific prompts can be mounted read-only at /app/prompts. Keep real configuration and credentials in local config.yaml, .env, or a secret manager; never commit them.

Security model

The player listens on :8080. Setting an admin secret enables code-protected admin routes on the same port. Health checks, polling, retries, and podcast management routes listen on 127.0.0.1:8081; do not expose or proxy that listener to the internet. Use HTTPS for public access. Point reverse proxies at the player port and preserve the original Host.

Inspiration

rss-pod was inspired by Zenfeed. It focuses on my self-hosted podcast workflow, with different choices around job orchestration and the listening experience.

License

Released under the MIT License.

Directories

Path Synopsis
cmd
rss-pod command
internal
app
tts

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL