1
0
mirror of https://github.com/azlux/botamusique synced 2024-11-23 22:06:09 +00:00
botamusique/media/radio.py
Martin Weinelt 48d54beffd
radio: don't get stuck on mpd http streams
MPD HTTP streaming provide the media content at every URL thrown at the
HTTP backend. So requests for shoutcast and icecast metadata get stuck
receiving the actual media content, instead of the expected metadata.

The conclusion is to only request these metadata files, when they're not
actually advertised as audio or video content types in a HEAD request.
2021-06-01 23:16:22 +02:00

169 lines
5.4 KiB
Python

import re
import logging
import struct
import requests
import traceback
import hashlib
from media.item import BaseItem
from media.item import item_builders, item_loaders, item_id_generators
from constants import tr_cli as tr
log = logging.getLogger("bot")
def get_radio_server_description(url):
global log
log.debug("radio: fetching radio server description")
p = re.compile('(https?://[^/]*)', re.IGNORECASE)
res = re.search(p, url)
base_url = res.group(1)
url_icecast = base_url + '/status-json.xsl'
url_shoutcast = base_url + '/stats?json=1'
try:
response = requests.head(url_shoutcast, timeout=3)
if not response.headers.get('content-type', '').startswith(("audio/", "video/")):
response = requests.get(url_shoutcast, timeout=10)
data = response.json()
title_server = data['servertitle']
return title_server
# logging.info("TITLE FOUND SHOUTCAST: " + title_server)
except (requests.exceptions.ConnectionError,
requests.exceptions.HTTPError,
requests.exceptions.ReadTimeout,
requests.exceptions.Timeout):
error_traceback = traceback.format_exc()
error = error_traceback.rstrip().split("\n")[-1]
log.debug("radio: unsuccessful attempts on fetching radio description (shoutcast): " + error)
except ValueError:
return url
try:
response = requests.head(url_shoutcast, timeout=3)
if not response.headers.get('content-type', '').startswith(("audio/", "video/")):
response = requests.get(url_icecast, timeout=10)
data = response.json()
source = data['icestats']['source']
if type(source) is list:
source = source[0]
title_server = source['server_name']
if 'server_description' in source:
title_server += ' - ' + source['server_description']
# logging.info("TITLE FOUND ICECAST: " + title_server)
return title_server
except (requests.exceptions.ConnectionError,
requests.exceptions.HTTPError,
requests.exceptions.ReadTimeout,
requests.exceptions.Timeout):
error_traceback = traceback.format_exc()
error = error_traceback.rstrip().split("\n")[-1]
log.debug("radio: unsuccessful attempts on fetching radio description (icecast): " + error)
return url
def get_radio_title(url):
global log
log.debug("radio: fetching radio server description")
try:
r = requests.get(url, headers={'Icy-MetaData': '1'}, stream=True, timeout=10)
icy_metaint_header = int(r.headers['icy-metaint'])
r.raw.read(icy_metaint_header)
metadata_length = struct.unpack('B', r.raw.read(1))[0] * 16 # length byte
metadata = r.raw.read(metadata_length).rstrip(b'\0')
logging.info(metadata)
# extract title from the metadata
m = re.search(br"StreamTitle='([^']*)';", metadata)
if m:
title = m.group(1)
if title:
return title.decode()
except (requests.exceptions.ConnectionError,
requests.exceptions.HTTPError,
requests.exceptions.ReadTimeout,
requests.exceptions.Timeout,
KeyError):
log.debug("radio: unsuccessful attempts on fetching radio title (icy)")
return url
def radio_item_builder(**kwargs):
if 'name' in kwargs:
return RadioItem(kwargs['url'], kwargs['name'])
else:
return RadioItem(kwargs['url'], '')
def radio_item_loader(_dict):
return RadioItem("", "", _dict)
def radio_item_id_generator(**kwargs):
return hashlib.md5(kwargs['url'].encode()).hexdigest()
item_builders['radio'] = radio_item_builder
item_loaders['radio'] = radio_item_loader
item_id_generators['radio'] = radio_item_id_generator
class RadioItem(BaseItem):
def __init__(self, url, name="", from_dict=None):
if from_dict is None:
super().__init__()
self.url = url
if not name:
self.title = get_radio_server_description(self.url) # The title of the radio station
else:
self.title = name
self.id = hashlib.md5(url.encode()).hexdigest()
else:
super().__init__(from_dict)
self.url = from_dict['url']
self.title = from_dict['title']
self.type = "radio"
def validate(self):
self.version += 1 # 0 -> 1, notify the wrapper to save me when validate() is visited the first time
return True
def is_ready(self):
return True
def uri(self):
return self.url
def to_dict(self):
dict = super().to_dict()
dict['url'] = self.url
dict['title'] = self.title
return dict
def format_debug_string(self):
return "[radio] {name} ({url})".format(
name=self.title,
url=self.url
)
def format_song_string(self, user):
return tr("radio_item",
url=self.url,
title=get_radio_title(self.url), # the title of current song
name=self.title, # the title of radio station
user=user
)
def format_current_playing(self, user):
return tr("now_playing", item=self.format_song_string(user))
def format_title(self):
return self.title if self.title else self.url
def display_type(self):
return tr("radio")