Aktualisiere Basis-URL auf 'https://www.serman.club' in local_podcast_generator.py und main.py; füge Funktion zum Extrahieren und Speichern von Episode-Covern hinzu.

This commit is contained in:
2025-07-05 20:11:58 +02:00
parent d1a1e5e6cf
commit f90c17f404
2 changed files with 57 additions and 9 deletions

View File

@ -16,7 +16,7 @@ from pathlib import Path
class LocalPodcastGenerator:
def __init__(self, audio_dir="../httpdocs/_audio", output_file="podcast_feed.xml", base_url="http://localhost:8087"):
def __init__(self, audio_dir="../httpdocs/_audio", output_file="podcast_feed.xml", base_url="https://www.serman.club"):
self.audio_dir = audio_dir
self.output_file = output_file
self.base_url = base_url.rstrip('/')
@ -46,11 +46,21 @@ class LocalPodcastGenerator:
artist = None
album = None
duration = None
cover_data = None
if audio.tags:
title = str(audio.tags.get('TIT2', [''])[0]) if audio.tags.get('TIT2') else None
artist = str(audio.tags.get('TPE1', [''])[0]) if audio.tags.get('TPE1') else None
album = str(audio.tags.get('TALB', [''])[0]) if audio.tags.get('TALB') else None
# Cover-Art extrahieren (APIC = Attached Picture)
if 'APIC:' in audio.tags:
cover_data = audio.tags.get('APIC:').data
elif audio.tags.get('APIC'):
# Fallback für andere APIC-Varianten
apic_tags = [tag for tag in audio.tags if tag.startswith('APIC')]
if apic_tags:
cover_data = audio.tags.get(apic_tags[0]).data
# Dauer in Sekunden
if hasattr(audio, 'info') and audio.info.length:
@ -73,7 +83,8 @@ class LocalPodcastGenerator:
'duration': duration,
'file_size': file_size,
'pub_date': pub_date,
'filename': file_path.name
'filename': file_path.name,
'cover_data': cover_data
}
except Exception as e:
@ -86,9 +97,38 @@ class LocalPodcastGenerator:
'duration': None,
'file_size': file_path.stat().st_size,
'pub_date': datetime.fromtimestamp(file_path.stat().st_mtime),
'filename': file_path.name
'filename': file_path.name,
'cover_data': None
}
def extract_episode_cover(self, metadata, episode_index):
"""Extrahiert und speichert das Episode-Cover."""
if not metadata['cover_data']:
return None
try:
# Erstelle Cover-Dateiname basierend auf MP3-Dateiname
cover_filename = f"cover_{Path(metadata['filename']).stem}.jpg"
# Speichere Cover im httpdocs-Verzeichnis
if "../httpdocs" in self.audio_dir:
cover_path = Path("../httpdocs") / cover_filename
else:
cover_path = Path(cover_filename)
# Schreibe Cover-Daten in Datei
with open(cover_path, 'wb') as f:
f.write(metadata['cover_data'])
# Rückgabe der URL zum Cover
cover_url = f"{self.base_url}/{cover_filename}"
print(f" 🖼️ Cover extrahiert: {cover_filename}")
return cover_url
except Exception as e:
print(f" ⚠️ Fehler beim Extrahieren des Covers: {e}")
return None
def format_duration(self, seconds):
"""Formatiert die Dauer in HH:MM:SS Format."""
if not seconds:
@ -153,7 +193,7 @@ Ich spezialisiere mich auf House Music, die mehr als nur Beats bietet sie er
itunes_explicit.text = "false"
# Standard-Bild (kann später angepasst werden)
image_url = f"{self.base_url}/podcast-cover.jpg"
image_url = f"{self.base_url}/_img/podcast-cover.png"
image = ET.SubElement(channel, "image")
image_url_elem = ET.SubElement(image, "url")
image_url_elem.text = image_url
@ -168,9 +208,12 @@ Ich spezialisiere mich auf House Music, die mehr als nur Beats bietet sie er
print(f"📦 Erstelle RSS-Feed mit {len(mp3_files)} Episoden...")
# Items (Episoden) hinzufügen
for mp3_file in mp3_files:
for episode_index, mp3_file in enumerate(mp3_files):
metadata = self.get_mp3_metadata(mp3_file)
# Episode-Cover extrahieren
episode_cover_url = self.extract_episode_cover(metadata, episode_index)
item = ET.SubElement(channel, "item")
# Titel
@ -220,6 +263,11 @@ Ich spezialisiere mich auf House Music, die mehr als nur Beats bietet sie er
itunes_explicit_item = ET.SubElement(item, "itunes:explicit")
itunes_explicit_item.text = "false"
# Episode-Cover hinzufügen (falls vorhanden)
if episode_cover_url:
itunes_image_item = ET.SubElement(item, "itunes:image")
itunes_image_item.set("href", episode_cover_url)
# Keywords/Tags basierend auf Dateiname
if any(keyword in metadata['title'].lower() for keyword in ['organic', 'house']):
itunes_keywords = ET.SubElement(item, "itunes:keywords")
@ -309,8 +357,8 @@ def main():
help="Verzeichnis mit MP3-Dateien (Standard: ../httpdocs/_audio)")
parser.add_argument("-o", "--output", default="podcast_feed.xml",
help="Ausgabedatei für den RSS-Feed (Standard: podcast_feed.xml)")
parser.add_argument("-u", "--base-url", default="http://localhost:8087",
help="Basis-URL für Audio-Dateien (Standard: http://localhost:8087)")
parser.add_argument("-u", "--base-url", default="https://www.serman.club",
help="Basis-URL für Audio-Dateien (Standard: https://www.serman.club)")
parser.add_argument("-t", "--title", default="SERMAN - Organic House Podcast",
help="Titel des Podcasts")
parser.add_argument("--author", default="SERMAN",

View File

@ -16,8 +16,8 @@ def main():
help="Verzeichnis mit MP3-Dateien (Standard: ../httpdocs/_audio)")
parser.add_argument("-o", "--output", default="serman_podcast.xml",
help="Ausgabedatei für den RSS-Feed (Standard: serman_podcast.xml)")
parser.add_argument("-u", "--base-url", default="http://localhost:8087",
help="Basis-URL für Audio-Dateien (Standard: http://localhost:8087)")
parser.add_argument("-u", "--base-url", default="https://www.serman.club",
help="Basis-URL für Audio-Dateien (Standard: https://www.serman.club)")
parser.add_argument("--serve", action="store_true",
help="Startet automatisch einen HTTP-Server nach der Generierung")
parser.add_argument("--port", type=int, default=8087,