{"id":13892841,"url":"https://github.com/janw/podcast-archiver","last_synced_at":"2025-03-27T07:09:00.743Z","repository":{"id":47284518,"uuid":"78279779","full_name":"janw/podcast-archiver","owner":"janw","description":"Archive all your favorite podcasts","archived":false,"fork":false,"pushed_at":"2025-03-17T00:14:10.000Z","size":2591,"stargazers_count":142,"open_issues_count":12,"forks_count":22,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-19T20:15:28.861Z","etag":null,"topics":["archiving","feedparser","podcast","podcasts","python","rss"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/janw.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"janw","liberapay":"janw","ko_fi":"janwxyz"}},"created_at":"2017-01-07T13:12:46.000Z","updated_at":"2025-03-12T17:36:24.000Z","dependencies_parsed_at":"2024-05-06T02:29:52.281Z","dependency_job_id":"a7f1a306-b5eb-4877-a37a-c4439064195c","html_url":"https://github.com/janw/podcast-archiver","commit_stats":{"total_commits":122,"total_committers":1,"mean_commits":122.0,"dds":0.0,"last_synced_commit":"a278dcb13c4d78450af6ddfe8d9e47781681ea78"},"previous_names":[],"tags_count":42,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janw%2Fpodcast-archiver","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janw%2Fpodcast-archiver/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janw%2Fpodcast-archiver/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janw%2Fpodcast-archiver/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/janw","download_url":"https://codeload.github.com/janw/podcast-archiver/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245798358,"owners_count":20673902,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["archiving","feedparser","podcast","podcasts","python","rss"],"created_at":"2024-08-06T17:01:16.326Z","updated_at":"2025-03-27T07:09:00.710Z","avatar_url":"https://github.com/janw.png","language":"Python","funding_links":["https://github.com/sponsors/janw","https://liberapay.com/janw","https://ko-fi.com/janwxyz"],"categories":["Python"],"sub_categories":[],"readme":"# Podcast Archiver\n\n\u003c!-- markdownlint-disable MD033 MD013 --\u003e\n\u003cdiv align=\"center\"\u003e\n\n![Podcast Archiver Logo](.assets/icon.png)\n\n[![version](https://img.shields.io/pypi/v/podcast-archiver.svg)](https://pypi.org/project/podcast-archiver/)\n[![python](https://img.shields.io/pypi/pyversions/podcast-archiver.svg)](https://pypi.org/project/podcast-archiver/)\n[![downloads](https://img.shields.io/pypi/dm/podcast-archiver)](https://pypi.org/project/podcast-archiver/)\n\n[![Docker Build](https://github.com/janw/podcast-archiver/actions/workflows/docker-build.yaml/badge.svg)](https://ghcr.io/janw/podcast-archiver)\n[![Tests](https://github.com/janw/podcast-archiver/actions/workflows/tests.yaml/badge.svg)](https://github.com/janw/podcast-archiver/actions/workflows/tests.yaml?query=branch%3Amain)\n[![pre-commit.ci](https://results.pre-commit.ci/badge/github/janw/podcast-archiver/main.svg)](https://results.pre-commit.ci/latest/github/janw/podcast-archiver/main)\n\n[![Maintainability](https://api.codeclimate.com/v1/badges/1cdd7513333043558ee7/maintainability)](https://codeclimate.com/github/janw/podcast-archiver/maintainability)\n[![codecov](https://codecov.io/gh/janw/podcast-archiver/branch/main/graph/badge.svg?token=G8WI2ZILRG)](https://codecov.io/gh/janw/podcast-archiver)\n\n[![ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://docs.astral.sh/ruff/)\n[![poetry](https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json)](https://python-poetry.org/docs/)\n[![pre-commit](https://img.shields.io/badge/-pre--commit-f8b424?logo=pre-commit\u0026labelColor=grey)](https://github.com/pre-commit/pre-commit)\n\n\u003c/div\u003e\n\nA fast and simple command line client to archive all episodes from your favorite podcasts.\n\nPodcast Archiver takes the feed URLs of your favorite podcasts and downloads all available episodes for you—even those \"hidden\" in [paged feeds](https://podlove.org/paged-feeds/). You'll end up with a complete archive of your shows. The archiver also supports updating an existing archive, so that it lends itself to be set up as a cronjob.\n\n![Demo of podcast-archiver](.assets/demo.gif)\n\n## Setup\n\nInstall via [pipx](https://pipx.pypa.io/stable/):\n\n```bash\npipx install podcast-archiver\n```\n\nInstall via [brew](https://brew.sh/):\n\n```bash\nbrew install janw/tap/podcast-archiver\n```\n\nOr use it via Docker:\n\n```bash\ndocker run --tty --rm ghcr.io/janw/podcast-archiver --help\n```\n\nBy default, the docker image downloads episodes to a volume mounted at `/archive`.\n\n## Usage\n\nPodcast Archiver supports command line arguments, environment variables, and a config file to set it up. The most simple invocation, passing feeds as command line arguments, would like like this:\n\n```sh\npodcast-archiver --dir ~/Podcasts --feed https://feeds.feedburner.com/TheAnthropoceneReviewed\n```\n\n### What constitutes a \"feed\"\n\nPodcast Archiver expects values to its `--feed/-f` argument to be URLs pointing to an [RSS feed of a podcast](https://archive.is/jYk3E).\n\nIf you are not certain if the link you have for a show that you like, you can try and pass it to Podcast Archiver directly. The archiver supports a variety of links from popular podcast players and platforms, including [Apple Podcasts](https://podcasts.apple.com/us/browse), [Overcast.fm](https://overcast.fm/), [Castro](https://castro.fm/), and [Pocket Casts](https://pocketcasts.com/):\n\n```sh\n# Archive from Apple Podcasts URL\npodcast-archiver -f https://podcasts.apple.com/us/podcast/black-girl-gone-a-true-crime-podcast/id1556267741\n# ... or just the ID\npodcast-archiver -f 1556267741\n\n# From Overcast podcast URL\npodcast-archiver -f https://overcast.fm/itunes394775318/99-invisible\n# ... or episode sharing links (will resolve to all episodes)\npodcast-archiver -f https://overcast.fm/+AAyIOzrEy1g\n```\n\n#### Supported services\n\nThe table below lists most of the supported services and URLs. If you think that some service you like is missing here, [please let me know](https://github.com/janw/podcast-archiver/issues/new)!\n\n| Service                               | Example URL                                                                            |\n| ------------------------------------- | -------------------------------------------------------------------------------------- |\n| Apple Podcasts                        | \u003chttps://podcasts.apple.com/us/podcast/the-anthropocene-reviewed/id1342003491\u003e         |\n| [Overcast](https://overcast.fm/)      | \u003chttps://overcast.fm/itunes394775318/99-invisible\u003e, \u003chttps://overcast.fm/+AAyIOzrEy1g\u003e |\n| [Castro](https://castro.fm/)          | \u003chttps://castro.fm/podcast/f996ae94-70a2-4d9c-afbc-c70b5bacd120\u003e                       |\n| [SoundCloud](https://soundcloud.com/) | \u003chttps://soundcloud.com/chapo-trap-house\u003e                                              |\n\n#### Local files\n\nFeeds can also be \"fetched\" from a local file:\n\n```bash\npodcast-archiver -f file:/Users/janw/downloaded_feed.xml\n```\n\n#### Testing without downloading\n\nTo find out if you have to the right feed, you may want to use the `--dry-run` option to output the discovered feed information and found episodes. It will prevent all downloads.\n\n```sh\npodcast-archiver -f https://feeds.feedburner.com/TheAnthropoceneReviewed --dry-run\n```\n\n### Using a config file\n\nPodcast Archiver can be configured using a YAML config file as well. This way you can easily add and remove feeds to the list and let the archiver fetch the newest episodes, for example using a daily cronjob.\n\nA simple config file can look like this:\n\n```yaml\narchive_directory: ~/Podcasts\nfilename_template: '{show.title}/{episode.published_time:%Y-%m-%d} - {episode.title}.{ext}'\nwrite_info_json: true\nfeeds:\n  - https://feeds.feedburner.com/TheAnthropoceneReviewed  # The Anthropocene Reviewed\n  - https://feeds.megaphone.fm/heavyweight-spot  # Heavyweight\n```\n\nTo create a config file, you may use `podcast-archiver --config-generate` to emit an example configuration locally. You can also find a [pre-populated config file here](https://github.com/janw/podcast-archiver/blob/main/config.yaml.example). The example config contains descriptions and default values for all available parameters. After modifying it to your liking, you can invoke the archiver by bassing the config file as a command line argument:\n\n```sh\npodcast-archiver --config config.yaml\n```\n\nAlternatively (for example, if you're running `podcast-archiver` in Docker), you may point it to the config file using the `PODCAST_ARCHIVER_CONFIG=path/to/config.yaml` environment variable.\n\nIf the `--config` parameter is omitted, the archiver will look for a config file in its app config directory. The location of this directory is OS-specific; it is printed with the `podcast-archiver --help` output (next to the `--config` option help text).\n\n### Using environment variables\n\nMost settings of Podcast Archiver are available as environment variables, too. Check `podcast-archiver --help` for options with `env var: …` next to them.\n\n```sh\nexport PODCAST_ARCHIVER_FEEDS='https://feeds.feedburner.com/TheAnthropoceneReviewed'  # multiple must be space-separated\nexport PODCAST_ARCHIVER_ARCHIVE_DIRECTORY=\"$HOME/Podcasts\"\n\npodcast-archiver\n```\n\n## Advanced use\n\n### Continuous mode\n\nWhen the `--sleep-seconds` option is set to a non-zero value, Podcast Archiver operates in continuous mode. After successfully populating the archive, it will not terminate but rather sleep for the given number of seconds until it refreshes the feeds again and downloads episodes that have been published in the meantime.\n\nIf no new episodes have been published, no download attempts will be made, and the archiver will go to sleep again. This mode of operation is ideal to be run in a containerized setup, for example using [docker compose](https://docs.docker.com/compose/install/):\n\n```yaml\nservices:\n  podcast-archiver:\n    restart: always\n    image: ghcr.io/janw/podcast-archiver\n    volumes:\n      - ./archive:/archive\n    command:\n      - --sleep-seconds=3600  # sleep for 1 hour between updates\n      - --feed=https://feeds.feedburner.com/TheAnthropoceneReviewed\n      - --feed=https://feeds.megaphone.fm/heavyweight-spot\n```\n\n### Changing the filename format\n\nPodcast Archiver has a `--filename-template` option that allows you to change the particular naming scheme of the archive. The default value for `--filename-template`. is shown in `podcast-archiver --help`, as well as all the available variables. The basic ones are:\n\n* Episode: `episode.title`, `episode.subtitle`, `episode.author`, `episode.published_time`, `episode.original_filename`\n* Podcast: `show.title`, `show.subtitle`, `show.author`, `show.language`\n\nNote here that `episode.published_time` is a Python-native datetime, so its exact format can be adjusted further a la `{episode.published_time:%Y-%m-%d %H%M%S}` using [strftime-placeholders](https://strftime.org/). By default it uses `%Y-%m-%d` (e.g. 2024-12-31).\n\n#### Examples\n\n* More precise published time\n\n  ```plain\n  {show.title}/{episode.published_time:%Y-%m-%d %H%M%S %Z} - {episode.title}.{ext}\n  ```\n\n  Results in `…/That Show/2023-03-12 123456 UTC - Some Episode.mp3`\n\n* Using the original filename (roughly equivalent to pre-1.0 `--subdirs`)\n\n  ```plain\n  {show.title}/{episode.original_filename}\n  ```\n\n  Results in `…/That Show/ts001-episodefilename.mp3`\n\n* Using the original filename (roughly equivalent to pre-1.0 `--subdirs` + `--date-prefix`)\n\n  ```plain\n  {show.title}/{episode.published_time} {episode.original_filename}\n  ```\n\n  Results in `…/That Show/2023-03-12 ts001-episodefilename.mp3`\n\n### All available options\n\nRun `podcast-archiver --help` to see all available parameters and the corresponding environment variables.\n\n![`podcast-archiver --help`](.assets/podcast-archiver-help.svg)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjanw%2Fpodcast-archiver","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjanw%2Fpodcast-archiver","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjanw%2Fpodcast-archiver/lists"}