Füge Mixcloud RSS Feed Generator hinzu, einschließlich Hauptskript, Serverstarter und Testskript. Aktualisiere .gitignore, .python-version, pyproject.toml und requirements.txt.
This commit is contained in:
200
.gitignore
vendored
Normal file
200
.gitignore
vendored
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
|
#poetry.lock
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
|
#pdm.lock
|
||||||
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
|
# in version control.
|
||||||
|
# https://pdm.fming.dev/#use-with-ide
|
||||||
|
.pdm.toml
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be added to the global gitignore or merged into this project gitignore. For a PyCharm
|
||||||
|
# project, it is recommended to comment out the following line:
|
||||||
|
#.idea/
|
||||||
|
|
||||||
|
# Project-specific files
|
||||||
|
# Generated RSS feeds
|
||||||
|
*.xml
|
||||||
|
!example_feed.xml
|
||||||
|
|
||||||
|
# Temporary test files
|
||||||
|
test_*.xml
|
||||||
|
*_test.xml
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# OS generated files
|
||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
._*
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
ehthumbs.db
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# VS Code
|
||||||
|
.vscode/
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
|
||||||
|
# IDE files
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Backup files
|
||||||
|
*.backup
|
||||||
|
*.bak
|
||||||
|
|
||||||
|
# UV package manager
|
||||||
|
.uv_cache/
|
1
.python-version
Normal file
1
.python-version
Normal file
@ -0,0 +1 @@
|
|||||||
|
3.11
|
92
README.md
Normal file
92
README.md
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
# Mixcloud RSS Feed Generator
|
||||||
|
|
||||||
|
Dieses Python-Script erstellt einen RSS-Feed aus deinen Mixcloud-Tracks, damit du sie über Podcast-Apps abonnieren und anhören kannst.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Abhängigkeiten installieren:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verwendung
|
||||||
|
|
||||||
|
### RSS-Feed erstellen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python mixcloud_rss.py serman_dj
|
||||||
|
```
|
||||||
|
|
||||||
|
Das erstellt eine `mixcloud_feed.xml` Datei mit deinen neuesten Mixcloud-Tracks.
|
||||||
|
|
||||||
|
### Erweiterte Optionen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Feed mit 100 Tracks erstellen
|
||||||
|
python mixcloud_rss.py serman_dj --limit 100
|
||||||
|
|
||||||
|
# Feed in spezifische Datei speichern
|
||||||
|
python mixcloud_rss.py serman_dj --output mein_feed.xml
|
||||||
|
|
||||||
|
# HTTP-Server starten für den Feed
|
||||||
|
python mixcloud_rss.py serman_dj --serve
|
||||||
|
|
||||||
|
# Server auf anderem Port starten
|
||||||
|
python mixcloud_rss.py serman_dj --serve --port 8080
|
||||||
|
```
|
||||||
|
|
||||||
|
### RSS-Feed in Podcast-App hinzufügen
|
||||||
|
|
||||||
|
1. **Mit HTTP-Server (empfohlen):**
|
||||||
|
- Starte den Server: `python mixcloud_rss.py serman_dj --serve`
|
||||||
|
- Füge diese URL in deiner Podcast-App hinzu: `http://localhost:8000/mixcloud_feed.xml`
|
||||||
|
|
||||||
|
2. **Feed-Datei hosten:**
|
||||||
|
- Lade die generierte XML-Datei auf einen Webserver hoch
|
||||||
|
- Verwende die öffentliche URL in deiner Podcast-App
|
||||||
|
|
||||||
|
## Funktionen
|
||||||
|
|
||||||
|
- ✅ Holt automatisch deine neuesten Mixcloud-Tracks
|
||||||
|
- ✅ Erstellt RSS-Feed im Podcast-Format
|
||||||
|
- ✅ Unterstützt iTunes-Tags für bessere Kompatibilität
|
||||||
|
- ✅ Inkludiert Track-Metadaten (Titel, Beschreibung, Dauer, Tags)
|
||||||
|
- ✅ Eingebauter HTTP-Server zum Testen
|
||||||
|
- ✅ Konfigurierbare Anzahl von Tracks
|
||||||
|
|
||||||
|
## Bekannte Einschränkungen
|
||||||
|
|
||||||
|
**Audio-Streaming:** Mixcloud erlaubt kein direktes Audio-Streaming ohne Autorisierung. Die generierten Links verweisen auf die Mixcloud-Webseite. Für echtes Audio-Streaming müsste man:
|
||||||
|
|
||||||
|
1. Die offizielle Mixcloud API für Streaming verwenden
|
||||||
|
2. Oder eine Alternative wie yt-dlp für das Extrahieren der Audio-URLs nutzen
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### "Keine Cloudcasts gefunden"
|
||||||
|
|
||||||
|
- Überprüfe den Benutzernamen
|
||||||
|
- Stelle sicher, dass das Profil öffentlich ist
|
||||||
|
|
||||||
|
### RSS-Feed wird nicht in Podcast-App erkannt
|
||||||
|
|
||||||
|
- Überprüfe, ob der HTTP-Server läuft
|
||||||
|
- Teste die URL im Browser: `http://localhost:8000/mixcloud_feed.xml`
|
||||||
|
|
||||||
|
## Beispiel-Ausgabe
|
||||||
|
|
||||||
|
```text
|
||||||
|
Erstelle RSS-Feed für Mixcloud-User: serman_dj
|
||||||
|
RSS-Feed erfolgreich erstellt: mixcloud_feed.xml
|
||||||
|
Anzahl der Episoden: 50
|
||||||
|
```
|
||||||
|
|
||||||
|
## Automatisierung
|
||||||
|
|
||||||
|
Du kannst das Script regelmäßig ausführen lassen, um den Feed aktuell zu halten:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Crontab-Eintrag für tägliche Updates um 6:00 Uhr
|
||||||
|
0 6 * * * cd /pfad/zu/rss-feeder && python mixcloud_rss.py serman_dj
|
||||||
|
```
|
6
main.py
Normal file
6
main.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
def main():
|
||||||
|
print("Hello from rss-feeder!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
240
mixcloud_rss.py
Executable file
240
mixcloud_rss.py
Executable file
@ -0,0 +1,240 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Mixcloud RSS Feed Generator
|
||||||
|
Erstellt einen RSS-Feed aus Mixcloud-Tracks für Podcast-Apps.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
from datetime import datetime
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
from urllib.parse import quote
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class MixcloudRSSGenerator:
|
||||||
|
def __init__(self, username, output_file="mixcloud_feed.xml"):
|
||||||
|
self.username = username
|
||||||
|
self.output_file = output_file
|
||||||
|
self.base_url = "https://api.mixcloud.com"
|
||||||
|
self.user_url = f"{self.base_url}/{username}/"
|
||||||
|
|
||||||
|
def get_user_info(self):
|
||||||
|
"""Holt Benutzerinformationen von Mixcloud."""
|
||||||
|
try:
|
||||||
|
response = requests.get(self.user_url)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
except requests.RequestException as e:
|
||||||
|
print(f"Fehler beim Abrufen der Benutzerinformationen: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_cloudcasts(self, limit=50):
|
||||||
|
"""Holt die neuesten Cloudcasts (Tracks) des Benutzers."""
|
||||||
|
cloudcasts_url = f"{self.user_url}cloudcasts/"
|
||||||
|
params = {"limit": limit}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(cloudcasts_url, params=params)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
return data.get("data", [])
|
||||||
|
except requests.RequestException as e:
|
||||||
|
print(f"Fehler beim Abrufen der Cloudcasts: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def format_duration(self, seconds):
|
||||||
|
"""Formatiert die Dauer in HH:MM:SS Format."""
|
||||||
|
hours = seconds // 3600
|
||||||
|
minutes = (seconds % 3600) // 60
|
||||||
|
seconds = seconds % 60
|
||||||
|
return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
|
||||||
|
|
||||||
|
def get_stream_url(self, cloudcast_key):
|
||||||
|
"""
|
||||||
|
Generiert eine Stream-URL für den Cloudcast.
|
||||||
|
Hinweis: Mixcloud erlaubt direktes Streaming nur über ihre eigene API.
|
||||||
|
"""
|
||||||
|
# Dies ist eine vereinfachte URL - in der Praxis müsste man
|
||||||
|
# die offizielle Mixcloud-Streaming-API verwenden
|
||||||
|
return f"https://www.mixcloud.com{cloudcast_key}stream/"
|
||||||
|
|
||||||
|
def create_rss_feed(self):
|
||||||
|
"""Erstellt den RSS-Feed aus den Mixcloud-Daten."""
|
||||||
|
user_info = self.get_user_info()
|
||||||
|
if not user_info:
|
||||||
|
return False
|
||||||
|
|
||||||
|
cloudcasts = self.get_cloudcasts()
|
||||||
|
if not cloudcasts:
|
||||||
|
print("Keine Cloudcasts gefunden.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# RSS Root Element
|
||||||
|
rss = ET.Element("rss")
|
||||||
|
rss.set("version", "2.0")
|
||||||
|
rss.set("xmlns:itunes", "http://www.itunes.com/dtds/podcast-1.0.dtd")
|
||||||
|
rss.set("xmlns:content", "http://purl.org/rss/1.0/modules/content/")
|
||||||
|
|
||||||
|
# Channel Element
|
||||||
|
channel = ET.SubElement(rss, "channel")
|
||||||
|
|
||||||
|
# Channel Metadaten
|
||||||
|
title = ET.SubElement(channel, "title")
|
||||||
|
title.text = f"{user_info.get('name', self.username)} - Mixcloud Feed"
|
||||||
|
|
||||||
|
description = ET.SubElement(channel, "description")
|
||||||
|
description.text = user_info.get('biog', f"Mixcloud-Feed von {self.username}")
|
||||||
|
|
||||||
|
link = ET.SubElement(channel, "link")
|
||||||
|
link.text = f"https://www.mixcloud.com/{self.username}/"
|
||||||
|
|
||||||
|
language = ET.SubElement(channel, "language")
|
||||||
|
language.text = "de-DE"
|
||||||
|
|
||||||
|
# iTunes-spezifische Tags
|
||||||
|
itunes_author = ET.SubElement(channel, "itunes:author")
|
||||||
|
itunes_author.text = user_info.get('name', self.username)
|
||||||
|
|
||||||
|
itunes_summary = ET.SubElement(channel, "itunes:summary")
|
||||||
|
itunes_summary.text = user_info.get('biog', f"Mixcloud-Feed von {self.username}")
|
||||||
|
|
||||||
|
itunes_category = ET.SubElement(channel, "itunes:category")
|
||||||
|
itunes_category.set("text", "Music")
|
||||||
|
|
||||||
|
# Bild falls vorhanden
|
||||||
|
if user_info.get('pictures', {}).get('large'):
|
||||||
|
image = ET.SubElement(channel, "image")
|
||||||
|
image_url = ET.SubElement(image, "url")
|
||||||
|
image_url.text = user_info['pictures']['large']
|
||||||
|
image_title = ET.SubElement(image, "title")
|
||||||
|
image_title.text = title.text
|
||||||
|
image_link = ET.SubElement(image, "link")
|
||||||
|
image_link.text = link.text
|
||||||
|
|
||||||
|
itunes_image = ET.SubElement(channel, "itunes:image")
|
||||||
|
itunes_image.set("href", user_info['pictures']['large'])
|
||||||
|
|
||||||
|
# Items (Episoden) hinzufügen
|
||||||
|
for cloudcast in cloudcasts:
|
||||||
|
item = ET.SubElement(channel, "item")
|
||||||
|
|
||||||
|
# Titel
|
||||||
|
item_title = ET.SubElement(item, "title")
|
||||||
|
item_title.text = cloudcast.get('name', 'Unbekannter Titel')
|
||||||
|
|
||||||
|
# Beschreibung
|
||||||
|
item_description = ET.SubElement(item, "description")
|
||||||
|
description_text = cloudcast.get('description', '')
|
||||||
|
if not description_text:
|
||||||
|
description_text = f"Mix von {self.username}"
|
||||||
|
item_description.text = description_text
|
||||||
|
|
||||||
|
# Link zur Mixcloud-Seite
|
||||||
|
item_link = ET.SubElement(item, "link")
|
||||||
|
item_link.text = cloudcast.get('url', '')
|
||||||
|
|
||||||
|
# GUID
|
||||||
|
item_guid = ET.SubElement(item, "guid")
|
||||||
|
item_guid.text = cloudcast.get('key', '')
|
||||||
|
item_guid.set("isPermaLink", "false")
|
||||||
|
|
||||||
|
# Veröffentlichungsdatum
|
||||||
|
item_pubdate = ET.SubElement(item, "pubDate")
|
||||||
|
created_time = cloudcast.get('created_time')
|
||||||
|
if created_time:
|
||||||
|
# Konvertiere ISO-Format zu RFC 2822
|
||||||
|
dt = datetime.fromisoformat(created_time.replace('Z', '+00:00'))
|
||||||
|
item_pubdate.text = dt.strftime('%a, %d %b %Y %H:%M:%S %z')
|
||||||
|
|
||||||
|
# Audio-Enclosure
|
||||||
|
# Hinweis: Mixcloud erlaubt kein direktes Audio-Streaming ohne Autorisierung
|
||||||
|
# Dies ist ein Platzhalter - für echtes Streaming müsste man die Mixcloud API verwenden
|
||||||
|
enclosure = ET.SubElement(item, "enclosure")
|
||||||
|
stream_url = f"https://www.mixcloud.com{cloudcast.get('key', '')}"
|
||||||
|
enclosure.set("url", stream_url)
|
||||||
|
enclosure.set("type", "audio/mpeg")
|
||||||
|
|
||||||
|
# Dauer
|
||||||
|
duration = cloudcast.get('audio_length', 0)
|
||||||
|
if duration:
|
||||||
|
item_duration = ET.SubElement(item, "itunes:duration")
|
||||||
|
item_duration.text = self.format_duration(duration)
|
||||||
|
|
||||||
|
# iTunes-spezifische Tags
|
||||||
|
itunes_title = ET.SubElement(item, "itunes:title")
|
||||||
|
itunes_title.text = item_title.text
|
||||||
|
|
||||||
|
itunes_summary = ET.SubElement(item, "itunes:summary")
|
||||||
|
itunes_summary.text = description_text
|
||||||
|
|
||||||
|
# Tags hinzufügen
|
||||||
|
tags = cloudcast.get('tags', [])
|
||||||
|
if tags:
|
||||||
|
keywords = ", ".join([tag['name'] for tag in tags[:5]]) # Nur erste 5 Tags
|
||||||
|
itunes_keywords = ET.SubElement(item, "itunes:keywords")
|
||||||
|
itunes_keywords.text = keywords
|
||||||
|
|
||||||
|
# XML in Datei schreiben
|
||||||
|
tree = ET.ElementTree(rss)
|
||||||
|
ET.indent(tree, space=" ", level=0)
|
||||||
|
|
||||||
|
try:
|
||||||
|
tree.write(self.output_file, encoding='utf-8', xml_declaration=True)
|
||||||
|
print(f"RSS-Feed erfolgreich erstellt: {self.output_file}")
|
||||||
|
print(f"Anzahl der Episoden: {len(cloudcasts)}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fehler beim Schreiben der XML-Datei: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def serve_feed(self, port=8000):
|
||||||
|
"""Startet einen einfachen HTTP-Server für den RSS-Feed."""
|
||||||
|
import http.server
|
||||||
|
import socketserver
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Wechsle in das Verzeichnis mit der XML-Datei
|
||||||
|
os.chdir(os.path.dirname(os.path.abspath(self.output_file)))
|
||||||
|
|
||||||
|
handler = http.server.SimpleHTTPRequestHandler
|
||||||
|
|
||||||
|
try:
|
||||||
|
with socketserver.TCPServer(("", port), handler) as httpd:
|
||||||
|
print(f"Server läuft auf http://localhost:{port}")
|
||||||
|
print(f"RSS-Feed verfügbar unter: http://localhost:{port}/{os.path.basename(self.output_file)}")
|
||||||
|
print("Drücke Ctrl+C zum Beenden")
|
||||||
|
httpd.serve_forever()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nServer beendet.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fehler beim Starten des Servers: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Erstellt einen RSS-Feed aus Mixcloud-Tracks")
|
||||||
|
parser.add_argument("username", help="Mixcloud-Benutzername (z.B. serman_dj)")
|
||||||
|
parser.add_argument("-o", "--output", default="mixcloud_feed.xml",
|
||||||
|
help="Ausgabedatei für den RSS-Feed (Standard: mixcloud_feed.xml)")
|
||||||
|
parser.add_argument("-l", "--limit", type=int, default=50,
|
||||||
|
help="Anzahl der zu holenden Tracks (Standard: 50)")
|
||||||
|
parser.add_argument("--serve", action="store_true",
|
||||||
|
help="Startet einen HTTP-Server für den RSS-Feed")
|
||||||
|
parser.add_argument("--port", type=int, default=8000,
|
||||||
|
help="Port für den HTTP-Server (Standard: 8000)")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
generator = MixcloudRSSGenerator(args.username, args.output)
|
||||||
|
|
||||||
|
print(f"Erstelle RSS-Feed für Mixcloud-User: {args.username}")
|
||||||
|
success = generator.create_rss_feed()
|
||||||
|
|
||||||
|
if success and args.serve:
|
||||||
|
generator.serve_feed(args.port)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
7
pyproject.toml
Normal file
7
pyproject.toml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[project]
|
||||||
|
name = "rss-feeder"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Add your description here"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
dependencies = []
|
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
requests>=2.31.0
|
95
start_server.py
Executable file
95
start_server.py
Executable file
@ -0,0 +1,95 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Einfacher Server-Starter für den Mixcloud RSS-Feed
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
import http.server
|
||||||
|
import socketserver
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def update_feed():
|
||||||
|
"""Aktualisiert den RSS-Feed."""
|
||||||
|
print("🔄 Aktualisiere RSS-Feed...")
|
||||||
|
result = subprocess.run([
|
||||||
|
sys.executable, "mixcloud_rss.py", "serman_dj",
|
||||||
|
"--output", "mixcloud_feed.xml"
|
||||||
|
], capture_output=True, text=True)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
print("✅ RSS-Feed erfolgreich aktualisiert!")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"❌ Fehler beim Aktualisieren: {result.stderr}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def start_server(port=8000):
|
||||||
|
"""Startet den HTTP-Server für den RSS-Feed."""
|
||||||
|
handler = http.server.SimpleHTTPRequestHandler
|
||||||
|
|
||||||
|
try:
|
||||||
|
with socketserver.TCPServer(("", port), handler) as httpd:
|
||||||
|
print(f"🌐 Server läuft auf http://localhost:{port}")
|
||||||
|
print(f"📡 RSS-Feed: http://localhost:{port}/mixcloud_feed.xml")
|
||||||
|
print("=" * 60)
|
||||||
|
print("📱 Podcast-App Anleitung:")
|
||||||
|
print(" 1. Kopiere diese URL: http://localhost:{port}/mixcloud_feed.xml")
|
||||||
|
print(" 2. Öffne deine Podcast-App")
|
||||||
|
print(" 3. Wähle 'Podcast hinzufügen' oder 'Feed hinzufügen'")
|
||||||
|
print(" 4. Füge die URL ein")
|
||||||
|
print("=" * 60)
|
||||||
|
print("⏹️ Drücke Ctrl+C zum Beenden")
|
||||||
|
print()
|
||||||
|
|
||||||
|
httpd.serve_forever()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n👋 Server beendet.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Fehler beim Starten des Servers: {e}")
|
||||||
|
|
||||||
|
def auto_update_feed(interval_minutes=60):
|
||||||
|
"""Aktualisiert den Feed automatisch in regelmäßigen Abständen."""
|
||||||
|
while True:
|
||||||
|
time.sleep(interval_minutes * 60) # Warte x Minuten
|
||||||
|
print(f"\n⏰ Automatische Aktualisierung nach {interval_minutes} Minuten...")
|
||||||
|
update_feed()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Hauptfunktion."""
|
||||||
|
print("🎵 SERMAN DJ - Mixcloud RSS Server")
|
||||||
|
print("=" * 40)
|
||||||
|
|
||||||
|
# Überprüfe ob wir im richtigen Verzeichnis sind
|
||||||
|
if not Path("mixcloud_rss.py").exists():
|
||||||
|
print("❌ mixcloud_rss.py nicht gefunden!")
|
||||||
|
print(" Stelle sicher, dass du im richtigen Verzeichnis bist.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Erstelle initialen Feed
|
||||||
|
print("🏁 Erstelle initialen RSS-Feed...")
|
||||||
|
if not update_feed():
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Überprüfe ob Feed existiert
|
||||||
|
if not Path("mixcloud_feed.xml").exists():
|
||||||
|
print("❌ RSS-Feed konnte nicht erstellt werden!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Starte automatische Updates in separatem Thread
|
||||||
|
update_thread = threading.Thread(
|
||||||
|
target=auto_update_feed,
|
||||||
|
args=(60,), # Aktualisiere jede Stunde
|
||||||
|
daemon=True
|
||||||
|
)
|
||||||
|
update_thread.start()
|
||||||
|
print("⏰ Automatische Updates aktiviert (alle 60 Minuten)")
|
||||||
|
|
||||||
|
# Starte Server
|
||||||
|
start_server()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
96
test_generator.py
Executable file
96
test_generator.py
Executable file
@ -0,0 +1,96 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Beispiel-Script zum Testen des Mixcloud RSS Generators
|
||||||
|
"""
|
||||||
|
|
||||||
|
from mixcloud_rss import MixcloudRSSGenerator
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def test_generator():
|
||||||
|
"""Testet den RSS-Generator mit deinem Mixcloud-Profil."""
|
||||||
|
username = "serman_dj"
|
||||||
|
|
||||||
|
print(f"🎵 Teste RSS-Generator für: {username}")
|
||||||
|
print("-" * 50)
|
||||||
|
|
||||||
|
# Generator erstellen
|
||||||
|
generator = MixcloudRSSGenerator(username, "test_feed.xml")
|
||||||
|
|
||||||
|
# Benutzerinfo testen
|
||||||
|
print("📋 Hole Benutzerinformationen...")
|
||||||
|
user_info = generator.get_user_info()
|
||||||
|
|
||||||
|
if user_info:
|
||||||
|
print(f"✅ Benutzer gefunden: {user_info.get('name', 'Unbekannt')}")
|
||||||
|
print(f" Follower: {user_info.get('follower_count', 0)}")
|
||||||
|
print(f" Following: {user_info.get('following_count', 0)}")
|
||||||
|
print(f" Cloudcasts: {user_info.get('cloudcast_count', 0)}")
|
||||||
|
else:
|
||||||
|
print("❌ Benutzer nicht gefunden!")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Cloudcasts testen
|
||||||
|
print("\n🎧 Hole die ersten 10 Cloudcasts...")
|
||||||
|
cloudcasts = generator.get_cloudcasts(limit=10)
|
||||||
|
|
||||||
|
if cloudcasts:
|
||||||
|
print(f"✅ {len(cloudcasts)} Cloudcasts gefunden:")
|
||||||
|
for i, cloudcast in enumerate(cloudcasts[:5], 1):
|
||||||
|
name = cloudcast.get('name', 'Unbekannter Titel')
|
||||||
|
duration = cloudcast.get('audio_length', 0)
|
||||||
|
duration_str = generator.format_duration(duration) if duration else "Unbekannt"
|
||||||
|
print(f" {i}. {name} ({duration_str})")
|
||||||
|
|
||||||
|
if len(cloudcasts) > 5:
|
||||||
|
print(f" ... und {len(cloudcasts) - 5} weitere")
|
||||||
|
else:
|
||||||
|
print("❌ Keine Cloudcasts gefunden!")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# RSS-Feed erstellen
|
||||||
|
print("\n📡 Erstelle RSS-Feed...")
|
||||||
|
success = generator.create_rss_feed()
|
||||||
|
|
||||||
|
if success:
|
||||||
|
print("✅ RSS-Feed erfolgreich erstellt!")
|
||||||
|
print(f" Datei: {generator.output_file}")
|
||||||
|
|
||||||
|
# Feed-Info anzeigen
|
||||||
|
import os
|
||||||
|
if os.path.exists(generator.output_file):
|
||||||
|
size = os.path.getsize(generator.output_file)
|
||||||
|
print(f" Größe: {size} Bytes")
|
||||||
|
|
||||||
|
# Erste Zeilen der XML-Datei anzeigen
|
||||||
|
print("\n📄 Feed-Preview:")
|
||||||
|
with open(generator.output_file, 'r', encoding='utf-8') as f:
|
||||||
|
lines = f.readlines()[:10]
|
||||||
|
for line in lines:
|
||||||
|
print(f" {line.rstrip()}")
|
||||||
|
if len(lines) >= 10:
|
||||||
|
print(" ...")
|
||||||
|
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print("❌ Fehler beim Erstellen des RSS-Feeds!")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Hauptfunktion."""
|
||||||
|
print("🎵 Mixcloud RSS Generator - Test")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
success = test_generator()
|
||||||
|
|
||||||
|
if success:
|
||||||
|
print("\n🎉 Test erfolgreich abgeschlossen!")
|
||||||
|
print("\nNächste Schritte:")
|
||||||
|
print("1. Starte den Server: python mixcloud_rss.py serman_dj --serve")
|
||||||
|
print("2. Öffne http://localhost:8000/mixcloud_feed.xml im Browser")
|
||||||
|
print("3. Füge die URL in deiner Podcast-App hinzu")
|
||||||
|
else:
|
||||||
|
print("\n❌ Test fehlgeschlagen!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
Reference in New Issue
Block a user