2026-09-03 12:25:52 +02:00
2026-09-03 11:57:16 +02:00
2026-09-03 11:57:16 +02:00
2026-09-03 11:57:16 +02:00
2026-09-03 11:57:16 +02:00
2026-09-03 12:25:52 +02:00
2026-09-03 11:57:16 +02:00
2026-09-03 11:57:16 +02:00
2026-09-03 12:25:52 +02:00
2026-09-03 11:57:16 +02:00

RadioSync Hub

RadioSync Hub is a self-hosted podcast synchronization service for selected Polish radio stations. It collects podcast metadata, retrieves protected audio URLs where supported, stores the local catalogue, and exposes podcast feeds for compatible clients.

The project is designed to run on a home server or Raspberry Pi using Docker.

Supported stations

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.

Features

  • FastAPI web dashboard.
  • Automatic synchronization using APScheduler.
  • Manual full-station and single-programme synchronization.
  • SQLite storage through SQLAlchemy.
  • RSS feeds generated for locally synchronized episodes.
  • Master and station-specific OPML files.
  • Error and warning tracking with acknowledgement from the dashboard.
  • Grouping of repeated errors and warnings.
  • Diagnostic snapshots for unexpected API or HTML responses.
  • ntfy notifications for synchronization errors.
  • CSRF protection for dashboard actions.
  • Encrypted configuration values in SQLite, including passwords, tokens, and cookies.
  • Docker Compose deployment.

Requirements

  • Docker Engine
  • Docker Compose
  • A Gitea or Git server is optional and only needed for source-code hosting

Configuration

Create a local .env file next to docker-compose.yml:

RADIOSYNC_SECRET_KEY=generate-a-long-random-value
RADIOSYNC_CONFIG_KEY=generate-a-fernet-key

Generate the values with:

python -c "import secrets; print(secrets.token_urlsafe(48))"
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

RADIOSYNC_SECRET_KEY signs browser sessions. RADIOSYNC_CONFIG_KEY encrypts sensitive values stored in SQLite.

Keep both keys safe and do not change them after the application has stored credentials. Losing or changing the encryption 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/.

Running with Docker

The repository contains docker-compose.example.yml as a portable default configuration. Copy it to docker-compose.yml before starting the service:

cp docker-compose.example.yml docker-compose.yml

The copied docker-compose.yml is ignored by Git, so it can be customized locally without affecting the public repository. The example exposes port 8000 directly, which is convenient for a local installation.

Build and start the service:

docker compose up -d --build

Open the dashboard at:

http://localhost:8000

View container logs:

docker compose logs -f radiosync

Stop the service:

docker compose down

The data/ directory is mounted as a Docker volume, so the database survives container rebuilds.

Dashboard and feeds

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

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.

Project structure

.
├── 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

Important 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

Collaboration with AI

This project was developed with assistance from GitHub Copilot. Human decisions, configuration, deployment, and responsibility for running the service remain with the project owner.

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