{"id":40305287,"url":"https://github.com/vpetersson/podcast-rss-generator","last_synced_at":"2026-01-20T06:03:22.877Z","repository":{"id":213817944,"uuid":"734997373","full_name":"vpetersson/podcast-rss-generator","owner":"vpetersson","description":"A simple script to generate an RSS feed for self-hosted audio/videos that can be used with Apple Podcast, Amazon Podcasts and more.","archived":false,"fork":false,"pushed_at":"2025-07-26T07:06:58.000Z","size":163,"stargazers_count":34,"open_issues_count":1,"forks_count":5,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-07-26T13:03:49.364Z","etag":null,"topics":["podcast","rss"],"latest_commit_sha":null,"homepage":"https://vpetersson.com/","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/vpetersson.png","metadata":{"files":{"readme":"README.md","changelog":null,"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,"zenodo":null}},"created_at":"2023-12-23T09:47:39.000Z","updated_at":"2025-07-26T07:07:02.000Z","dependencies_parsed_at":"2024-01-22T12:55:34.640Z","dependency_job_id":"4dc1de23-a69a-4081-878b-dccd95058b3d","html_url":"https://github.com/vpetersson/podcast-rss-generator","commit_stats":null,"previous_names":["vpetersson/podcast-rss-generator"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/vpetersson/podcast-rss-generator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vpetersson%2Fpodcast-rss-generator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vpetersson%2Fpodcast-rss-generator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vpetersson%2Fpodcast-rss-generator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vpetersson%2Fpodcast-rss-generator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vpetersson","download_url":"https://codeload.github.com/vpetersson/podcast-rss-generator/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vpetersson%2Fpodcast-rss-generator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28597087,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-20T02:08:49.799Z","status":"ssl_error","status_checked_at":"2026-01-20T02:08:44.148Z","response_time":117,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["podcast","rss"],"created_at":"2026-01-20T06:03:09.253Z","updated_at":"2026-01-20T06:03:22.870Z","avatar_url":"https://github.com/vpetersson.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Podcast RSS Generator\n\n[![Python Unit Tests and Linting](https://github.com/vpetersson/podcast-rss-generator/actions/workflows/python-tests.yml/badge.svg)](https://github.com/vpetersson/podcast-rss-generator/actions/workflows/python-tests.yml)\n\n## Description\n\nThis an RSS Feed Generator is designed to generate an RSS feed for audio/video podcasts, reading metadata and episode data from a YAML file.\n\nIt assumes that you self-host your video episodes somewhere (e.g. S3/GCS/R2) as well as the output of this script. You can then point YouTube/Spotify/Apple Podcast to this path.\n\nThis tool was written for my podcast [Nerding Out with Viktor](https://vpetersson.com/podcast/) to solve for the fact that Apple's [Podcast Connect](https://podcastsconnect.apple.com) require you to self-host videos in order to publish.\n\nI also wrote an article on how you can use this tool to automatically turn a video podcast into audio in [this article](https://vpetersson.com/2024/06/27/video-to-audio-podcast.html).\n\n## Features\n\n- Generates RSS feed for audio/video podcasts\n- Reads podcast metadata and episode data from a YAML file\n- Converts ISO format dates to RFC 2822 format\n- Attempts to follow [The Podcast RSS Standard](https://github.com/Podcast-Standards-Project/PSP-1-Podcast-RSS-Specification)\n\n## Known Issues\n\n- Videos uploaded to YouTube [via RSS](https://support.google.com/youtube/answer/13525207?hl=en#zippy=%2Ccan-i-deliver-an-rss-feed-if-i-already-have-a-podcast-on-youtube) will be uploaded as audio.\n- Spotify can't handle videos via RSS yet. You will be able to see the episodes in Podcaster, but they will not be processed and sent to Spotify properly. This is apparently a known issue that they are working on resolving.\n\nThe workaround for the above issues is to manually upload the episodes.\n\n## Installation\n\n### Prerequisites\n\n- Python 3.8 or higher\n- pip (Python package installer)\n- ffmpeg\n\n### Setup\n\n1. **Clone the Repository**\n\n```bash\n$ git clone https://github.com/vpetersson/podcast-rss-generator.git\n$ cd podcast-rss-generator\n```\n\n2. **Install Dependencies**\n\n```bash\n$ pip install -r requirements.txt\n```\n\n**Optional:** Install `yamllint`, `xq` and `flake8`.\n\n## Usage\n\n```bash\n$ python rss_generator.py --help\nusage: rss_generator.py [-h] [--input-file INPUT_FILE] [--output-file OUTPUT_FILE]\n                        [--skip-asset-verification] [--dry-run]\n\nProcess some parameters.\n\noptions:\n  -h, --help            show this help message and exit\n  --input-file INPUT_FILE\n                        Input YAML file\n  --output-file OUTPUT_FILE\n                        Output XML file\n  --skip-asset-verification\n                        Skip HTTP HEAD and ffprobe checks for asset URLs (use for testing/fake URLs)\n  --dry-run             Validate configuration file only, do not generate RSS feed\n```\n\n1. **Prepare Your Data Files**\n\nCopy `podcast_config.example.yaml` to `podcast_config.yaml` and fill out your podcast metadata and eepisodes.\n\n2. **Validate Your Configuration** (Dry-run)\n\nBefore generating the RSS feed, you can validate your configuration file using the `--dry-run` flag:\n\n```bash\n$ python rss_generator.py --dry-run\n```\n\nThis will:\n\n- Check if your YAML file has valid syntax\n- Validate all required fields are present\n- Check URL formats for links, asset URLs, and images\n- Verify email addresses and ISO date formats\n- Validate episode structure and optional fields\n- Exit with code 0 if valid, or code 1 if errors are found\n\nExample output for a valid config:\n\n```\n✓ Config validation passed!\n✓ Dry-run completed successfully.\n```\n\nExample output for an invalid config:\n\n```\n✗ Config validation failed:\n  - Invalid email format: 'invalid-email'\n  - Episode 1: Invalid publication_date format 'invalid-date'\n  - Episode 1: Invalid asset_url format 'not-a-url'\n```\n\n3. **Generate the RSS Feed**\n\nThe `podcast_config.yaml` file contains two main sections: `metadata` and `episodes`.\n\n### Metadata Section\n\nThis section contains general information about your podcast:\n\n| Key                      | Description                                                                                                                                                                                                                                                                                                    | Notes                                                                                                                                                                                                                               |\n| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `title`                  | The title of your podcast.                                                                                                                                                                                                                                                                                     | Required                                                                                                                                                                                                                            |\n| `description`            | A description of your podcast.                                                                                                                                                                                                                                                                                 | Required. Markdown is supported.                                                                                                                                                                                                    |\n| `link`                   | The URL of the main website for your podcast.                                                                                                                                                                                                                                                                  | Required. Also the default link for episodes if not provided per-episode.                                                                                                                                                           |\n| `rss_feed_url`           | The public URL where your generated `podcast_feed.xml` will be hosted.                                                                                                                                                                                                                                         | Required                                                                                                                                                                                                                            |\n| `language`               | The language of the podcast (e.g., `en-us`).                                                                                                                                                                                                                                                                   | Optional. Default: `en-us`.                                                                                                                                                                                                         |\n| `email`                  | The contact email for the podcast owner.                                                                                                                                                                                                                                                                       | Required. Backward compatibility: `itunes_email`. Used for `\u003cpodcast:locked\u003e`.                                                                                                                                                      |\n| `author`                 | The author name(s).                                                                                                                                                                                                                                                                                            | Required. Backward compatibility: `itunes_author`.                                                                                                                                                                                  |\n| `category`               | The primary category for iTunes.                                                                                                                                                                                                                                                                               | Optional. Backward compatibility: `itunes_category`.                                                                                                                                                                                |\n| `image`                  | The URL for the main podcast cover art (JPEG or PNG, 1400x1400 to 3000x3000 pixels).                                                                                                                                                                                                                           | Required. Also the default image for episodes if not provided per-episode.                                                                                                                                                          |\n| `explicit`               | Indicates if the podcast contains explicit content.                                                                                                                                                                                                                                                            | Optional (`true`/`false`). Default: `false`. Backward compatibility: `itunes_explicit`. Can be overridden per-episode.                                                                                                              |\n| `use_asset_hash_as_guid` | Use a content hash or ETag from the asset file\\'s headers as the episode\\'s `\u003cguid\u003e`. Prioritizes `x-amz-checksum-sha256`, then `x-goog-hash` (MD5 part), then `ETag`. **Warning:** This can break GUID permanence if asset files change or are re-uploaded, potentially causing re-downloads for subscribers. | Optional (`true`/`false`). Default: `false` (uses `asset_url`).                                                                                                                                                                     |\n| `copyright`              | A string containing the copyright notice for the podcast.                                                                                                                                                                                                                                                      | Optional.                                                                                                                                                                                                                           |\n| `podcast_locked`         | Tells platforms not to import the feed without confirming ownership via the `email` address.                                                                                                                                                                                                                   | Optional (`yes`/`no`). Default: `no`. Based on [Podcast Standards Project](https://github.com/Podcast-Standards-Project/PSP-1-Podcast-RSS-Specification).                                                                           |\n| `podcast_guid`           | A globally unique, permanent identifier (UUID recommended) for the _entire podcast show_.                                                                                                                                                                                                                      | Optional. If omitted, a stable UUIDv5 is generated from `rss_feed_url`. Strongly recommended to set explicitly. Based on [Podcast Standards Project](https://github.com/Podcast-Standards-Project/PSP-1-Podcast-RSS-Specification). |\n\n### Episodes Section\n\nThis section is a list of your podcast episodes. Each episode is an object with the following fields:\n\n| Key                | Description                                                                                                                                             | Notes                                                                                                                           |\n| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |\n| `title`            | The title of the episode.                                                                                                                               | Required.                                                                                                                       |\n| `description`      | A description of the episode.                                                                                                                           | Required. Markdown is supported.                                                                                                |\n| `publication_date` | The date and time the episode was published. Episodes with future dates will not be included.                                                           | Required. ISO 8601 format (e.g., `2023-01-15T10:00:00Z`).                                                                       |\n| `asset_url`        | The direct URL to the audio or video file for the episode.                                                                                              | Required.                                                                                                                       |\n| `link`             | The URL for a webpage specific to this episode.                                                                                                         | Optional. Defaults to the global `link` from `metadata`.                                                                        |\n| `image`            | The URL for artwork specific to this episode (same format requirements as the main podcast image).                                                      | Optional. Defaults to the global `image` from `metadata`.                                                                       |\n| `episode`          | The episode number.                                                                                                                                     | Optional. Integer.                                                                                                              |\n| `season`           | The season number.                                                                                                                                      | Optional. Integer.                                                                                                              |\n| `episode_type`     | Defines the type of content for the episode.                                                                                                            | Optional. Can be `full` (default), `trailer`, or `bonus`.                                                                       |\n| `explicit`         | Indicates if this specific episode contains explicit content.                                                                                           | Optional (`true`/`false`). Overrides the global `explicit` setting for this episode. Backward compatibility: `itunes_explicit`. |\n| `transcripts`      | A list of transcript files associated with the episode. Each item is an object with `url` (required), `type` (required), `language` (opt), `rel` (opt). | Optional. See example config for structure.                                                                                     |\n\n4. **Generate the RSS Feed**\n\nMake sure your YAML is valid:\n\n```bash\n$ yamllint podcast_config.yaml\n```\n\nGenerate your `podcast_feed.xml` file:\n\n```bash\n$ python rss_generator.py\n```\n\nNow copy your `podcast_feed.xml` to S3/GCS/R2 using a tool like `s3cmd`, `aws` or `mc` (from Minio).\n\nYou can verify your RSS feed using a tool like [Podbase](https://podba.se/validate/).\n\n## **Optional:** Optimize video\n\nIf you're dealing with video podcasts, the file size matters for obvious reasons. What I do is to export the video as h624 from my video editor (which I upload to YouTube and Spotify).\n\nI then re-encode the h264 video to h265 for other platforms using `ffmpeg` with the following command (on macOS):\n\n```bash\n$ ffmpeg -i input.mp4 \\\n    -tag:v hvc1 \\\n    -c:v hevc_videotoolbox \\\n    -crf 28 \\\n    -preset slowest \\\n    -c:a aac \\\n    -b:a 128k \\\n    -movflags faststart \\\n    output.mp4\n```\n\n## Usage with GitHub Actions\n\nTo incorporate this action into your workflow, follow these steps:\n\n1. **Create a Workflow File**:\n   - In your repository, create a new file under `.github/workflows`, for example, `rss_workflow.yml`.\n\n2. **Set Up the Workflow**:\n   - Use the following configuration as a starting point:\n\n```yaml\nname: Generate Podcast RSS Feed\n\non: [push, pull_request]\n\nenv:\n  R2_BUCKET: 'foobar'\njobs:\n  generate-rss:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v2\n\n      - name: Install yamllint\n        run: |\n          sudo apt-get update\n          sudo apt-get install yamllint\n\n      - name: Lint YAML file\n        run: yamllint podcast_config.yaml\n\n      - name: Run Podcast RSS Generator\n        uses: vpetersson/podcast-rss-generator@master\n        with:\n          input_file: 'podcast_config.yaml'\n          output_file: 'podcast_feed.xml'\n\n      - name: Validate output with xq\n        run: |\n          wget -q https://github.com/sibprogrammer/xq/releases/download/v1.2.3/xq_1.2.3_linux_amd64.tar.gz\n          tar xfz xq_1.2.3_linux_amd64.tar.gz\n          cat podcast_feed.xml | ./xq\n\n      - uses: actions/upload-artifact@v2\n        with:\n          name: podcast_feed.xml\n          path: podcast_feed.xml\n\n  deploy:\n    runs-on: ubuntu-latest\n    needs: generate-rss\n    if: github.ref == 'refs/heads/master'\n    steps:\n      - uses: actions/download-artifact@v2\n        with:\n          name: podcast_feed.xml\n\n      - name: Install mc\n        run: |\n          wget -q https://dl.min.io/client/mc/release/linux-amd64/mc\n          chmod +x mc\n\n      - name: Set up mc\n        env:\n          R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }}\n          R2_KEY_ID: ${{ secrets.R2_KEY_ID }}\n          R2_KEY_SECRET: ${{ secrets.R2_KEY_SECRET }}\n        run: ./mc alias set r2-storage ${R2_ENDPOINT} ${R2_KEY_ID} ${R2_KEY_SECRET}\n\n      - name: Copy file\n        run: ./mc cp podcast_feed.xml r2-storage/${R2_BUCKET}/\n```\n\n3. **Customize Your Workflow**:\n   - Adjust paths to the YAML configuration and the output XML files as per your repository structure.\n   - Ensure the `uses` field points to `vpetersson/podcast-rss-generator@master` (or specify a specific release tag/version instead of `master`).\n\n4. **Commit and Push Your Workflow**:\n   - Once you commit this workflow file to your repository, the action will be triggered based on the defined events (e.g., on push or pull request).\n\n## Usage with Docker\nBuild the image with the following and tagging with `latest`:\n```\ndocker build -t podcast-rss-generator:latest .\n```\nTo spin up a container from the built image that uses a custom config file and writes out to `myfeed.xml`.\n\n```\ndocker run --rm -v .:/opt podcast-rss-generator:latest --output-file /opt/myfeed.xml --input-file /opt/custom_podcast_config.yaml\n```\n\nN.B. The switches `-v` share files between host and container and `--rm` automatically removes the container when it exits.\n### Inputs\n\n- `input_file`: Path to the input YAML file. Default: `podcast_config.yaml`.\n- `output_file`: Path for the generated RSS feed XML file. Default: `podcast_feed.xml`.\n\n## Running Tests\n\nTo run unit tests, use:\n\n```bash\n$ python -m unittest discover tests\n```\n\n## Contributing\n\nContributions to this project are welcome! Please follow these steps:\n\n1. Fork the repository.\n2. Create a new branch for your feature.\n3. Commit your changes.\n4. Push to the branch.\n5. Submit a pull request.\n\n## License\n\n[MIT License](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvpetersson%2Fpodcast-rss-generator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvpetersson%2Fpodcast-rss-generator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvpetersson%2Fpodcast-rss-generator/lists"}