Merge pull request #100 from Lartza/pep8

Cleanup
This commit is contained in:
azlux 2020-03-10 23:30:35 +01:00 committed by GitHub
commit 1a97d1c588
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 194 additions and 208 deletions

View File

@ -1,6 +1,5 @@
# coding=utf-8 # coding=utf-8
import logging import logging
import os.path
import pymumble.pymumble_py3 as pymumble import pymumble.pymumble_py3 as pymumble
import re import re
@ -10,15 +9,13 @@ import util
import variables as var import variables as var
from librb import radiobrowser from librb import radiobrowser
from database import SettingsDatabase, MusicDatabase from database import SettingsDatabase, MusicDatabase
from media.item import item_builders, item_loaders, 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
from media.file import FileItem from media.url_from_playlist import get_playlist_info
from media.url_from_playlist import PlaylistURLItem, get_playlist_info
from media.url import URLItem
from media.radio import RadioItem
log = logging.getLogger("bot") log = logging.getLogger("bot")
def register_all_commands(bot): def register_all_commands(bot):
bot.register_command(constants.commands('joinme'), cmd_joinme, no_partial_match=False, access_outside_channel=True) bot.register_command(constants.commands('joinme'), cmd_joinme, no_partial_match=False, access_outside_channel=True)
bot.register_command(constants.commands('user_ban'), cmd_user_ban, no_partial_match=True) bot.register_command(constants.commands('user_ban'), cmd_user_ban, no_partial_match=True)
@ -71,6 +68,7 @@ def register_all_commands(bot):
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 />"):
global log global log
@ -86,12 +84,13 @@ def send_multi_lines(bot, lines, text, linebreak="<br />"):
bot.send_msg(msg, text) bot.send_msg(msg, text)
# ---------------- Variables ----------------- # ---------------- Variables -----------------
song_shortlist = [] song_shortlist = []
# ---------------- Commands ------------------
# ---------------- Commands ------------------
def cmd_joinme(bot, user, text, command, parameter): def cmd_joinme(bot, user, text, command, parameter):
global log global log
@ -146,6 +145,7 @@ def cmd_url_ban(bot, user, text, command, parameter):
bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin')) bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
return return
def cmd_url_ban_list(bot, user, text, command, parameter): def cmd_url_ban_list(bot, user, text, command, parameter):
if bot.is_admin(user): if bot.is_admin(user):
bot.mumble.users[text.actor].send_text_message(util.get_url_ban()) bot.mumble.users[text.actor].send_text_message(util.get_url_ban())
@ -153,6 +153,7 @@ def cmd_url_ban_list(bot, user, text, command, parameter):
bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin')) bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
return return
def cmd_url_unban(bot, user, text, command, parameter): def cmd_url_unban(bot, user, text, command, parameter):
global log global log
@ -170,9 +171,11 @@ def cmd_play(bot, user, text, command, parameter):
if len(var.playlist) > 0: if len(var.playlist) > 0:
if parameter: if parameter:
if parameter.isdigit() and 1 <= int(parameter) <= len(var.playlist): if parameter.isdigit() and 1 <= int(parameter) <= len(var.playlist):
var.playlist.point_to(int(parameter) - 1 - 1) # First "-1" transfer 12345 to 01234, second "-1" # First "-1" transfer 12345 to 01234, second "-1"
# point to the previous item. the loop will next to # point to the previous item. the loop will next to
# the one you want # the one you want
var.playlist.point_to(int(parameter) - 1 - 1)
bot.interrupt() bot.interrupt()
else: else:
bot.send_msg(constants.strings('invalid_index', index=parameter), text) bot.send_msg(constants.strings('invalid_index', index=parameter), text)
@ -274,7 +277,6 @@ def cmd_play_file(bot, user, text, command, parameter, do_not_refresh_cache=Fals
def cmd_play_file_match(bot, user, text, command, parameter, do_not_refresh_cache=False): def cmd_play_file_match(bot, user, text, command, parameter, do_not_refresh_cache=False):
global log global log
music_folder = var.music_folder
if parameter: if parameter:
files = var.cache.files files = var.cache.files
msgs = [constants.strings('multiple_file_added') + "<ul>"] msgs = [constants.strings('multiple_file_added') + "<ul>"]
@ -331,7 +333,6 @@ def cmd_play_url(bot, user, text, command, parameter):
bot.send_msg(constants.strings('bad_parameter', command=command)) bot.send_msg(constants.strings('bad_parameter', command=command))
def cmd_play_playlist(bot, user, text, command, parameter): def cmd_play_playlist(bot, user, text, command, parameter):
global log global log
@ -480,9 +481,11 @@ def cmd_rb_play(bot, user, text, command, parameter):
msg += "No playable url found for this station, please try another station." msg += "No playable url found for this station, please try another station."
bot.send_msg(msg, text) bot.send_msg(msg, text)
yt_last_result = [] yt_last_result = []
yt_last_page = 0 # TODO: if we keep adding global variables, we need to consider sealing all commands up into classes. yt_last_page = 0 # TODO: if we keep adding global variables, we need to consider sealing all commands up into classes.
def cmd_yt_search(bot, user, text, command, parameter): def cmd_yt_search(bot, user, text, command, parameter):
global log, yt_last_result, yt_last_page, song_shortlist global log, yt_last_result, yt_last_page, song_shortlist
item_per_page = 5 item_per_page = 5
@ -516,6 +519,7 @@ def cmd_yt_search(bot, user, text, command, parameter):
else: else:
bot.send_msg(constants.strings('bad_parameter', command=command), text) bot.send_msg(constants.strings('bad_parameter', command=command), text)
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]):
@ -692,12 +696,10 @@ def cmd_remove(bot, user, text, command, parameter):
global log global log
# Allow to remove specific music into the queue with a number # Allow to remove specific music into the queue with a number
if parameter and parameter.isdigit() and int(parameter) > 0 \ if parameter and parameter.isdigit() and 0 < int(parameter) <= len(var.playlist):
and int(parameter) <= len(var.playlist):
index = int(parameter) - 1 index = int(parameter) - 1
removed = None
if index == var.playlist.current_index: if index == var.playlist.current_index:
removed = var.playlist.remove(index) removed = var.playlist.remove(index)
@ -757,7 +759,6 @@ def cmd_queue(bot, user, text, command, parameter):
else: else:
msgs = [constants.strings('queue_contents')] msgs = [constants.strings('queue_contents')]
for i, music in enumerate(var.playlist): for i, music in enumerate(var.playlist):
newline = ''
tags = '' tags = ''
if len(music.item().tags) > 0: if len(music.item().tags) > 0:
tags = "<sup>{}</sup>".format(", ".join(music.item().tags)) tags = "<sup>{}</sup>".format(", ".join(music.item().tags))
@ -772,12 +773,14 @@ def cmd_queue(bot, user, text, command, parameter):
send_multi_lines(bot, msgs, text) send_multi_lines(bot, msgs, text)
def cmd_random(bot, user, text, command, parameter): def cmd_random(bot, user, text, command, parameter):
global log global log
bot.interrupt() bot.interrupt()
var.playlist.randomize() var.playlist.randomize()
def cmd_repeat(bot, user, text, command, parameter): def cmd_repeat(bot, user, text, command, parameter):
global log global log
@ -795,13 +798,14 @@ def cmd_repeat(bot, user, text, command, parameter):
bot.send_msg(constants.strings("repeat", song=music.format_song_string(), n=str(repeat)), text) bot.send_msg(constants.strings("repeat", song=music.format_song_string(), n=str(repeat)), text)
def cmd_mode(bot, user, text, command, parameter): def cmd_mode(bot, user, text, command, parameter):
global log global log
if not parameter: if not parameter:
bot.send_msg(constants.strings("current_mode", mode=var.playlist.mode), text) bot.send_msg(constants.strings("current_mode", mode=var.playlist.mode), text)
return return
if not parameter in ["one-shot", "repeat", "random", "autoplay"]: if parameter not in ["one-shot", "repeat", "random", "autoplay"]:
bot.send_msg(constants.strings('unknown_mode', mode=parameter), text) bot.send_msg(constants.strings('unknown_mode', mode=parameter), text)
else: else:
var.db.set('playlist', 'playback_mode', parameter) var.db.set('playlist', 'playback_mode', parameter)
@ -813,6 +817,7 @@ def cmd_mode(bot, user, text, command, parameter):
bot.interrupt() bot.interrupt()
bot.launch_music() bot.launch_music()
def cmd_play_tags(bot, user, text, command, parameter): def cmd_play_tags(bot, user, text, command, parameter):
if not parameter: if not parameter:
bot.send_msg(constants.strings('bad_parameter', command=command), text) bot.send_msg(constants.strings('bad_parameter', command=command), text)
@ -829,7 +834,6 @@ def cmd_play_tags(bot, user, text, command, parameter):
log.info("cmd: add to playlist: " + music_wrapper.format_debug_string()) log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
msgs.append("<li><b>{}</b> (<i>{}</i>)</li>".format(music_wrapper.item().title, ", ".join(music_wrapper.item().tags))) msgs.append("<li><b>{}</b> (<i>{}</i>)</li>".format(music_wrapper.item().title, ", ".join(music_wrapper.item().tags)))
if count != 0: if count != 0:
msgs.append("</ul>") msgs.append("</ul>")
var.playlist.extend(music_wrappers) var.playlist.extend(music_wrappers)
@ -842,8 +846,6 @@ def cmd_add_tag(bot, user, text, command, parameter):
global log global log
params = parameter.split() params = parameter.split()
index = ""
tags = []
if len(params) == 2: if len(params) == 2:
index = params[0] index = params[0]
tags = list(map(lambda t: t.strip(), params[1].split(","))) tags = list(map(lambda t: t.strip(), params[1].split(",")))
@ -880,8 +882,6 @@ def cmd_remove_tag(bot, user, text, command, parameter):
params = parameter.split() params = parameter.split()
index = ""
tags = []
if len(params) == 2: if len(params) == 2:
index = params[0] index = params[0]
tags = list(map(lambda t: t.strip(), params[1].split(","))) tags = list(map(lambda t: t.strip(), params[1].split(",")))
@ -926,6 +926,7 @@ def cmd_remove_tag(bot, user, text, command, parameter):
bot.send_msg(constants.strings('bad_parameter', command=command), text) bot.send_msg(constants.strings('bad_parameter', command=command), text)
def cmd_find_tagged(bot, user, text, command, parameter): def cmd_find_tagged(bot, user, text, command, parameter):
global song_shortlist global song_shortlist
@ -954,6 +955,7 @@ def cmd_find_tagged(bot, user, text, command, parameter):
else: else:
bot.send_msg(constants.strings("no_file"), text) bot.send_msg(constants.strings("no_file"), text)
def cmd_search_library(bot, user, text, command, parameter): def cmd_search_library(bot, user, text, command, parameter):
global song_shortlist global song_shortlist
if not parameter: if not parameter:
@ -993,7 +995,6 @@ def cmd_search_library(bot, user, text, command, parameter):
def cmd_shortlist(bot, user, text, command, parameter): def cmd_shortlist(bot, user, text, command, parameter):
global song_shortlist, log global song_shortlist, log
indexes = []
try: try:
indexes = [int(i) for i in parameter.split(" ")] indexes = [int(i) for i in parameter.split(" ")]
except ValueError: except ValueError:
@ -1033,7 +1034,6 @@ def cmd_shortlist(bot, user, text, command, parameter):
def cmd_delete_from_library(bot, user, text, command, parameter): def cmd_delete_from_library(bot, user, text, command, parameter):
global song_shortlist, log global song_shortlist, log
indexes = []
try: try:
indexes = [int(i) for i in parameter.split(" ")] indexes = [int(i) for i in parameter.split(" ")]
except ValueError: except ValueError:
@ -1078,6 +1078,7 @@ def cmd_delete_from_library(bot, user, text, command, parameter):
bot.send_msg(constants.strings('bad_parameter', command=command), text) bot.send_msg(constants.strings('bad_parameter', command=command), text)
def cmd_drop_database(bot, user, text, command, parameter): def cmd_drop_database(bot, user, text, command, parameter):
global log global log
@ -1091,6 +1092,7 @@ def cmd_drop_database(bot, user, text, command, parameter):
else: else:
bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin')) bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
def cmd_refresh_cache(bot, user, text, command, parameter): def cmd_refresh_cache(bot, user, text, command, parameter):
global log global log
if bot.is_admin(user): if bot.is_admin(user):
@ -1100,13 +1102,16 @@ def cmd_refresh_cache(bot, user, text, command, parameter):
else: else:
bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin')) bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
# Just for debug use # Just for debug use
def cmd_real_time_rms(bot, user, text, command, parameter): def cmd_real_time_rms(bot, user, text, command, parameter):
bot._display_rms = not bot._display_rms bot._display_rms = not bot._display_rms
def cmd_loop_state(bot, user, text, command, parameter): def cmd_loop_state(bot, user, text, command, parameter):
print(bot._loop_status) print(bot._loop_status)
def cmd_item(bot, user, text, command, parameter): def cmd_item(bot, user, text, command, parameter):
print(bot.wait_for_downloading) print(bot.wait_for_downloading)
print(var.playlist.current_item().to_dict()) print(var.playlist.current_item().to_dict())

