#!/usr/bin/env python3 """Writes resources/radio.m3u from tools/stations.txt and downloads the logos that are missing from resources/logos (512 px PNG, needs macOS sips). Run with: python3 -I tools/build.py [--logos] (--logos downloads every logo again) """ import json import os import re import subprocess import sys import unicodedata import urllib.parse import urllib.request ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) LOGOS = os.path.join(ROOT, 'resources', 'logos') UA = {'User-Agent': 'Mozilla/5.0'} def get(url): return urllib.request.urlopen(urllib.request.Request(url, headers=UA), timeout=20).read() def channel_id(name): name = unicodedata.normalize('NFKD', name.replace('+', ' mas')).encode('ascii', 'ignore').decode() return re.sub(r'[^a-z0-9]+', '-', name.lower()).strip('-') def stations(): with open(os.path.join(ROOT, 'tools', 'stations.txt'), encoding='utf-8') as f: for line in f: line = line.strip() if line and not line.startswith('#'): yield line.split('|', 4) def logo_url(source): if source.startswith('rb:'): query = urllib.parse.urlencode({'name': source[3:], 'limit': 5, 'order': 'clickcount', 'reverse': 'true'}) for s in json.loads(get('https://de1.api.radio-browser.info/json/stations/search?' + query)): if s['favicon']: return s['favicon'] return None country, slug = source.split('.', 1) page = get('https://onlineradiobox.com/{}/{}/'.format(country, slug)).decode('utf-8', 'replace') m = re.search(r'og:image" content="https://cdn\.onlineradiobox\.com/img/fblogo/([^"]+)"', page) return 'https://cdn.onlineradiobox.com/img/l/' + m.group(1) if m else None # the logo, not the share banner def fetch_logo(source, dest): url = logo_url(source) if not url: return False tmp = dest + '.tmp' with open(tmp, 'wb') as f: f.write(get(url)) try: subprocess.run(['sips', '-s', 'format', 'png', '-Z', '512', tmp, '--out', dest], check=True, capture_output=True) finally: os.remove(tmp) return True def main(): refresh = '--logos' in sys.argv os.makedirs(LOGOS, exist_ok=True) lines, used = ['#EXTM3U'], set() for chno, name, groups, source, url in stations(): cid = channel_id(name) logo = cid + '.png' path = os.path.join(LOGOS, logo) if source != '-' and (refresh or not os.path.exists(path)): try: if not fetch_logo(source, path): print('no logo found:', name) except Exception as e: print('logo failed:', name, e) attrs = 'tvg-id="{}" tvg-chno="{}" radio="true" group-title="{}"'.format(cid, chno, groups) if os.path.exists(path): attrs += ' tvg-logo="{}"'.format(logo) used.add(logo) else: print('no logo:', name) lines += ['#EXTINF:-1 {},{}'.format(attrs, name), url] for f in sorted(set(os.listdir(LOGOS)) - used): print('unused logo removed:', f) os.remove(os.path.join(LOGOS, f)) with open(os.path.join(ROOT, 'resources', 'radio.m3u'), 'w', encoding='utf-8') as f: f.write('\n'.join(lines) + '\n') print(len(lines) // 2, 'stations') if __name__ == '__main__': main()