Update .forgejo/scripts/radio.py

This commit is contained in:
Vlastimil Novotny / Ch-last / mxnticek 2025-10-25 23:14:07 +02:00
parent 5aced3d050
commit f0854ce3ae

View file

@ -1,10 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Multi-Country Radio M3U Playlist Generator (resilient version) Multi-Country Radio M3U Playlist Generator (resilient + dynamic mirrors)
-------------------------------------------------------------- -----------------------------------------------------------------------
Generuje M3U playlist s radii z více zemí pomocí radio-browser.info API. Generuje M3U playlist s radii z více zemí pomocí radio-browser.info API.
Tahle verze automaticky přepíná mezi dostupnými servery (mirrory) Automaticky detekuje dostupné servery přes DNS a přepíná mezi nimi.
a zvládá výpadky i HTTP 502 chyby.
""" """
import sys import sys
@ -13,6 +12,7 @@ import argparse
import time import time
import random import random
import requests import requests
import socket
from datetime import datetime from datetime import datetime
from pyradios import RadioBrowser from pyradios import RadioBrowser
@ -20,24 +20,60 @@ class RateLimitError(Exception):
"""Exception raised when rate limit is hit""" """Exception raised when rate limit is hit"""
pass pass
# === Nové: automatická inicializace s fallbacky === # === Funkce pro detekci aktuálních mirrorů ===
def get_dynamic_mirrors():
"""Získá aktuální dostupné RadioBrowser servery z DNS"""
hosts = []
try:
infos = socket.getaddrinfo('all.api.radio-browser.info', 80, 0, 0, socket.IPPROTO_TCP)
for info in infos:
ip = info[4][0]
try:
host = socket.gethostbyaddr(ip)[0]
if host not in hosts:
hosts.append(host)
except socket.herror:
continue
hosts.sort()
mirrors = [f"https://{h}" for h in hosts]
if mirrors:
print(f"🌍 Načteno {len(mirrors)} mirrorů z DNS:")
for m in mirrors:
print("", m)
return mirrors
except Exception as e:
print("⚠️ Nepodařilo se zjistit servery přes DNS:", e)
return []
# === Inicializace RadioBrowseru s fallbacky ===
def init_radio_browser(): def init_radio_browser():
mirrors = [ mirrors = get_dynamic_mirrors()
"https://de1.api.radio-browser.info",
"https://de2.api.radio-browser.info", if not mirrors:
"https://fi1.api.radio-browser.info", mirrors = [
] "https://de1.api.radio-browser.info",
"https://de2.api.radio-browser.info",
"https://fi1.api.radio-browser.info",
"https://nl1.api.radio-browser.info",
"https://at1.api.radio-browser.info",
]
print("⚠️ Používám fallback seznam mirrorů.")
random.shuffle(mirrors)
for mirror in mirrors: for mirror in mirrors:
try: try:
rb = RadioBrowser(base_url=mirror) rb = RadioBrowser(base_url=mirror)
rb.countries() # test funkčnosti rb.countries() # test funkčnosti
print(f"✅ Using RadioBrowser mirror: {mirror}") print(f"Používám RadioBrowser mirror: {mirror}")
return rb return rb
except Exception as e: except Exception as e:
print(f"⚠️ Mirror {mirror} failed: {e}") print(f"⚠️ Mirror {mirror} selhal: {e}")
continue continue
raise RuntimeError("❌ No working RadioBrowser mirrors found.")
raise RuntimeError("❌ Žádný funkční RadioBrowser mirror nebyl nalezen.")
# === Pomocné funkce ===
def get_workspace_safe_path(output_file): def get_workspace_safe_path(output_file):
if not output_file: if not output_file:
return None return None
@ -55,7 +91,7 @@ def generate_safe_output_path(output_file, country_codes):
country_string += "_etc" country_string += "_etc"
output_file = f"radio_playlist_{country_string}_{datetime.now().strftime('%Y%m%d')}.m3u" output_file = f"radio_playlist_{country_string}_{datetime.now().strftime('%Y%m%d')}.m3u"
safe_path = get_workspace_safe_path(output_file) safe_path = get_workspace_safe_path(output_file)
print(f"Output file will be created at: {safe_path}") print(f"📄 Výstupní soubor: {safe_path}")
return safe_path return safe_path
def fetch_stations_with_retry(rb, country_code, max_retries=5, initial_backoff=10): def fetch_stations_with_retry(rb, country_code, max_retries=5, initial_backoff=10):
@ -69,38 +105,38 @@ def fetch_stations_with_retry(rb, country_code, max_retries=5, initial_backoff=1
retry_count += 1 retry_count += 1
jitter = random.uniform(0.8, 1.2) jitter = random.uniform(0.8, 1.2)
wait_time = backoff_time * jitter wait_time = backoff_time * jitter
print(f"Rate limit exceeded. Waiting for {wait_time:.1f}s (retry {retry_count}/{max_retries})...") print(f"⏳ Rate limit čekám {wait_time:.1f}s (pokus {retry_count}/{max_retries})...")
time.sleep(wait_time) time.sleep(wait_time)
backoff_time *= 2 backoff_time *= 2
else: else:
raise raise
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
retry_count += 1 retry_count += 1
print(f"Network error ({e}). Retrying in {backoff_time}s...") print(f"⚠️ Síťová chyba ({e}). Opakuji za {backoff_time:.1f}s...")
time.sleep(backoff_time) time.sleep(backoff_time)
backoff_time *= 1.5 backoff_time *= 1.5
raise RateLimitError(f"Failed to fetch stations for {country_code} after {max_retries} retries") raise RateLimitError(f"❌ Nezdařilo se načíst stanice pro {country_code} po {max_retries} pokusech")
# === Hlavní logika ===
def create_multi_country_playlist(country_codes, output_file=None, group_title="Radio Stations", def create_multi_country_playlist(country_codes, output_file=None, group_title="Radio Stations",
default_logo_url="https://amz.odjezdy.online/xbackbone/VUne9/HilOMeka75.png/raw", default_logo_url="https://amz.odjezdy.online/xbackbone/VUne9/HilOMeka75.png/raw",
use_country_as_group=False): use_country_as_group=False):
rb = init_radio_browser() # <== použije odolnou inicializaci rb = init_radio_browser() # použije autodetekovaný mirror
output_file = generate_safe_output_path(output_file, country_codes) output_file = generate_safe_output_path(output_file, country_codes)
try: try:
countries_info = rb.countries() countries_info = rb.countries()
country_dict = {c['iso_3166_1']: c['name'] for c in countries_info} country_dict = {c['iso_3166_1']: c['name'] for c in countries_info}
except Exception as e: except Exception as e:
print(f"Failed to fetch country list: {e}") print(f"Nepodařilo se načíst seznam zemí: {e}")
return return
invalid_codes = [code.upper() for code in country_codes if code.upper() not in country_dict] invalid_codes = [code.upper() for code in country_codes if code.upper() not in country_dict]
if invalid_codes: if invalid_codes:
print(f"⚠️ Warning: invalid country codes: {', '.join(invalid_codes)}") print(f"⚠️ Neplatné kódy zemí: {', '.join(invalid_codes)}")
country_codes = [c for c in country_codes if c.upper() in country_dict] country_codes = [c for c in country_codes if c.upper() in country_dict]
if not country_codes: if not country_codes:
print("No valid country codes. Exiting.") print("❌ Žádné platné kódy zemí. Ukončuji.")
sys.exit(1) sys.exit(1)
unique_stations = {} unique_stations = {}
@ -110,15 +146,15 @@ def create_multi_country_playlist(country_codes, output_file=None, group_title="
for i, country_code in enumerate(country_codes): for i, country_code in enumerate(country_codes):
country_code = country_code.upper() country_code = country_code.upper()
country_name = country_dict.get(country_code, "Unknown") country_name = country_dict.get(country_code, "Unknown")
print(f"[{i+1}/{len(country_codes)}] Fetching {country_name} ({country_code})...") print(f"[{i+1}/{len(country_codes)}] 📻 {country_name} ({country_code})")
try: try:
stations = fetch_stations_with_retry(rb, country_code) stations = fetch_stations_with_retry(rb, country_code)
valid_stations = [s for s in stations if s.get('url')] valid_stations = [s for s in stations if s.get('url')]
if not valid_stations: if not valid_stations:
print(f"No stations found for {country_name}.") print(f"⚠️ Žádné stanice pro {country_name}.")
continue continue
total_found += len(valid_stations) total_found += len(valid_stations)
print(f"{len(valid_stations)} stations found for {country_name}.") print(f"{len(valid_stations)} stanic načteno pro {country_name}.")
for s in valid_stations: for s in valid_stations:
key = f"{s['name'].lower()}_{s['url']}" key = f"{s['name'].lower()}_{s['url']}"
if key not in unique_stations: if key not in unique_stations:
@ -126,17 +162,17 @@ def create_multi_country_playlist(country_codes, output_file=None, group_title="
s['country_name'] = country_name s['country_name'] = country_name
unique_stations[key] = s unique_stations[key] = s
except RateLimitError as e: except RateLimitError as e:
print(f"❌ Rate limit: {e}") print(f"❌ Rate limit pro {country_name}: {e}")
failed_countries.append(country_code) failed_countries.append(country_code)
except Exception as e: except Exception as e:
print(f"Error fetching {country_name}: {e}") print(f"Chyba při načítání {country_name}: {e}")
failed_countries.append(country_code) failed_countries.append(country_code)
if i < len(country_codes) - 1: if i < len(country_codes) - 1:
time.sleep(random.uniform(0.5, 1.5)) time.sleep(random.uniform(0.5, 1.5))
print(f"\n📊 Found {total_found} total, {len(unique_stations)} unique stations.") print(f"\n📊 Celkem nalezeno {total_found} (unikátních {len(unique_stations)}).")
if failed_countries: if failed_countries:
print(f"⚠️ Failed countries: {', '.join(failed_countries)}") print(f"⚠️ Neúspěšné země: {', '.join(failed_countries)}")
with open(output_file, 'w', encoding='utf-8') as f: with open(output_file, 'w', encoding='utf-8') as f:
f.write("#EXTM3U\n") f.write("#EXTM3U\n")
@ -149,7 +185,7 @@ def create_multi_country_playlist(country_codes, output_file=None, group_title="
f.write(f'#EXTINF:-1 group-title="{group}" tvg-logo="{logo}",{s["country_code"]} | {name}\n') f.write(f'#EXTINF:-1 group-title="{group}" tvg-logo="{logo}",{s["country_code"]} | {name}\n')
f.write(f"{s['url']}\n") f.write(f"{s['url']}\n")
print(f"🎧 Playlist created: {output_file} ({len(unique_stations)} stations)") print(f"🎧 Playlist vytvořen: {output_file} ({len(unique_stations)} stanic)")
return output_file return output_file
def parse_country_codes(countries_str): def parse_country_codes(countries_str):
@ -178,7 +214,7 @@ def main():
try: try:
create_multi_country_playlist(codes, args.output, args.group, args.logo, args.use_country_as_group) create_multi_country_playlist(codes, args.output, args.group, args.logo, args.use_country_as_group)
except KeyboardInterrupt: except KeyboardInterrupt:
print("\nProcess interrupted.") print("\n🛑 Přerušeno uživatelem.")
sys.exit(1) sys.exit(1)
if __name__ == "__main__": if __name__ == "__main__":