View File

@ -1,32 +1,32 @@
import variables as var import variables as var
def strings(option, *argv, **kwargs): def strings(option, *argv, **kwargs):
string = ""
try: try:
string = var.config.get("strings", option) string = var.config.get("strings", option)
except KeyError as e: except KeyError:
raise KeyError("Missed strings in configuration file: '{string}'. ".format(string=option) + raise KeyError("Missed strings in configuration file: '{string}'. ".format(string=option)
"Please restore you configuration file back to default if necessary.") + "Please restore you configuration file back to default if necessary.")
if argv or kwargs: if argv or kwargs:
try: try:
formatted = string.format(*argv, **kwargs) formatted = string.format(*argv, **kwargs)
return formatted return formatted
except KeyError as e: except KeyError as e:
raise KeyError( raise KeyError(
"Missed/Unexpected placeholder {{{placeholder}}} in string '{string}'. ".format(placeholder=str(e).strip("'"), string=option) + "Missed/Unexpected placeholder {{{placeholder}}} in string '{string}'. ".format(placeholder=str(e).strip("'"), string=option)
"Please restore you configuration file back to default if necessary.") + "Please restore you configuration file back to default if necessary.")
except TypeError as e: except TypeError:
raise KeyError( raise KeyError(
"Missed placeholder in string '{string}'. ".format(string=option) + "Missed placeholder in string '{string}'. ".format(string=option)
"Please restore you configuration file back to default if necessary.") + "Please restore you configuration file back to default if necessary.")
else: else:
return string return string
def commands(command): def commands(command):
string = ""
try: try:
string = var.config.get("commands", command) string = var.config.get("commands", command)
return string return string
except KeyError as e: except KeyError:
raise KeyError("Missed command in configuration file: '{string}'. ".format(string=command) + raise KeyError("Missed command in configuration file: '{string}'. ".format(string=command)
"Please restore you configuration file back to default if necessary.") + "Please restore you configuration file back to default if necessary.")

View File

@ -2,17 +2,19 @@ import sqlite3
import json import json
import datetime import datetime
class DatabaseError(Exception): class DatabaseError(Exception):
pass pass
class SettingsDatabase: class SettingsDatabase:
version = 1 version = 1
def __init__(self, db_path): def __init__(self, db_path):
self.db_path = db_path self.db_path = db_path
# connect # connect
conn = sqlite3.connect(self.db_path) conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
self.db_version_check_and_create() self.db_version_check_and_create()
@ -227,7 +229,6 @@ class MusicDatabase:
condition.append('LOWER(title) LIKE ?') condition.append('LOWER(title) LIKE ?')
filler.append("%{:s}%".format(keyword.lower())) filler.append("%{:s}%".format(keyword.lower()))
condition_str = " AND ".join(condition) condition_str = " AND ".join(condition)
conn = sqlite3.connect(self.db_path) conn = sqlite3.connect(self.db_path)
@ -246,7 +247,6 @@ class MusicDatabase:
condition.append('LOWER(tags) LIKE ?') condition.append('LOWER(tags) LIKE ?')
filler.append("%{:s},%".format(tag.lower())) filler.append("%{:s},%".format(tag.lower()))
condition_str = " AND ".join(condition) condition_str = " AND ".join(condition)
conn = sqlite3.connect(self.db_path) conn = sqlite3.connect(self.db_path)
@ -284,7 +284,6 @@ class MusicDatabase:
return self._result_to_dict(results) return self._result_to_dict(results)
def _result_to_dict(self, results): def _result_to_dict(self, results):
if len(results) > 0: if len(results) > 0:
music_dicts = [] music_dicts = []
@ -324,7 +323,6 @@ class MusicDatabase:
conn.commit() conn.commit()
conn.close() conn.close()
def drop_table(self): def drop_table(self):
conn = sqlite3.connect(self.db_path) conn = sqlite3.connect(self.db_path)
cursor = conn.cursor() cursor = conn.cursor()

View File

@ -16,7 +16,7 @@ import time
class ReverseProxied(object): class ReverseProxied(object):
'''Wrap the application in this middleware and configure the """Wrap the application in this middleware and configure the
front-end server to add these headers, to let you quietly bind front-end server to add these headers, to let you quietly bind
this to a URL other than / and to an HTTP scheme that is this to a URL other than / and to an HTTP scheme that is
different than what is used locally. different than what is used locally.
@ -31,7 +31,7 @@ class ReverseProxied(object):
} }
:param app: the WSGI application :param app: the WSGI application
''' """
def __init__(self, app): def __init__(self, app):
self.app = app self.app = app
@ -57,6 +57,7 @@ web = Flask(__name__)
log = logging.getLogger("bot") log = logging.getLogger("bot")
user = 'Remote Control' user = 'Remote Control'
def init_proxy(): def init_proxy():
global web global web
if var.is_proxified: if var.is_proxified:
@ -64,20 +65,22 @@ def init_proxy():
# https://stackoverflow.com/questions/29725217/password-protect-one-webpage-in-flask-app # https://stackoverflow.com/questions/29725217/password-protect-one-webpage-in-flask-app
def check_auth(username, password): def check_auth(username, password):
"""This function is called to check if a username / """This function is called to check if a username /
password combination is valid. password combination is valid.
""" """
return username == var.config.get("webinterface", "user") and password == var.config.get("webinterface", "password") return username == var.config.get("webinterface", "user") and password == var.config.get("webinterface", "password")
def authenticate(): def authenticate():
"""Sends a 401 response that enables basic auth""" """Sends a 401 response that enables basic auth"""
global log global log
return Response( return Response('Could not verify your access level for that URL.\n'
'Could not verify your access level for that URL.\n'
'You have to login with proper credentials', 401, 'You have to login with proper credentials', 401,
{'WWW-Authenticate': 'Basic realm="Login Required"'}) {'WWW-Authenticate': 'Basic realm="Login Required"'})
def requires_auth(f): def requires_auth(f):
@wraps(f) @wraps(f)
def decorated(*args, **kwargs): def decorated(*args, **kwargs):
@ -90,6 +93,7 @@ def requires_auth(f):
return f(*args, **kwargs) return f(*args, **kwargs)
return decorated return decorated
def tag_color(tag): def tag_color(tag):
num = hash(tag) % 8 num = hash(tag) % 8
if num == 0: if num == 0:
@ -109,14 +113,15 @@ def tag_color(tag):
elif num == 7: elif num == 7:
return "dark" return "dark"
def build_tags_color_lookup(): def build_tags_color_lookup():
color_lookup = {} color_lookup = {}
for tag in var.music_db.query_all_tags(): for tag in var.music_db.query_all_tags():
color_lookup[tag] = tag_color(tag) color_lookup[tag] = tag_color(tag)
return color_lookup return color_lookup
def build_path_tags_lookup(): def build_path_tags_lookup():
path_tags_lookup = {} path_tags_lookup = {}
ids = list(var.cache.file_id_lookup.values()) ids = list(var.cache.file_id_lookup.values())
@ -128,11 +133,13 @@ def build_path_tags_lookup():
return path_tags_lookup return path_tags_lookup
def recur_dir(dirobj): def recur_dir(dirobj):
for name, dir in dirobj.get_subdirs().items(): for name, dir in dirobj.get_subdirs().items():
print(dirobj.fullpath + "/" + name) print(dirobj.fullpath + "/" + name)
recur_dir(dir) recur_dir(dir)
@web.route("/", methods=['GET']) @web.route("/", methods=['GET'])
@requires_auth @requires_auth
def index(): def index():
@ -153,6 +160,7 @@ def index():
paused=var.bot.is_pause, paused=var.bot.is_pause,
) )
@web.route("/playlist", methods=['GET']) @web.route("/playlist", methods=['GET'])
@requires_auth @requires_auth
def playlist(): def playlist():
@ -177,6 +185,7 @@ def playlist():
return jsonify({'items': items}) return jsonify({'items': items})
def status(): def status():
if len(var.playlist) > 0: if len(var.playlist) > 0:
return jsonify({'ver': var.playlist.version, return jsonify({'ver': var.playlist.version,
@ -231,15 +240,13 @@ def post():
music_wrappers = list(map( music_wrappers = list(map(
lambda file: lambda file:
get_cached_wrapper_by_id(var.bot, var.cache.file_id_lookup[folder + file], user), get_cached_wrapper_by_id(var.bot, var.cache.file_id_lookup[folder + file], user), files))
files))
var.playlist.extend(music_wrappers) var.playlist.extend(music_wrappers)
for music_wrapper in music_wrappers: for music_wrapper in music_wrappers:
log.info('web: add to playlist: ' + music_wrapper.format_debug_string()) log.info('web: add to playlist: ' + music_wrapper.format_debug_string())
elif 'add_url' in request.form: elif 'add_url' in request.form:
music_wrapper = get_cached_wrapper_from_scrap(var.bot, type='url', url=request.form['add_url'], user=user) music_wrapper = get_cached_wrapper_from_scrap(var.bot, type='url', url=request.form['add_url'], user=user)
var.playlist.append(music_wrapper) var.playlist.append(music_wrapper)
@ -279,7 +286,6 @@ def post():
else: else:
var.playlist.remove(index) var.playlist.remove(index)
elif 'play_music' in request.form: elif 'play_music' in request.form:
music_wrapper = var.playlist[int(request.form['play_music'])] music_wrapper = var.playlist[int(request.form['play_music'])]
log.info("web: jump to: " + music_wrapper.format_debug_string()) log.info("web: jump to: " + music_wrapper.format_debug_string())
@ -358,6 +364,7 @@ def post():
return status() return status()
@web.route('/upload', methods=["POST"]) @web.route('/upload', methods=["POST"])
def upload(): def upload():
global log global log

View File

@ -2,6 +2,7 @@ from librb.rbRadios import RadioBrowser
rb = RadioBrowser() rb = RadioBrowser()
def getstations_byname(query): def getstations_byname(query):
results = rb.stations_byname(query) results = rb.stations_byname(query)
stations = [] stations = []
@ -13,16 +14,19 @@ def getstations_byname(query):
pass pass
return stations return stations
def geturl_byid(id): def geturl_byid(id):
url = rb.playable_station(id)['url'] url = rb.playable_station(id)['url']
if url != None: if url is not None:
return url return url
else: else:
return "-1" return "-1"
def getstationname_byid(id): def getstationname_byid(id):
return rb.stations_byid(id) return rb.stations_byid(id)
if __name__ == "__main__": if __name__ == "__main__":
r = getstations_byname('r.sh') r = getstations_byname('r.sh')
stationinfo = getstationname_byid(96748) stationinfo = getstationname_byid(96748)

View File

@ -1,8 +1,5 @@
import requests import requests
from xml.etree import ElementTree
from urllib.parse import urljoin
from librb.rbConstants import endpoints, BASE_URL from librb.rbConstants import endpoints, BASE_URL
@ -180,4 +177,3 @@ class RadioBrowser:
kwargs["params"] = params kwargs["params"] = params
endpoint = self.builder.produce_endpoint(endpoint="station_search") endpoint = self.builder.produce_endpoint(endpoint="station_search")
return request(endpoint, **kwargs) return request(endpoint, **kwargs)

View File

@ -1,11 +1,10 @@
import logging import logging
import os import os
from database import MusicDatabase
import json import json
import threading import threading
from media.item import item_builders, item_loaders, item_id_generators, dict_to_item, dicts_to_items from media.item import item_builders, item_id_generators, dict_to_item
from database import MusicDatabase from database import MusicDatabase
import variables as var import variables as var
import util import util
@ -18,6 +17,7 @@ class MusicCache(dict):
self.log = logging.getLogger("bot") self.log = logging.getLogger("bot")
self.dir = None self.dir = None
self.files = [] self.files = []
self.file_id_lookup = {}
self.dir_lock = threading.Lock() self.dir_lock = threading.Lock()
def get_item_by_id(self, bot, id): # Why all these functions need a bot? Because it need the bot to send message! def get_item_by_id(self, bot, id): # Why all these functions need a bot? Because it need the bot to send message!
@ -35,7 +35,6 @@ class MusicCache(dict):
# print(id) # print(id)
# raise KeyError("Unable to fetch item from the database! Please try to refresh the cache by !recache.") # raise KeyError("Unable to fetch item from the database! Please try to refresh the cache by !recache.")
def get_item(self, bot, **kwargs): def get_item(self, bot, **kwargs):
# kwargs should provide type and id, and parameters to build the item if not existed in the library. # kwargs should provide type and id, and parameters to build the item if not existed in the library.
# if cached # if cached
@ -112,7 +111,6 @@ 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")
self.files = [] self.files = []
self.file_id_lookup = {}
files = util.get_recursive_file_list_sorted(var.music_folder) files = util.get_recursive_file_list_sorted(var.music_folder)
self.dir = util.Dir(var.music_folder) self.dir = util.Dir(var.music_folder)
for file in files: for file in files:

View File

@ -1,4 +1,3 @@
import logging
import os import os
import re import re
from io import BytesIO from io import BytesIO
@ -6,9 +5,7 @@ import base64
import hashlib import hashlib
import mutagen import mutagen
from PIL import Image from PIL import Image
import json
import util
import variables as var import variables as var
from media.item import BaseItem, item_builders, item_loaders, item_id_generators from media.item import BaseItem, item_builders, item_loaders, item_id_generators
import constants import constants
@ -24,15 +21,19 @@ type : file
user user
''' '''
def file_item_builder(bot, **kwargs): def file_item_builder(bot, **kwargs):
return FileItem(bot, kwargs['path']) return FileItem(bot, kwargs['path'])
def file_item_loader(bot, _dict): def file_item_loader(bot, _dict):
return FileItem(bot, "", _dict) return FileItem(bot, "", _dict)
def file_item_id_generator(**kwargs): def file_item_id_generator(**kwargs):
return hashlib.md5(kwargs['path'].encode()).hexdigest() return hashlib.md5(kwargs['path'].encode()).hexdigest()
item_builders['file'] = file_item_builder item_builders['file'] = file_item_builder
item_loaders['file'] = file_item_loader item_loaders['file'] = file_item_loader
item_id_generators['file'] = file_item_id_generator item_id_generators['file'] = file_item_id_generator
@ -79,7 +80,7 @@ class FileItem(BaseItem):
return True return True
def _get_info_from_tag(self): def _get_info_from_tag(self):
match = re.search("(.+)\.(.+)", self.uri()) match = re.search(r"(.+)\.(.+)", self.uri())
assert match is not None assert match is not None
file_no_ext = match[1] file_no_ext = match[1]

View File

@ -1,33 +1,27 @@
import logging import logging
import threading
import os
import re
from io import BytesIO
import base64
import hashlib
import mutagen
from PIL import Image
import util
import variables as var
item_builders = {} item_builders = {}
item_loaders = {} item_loaders = {}
item_id_generators = {} item_id_generators = {}
def example_builder(bot, **kwargs): def example_builder(bot, **kwargs):
return BaseItem(bot) return BaseItem(bot)
def example_loader(bot, _dict): def example_loader(bot, _dict):
return BaseItem(bot, from_dict=_dict) return BaseItem(bot, from_dict=_dict)
def example_id_generator(**kwargs): def example_id_generator(**kwargs):
return "" return ""
item_builders['base'] = example_builder item_builders['base'] = example_builder
item_loaders['base'] = example_loader item_loaders['base'] = example_loader
item_id_generators['base'] = example_id_generator item_id_generators['base'] = example_id_generator
def dicts_to_items(bot, music_dicts): def dicts_to_items(bot, music_dicts):
items = [] items = []
for music_dict in music_dicts: for music_dict in music_dicts:
@ -35,6 +29,7 @@ def dicts_to_items(bot, music_dicts):
items.append(item_loaders[type](bot, music_dict)) items.append(item_loaders[type](bot, music_dict))
return items return items
def dict_to_item(bot, music_dict): def dict_to_item(bot, music_dict):
type = music_dict['type'] type = music_dict['type']
return item_loaders[type](bot, music_dict) return item_loaders[type](bot, music_dict)
@ -111,5 +106,3 @@ class BaseItem:
def to_dict(self): def to_dict(self):
return {"type": self.type, "id": self.id, "ready": self.ready, "path": self.path, "tags": self.tags} return {"type": self.type, "id": self.id, "ready": self.ready, "path": self.path, "tags": self.tags}

View File

@ -359,4 +359,3 @@ class AutoPlaylist(OneshotPlaylist):
if len(self) == 0: if len(self) == 0:
self.refresh() self.refresh()
return super().next() return super().next()

View File

@ -11,16 +11,16 @@ import constants
log = logging.getLogger("bot") log = logging.getLogger("bot")
def get_radio_server_description(url): def get_radio_server_description(url):
global log global log
log.debug("radio: fetching radio server description") log.debug("radio: fetching radio server description")
p = re.compile('(https?\:\/\/[^\/]*)', re.IGNORECASE) p = re.compile('(https?://[^/]*)', re.IGNORECASE)
res = re.search(p, url) res = re.search(p, url)
base_url = res.group(1) base_url = res.group(1)
url_icecast = base_url + '/status-json.xsl' url_icecast = base_url + '/status-json.xsl'
url_shoutcast = base_url + '/stats?json=1' url_shoutcast = base_url + '/stats?json=1'
title_server = None
try: try:
r = requests.get(url_shoutcast, timeout=10) r = requests.get(url_shoutcast, timeout=10)
data = r.json() data = r.json()
@ -30,7 +30,7 @@ def get_radio_server_description(url):
except (requests.exceptions.ConnectionError, except (requests.exceptions.ConnectionError,
requests.exceptions.HTTPError, requests.exceptions.HTTPError,
requests.exceptions.ReadTimeout, requests.exceptions.ReadTimeout,
requests.exceptions.Timeout) as e: requests.exceptions.Timeout):
error_traceback = traceback.format_exc() error_traceback = traceback.format_exc()
error = error_traceback.rstrip().split("\n")[-1] error = error_traceback.rstrip().split("\n")[-1]
log.debug("radio: unsuccessful attempts on fetching radio description (shoutcast): " + error) log.debug("radio: unsuccessful attempts on fetching radio description (shoutcast): " + error)
@ -51,7 +51,7 @@ def get_radio_server_description(url):
except (requests.exceptions.ConnectionError, except (requests.exceptions.ConnectionError,
requests.exceptions.HTTPError, requests.exceptions.HTTPError,
requests.exceptions.ReadTimeout, requests.exceptions.ReadTimeout,
requests.exceptions.Timeout) as e: requests.exceptions.Timeout):
error_traceback = traceback.format_exc() error_traceback = traceback.format_exc()
error = error_traceback.rstrip().split("\n")[-1] error = error_traceback.rstrip().split("\n")[-1]
log.debug("radio: unsuccessful attempts on fetching radio description (icecast): " + error) log.debug("radio: unsuccessful attempts on fetching radio description (icecast): " + error)
@ -81,7 +81,7 @@ def get_radio_title(url):
requests.exceptions.HTTPError, requests.exceptions.HTTPError,
requests.exceptions.ReadTimeout, requests.exceptions.ReadTimeout,
requests.exceptions.Timeout, requests.exceptions.Timeout,
KeyError) as e: KeyError):
log.debug("radio: unsuccessful attempts on fetching radio title (icy)") log.debug("radio: unsuccessful attempts on fetching radio title (icy)")
return url return url
@ -92,12 +92,15 @@ def radio_item_builder(bot, **kwargs):
else: else:
return RadioItem(bot, kwargs['url'], '') return RadioItem(bot, kwargs['url'], '')
def radio_item_loader(bot, _dict): def radio_item_loader(bot, _dict):
return RadioItem(bot, "", "", _dict) return RadioItem(bot, "", "", _dict)
def radio_item_id_generator(**kwargs): def radio_item_id_generator(**kwargs):
return hashlib.md5(kwargs['url'].encode()).hexdigest() return hashlib.md5(kwargs['url'].encode()).hexdigest()
item_builders['radio'] = radio_item_builder item_builders['radio'] = radio_item_builder
item_loaders['radio'] = radio_item_loader item_loaders['radio'] = radio_item_loader
item_id_generators['radio'] = radio_item_id_generator item_id_generators['radio'] = radio_item_id_generator
@ -159,6 +162,3 @@ class RadioItem(BaseItem):
def display_type(self): def display_type(self):
return constants.strings("radio") return constants.strings("radio")

View File

@ -17,15 +17,19 @@ import media.system
log = logging.getLogger("bot") log = logging.getLogger("bot")
def url_item_builder(bot, **kwargs): def url_item_builder(bot, **kwargs):
return URLItem(bot, kwargs['url']) return URLItem(bot, kwargs['url'])
def url_item_loader(bot, _dict): def url_item_loader(bot, _dict):
return URLItem(bot, "", _dict) return URLItem(bot, "", _dict)
def url_item_id_generator(**kwargs): def url_item_id_generator(**kwargs):
return hashlib.md5(kwargs['url'].encode()).hexdigest() return hashlib.md5(kwargs['url'].encode()).hexdigest()
item_builders['url'] = url_item_builder item_builders['url'] = url_item_builder
item_loaders['url'] = url_item_loader item_loaders['url'] = url_item_loader
item_id_generators['url'] = url_item_id_generator item_id_generators['url'] = url_item_id_generator
@ -145,8 +149,6 @@ class URLItem(BaseItem):
self.ready = "preparing" self.ready = "preparing"
self.log.info("bot: downloading url (%s) %s " % (self.title, self.url)) self.log.info("bot: downloading url (%s) %s " % (self.title, self.url))
ydl_opts = ""
ydl_opts = { ydl_opts = {
'format': 'bestaudio/best', 'format': 'bestaudio/best',
'outtmpl': save_path, 'outtmpl': save_path,
@ -166,7 +168,7 @@ class URLItem(BaseItem):
for i in range(attempts): for i in range(attempts):
self.log.info("bot: download attempts %d / %d" % (i+1, attempts)) self.log.info("bot: download attempts %d / %d" % (i+1, attempts))
try: try:
info = ydl.extract_info(self.url) ydl.extract_info(self.url)
download_succeed = True download_succeed = True
break break
except: except:
@ -214,7 +216,6 @@ class URLItem(BaseItem):
return dict return dict
def format_debug_string(self): def format_debug_string(self):
return "[url] {title} ({url})".format( return "[url] {title} ({url})".format(
title=self.title, title=self.title,

View File

@ -1,11 +1,10 @@
import youtube_dl import youtube_dl
import constants import constants
import media
import variables as var import variables as var
import hashlib
from media.item import item_builders, item_loaders, item_id_generators from media.item import item_builders, item_loaders, item_id_generators
from media.url import URLItem, url_item_id_generator from media.url import URLItem, url_item_id_generator
def get_playlist_info(url, start_index=0, user=""): def get_playlist_info(url, start_index=0, user=""):
items = [] items = []
ydl_opts = { ydl_opts = {
@ -63,6 +62,7 @@ def playlist_url_item_builder(bot, **kwargs):
def playlist_url_item_loader(bot, _dict): def playlist_url_item_loader(bot, _dict):
return PlaylistURLItem(bot, "", "", "", "", _dict) return PlaylistURLItem(bot, "", "", "", "", _dict)
item_builders['url_from_playlist'] = playlist_url_item_builder item_builders['url_from_playlist'] = playlist_url_item_builder
item_loaders['url_from_playlist'] = playlist_url_item_loader item_loaders['url_from_playlist'] = playlist_url_item_loader
item_id_generators['url_from_playlist'] = url_item_id_generator item_id_generators['url_from_playlist'] = url_item_id_generator

View File

@ -14,8 +14,6 @@ import os
import os.path import os.path
import pymumble.pymumble_py3 as pymumble import pymumble.pymumble_py3 as pymumble
import variables as var import variables as var
import hashlib
import youtube_dl
import logging import logging
import logging.handlers import logging.handlers
import traceback import traceback
@ -25,9 +23,6 @@ import util
import command import command
import constants import constants
from database import SettingsDatabase, MusicDatabase from database import SettingsDatabase, MusicDatabase
import media.url
import media.file
import media.radio
import media.system import media.system
from media.playlist import BasePlaylist from media.playlist import BasePlaylist
from media.cache import MusicCache from media.cache import MusicCache
@ -151,7 +146,7 @@ class MumbleBot:
self.ducking_volume = var.db.getfloat("bot", "ducking_volume", fallback=self.ducking_volume) self.ducking_volume = var.db.getfloat("bot", "ducking_volume", fallback=self.ducking_volume)
self.ducking_threshold = var.config.getfloat("bot", "ducking_threshold", fallback=5000) self.ducking_threshold = var.config.getfloat("bot", "ducking_threshold", fallback=5000)
self.ducking_threshold = var.db.getfloat("bot", "ducking_threshold", fallback=self.ducking_threshold) self.ducking_threshold = var.db.getfloat("bot", "ducking_threshold", fallback=self.ducking_threshold)
self.mumble.callbacks.set_callback(pymumble.constants.PYMUMBLE_CLBK_SOUNDRECEIVED, \ self.mumble.callbacks.set_callback(pymumble.constants.PYMUMBLE_CLBK_SOUNDRECEIVED,
self.ducking_sound_received) self.ducking_sound_received)
self.mumble.set_receive_sound(True) self.mumble.set_receive_sound(True)
@ -247,7 +242,6 @@ class MumbleBot:
constants.strings('url_ban')) constants.strings('url_ban'))
return return
command_exc = "" command_exc = ""
try: try:
if command in self.cmd_handle: if command in self.cmd_handle:
@ -319,7 +313,7 @@ class MumbleBot:
def launch_music(self): def launch_music(self):
if var.playlist.is_empty(): if var.playlist.is_empty():
return return
assert self.wait_for_downloading == False assert self.wait_for_downloading is False
music_wrapper = var.playlist.current_item() music_wrapper = var.playlist.current_item()
uri = music_wrapper.uri() uri = music_wrapper.uri()
@ -367,7 +361,6 @@ class MumbleBot:
var.playlist.remove_by_id(next.id) var.playlist.remove_by_id(next.id)
var.cache.free_and_delete(next.id) var.cache.free_and_delete(next.id)
# ======================= # =======================
# Loop # Loop
# ======================= # =======================
@ -490,7 +483,7 @@ class MumbleBot:
if rms < self.ducking_threshold: if rms < self.ducking_threshold:
print('%6d/%6d ' % (rms, self._max_rms) + '-'*int(rms/200), end='\r') print('%6d/%6d ' % (rms, self._max_rms) + '-'*int(rms/200), end='\r')
else: else:
print('%6d/%6d ' % (rms, self._max_rms) + '-'*int(self.ducking_threshold/200) \ print('%6d/%6d ' % (rms, self._max_rms) + '-'*int(self.ducking_threshold/200)
+ '+'*int((rms - self.ducking_threshold)/200), end='\r') + '+'*int((rms - self.ducking_threshold)/200), end='\r')
if rms > self.ducking_threshold: if rms > self.ducking_threshold:
@ -558,7 +551,6 @@ class MumbleBot:
command = ("ffmpeg", '-v', ffmpeg_debug, '-nostdin', '-ss', "%f" % self.playhead, '-i', command = ("ffmpeg", '-v', ffmpeg_debug, '-nostdin', '-ss', "%f" % self.playhead, '-i',
uri, '-ac', '1', '-f', 's16le', '-ar', '48000', '-') uri, '-ac', '1', '-f', 's16le', '-ar', '48000', '-')
if var.config.getboolean('bot', 'announce_current_music'): if var.config.getboolean('bot', 'announce_current_music'):
self.send_msg(var.playlist.current_item().format_current_playing()) self.send_msg(var.playlist.current_item().format_current_playing())
@ -572,7 +564,6 @@ class MumbleBot:
self.last_volume_cycle_time = time.time() self.last_volume_cycle_time = time.time()
self.pause_at_id = "" self.pause_at_id = ""
# TODO: this is a temporary workaround for issue #44 of pymumble. # TODO: this is a temporary workaround for issue #44 of pymumble.
def _clear_pymumble_soundqueue(self): def _clear_pymumble_soundqueue(self):
for id, user in self.mumble.users.items(): for id, user in self.mumble.users.items():
@ -582,7 +573,6 @@ class MumbleBot:
self.log.debug("bot: pymumble soundqueue cleared.") self.log.debug("bot: pymumble soundqueue cleared.")
def start_web_interface(addr, port): def start_web_interface(addr, port):
global formatter global formatter
import interface import interface
@ -590,7 +580,6 @@ def start_web_interface(addr, port):
# setup logger # setup logger
werkzeug_logger = logging.getLogger('werkzeug') werkzeug_logger = logging.getLogger('werkzeug')
logfile = util.solve_filepath(var.config.get('webinterface', 'web_logfile')) logfile = util.solve_filepath(var.config.get('webinterface', 'web_logfile'))
handler = None
if logfile: if logfile:
handler = logging.handlers.RotatingFileHandler(logfile, mode='a', maxBytes=10240) # Rotate after 10KB handler = logging.handlers.RotatingFileHandler(logfile, mode='a', maxBytes=10240) # Rotate after 10KB
else: else:

17
util.py
View File

@ -6,23 +6,16 @@ import magic
import os import os
import sys import sys
import variables as var import variables as var
import constants
import zipfile import zipfile
import requests import requests
import mutagen
import re import re
import subprocess as sp import subprocess as sp
import logging import logging
import youtube_dl import youtube_dl
from importlib import reload from importlib import reload
from PIL import Image
from io import BytesIO
from sys import platform from sys import platform
import traceback import traceback
import urllib.parse, urllib.request, urllib.error import urllib.request
import base64
import media
import media.radio
from packaging import version from packaging import version
log = logging.getLogger("bot") log = logging.getLogger("bot")
@ -65,6 +58,7 @@ def get_recursive_file_list_sorted(path):
filelist.sort() filelist.sort()
return filelist return filelist
# - zips all files of the given zippath (must be a directory) # - zips all files of the given zippath (must be a directory)
# - returns the absolute path of the created zip file # - returns the absolute path of the created zip file
# - zip file will be in the applications tmp folder (according to configuration) # - zip file will be in the applications tmp folder (according to configuration)
@ -172,7 +166,7 @@ def url_unban(url):
def pipe_no_wait(pipefd): def pipe_no_wait(pipefd):
''' Used to fetch the STDERR of ffmpeg. pipefd is the file descriptor returned from os.pipe()''' """ Used to fetch the STDERR of ffmpeg. pipefd is the file descriptor returned from os.pipe()"""
if platform == "linux" or platform == "linux2" or platform == "darwin": if platform == "linux" or platform == "linux2" or platform == "darwin":
import fcntl import fcntl
import os import os
@ -309,18 +303,19 @@ def get_url_from_input(string):
else: else:
return False return False
def youtube_search(query): def youtube_search(query):
global log global log
try: try:
r = requests.get("https://www.youtube.com/results", params={'search_query': query}, timeout=5) r = requests.get("https://www.youtube.com/results", params={'search_query': query}, timeout=5)
results = re.findall("watch\?v=(.*?)\".*?title=\"(.*?)\".*?" results = re.findall(r"watch\?v=(.*?)\".*?title=\"(.*?)\".*?"
"(?:user|channel).*?>(.*?)<", r.text) # (id, title, uploader) "(?:user|channel).*?>(.*?)<", r.text) # (id, title, uploader)
if len(results) > 0: if len(results) > 0:
return results return results
except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError, requests.exceptions.Timeout) as e: except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError, requests.exceptions.Timeout):
error_traceback = traceback.format_exc().split("During")[0] error_traceback = traceback.format_exc().split("During")[0]
log.error("util: youtube query failed with error:\n %s" % error_traceback) log.error("util: youtube query failed with error:\n %s" % error_traceback)
return False return False