Merge upstream master and format HTML

This commit is contained in:
Tyler Vigario 2020-06-01 17:24:21 -07:00
commit d2e32a3af5
No known key found for this signature in database
GPG Key ID: 4D670648A0376AA4
11 changed files with 339 additions and 626 deletions

View File

@ -11,7 +11,7 @@ import interface
import media.system import media.system
import util import util
import variables as var import variables as var
from librb import radiobrowser from pyradios import RadioBrowser
from database import SettingsDatabase, MusicDatabase, Condition from database import SettingsDatabase, MusicDatabase, Condition
from media.item import item_id_generators, dict_to_item, dicts_to_items from media.item import item_id_generators, dict_to_item, dicts_to_items
from media.cache import get_cached_wrapper_from_scrap, get_cached_wrapper_by_id, get_cached_wrappers_by_tags, \ from media.cache import get_cached_wrapper_from_scrap, get_cached_wrapper_by_id, get_cached_wrappers_by_tags, \
@ -74,8 +74,8 @@ def register_all_commands(bot):
bot.register_command(constants.commands('change_user_password'), cmd_user_password, no_partial_match=True) bot.register_command(constants.commands('change_user_password'), cmd_user_password, no_partial_match=True)
# Just for debug use # Just for debug use
bot.register_command('rtrms', cmd_real_time_rms, True) bot.register_command('rtrms', cmd_real_time_rms, True)
#bot.register_command('loop', cmd_loop_state, True) # bot.register_command('loop', cmd_loop_state, True)
#bot.register_command('item', cmd_item, True) # bot.register_command('item', cmd_item, True)
def send_multi_lines(bot, lines, text, linebreak="<br />"): def send_multi_lines(bot, lines, text, linebreak="<br />"):
@ -86,8 +86,8 @@ def send_multi_lines(bot, lines, text, linebreak="<br />"):
for newline in lines: for newline in lines:
msg += br msg += br
br = linebreak br = linebreak
if bot.mumble.get_max_message_length()\ if bot.mumble.get_max_message_length() \
and (len(msg) + len(newline)) > (bot.mumble.get_max_message_length() - 4): # 4 == len("<br>") and (len(msg) + len(newline)) > (bot.mumble.get_max_message_length() - 4): # 4 == len("<br>")
bot.send_msg(msg, text) bot.send_msg(msg, text)
msg = "" msg = ""
msg += newline msg += newline
@ -227,8 +227,8 @@ def cmd_play_file(bot, user, text, command, parameter, do_not_refresh_cache=Fals
# assume parameter is a folder # assume parameter is a folder
music_wrappers = get_cached_wrappers_from_dicts(var.music_db.query_music(Condition() music_wrappers = get_cached_wrappers_from_dicts(var.music_db.query_music(Condition()
.and_equal('type', 'file') .and_equal('type', 'file')
.and_like('path', parameter + '%')), user) .and_like('path', parameter + '%')), user)
if music_wrappers: if music_wrappers:
msgs = [constants.strings('multiple_file_added')] msgs = [constants.strings('multiple_file_added')]
@ -338,11 +338,10 @@ def cmd_play_playlist(bot, user, text, command, parameter):
pass pass
url = util.get_url_from_input(parameter) url = util.get_url_from_input(parameter)
log.debug("cmd: fetching media info from playlist url %s" % url) log.debug(f"cmd: fetching media info from playlist url {url}")
items = get_playlist_info(url=url, start_index=offset, user=user) items = get_playlist_info(url=url, start_index=offset, user=user)
if len(items) > 0: if len(items) > 0:
items = var.playlist.extend(list(map( items = var.playlist.extend(list(map(lambda item: get_cached_wrapper_from_scrap(**item), items)))
lambda item: get_cached_wrapper_from_scrap(**item), items)))
for music in items: for music in items:
log.info("cmd: add to playlist: " + music.format_debug_string()) log.info("cmd: add to playlist: " + music.format_debug_string())
else: else:
@ -386,24 +385,22 @@ def cmd_rb_query(bot, user, text, command, parameter):
bot.send_msg(msg, text) bot.send_msg(msg, text)
else: else:
log.debug('cmd: Found query parameter: ' + parameter) log.debug('cmd: Found query parameter: ' + parameter)
# bot.send_msg('Searching for stations - this may take some seconds...', text) rb = RadioBrowser()
rb_stations = radiobrowser.getstations_byname(parameter) rb_stations = rb.search(name=parameter, name_exact=False)
msg = constants.strings('rb_query_result') msg = constants.strings('rb_query_result')
msg += '\n<table><tr><th>!rbplay ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th></tr>' msg += '\n<table><tr><th>!rbplay ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th></tr>'
if not rb_stations: if not rb_stations:
log.debug('cmd: No matches found for rbquery ' + parameter) log.debug(f"cmd: No matches found for rbquery {parameter}")
bot.send_msg('Radio-Browser found no matches for ' + parameter, text) bot.send_msg(f"Radio-Browser found no matches for {parameter}", text)
else: else:
for s in rb_stations: for s in rb_stations:
stationid = s['id'] station_id = s['stationuuid']
stationname = s['stationname'] station_name = s['name']
country = s['country'] country = s['countrycode']
codec = s['codec'] codec = s['codec']
bitrate = s['bitrate'] bitrate = s['bitrate']
genre = s['genre'] genre = s['tags']
# msg += f'<tr><td>{stationid}</td><td>{stationname}</td><td>{genre}</td><td>{codec}/{bitrate}</td><td>{country}</td></tr>' msg += f"<tr><td>{station_id}</td><td>{station_name}</td><td>{genre}</td><td>{codec}/{bitrate}</td><td>{country}</td></tr>"
msg += '<tr><td>%s</td><td>%s</td><td>%s</td><td>%s/%s</td><td>%s</td></tr>' % (
stationid, stationname, genre, codec, bitrate, country)
msg += '</table>' msg += '</table>'
# Full message as html table # Full message as html table
if len(msg) <= 5000: if len(msg) <= 5000:
@ -414,10 +411,9 @@ def cmd_rb_query(bot, user, text, command, parameter):
msg = constants.strings('rb_query_result') + ' (shortened L1)' msg = constants.strings('rb_query_result') + ' (shortened L1)'
msg += '\n<table><tr><th>!rbplay ID</th><th>Station Name</th></tr>' msg += '\n<table><tr><th>!rbplay ID</th><th>Station Name</th></tr>'
for s in rb_stations: for s in rb_stations:
stationid = s['id'] station_id = s['stationuuid']
stationname = s['stationname'] station_name = s['name']
# msg += f'<tr><td>{stationid}</td><td>{stationname}</td>' msg += f'<tr><td>{station_id}</td><td>{station_name}</td>'
msg += '<tr><td>%s</td><td>%s</td>' % (stationid, stationname)
msg += '</table>' msg += '</table>'
if len(msg) <= 5000: if len(msg) <= 5000:
bot.send_msg(msg, text) bot.send_msg(msg, text)
@ -427,16 +423,14 @@ def cmd_rb_query(bot, user, text, command, parameter):
msg = constants.strings('rb_query_result') + ' (shortened L2)' msg = constants.strings('rb_query_result') + ' (shortened L2)'
msg += '!rbplay ID - Station Name' msg += '!rbplay ID - Station Name'
for s in rb_stations: for s in rb_stations:
stationid = s['id'] station_id = s['stationuuid']
stationname = s['stationname'][:12] station_name = s['name'][:12]
# msg += f'{stationid} - {stationname}' msg += f'{station_id} - {station_name}'
msg += '%s - %s' % (stationid, stationname)
if len(msg) <= 5000: if len(msg) <= 5000:
bot.send_msg(msg, text) bot.send_msg(msg, text)
# Message still too long # Message still too long
else: else:
bot.send_msg('Query result too long to post (> 5000 characters), please try another query.', bot.send_msg('Query result too long to post (> 5000 characters), please try another query.', text)
text)
def cmd_rb_play(bot, user, text, command, parameter): def cmd_rb_play(bot, user, text, command, parameter):
@ -449,22 +443,21 @@ def cmd_rb_play(bot, user, text, command, parameter):
bot.send_msg(msg, text) bot.send_msg(msg, text)
else: else:
log.debug('cmd: Retreiving url for station ID ' + parameter) log.debug('cmd: Retreiving url for station ID ' + parameter)
rstation = radiobrowser.getstationname_byid(parameter) rb = RadioBrowser()
rstation = rb.station_by_uuid(parameter)
stationname = rstation[0]['name'] stationname = rstation[0]['name']
country = rstation[0]['country'] country = rstation[0]['countrycode']
codec = rstation[0]['codec'] codec = rstation[0]['codec']
bitrate = rstation[0]['bitrate'] bitrate = rstation[0]['bitrate']
genre = rstation[0]['tags'] genre = rstation[0]['tags']
homepage = rstation[0]['homepage'] homepage = rstation[0]['homepage']
url = rstation[0]['url']
msg = 'Radio station added to playlist:' msg = 'Radio station added to playlist:'
# msg += '<table><tr><th>ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th><th>Homepage</th></tr>' + \
# f'<tr><td>{parameter}</td><td>{stationname}</td><td>{genre}</td><td>{codec}/{bitrate}</td><td>{country}</td><td>{homepage}</td></tr></table>'
msg += '<table><tr><th>ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th><th>Homepage</th></tr>' + \ msg += '<table><tr><th>ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th><th>Homepage</th></tr>' + \
'<tr><td>%s</td><td>%s</td><td>%s</td><td>%s/%s</td><td>%s</td><td>%s</td></tr></table>' \ f"<tr><td>{parameter}</td><td>{stationname}</td><td>{genre}</td><td>{codec}/{bitrate}</td><td>{country}</td><td>{homepage}</td></tr></table>"
% (parameter, stationname, genre, codec, bitrate, country, homepage) log.debug(f'cmd: Added station to playlist {stationname}')
log.debug('cmd: Added station to playlist %s' % stationname)
bot.send_msg(msg, text) bot.send_msg(msg, text)
url = radiobrowser.geturl_byid(parameter)
if url != "-1": if url != "-1":
log.info('cmd: Found url: ' + url) log.info('cmd: Found url: ' + url)
music_wrapper = get_cached_wrapper_from_scrap(type='radio', url=url, name=stationname, user=user) music_wrapper = get_cached_wrapper_from_scrap(type='radio', url=url, name=stationname, user=user)
@ -517,7 +510,7 @@ def cmd_yt_search(bot, user, text, command, parameter):
def _yt_format_result(results, start, count): def _yt_format_result(results, start, count):
msg = '<table><tr><th width="10%">Index</th><th>Title</th><th width="20%">Uploader</th></tr>' msg = '<table><tr><th width="10%">Index</th><th>Title</th><th width="20%">Uploader</th></tr>'
for index, item in enumerate(results[start:start+count]): for index, item in enumerate(results[start:start + count]):
msg += '<tr><td>{index:d}</td><td>{title}</td><td>{uploader}</td></tr>'.format( msg += '<tr><td>{index:d}</td><td>{title}</td><td>{uploader}</td></tr>'.format(
index=index + 1, title=item[1], uploader=item[2]) index=index + 1, title=item[1], uploader=item[2])
msg += '</table>' msg += '</table>'
@ -602,10 +595,9 @@ def cmd_volume(bot, user, text, command, parameter):
# The volume is a percentage # The volume is a percentage
if parameter and parameter.isdigit() and 0 <= int(parameter) <= 100: if parameter and parameter.isdigit() and 0 <= int(parameter) <= 100:
bot.volume_set = float(float(parameter) / 100) bot.volume_set = float(float(parameter) / 100)
bot.send_msg(constants.strings('change_volume', bot.send_msg(constants.strings('change_volume', volume=int(bot.volume_set * 100), user=bot.mumble.users[text.actor]['name']), text)
volume=int(bot.volume_set * 100), user=bot.mumble.users[text.actor]['name']), text)
var.db.set('bot', 'volume', str(bot.volume_set)) var.db.set('bot', 'volume', str(bot.volume_set))
log.info('cmd: volume set to %d' % (bot.volume_set * 100)) log.info(f'cmd: volume set to {bot.volume_set * 100}')
else: else:
bot.send_msg(constants.strings('current_volume', volume=int(bot.volume_set * 100)), text) bot.send_msg(constants.strings('current_volume', volume=int(bot.volume_set * 100)), text)
@ -618,8 +610,7 @@ def cmd_ducking(bot, user, text, command, parameter):
var.db.set('bot', 'ducking', True) var.db.set('bot', 'ducking', True)
bot.ducking_volume = var.config.getfloat("bot", "ducking_volume", fallback=0.05) bot.ducking_volume = var.config.getfloat("bot", "ducking_volume", fallback=0.05)
bot.ducking_threshold = var.config.getint("bot", "ducking_threshold", fallback=5000) bot.ducking_threshold = var.config.getint("bot", "ducking_threshold", fallback=5000)
bot.mumble.callbacks.set_callback(pymumble.constants.PYMUMBLE_CLBK_SOUNDRECEIVED, bot.mumble.callbacks.set_callback(pymumble.constants.PYMUMBLE_CLBK_SOUNDRECEIVED, bot.ducking_sound_received)
bot.ducking_sound_received)
bot.mumble.set_receive_sound(True) bot.mumble.set_receive_sound(True)
log.info('cmd: ducking is on') log.info('cmd: ducking is on')
msg = "Ducking on." msg = "Ducking on."
@ -639,10 +630,10 @@ def cmd_ducking_threshold(bot, user, text, command, parameter):
if parameter and parameter.isdigit(): if parameter and parameter.isdigit():
bot.ducking_threshold = int(parameter) bot.ducking_threshold = int(parameter)
var.db.set('bot', 'ducking_threshold', str(bot.ducking_threshold)) var.db.set('bot', 'ducking_threshold', str(bot.ducking_threshold))
msg = "Ducking threshold set to %d." % bot.ducking_threshold msg = f"Ducking threshold set to {bot.ducking_threshold}."
bot.send_msg(msg, text) bot.send_msg(msg, text)
else: else:
msg = "Current ducking threshold is %d." % bot.ducking_threshold msg = f"Current ducking threshold is {bot.ducking_threshold}."
bot.send_msg(msg, text) bot.send_msg(msg, text)
@ -652,11 +643,9 @@ def cmd_ducking_volume(bot, user, text, command, parameter):
# The volume is a percentage # The volume is a percentage
if parameter and parameter.isdigit() and 0 <= int(parameter) <= 100: if parameter and parameter.isdigit() and 0 <= int(parameter) <= 100:
bot.ducking_volume = float(float(parameter) / 100) bot.ducking_volume = float(float(parameter) / 100)
bot.send_msg(constants.strings('change_ducking_volume', bot.send_msg(constants.strings('change_ducking_volume', volume=int(bot.ducking_volume * 100), user=bot.mumble.users[text.actor]['name']), text)
volume=int(bot.ducking_volume * 100), user=bot.mumble.users[text.actor]['name']), text)
# var.db.set('bot', 'volume', str(bot.volume_set))
var.db.set('bot', 'ducking_volume', str(bot.ducking_volume)) var.db.set('bot', 'ducking_volume', str(bot.ducking_volume))
log.info('cmd: volume on ducking set to %d' % (bot.ducking_volume * 100)) log.info(f'cmd: volume on ducking set to {bot.ducking_volume * 100}')
else: else:
bot.send_msg(constants.strings('current_ducking_volume', volume=int(bot.ducking_volume * 100)), text) bot.send_msg(constants.strings('current_ducking_volume', volume=int(bot.ducking_volume * 100)), text)
@ -826,7 +815,7 @@ def cmd_mode(bot, user, text, command, parameter):
else: else:
var.db.set('playlist', 'playback_mode', parameter) var.db.set('playlist', 'playback_mode', parameter)
var.playlist = media.playlist.get_playlist(parameter, var.playlist) var.playlist = media.playlist.get_playlist(parameter, var.playlist)
log.info("command: playback mode changed to %s." % parameter) log.info(f"command: playback mode changed to {parameter}.")
bot.send_msg(constants.strings("change_mode", mode=var.playlist.mode, bot.send_msg(constants.strings("change_mode", mode=var.playlist.mode,
user=bot.mumble.users[text.actor]['name']), text) user=bot.mumble.users[text.actor]['name']), text)
if parameter == "random": if parameter == "random":
@ -878,8 +867,7 @@ def cmd_add_tag(bot, user, text, command, parameter):
if tags[0]: if tags[0]:
if index.isdigit() and 1 <= int(index) <= len(var.playlist): if index.isdigit() and 1 <= int(index) <= len(var.playlist):
var.playlist[int(index) - 1].add_tags(tags) var.playlist[int(index) - 1].add_tags(tags)
log.info("cmd: add tags %s to song %s" % (", ".join(tags), log.info(f"cmd: add tags {', '.join(tags)} to song {var.playlist[int(index) - 1].format_debug_string()}")
var.playlist[int(index) - 1].format_debug_string()))
bot.send_msg(constants.strings("added_tags", bot.send_msg(constants.strings("added_tags",
tags=", ".join(tags), tags=", ".join(tags),
song=var.playlist[int(index) - 1].format_title()), text) song=var.playlist[int(index) - 1].format_title()), text)
@ -888,8 +876,7 @@ def cmd_add_tag(bot, user, text, command, parameter):
elif index == "*": elif index == "*":
for item in var.playlist: for item in var.playlist:
item.add_tags(tags) item.add_tags(tags)
log.info("cmd: add tags %s to song %s" % (", ".join(tags), log.info(f"cmd: add tags {', '.join(tags)} to song {item.format_debug_string()}")
item.format_debug_string()))
bot.send_msg(constants.strings("added_tags_to_all", tags=", ".join(tags)), text) bot.send_msg(constants.strings("added_tags_to_all", tags=", ".join(tags)), text)
return return
@ -917,15 +904,14 @@ def cmd_remove_tag(bot, user, text, command, parameter):
if index.isdigit() and 1 <= int(index) <= len(var.playlist): if index.isdigit() and 1 <= int(index) <= len(var.playlist):
if tags[0] != "*": if tags[0] != "*":
var.playlist[int(index) - 1].remove_tags(tags) var.playlist[int(index) - 1].remove_tags(tags)
log.info("cmd: remove tags %s from song %s" % (", ".join(tags), log.info(f"cmd: remove tags {', '.join(tags)} from song {var.playlist[int(index) - 1].format_debug_string()}")
var.playlist[int(index) - 1].format_debug_string()))
bot.send_msg(constants.strings("removed_tags", bot.send_msg(constants.strings("removed_tags",
tags=", ".join(tags), tags=", ".join(tags),
song=var.playlist[int(index) - 1].format_title()), text) song=var.playlist[int(index) - 1].format_title()), text)
return return
else: else:
var.playlist[int(index) - 1].clear_tags() var.playlist[int(index) - 1].clear_tags()
log.info("cmd: clear tags from song %s" % (var.playlist[int(index) - 1].format_debug_string())) log.info(f"cmd: clear tags from song {var.playlist[int(index) - 1].format_debug_string()}")
bot.send_msg(constants.strings("cleared_tags", bot.send_msg(constants.strings("cleared_tags",
song=var.playlist[int(index) - 1].format_title()), text) song=var.playlist[int(index) - 1].format_title()), text)
return return
@ -934,14 +920,13 @@ def cmd_remove_tag(bot, user, text, command, parameter):
if tags[0] != "*": if tags[0] != "*":
for item in var.playlist: for item in var.playlist:
item.remove_tags(tags) item.remove_tags(tags)
log.info("cmd: remove tags %s from song %s" % (", ".join(tags), log.info(f"cmd: remove tags {', '.join(tags)} from song {item.format_debug_string()}")
item.format_debug_string()))
bot.send_msg(constants.strings("removed_tags_from_all", tags=", ".join(tags)), text) bot.send_msg(constants.strings("removed_tags_from_all", tags=", ".join(tags)), text)
return return
else: else:
for item in var.playlist: for item in var.playlist:
item.clear_tags() item.clear_tags()
log.info("cmd: clear tags from song %s" % (item.format_debug_string())) log.info(f"cmd: clear tags from song {item.format_debug_string()}")
bot.send_msg(constants.strings("cleared_tags_from_all"), text) bot.send_msg(constants.strings("cleared_tags_from_all"), text)
return return
@ -969,7 +954,7 @@ def cmd_find_tagged(bot, user, text, command, parameter):
count += 1 count += 1
if count > ITEMS_PER_PAGE: if count > ITEMS_PER_PAGE:
break break
msgs.append("<li><b>{:d}</b> - <b>{}</b> (<i>{}</i>)</li>".format(i+1, item.title, ", ".join(item.tags))) msgs.append("<li><b>{:d}</b> - <b>{}</b> (<i>{}</i>)</li>".format(i + 1, item.title, ", ".join(item.tags)))
if count != 0: if count != 0:
msgs.append("</ul>") msgs.append("</ul>")
@ -1210,7 +1195,7 @@ def cmd_web_user_add(bot, user, text, command, parameter):
if parameter not in web_users: if parameter not in web_users:
web_users.append(parameter) web_users.append(parameter)
var.db.set("privilege", "web_access", json.dumps(web_users)) var.db.set("privilege", "web_access", json.dumps(web_users))
bot.send_msg(constants.strings('web_user_list', users=", ". join(web_users)), text) bot.send_msg(constants.strings('web_user_list', users=", ".join(web_users)), text)
else: else:
bot.send_msg(constants.strings('command_disabled', command=command), text) bot.send_msg(constants.strings('command_disabled', command=command), text)
@ -1227,7 +1212,7 @@ def cmd_web_user_remove(bot, user, text, command, parameter):
if parameter in web_users: if parameter in web_users:
web_users.remove(parameter) web_users.remove(parameter)
var.db.set("privilege", "web_access", json.dumps(web_users)) var.db.set("privilege", "web_access", json.dumps(web_users))
bot.send_msg(constants.strings('web_user_list', users=", ". join(web_users)), text) bot.send_msg(constants.strings('web_user_list', users=", ".join(web_users)), text)
else: else:
bot.send_msg(constants.strings('command_disabled', command=command), text) bot.send_msg(constants.strings('command_disabled', command=command), text)
@ -1237,7 +1222,7 @@ def cmd_web_user_list(bot, user, text, command, parameter):
if auth_method == 'password': if auth_method == 'password':
web_users = json.loads(var.db.get("privilege", "web_access", fallback='[]')) web_users = json.loads(var.db.get("privilege", "web_access", fallback='[]'))
bot.send_msg(constants.strings('web_user_list', users=", ". join(web_users)), text) bot.send_msg(constants.strings('web_user_list', users=", ".join(web_users)), text)
else: else:
bot.send_msg(constants.strings('command_disabled', command=command), text) bot.send_msg(constants.strings('command_disabled', command=command), text)

View File

View File

@ -1,33 +0,0 @@
from librb.rbRadios import RadioBrowser
rb = RadioBrowser()
def getstations_byname(query):
results = rb.stations_byname(query)
stations = []
for st in results:
try:
station = {'stationname': st['name'], 'id': st['id'], 'codec': st['codec'], 'bitrate': st['bitrate'], 'country': st['country'], 'homepage': st['homepage'], 'genre': st['tags']}
stations.append(station)
except:
pass
return stations
def geturl_byid(id):
url = rb.playable_station(id)['url']
if url is not None:
return url
else:
return "-1"
def getstationname_byid(id):
return rb.stations_byid(id)
if __name__ == "__main__":
r = getstations_byname('r.sh')
stationinfo = getstationname_byid(96748)
pass

View File

@ -1,16 +0,0 @@
BASE_URL = "http://www.radio-browser.info/webservice/"
endpoints = {
"countries": {1: "{fmt}/countries", 2: "{fmt}/countries/{filter}"},
"codecs": {1: "{fmt}/codecs", 2: "{fmt}/codecs/{filter}"},
"states": {
1: "{fmt}/states",
2: "{fmt}/states/{filter}",
3: "{fmt}/states/{country}/{filter}",
},
"languages": {1: "{fmt}/languages", 2: "{fmt}/languages/{filter}"},
"tags": {1: "{fmt}/tags", 2: "{fmt}/tags/{filter}"},
"stations": {1: "{fmt}/stations", 3: "{fmt}/stations/{by}/{search_term}"},
"playable_station": {3: "{ver}/{fmt}/url/{station_id}"},
"station_search": {1: "{fmt}/stations/search"},
}

View File

@ -1,179 +0,0 @@
import requests
from librb.rbConstants import endpoints, BASE_URL
def request(endpoint, **kwargs):
fmt = kwargs.get("format", "json")
if fmt == "xml":
content_type = "application/%s" % fmt
else:
content_type = "application/%s" % fmt
headers = {"content-type": content_type, "User-Agent": "pyradios/dev"}
params = kwargs.get("params", {})
url = BASE_URL + endpoint
resp = requests.get(url, headers=headers, params=params)
if resp.status_code == 200:
if fmt == "xml":
return resp.text
return resp.json()
return resp.raise_for_status()
class EndPointBuilder:
def __init__(self, fmt="json"):
self.fmt = fmt
self._option = None
self._endpoint = None
@property
def endpoint(self):
return endpoints[self._endpoint][self._option]
def produce_endpoint(self, **parts):
self._option = len(parts)
self._endpoint = parts["endpoint"]
parts.update({"fmt": self.fmt})
return self.endpoint.format(**parts)
class RadioBrowser:
def __init__(self, fmt="json"):
self.fmt = fmt
self.builder = EndPointBuilder(fmt=self.fmt)
def countries(self, filter=""):
endpoint = self.builder.produce_endpoint(endpoint="countries")
return request(endpoint)
def codecs(self, filter=""):
endpoint = self.builder.produce_endpoint(endpoint="codecs")
return request(endpoint)
def states(self, country="", filter=""):
endpoint = self.builder.produce_endpoint(
endpoint="states", country=country, filter=filter
)
return request(endpoint)
def languages(self, filter=""):
endpoint = self.builder.produce_endpoint(endpoint="languages", filter=filter)
return request(endpoint)
def tags(self, filter=""):
endpoint = self.builder.produce_endpoint(endpoint="tags", filter=filter)
return request(endpoint)
def stations(self, **params):
endpoint = self.builder.produce_endpoint(endpoint="stations")
kwargs = {}
if params:
kwargs.update({"params": params})
return request(endpoint, **kwargs)
def stations_byid(self, id):
endpoint = self.builder.produce_endpoint(
endpoint="stations", by="byid", search_term=id
)
return request(endpoint)
def stations_byuuid(self, uuid):
endpoint = self.builder.produce_endpoint(
endpoint="stations", by="byuuid", search_term=uuid
)
return request(endpoint)
def stations_byname(self, name):
endpoint = self.builder.produce_endpoint(
endpoint="stations", by="byname", search_term=name
)
return request(endpoint)
def stations_bynameexact(self, nameexact):
endpoint = self.builder.produce_endpoint(
endpoint="stations", by="bynameexact", search_term=nameexact
)
return request(endpoint)
def stations_bycodec(self, codec):
endpoint = self.builder.produce_endpoint(
endpoint="stations", by="bycodec", search_term=codec
)
return request(endpoint)
def stations_bycodecexact(self, codecexact):
endpoint = self.builder.produce_endpoint(
endpoint="stations", by="bycodecexact", search_term=codecexact
)
return request(endpoint)
def stations_bycountry(self, country):
endpoint = self.builder.produce_endpoint(
endpoint="stations", by="bycountry", search_term=country
)
return request(endpoint)
def stations_bycountryexact(self, countryexact):
endpoint = self.builder.produce_endpoint(
endpoint="stations", by="bycountryexact", search_term=countryexact
)
return request(endpoint)
def stations_bystate(self, state):
endpoint = self.builder.produce_endpoint(
endpoint="stations", by="bystate", search_term=state
)
return request(endpoint)
def stations_bystateexact(self, stateexact):
endpoint = self.builder.produce_endpoint(
endpoint="stations", by="bystateexact", search_term=stateexact
)
return request(endpoint)
#
def stations_bylanguage(self, language):
endpoint = self.builder.produce_endpoint(
endpoint="stations", by="bylanguage", search_term=language
)
return request(endpoint)
def stations_bylanguageexact(self, languageexact):
endpoint = self.builder.produce_endpoint(
endpoint="stations", by="bylanguageexact", search_term=languageexact
)
return request(endpoint)
def stations_bytag(self, tag):
endpoint = self.builder.produce_endpoint(
endpoint="stations", by="bytag", search_term=tag
)
return request(endpoint)
def stations_bytagexact(self, tagexact):
endpoint = self.builder.produce_endpoint(
endpoint="stations", by="bytagexact", search_term=tagexact
)
return request(endpoint)
def playable_station(self, station_id):
endpoint = self.builder.produce_endpoint(
endpoint="playable_station", station_id=station_id, ver="v2"
)
return request(endpoint)
def station_search(self, params, **kwargs):
# http://www.radio-browser.info/webservice#Advanced_station_search
assert isinstance(params, dict), "params is not a dict"
kwargs["params"] = params
endpoint = self.builder.produce_endpoint(endpoint="station_search")
return request(endpoint, **kwargs)

View File

@ -108,6 +108,16 @@ class MusicCache(dict):
self.dir_lock.acquire() self.dir_lock.acquire()
self.log.info("library: rebuild directory cache") self.log.info("library: rebuild directory cache")
files = util.get_recursive_file_list_sorted(var.music_folder) files = util.get_recursive_file_list_sorted(var.music_folder)
# remove deleted files
results = self.db.query_music(Condition().or_equal('type', 'file'))
for result in results:
if result['path'] not in files:
self.log.debug("library: music file missed: %s, delete from library." % result['path'])
self.db.delete_music(Condition().and_equal('id', result['id']))
else:
files.remove(result['path'])
for file in files: for file in files:
results = self.db.query_music(Condition().and_equal('path', file)) results = self.db.query_music(Condition().and_equal('path', file))
if not results: if not results:
@ -129,7 +139,10 @@ class CachedItemWrapper:
self.version = 0 self.version = 0
def item(self): def item(self):
return self.lib[self.id] if self.id in self.lib:
return self.lib[self.id]
else:
raise ValueError(f"Uncached item of id {self.id}.")
def to_dict(self): def to_dict(self):
dict = self.item().to_dict() dict = self.item().to_dict()

View File

@ -35,7 +35,7 @@ class MumbleBot:
def __init__(self, args): def __init__(self, args):
self.log = logging.getLogger("bot") self.log = logging.getLogger("bot")
self.log.info("bot: botamusique version %s, starting..." % self.version) self.log.info(f"bot: botamusique version {self.version}, starting...")
signal.signal(signal.SIGINT, self.ctrl_caught) signal.signal(signal.SIGINT, self.ctrl_caught)
self.cmd_handle = {} self.cmd_handle = {}
self.volume_set = var.config.getfloat('bot', 'volume') self.volume_set = var.config.getfloat('bot', 'volume')
@ -50,8 +50,6 @@ class MumbleBot:
self.channel = var.config.get("server", "channel", fallback=None) self.channel = var.config.get("server", "channel", fallback=None)
var.user = args.user var.user = args.user
var.music_folder = util.solve_filepath(var.config.get('bot', 'music_folder'))
var.tmp_folder = util.solve_filepath(var.config.get('bot', 'tmp_folder'))
var.is_proxified = var.config.getboolean( var.is_proxified = var.config.getboolean(
"webinterface", "is_web_proxified") "webinterface", "is_web_proxified")
self.exit = False self.exit = False
@ -186,8 +184,8 @@ class MumbleBot:
new_version = util.new_release_version() new_version = util.new_release_version()
if version.parse(new_version) > version.parse(self.version): if version.parse(new_version) > version.parse(self.version):
changelog = util.fetch_changelog() changelog = util.fetch_changelog()
self.log.info("update: new version %s found, current installed version %s." % (new_version, self.version)) self.log.info(f"update: new version {new_version} found, current installed version {self.version}.")
self.log.info("update: changelog: " + changelog) self.log.info(f"update: changelog: {changelog}")
changelog = changelog.replace("\n", "<br>") changelog = changelog.replace("\n", "<br>")
self.send_channel_msg(constants.strings('new_version_found', new_version=new_version, changelog=changelog)) self.send_channel_msg(constants.strings('new_version_found', new_version=new_version, changelog=changelog))
else: else:
@ -305,7 +303,7 @@ class MumbleBot:
except: except:
error_traceback = traceback.format_exc() error_traceback = traceback.format_exc()
error = error_traceback.rstrip().split("\n")[-1] error = error_traceback.rstrip().split("\n")[-1]
self.log.error("bot: command %s failed with error: %s\n" % (command_exc, error_traceback)) self.log.error(f"bot: command {command_exc} failed with error: {error_traceback}\n")
self.send_msg(constants.strings('error_executing_command', command=command_exc, error=error), text) self.send_msg(constants.strings('error_executing_command', command=command_exc, error=error), text)
def send_msg(self, msg, text): def send_msg(self, msg, text):
@ -408,7 +406,7 @@ class MumbleBot:
def async_download(self, item): def async_download(self, item):
th = threading.Thread( th = threading.Thread(
target=self._download, name="Prepare-" + item.id[:7], args=(item,)) target=self._download, name="Prepare-" + item.id[:7], args=(item,))
self.log.info("bot: start preparing item in thread: %s" % item.format_debug_string()) self.log.info(f"bot: start preparing item in thread: {item.format_debug_string()}")
th.daemon = True th.daemon = True
th.start() th.start()
return th return th
@ -443,7 +441,7 @@ class MumbleBot:
while self.thread and self.mumble.sound_output.get_buffer_size() > 0.5 and not self.exit: while self.thread and self.mumble.sound_output.get_buffer_size() > 0.5 and not self.exit:
# If the buffer isn't empty, I cannot send new music part, so I wait # If the buffer isn't empty, I cannot send new music part, so I wait
self._loop_status = 'Wait for buffer %.3f' % self.mumble.sound_output.get_buffer_size() self._loop_status = f'Wait for buffer {self.mumble.sound_output.get_buffer_size():.3f}'
time.sleep(0.01) time.sleep(0.01)
if self.thread: if self.thread:
@ -629,7 +627,7 @@ class MumbleBot:
self.song_start_at = -1 self.song_start_at = -1
if len(var.playlist) > 0: if len(var.playlist) > 0:
self.pause_at_id = var.playlist.current_item().id self.pause_at_id = var.playlist.current_item().id
self.log.info("bot: music paused at %.2f seconds." % self.playhead) self.log.info(f"bot: music paused at {self.playhead:.2f} seconds.")
def resume(self): def resume(self):
self.is_pause = False self.is_pause = False
@ -768,6 +766,8 @@ if __name__ == '__main__':
DatabaseMigration(var.db, var.music_db).migrate() DatabaseMigration(var.db, var.music_db).migrate()
var.music_folder = util.solve_filepath(var.config.get('bot', 'music_folder'))
var.tmp_folder = util.solve_filepath(var.config.get('bot', 'tmp_folder'))
var.cache = MusicCache(var.music_db) var.cache = MusicCache(var.music_db)
if var.config.get("bot", "refresh_cache_on_startup", fallback=True): if var.config.get("bot", "refresh_cache_on_startup", fallback=True):
@ -785,7 +785,7 @@ if __name__ == '__main__':
if playback_mode in ["one-shot", "repeat", "random", "autoplay"]: if playback_mode in ["one-shot", "repeat", "random", "autoplay"]:
var.playlist = media.playlist.get_playlist(playback_mode) var.playlist = media.playlist.get_playlist(playback_mode)
else: else:
raise KeyError("Unknown playback mode '%s'" % playback_mode) raise KeyError(f"Unknown playback mode '{playback_mode}'")
# ====================== # ======================
# Create bot instance # Create bot instance

View File

@ -8,3 +8,4 @@ mutagen
requests requests
packaging packaging
pymumble pymumble
pyradios

File diff suppressed because one or more lines are too long

View File

@ -28,14 +28,12 @@
<button type="button" id="play-pause-btn" class="btn btn-info mb-2 btn-space" onclick="togglePlayPause()"> <button type="button" id="play-pause-btn" class="btn btn-info mb-2 btn-space" onclick="togglePlayPause()">
<i class="fas fa-play"></i> <i class="fas fa-play"></i>
</button> </button>
<button type="button" id="fast-forward-btn" class="btn btn-info mb-2" <button type="button" id="fast-forward-btn" class="btn btn-info mb-2" onclick="request('post', {action : 'next'})">
onclick="request('post', {action : 'next'})">
<i class="fas fa-fast-forward"></i> <i class="fas fa-fast-forward"></i>
</button> </button>
<div class="ml-auto"> <div class="ml-auto">
<div class="dropdown mr-2"> <div class="dropdown mr-2">
<button class="btn btn-secondary dropdown-toggle" type="button" id="play-mode" <button class="btn btn-secondary dropdown-toggle" type="button" id="play-mode" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fas fa-tasks mr-2" aria-hidden="true" id="modeIndicator"></i> <i class="fas fa-tasks mr-2" aria-hidden="true" id="modeIndicator"></i>
</button> </button>
<div class="dropdown-menu" aria-labelledby="play-mode"> <div class="dropdown-menu" aria-labelledby="play-mode">
@ -53,8 +51,7 @@
</a> </a>
</div> </div>
</div> </div>
<button type="button" id="volume-popover-btn" class="btn btn-warning ml-1" <button type="button" id="volume-popover-btn" class="btn btn-warning ml-1" onclick="toggleVolumePopover()">
onclick="toggleVolumePopover()">
<i class="fa fa-volume-up" aria-hidden="true"></i> <i class="fa fa-volume-up" aria-hidden="true"></i>
</button> </button>
@ -63,8 +60,7 @@
<i class="fa fa-volume-down" aria-hidden="true"></i> <i class="fa fa-volume-down" aria-hidden="true"></i>
</a> </a>
<input type="range" class="custom-range ml-1" id="volume-slider" min="0" max="1" step="0.01" <input type="range" class="custom-range ml-1" id="volume-slider" min="0" max="1" step="0.01" value="0.5" onchange="setVolumeDelayed(this.value)" />
value="0.5" onchange="setVolumeDelayed(this.value)" />
<a onclick="request('post', {action : 'volume_up'})"> <a onclick="request('post', {action : 'volume_up'})">
<i class="fa fa-volume-up" aria-hidden="true"></i> <i class="fa fa-volume-up" aria-hidden="true"></i>
@ -98,8 +94,7 @@
</tr> </tr>
<tr class="playlist-expand table-dark d-none"> <tr class="playlist-expand table-dark d-none">
<td colspan="4" class="text-center"> <td colspan="4" class="text-center">
<a class="text-muted" href="javascript:">See item <span <a class="text-muted" href="javascript:">See item <span class="playlist-expand-item-range"></span> on the playlist.
class="playlist-expand-item-range"></span> on the playlist.
</a> </a>
</td> </td>
</tr> </tr>
@ -161,16 +156,13 @@
<label>Type</label> <label>Type</label>
<div id="filter-type" class="input-group mb-2"> <div id="filter-type" class="input-group mb-2">
<div class="btn-group btn-group-sm btn-group-toggle"> <div class="btn-group btn-group-sm btn-group-toggle">
<label id="filter-type-file" class="btn btn-secondary" <label id="filter-type-file" class="btn btn-secondary" onclick="setFilterType(event, 'file')">
onclick="setFilterType(event, 'file')">
<input type="checkbox" name="options"> File <input type="checkbox" name="options"> File
</label> </label>
<label id="filter-type-url" class="btn btn-secondary" <label id="filter-type-url" class="btn btn-secondary" onclick="setFilterType(event, 'url')">
onclick="setFilterType(event, 'url')">
<input type="checkbox" name="options"> URL <input type="checkbox" name="options"> URL
</label> </label>
<label id="filter-type-radio" class="btn btn-secondary" <label id="filter-type-radio" class="btn btn-secondary" onclick="setFilterType(event, 'radio')">
onclick="setFilterType(event, 'radio')">
<input type="checkbox" name="options"> Radio <input type="checkbox" name="options"> Radio
</label> </label>
</div> </div>
@ -188,8 +180,7 @@
<label for="filter-keywords">Keywords</label> <label for="filter-keywords">Keywords</label>
<div id="filter-path" class="input-group mb-2"> <div id="filter-path" class="input-group mb-2">
<input class="form-control form-control-sm" id="filter-keywords" name="keywords" <input class="form-control form-control-sm" id="filter-keywords" name="keywords" placeholder="Keywords..." style="margin-top:5px;" />
placeholder="Keywords..." style="margin-top:5px;" />
</div> </div>
</div> </div>
@ -197,8 +188,7 @@
<label for="filter-tag">Tags</label> <label for="filter-tag">Tags</label>
<div id="filter-type mb-2"> <div id="filter-type mb-2">
{% for tag in tags_color_lookup.keys() %} {% for tag in tags_color_lookup.keys() %}
<span id="filter-tag" <span id="filter-tag" class="filter-tag tag-unclicked tag-click badge badge-{{ tags_color_lookup[tag] }}">{{ tag }}</span>
class="filter-tag tag-unclicked tag-click badge badge-{{ tags_color_lookup[tag] }}">{{ tag }}</span>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
@ -226,21 +216,89 @@
</div> </div>
</div> </div>
<label for="filter-keywords">Keywords</label> <div class="library-info-col library-info-title col-5" style="padding: 12px 0;">
<div id="filter-path" class="input-group mb-2"> <div>
<input class="form-control form-control-sm" id="filter-keywords" name="keywords" <span class="library-item-type lead text-muted btn-space">[File]</span>
placeholder="Keywords..." style="margin-top:5px;" /> <span class="library-item-title lead btn-space">This is my title</span>
<span class="library-item-artist text-muted"> - Artist</span>
</div>
</div>
<div class="library-info-col col-4 d-none d-md-flex" style="padding: 3px;">
<span class="library-item-path text-muted path">Path/to/the/file</span>
<div class="library-item-tags">
<a class="tag-space tag-click library-item-edit"><i class="fas fa-edit" style="color: #AAAAAA"></i></a>
<span class="library-item-notag badge badge-light text-muted font-italic">No tag</span>
<span class="library-item-tag tag-space badge">Tag</span>
</div>
</div>
<div class="btn-group library-action">
<button class="library-item-add-next btn btn-info btn-sm btn-space" type="button" title="Next to play">
<svg class="library-btn-svg" style="width: 1rem; fill: currentColor;" viewBox="5 5 17 17">
<path d="m5.700245,3.92964l0,14.150376l11.451127,-7.075188l-11.451127,-7.075188z"></path>
<path d="m20.942859,18.221072l-3.323292,0l0,3.323292l-1.107764,0l0,-3.323292l-3.323292,0l0,-1.107764l3.323292,0l0,-3.323292l1.107764,0l0,3.323292l3.323292,0l0,1.107764z"></path>
</svg>
</button>
<button class="library-item-add-bottom library-btn btn btn-info btn-sm btn-space" type="button" title="Add to bottom">
<svg class="library-btn-svg" style="width: 1rem; fill: currentColor;" viewBox="2 2 20 20">
<path d="M2,16H10V14H2M18,14V10H16V14H12V16H16V20H18V16H22V14M14,6H2V8H14M14,10H2V12H14V10Z"></path>
</svg>
</button>
<button class="library-item-download btn btn-primary btn-sm btn-space" type="button">
<i class="fas fa-download" aria-hidden="true"></i>
</button>
<button class="library-item-trash btn btn-danger btn-sm btn-space" type="button">
<i class="fas fa-trash-alt"></i>
</button>
</div> </div>
</div> </div>
<div class="library-info-col col-4 d-none d-md-flex" style="padding: 3px;"> </div>
<span class="library-item-path text-muted path">Path/to/the/file</span>
<div class="library-item-tags"> <div class="list-group">
<a class="tag-space tag-click library-item-edit"><i class="fas fa-edit" <div id="library-pagination" style="margin-left: auto; margin-top: 10px;">
style="color: #AAAAAA"></i></a> <ul id="library-page-ul" class="pagination pagination">
<span class="library-item-notag badge badge-light text-muted font-italic">No <li class="library-page-li page-item active">
tag</span> <a class="library-page-no page-link">1</a>
<span class="library-item-tag tag-space badge">Tag</span> </li>
</ul>
</div>
</div>
<div class="btn-group mb-2" role="group">
<button type="submit" class="btn btn-secondary mr-1" onclick="addAllResults()"><i class="fas fa-plus" aria-hidden="true"></i> Add All
</button>
<button type="submit" class="btn btn-secondary mr-1" onclick="request('post', {action : 'rescan'}); updateResults()">
<i class="fas fa-sync-alt" aria-hidden="true"></i> Rescan Files
</button>
<button type="submit" class="btn btn-secondary mr-1" onclick="downloadAllResults()"><i class="fas fa-download" aria-hidden="true"></i> Download All
</button>
<button type="button" class="btn btn-danger mr-1" data-toggle="modal" data-target="#deleteWarningModal"><i class="fas fa-trash-alt" aria-hidden="true"></i>
Delete All
</button>
</div>
<div class="modal fade" id="deleteWarningModal" tabindex="-1" role="dialog" aria-labelledby="Warning-Delete-File" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="deleteWarningModalLabel">Are you really sure?</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
All files listed here, include files on other pages, will be deleted from your
hard-drive.
Is that what you want?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-danger" data-dismiss="modal" onclick="deleteAllResults()">Delete All Listed Files</button>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -248,328 +306,213 @@
</div> </div>
</div> </div>
<div id="library-group" class="list-group library-group" style="overflow: auto;"> <!-- beautify ignore:start -->
<div id="library-item-loading" class="list-group-item library-item"> {% if upload_enabled %}
<img style="margin: auto; width: 35px;" src="static/img/loading.svg" /> <div id="upload" class="container mb-3">
{% else %}
<div id="upload" class="container mb-3" style="display: none;">
<input type="hidden" id="uploadDisabled" value="true" />
{% endif %}
<div class="card">
<div class="card-header">
<h5 class="card-title">Upload File</h5>
</div>
<div class="card-body">
<form action="./upload" method="post" enctype="multipart/form-data">
<div class="row">
<div id="uploadBox" class="col-lg-7">
<div class="input-group mb-3">
<div id="uploadField" style="display: flex; width: 100%">
<div class="custom-file">
<input type="file" name="file[]" class="custom-file-input" id="uploadSelectFile" aria-describedby="uploadSubmit" value="Browse Music file" multiple />
<label class="custom-file-label" for="uploadSelectFile">Choose file</label>
</div>
</div>
</div>
</div>
<div class="col-lg-5">
<div class="row">
<div class="col">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">Upload To</span>
</div>
<input class="form-control" list="targetdirs" id="uploadTargetDir" name="targetdir" placeholder="uploads" />
<datalist id="targetdirs">
{% for dir in dirs %}
<option value="{{ dir }}">
{% endfor %}
</datalist>
</div>
</div>
<div class="col-auto">
<button class="btn btn-primary" type="button" id="uploadSubmit"><i class="fas fa-upload mr-1"></i>Upload!</button>
</div>
</div>
</div>
</div>
</form>
</div>
</div> </div>
<div id="library-item-empty" style="display: none" class="list-group-item library-item"> </div>
<img style="margin: auto; width: 35px;" src="static/img/empty_box.svg" /> <!-- beautify ignore:end -->
</div>
<div id="library-item" style="display: none;" class="list-group-item library-item">
<input hidden type="text" class="library-item-id" value="" />
<div class="btn-group library-action"> <div class="container mb-5">
<button class="library-item-add-next btn btn-info btn-sm btn-space" type="button" title="Next to play"> <div class="card-deck">
<svg class="library-btn-svg" style="width: 1rem; fill: currentColor;" viewBox="5 5 17 17"> <div class="card">
<path d="m5.700245,3.92964l0,14.150376l11.451127,-7.075188l-11.451127,-7.075188z"></path> <div class="card-header">
<path <h5 class="card-title">Add URL</h5>
d="m20.942859,18.221072l-3.323292,0l0,3.323292l-1.107764,0l0,-3.323292l-3.323292,0l0,-1.107764l3.323292,0l0,-3.323292l1.107764,0l0,3.323292l3.323292,0l0,1.107764z"> </div>
</path> <div class="card-body">
</svg> <label for="add_url_input">Add Youtube or Soundcloud URL</label>
<div class="input-group mb-2">
</button> <input class="form-control" type="text" id="add_url_input" placeholder="URL...">
<button class="library-item-add-bottom library-btn btn btn-info btn-sm btn-space" type="button" </div>
title="Add to bottom"> <button type="submit" class="btn btn-primary" onclick="var $i = $('#add_url_input')[0]; request('post', {add_url : $i.value }); $i.value = ''; ">Add
<svg class="library-btn-svg" style="width: 1rem; fill: currentColor;" viewBox="2 2 20 20"> URL
<path d="M2,16H10V14H2M18,14V10H16V14H12V16H16V20H18V16H22V14M14,6H2V8H14M14,10H2V12H14V10Z"> </button>
</path> </div>
</svg> </div>
<div class="card">
</button> <div class="card-header">
<button class="library-item-download btn btn-primary btn-sm btn-space" type="button"> <h5 class="card-title">Add Radio</h5>
<i class="fas fa-download" aria-hidden="true"></i> </div>
</button> <div class="card-body">
<button class="library-item-trash btn btn-danger btn-sm btn-space" type="button"> <label for="add_radio_input">Add Radio URL</label>
<i class="fas fa-trash-alt"></i> <div class="input-group mb-2">
</button> <input class="form-control" type="text" id="add_radio_input" placeholder="Radio Address...">
</div>
<button type="submit" class="btn btn-primary" onclick="var $i = $('#add_radio_input')[0]; request('post', {add_radio : $i.value }); $i.value = '';">Add
Radio
</button>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="library-info-col library-info-title col-5" style="padding: 12px 0;"> <div class="floating-button" style="bottom: 120px;" onclick="togglePlayer()">
<div> <i class="fas fa-play" aria-hidden="true"></i>
<span class="library-item-type lead text-muted btn-space">[File]</span> </div>
<span class="library-item-title lead btn-space">This is my title</span>
<span class="library-item-artist text-muted"> - Artist</span> <div class="floating-button" style="bottom: 50px;" onclick="switchTheme()">
<i class="fas fa-lightbulb" aria-hidden="true"></i>
</div>
<div id="playerToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<i class="fas fa-play-circle mr-2 text-primary"></i>
<strong class="mr-auto">Now Playing...</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body" id="playerContainer">
<img id="playerArtworkIdle" src="static/img/empty_box.svg" />
<img id="playerArtwork" src="static/img/unknown-album.png" style="display: none;" />
<div id="playerInfo">
<div id="playerActionBox">
<button id="playerPlayBtn" class="btn btn-primary btn-sm btn-space" style="display: none" onclick="request('post', {action: 'resume'})">
<i class="fas fa-play"></i>
</button>
<button id="playerPauseBtn" class="btn btn-primary btn-sm btn-space" style="display: none" onclick="request('post', {action: 'pause'})">
<i class="fas fa-pause"></i>
</button>
<button id="playerSkipBtn" class="btn btn-primary btn-sm" onclick="request('post', {action : 'next'})">
<i class="fas fa-fast-forward"></i>
</button>
</div>
<div style="overflow: hidden; max-width: 320px;">
<strong id="playerTitle">Song Title</strong>
</div>
<span id="playerArtist">Artist</span>
<div id="playerBarBox" class="progress">
<div id="playerBar" class="progress-bar" role="progressbar" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
</div> </div>
</div> </div>
<div class="library-info-col col-4 d-none d-md-flex" style="padding: 3px;"> <div id="footer" style="height:50px; width: 100%; margin-top: 100px;"></div>
<span class="library-item-path text-muted path">Path/to/the/file</span>
<div class="library-item-tags">
<a class="tag-space tag-click library-item-edit"><i class="fas fa-edit" style="color: #AAAAAA"></i></a>
<span class="library-item-notag badge badge-light text-muted font-italic">No
tag</span>
<span class="library-item-tag tag-space badge">Tag</span>
</div>
</div>
<div class="btn-group" role="group" class="mb-2"> <form id="download-form" action="download" method="GET" target="_blank">
<button type="submit" class="btn btn-secondary mr-1" onclick="addAllResults()"><i class="fas fa-plus" <input hidden type="text" name="id" value="">
aria-hidden="true"></i> Add All <input hidden type="text" name="type" value="">
</button> <input hidden type="text" name="dir" value="">
<button type="submit" class="btn btn-secondary mr-1" <input hidden type="text" name="tags" value="">
onclick="request('post', {action : 'rescan'}); updateResults()"> <input hidden type="text" name="keywords" value="">
<i class="fas fa-sync-alt" aria-hidden="true"></i> Rescan Files </form>
</button>
<button type="submit" class="btn btn-secondary mr-1" onclick="downloadAllResults()"><i class="fas fa-download"
aria-hidden="true"></i> Download All
</button>
<button type="button" class="btn btn-danger mr-1" data-toggle="modal" data-target="#deleteWarningModal"><i
class="fas fa-trash-alt" aria-hidden="true"></i>
Delete All
</button>
</div>
<div class="modal fade" id="deleteWarningModal" tabindex="-1" role="dialog" aria-labelledby="Warning-Delete-File" <!-- Add tags modal -->
aria-hidden="true"> <div class="modal fade" id="addTagModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="deleteWarningModalLabel">Are you really sure?</h5> <h5 class="modal-title" id="addTagModalTitle">Edit tags for ?</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div id="addTagModalBody" class="modal-body">
All files listed here, include files on other pages, will be deleted from your <input hidden type="text" id="addTagModalItemId" name="id" value="">
hard-drive. <div class="modal-tag" style="display: none; width: 100%;">
Is that what you want? <span class="modal-tag-text tag-space badge badge-pill badge-dark">Tag</span>
<a class="modal-tag-remove tag-click small"><i class="fas fa-times-circle btn-outline-danger"></i></a>
</div>
<div id="addTagModalTags" style="margin-left: 5px; margin-bottom: 10px;">
</div>
<div class="input-group">
<input class="form-control form-control-sm btn-space" type="text" id="addTagModalInput" placeholder="tag1,tag2,...">
<button id="addTagModalAddBtn" type="button" class="btn btn-primary btn-sm" onclick="addTagModalAdd()">
<i class="fas fa-plus" aria-hidden="true"></i>
Add
</button>
</div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-danger" data-dismiss="modal" <button id="addTagModalSubmit" type="button" class="btn btn-success" data-dismiss="modal" onclick="addTagModalSubmit()">Edit!</button>
onclick="deleteAllResults()">Delete All Listed Files</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% if upload_enabled %} <!-- Upload files modal -->
<div id="upload" class="container mb-3"> <div class="modal fade" id="uploadModal" tabindex="-1" role="dialog" aria-hidden="true">
{% else %} <div class="modal-dialog" role="document">
<div id="upload" class="container mb-3" style="display: none;"> <div class="modal-content">
<input type="hidden" id="uploadDisabled" value="true" /> <div class="modal-header">
{% endif %} <h5 class="modal-title" id="uploadTitle"><i class="fas fa-upload mr-1"></i>Uploading files...</h5>
<div class="card">
<div class="card-header">
<h5 class="card-title">Upload File</h5>
</div> </div>
<div class="card-body"> <div id="uploadModalBody" class="modal-body">
<form action="./upload" method="post" enctype="multipart/form-data"> <div id="uploadSuccessAlert" class="alert alert-success" role="alert" style="display: none">
<div class="row"> <i class="fas fa-check mr-1"></i>
<div id="uploadBox" class="col-lg-7"> Uploaded finished!
<div class="input-group mb-3">
<div id="uploadField" style="display: flex; width: 100%">
<div class="custom-file">
<input type="file" name="file[]" class="custom-file-input"
id="uploadSelectFile" aria-describedby="uploadSubmit"
value="Browse Music file" multiple />
<label class="custom-file-label" for="uploadSelectFile">Choose file</label>
</div>
</div>
</div>
</div>
<div class="col-lg-5">
<div class="row">
<div class="col">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">Upload To</span>
</div>
<input class="form-control" list="targetdirs" id="uploadTargetDir"
name="targetdir" placeholder="uploads" />
<datalist id="targetdirs">
{% for dir in dirs %}
<option value="{{ dir }}">
{% endfor %}
</datalist>
</div>
</div>
<div class="col-auto">
<button class="btn btn-primary" type="button" id="uploadSubmit"><i
class="fas fa-upload mr-1"></i>Upload!</button>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="container mb-5">
<div class="card-deck">
<div class="card">
<div class="card-header">
<h5 class="card-title">Add URL</h5>
</div> </div>
<div class="card-body"> <div id="uploadModalList" style="margin-left: 5px; margin-bottom: 10px;">
<label for="add_url_input">Add Youtube or Soundcloud URL</label> <div class="uploadItem" style="display: none; width: 100%; padding-bottom: 8px;">
<div class="input-group mb-2"> <i class="far fa-file-alt mr-1"></i>
<input class="form-control" type="text" id="add_url_input" placeholder="URL..."> <span class="uploadItemTitle mr-3"></span>
</div> <span class="uploadItemError text-danger"></span>
<button type="submit" class="btn btn-primary" <div class="progress" style="margin-top: 5px; height: 10px;">
onclick="var $i = $('#add_url_input')[0]; request('post', {add_url : $i.value }); $i.value = ''; ">Add <div class="uploadProgress progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
URL
</button>
</div>
</div>
<div class="card">
<div class="card-header">
<h5 class="card-title">Add Radio</h5>
</div>
<div class="card-body">
<label for="add_radio_input">Add Radio URL</label>
<div class="input-group mb-2">
<input class="form-control" type="text" id="add_radio_input" placeholder="Radio Address...">
</div>
<button type="submit" class="btn btn-primary"
onclick="var $i = $('#add_radio_input')[0]; request('post', {add_radio : $i.value }); $i.value = '';">Add
Radio
</button>
</div>
</div>
</div>
</div>
<div class="floating-button" style="bottom: 120px;" onclick="togglePlayer()">
<i class="fas fa-play" aria-hidden="true"></i>
</div>
<div id="theme-switch-btn" class="floating-button" style="bottom: 50px;">
<i class="fas fa-lightbulb" aria-hidden="true"></i>
</div>
<div id="playerToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<i class="fas fa-play-circle mr-2 text-primary"></i>
<strong class="mr-auto">Now Playing...</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body" id="playerContainer">
<img id="playerArtworkIdle" src="static/img/empty_box.svg" />
<img id="playerArtwork" src="static/img/unknown-album.png" style="display: none;" />
<div id="playerInfo">
<div id="playerActionBox">
<button id="playerPlayBtn" class="btn btn-primary btn-sm btn-space" style="display: none"
onclick="request('post', {action: 'resume'})">
<i class="fas fa-play"></i>
</button>
<button id="playerPauseBtn" class="btn btn-primary btn-sm btn-space" style="display: none"
onclick="request('post', {action: 'pause'})">
<i class="fas fa-pause"></i>
</button>
<button id="playerSkipBtn" class="btn btn-primary btn-sm"
onclick="request('post', {action : 'next'})">
<i class="fas fa-fast-forward"></i>
</button>
</div>
<div style="overflow: hidden; max-width: 320px;">
<strong id="playerTitle">Song Title</strong>
</div>
<span id="playerArtist">Artist</span>
<div id="playerBarBox" class="progress">
<div id="playerBar" class="progress-bar" role="progressbar" aria-valuenow="50" aria-valuemin="0"
aria-valuemax="100"></div>
</div>
</div>
</div>
</div>
<div id="footer" style="height:50px; width: 100%; margin-top: 100px;"></div>
<form id="download-form" action="download" method="GET" target="_blank">
<input hidden type="text" name="id" value="">
<input hidden type="text" name="type" value="">
<input hidden type="text" name="dir" value="">
<input hidden type="text" name="tags" value="">
<input hidden type="text" name="keywords" value="">
</form>
<!-- Add tags modal -->
<div class="modal fade" id="addTagModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addTagModalTitle">Edit tags for ?</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div id="addTagModalBody" class="modal-body">
<input hidden type="text" id="addTagModalItemId" name="id" value="">
<div class="modal-tag" style="display: none; width: 100%;">
<span class="modal-tag-text tag-space badge badge-pill badge-dark">Tag</span>
<a class="modal-tag-remove tag-click small"><i
class="fas fa-times-circle btn-outline-danger"></i></a>
</div>
<div id="addTagModalTags" style="margin-left: 5px; margin-bottom: 10px;">
</div>
<div class="input-group">
<input class="form-control form-control-sm btn-space" type="text" id="addTagModalInput"
placeholder="tag1,tag2,...">
<button id="addTagModalAddBtn" type="button" class="btn btn-primary btn-sm"
onclick="addTagModalAdd()">
<i class="fas fa-plus" aria-hidden="true"></i>
Add
</button>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button id="addTagModalSubmit" type="button" class="btn btn-success" data-dismiss="modal"
onclick="addTagModalSubmit()">Edit!</button>
</div>
</div>
</div>
</div>
<!-- Upload files modal -->
<div class="modal fade" id="uploadModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="uploadTitle"><i class="fas fa-upload mr-1"></i>Uploading files...
</h5>
</div>
<div id="uploadModalBody" class="modal-body">
<div id="uploadSuccessAlert" class="alert alert-success" role="alert" style="display: none">
<i class="fas fa-check mr-1"></i>
Uploaded finished!
</div>
<div id="uploadModalList" style="margin-left: 5px; margin-bottom: 10px;">
<div class="uploadItem" style="display: none; width: 100%; padding-bottom: 8px;">
<i class="far fa-file-alt mr-1"></i>
<span class="uploadItemTitle mr-3"></span>
<span class="uploadItemError text-danger"></span>
<div class="progress" style="margin-top: 5px; height: 10px;">
<div class="uploadProgress progress-bar" role="progressbar" aria-valuenow="0"
aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="modal-footer"> </div>
<button type="button" id="uploadClose" class="btn btn-success" data-dismiss="modal"> <div class="modal-footer">
<i class="fas fa-times mr-1"></i> Close</button> <button type="button" id="uploadClose" class="btn btn-success" data-dismiss="modal">
<button type="button" id="uploadCancel" class="btn btn-danger" data-toggle="tooltip" <i class="fas fa-times mr-1"></i> Close</button>
data-html="true" <button type="button" id="uploadCancel" class="btn btn-danger" data-toggle="tooltip" data-html="true" title="<strong>Are you really sure?</strong> <br /> Click again to abort uploading.">
title="<strong>Are you really sure?</strong> <br /> Click again to abort uploading."> <i class="fas fa-trash-alt mr-1" aria-hidden="true"></i> Cancel</button>
<i class="fas fa-trash-alt mr-1" aria-hidden="true"></i> Cancel</button>
</div>
</div> </div>
</div> </div>
</div> </div>
</div>
<input type="hidden" id="maxUploadFileSize" value="{{ max_upload_file_size }}" /> <input type="hidden" id="maxUploadFileSize" value="{{ max_upload_file_size }}" />
<script src="static/js/main.js"></script> <script src="static/js/main.js"></script>
</body> </body>
</html> </html>

View File

@ -27,8 +27,7 @@
<div class="form-group mt-3"> <div class="form-group mt-3">
<label for="token_input">Token</label> <label for="token_input">Token</label>
<div class="input-group"> <div class="input-group">
<input type="password" class="form-control btn-space" id="token_input" name="token" <input type="password" class="form-control btn-space" id="token_input" name="token" placeholder="xxxxxxx">
placeholder="xxxxxxx">
<button type="submit" class="btn btn-primary">Submit</button> <button type="submit" class="btn btn-primary">Submit</button>
</div> </div>
</div> </div>