mirror of
https://github.com/azlux/botamusique
synced 2024-11-23 13:56:17 +00:00
48d54beffd
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.
169 lines
5.4 KiB
Python
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")
|