Personal project — built for self-use and as a testbed for exploring different AI-assisted programming techniques and tools. Shared as-is with no guarantees of stability, support, or general-purpose usability.
Narratio is a TypeScript-based application that periodically fetches articles from RSS feeds, converts them into natural-sounding audio files using the Piper TTS engine, and serves them as a valid Podcast RSS feed compatible with services like Pinepods.
- Node.js: version 24 or higher
- Docker & Docker Compose (for easy deployment)
-
Clone the repository:
git clone <repository-url> cd rss-to-podcast
-
Install dependencies:
npm install
-
Build the project:
npm run build
The application can be configured using environment variables. Default values are provided in compose.yaml.
Once the application is started with an RSS_URL, it stores this URL in its database to maintain consistency. If you need to change the RSS feed URL later, you must reinitialize the database, which will also delete all previously fetched articles and generated audio files.
Manual Reinitialization:
If you are running the worker manually, use the --force-reset flag:
npm run start:worker -- <NEW_RSS_URL> --force-resetDocker Reinitialization:
If you are using Docker Compose, you can update the RSS_URL in your compose.yaml (or environment) and then run the following command to reinitialize:
docker compose run --rm worker npm run start:worker -- <NEW_RSS_URL> --force-resetAlternatively, you can manually delete the contents of the data/ directory and restart the stack.
RSS_URL: The RSS feed URL to parse (required).POLL_INTERVAL: Cron expression for periodic polling (e.g.,0 * * * *for hourly). If not set, the worker runs once and exits.PIPER_HOST: Host of the Piper TTS engine Wyoming API (default:localhost).PIPER_PORT: Port of the Piper TTS engine Wyoming API (default:10200).MAX_AUDIO_FILES: Maximum number of audio files to keep (default:Infinity).MAX_AUDIO_SIZE_MB: Maximum total size of audio files in MB (default:Infinity).NODE_ENV: Set toproductionfor optimized execution.TTS_TIMEOUT: Timeout for the TTS engine in seconds (default:300).TTS_MAX_RETRIES: Maximum number of times to retry TTS generation for a failed article on subsequent polls (default:3). Set to0to disable retries.RSS_FETCH_TIMEOUT: Timeout for fetching the RSS feed in milliseconds (default:30000).
PORT: The port the web server will listen on (default:3000).PODCAST_TITLE: Title of your generated podcast (default:Narratio).PODCAST_DESCRIPTION: Description of your podcast (default: dynamic based on feed URL).PODCAST_AUTHOR: Author name for the podcast (default:Narratio Worker).PODCAST_LANGUAGE: Language code (default:en).PODCAST_ITUNES_AUTHOR: iTunes-specific author name (defaults toPODCAST_AUTHOR).PODCAST_ITUNES_SUMMARY: iTunes-specific summary (defaults toPODCAST_DESCRIPTION).PODCAST_ITUNES_OWNER_NAME: iTunes-specific owner name (defaults toPODCAST_AUTHOR).PODCAST_ITUNES_OWNER_EMAIL: iTunes-specific owner email (default:worker@example.com).PODCAST_ITUNES_CATEGORY: iTunes-specific category (default:Technology).UNAVAILABLE_MESSAGE: Message to be used for purged articles (default:This content is no longer available on the server.).
The easiest way to run the full stack (Worker, Server, and TTS engine) is using Docker Compose:
docker compose up -dThis will:
- Start the TTS engine using the Piper image.
- Start the Worker to fetch RSS feeds and generate audio.
- Start the Server to provide the podcast feed at
http://localhost:3000/rss.
If you prefer to run components manually:
-
Start the Worker to ingest feeds:
npm run start:worker -- <RSS_URL1> <RSS_URL2>
-
Start the Server to serve the podcast:
npm run start:server
We use Jest for testing. To run the full test suite:
npm testTo check for code style issues:
npm run lintTo automatically fix linting issues:
npm run lint:fix- Worker: Periodically polls RSS feeds, extracts content and images (feed image, iTunes/media/HTML), and calls the TTS engine via Wyoming protocol (TCP).
- TTS Engine: Converts text to WAV using Piper.
- SQLite: Stores article metadata, processing status, and image URLs (both feed-level and per-article).
- Server: Express-based web server that serves the generated MP3 files and the Podcast XML.