RadioSync Hub
Your radio catalogue, ready for your podcast player. A self-hosted service that collects podcast metadata from selected Polish radio stations, retrieves protected audio URLs where supported, and exposes feeds for compatible clients.
| Project | Deployment | Storage | Output |
|---|---|---|---|
| Self-hosted service | Docker Compose | SQLite | RSS + OPML |
Get started · Supported stations · Configure · Use your feeds
Designed for a home server or Raspberry Pi. Station credentials and synchronization are managed from the web dashboard.
Browse all sections
Features
| Capability | Included behavior |
|---|---|
| Collect | Scheduled synchronization with APScheduler; manual station or programme sync. |
| Subscribe | RSS for synchronized episodes; master and station-specific OPML files. |
| Manage | FastAPI dashboard, error acknowledgement, and grouped errors and warnings. |
| Diagnose | Response snapshots for unexpected API or HTML responses; ntfy error notifications. |
| Store | SQLite through SQLAlchemy; encrypted passwords, tokens, and cookies. |
| Deploy | Docker Compose with persistent local data; CSRF protection for dashboard actions. |
Supported stations
| Station | Catalogue and feeds | Access |
|---|---|---|
| Radio 357 | API metadata with an initial catalogue index and incremental sync. | Authentication for protected audio URLs. |
| Radio Nowy Świat | Programme and episode pages; recent sync followed by older catalogue backfill. | Logged-in session for patron-only content. |
| Radio Jazz FM | Discovery of public programme RSS feeds; links to the station's original feeds. | Public feeds. |
Station-specific synchronization details
Radio 357
- Fetches podcast metadata from the Radio 357 content API.
- Performs an initial metadata index of the available catalogue.
- On later synchronizations, stops when it reaches a page containing only known episodes.
- Retrieves protected audio URLs separately using an authenticated gateway.
- Stores and refreshes the Radio 357 access token.
Radio Nowy Świat
- Fetches programme and episode pages from the station website.
- Uses a logged-in web session for patron-only content.
- Stores the authenticated cookies securely in the local database.
- Synchronizes recent episodes before gradually backfilling older pages.
- Uses an adaptive cooldown for programmes that publish infrequently.
- Retries request timeouts up to three times with a 30-second delay.
- Reports unavailable episode players as warnings instead of failing the entire synchronization.
Radio Jazz FM
Radio Jazz FM is treated as a catalogue of public RSS feeds. The service discovers available programmes and links directly to the original feeds hosted by the station.
Requirements
- Docker Engine
- Docker Compose
- A Gitea or Git server is optional and only needed for source-code hosting
Getting started
Prepare the local files
From the repository root:
cp docker-compose.example.yml docker-compose.yml
cp .env.example .env
Both local configuration files are ignored by Git. The Compose example exposes port 8000 directly.
Set your keys
Follow Configuration to generate and set both keys in .env before starting the service.
Start the service
docker compose up -d --build
Open the dashboard: http://localhost:8000, or your server's address on port 8000. Configure station credentials in the dashboard.
The data/ directory is mounted into the container, so the database survives container rebuilds.
View logs or stop the service
# Follow application logs
docker compose logs -f radiosync
# Stop the service
docker compose down
Configuration
Copy .env.example to .env in the repository root, then set both keys:
RADIOSYNC_SECRET_KEY=generate-a-long-random-value
RADIOSYNC_CONFIG_KEY=generate-a-fernet-key
Generate the values with Python 3 (only needed for this setup step):
python3 -c "import secrets; print(secrets.token_urlsafe(48))"
python3 -c "import base64, secrets; print(base64.urlsafe_b64encode(secrets.token_bytes(32)).decode())"
RADIOSYNC_SECRET_KEY signs browser sessions. RADIOSYNC_CONFIG_KEY encrypts sensitive values stored in SQLite.
Keep your keys — Back up both keys and keep them stable after storing credentials. Losing or changing
RADIOSYNC_CONFIG_KEYmakes existing encrypted configuration unreadable.
The repository includes .env.example as a template. Never commit .env, the SQLite database, or the contents of data/.
Usage
The dashboard allows you to:
- configure station credentials and synchronization limits,
- start or stop synchronization jobs,
- test authentication sessions,
- force a station or programme synchronization,
- review errors and warnings,
- acknowledge problems that have been read,
- test ntfy notifications.
Available feed endpoints:
/feeds/Podcasts.opml
/feeds/{station}.opml
/feeds/{station}/{program-slug}.xml
Station names are radio357, rns, and jazz.
Diagnostics
Diagnostic codes and response snapshots
Unexpected responses are recorded with a diagnostic code such as:
HTTP_ERRORNETWORK_ERRORNETWORK_TIMEOUTINVALID_JSONAPI_SCHEMA_CHANGEDHTML_SCHEMA_CHANGEDAUTH_OR_HTML_CHANGED
When possible, the response is saved under data/diagnostics/ for investigation. Diagnostic snapshots can contain website HTML and should be treated as local operational data.
Architecture
Station APIs / websites → Scrapers → Local SQLite catalogue → RSS / OPML
↑
Scheduler + web dashboard
Repository layout
.
├── core/
│ ├── database.py
│ ├── diagnostics.py
│ ├── notifier.py
│ ├── rss_generator.py
│ └── scrapers/
│ ├── jazz.py
│ ├── radio357.py
│ └── rns.py
├── data/ # local database and diagnostics; ignored by Git
├── static/ # static assets, including the favicon
├── templates/ # Jinja2 dashboard templates
├── Dockerfile
├── docker-compose.example.yml
├── main.py
├── requirements.txt
└── .env.example
Limitations
This project relies on unofficial endpoints and the current HTML structure of the station websites. The stations may change those interfaces without notice. The scrapers therefore validate responses, retry transient failures, preserve existing data, and report unexpected changes through the dashboard and ntfy.
Some programmes or episodes may require an active patron account. A successful login does not guarantee that every episode has an available player or audio URL.
The service is intended for private, personal use. Make sure your use complies with the stations' terms, applicable law, and the access rights associated with your account.
Development
Install dependencies in a virtual environment if you want to run the application outside Docker:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
RADIOSYNC_SECRET_KEY=local-development-key python -m uvicorn main:app --reload
The application currently has no dedicated automated test suite. At minimum, validate Python syntax before committing:
python -m py_compile main.py core/*.py core/scrapers/*.py
Documentation
| Reference | What you will find |
|---|---|
| Environment example | Required application keys. |
| Compose example | Portable container deployment. |
| README style guide | Shared documentation structure and presentation rules. |
Credits
This project was developed with assistance from GitHub Copilot. Human decisions, configuration, deployment, and responsibility for running the service remain with the project owner.
License
No project-level license file is currently included in this repository. Third-party components retain their own licenses.