# tquin/podqueue
Automate the archiving of podcast feeds, including show notes and images.
This Python project aims for a simple user interface - you just need to modify `podqueue.conf` with your inputs and outputs, and then schedule the program to run periodically.
* https://pypi.org/project/podqueue
* https://github.com/tquin/podqueue
* https://hub.docker.com/r/tquin/podqueue
# What does my config file look like?
The default `podqueue.conf` looks like the below. You have two choices to run this app:
1) Edit this config file with your inputs and outputs, or
2) Overwrite these values with the CLI flags below.
```
[podqueue]
opml = podqueue.opml
dest = pq/
log_file = podqueue.log
# Please note, '%' in time_format must be escaped with '%%'
time_format = %%Y-%%m-%%d
verbose = False
```
# CLI arguments
As mentioned, if any of these CLI arguments are specified, they will **overwrite** any values in the config file.
* `-o`, `--opml` - Pass an OPML file that contains a podcast subscription list.
* `-d`, `--dest` - The destination folder for downloads. Will be created if required, including sub-directories for each separate podcast.
* `-t`, `--time_format` - Specify a time format string for JSON files. Defaults to '%Y-%m-%d' (2022-06-31) if not specified.
* `-v`, `--verbose` - Prints additional debug information. If excluded, only errors are logged (no stdout for automation).
* `-l`, `--log_file` - Specify the log file path. Defaults to `./podqueue.log`
# Where do I get my OPML?
This will depend on your podcast app, but most will be able to export your list of subscriptions into a common XML format.
If you use a different app that has a similar functionality, please let me know and I'll add it to this list.
|Podcast App|Podcast App|Supported|OPML Export Options|
|---|---|---|---|
|<img src="https://www.pocketcasts.com/assets/images/roundel.svg" width=50 height=50>|Pocket Casts|✔|[OPML export](https://support.pocketcasts.com/article/exporting-an-opml/)|
|<img src="https://upload.wikimedia.org/wikipedia/en/thumb/d/d9/Overcast_%28podcast_app%29_logo.svg/1280px-Overcast_%28podcast_app%29_logo.svg.png" width=50 height=50>|Overcast|✔|Option available in the app's Settings page, or [here on the web.](https://overcast.fm/account/export_opml)|
|<img src="https://castro.fm/assets/images/Bitmap.svg" width=50 height=50>|Castro|✔|[Export Subscriptions](https://castro.fm/support/export-subscriptions)|
|<img src="https://downcast.fm/images/downcast-site-logo.svg" width=50 height=50>|Downcast|✔|[Exporting Podcast Subscriptions](https://support.downcast.fm/article/vYyHP2SOOc-exporting-podcast-subscriptions)|
|<img src="https://www.podcastaddict.com/res/images/logo.svg" width=50 height=50>|Podcast Addict|✔|[How can I backup and restore my subscription & data?](https://podcastaddict.com/faq/20)|
|<img src="https://play-lh.googleusercontent.com/kG4QJCsky97lbfX83zV2qQKUVuFQj07Ot9EJJvHt1meM5WjUXl3T96KRIPlSf-tHAfI=s180" width=50 height=50>|Castbox|✔|[OPML Export](https://helpcenter.castbox.fm/portal/en/kb/articles/settings-on-the-personal-tab-android#OPML_Export)|
|<img src="https://www.apple.com/v/apple-podcasts/b/images/overview/hero_icon__c135x5gz14mu_large.png" width=50 height=50>|Apple Podcasts|🛠|Not available in iOS app or macOS since Catalina. However, if you sync your podcasts to your Mac, there is an [open-source workaround.](https://liujiacai.net/podcasts-opml-exporter/)|
|<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/25/Google_Podcasts_icon.svg/400px-Google_Podcasts_icon.svg.png" width=50 height=50>|Google Podcasts|🛠|Officially unavailable. There is a [Gist by @telmen](https://gist.github.com/telmen/4d67cba98ba7181424a681c1cbfc5f34) (I tested, seems to work) that can be run in your browser's Devtools if you're feeling lucky.|
|<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Spotify_logo_without_text.svg/1280px-Spotify_logo_without_text.svg.png" width=50 height=50>|Spotify|❌|Not available, since Spotify doesn't use open Podcast standards. Community suggestion is ['now reaching the internal teams at Spotify'](https://community.spotify.com/t5/Live-Ideas/Podcasts-Import-for-Podcasts-OPML/idi-p/4423445), as of six months ago.|
|<img src="https://play-lh.googleusercontent.com/2wd59_1csnF1lIt6wG5DdBiDUFEeov1jIW9ax0scfwvDk_OUsK7-6LZ86I8MAsVCuhM=s180" width=50 height=50>|Stitcher|❌|Not available.|
Your file should look something like this, with one line per podcast:
```
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<opml version="1.0">
<head>
<title>Pocket Casts Feeds</title>
</head>
<body>
<outline text="feeds">
<outline type="rss" text="Accidental Tech Podcast" xmlUrl="https://atp.fm/episodes?format=rss" />
<outline type="rss" text="The Unmade Podcast" xmlUrl="https://www.unmade.fm/episodes?format=rss" />
<outline type="rss" text="You Look Nice Today" xmlUrl="https://feeds.fireside.fm/youlooknicetoday/rss" />
<outline type="rss" text="The Pen Addict" xmlUrl="https://www.relay.fm/penaddict/feed" />
... etc ...
</outline>
</body>
</opml>
```
# How to install
Installation is done through Python's pip:
```
python3 -m pip install --upgrade podqueue
python3 -m podqueue --help
```
Alternatively, you can use Docker to containerise `podqueue`. This requires two volumes mounts, one for the output location to save podcast files to on the host, and the other to store the `podqueue.opml` config and log files.
Docker Compose:
```
services:
podqueue:
name: podqueue
image: tquin/podqueue:latest
user: "<YOUR UID>:<YOUR GUID>"
restart: unless-stopped
volumes:
- "<YOUR_OUTPUT_DIRECTORY>:/data"
- "<YOUR_CONFIG_DIRECTORY>:/config"
```
Docker CLI:
```
docker pull tquin/podqueue:latest
cat <YOUR_CONFIG_DIRECTORY>/podqueue.opml # Replace this example with your subscription list
docker run -it \
-v <YOUR_OUTPUT_DIRECTORY>:/data \
-v <YOUR_CONFIG_DIRECTORY>:/config \
--restart unless-stopped \
--user "<YOUR UID>:<YOUR GUID>" \
tquin/podqueue:latest
```
Or you can just clone this repo directly:
```
git clone https://github.com/tquin/podqueue
cd podqueue/
python3 podqueue/main.py --help
```
# Output
Executing the script will download each podcast into their own subdirectory, with episode metadata (shownotes, date, title, link) and show metadata (episode count, description, image) in each subdirectory. Episodes will be downloaded in default feed order - usually newest first, but it could depend on the podcast.
Example directory tree:
```
output/
├─ Accidental_Tech_Podcast/
├── episodes/
│ ├── 2021-12-30_463_No_Indication_of_Progress.json
│ ├── 2021-12-30_463_No_Indication_of_Progress.mp3
│ ├── 2022-01-06_464_Monks_at_Drafting_Tables.json
│ ├── 2022-01-06_464_Monks_at_Drafting_Tables.mp3
│ ├── ...
├── Accidental_Tech_Podcast.png
├── Accidental_Tech_Podcast.json
├─ The_Pen_Addict/
├── episodes/
│ ├── 2021-12-29_494_The_Centre_is_Twisty.json
│ ├── 2021-12-29_494_The_Centre_is_Twisty.mp3
│ ├── 2022-01-05_495_Parter_Jocker.json
│ ├── 2022-01-05_495_Parter_Jocker.mp3
│ ├── ...
├── The_Pen_Addict.png
├── The_Pen_Addict.json
```
And a sample JSON file for an episode (note that `description` will often be HTML-formatted):
```
{
"published_parsed": "2022-04-05",
"title": "Episode 451: Minitel (Entry 791.IS4209)",
"link": "https://pdst.fm/e/aphid.fireside.fm/d/1437767933/8658dd0c-baa7-4412-9466-918650a0013d/e373ca83-d5d9-4b8b-b03f-5fcd012ac9a2.mp3",
"description": "In which a French-only precursor to the World Wide Web appears in the late 1970s in a wave of Gallic futurist fervor, and John may have been making long distance calls from a Parisian prison. Certificate #23054."
}
```
# Todos
* Distro packaging
* Better config file location, eg $HOME/.config/podqueue.conf
* Built-in systemd/cron timers
* Option to only download after X date (--no-backlog or --earliest ?)
Raw data
{
"_id": null,
"home_page": "https://github.com/tquin/podqueue",
"name": "podqueue",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.6",
"maintainer_email": "",
"keywords": "podcast,podqueue,archive,download",
"author": "Tyler Quinlivan",
"author_email": "hello@tylerquinlivan.com",
"download_url": "https://files.pythonhosted.org/packages/44/de/fbbf293c99256ab3fb4da2e4232ff29a2fc1190ed5fc162d3bbeb803c6ad/podqueue-0.1.9.tar.gz",
"platform": null,
"description": "# tquin/podqueue\n\nAutomate the archiving of podcast feeds, including show notes and images.\n\nThis Python project aims for a simple user interface - you just need to modify `podqueue.conf` with your inputs and outputs, and then schedule the program to run periodically.\n\n* https://pypi.org/project/podqueue\n* https://github.com/tquin/podqueue\n* https://hub.docker.com/r/tquin/podqueue\n\n# What does my config file look like?\n\nThe default `podqueue.conf` looks like the below. You have two choices to run this app:\n\n1) Edit this config file with your inputs and outputs, or\n2) Overwrite these values with the CLI flags below.\n\n```\n[podqueue]\nopml = podqueue.opml\ndest = pq/\nlog_file = podqueue.log\n# Please note, '%' in time_format must be escaped with '%%'\ntime_format = %%Y-%%m-%%d\nverbose = False\n```\n\n# CLI arguments\n\nAs mentioned, if any of these CLI arguments are specified, they will **overwrite** any values in the config file.\n\n* `-o`, `--opml` - Pass an OPML file that contains a podcast subscription list.\n* `-d`, `--dest` - The destination folder for downloads. Will be created if required, including sub-directories for each separate podcast.\n* `-t`, `--time_format` - Specify a time format string for JSON files. Defaults to '%Y-%m-%d' (2022-06-31) if not specified.\n* `-v`, `--verbose` - Prints additional debug information. If excluded, only errors are logged (no stdout for automation).\n* `-l`, `--log_file` - Specify the log file path. Defaults to `./podqueue.log`\n\n# Where do I get my OPML?\n\nThis will depend on your podcast app, but most will be able to export your list of subscriptions into a common XML format.\n\nIf you use a different app that has a similar functionality, please let me know and I'll add it to this list.\n\n|Podcast App|Podcast App|Supported|OPML Export Options|\n|---|---|---|---|\n|<img src=\"https://www.pocketcasts.com/assets/images/roundel.svg\" width=50 height=50>|Pocket Casts|\u2714|[OPML export](https://support.pocketcasts.com/article/exporting-an-opml/)|\n|<img src=\"https://upload.wikimedia.org/wikipedia/en/thumb/d/d9/Overcast_%28podcast_app%29_logo.svg/1280px-Overcast_%28podcast_app%29_logo.svg.png\" width=50 height=50>|Overcast|\u2714|Option available in the app's Settings page, or [here on the web.](https://overcast.fm/account/export_opml)|\n|<img src=\"https://castro.fm/assets/images/Bitmap.svg\" width=50 height=50>|Castro|\u2714|[Export Subscriptions](https://castro.fm/support/export-subscriptions)|\n|<img src=\"https://downcast.fm/images/downcast-site-logo.svg\" width=50 height=50>|Downcast|\u2714|[Exporting Podcast Subscriptions](https://support.downcast.fm/article/vYyHP2SOOc-exporting-podcast-subscriptions)|\n|<img src=\"https://www.podcastaddict.com/res/images/logo.svg\" width=50 height=50>|Podcast Addict|\u2714|[How can I backup and restore my subscription & data?](https://podcastaddict.com/faq/20)|\n|<img src=\"https://play-lh.googleusercontent.com/kG4QJCsky97lbfX83zV2qQKUVuFQj07Ot9EJJvHt1meM5WjUXl3T96KRIPlSf-tHAfI=s180\" width=50 height=50>|Castbox|\u2714|[OPML Export](https://helpcenter.castbox.fm/portal/en/kb/articles/settings-on-the-personal-tab-android#OPML_Export)| \n|<img src=\"https://www.apple.com/v/apple-podcasts/b/images/overview/hero_icon__c135x5gz14mu_large.png\" width=50 height=50>|Apple Podcasts|\ud83d\udee0|Not available in iOS app or macOS since Catalina. However, if you sync your podcasts to your Mac, there is an [open-source workaround.](https://liujiacai.net/podcasts-opml-exporter/)|\n|<img src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/2/25/Google_Podcasts_icon.svg/400px-Google_Podcasts_icon.svg.png\" width=50 height=50>|Google Podcasts|\ud83d\udee0|Officially unavailable. There is a [Gist by @telmen](https://gist.github.com/telmen/4d67cba98ba7181424a681c1cbfc5f34) (I tested, seems to work) that can be run in your browser's Devtools if you're feeling lucky.|\n|<img src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Spotify_logo_without_text.svg/1280px-Spotify_logo_without_text.svg.png\" width=50 height=50>|Spotify|\u274c|Not available, since Spotify doesn't use open Podcast standards. Community suggestion is ['now reaching the internal teams at Spotify'](https://community.spotify.com/t5/Live-Ideas/Podcasts-Import-for-Podcasts-OPML/idi-p/4423445), as of six months ago.|\n|<img src=\"https://play-lh.googleusercontent.com/2wd59_1csnF1lIt6wG5DdBiDUFEeov1jIW9ax0scfwvDk_OUsK7-6LZ86I8MAsVCuhM=s180\" width=50 height=50>|Stitcher|\u274c|Not available.|\n\n\nYour file should look something like this, with one line per podcast:\n\n```\n<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>\n<opml version=\"1.0\">\n <head>\n <title>Pocket Casts Feeds</title>\n </head>\n <body>\n <outline text=\"feeds\">\n <outline type=\"rss\" text=\"Accidental Tech Podcast\" xmlUrl=\"https://atp.fm/episodes?format=rss\" />\n <outline type=\"rss\" text=\"The Unmade Podcast\" xmlUrl=\"https://www.unmade.fm/episodes?format=rss\" />\n <outline type=\"rss\" text=\"You Look Nice Today\" xmlUrl=\"https://feeds.fireside.fm/youlooknicetoday/rss\" />\n <outline type=\"rss\" text=\"The Pen Addict\" xmlUrl=\"https://www.relay.fm/penaddict/feed\" />\n\n ... etc ...\n\n </outline>\n </body>\n</opml>\n```\n\n# How to install\n\nInstallation is done through Python's pip:\n\n```\npython3 -m pip install --upgrade podqueue\npython3 -m podqueue --help\n```\n\nAlternatively, you can use Docker to containerise `podqueue`. This requires two volumes mounts, one for the output location to save podcast files to on the host, and the other to store the `podqueue.opml` config and log files.\n\nDocker Compose:\n```\nservices:\n podqueue:\n name: podqueue\n image: tquin/podqueue:latest\n user: \"<YOUR UID>:<YOUR GUID>\"\n restart: unless-stopped\n volumes:\n - \"<YOUR_OUTPUT_DIRECTORY>:/data\"\n - \"<YOUR_CONFIG_DIRECTORY>:/config\"\n```\n\nDocker CLI:\n```\ndocker pull tquin/podqueue:latest\ncat <YOUR_CONFIG_DIRECTORY>/podqueue.opml # Replace this example with your subscription list\ndocker run -it \\\n -v <YOUR_OUTPUT_DIRECTORY>:/data \\\n -v <YOUR_CONFIG_DIRECTORY>:/config \\\n --restart unless-stopped \\\n --user \"<YOUR UID>:<YOUR GUID>\" \\\n tquin/podqueue:latest\n```\n\nOr you can just clone this repo directly:\n```\ngit clone https://github.com/tquin/podqueue\ncd podqueue/\npython3 podqueue/main.py --help\n```\n\n# Output\n\nExecuting the script will download each podcast into their own subdirectory, with episode metadata (shownotes, date, title, link) and show metadata (episode count, description, image) in each subdirectory. Episodes will be downloaded in default feed order - usually newest first, but it could depend on the podcast.\n\nExample directory tree:\n```\noutput/\n\u251c\u2500 Accidental_Tech_Podcast/\n\u251c\u2500\u2500 episodes/\n\u2502 \u251c\u2500\u2500 2021-12-30_463_No_Indication_of_Progress.json\n\u2502 \u251c\u2500\u2500 2021-12-30_463_No_Indication_of_Progress.mp3\n\u2502 \u251c\u2500\u2500 2022-01-06_464_Monks_at_Drafting_Tables.json\n\u2502 \u251c\u2500\u2500 2022-01-06_464_Monks_at_Drafting_Tables.mp3\n\u2502 \u251c\u2500\u2500 ...\n\u251c\u2500\u2500 Accidental_Tech_Podcast.png\n\u251c\u2500\u2500 Accidental_Tech_Podcast.json\n\u251c\u2500 The_Pen_Addict/\n\u251c\u2500\u2500 episodes/\n\u2502 \u251c\u2500\u2500 2021-12-29_494_The_Centre_is_Twisty.json\n\u2502 \u251c\u2500\u2500 2021-12-29_494_The_Centre_is_Twisty.mp3\n\u2502 \u251c\u2500\u2500 2022-01-05_495_Parter_Jocker.json\n\u2502 \u251c\u2500\u2500 2022-01-05_495_Parter_Jocker.mp3\n\u2502 \u251c\u2500\u2500 ...\n\u251c\u2500\u2500 The_Pen_Addict.png\n\u251c\u2500\u2500 The_Pen_Addict.json\n\n```\n\nAnd a sample JSON file for an episode (note that `description` will often be HTML-formatted):\n```\n{\n \"published_parsed\": \"2022-04-05\",\n \"title\": \"Episode 451: Minitel (Entry 791.IS4209)\",\n \"link\": \"https://pdst.fm/e/aphid.fireside.fm/d/1437767933/8658dd0c-baa7-4412-9466-918650a0013d/e373ca83-d5d9-4b8b-b03f-5fcd012ac9a2.mp3\",\n \"description\": \"In which a French-only precursor to the World Wide Web appears in the late 1970s in a wave of Gallic futurist fervor, and John may have been making long distance calls from a Parisian prison. Certificate #23054.\"\n}\n```\n\n# Todos\n\n* Distro packaging\n* Better config file location, eg $HOME/.config/podqueue.conf\n* Built-in systemd/cron timers\n* Option to only download after X date (--no-backlog or --earliest ?)\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Automate the archiving of podcast feeds, including show notes and images.",
"version": "0.1.9",
"project_urls": {
"Download": "https://pypi.org/project/podqueue/",
"Homepage": "https://github.com/tquin/podqueue"
},
"split_keywords": [
"podcast",
"podqueue",
"archive",
"download"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "2845cbfd07ba3b05eac755848cdd890c6580c0632dab23eec92bce38d6a08996",
"md5": "b5021e0414986f25af3148c212261357",
"sha256": "ac0aa24b9fa198a27a81533dcd41a883f6ff705960954d2c8101fe4c94c9315c"
},
"downloads": -1,
"filename": "podqueue-0.1.9-py3-none-any.whl",
"has_sig": false,
"md5_digest": "b5021e0414986f25af3148c212261357",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.6",
"size": 15566,
"upload_time": "2023-09-04T10:26:57",
"upload_time_iso_8601": "2023-09-04T10:26:57.320058Z",
"url": "https://files.pythonhosted.org/packages/28/45/cbfd07ba3b05eac755848cdd890c6580c0632dab23eec92bce38d6a08996/podqueue-0.1.9-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "44defbbf293c99256ab3fb4da2e4232ff29a2fc1190ed5fc162d3bbeb803c6ad",
"md5": "c72626af40ffd59538527c8650d4f200",
"sha256": "b3929173546418040e5580e8f84ffbdaad377232144a5bcfde6f774c7f1fbbe7"
},
"downloads": -1,
"filename": "podqueue-0.1.9.tar.gz",
"has_sig": false,
"md5_digest": "c72626af40ffd59538527c8650d4f200",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.6",
"size": 13399,
"upload_time": "2023-09-04T10:26:59",
"upload_time_iso_8601": "2023-09-04T10:26:59.594702Z",
"url": "https://files.pythonhosted.org/packages/44/de/fbbf293c99256ab3fb4da2e4232ff29a2fc1190ed5fc162d3bbeb803c6ad/podqueue-0.1.9.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-09-04 10:26:59",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "tquin",
"github_project": "podqueue",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "podqueue"
}