Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/Yetangitu/Spodcast

Spodcast is a caching Spotify podcast to RSS proxy. Using Spodcast you can follow Spotify-hosted netcasts/podcasts using any player which supports RSS, thus enabling the use of older hardware which is not compatible with the Spotify (web) app.
https://github.com/Yetangitu/Spodcast

podcast-fetcher rss-generator spotify-api

Last synced: 7 days ago
JSON representation

Spodcast is a caching Spotify podcast to RSS proxy. Using Spodcast you can follow Spotify-hosted netcasts/podcasts using any player which supports RSS, thus enabling the use of older hardware which is not compatible with the Spotify (web) app.

Awesome Lists containing this project

README

        

# _Spodcast_

_Spodcast_ is a caching _Spotify_ podcast to RSS proxy. Using _Spodcast_ you can follow _Spotify_-hosted netcasts/podcasts using any player which supports RSS, thus enabling the use of older hardware which is not compatible with the _Spotify_ (web) app. _Spodcast_ consists of the main _Spodcast_ application - a Python 3 command line tool - and a PHP-based RSS feed generator. It uses the _librespot-python_ library to access the Spotift API. To use _Spodcast_ you need a (free) _Spotify_ account.
_Spodcast_ only supports the _Spotify_ podcast service, it does not interface with the music streaming service.

## How does it work

_Spotify_ hosts podcasts through their proprietary API and does not offer an RSS feed, making it mandatory to use the _Spotify_ (web) app to follow these shows. This makes it impossible to follow _Spotify_-hosted shows on any device which does not support the _Spotify_ (web) app. *_Spodcast_* solves this problem by creating an RSS feed out of data returned through the _Spotify_ podcast API. This feed can be served by any web server which supports PHP. By running *_Spodcast_* through a task scheduler (*cron* on \*nix, *Task Scheduler* on Windows) the feed will be kept up to date without the need for intervention. Have a look at these glorious *ASCIIGraphs™* which should answer all unasked questions:

### _Spodcast_ regularly queries _Spotify_ for new episodes...
```
--------------
|task scheduler|
--------------
| ___________
------- APIv1 ----V--- / \
|Spotify|- - - - >|Spodcast|------>| File system |
------- -------- \___________/
```
### You want to listen to an episode using your old, unsupported but still functional phone...
```
_____ ............
___________ ---------- | | . o O |bla bla bla.|
/ \ |Web server| RSS | YOUR| ````````````
| File system |------>| + PHP |------>| OLD |
\___________/ ---------- |PHONE|
|_____|

```
Thus, by the simple expedient of using a piece of code which produces another piece of code which is used by yet another piece of code to speak to that old, creaky but still functional phone the latter is saved from early forced retirement. You can both feel virtuous for not adding another piece of waste to the pile, provident for not spending funds on a new device which does the same as the old one, smart for escaping the trap of planned obsolescence and whatever other emotion you prefer, including none whatsover.

## Installation

_Spodcast_ is available as an installable package on PyPi, just run:
```
$ pip install spodcast
```
...and you're set in case you only want to use _Spodcast_ from your own account. Run this command as root to install it globally which makes it available to e.g. the web server user (`www-data` on Debian and derived distributions), this is necessary if you want to make use of the RSS feed service.

_Spodcast_ can be installed from source by running `pip install .` (or `pip install -e .` if you want to be able to hack the code without needing to re-install after every change) from within the package root directory:
```shell
$ git clone https://github.com/Yetangitu/spodcast.git
$ cd spodcast
$ pip install .
```

Once installed this way it can be uninstalled using `pip uninstall spodcast` if so required. If you're planning to use the RSS proxy and web UI you need to make sure the `spodcast` command is available to the web server user.

## Usage with Docker:

Pre-built images are [available at Docker Hub](https://hub.docker.com/r/heywoodlh/spodcast). Refer to documentation below on how to build the image locally if that is desired.

### A Note on Environment Variables:

The Spodcast images are built to either be configured exclusively through environment variables OR by passing arguments to the image -- if arguments are passed to the container any environment variables will be ignored.

Here is the list of permitted environment variables with a brief description:

| Variable | Default Value | Description |
|----------|---------------|-------------|
| SPOTIFY_PASSWORD | None | Spotify password |
| SPOTIFY_USERNAME | None | Spotify username |
| SPOTIFY_PODCAST_URLS | None | URLs of podcasts to download from spotify (separated by space) |
| CRON_SCHEDULE | `0 0 * * Sun` | Cron schedule for how often [heywoodlh/spodcast-cron](https://hub.docker.com/r/heywoodlh/spodcast-cron) will attempt to download content from Spotify |
| SPODCAST_ROOT | `/data` | Where all assets will go in the container filesystem |
| SPODCAST_HTML | `${SPODCAST_ROOT}/html` | Where HTML content will be places in container filesystem |
| SPODCAST_CONFIG_JSON | `${SPODCAST_ROOT}/spodcast.json` | Location of the spodcast config file |
| SPOTIFY_CREDS_JSON | `${SPODCAST_ROOT}/creds.json` | Location of the file containing Spotify credentials that Spodcast will use to continue logging in |
| SPOTIFY_RC_PATH | `${SPODCAST_ROOT}/spotify.rc` | Location of file containing creds to initially login |
| MAX_EPISODES | `10` | Max amount of episodes to download per podcast |
| LOG_LEVEL | `info` | Log level of Spodcast |
| CHUNK_SIZE | `50000` | Download chunk size |
| RSS_FEED | `yes` | Create an RSS feed for a web server |
| TRANSCODE | `no` | Transcode to MP3 format (i.e. so iOS devices can play the audio files) |
| LANGUAGE | `en` | Language of the content |
| SKIP_EXISTING | `yes` | Do not re-download episodes that already exist on filesystem |

`SPOTIFY_PASSWORD`, `SPOTIFY_USERNAME`, and `SPOTIFY_PODCAST_URLS` are the only ones that are absolutely required -- everything else has a default value that is considered sane that it will fall back to if left undefined.

### Usage with Docker-Compose and environment variables:

The following `docker-compose.yml` (also available at [docker/docker-compose.yml](docker/docker-compose.yml)) should work to easily deploy Spodcast, a web server and PHP server:

```
services:
spodcast-cron:
image: heywoodlh/spodcast-cron:latest
volumes:
- spodcast_data:/data
restart: unless-stopped
environment:
- CRON_SCHEDULE=0 * * * *
- SPOTIFY_PODCAST_URLS=https://open.spotify.com/show/4rOoJ6Egrf8K2IrywzwOMk
- SPOTIFY_PASSWORD=myawesomepassword
- [email protected]
- MAX_EPISODES=1

spodcast-php:
image: php:7-fpm
volumes:
- spodcast_data:/data
restart: unless-stopped
user: "101:101"
networks:
- spodcast

spodcast-web:
image: heywoodlh/spodcast-web:latest
volumes:
- spodcast_data:/data
restart: unless-stopped
networks:
- spodcast
ports:
- 8080:80

networks:
spodcast:
volumes:
spodcast_data:
```

*Note: if this is the first time running Spodcast, the web server will error out with a 404. That is due to no HTML content having been setup by Spodcast yet. When running this the first time, it would be recommended to set `CRON_SCHEDULE` to something a bit more aggressive like `* * * * *` so that way episodes will be downloaded every minute and then once the initial HTML content has been populated change the `CRON_SCHEDULE` to something less aggressive.

### Using the Docker CLI and supplying arguments:

Prepare `spodcast` directory (this example assumes you will store Spodcast's data in `/tmp/spodcast`):

```
mkdir -p /tmp/spodcast

echo 'spotify_username spotify_password' > /tmp/spodcast/spotify.rc
```

Login to Spotify and configure `spodcast` initially:

```
docker run -it -v /tmp/spodcast:/data heywoodlh/spodcast -c /data/spodcast.json --root-path /data/html --log-level info --credentials-location /data/creds.json -p -l /data/spotify.rc
```

Now run `spodcast` (this example will download The Joe Rogan Experience to `/tmp/spodcast/html` on the Docker host):

```
docker run -it -v /tmp/spodcast:/data heywoodlh/spodcast -c /data/spodcast.json --log-level info --max-episodes 10 'https://open.spotify.com/show/4rOoJ6Egrf8K2IrywzwOMk'
```

#### Build the image locally:

If you would prefer to build the image locally:

```
git clone https://github.com/Yetangitu/Spodcast && cd Spodcast

docker build -t spodcast -f docker/Dockerfile .
```

Docker usage will be exactly the same as the examples above with the exception that you will want to replace the `heywoodlh/spodcast` image with `spodcast`.

## Usage
To use _Spodcast_ you need a (free) _Spotify_ account, if you don't have one yet you'll need to take care of that first at https://www.spotify.com/signup/ . If you plan to use the RSS proxy feature you'll also need a web server to serve the RSS feed(s), any server which supports PHP will do here. See [Web server requirements](#web-server-configuration) for more information on how to configure the server.

Here's `spodcast` displaying its help message:
```
$ spodcast -h
usage: spodcast [-h] [-c CONFIG_LOCATION] [-p] [-v] [-l LOGIN] [--root-path ROOT_PATH]
[--skip-existing SKIP_EXISTING] [--retry RETRY] [--max-episodes MAX_EPISODES]
[--chunk-size CHUNK_SIZE] [--download-real-time DOWNLOAD_REAL_TIME]
[--language LANGUAGE] [--credentials-location CREDENTIALS_LOCATION]
[--rss-feed RSS_FEED] [--transcode TRANSCODE] [--log-level LOG_LEVEL]
[urls ...]

A caching Spotify podcast to RSS proxy.

positional arguments:
urls Download podcast episode(s) from a url. Can take multiple urls.

optional arguments:
-h, --help show this help message and exit
-c CONFIG_LOCATION, --config-location CONFIG_LOCATION
Specify the spodcast.json location
-p, --prepare-feed Installs RSS feed server code in ROOT_PATH.
-v, --version show program's version number and exit
-l LOGIN, --login LOGIN
Reads username and password from file passed as argument and stores
credentials for later use.
--root-path ROOT_PATH
set root path for podcast cache
--skip-existing SKIP_EXISTING
[yes|no] skip files with the same name and size. Defaults to "yes".
--retry RETRY retry count for Spotify API access
--max-episodes MAX_EPISODES
number of episodes to download
--chunk-size CHUNK_SIZE
download chunk size
--download-real-time DOWNLOAD_REAL_TIME
simulate streaming client
--language LANGUAGE preferred content language
--credentials-location CREDENTIALS_LOCATION
path to credentials file. If a relative path is used the file will be
stored in the same directory as the configuration file (-c
/path/to/config.json -> /path/to).
--rss-feed RSS_FEED [yes|no] add a (php) RSS feed server and related metadata for feed. To
manage feeds, point a web server at the spodcast root path as configured
using --root-path. Defaults to "yes".
--transcode TRANSCODE
[yes|no] transcode ogg/vorbis to mp3 (where applicable) - only needed
for devices which do not support open formats (e.g. iOS). Defaults to
"no".
--log-level LOG_LEVEL
log level (debug/info/warning/error/critical)
```

### Using _Spodcast_ to proxy _Spotify_ podcasts to RSS
The following example shows how to use the `spodcast` command to prepare the feed root directory and add a _Spotify_ account to be used. It specifies the configuration file to create (`-c /mnt/audio/podcast/spodcast.json`) and the root path where podcasts will be downloaded to (`--root-path /mnt/audio/spodcast`). The `-p` option tells _spodcast_ to prepare the RSS feed server in the root directory which will also be used to store the credential file created by the `-l spotify.rc` command. That `spotify.rc` file is a plain text file containing the username and password (separated by a single space character) to use to login to _Spotify_. It is only needed to create the stored credentials file(s) so it can be deleted once _Spotcast_ is up and running. If one or more of your preferred listening devices does not supports open audio codecs - e.g. because it runs iOS - you can use `--transcode yes` to enable transcoding of such streams to `mp3`. Transcoding is performed using _ffmpeg_ which needs to be available on your _Spodcast_ host. It can take a considerable amount of time depending on the hardware you're using to run _Spodcast_ so only enable this option when there are no other options.
```
spodcast -c /mnt/audio/podcast/spodcast.json --root-path /mnt/audio/spodcast -p -l /home/exampleuser/spotify.rc
```
Configure the [Web server](#web-server-configuration) using the path given as root path (in this example that would be `/mnt/audio/spodcast`) as web root, making sure to exclude files with `.json` and `.info` extenstions to avoid leaking your _Spotify_ credentials (even though these are stored in hashed form using hashed file names). Now point a browser at the site you configured for _Spodcast_ and you're ready to add the first show or episode. This is done easily by entering the _Spotify_ show/episode url (e.g. `https://open.spotify.com/show/4rOoJ6Egrf8K2IrywzwOMk` for _The Joe Rogan Experience_ for the whole show, `https://open.spotify.com/episode/2rYwwE7hcpgsDo9vRVHxAI?si=24fb00294b7f40db` for a specific episode, notice the `show` and `episode` parts of these links) and either hitting _Enter_ or clicking the _Add_ button. _Spodcast_ will now create a directory under the given root path, add the `.index.php` RSS feed generator script and the `index.info` show info URL used by that script and the RSS manager script and whatever episode(s) you decided to sync.

Once the initial feed has been created it can be kept up to date by enabling the feed update service found in the _Settings_ menu. Select the update frequency and the start time and click _Update_, this will create a _cron_ job for the web server which will run the _Spodcast_ manager script to update feeds. If the system scheduler can not be used for some reason - e.g. because the web server user is not allowed to create cron jobs or because the web server is run from a Docker container which does not support cron jobs - you can use the _webcron_ endpoint to initiate update runs. Just point any web client at `SPODCAST_URL/?action=update_shows` to run an update and get a json-encoded report on what has been updated. By running a command line web client - _curl_ and _wget_ are good options - as a user cron job at regular intervals the feeds can be kept up to date.

While the update frequency is configured for all shows simultaneously this is not the case for the number of episodes to _sync_ and the number to _keep_ in cache, these can be configured individually for each show. The idea here is that some shows may publish more than one episode between update intervals so by fetching the last X episodes on each update nothing will be missed. Episodes which have already been synced will not be synced again so no time or bandwidth is wasted. In the same vein the number of episodes to _keep_ can be configured to make sure your RSS clients have the opportunity to download these before they are rotated out of cache. Once more than X (being the value chosen for _keep_) episodes have been downloaded the oldest episodes will be deleted to keep the total no more than X.

Point your RSS clients at the _Spodcast_ feed URL for this show and you should see new episodes appear after they were published on _Spotify_ and subsequently picked up on the next update. For the example given in the [Web server requirements](#web-server-configuration) example that URL would be `http://spodcast.example.org/The_Joe_Rogan_Experience`.

Here's what the Spodcast feed manager looks like:

![Spodcast feed manager](/resources/screenshots/spodcast_showing_a_list_of_shows_likely_to_disappear_from_spotify_sooner_or_later.png?raw=true "Spodcast feed manager")

...and on smaller screens it looks like this:

![Spodcast feed manager on a small screen](/resources/screenshots/spodcast_is_responsive.png?raw=true "Spodcast feed manager, vertical layout on smaller screens")

The settings screen is simple and concise:

![Spodcast feed manager settings](/resources/screenshots/spodcast_settings.png?raw=true "Spodcast feed manager: Settings")

Each show has its own _sync_ and _keep_ settings. Use the _Delete_ button to, well, delete the show. Use _Refresh_ to retrieve the last [_sync_] episodes, skipping those which have already been synced.

![Show controls](/resources/screenshots/sync_keep_delete_refresh.png?raw=true "Configure _sync_ and _keep_ settings for each show")

### Using the _Spodcast_ CLI command to download a single episode
_Spodcast_ can also be used stand-alone (without the need for a web server) by either just ignoring the feed-related files (`.index.php`, `index.info` plus a `*.info` file for every episode) or by disabling the RSS feed using `--rss no` on the command line. Instead of using the `-l spotify.rc` command to add _Spotify_ credentials it is possible to point _Spotcast_ at a single `credentials.json` file (which will be created if it does not exist yet), `spodcast` wil ask for the username and password when needed. To get single episode links use the _Spotify_ web app and select _Share->Copy Episode Link_ from the episode menu (three dots in the top-right corner of the episode block). The following example shows (an already configured instance of) `spodcast` ready to download a single episode:
```
spodcast -c ~/.config/spodcast/spodcast.json --credentials-location ~/.config/spodcast/credentials.json --rss-feed no https://open.spotify.com/episode/2rYwwE7hcpgsDo9vRVHxAI?si=24fb00294b7f40db
```
Like in the previous example _Spodcast_ will create a directory under the root path with the same name as the show from which the episode is downloaded. The episode will be downloaded into this directory under a `SHOW_NAME_-__EPISODE_NAME.[ogg|mp3]` name. Point a mediaplayer of choice at this file to play the episode.
In "manual" mode _Spodcast_ does not do anything by itself, feeds can be kept up to date by running _Spotcast_ with the required settings for `--max-episodes` (which is the value used for _sync_ in the web UI) and the show URL. Here's how to update the _The Joe Rogan Experience_ show using the `spodcast` CLI command, syncing the last 3 episodes:
```
spodcast -c ~/.config/spodcast/spodcast.json --rss-feed no --max-episodes 3 https://open.spotify.com/show/4rOoJ6Egrf8K2IrywzwOMk`
```
## Web server configuration
_Spodcast_ places a hidden `.index.php` file in the root path and each show directory. The one in the root directory is used to manage feeds while those in the show directories produce RSS feeds based on the information found in all `*.info` files in that directory. Configure the server to serve those `.index.php` files as index to make things work as intended. Don't forget to block all web access to files ending in `.json` and `.info` to make sure you _Spotify_ credentials (which are stored in hashed form in files named `spodcast-cred-MD5_HASH_OF_SPOTIFY_USER_NAME.json` in the root path) can not be accessed. Especially when using the transcoding feature (`--transcode yes`) on less powerful hardware (Raspberry Pi etc.) it can be necessary to increase the timeout for php-fpm/proxy/fastcgi requests. For _nginx_ the following should suffice to produce an unencrypted (HTTP) feed under the domain name `spodcast.example.org` given a feed root directory (as configured using `--root-path`) of `/mnt/audio/spodcast` with _php-fpm 7.4_ listening on `unix:/run/php/php7.4-fpm.sock`, using a 5-minute timeout for fastcgi requests:
```
server {
listen 80;
listen [::]:80;
server_name spodcast.example.org;

root /mnt/audio/spodcast;

index .index.php;

fastcgi_read_timeout 300;
fastcgi_send_timeout 300;

# these files should not be accessible
location ~\.(json|info)$ {
deny all;
return 404;
}

location / {
try_files $uri $uri/ =404;
}

location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
}
}
```
Examples for other web servers can be found elsewhere, this is basically a default PHP configuration with the only difference being that `.index.php` is a hidden file.