RadioSync Hub

Radio stations, a local catalogue, and RSS or OPML feeds

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_KEY makes 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_ERROR
  • NETWORK_ERROR
  • NETWORK_TIMEOUT
  • INVALID_JSON
  • API_SCHEMA_CHANGED
  • HTML_SCHEMA_CHANGED
  • AUTH_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.

S
Description
No description provided
Readme 214 KiB
Languages
Python 74.8%
HTML 24.9%
Dockerfile 0.3%