Skip to content

Instantly share code, notes, and snippets.

@Alistair1231
Last active January 25, 2025 15:56
Show Gist options
  • Save Alistair1231/01b608633b9ef3a1b8ddc3df5e139494 to your computer and use it in GitHub Desktop.
Save Alistair1231/01b608633b9ef3a1b8ddc3df5e139494 to your computer and use it in GitHub Desktop.
podcast-dlgen.py - Easily download podcasts with names from a RSS feed and jdownloader
#!/usr/bin/env python3
# /// script
# dependencies = [
# "requests>=2.31.0",
# "lxml>=4.9.4",
# "pyperclip>=1.8.2",
# ]
# ///
# Inspired by https://maxbarrass.com/blog/2020/04/10/downloading-files-from-rss-with-jdownloader-packegizer%F0%9F%92%BB%F0%9F%92%A5%F0%9F%98%8D
# archive link: https://archive.is/rP7Qg
#! jDownloader2 Packagizer Rule:
# Sourceulr(s): contains | #name=(.+)(?=\....)
# Package Name: <jd:orgfiletype>
# Filename: <jd:source:1>.<jd:orgfiletype>
#!
#* $ uv run python-dlgen.py -h
#* usage: python-dlgen.py [-h] url
#*
#* Process podcast RSS feed and generate download list.
#*
#* positional arguments:
#* url URL of the RSS feed
#*
#* options:
#* -h, --help show this help message and exit
import argparse
import sys
try:
import pyperclip
except ImportError:
pyperclip = None
import requests
from lxml import etree
XSLT_STR = '''\
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
<xsl:output method="text" encoding="utf-8"/>
<xsl:template match="/">
<xsl:apply-templates select="//item"/>
</xsl:template>
<xsl:template match="item">
<xsl:variable name="url" select="enclosure/@url"/>
<xsl:variable name="episode" select="itunes:episode"/>
<xsl:variable name="title" select="normalize-space(title)"/>
<xsl:variable name="mime-type" select="enclosure/@type"/>
<!-- Build filename -->
<xsl:variable name="base-name">
<xsl:choose>
<xsl:when test="$episode != ''">
<xsl:value-of select="concat($episode, ' - ', $title)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$title"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- Determine extension -->
<xsl:variable name="extension">
<xsl:choose>
<xsl:when test="contains($mime-type, 'mpeg')">mp3</xsl:when>
<xsl:when test="contains($mime-type, 'm4a')">m4a</xsl:when>
<xsl:when test="contains($mime-type, 'mp4')">mp4</xsl:when>
<xsl:otherwise>
<!-- Fallback: Check URL extension -->
<xsl:variable name="url-lower" select="translate($url, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')"/>
<xsl:choose>
<xsl:when test="contains($url-lower, '.mp3')">mp3</xsl:when>
<xsl:when test="contains($url-lower, '.m4a')">m4a</xsl:when>
<xsl:when test="contains($url-lower, '.mp4')">mp4</xsl:when>
<xsl:otherwise>mp3</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="concat(normalize-space($url), '#name=', $base-name, '.', $extension)"/>
<xsl:text>&#10;</xsl:text>
</xsl:template>
</xsl:stylesheet>
'''
def main():
parser = argparse.ArgumentParser(description='Process podcast RSS feed and generate download list.')
parser.add_argument('url', help='URL of the RSS feed')
args = parser.parse_args()
try:
response = requests.get(args.url)
response.raise_for_status()
except requests.exceptions.RequestException as e:
print(f"Error downloading RSS feed: {e}", file=sys.stderr)
sys.exit(1)
try:
# Parse XML and XSLT with consistent encoding
xml_doc = etree.fromstring(response.content)
xslt_doc = etree.fromstring(XSLT_STR.encode('utf-8'))
transformer = etree.XSLT(xslt_doc)
result = transformer(xml_doc)
output = str(result).strip()
except Exception as e:
print(f"Processing Error: {e}", file=sys.stderr)
sys.exit(1)
print(output)
if pyperclip is not None:
try:
pyperclip.copy(output)
print("Output copied to clipboard.", file=sys.stderr)
except pyperclip.PyperclipException as e:
print(f"Clipboard Error: {e}", file=sys.stderr)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment