A local-first podcast feed generator and editor.
Project description
offshore
Local-first podcast feed generator and editor. Manage metadata locally and generate feed from assets hosted anywhere.
Conventional podcast hosts like Fireside or Transistor work fine, but they can be expensive for small shows, and handing all your data to a single service is a real dependency. Self-hosted alternatives exist, but most are still designed as full-stack replacements that bundles frontend website, audio storage, and feed generation into one thing.
A podcast feed is just a plain-text XML file. Hosting a handful of audio files and a text file is much easier, and cheaper than running a media server with a web frontend and database. And if you don’t want the website that comes with a podcast host then that part just becomes redundant.
offshore aims to be a more basic solution. It simply helps you manages your show metadata locally and generates feed you can host wherever makes sense — a static site, an object storage, a CDN. Audio files and any other asset live wherever you want to host them. offshore ensures your metadata and assets comply with podcast standards, but otherwise stays out of the way. It can be used as a local feed generator, or reliably on a server to serve the feed directly.
Installation
Python 3.11+.
pip install -e .
Data layout
Shows are stored in a dedicated directory outside the project, defaulting to $XDG_DATA_HOME or $HOME. On first run you will be prompted to confirm or change this.
~/offshore/ # default shows root
<slug>/
<slug>.json # show + episodes
<slug>.lock.json # feed stability lock
podcast.xml # generated
rss.xsl # generated
Each show lives in its own subdirectory. The JSON contains all show metadata and episodes. The lock file records each episode's GUID and pub_date to catch accidental edits that would reorder the feed for existing subscribers.
To inspect or change the configured root:
offshore config # show current setting
offshore config --shows-dir ~/mypodcasts # update it
Usage
Web UI
offshore serve
Opens at http://127.0.0.1:8765. On first run with no shows, the landing page offers three options: New, Import, or Open an existing file.
All CLI commands are also available from the UI.
CLI
New show:
offshore init <slug>
Import from an RSS feed URL or an offshore JSON file:
offshore import <url-or-path> --slug <slug> --feed-self-url <public-feed-url>
Validate:
offshore check # run checks (structural + remote assets)
offshore check --no-remote # skip network checks
Probe audio metadata:
offshore probe --all # all episodes
offshore probe <guid> # single episode
Probing downloads only the first 256 KB of each file to read the header. Results are cached in the episode JSON.
Generate feed:
offshore generate
offshore generate --force # skip check failures
offshore generate --output-dir <path> # write to a different directory
Output goes to <shows-root>/<slug>/podcast.xml by default. Falls back to the current directory if the shows root is not writable.
Republish an episode (changes pub_date, updates lock):
offshore republish <guid> --date <ISO8601>
Open a specific show file:
offshore serve --show /path/to/<slug>.json
Serving the feed
shows/<slug>/ is a static directory. Point nginx (or any web server) at it:
server {
listen 80;
server_name your.domain.com;
root /path/to/shows/my-show;
location /podcast.xml {
add_header Content-Type "application/rss+xml; charset=utf-8";
}
}
Make sure feed_self_url in the show JSON matches the public URL you serve the feed at.
To redirect feed output to a shared web root:
offshore generate --output-dir /var/www/html
offshore serve --output-dir /var/www/html
offshore serve can also be exposed directly behind a reverse proxy (--host 0.0.0.0). Each show's feed is available at /download/<slug>/podcast.xml.
Audio
MP3 (audio/mpeg) and M4A/AAC (audio/mp4, audio/aac) are accepted. Format is verified when an episode is saved. If the URL is unreachable at save time you can skip verification and probe later.
Checks
offshore check runs before every generate. Fail-level findings block generation; --force overrides. Checks cover:
- All required fields on the show and each episode
- Audio probe cache present (duration + byte count are required for a valid enclosure)
- Audio and image URLs reachable with the correct MIME type
- Episode pub_date is not in the future
- All person roster references resolve
- Episode GUID and pub_date match the lock file
Development
tests/ includes a sample feed and a group of scripts to validate the output for development.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file offshore_podcast-0.1.0.tar.gz.
File metadata
- Download URL: offshore_podcast-0.1.0.tar.gz
- Upload date:
- Size: 55.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
04cd894f4efc5a241f5a2ff17e2d34b23ef6e4809f3ee307ef371170c00ce8ad
|
|
| MD5 |
008871f9904615e9474188f9ad1b8512
|
|
| BLAKE2b-256 |
2e44d2578fbadbb77b1877b635d196ec5da78284ca9f5f4f14db8c0b4676ac03
|
File details
Details for the file offshore_podcast-0.1.0-py3-none-any.whl.
File metadata
- Download URL: offshore_podcast-0.1.0-py3-none-any.whl
- Upload date:
- Size: 61.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
60f0e6cecd36bd439c74717937e517abd37bb3e1756c0e10180728959b969515
|
|
| MD5 |
95987fe2f32bb5533738085e73975e08
|
|
| BLAKE2b-256 |
1379b5ad8cc2d1cab78e2dbb2c003a6f6a3a042b3ccf533c5268550e96042034
|