Last active
January 25, 2025 15:56
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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> </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