﻿#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
update_rss.py  â€”  One-shot RSS updater for a static podcast feed.
Now supports both .mp3 and .m4a (AAC) files.
Place this file in the SAME folder as your feed.xml and audio files (e.g., D:\TTS Podcast\).

Usage:
    python update_rss.py
or  python update_rss.py --verbose

This will scan the folder for *.mp3 and *.m4a files that are not yet listed
in feed.xml and add them as <item>s. Safe to run repeatedly; it won't duplicate existing entries.
"""

import os
import sys
import argparse
import datetime
import xml.etree.ElementTree as ET
from urllib.parse import urlparse
from email.utils import format_datetime

# ------------ CONFIG (edit if needed) ------------
PODCAST_DIR = os.path.abspath(os.getcwd())  # current folder (recommended)
FEED_FILE    = os.path.join(PODCAST_DIR, "feed.xml")
BASE_URL = "https://tr0janv1ru5-encrypted.tail44dd40.ts.net/"  # include trailing slash
# -------------------------------------------------

SUPPORTED_EXTS = (".mp3", ".m4a")

# MIME types by extension (common, compatible with Overcast/Apple Podcasts)
MIME_BY_EXT = {
    ".mp3": "audio/mpeg",
    ".m4a": "audio/mp4",   # alternative seen: "audio/x-m4a"
}

ITUNES_NS = "http://www.itunes.com/dtds/podcast-1.0.dtd"
ATOM_NS   = "http://www.w3.org/2005/Atom"
ET.register_namespace('itunes', ITUNES_NS)
ET.register_namespace('atom', ATOM_NS)

def rfc2822(dt_utc: datetime.datetime) -> str:
    """Return RFC 2822 date string in GMT for podcast pubDate."""
    if dt_utc.tzinfo is None:
        dt_utc = dt_utc.replace(tzinfo=datetime.timezone.utc)
    return format_datetime(dt_utc)

def get_existing_filenames_from_feed(channel_elem):
    """Return a set of filenames already present in the feed by parsing enclosure URLs."""
    existing = set()
    for item in channel_elem.findall("item"):
        enc = item.find("enclosure")
        if enc is not None and 'url' in enc.attrib:
            try:
                path = urlparse(enc.attrib['url']).path
                fname = os.path.basename(path)
                if fname:
                    existing.add(fname)
            except Exception:
                pass
    return existing

def ensure_feed_exists():
    if not os.path.exists(FEED_FILE):
        sys.exit(f"[ERROR] feed.xml not found at: {FEED_FILE}\nMake sure you're running this in the folder that contains feed.xml.")

def open_feed_get_channel():
    try:
        tree = ET.parse(FEED_FILE)
        root = tree.getroot()
        channel = root.find("channel")
        if channel is None:
            sys.exit("[ERROR] RSS feed missing <channel> element.")
        return tree, root, channel
    except ET.ParseError as e:
        sys.exit(f"[ERROR] Unable to parse feed.xml: {e}")

def list_new_audio(existing_set):
    all_audio = [f for f in os.listdir(PODCAST_DIR) if os.path.splitext(f.lower())[1] in SUPPORTED_EXTS]
    new_files = [f for f in all_audio if f not in existing_set]
    new_files.sort(key=lambda f: os.path.getmtime(os.path.join(PODCAST_DIR, f)), reverse=True)
    return new_files

def build_item(filename: str):
    filepath = os.path.join(PODCAST_DIR, filename)
    size = os.path.getsize(filepath)
    mtime = datetime.datetime.utcfromtimestamp(os.path.getmtime(filepath)).replace(tzinfo=datetime.timezone.utc)

    ext = os.path.splitext(filename)[1].lower()
    mime = MIME_BY_EXT.get(ext, "application/octet-stream")

    item = ET.Element("item")
    title_el = ET.SubElement(item, "title")
    title_el.text = os.path.splitext(filename)[0]

    enclosure_el = ET.SubElement(item, "enclosure")
    enclosure_el.set("url", f"{BASE_URL}{filename}")
    enclosure_el.set("length", str(size))
    enclosure_el.set("type", mime)

    guid_el = ET.SubElement(item, "guid")
    # Use stable GUID = stable URL; include filename to avoid collisions
    guid_el.text = f"{BASE_URL}{filename}"

    pub_el = ET.SubElement(item, "pubDate")
    pub_el.text = rfc2822(mtime)  # file modified time as published time

    return item

def main():
    parser = argparse.ArgumentParser(description="Append new MP3/M4A files to feed.xml as <item>s.")
    parser.add_argument("--verbose", action="store_true", help="Print details while updating")
    args = parser.parse_args()

    ensure_feed_exists()
    tree, root, channel = open_feed_get_channel()

    existing = get_existing_filenames_from_feed(channel)
    if args.verbose:
        print(f"[INFO] Existing items: {len(existing)}")

    new_files = list_new_audio(existing)
    if not new_files:
        if args.verbose:
            print("[INFO] No new audio files detected. Nothing to do.")
        return

    if args.verbose:
        print(f"[INFO] Adding {len(new_files)} new file(s):")
        for f in new_files:
            print("   -", f)

    insert_index = 1
    for f in new_files:
        item = build_item(f)
        channel.insert(insert_index, item)
        insert_index += 1

    tree.write(FEED_FILE, encoding="utf-8", xml_declaration=True)
    if args.verbose:
        print("[OK] feed.xml updated successfully.")

if __name__ == "__main__":
    main()

