changed indent style

This commit is contained in:
Manuel Cortez 2021-09-22 15:32:52 -05:00
parent de28e80327
commit ab264d4100
39 changed files with 1871 additions and 1872 deletions

View File

@ -13,4 +13,4 @@ bts_access_token = "fe3j2ijirvevv9"
bts_url = "https://issues.manuelcortez.net" bts_url = "https://issues.manuelcortez.net"
update_url = "https://files.manuelcortez.net/music_dl/update/latest.json" update_url = "https://files.manuelcortez.net/music_dl/update/latest.json"
version = "2020.07.23" version = "2020.07.23"
update_next_version = "14775226" update_next_version = "14775226"

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import sys import sys
if sys.version[0] == "3": if sys.version[0] == "3":
raise ImportError() raise ImportError()
import os import os
import paths import paths
def get(): def get():
return os.path.join(paths.app_path(), "cacerts.txt") return os.path.join(paths.app_path(), "cacerts.txt")

View File

@ -12,7 +12,6 @@ MAINSPEC = "app-configuration.defaults"
app = None app = None
def setup (): def setup ():
global app global app
log.debug("Loading global app settings...") log.debug("Loading global app settings...")
app = config_utils.load_config(os.path.join(storage.data_directory, MAINFILE), os.path.join(paths.app_path(), MAINSPEC)) app = config_utils.load_config(os.path.join(storage.data_directory, MAINFILE), os.path.join(paths.app_path(), MAINSPEC))

View File

@ -8,66 +8,66 @@ class ConfigLoadError(Exception): pass
def load_config(config_path, configspec_path=None, *args, **kwargs): def load_config(config_path, configspec_path=None, *args, **kwargs):
# if os.path.exists(config_path): # if os.path.exists(config_path):
# clean_config(config_path) # clean_config(config_path)
spec = ConfigObj(configspec_path, encoding='UTF8', list_values=False, _inspec=True) spec = ConfigObj(configspec_path, encoding='UTF8', list_values=False, _inspec=True)
try: try:
config = ConfigObj(infile=config_path, configspec=spec, create_empty=True, encoding='UTF8', *args, **kwargs) config = ConfigObj(infile=config_path, configspec=spec, create_empty=True, encoding='UTF8', *args, **kwargs)
except ParseError: except ParseError:
raise ConfigLoadError("Unable to load %r" % config_path) raise ConfigLoadError("Unable to load %r" % config_path)
validator = Validator() validator = Validator()
validated = config.validate(validator, copy=True) validated = config.validate(validator, copy=True)
if validated == True: if validated == True:
config.write() config.write()
return config return config
def is_blank(arg): def is_blank(arg):
"Check if a line is blank." "Check if a line is blank."
for c in arg: for c in arg:
if c not in string.whitespace: if c not in string.whitespace:
return False return False
return True return True
def get_keys(path): def get_keys(path):
"Gets the keys of a configobj config file." "Gets the keys of a configobj config file."
res=[] res=[]
fin=open(path) fin=open(path)
for line in fin: for line in fin:
if not is_blank(line): if not is_blank(line):
res.append(line[0:line.find('=')].strip()) res.append(line[0:line.find('=')].strip())
fin.close() fin.close()
return res return res
def hist(keys): def hist(keys):
"Generates a histogram of an iterable." "Generates a histogram of an iterable."
res={} res={}
for k in keys: for k in keys:
res[k]=res.setdefault(k,0)+1 res[k]=res.setdefault(k,0)+1
return res return res
def find_problems(hist): def find_problems(hist):
"Takes a histogram and returns a list of items occurring more than once." "Takes a histogram and returns a list of items occurring more than once."
res=[] res=[]
for k,v in hist.items(): for k,v in hist.items():
if v>1: if v>1:
res.append(k) res.append(k)
return res return res
def clean_config(path): def clean_config(path):
"Cleans a config file. If duplicate values are found, delete all of them and just use the default." "Cleans a config file. If duplicate values are found, delete all of them and just use the default."
orig=[] orig=[]
cleaned=[] cleaned=[]
fin=open(path) fin=open(path)
for line in fin: for line in fin:
orig.append(line) orig.append(line)
fin.close() fin.close()
for p in find_problems(hist(get_keys(path))): for p in find_problems(hist(get_keys(path))):
for o in orig: for o in orig:
o.strip() o.strip()
if p not in o: if p not in o:
cleaned.append(o) cleaned.append(o)
if len(cleaned) != 0: if len(cleaned) != 0:
cam=open(path,'w') cam=open(path,'w')
for c in cleaned: for c in cleaned:
cam.write(c) cam.write(c)
cam.close() cam.close()
return True return True
else: else:
return False return False

View File

@ -6,47 +6,47 @@ from . import player
class configuration(object): class configuration(object):
def __init__(self): def __init__(self):
self.view = configurationDialog(_("Settings")) self.view = configurationDialog(_("Settings"))
self.create_config() self.create_config()
self.view.get_response() self.view.get_response()
self.save() self.save()
def create_config(self): def create_config(self):
self.output_devices = player.player.get_output_devices() self.output_devices = player.player.get_output_devices()
self.view.create_general(output_devices=[i["name"] for i in self.output_devices]) self.view.create_general(output_devices=[i["name"] for i in self.output_devices])
current_output_device = config.app["main"]["output_device"] current_output_device = config.app["main"]["output_device"]
for i in self.output_devices: for i in self.output_devices:
# here we must compare against the str version of the vlc's device identifier. # here we must compare against the str version of the vlc's device identifier.
if str(i["id"]) == current_output_device: if str(i["id"]) == current_output_device:
self.view.set_value("general", "output_device", i["name"]) self.view.set_value("general", "output_device", i["name"])
break break
self.view.realize() self.view.realize()
extractors = get_services(import_all=True) extractors = get_services(import_all=True)
for i in extractors: for i in extractors:
if hasattr(i, "settings"): if hasattr(i, "settings"):
panel = getattr(i, "settings")(self.view.notebook) panel = getattr(i, "settings")(self.view.notebook)
self.view.notebook.InsertSubPage(1, panel, panel.name) self.view.notebook.InsertSubPage(1, panel, panel.name)
panel.load() panel.load()
if hasattr(panel, "on_enabled"): if hasattr(panel, "on_enabled"):
panel.on_enabled() panel.on_enabled()
def save(self): def save(self):
selected_output_device = self.view.get_value("general", "output_device") selected_output_device = self.view.get_value("general", "output_device")
selected_device_id = None selected_device_id = None
for i in self.output_devices: for i in self.output_devices:
# Vlc returns everything as bytes object whereas WX works with string objects, so I need to convert the wx returned string to bytes before # Vlc returns everything as bytes object whereas WX works with string objects, so I need to convert the wx returned string to bytes before
# Otherwise the comparison will be false. # Otherwise the comparison will be false.
# toDo: Check if utf-8 would be enough or we'd have to use the fylesystem encode for handling this. # toDo: Check if utf-8 would be enough or we'd have to use the fylesystem encode for handling this.
if i["name"] == bytes(selected_output_device, "utf-8"): if i["name"] == bytes(selected_output_device, "utf-8"):
selected_device_id = i["id"] selected_device_id = i["id"]
break break
if config.app["main"]["output_device"] != selected_device_id: if config.app["main"]["output_device"] != selected_device_id:
config.app["main"]["output_device"] = selected_device_id config.app["main"]["output_device"] = selected_device_id
player.player.set_output_device(config.app["main"]["output_device"]) player.player.set_output_device(config.app["main"]["output_device"])
for i in range(0, self.view.notebook.GetPageCount()): for i in range(0, self.view.notebook.GetPageCount()):
page = self.view.notebook.GetPage(i) page = self.view.notebook.GetPage(i)
if hasattr(page, "save"): if hasattr(page, "save"):
page.save() page.save()
config.app.write() config.app.write()

View File

@ -20,252 +20,252 @@ log = logging.getLogger("controller.main")
class Controller(object): class Controller(object):
def __init__(self): def __init__(self):
super(Controller, self).__init__() super(Controller, self).__init__()
log.debug("Starting main controller...") log.debug("Starting main controller...")
# Setting up the player object # Setting up the player object
player.setup() player.setup()
# Get main window # Get main window
self.window = mainWindow.mainWindow(extractors=[i.interface.name for i in get_services()]) self.window = mainWindow.mainWindow(extractors=[i.interface.name for i in get_services()])
log.debug("Main window created") log.debug("Main window created")
self.window.change_status(_(u"Ready")) self.window.change_status(_(u"Ready"))
# Here we will save results for searches as song objects. # Here we will save results for searches as song objects.
self.results = [] self.results = []
self.connect_events() self.connect_events()
self.timer = wx.Timer(self.window) self.timer = wx.Timer(self.window)
self.window.Bind(wx.EVT_TIMER, self.on_timer, self.timer) self.window.Bind(wx.EVT_TIMER, self.on_timer, self.timer)
self.timer.Start(75) self.timer.Start(75)
self.window.vol_slider.SetValue(player.player.volume) self.window.vol_slider.SetValue(player.player.volume)
# Shows window. # Shows window.
utils.call_threaded(updater.do_update) utils.call_threaded(updater.do_update)
log.debug("Music DL is ready") log.debug("Music DL is ready")
self.window.Show() self.window.Show()
def get_status_info(self): def get_status_info(self):
""" Formatting string for status bar messages """ """ Formatting string for status bar messages """
if len(self.results) > 0: if len(self.results) > 0:
results = _(u"Showing {0} results.").format(len(self.results)) results = _(u"Showing {0} results.").format(len(self.results))
else: else:
results = u"" results = u""
if player.player.shuffle: if player.player.shuffle:
shuffle = _(u"Shuffle on") shuffle = _(u"Shuffle on")
else: else:
shuffle = u"" shuffle = u""
final = u"{0} {1}".format(results, shuffle) final = u"{0} {1}".format(results, shuffle)
return final return final
def connect_events(self): def connect_events(self):
""" connects all widgets to their corresponding events.""" """ connects all widgets to their corresponding events."""
log.debug("Binding events...") log.debug("Binding events...")
widgetUtils.connect_event(self.window.search, widgetUtils.BUTTON_PRESSED, self.on_search) widgetUtils.connect_event(self.window.search, widgetUtils.BUTTON_PRESSED, self.on_search)
widgetUtils.connect_event(self.window.list, widgetUtils.LISTBOX_ITEM_ACTIVATED, self.on_activated) widgetUtils.connect_event(self.window.list, widgetUtils.LISTBOX_ITEM_ACTIVATED, self.on_activated)
widgetUtils.connect_event(self.window.list, widgetUtils.KEYPRESS, self.on_keypress) widgetUtils.connect_event(self.window.list, widgetUtils.KEYPRESS, self.on_keypress)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_play, menuitem=self.window.player_play) widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_play, menuitem=self.window.player_play)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_settings, menuitem=self.window.settings) widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_settings, menuitem=self.window.settings)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_next, menuitem=self.window.player_next) widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_next, menuitem=self.window.player_next)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_previous, menuitem=self.window.player_previous) widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_previous, menuitem=self.window.player_previous)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_stop, menuitem=self.window.player_stop) widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_stop, menuitem=self.window.player_stop)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_volume_down, menuitem=self.window.player_volume_down) widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_volume_down, menuitem=self.window.player_volume_down)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_volume_up, menuitem=self.window.player_volume_up) widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_volume_up, menuitem=self.window.player_volume_up)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_mute, menuitem=self.window.player_mute) widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_mute, menuitem=self.window.player_mute)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_shuffle, menuitem=self.window.player_shuffle) widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_shuffle, menuitem=self.window.player_shuffle)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.window.about_dialog, menuitem=self.window.about) widgetUtils.connect_event(self.window, widgetUtils.MENU, self.window.about_dialog, menuitem=self.window.about)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_check_for_updates, menuitem=self.window.check_for_updates) widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_check_for_updates, menuitem=self.window.check_for_updates)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_visit_changelog, menuitem=self.window.changelog) widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_visit_changelog, menuitem=self.window.changelog)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_visit_website, menuitem=self.window.website) widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_visit_website, menuitem=self.window.website)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_report_error, menuitem=self.window.report) widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_report_error, menuitem=self.window.report)
widgetUtils.connect_event(self.window.previous, widgetUtils.BUTTON_PRESSED, self.on_previous) widgetUtils.connect_event(self.window.previous, widgetUtils.BUTTON_PRESSED, self.on_previous)
widgetUtils.connect_event(self.window.play, widgetUtils.BUTTON_PRESSED, self.on_play_pause) widgetUtils.connect_event(self.window.play, widgetUtils.BUTTON_PRESSED, self.on_play_pause)
widgetUtils.connect_event(self.window.stop, widgetUtils.BUTTON_PRESSED, self.on_stop) widgetUtils.connect_event(self.window.stop, widgetUtils.BUTTON_PRESSED, self.on_stop)
widgetUtils.connect_event(self.window.next, widgetUtils.BUTTON_PRESSED, self.on_next) widgetUtils.connect_event(self.window.next, widgetUtils.BUTTON_PRESSED, self.on_next)
self.window.Bind(wx.EVT_COMMAND_SCROLL_THUMBTRACK, self.on_set_volume, self.window.vol_slider) self.window.Bind(wx.EVT_COMMAND_SCROLL_THUMBTRACK, self.on_set_volume, self.window.vol_slider)
self.window.Bind(wx.EVT_COMMAND_SCROLL_CHANGED, self.on_set_volume, self.window.vol_slider) self.window.Bind(wx.EVT_COMMAND_SCROLL_CHANGED, self.on_set_volume, self.window.vol_slider)
self.window.Bind(wx.EVT_COMMAND_SCROLL_THUMBTRACK, self.on_time_change, self.window.time_slider) self.window.Bind(wx.EVT_COMMAND_SCROLL_THUMBTRACK, self.on_time_change, self.window.time_slider)
self.window.Bind(wx.EVT_COMMAND_SCROLL_CHANGED, self.on_time_change, self.window.time_slider) self.window.Bind(wx.EVT_COMMAND_SCROLL_CHANGED, self.on_time_change, self.window.time_slider)
self.window.list.Bind(wx.EVT_LISTBOX_DCLICK, self.on_play) self.window.list.Bind(wx.EVT_LISTBOX_DCLICK, self.on_play)
self.window.list.Bind(wx.EVT_CONTEXT_MENU, self.on_context) self.window.list.Bind(wx.EVT_CONTEXT_MENU, self.on_context)
self.window.Bind(wx.EVT_CLOSE, self.on_close) self.window.Bind(wx.EVT_CLOSE, self.on_close)
pub.subscribe(self.change_status, "change_status") pub.subscribe(self.change_status, "change_status")
pub.subscribe(self.on_download_finished, "download_finished") pub.subscribe(self.on_download_finished, "download_finished")
pub.subscribe(self.on_notify, "notify") pub.subscribe(self.on_notify, "notify")
pub.subscribe(self.on_update_progress, "update-progress") pub.subscribe(self.on_update_progress, "update-progress")
# Event functions. These functions will call other functions in a thread and are bound to widget events. # Event functions. These functions will call other functions in a thread and are bound to widget events.
def on_update_progress(self, value): def on_update_progress(self, value):
wx.CallAfter(self.window.progressbar.SetValue, value) wx.CallAfter(self.window.progressbar.SetValue, value)
def on_settings(self, *args, **kwargs): def on_settings(self, *args, **kwargs):
settings = configuration.configuration() settings = configuration.configuration()
self.reload_extractors() self.reload_extractors()
def on_search(self, *args, **kwargs): def on_search(self, *args, **kwargs):
text = self.window.get_text() text = self.window.get_text()
if text == "": if text == "":
return return
extractor = self.window.extractor.GetValue() extractor = self.window.extractor.GetValue()
self.change_status(_(u"Searching {0}... ").format(text)) self.change_status(_(u"Searching {0}... ").format(text))
utils.call_threaded(self.search, text=text, extractor=extractor) utils.call_threaded(self.search, text=text, extractor=extractor)
def on_activated(self, *args, **kwargs): def on_activated(self, *args, **kwargs):
self.on_play() self.on_play()
def on_keypress(self, ev): def on_keypress(self, ev):
if ev.GetKeyCode() == wx.WXK_RETURN: if ev.GetKeyCode() == wx.WXK_RETURN:
return self.on_play() return self.on_play()
elif ev.GetKeyCode() == wx.WXK_SPACE: elif ev.GetKeyCode() == wx.WXK_SPACE:
return self.on_play_pause() return self.on_play_pause()
elif ev.GetKeyCode() == wx.WXK_LEFT and ev.ShiftDown(): elif ev.GetKeyCode() == wx.WXK_LEFT and ev.ShiftDown():
position = player.player.player.get_time() position = player.player.player.get_time()
if position > 5000: if position > 5000:
player.player.player.set_time(position-5000) player.player.player.set_time(position-5000)
else: else:
player.player.player.set_time(0) player.player.player.set_time(0)
return return
elif ev.GetKeyCode() == wx.WXK_RIGHT and ev.ShiftDown(): elif ev.GetKeyCode() == wx.WXK_RIGHT and ev.ShiftDown():
position = player.player.player.get_time() position = player.player.player.get_time()
player.player.player.set_time(position+5000) player.player.player.set_time(position+5000)
return return
elif ev.GetKeyCode() == wx.WXK_UP and ev.ControlDown(): elif ev.GetKeyCode() == wx.WXK_UP and ev.ControlDown():
return self.on_volume_up() return self.on_volume_up()
elif ev.GetKeyCode() == wx.WXK_DOWN and ev.ControlDown(): elif ev.GetKeyCode() == wx.WXK_DOWN and ev.ControlDown():
return self.on_volume_down() return self.on_volume_down()
elif ev.GetKeyCode() == wx.WXK_LEFT and ev.AltDown(): elif ev.GetKeyCode() == wx.WXK_LEFT and ev.AltDown():
return self.on_previous() return self.on_previous()
elif ev.GetKeyCode() == wx.WXK_RIGHT and ev.AltDown(): elif ev.GetKeyCode() == wx.WXK_RIGHT and ev.AltDown():
return self.on_next() return self.on_next()
ev.Skip() ev.Skip()
def on_play_pause(self, *args, **kwargs): def on_play_pause(self, *args, **kwargs):
if player.player.player.is_playing() == 1: if player.player.player.is_playing() == 1:
self.window.play.SetLabel(_(u"Play")) self.window.play.SetLabel(_(u"Play"))
return player.player.pause() return player.player.pause()
else: else:
self.window.play.SetLabel(_(u"Pause")) self.window.play.SetLabel(_(u"Pause"))
return player.player.player.play() return player.player.player.play()
def on_next(self, *args, **kwargs): def on_next(self, *args, **kwargs):
return utils.call_threaded(player.player.next) return utils.call_threaded(player.player.next)
def on_previous(self, *args, **kwargs): def on_previous(self, *args, **kwargs):
return utils.call_threaded(player.player.previous) return utils.call_threaded(player.player.previous)
def on_play(self, *args, **kwargs): def on_play(self, *args, **kwargs):
items = self.results[::] items = self.results[::]
playing_item = self.window.get_item() playing_item = self.window.get_item()
self.window.play.SetLabel(_(u"Pause")) self.window.play.SetLabel(_(u"Pause"))
return utils.call_threaded(player.player.play_all, items, playing=playing_item, shuffle=self.window.player_shuffle.IsChecked()) return utils.call_threaded(player.player.play_all, items, playing=playing_item, shuffle=self.window.player_shuffle.IsChecked())
def on_stop(self, *args, **kwargs): def on_stop(self, *args, **kwargs):
player.player.stop() player.player.stop()
self.window.play.SetLabel(_(u"Play")) self.window.play.SetLabel(_(u"Play"))
def on_volume_down(self, *args, **kwargs): def on_volume_down(self, *args, **kwargs):
self.window.vol_slider.SetValue(self.window.vol_slider.GetValue()-5) self.window.vol_slider.SetValue(self.window.vol_slider.GetValue()-5)
self.on_set_volume() self.on_set_volume()
def on_volume_up(self, *args, **kwargs): def on_volume_up(self, *args, **kwargs):
self.window.vol_slider.SetValue(self.window.vol_slider.GetValue()+5) self.window.vol_slider.SetValue(self.window.vol_slider.GetValue()+5)
self.on_set_volume() self.on_set_volume()
def on_mute(self, *args, **kwargs): def on_mute(self, *args, **kwargs):
self.window.vol_slider.SetValue(0) self.window.vol_slider.SetValue(0)
self.on_set_volume() self.on_set_volume()
def on_shuffle(self, *args, **kwargs): def on_shuffle(self, *args, **kwargs):
player.player.shuffle = self.window.player_shuffle.IsChecked() player.player.shuffle = self.window.player_shuffle.IsChecked()
def on_context(self, *args, **kwargs): def on_context(self, *args, **kwargs):
item = self.window.get_item() item = self.window.get_item()
if item == -1: if item == -1:
return wx.Bell() return wx.Bell()
menu = menus.contextMenu() menu = menus.contextMenu()
widgetUtils.connect_event(menu, widgetUtils.MENU, self.on_play, menuitem=menu.play) widgetUtils.connect_event(menu, widgetUtils.MENU, self.on_play, menuitem=menu.play)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.on_download, menuitem=menu.download) widgetUtils.connect_event(menu, widgetUtils.MENU, self.on_download, menuitem=menu.download)
self.window.PopupMenu(menu, wx.GetMousePosition()) self.window.PopupMenu(menu, wx.GetMousePosition())
menu.Destroy() menu.Destroy()
def on_download(self, *args, **kwargs): def on_download(self, *args, **kwargs):
item = self.results[self.window.get_item()] item = self.results[self.window.get_item()]
if item.download_url == "": if item.download_url == "":
item.get_download_url() item.get_download_url()
log.debug("Starting requested download: {0} (using extractor: {1})".format(item.title, self.extractor.name)) log.debug("Starting requested download: {0} (using extractor: {1})".format(item.title, self.extractor.name))
f = "{item_name}.{item_extension}".format(item_name=item.format_track(), item_extension=item.extractor.get_file_format()) f = "{item_name}.{item_extension}".format(item_name=item.format_track(), item_extension=item.extractor.get_file_format())
path = self.window.get_destination_path(utils.safe_filename(f)) path = self.window.get_destination_path(utils.safe_filename(f))
if path != None: if path != None:
log.debug("User has requested the following path: {0}".format(path,)) log.debug("User has requested the following path: {0}".format(path,))
if self.extractor.transcoder_enabled() == True: # Send download to vlc based transcoder if self.extractor.transcoder_enabled() == True: # Send download to vlc based transcoder
utils.call_threaded(player.player.transcode_audio, item, path, _format=item.extractor.get_file_format(), metadata=item.get_metadata()) utils.call_threaded(player.player.transcode_audio, item, path, _format=item.extractor.get_file_format(), metadata=item.get_metadata())
else: else:
log.debug("downloading %s URL to %s filename" % (item.download_url, path,)) log.debug("downloading %s URL to %s filename" % (item.download_url, path,))
utils.call_threaded(utils.download_file, item.download_url, path, metadata=item.get_metadata()) utils.call_threaded(utils.download_file, item.download_url, path, metadata=item.get_metadata())
def on_set_volume(self, *args, **kwargs): def on_set_volume(self, *args, **kwargs):
volume = self.window.vol_slider.GetValue() volume = self.window.vol_slider.GetValue()
player.player.volume = volume player.player.volume = volume
def on_time_change(self, event, *args, **kwargs): def on_time_change(self, event, *args, **kwargs):
p = event.GetPosition() p = event.GetPosition()
player.player.player.set_position(p/100.0) player.player.player.set_position(p/100.0)
event.Skip() event.Skip()
def on_timer(self, *args, **kwargs): def on_timer(self, *args, **kwargs):
if not self.window.time_slider.HasFocus(): if not self.window.time_slider.HasFocus():
progress = player.player.player.get_position()*100 progress = player.player.player.get_position()*100
self.window.time_slider.SetValue(progress) self.window.time_slider.SetValue(progress)
def on_close(self, event): def on_close(self, event):
log.debug("Exiting...") log.debug("Exiting...")
self.timer.Stop() self.timer.Stop()
pub.unsubscribe(self.on_download_finished, "download_finished") pub.unsubscribe(self.on_download_finished, "download_finished")
config.app.write() config.app.write()
event.Skip() event.Skip()
widgetUtils.exit_application() widgetUtils.exit_application()
def change_status(self, status): def change_status(self, status):
""" Function used for changing the status bar from outside the main controller module.""" """ Function used for changing the status bar from outside the main controller module."""
self.window.change_status(u"{0} {1}".format(status, self.get_status_info())) self.window.change_status(u"{0} {1}".format(status, self.get_status_info()))
def on_visit_website(self, *args, **kwargs): def on_visit_website(self, *args, **kwargs):
webbrowser.open_new_tab(application.url) webbrowser.open_new_tab(application.url)
def on_report_error(self, *args, **kwargs): def on_report_error(self, *args, **kwargs):
r = issueReporter.reportBug() r = issueReporter.reportBug()
def on_visit_changelog(self, *args, **kwargs): def on_visit_changelog(self, *args, **kwargs):
webbrowser.open_new_tab(application.url+"/news") webbrowser.open_new_tab(application.url+"/news")
def on_check_for_updates(self, *args, **kwargs): def on_check_for_updates(self, *args, **kwargs):
utils.call_threaded(updater.do_update) utils.call_threaded(updater.do_update)
def on_download_finished(self, file): def on_download_finished(self, file):
title = "MusicDL" title = "MusicDL"
msg = _(u"File downloaded: {0}").format(file,) msg = _(u"File downloaded: {0}").format(file,)
self.window.notify(title, msg) self.window.notify(title, msg)
def on_notify(self, title, message): def on_notify(self, title, message):
self.window.notify(title, message) self.window.notify(title, message)
# real functions. These functions really are doing the work. # real functions. These functions really are doing the work.
def search(self, text, extractor, *args, **kwargs): def search(self, text, extractor, *args, **kwargs):
extractors = get_services() extractors = get_services()
for i in extractors: for i in extractors:
if extractor == i.interface.name: if extractor == i.interface.name:
self.extractor = i.interface() self.extractor = i.interface()
break break
log.debug("Started search for {0} (selected extractor: {1})".format(text, self.extractor.name)) log.debug("Started search for {0} (selected extractor: {1})".format(text, self.extractor.name))
wx.CallAfter(self.window.list.Clear) wx.CallAfter(self.window.list.Clear)
self.extractor.search(text) self.extractor.search(text)
self.results = self.extractor.results self.results = self.extractor.results
for i in self.results: for i in self.results:
wx.CallAfter(self.window.list.Append, i.format_track()) wx.CallAfter(self.window.list.Append, i.format_track())
if len(self.results) == 0: if len(self.results) == 0:
wx.CallAfter(self.change_status, _(u"No results found. ")) wx.CallAfter(self.change_status, _(u"No results found. "))
else: else:
wx.CallAfter(self.change_status, u"") wx.CallAfter(self.change_status, u"")
wx.CallAfter(self.window.list.SetFocus) wx.CallAfter(self.window.list.SetFocus)
def reload_extractors(self): def reload_extractors(self):
extractors = [i.interface.name for i in get_services()] extractors = [i.interface.name for i in get_services()]
self.window.extractor.SetItems(extractors) self.window.extractor.SetItems(extractors)
self.window.extractor.SetValue(extractors[0]) self.window.extractor.SetValue(extractors[0])

View File

@ -12,158 +12,158 @@ player = None
log = logging.getLogger("controller.player") log = logging.getLogger("controller.player")
def setup(): def setup():
global player global player
if player == None: if player == None:
player = audioPlayer() player = audioPlayer()
class audioPlayer(object): class audioPlayer(object):
def __init__(self): def __init__(self):
self.is_playing = False self.is_playing = False
self.vol = config.app["main"]["volume"] self.vol = config.app["main"]["volume"]
self.is_working = False self.is_working = False
self.queue = [] self.queue = []
self.stopped = True self.stopped = True
self.queue_pos = 0 self.queue_pos = 0
self.shuffle = False self.shuffle = False
self.instance = vlc.Instance() self.instance = vlc.Instance()
log.debug("Instantiating Media player with the following information: VLC version detected={}. VLC bindings for python version{}".format(vlc.libvlc_get_version(), vlc.__version__)) log.debug("Instantiating Media player with the following information: VLC version detected={}. VLC bindings for python version{}".format(vlc.libvlc_get_version(), vlc.__version__))
self.player = self.instance.media_player_new() self.player = self.instance.media_player_new()
log.debug("Media player instantiated.") log.debug("Media player instantiated.")
self.event_manager = self.player.event_manager() self.event_manager = self.player.event_manager()
self.event_manager.event_attach(vlc.EventType.MediaPlayerEndReached, self.end_callback) self.event_manager.event_attach(vlc.EventType.MediaPlayerEndReached, self.end_callback)
self.event_manager.event_attach(vlc.EventType.MediaPlayerEncounteredError, self.playback_error) self.event_manager.event_attach(vlc.EventType.MediaPlayerEncounteredError, self.playback_error)
log.debug("Bound media playback events.") log.debug("Bound media playback events.")
# configure output device # configure output device
self.set_output_device(config.app["main"]["output_device"]) self.set_output_device(config.app["main"]["output_device"])
def get_output_devices(self): def get_output_devices(self):
""" Retrieve enabled output devices so we can switch or use those later. """ """ Retrieve enabled output devices so we can switch or use those later. """
log.debug("Retrieving output devices...") log.debug("Retrieving output devices...")
devices = [] devices = []
mods = self.player.audio_output_device_enum() mods = self.player.audio_output_device_enum()
if mods: if mods:
mod = mods mod = mods
while mod: while mod:
mod = mod.contents mod = mod.contents
devices.append(dict(id=mod.device, name=mod.description)) devices.append(dict(id=mod.device, name=mod.description))
mod = mod.next mod = mod.next
vlc.libvlc_audio_output_device_list_release(mods) vlc.libvlc_audio_output_device_list_release(mods)
return devices return devices
def set_output_device(self, device_id): def set_output_device(self, device_id):
""" Set Output device to be used in LibVLC""" """ Set Output device to be used in LibVLC"""
log.debug("Setting output audio device to {device}...".format(device=device_id,)) log.debug("Setting output audio device to {device}...".format(device=device_id,))
self.player.audio_output_device_set(None, device_id) self.player.audio_output_device_set(None, device_id)
def play(self, item): def play(self, item):
self.stopped = True self.stopped = True
if self.is_working == False: if self.is_working == False:
self.is_working = True self.is_working = True
if item.download_url == "": if item.download_url == "":
item.get_download_url() item.get_download_url()
log.debug("playing {0}...".format(item.download_url,)) log.debug("playing {0}...".format(item.download_url,))
self.stream_new = self.instance.media_new(item.download_url) self.stream_new = self.instance.media_new(item.download_url)
self.player.set_media(self.stream_new) self.player.set_media(self.stream_new)
if self.player.play() == -1: if self.player.play() == -1:
log.debug("Error when playing the file {0}".format(item.title,)) log.debug("Error when playing the file {0}".format(item.title,))
pub.sendMessage("change_status", status=_("Error playing {0}. {1}.").format(item.title, e.description)) pub.sendMessage("change_status", status=_("Error playing {0}. {1}.").format(item.title, e.description))
self.stopped = True self.stopped = True
self.is_working = False self.is_working = False
self.next() self.next()
return return
self.player.audio_set_volume(self.vol) self.player.audio_set_volume(self.vol)
pub.sendMessage("change_status", status=_("Playing {0}.").format(item.title)) pub.sendMessage("change_status", status=_("Playing {0}.").format(item.title))
self.stopped = False self.stopped = False
self.is_working = False self.is_working = False
def next(self): def next(self):
if len(self.queue) > 0: if len(self.queue) > 0:
if self.shuffle: if self.shuffle:
self.queue_pos = random.randint(0, len(self.queue)-1) self.queue_pos = random.randint(0, len(self.queue)-1)
else: else:
if self.queue_pos < len(self.queue)-1: if self.queue_pos < len(self.queue)-1:
self.queue_pos += 1 self.queue_pos += 1
else: else:
self.queue_pos = 0 self.queue_pos = 0
self.play(self.queue[self.queue_pos]) self.play(self.queue[self.queue_pos])
def previous(self): def previous(self):
if len(self.queue) > 0: if len(self.queue) > 0:
if self.shuffle: if self.shuffle:
self.queue_pos = random.randint(0, len(self.queue)-1) self.queue_pos = random.randint(0, len(self.queue)-1)
else: else:
if self.queue_pos > 0: if self.queue_pos > 0:
self.queue_pos -= 1 self.queue_pos -= 1
else: else:
self.queue_pos = len(self.queue)-1 self.queue_pos = len(self.queue)-1
self.play(self.queue[self.queue_pos]) self.play(self.queue[self.queue_pos])
def stop(self): def stop(self):
self.player.stop() self.player.stop()
self.stopped = True self.stopped = True
def pause(self): def pause(self):
self.player.pause() self.player.pause()
if self.stopped == True: if self.stopped == True:
self.stopped = False self.stopped = False
else: else:
self.stopped = True self.stopped = True
@property @property
def volume(self): def volume(self):
return self.vol return self.vol
@volume.setter @volume.setter
def volume(self, vol): def volume(self, vol):
if vol <= 100 and vol >= 0: if vol <= 100 and vol >= 0:
config.app["main"]["volume"] = vol config.app["main"]["volume"] = vol
self.vol = vol self.vol = vol
self.player.audio_set_volume(self.vol) self.player.audio_set_volume(self.vol)
def play_all(self, list_of_items, playing=0, shuffle=False): def play_all(self, list_of_items, playing=0, shuffle=False):
if list_of_items != self.queue: if list_of_items != self.queue:
self.queue = list_of_items self.queue = list_of_items
self.shuffle = shuffle self.shuffle = shuffle
self.queue_pos = playing self.queue_pos = playing
self.play(self.queue[self.queue_pos]) self.play(self.queue[self.queue_pos])
def end_callback(self, event, *args, **kwargs): def end_callback(self, event, *args, **kwargs):
#https://github.com/ZeBobo5/Vlc.DotNet/issues/4 #https://github.com/ZeBobo5/Vlc.DotNet/issues/4
call_threaded(self.next) call_threaded(self.next)
def transcode_audio(self, item, path, _format="mp3", bitrate=320, metadata=dict()): def transcode_audio(self, item, path, _format="mp3", bitrate=320, metadata=dict()):
""" Converts given item to mp3. This method will be available when needed automatically.""" """ Converts given item to mp3. This method will be available when needed automatically."""
if item.download_url == "": if item.download_url == "":
item.get_download_url() item.get_download_url()
log.debug("Download started: filename={0}, url={1}".format(path, item.download_url)) log.debug("Download started: filename={0}, url={1}".format(path, item.download_url))
temporary_filename = "chunk_{0}".format(random.randint(0,2000000)) temporary_filename = "chunk_{0}".format(random.randint(0,2000000))
temporary_path = os.path.join(os.path.dirname(path), temporary_filename) temporary_path = os.path.join(os.path.dirname(path), temporary_filename)
# Let's get a new VLC instance for transcoding this file. # Let's get a new VLC instance for transcoding this file.
transcoding_instance = vlc.Instance(*["--sout=#transcode{acodec=%s,ab=%d}:file{mux=raw,dst=\"%s\"}"% (_format, bitrate, temporary_path,)]) transcoding_instance = vlc.Instance(*["--sout=#transcode{acodec=%s,ab=%d}:file{mux=raw,dst=\"%s\"}"% (_format, bitrate, temporary_path,)])
transcoder = transcoding_instance.media_player_new() transcoder = transcoding_instance.media_player_new()
transcoder.set_mrl(item.download_url) transcoder.set_mrl(item.download_url)
pub.sendMessage("change_status", status=_(u"Downloading {0}.").format(item.title,)) pub.sendMessage("change_status", status=_(u"Downloading {0}.").format(item.title,))
media = transcoder.get_media() media = transcoder.get_media()
transcoder.play() transcoder.play()
while True: while True:
state = media.get_state() state = media.get_state()
pub.sendMessage("change_status", status=_("Downloading {0} ({1}%).").format(item.title, int(transcoder.get_position()*100))) pub.sendMessage("change_status", status=_("Downloading {0} ({1}%).").format(item.title, int(transcoder.get_position()*100)))
pub.sendMessage("update-progress", value=int(transcoder.get_position()*100)) pub.sendMessage("update-progress", value=int(transcoder.get_position()*100))
if str(state) == 'State.Ended': if str(state) == 'State.Ended':
break break
elif str(state) == 'state.error': elif str(state) == 'state.error':
os.remove(temporary_path) os.remove(temporary_path)
break break
transcoder.release() transcoder.release()
os.rename(temporary_path, path) os.rename(temporary_path, path)
log.debug("Download finished sucsessfully.") log.debug("Download finished sucsessfully.")
pub.sendMessage("download_finished", file=os.path.basename(path)) pub.sendMessage("download_finished", file=os.path.basename(path))
def playback_error(self, event): def playback_error(self, event):
pub.sendMessage("notify", title=_("Error"), message=_("There was an error while trying to access the file you have requested.")) pub.sendMessage("notify", title=_("Error"), message=_("There was an error while trying to access the file you have requested."))
def __del__(self): def __del__(self):
self.event_manager.event_detach(vlc.EventType.MediaPlayerEndReached) self.event_manager.event_detach(vlc.EventType.MediaPlayerEndReached)
if hasattr(self, "event_manager"): if hasattr(self, "event_manager"):
self.event_manager.event_detach(vlc.EventType.MediaPlayerEncounteredError, self.playback_error) self.event_manager.event_detach(vlc.EventType.MediaPlayerEncounteredError, self.playback_error)

View File

@ -4,4 +4,4 @@ from . import fix_requests
from .import fix_winpaths from .import fix_winpaths
def setup(): def setup():
fix_requests.fix() fix_requests.fix()

View File

@ -7,6 +7,6 @@ import paths
log = logging.getLogger("fixes.fix_requests") log = logging.getLogger("fixes.fix_requests")
def fix(): def fix():
log.debug("Applying fix for requests...") log.debug("Applying fix for requests...")
os.environ["REQUESTS_CA_BUNDLE"] = os.path.join(paths.app_path(), "cacerts.txt") os.environ["REQUESTS_CA_BUNDLE"] = os.path.join(paths.app_path(), "cacerts.txt")
log.debug("Changed CA path to %s" % (os.environ["REQUESTS_CA_BUNDLE"],)) log.debug("Changed CA path to %s" % (os.environ["REQUESTS_CA_BUNDLE"],))

View File

@ -4,9 +4,9 @@ import winpaths
from ctypes import wintypes from ctypes import wintypes
def _get_path_buf(csidl): def _get_path_buf(csidl):
path_buf = ctypes.create_unicode_buffer(wintypes.MAX_PATH) path_buf = ctypes.create_unicode_buffer(wintypes.MAX_PATH)
result = winpaths._SHGetFolderPath(0, csidl, 0, 0, path_buf) result = winpaths._SHGetFolderPath(0, csidl, 0, 0, path_buf)
return path_buf.value return path_buf.value
def fix(): def fix():
winpaths._get_path_buf = _get_path_buf winpaths._get_path_buf = _get_path_buf

View File

@ -9,10 +9,10 @@ import paths
log = logging.getLogger("i18n") log = logging.getLogger("i18n")
def setup(): def setup():
lang = locale.getdefaultlocale()[0] lang = locale.getdefaultlocale()[0]
os.environ["lang"] = lang os.environ["lang"] = lang
log.debug("System detected language: {0}".format(lang,)) log.debug("System detected language: {0}".format(lang,))
if sys.version[0] == "3": if sys.version[0] == "3":
gettext.install("musicdl", localedir=os.path.join(paths.app_path(), "locales")) gettext.install("musicdl", localedir=os.path.join(paths.app_path(), "locales"))
else: else:
gettext.install("musicdl", localedir=os.path.join(paths.app_path(), "locales"), unicode=True) gettext.install("musicdl", localedir=os.path.join(paths.app_path(), "locales"), unicode=True)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
############################################################ ############################################################
# Copyright (c) 2018 Manuel Cortez <manuel@manuelcortez.net> # Copyright (c) 2018 Manuel Cortez <manuel@manuelcortez.net>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or # the Free Software Foundation, either version 2 of the License, or
@ -27,37 +27,37 @@ from utils import call_threaded
from . import wx_ui from . import wx_ui
class reportBug(object): class reportBug(object):
def __init__(self): def __init__(self):
self.dialog = wx_ui.reportBugDialog() self.dialog = wx_ui.reportBugDialog()
widgetUtils.connect_event(self.dialog.ok, widgetUtils.BUTTON_PRESSED, self.send) widgetUtils.connect_event(self.dialog.ok, widgetUtils.BUTTON_PRESSED, self.send)
self.dialog.get_response() self.dialog.get_response()
def do_report(self, *args, **kwargs): def do_report(self, *args, **kwargs):
r = requests.post(*args, **kwargs) r = requests.post(*args, **kwargs)
if r.status_code > 300: if r.status_code > 300:
wx.CallAfter(self.dialog.error) wx.CallAfter(self.dialog.error)
wx.CallAfter(self.dialog.progress.Destroy) wx.CallAfter(self.dialog.progress.Destroy)
wx.CallAfter(self.dialog.success, r.json()["data"]["issue"]["id"]) wx.CallAfter(self.dialog.success, r.json()["data"]["issue"]["id"])
def send(self, *args, **kwargs): def send(self, *args, **kwargs):
if self.dialog.get("summary") == "" or self.dialog.get("description") == "" or self.dialog.get("first_name") == "" or self.dialog.get("last_name") == "": if self.dialog.get("summary") == "" or self.dialog.get("description") == "" or self.dialog.get("first_name") == "" or self.dialog.get("last_name") == "":
self.dialog.no_filled() self.dialog.no_filled()
return return
if self.dialog.get("agree") == False: if self.dialog.get("agree") == False:
self.dialog.no_checkbox() self.dialog.no_checkbox()
return return
title = self.dialog.get("summary") title = self.dialog.get("summary")
body = self.dialog.get("description") body = self.dialog.get("description")
issue_type = "issue" # for now just have issue issue_type = "issue" # for now just have issue
app_type = storage.app_type app_type = storage.app_type
app_version = application.version app_version = application.version
reporter_name = "{first_name} {last_name}".format(first_name=self.dialog.get("first_name"), last_name=self.dialog.get("last_name")) reporter_name = "{first_name} {last_name}".format(first_name=self.dialog.get("first_name"), last_name=self.dialog.get("last_name"))
reporter_contact_type = "email" # For now just email is supported in the issue reporter reporter_contact_type = "email" # For now just email is supported in the issue reporter
reporter_contact_handle = self.dialog.get("email") reporter_contact_handle = self.dialog.get("email")
operating_system = platform.platform() operating_system = platform.platform()
json = dict(title=title, issue_type=issue_type, body=body, operating_system=operating_system, app_type=app_type, app_version=app_version, reporter_name=reporter_name, reporter_contact_handle=reporter_contact_handle, reporter_contact_type=reporter_contact_type) json = dict(title=title, issue_type=issue_type, body=body, operating_system=operating_system, app_type=app_type, app_version=app_version, reporter_name=reporter_name, reporter_contact_handle=reporter_contact_handle, reporter_contact_type=reporter_contact_type)
auth=HTTPBasicAuth(application.bts_name, application.bts_access_token) auth=HTTPBasicAuth(application.bts_name, application.bts_access_token)
url = "{bts_url}/issue/new".format(bts_url=application.bts_url) url = "{bts_url}/issue/new".format(bts_url=application.bts_url)
call_threaded(self.do_report, url, json=json, auth=auth) call_threaded(self.do_report, url, json=json, auth=auth)
self.dialog.show_progress() self.dialog.show_progress()
self.dialog.EndModal(wx.ID_OK) self.dialog.EndModal(wx.ID_OK)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
############################################################ ############################################################
# Copyright (c) 2018 Manuel cortez <manuel@manuelcortez.net> # Copyright (c) 2018 Manuel cortez <manuel@manuelcortez.net>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or # the Free Software Foundation, either version 2 of the License, or
@ -21,89 +21,89 @@ import widgetUtils
import application import application
class reportBugDialog(widgetUtils.BaseDialog): class reportBugDialog(widgetUtils.BaseDialog):
def __init__(self): def __init__(self):
super(reportBugDialog, self).__init__(parent=None, id=wx.NewId()) super(reportBugDialog, self).__init__(parent=None, id=wx.NewId())
self.SetTitle(_(u"Report an error")) self.SetTitle(_(u"Report an error"))
panel = wx.Panel(self) panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL) sizer = wx.BoxSizer(wx.VERTICAL)
summaryLabel = wx.StaticText(panel, -1, _(u"Briefly describe what happened. You will be able to thoroughly explain it later"), size=wx.DefaultSize) summaryLabel = wx.StaticText(panel, -1, _(u"Briefly describe what happened. You will be able to thoroughly explain it later"), size=wx.DefaultSize)
self.summary = wx.TextCtrl(panel, -1) self.summary = wx.TextCtrl(panel, -1)
dc = wx.WindowDC(self.summary) dc = wx.WindowDC(self.summary)
dc.SetFont(self.summary.GetFont()) dc.SetFont(self.summary.GetFont())
self.summary.SetSize(dc.GetTextExtent("a"*80)) self.summary.SetSize(dc.GetTextExtent("a"*80))
summaryB = wx.BoxSizer(wx.HORIZONTAL) summaryB = wx.BoxSizer(wx.HORIZONTAL)
summaryB.Add(summaryLabel, 0, wx.ALL, 5) summaryB.Add(summaryLabel, 0, wx.ALL, 5)
summaryB.Add(self.summary, 0, wx.ALL, 5) summaryB.Add(self.summary, 0, wx.ALL, 5)
sizer.Add(summaryB, 0, wx.ALL, 5) sizer.Add(summaryB, 0, wx.ALL, 5)
first_nameLabel = wx.StaticText(panel, -1, _(u"First Name"), size=wx.DefaultSize) first_nameLabel = wx.StaticText(panel, -1, _(u"First Name"), size=wx.DefaultSize)
self.first_name = wx.TextCtrl(panel, -1) self.first_name = wx.TextCtrl(panel, -1)
dc = wx.WindowDC(self.first_name) dc = wx.WindowDC(self.first_name)
dc.SetFont(self.first_name.GetFont()) dc.SetFont(self.first_name.GetFont())
self.first_name.SetSize(dc.GetTextExtent("a"*40)) self.first_name.SetSize(dc.GetTextExtent("a"*40))
first_nameB = wx.BoxSizer(wx.HORIZONTAL) first_nameB = wx.BoxSizer(wx.HORIZONTAL)
first_nameB.Add(first_nameLabel, 0, wx.ALL, 5) first_nameB.Add(first_nameLabel, 0, wx.ALL, 5)
first_nameB.Add(self.first_name, 0, wx.ALL, 5) first_nameB.Add(self.first_name, 0, wx.ALL, 5)
sizer.Add(first_nameB, 0, wx.ALL, 5) sizer.Add(first_nameB, 0, wx.ALL, 5)
last_nameLabel = wx.StaticText(panel, -1, _(u"Last Name"), size=wx.DefaultSize) last_nameLabel = wx.StaticText(panel, -1, _(u"Last Name"), size=wx.DefaultSize)
self.last_name = wx.TextCtrl(panel, -1) self.last_name = wx.TextCtrl(panel, -1)
dc = wx.WindowDC(self.last_name) dc = wx.WindowDC(self.last_name)
dc.SetFont(self.last_name.GetFont()) dc.SetFont(self.last_name.GetFont())
self.last_name.SetSize(dc.GetTextExtent("a"*40)) self.last_name.SetSize(dc.GetTextExtent("a"*40))
last_nameB = wx.BoxSizer(wx.HORIZONTAL) last_nameB = wx.BoxSizer(wx.HORIZONTAL)
last_nameB.Add(last_nameLabel, 0, wx.ALL, 5) last_nameB.Add(last_nameLabel, 0, wx.ALL, 5)
last_nameB.Add(self.last_name, 0, wx.ALL, 5) last_nameB.Add(self.last_name, 0, wx.ALL, 5)
sizer.Add(last_nameB, 0, wx.ALL, 5) sizer.Add(last_nameB, 0, wx.ALL, 5)
emailLabel = wx.StaticText(panel, -1, _(u"Email address (Will not be public)"), size=wx.DefaultSize) emailLabel = wx.StaticText(panel, -1, _(u"Email address (Will not be public)"), size=wx.DefaultSize)
self.email = wx.TextCtrl(panel, -1) self.email = wx.TextCtrl(panel, -1)
dc = wx.WindowDC(self.email) dc = wx.WindowDC(self.email)
dc.SetFont(self.email.GetFont()) dc.SetFont(self.email.GetFont())
self.email.SetSize(dc.GetTextExtent("a"*30)) self.email.SetSize(dc.GetTextExtent("a"*30))
emailB = wx.BoxSizer(wx.HORIZONTAL) emailB = wx.BoxSizer(wx.HORIZONTAL)
emailB.Add(emailLabel, 0, wx.ALL, 5) emailB.Add(emailLabel, 0, wx.ALL, 5)
emailB.Add(self.email, 0, wx.ALL, 5) emailB.Add(self.email, 0, wx.ALL, 5)
sizer.Add(emailB, 0, wx.ALL, 5) sizer.Add(emailB, 0, wx.ALL, 5)
descriptionLabel = wx.StaticText(panel, -1, _(u"Here, you can describe the bug in detail"), size=wx.DefaultSize) descriptionLabel = wx.StaticText(panel, -1, _(u"Here, you can describe the bug in detail"), size=wx.DefaultSize)
self.description = wx.TextCtrl(panel, -1, style=wx.TE_MULTILINE) self.description = wx.TextCtrl(panel, -1, style=wx.TE_MULTILINE)
dc = wx.WindowDC(self.description) dc = wx.WindowDC(self.description)
dc.SetFont(self.description.GetFont()) dc.SetFont(self.description.GetFont())
(x, y) = dc.GetMultiLineTextExtent("0"*2000) (x, y) = dc.GetMultiLineTextExtent("0"*2000)
self.description.SetSize((x, y)) self.description.SetSize((x, y))
descBox = wx.BoxSizer(wx.HORIZONTAL) descBox = wx.BoxSizer(wx.HORIZONTAL)
descBox.Add(descriptionLabel, 0, wx.ALL, 5) descBox.Add(descriptionLabel, 0, wx.ALL, 5)
descBox.Add(self.description, 0, wx.ALL, 5) descBox.Add(self.description, 0, wx.ALL, 5)
sizer.Add(descBox, 0, wx.ALL, 5) sizer.Add(descBox, 0, wx.ALL, 5)
self.agree = wx.CheckBox(panel, -1, _(u"I know that the {0} bug system will get my email address to contact me and fix the bug quickly").format(application.name,)) self.agree = wx.CheckBox(panel, -1, _(u"I know that the {0} bug system will get my email address to contact me and fix the bug quickly").format(application.name,))
self.agree.SetValue(False) self.agree.SetValue(False)
sizer.Add(self.agree, 0, wx.ALL, 5) sizer.Add(self.agree, 0, wx.ALL, 5)
self.ok = wx.Button(panel, wx.ID_OK, _(u"Send report")) self.ok = wx.Button(panel, wx.ID_OK, _(u"Send report"))
self.ok.SetDefault() self.ok.SetDefault()
cancel = wx.Button(panel, wx.ID_CANCEL, _(u"Cancel")) cancel = wx.Button(panel, wx.ID_CANCEL, _(u"Cancel"))
btnBox = wx.BoxSizer(wx.HORIZONTAL) btnBox = wx.BoxSizer(wx.HORIZONTAL)
btnBox.Add(self.ok, 0, wx.ALL, 5) btnBox.Add(self.ok, 0, wx.ALL, 5)
btnBox.Add(cancel, 0, wx.ALL, 5) btnBox.Add(cancel, 0, wx.ALL, 5)
sizer.Add(btnBox, 0, wx.ALL, 5) sizer.Add(btnBox, 0, wx.ALL, 5)
panel.SetSizer(sizer) panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin()) self.SetClientSize(sizer.CalcMin())
def no_filled(self): def no_filled(self):
wx.MessageDialog(self, _(u"You must fill out the following fields: first name, last name, email address and issue information."), _(u"Error"), wx.OK|wx.ICON_ERROR).ShowModal() wx.MessageDialog(self, _(u"You must fill out the following fields: first name, last name, email address and issue information."), _(u"Error"), wx.OK|wx.ICON_ERROR).ShowModal()
def no_checkbox(self): def no_checkbox(self):
wx.MessageDialog(self, _(u"You need to mark the checkbox to provide us your email address to contact you if it is necessary."), _(u"Error"), wx.ICON_ERROR).ShowModal() wx.MessageDialog(self, _(u"You need to mark the checkbox to provide us your email address to contact you if it is necessary."), _(u"Error"), wx.ICON_ERROR).ShowModal()
def success(self, id): def success(self, id):
wx.MessageDialog(self, _(u"Thanks for reporting this bug! In future versions, you may be able to find it in the changes list. You have received an email with more information regarding your report. You've reported the bug number %i") % (id), _(u"reported"), wx.OK).ShowModal() wx.MessageDialog(self, _(u"Thanks for reporting this bug! In future versions, you may be able to find it in the changes list. You have received an email with more information regarding your report. You've reported the bug number %i") % (id), _(u"reported"), wx.OK).ShowModal()
self.Destroy() self.Destroy()
def error(self): def error(self):
wx.MessageDialog(self, _(u"Something unexpected occurred while trying to report the bug. Please, try again later"), _(u"Error while reporting"), wx.ICON_ERROR|wx.OK).ShowModal() wx.MessageDialog(self, _(u"Something unexpected occurred while trying to report the bug. Please, try again later"), _(u"Error while reporting"), wx.ICON_ERROR|wx.OK).ShowModal()
self.Destroy() self.Destroy()
def show_progress(self): def show_progress(self):
self.progress = wx.ProgressDialog(title=_(u"Sending report..."), message=_(u"Please wait while your report is being send."), maximum=100, parent=self) self.progress = wx.ProgressDialog(title=_(u"Sending report..."), message=_(u"Please wait while your report is being send."), maximum=100, parent=self)
self.progress.ShowModal() self.progress.ShowModal()

View File

@ -19,10 +19,10 @@ log = logging.getLogger("main")
log.debug("Logger initialized. Saving debug to {0}".format(storage.data_directory,)) log.debug("Logger initialized. Saving debug to {0}".format(storage.data_directory,))
log.debug("Using Python version {0}".format(sys.version,)) log.debug("Using Python version {0}".format(sys.version,))
if sys.version[0] == "2": if sys.version[0] == "2":
if hasattr(sys, "frozen"): if hasattr(sys, "frozen"):
log.debug("Applying fixes for Python 2 frozen executables.") log.debug("Applying fixes for Python 2 frozen executables.")
import fixes import fixes
fixes.setup() fixes.setup()
import i18n import i18n
i18n.setup() i18n.setup()
config.setup() config.setup()
@ -31,12 +31,12 @@ import widgetUtils
import paths import paths
def setup(): def setup():
log.debug("Starting music-dl %s" % (application.version,)) log.debug("Starting music-dl %s" % (application.version,))
log.debug("Application path is %s" % (paths.app_path(),)) log.debug("Application path is %s" % (paths.app_path(),))
from controller import mainController from controller import mainController
app = widgetUtils.mainLoopObject() app = widgetUtils.mainLoopObject()
log.debug("Created Application mainloop object") log.debug("Created Application mainloop object")
r = mainController.Controller() r = mainController.Controller()
app.run() app.run()
setup() setup()

View File

@ -13,58 +13,58 @@ directory = None
fsencoding = sys.getfilesystemencoding() fsencoding = sys.getfilesystemencoding()
if len(glob.glob("Uninstall.exe")) > 0: # installed copy if len(glob.glob("Uninstall.exe")) > 0: # installed copy
mode= "installed" mode= "installed"
def app_path(): def app_path():
return paths_.app_path() return paths_.app_path()
def config_path(): def config_path():
global mode, directory global mode, directory
if mode == "portable": if mode == "portable":
if directory != None: path = os.path.join(directory, "config") if directory != None: path = os.path.join(directory, "config")
elif directory == None: path = os.path.join(app_path(), "config") elif directory == None: path = os.path.join(app_path(), "config")
elif mode == "installed": elif mode == "installed":
path = os.path.join(data_path(), "config") path = os.path.join(data_path(), "config")
if not os.path.exists(path): if not os.path.exists(path):
# log.debug("%s path does not exist, creating..." % (path,)) # log.debug("%s path does not exist, creating..." % (path,))
os.mkdir(path) os.mkdir(path)
return path return path
def logs_path(): def logs_path():
global mode, directory global mode, directory
if mode == "portable": if mode == "portable":
if directory != None: path = os.path.join(directory, "logs") if directory != None: path = os.path.join(directory, "logs")
elif directory == None: path = os.path.join(app_path(), "logs") elif directory == None: path = os.path.join(app_path(), "logs")
elif mode == "installed": elif mode == "installed":
path = os.path.join(data_path(), "logs") path = os.path.join(data_path(), "logs")
if not os.path.exists(path): if not os.path.exists(path):
# log.debug("%s path does not exist, creating..." % (path,)) # log.debug("%s path does not exist, creating..." % (path,))
os.mkdir(path) os.mkdir(path)
return path return path
def data_path(app_name='socializer'): def data_path(app_name='socializer'):
if platform.system() == "Windows": if platform.system() == "Windows":
data_path = os.path.join(os.getenv("AppData"), app_name) data_path = os.path.join(os.getenv("AppData"), app_name)
else: else:
data_path = os.path.join(os.environ['HOME'], ".%s" % app_name) data_path = os.path.join(os.environ['HOME'], ".%s" % app_name)
if not os.path.exists(data_path): if not os.path.exists(data_path):
os.mkdir(data_path) os.mkdir(data_path)
return data_path return data_path
def locale_path(): def locale_path():
return os.path.join(app_path(), "locales") return os.path.join(app_path(), "locales")
def sound_path(): def sound_path():
return os.path.join(app_path(), "sounds") return os.path.join(app_path(), "sounds")
def com_path(): def com_path():
global mode, directory global mode, directory
if mode == "portable": if mode == "portable":
if directory != None: path = os.path.join(directory, "com_cache") if directory != None: path = os.path.join(directory, "com_cache")
elif directory == None: path = os.path.join(app_path(), "com_cache") elif directory == None: path = os.path.join(app_path(), "com_cache")
elif mode == "installed": elif mode == "installed":
path = os.path.join(data_path(), "com_cache") path = os.path.join(data_path(), "com_cache")
if not os.path.exists(path): if not os.path.exists(path):
# log.debug("%s path does not exist, creating..." % (path,)) # log.debug("%s path does not exist, creating..." % (path,))
os.mkdir(path) os.mkdir(path)
return path return path

View File

@ -15,4 +15,4 @@ for t in testmodules:
# else, just load all the test cases from the module. # else, just load all the test cases from the module.
suite.addTest(unittest.defaultTestLoader.loadTestsFromName(t)) suite.addTest(unittest.defaultTestLoader.loadTestsFromName(t))
unittest.TextTestRunner().run(suite) unittest.TextTestRunner().run(suite)

View File

@ -10,168 +10,168 @@ from .import base
log = logging.getLogger("extractors.funkwhale") log = logging.getLogger("extractors.funkwhale")
class interface(base.baseInterface): class interface(base.baseInterface):
name = "Funkwhale" name = "Funkwhale"
enabled = config.app["services"]["funkwhale"].get("enabled") enabled = config.app["services"]["funkwhale"].get("enabled")
# This should not be enabled if credentials are not in config. # This should not be enabled if credentials are not in config.
if config.app["services"]["funkwhale"]["username"] == "" or config.app["services"]["funkwhale"]["password"] == "": if config.app["services"]["funkwhale"]["username"] == "" or config.app["services"]["funkwhale"]["password"] == "":
enabled = False enabled = False
def __init__(self): def __init__(self):
super(interface, self).__init__() super(interface, self).__init__()
self.setup() self.setup()
def setup(self): def setup(self):
endpoint = config.app["services"]["funkwhale"]["endpoint"] endpoint = config.app["services"]["funkwhale"]["endpoint"]
username = config.app["services"]["funkwhale"]["username"] username = config.app["services"]["funkwhale"]["username"]
password = config.app["services"]["funkwhale"]["password"] password = config.app["services"]["funkwhale"]["password"]
self.session = session.Session(instance_endpoint=endpoint, username=username, password=password) self.session = session.Session(instance_endpoint=endpoint, username=username, password=password)
self.session.login() self.session.login()
self.api = self.session.get_api() self.api = self.session.get_api()
def get_file_format(self): def get_file_format(self):
self.file_extension = "flac" self.file_extension = "flac"
return self.file_extension return self.file_extension
def transcoder_enabled(self): def transcoder_enabled(self):
return False return False
def search(self, text, page=1): def search(self, text, page=1):
if text == "" or text == None: if text == "" or text == None:
raise ValueError("Text must be passed and should not be blank.") raise ValueError("Text must be passed and should not be blank.")
log.debug("Retrieving data from Tidal...") log.debug("Retrieving data from Tidal...")
fieldtypes = ["artist", "album", "playlist"] fieldtypes = ["artist", "album", "playlist"]
field = "track" field = "track"
for i in fieldtypes: for i in fieldtypes:
if text.startswith(i+"://"): if text.startswith(i+"://"):
field = i field = i
text = text.replace(i+"://", "") text = text.replace(i+"://", "")
log.debug("Searching for %s..." % (field)) log.debug("Searching for %s..." % (field))
search_response = self.session.search(value=text, field=field) search_response = self.session.search(value=text, field=field)
self.results = [] self.results = []
if field == "track": if field == "track":
data = search_response.tracks data = search_response.tracks
elif field == "artist": elif field == "artist":
data = [] data = []
artist = search_response.artists[0].id artist = search_response.artists[0].id
if config.app["services"]["tidal"]["include_albums"]: if config.app["services"]["tidal"]["include_albums"]:
albums = self.session.get_artist_albums(artist) albums = self.session.get_artist_albums(artist)
for album in albums: for album in albums:
tracks = self.session.get_album_tracks(album.id) tracks = self.session.get_album_tracks(album.id)
for track in tracks: for track in tracks:
data.append(track) data.append(track)
if config.app["services"]["tidal"]["include_compilations"]: if config.app["services"]["tidal"]["include_compilations"]:
compilations = self.session.get_artist_albums_other(artist) compilations = self.session.get_artist_albums_other(artist)
for album in compilations: for album in compilations:
tracks = self.session.get_album_tracks(album.id) tracks = self.session.get_album_tracks(album.id)
for track in tracks: for track in tracks:
data.append(track) data.append(track)
if config.app["services"]["tidal"]["include_singles"]: if config.app["services"]["tidal"]["include_singles"]:
singles = self.session.get_artist_albums_ep_singles(artist) singles = self.session.get_artist_albums_ep_singles(artist)
for album in singles: for album in singles:
tracks = self.session.get_album_tracks(album.id) tracks = self.session.get_album_tracks(album.id)
for track in tracks: for track in tracks:
track.single = True track.single = True
data.append(track) data.append(track)
for search_result in data: for search_result in data:
s = base.song(self) s = base.song(self)
if not hasattr(search_result, "single"): if not hasattr(search_result, "single"):
s.title = "{0}. {1}".format(self.format_number(search_result.track_num), search_result.name) s.title = "{0}. {1}".format(self.format_number(search_result.track_num), search_result.name)
else: else:
s.title = search_result.name s.title = search_result.name
s.artist = search_result.artist.name s.artist = search_result.artist.name
s.duration = seconds_to_string(search_result.duration) s.duration = seconds_to_string(search_result.duration)
s.url = search_result.id s.url = search_result.id
s.info = search_result s.info = search_result
self.results.append(s) self.results.append(s)
log.debug("{0} results found.".format(len(self.results))) log.debug("{0} results found.".format(len(self.results)))
def format_number(self, number): def format_number(self, number):
if number < 10: if number < 10:
return "0%d" % (number) return "0%d" % (number)
else: else:
return number return number
def get_download_url(self, url): def get_download_url(self, url):
url = self.session.get_media_url(url) url = self.session.get_media_url(url)
if url.startswith("https://") or url.startswith("http://") == False: if url.startswith("https://") or url.startswith("http://") == False:
url = "rtmp://"+url url = "rtmp://"+url
return url return url
def format_track(self, item): def format_track(self, item):
return "{title}. {artist}. {duration}".format(title=item.title, duration=item.duration, artist=item.artist) return "{title}. {artist}. {duration}".format(title=item.title, duration=item.duration, artist=item.artist)
class settings(base.baseSettings): class settings(base.baseSettings):
name = _("Tidal") name = _("Tidal")
config_section = "tidal" config_section = "tidal"
def get_quality_list(self): def get_quality_list(self):
results = dict(low=_("Low"), high=_("High"), lossless=_("Lossless")) results = dict(low=_("Low"), high=_("High"), lossless=_("Lossless"))
return results return results
def get_quality_value(self, *args, **kwargs): def get_quality_value(self, *args, **kwargs):
q = self.get_quality_list() q = self.get_quality_list()
for i in q.keys(): for i in q.keys():
if q.get(i) == self.quality.GetStringSelection(): if q.get(i) == self.quality.GetStringSelection():
return i return i
def set_quality_value(self, value, *args, **kwargs): def set_quality_value(self, value, *args, **kwargs):
q = self.get_quality_list() q = self.get_quality_list()
for i in q.keys(): for i in q.keys():
if i == value: if i == value:
self.quality.SetStringSelection(q.get(i)) self.quality.SetStringSelection(q.get(i))
break break
def __init__(self, parent): def __init__(self, parent):
super(settings, self).__init__(parent=parent) super(settings, self).__init__(parent=parent)
sizer = wx.BoxSizer(wx.VERTICAL) sizer = wx.BoxSizer(wx.VERTICAL)
self.enabled = wx.CheckBox(self, wx.NewId(), _("Enable this service")) self.enabled = wx.CheckBox(self, wx.NewId(), _("Enable this service"))
self.enabled.Bind(wx.EVT_CHECKBOX, self.on_enabled) self.enabled.Bind(wx.EVT_CHECKBOX, self.on_enabled)
self.map.append(("enabled", self.enabled)) self.map.append(("enabled", self.enabled))
sizer.Add(self.enabled, 0, wx.ALL, 5) sizer.Add(self.enabled, 0, wx.ALL, 5)
username = wx.StaticText(self, wx.NewId(), _("Tidal username or email address")) username = wx.StaticText(self, wx.NewId(), _("Tidal username or email address"))
self.username = wx.TextCtrl(self, wx.NewId()) self.username = wx.TextCtrl(self, wx.NewId())
usernamebox = wx.BoxSizer(wx.HORIZONTAL) usernamebox = wx.BoxSizer(wx.HORIZONTAL)
usernamebox.Add(username, 0, wx.ALL, 5) usernamebox.Add(username, 0, wx.ALL, 5)
usernamebox.Add(self.username, 0, wx.ALL, 5) usernamebox.Add(self.username, 0, wx.ALL, 5)
sizer.Add(usernamebox, 0, wx.ALL, 5) sizer.Add(usernamebox, 0, wx.ALL, 5)
self.map.append(("username", self.username)) self.map.append(("username", self.username))
password = wx.StaticText(self, wx.NewId(), _("Password")) password = wx.StaticText(self, wx.NewId(), _("Password"))
self.password = wx.TextCtrl(self, wx.NewId(), style=wx.TE_PASSWORD) self.password = wx.TextCtrl(self, wx.NewId(), style=wx.TE_PASSWORD)
passwordbox = wx.BoxSizer(wx.HORIZONTAL) passwordbox = wx.BoxSizer(wx.HORIZONTAL)
passwordbox.Add(password, 0, wx.ALL, 5) passwordbox.Add(password, 0, wx.ALL, 5)
passwordbox.Add(self.password, 0, wx.ALL, 5) passwordbox.Add(self.password, 0, wx.ALL, 5)
sizer.Add(passwordbox, 0, wx.ALL, 5) sizer.Add(passwordbox, 0, wx.ALL, 5)
self.map.append(("password", self.password)) self.map.append(("password", self.password))
self.get_account = wx.Button(self, wx.NewId(), _("You can subscribe for a tidal account here")) self.get_account = wx.Button(self, wx.NewId(), _("You can subscribe for a tidal account here"))
self.get_account.Bind(wx.EVT_BUTTON, self.on_get_account) self.get_account.Bind(wx.EVT_BUTTON, self.on_get_account)
sizer.Add(self.get_account, 0, wx.ALL, 5) sizer.Add(self.get_account, 0, wx.ALL, 5)
quality = wx.StaticText(self, wx.NewId(), _("Audio quality")) quality = wx.StaticText(self, wx.NewId(), _("Audio quality"))
self.quality = wx.ComboBox(self, wx.NewId(), choices=[i for i in self.get_quality_list().values()], value=_("High"), style=wx.CB_READONLY) self.quality = wx.ComboBox(self, wx.NewId(), choices=[i for i in self.get_quality_list().values()], value=_("High"), style=wx.CB_READONLY)
qualitybox = wx.BoxSizer(wx.HORIZONTAL) qualitybox = wx.BoxSizer(wx.HORIZONTAL)
qualitybox.Add(quality, 0, wx.ALL, 5) qualitybox.Add(quality, 0, wx.ALL, 5)
qualitybox.Add(self.quality, 0, wx.ALL, 5) qualitybox.Add(self.quality, 0, wx.ALL, 5)
sizer.Add(qualitybox, 0, wx.ALL, 5) sizer.Add(qualitybox, 0, wx.ALL, 5)
# Monkeypatch for getting the right quality value here. # Monkeypatch for getting the right quality value here.
self.quality.GetValue = self.get_quality_value self.quality.GetValue = self.get_quality_value
self.quality.SetValue = self.set_quality_value self.quality.SetValue = self.set_quality_value
self.map.append(("quality", self.quality)) self.map.append(("quality", self.quality))
include = wx.StaticBoxSizer(parent=self, orient=wx.HORIZONTAL, label=_("Search by artist")) include = wx.StaticBoxSizer(parent=self, orient=wx.HORIZONTAL, label=_("Search by artist"))
self.include_albums = wx.CheckBox(include.GetStaticBox(), wx.NewId(), _("Include albums")) self.include_albums = wx.CheckBox(include.GetStaticBox(), wx.NewId(), _("Include albums"))
self.include_compilations = wx.CheckBox(include.GetStaticBox(), wx.NewId(), _("Include compilations")) self.include_compilations = wx.CheckBox(include.GetStaticBox(), wx.NewId(), _("Include compilations"))
self.include_singles = wx.CheckBox(include.GetStaticBox(), wx.NewId(), _("Include singles")) self.include_singles = wx.CheckBox(include.GetStaticBox(), wx.NewId(), _("Include singles"))
sizer.Add(include, 0, wx.ALL, 5) sizer.Add(include, 0, wx.ALL, 5)
self.map.append(("include_albums", self.include_albums)) self.map.append(("include_albums", self.include_albums))
self.map.append(("include_compilations", self.include_compilations)) self.map.append(("include_compilations", self.include_compilations))
self.map.append(("include_singles", self.include_singles)) self.map.append(("include_singles", self.include_singles))
self.SetSizer(sizer) self.SetSizer(sizer)
def on_enabled(self, *args, **kwargs): def on_enabled(self, *args, **kwargs):
for i in self.map: for i in self.map:
if i[1] != self.enabled: if i[1] != self.enabled:
if self.enabled.GetValue() == True: if self.enabled.GetValue() == True:
i[1].Enable(True) i[1].Enable(True)
else: else:
i[1].Enable(False) i[1].Enable(False)
def on_get_account(self, *args, **kwargs): def on_get_account(self, *args, **kwargs):
webbrowser.open_new_tab("https://tidal.com") webbrowser.open_new_tab("https://tidal.com")

View File

@ -1,3 +1,3 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: UTF-8 -*- # -*- coding: UTF-8 -*-
from . import vk, youtube, zaycev, tidal from . import vk, youtube, zaycev, tidal

View File

@ -7,75 +7,75 @@ import config
log = logging.getLogger("extractors.config") log = logging.getLogger("extractors.config")
class baseInterface(object): class baseInterface(object):
name = "base" name = "base"
enabled = False enabled = False
needs_transcode = False needs_transcode = False
results = [] results = []
def __init__(self): def __init__(self):
super(baseInterface, self).__init__() super(baseInterface, self).__init__()
log.debug("started extraction service for {0}".format(self.name,)) log.debug("started extraction service for {0}".format(self.name,))
def search(self, text, *args, **kwargs): def search(self, text, *args, **kwargs):
raise NotImplementedError() raise NotImplementedError()
def get_download_url(self, url): def get_download_url(self, url):
raise NotImplementedError() raise NotImplementedError()
def format_track(self, item): def format_track(self, item):
raise NotImplementedError() raise NotImplementedError()
def get_file_format(self): def get_file_format(self):
return "mp3" return "mp3"
def transcoder_enabled(self): def transcoder_enabled(self):
return False return False
def get_metadata(self, item): def get_metadata(self, item):
data = dict() data = dict()
keys = ["title", "album", "artist", "tracknumber"] keys = ["title", "album", "artist", "tracknumber"]
for k in keys: for k in keys:
if hasattr(item, k): if hasattr(item, k):
data[k] = getattr(item, k) data[k] = getattr(item, k)
return data return data
class song(object): class song(object):
""" Represents a song in all services. Data will be filled by the service itself""" """ Represents a song in all services. Data will be filled by the service itself"""
def __init__(self, extractor): def __init__(self, extractor):
self.extractor = extractor self.extractor = extractor
self.bitrate = 0 self.bitrate = 0
self.title = "" self.title = ""
self.artist = "" self.artist = ""
self.duration = "" self.duration = ""
self.size = 0 self.size = 0
self.url = "" self.url = ""
self.download_url = "" self.download_url = ""
self.info = None self.info = None
def format_track(self): def format_track(self):
return self.extractor.format_track(self) return self.extractor.format_track(self)
def get_download_url(self): def get_download_url(self):
self.download_url = self.extractor.get_download_url(self.url) self.download_url = self.extractor.get_download_url(self.url)
def get_metadata(self): def get_metadata(self):
return self.extractor.get_metadata(self) return self.extractor.get_metadata(self)
class baseSettings(wx.Panel): class baseSettings(wx.Panel):
config_section = "base" config_section = "base"
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(baseSettings, self).__init__(*args, **kwargs) super(baseSettings, self).__init__(*args, **kwargs)
self.map = [] self.map = []
def save(self): def save(self):
for i in self.map: for i in self.map:
config.app["services"][self.config_section][i[0]] = i[1].GetValue() config.app["services"][self.config_section][i[0]] = i[1].GetValue()
def load(self): def load(self):
for i in self.map: for i in self.map:
if i[0] in config.app["services"][self.config_section]: if i[0] in config.app["services"][self.config_section]:
i[1].SetValue(config.app["services"][self.config_section][i[0]]) i[1].SetValue(config.app["services"][self.config_section][i[0]])
else: else:
log.error("No key available: {key} on extractor {extractor}".format(key=i[0], extractor=self.config_section)) log.error("No key available: {key} on extractor {extractor}".format(key=i[0], extractor=self.config_section))

View File

@ -15,221 +15,221 @@ from .import base
log = logging.getLogger("services.tidal") log = logging.getLogger("services.tidal")
class interface(base.baseInterface): class interface(base.baseInterface):
name = "tidal" name = "tidal"
if config.app != None: # Workaround for cx_freeze 6.2 in python 3.7. if config.app != None: # Workaround for cx_freeze 6.2 in python 3.7.
enabled = config.app["services"]["tidal"].get("enabled") enabled = config.app["services"]["tidal"].get("enabled")
# This should not be enabled if credentials are not set in config. # This should not be enabled if credentials are not set in config.
if config.app["services"]["tidal"]["username"] == "" or config.app["services"]["tidal"]["password"] == "": if config.app["services"]["tidal"]["username"] == "" or config.app["services"]["tidal"]["password"] == "":
enabled = False enabled = False
else: else:
enabled = False enabled = False
def __init__(self): def __init__(self):
super(interface, self).__init__() super(interface, self).__init__()
self.setup() self.setup()
def setup(self): def setup(self):
# Assign quality or switch to high if not specified/not found. # Assign quality or switch to high if not specified/not found.
if hasattr(tidalapi.Quality, config.app["services"]["tidal"]["quality"]): if hasattr(tidalapi.Quality, config.app["services"]["tidal"]["quality"]):
quality = getattr(tidalapi.Quality, config.app["services"]["tidal"]["quality"]) quality = getattr(tidalapi.Quality, config.app["services"]["tidal"]["quality"])
else: else:
quality = tidalapi.Quality.high quality = tidalapi.Quality.high
# We need to instantiate a config object to pass quality settings. # We need to instantiate a config object to pass quality settings.
_config = tidalapi.Config(quality=quality) _config = tidalapi.Config(quality=quality)
username = config.app["services"]["tidal"]["username"] username = config.app["services"]["tidal"]["username"]
password = config.app["services"]["tidal"]["password"] password = config.app["services"]["tidal"]["password"]
log.debug("Using quality: %s" % (quality,)) log.debug("Using quality: %s" % (quality,))
self.session = tidalapi.Session(config=_config) self.session = tidalapi.Session(config=_config)
self.session.login(username=username, password=password) self.session.login(username=username, password=password)
def get_file_format(self): def get_file_format(self):
""" Returns the file format (mp3 or flac) depending in quality set. """ """ Returns the file format (mp3 or flac) depending in quality set. """
if config.app["services"]["tidal"]["quality"] == "lossless": if config.app["services"]["tidal"]["quality"] == "lossless":
self.file_extension = "flac" self.file_extension = "flac"
elif config.app["services"]["tidal"]["avoid_transcoding"] == True: elif config.app["services"]["tidal"]["avoid_transcoding"] == True:
self.file_extension = "m4a" self.file_extension = "m4a"
else: else:
self.file_extension = "mp3" self.file_extension = "mp3"
return self.file_extension return self.file_extension
def transcoder_enabled(self): def transcoder_enabled(self):
# If quality is set to high, tidal returns audio in AAC format at 256 KBPS. So we convert it with vlc to mp3 at 320KBPS. # If quality is set to high, tidal returns audio in AAC format at 256 KBPS. So we convert it with vlc to mp3 at 320KBPS.
# toDo: Shall this be a setting and allow MusicDL to spit out the m4a file directly? # toDo: Shall this be a setting and allow MusicDL to spit out the m4a file directly?
if config.app["services"]["tidal"]["quality"] == "lossless": if config.app["services"]["tidal"]["quality"] == "lossless":
return False return False
elif config.app["services"]["tidal"]["avoid_transcoding"]: elif config.app["services"]["tidal"]["avoid_transcoding"]:
return False return False
else: else:
return True return True
def search(self, text, page=1): def search(self, text, page=1):
if text == "" or text == None: if text == "" or text == None:
raise ValueError("Text must be passed and should not be blank.") raise ValueError("Text must be passed and should not be blank.")
log.debug("Retrieving data from Tidal...") log.debug("Retrieving data from Tidal...")
# Check for top:// protocol. # Check for top:// protocol.
if text.startswith("top://"): if text.startswith("top://"):
text = text.replace("top://", "") text = text.replace("top://", "")
return self.search_for_top(text) return self.search_for_top(text)
fieldtypes = ["artist", "album", "playlist"] fieldtypes = ["artist", "album", "playlist"]
field = "track" field = "track"
for i in fieldtypes: for i in fieldtypes:
if text.startswith(i+"://"): if text.startswith(i+"://"):
field = i field = i
text = text.replace(i+"://", "") text = text.replace(i+"://", "")
log.debug("Searching for %s..." % (field)) log.debug("Searching for %s..." % (field))
search_response = self.session.search(value=text, field=field) search_response = self.session.search(value=text, field=field)
self.results = [] self.results = []
if field == "track": if field == "track":
data = search_response.tracks data = search_response.tracks
elif field == "artist": elif field == "artist":
data = [] data = []
artist = search_response.artists[0].id artist = search_response.artists[0].id
if config.app["services"]["tidal"]["include_albums"]: if config.app["services"]["tidal"]["include_albums"]:
albums = self.session.get_artist_albums(artist) albums = self.session.get_artist_albums(artist)
for album in albums: for album in albums:
tracks = self.session.get_album_tracks(album.id) tracks = self.session.get_album_tracks(album.id)
for track in tracks: for track in tracks:
track.album = album track.album = album
data.append(track) data.append(track)
if config.app["services"]["tidal"]["include_compilations"]: if config.app["services"]["tidal"]["include_compilations"]:
compilations = self.session.get_artist_albums_other(artist) compilations = self.session.get_artist_albums_other(artist)
for album in compilations: for album in compilations:
tracks = self.session.get_album_tracks(album.id) tracks = self.session.get_album_tracks(album.id)
for track in tracks: for track in tracks:
data.append(track) data.append(track)
if config.app["services"]["tidal"]["include_singles"]: if config.app["services"]["tidal"]["include_singles"]:
singles = self.session.get_artist_albums_ep_singles(artist) singles = self.session.get_artist_albums_ep_singles(artist)
for album in singles: for album in singles:
tracks = self.session.get_album_tracks(album.id) tracks = self.session.get_album_tracks(album.id)
for track in tracks: for track in tracks:
# track.single = True # track.single = True
data.append(track) data.append(track)
for search_result in data: for search_result in data:
s = base.song(self) s = base.song(self)
s.title = search_result.name s.title = search_result.name
s.artist = search_result.artist.name s.artist = search_result.artist.name
s.duration = seconds_to_string(search_result.duration) s.duration = seconds_to_string(search_result.duration)
s.url = search_result.id s.url = search_result.id
s.tracknumber = str(search_result.track_num) s.tracknumber = str(search_result.track_num)
s.album = search_result.album.name s.album = search_result.album.name
if search_result.album.num_tracks == None: if search_result.album.num_tracks == None:
s.single = True s.single = True
s.info = search_result s.info = search_result
self.results.append(s) self.results.append(s)
log.debug("{0} results found.".format(len(self.results))) log.debug("{0} results found.".format(len(self.results)))
def search_for_top(self, artist): def search_for_top(self, artist):
search_response = self.session.search(value=artist, field="artist") search_response = self.session.search(value=artist, field="artist")
self.results = [] self.results = []
artist = search_response.artists[0].id artist = search_response.artists[0].id
results = self.session.get_artist_top_tracks(artist) results = self.session.get_artist_top_tracks(artist)
for search_result in results: for search_result in results:
s = base.song(self) s = base.song(self)
s.title = search_result.name s.title = search_result.name
s.artist = search_result.artist.name s.artist = search_result.artist.name
s.duration = seconds_to_string(search_result.duration) s.duration = seconds_to_string(search_result.duration)
s.url = search_result.id s.url = search_result.id
s.tracknumber = str(search_result.track_num) s.tracknumber = str(search_result.track_num)
s.album = search_result.album.name s.album = search_result.album.name
if search_result.album.num_tracks == None: if search_result.album.num_tracks == None:
s.single = True s.single = True
s.info = search_result s.info = search_result
self.results.append(s) self.results.append(s)
log.debug("{0} results found.".format(len(self.results))) log.debug("{0} results found.".format(len(self.results)))
def format_number(self, number): def format_number(self, number):
if number < 10: if number < 10:
return "0%d" % (number) return "0%d" % (number)
else: else:
return number return number
def get_download_url(self, url): def get_download_url(self, url):
url = self.session.get_media_url(url) url = self.session.get_media_url(url)
if url.startswith("https://") or url.startswith("http://") == False: if url.startswith("https://") or url.startswith("http://") == False:
url = "rtmp://"+url url = "rtmp://"+url
return url return url
def format_track(self, item): def format_track(self, item):
if not hasattr(item, "single"): if not hasattr(item, "single"):
return "{0}. {1}".format(self.format_number(int(item.tracknumber)), item.title) return "{0}. {1}".format(self.format_number(int(item.tracknumber)), item.title)
else: else:
return "{title}. {artist}. {duration}".format(title=item.title, duration=item.duration, artist=item.artist) return "{title}. {artist}. {duration}".format(title=item.title, duration=item.duration, artist=item.artist)
class settings(base.baseSettings): class settings(base.baseSettings):
name = _("Tidal") name = _("Tidal")
config_section = "tidal" config_section = "tidal"
def get_quality_list(self): def get_quality_list(self):
results = dict(low=_("Low"), high=_("High"), lossless=_("Lossless")) results = dict(low=_("Low"), high=_("High"), lossless=_("Lossless"))
return results return results
def get_quality_value(self, *args, **kwargs): def get_quality_value(self, *args, **kwargs):
q = self.get_quality_list() q = self.get_quality_list()
for i in q.keys(): for i in q.keys():
if q.get(i) == self.quality.GetStringSelection(): if q.get(i) == self.quality.GetStringSelection():
return i return i
def set_quality_value(self, value, *args, **kwargs): def set_quality_value(self, value, *args, **kwargs):
q = self.get_quality_list() q = self.get_quality_list()
for i in q.keys(): for i in q.keys():
if i == value: if i == value:
self.quality.SetStringSelection(q.get(i)) self.quality.SetStringSelection(q.get(i))
break break
def __init__(self, parent): def __init__(self, parent):
super(settings, self).__init__(parent=parent) super(settings, self).__init__(parent=parent)
sizer = wx.BoxSizer(wx.VERTICAL) sizer = wx.BoxSizer(wx.VERTICAL)
self.enabled = wx.CheckBox(self, wx.NewId(), _("Enable this service")) self.enabled = wx.CheckBox(self, wx.NewId(), _("Enable this service"))
self.enabled.Bind(wx.EVT_CHECKBOX, self.on_enabled) self.enabled.Bind(wx.EVT_CHECKBOX, self.on_enabled)
self.map.append(("enabled", self.enabled)) self.map.append(("enabled", self.enabled))
sizer.Add(self.enabled, 0, wx.ALL, 5) sizer.Add(self.enabled, 0, wx.ALL, 5)
self.avoid_transcoding = wx.CheckBox(self, wx.NewId(), _("Avoid transcoding when downloading")) self.avoid_transcoding = wx.CheckBox(self, wx.NewId(), _("Avoid transcoding when downloading"))
self.map.append(("avoid_transcoding", self.avoid_transcoding)) self.map.append(("avoid_transcoding", self.avoid_transcoding))
sizer.Add(self.avoid_transcoding, 0, wx.ALL, 5) sizer.Add(self.avoid_transcoding, 0, wx.ALL, 5)
username = wx.StaticText(self, wx.NewId(), _("Tidal username or email address")) username = wx.StaticText(self, wx.NewId(), _("Tidal username or email address"))
self.username = wx.TextCtrl(self, wx.NewId()) self.username = wx.TextCtrl(self, wx.NewId())
usernamebox = wx.BoxSizer(wx.HORIZONTAL) usernamebox = wx.BoxSizer(wx.HORIZONTAL)
usernamebox.Add(username, 0, wx.ALL, 5) usernamebox.Add(username, 0, wx.ALL, 5)
usernamebox.Add(self.username, 0, wx.ALL, 5) usernamebox.Add(self.username, 0, wx.ALL, 5)
sizer.Add(usernamebox, 0, wx.ALL, 5) sizer.Add(usernamebox, 0, wx.ALL, 5)
self.map.append(("username", self.username)) self.map.append(("username", self.username))
password = wx.StaticText(self, wx.NewId(), _("Password")) password = wx.StaticText(self, wx.NewId(), _("Password"))
self.password = wx.TextCtrl(self, wx.NewId(), style=wx.TE_PASSWORD) self.password = wx.TextCtrl(self, wx.NewId(), style=wx.TE_PASSWORD)
passwordbox = wx.BoxSizer(wx.HORIZONTAL) passwordbox = wx.BoxSizer(wx.HORIZONTAL)
passwordbox.Add(password, 0, wx.ALL, 5) passwordbox.Add(password, 0, wx.ALL, 5)
passwordbox.Add(self.password, 0, wx.ALL, 5) passwordbox.Add(self.password, 0, wx.ALL, 5)
sizer.Add(passwordbox, 0, wx.ALL, 5) sizer.Add(passwordbox, 0, wx.ALL, 5)
self.map.append(("password", self.password)) self.map.append(("password", self.password))
self.get_account = wx.Button(self, wx.NewId(), _("You can subscribe for a tidal account here")) self.get_account = wx.Button(self, wx.NewId(), _("You can subscribe for a tidal account here"))
self.get_account.Bind(wx.EVT_BUTTON, self.on_get_account) self.get_account.Bind(wx.EVT_BUTTON, self.on_get_account)
sizer.Add(self.get_account, 0, wx.ALL, 5) sizer.Add(self.get_account, 0, wx.ALL, 5)
quality = wx.StaticText(self, wx.NewId(), _("Audio quality")) quality = wx.StaticText(self, wx.NewId(), _("Audio quality"))
self.quality = wx.ComboBox(self, wx.NewId(), choices=[i for i in self.get_quality_list().values()], value=_("High"), style=wx.CB_READONLY) self.quality = wx.ComboBox(self, wx.NewId(), choices=[i for i in self.get_quality_list().values()], value=_("High"), style=wx.CB_READONLY)
qualitybox = wx.BoxSizer(wx.HORIZONTAL) qualitybox = wx.BoxSizer(wx.HORIZONTAL)
qualitybox.Add(quality, 0, wx.ALL, 5) qualitybox.Add(quality, 0, wx.ALL, 5)
qualitybox.Add(self.quality, 0, wx.ALL, 5) qualitybox.Add(self.quality, 0, wx.ALL, 5)
sizer.Add(qualitybox, 0, wx.ALL, 5) sizer.Add(qualitybox, 0, wx.ALL, 5)
# Monkeypatch for getting the right quality value here. # Monkeypatch for getting the right quality value here.
self.quality.GetValue = self.get_quality_value self.quality.GetValue = self.get_quality_value
self.quality.SetValue = self.set_quality_value self.quality.SetValue = self.set_quality_value
self.map.append(("quality", self.quality)) self.map.append(("quality", self.quality))
include = wx.StaticBoxSizer(parent=self, orient=wx.HORIZONTAL, label=_("Search by artist")) include = wx.StaticBoxSizer(parent=self, orient=wx.HORIZONTAL, label=_("Search by artist"))
self.include_albums = wx.CheckBox(include.GetStaticBox(), wx.NewId(), _("Include albums")) self.include_albums = wx.CheckBox(include.GetStaticBox(), wx.NewId(), _("Include albums"))
self.include_compilations = wx.CheckBox(include.GetStaticBox(), wx.NewId(), _("Include compilations")) self.include_compilations = wx.CheckBox(include.GetStaticBox(), wx.NewId(), _("Include compilations"))
self.include_singles = wx.CheckBox(include.GetStaticBox(), wx.NewId(), _("Include singles")) self.include_singles = wx.CheckBox(include.GetStaticBox(), wx.NewId(), _("Include singles"))
sizer.Add(include, 0, wx.ALL, 5) sizer.Add(include, 0, wx.ALL, 5)
self.map.append(("include_albums", self.include_albums)) self.map.append(("include_albums", self.include_albums))
self.map.append(("include_compilations", self.include_compilations)) self.map.append(("include_compilations", self.include_compilations))
self.map.append(("include_singles", self.include_singles)) self.map.append(("include_singles", self.include_singles))
self.SetSizer(sizer) self.SetSizer(sizer)
def on_enabled(self, *args, **kwargs): def on_enabled(self, *args, **kwargs):
for i in self.map: for i in self.map:
if i[1] != self.enabled: if i[1] != self.enabled:
if self.enabled.GetValue() == True: if self.enabled.GetValue() == True:
i[1].Enable(True) i[1].Enable(True)
else: else:
i[1].Enable(False) i[1].Enable(False)
def on_get_account(self, *args, **kwargs): def on_get_account(self, *args, **kwargs):
webbrowser.open_new_tab("https://tidal.com") webbrowser.open_new_tab("https://tidal.com")

View File

@ -16,81 +16,81 @@ application_name = "music_dl"
access_token = "e2237f17af545a4ba0bf6cb0b1a662e6" access_token = "e2237f17af545a4ba0bf6cb0b1a662e6"
class interface(base.baseInterface): class interface(base.baseInterface):
""" Class downloader for VK audios. """ """ Class downloader for VK audios. """
name = "vk" name = "vk"
if config.app != None: # Workaround for cx_freeze 6.2 in python 3.7. if config.app != None: # Workaround for cx_freeze 6.2 in python 3.7.
enabled = config.app["services"]["vk"].get("enabled") enabled = config.app["services"]["vk"].get("enabled")
else: else:
enabled = False enabled = False
#util functions. #util functions.
def get_auth(self): def get_auth(self):
# Authentication object # Authentication object
self.auth=HTTPBasicAuth(application_name, access_token) self.auth=HTTPBasicAuth(application_name, access_token)
def get(self, endpoint, *args, **kwargs): def get(self, endpoint, *args, **kwargs):
response = requests.get(url+endpoint, auth=self.auth, *args, **kwargs) response = requests.get(url+endpoint, auth=self.auth, *args, **kwargs)
return response return response
def __init__(self): def __init__(self):
super(interface, self).__init__() super(interface, self).__init__()
self.get_auth() self.get_auth()
def get_file_format(self): def get_file_format(self):
# Only mp3 audio is supported in VK so return it without further checks. # Only mp3 audio is supported in VK so return it without further checks.
return "mp3" return "mp3"
def transcoder_enabled(self): def transcoder_enabled(self):
return False return False
def search(self, text): def search(self, text):
if text == "" or text == None: if text == "" or text == None:
raise ValueError("Text must be passed and should not be blank.") raise ValueError("Text must be passed and should not be blank.")
log.debug("Retrieving data from vk...") log.debug("Retrieving data from vk...")
self.results = [] self.results = []
results = self.get("/vk/search", params=dict(text=text, maxresults=config.app["services"]["vk"]["max_results"])) results = self.get("/vk/search", params=dict(text=text, maxresults=config.app["services"]["vk"]["max_results"]))
if results.status_code != 200: if results.status_code != 200:
return return
results = results.json() results = results.json()
for search_result in results: for search_result in results:
s = base.song(self) s = base.song(self)
s.title = search_result["title"] s.title = search_result["title"]
s.artist = search_result["artist"] s.artist = search_result["artist"]
s.duration = seconds_to_string(search_result["duration"]) s.duration = seconds_to_string(search_result["duration"])
s.url = search_result["url"] s.url = search_result["url"]
s.info = search_result s.info = search_result
self.results.append(s) self.results.append(s)
def get_download_url(self, file_url): def get_download_url(self, file_url):
return "{url}/vk/download/?url={url2}".format(url=url, url2=file_url) return "{url}/vk/download/?url={url2}".format(url=url, url2=file_url)
def format_track(self, item): def format_track(self, item):
return "{title}. {artist}. {duration}".format(title=item.title, duration=item.duration, artist=item.artist) return "{title}. {artist}. {duration}".format(title=item.title, duration=item.duration, artist=item.artist)
class settings(base.baseSettings): class settings(base.baseSettings):
name = _("VK") name = _("VK")
config_section = "vk" config_section = "vk"
def __init__(self, parent): def __init__(self, parent):
super(settings, self).__init__(parent=parent) super(settings, self).__init__(parent=parent)
sizer = wx.BoxSizer(wx.VERTICAL) sizer = wx.BoxSizer(wx.VERTICAL)
self.enabled = wx.CheckBox(self, wx.NewId(), _("Enable this service")) self.enabled = wx.CheckBox(self, wx.NewId(), _("Enable this service"))
self.enabled.Bind(wx.EVT_CHECKBOX, self.on_enabled) self.enabled.Bind(wx.EVT_CHECKBOX, self.on_enabled)
self.map.append(("enabled", self.enabled)) self.map.append(("enabled", self.enabled))
sizer.Add(self.enabled, 0, wx.ALL, 5) sizer.Add(self.enabled, 0, wx.ALL, 5)
max_results_label = wx.StaticText(self, wx.NewId(), _("Max results per page")) max_results_label = wx.StaticText(self, wx.NewId(), _("Max results per page"))
self.max_results = wx.SpinCtrl(self, wx.NewId()) self.max_results = wx.SpinCtrl(self, wx.NewId())
self.max_results.SetRange(1, 300) self.max_results.SetRange(1, 300)
max_results_sizer = wx.BoxSizer(wx.HORIZONTAL) max_results_sizer = wx.BoxSizer(wx.HORIZONTAL)
max_results_sizer.Add(max_results_label, 0, wx.ALL, 5) max_results_sizer.Add(max_results_label, 0, wx.ALL, 5)
max_results_sizer.Add(self.max_results, 0, wx.ALL, 5) max_results_sizer.Add(self.max_results, 0, wx.ALL, 5)
self.map.append(("max_results", self.max_results)) self.map.append(("max_results", self.max_results))
self.SetSizer(sizer) self.SetSizer(sizer)
def on_enabled(self, *args, **kwargs): def on_enabled(self, *args, **kwargs):
for i in self.map: for i in self.map:
if i[1] != self.enabled: if i[1] != self.enabled:
if self.enabled.GetValue() == True: if self.enabled.GetValue() == True:
i[1].Enable(True) i[1].Enable(True)
else: else:
i[1].Enable(False) i[1].Enable(False)

View File

@ -9,126 +9,126 @@ from .import base
log = logging.getLogger("extractors.youtube.com") log = logging.getLogger("extractors.youtube.com")
class interface(base.baseInterface): class interface(base.baseInterface):
name = "YouTube" name = "YouTube"
if config.app != None: # Workaround for cx_freeze 6.2 in python 3.7. if config.app != None: # Workaround for cx_freeze 6.2 in python 3.7.
enabled = config.app["services"]["youtube"].get("enabled") enabled = config.app["services"]["youtube"].get("enabled")
else: else:
enabled = False enabled = False
def search(self, text, page=1): def search(self, text, page=1):
if text == "" or text == None: if text == "" or text == None:
raise ValueError("Text must be passed and should not be blank.") raise ValueError("Text must be passed and should not be blank.")
if text.startswith("https") or text.startswith("http"): if text.startswith("https") or text.startswith("http"):
return self.search_from_url(text) return self.search_from_url(text)
type = "video" type = "video"
max_results = config.app["services"]["youtube"]["max_results"] max_results = config.app["services"]["youtube"]["max_results"]
log.debug("Retrieving data from Youtube...") log.debug("Retrieving data from Youtube...")
ydl = youtube_dl.YoutubeDL({'quiet': True, 'ignore_errors': True, 'no_warnings': True, 'logger': log, 'format': 'bestaudio/best', 'outtmpl': u'%(id)s%(ext)s'}) ydl = youtube_dl.YoutubeDL({'quiet': True, 'ignore_errors': True, 'no_warnings': True, 'logger': log, 'format': 'bestaudio/best', 'outtmpl': u'%(id)s%(ext)s'})
with ydl: with ydl:
search_param = "ytsearch{}:{}".format(max_results, text) search_param = "ytsearch{}:{}".format(max_results, text)
result = ydl.extract_info(search_param, download=False) result = ydl.extract_info(search_param, download=False)
self.results = [] self.results = []
for search_result in result["entries"]: for search_result in result["entries"]:
s = base.song(self) s = base.song(self)
s.title = search_result["title"] s.title = search_result["title"]
s.url = "https://www.youtube.com/watch?v="+search_result["id"] s.url = "https://www.youtube.com/watch?v="+search_result["id"]
s.duration = seconds_to_string(search_result["duration"]) s.duration = seconds_to_string(search_result["duration"])
if search_result.get("track") != None: if search_result.get("track") != None:
s.title = search_result["track"] s.title = search_result["track"]
if search_result.get("album") != None: if search_result.get("album") != None:
s.album = search_result["album"] s.album = search_result["album"]
if search_result.get("artist") != None: if search_result.get("artist") != None:
s.artist = search_result["artist"] s.artist = search_result["artist"]
self.results.append(s) self.results.append(s)
log.debug("{0} results found.".format(len(self.results))) log.debug("{0} results found.".format(len(self.results)))
def search_from_url(self, url): def search_from_url(self, url):
log.debug("Getting download URL for {0}".format(url,)) log.debug("Getting download URL for {0}".format(url,))
if "playlist?list=" in url: if "playlist?list=" in url:
return self.search_from_playlist(url) return self.search_from_playlist(url)
ydl = youtube_dl.YoutubeDL({'quiet': True, 'ignore_errors': True, 'no_warnings': True, 'logger': log, 'prefer-free-formats': True, 'format': 'bestaudio', 'outtmpl': u'%(id)s%(ext)s'}) ydl = youtube_dl.YoutubeDL({'quiet': True, 'ignore_errors': True, 'no_warnings': True, 'logger': log, 'prefer-free-formats': True, 'format': 'bestaudio', 'outtmpl': u'%(id)s%(ext)s'})
with ydl: with ydl:
result = ydl.extract_info(url, download=False) result = ydl.extract_info(url, download=False)
if 'entries' in result: if 'entries' in result:
videos = result['entries'] videos = result['entries']
else: else:
videos = [result] videos = [result]
for video in videos: for video in videos:
s = base.song(self) s = base.song(self)
s.title = video["title"] s.title = video["title"]
s.url = video["webpage_url"] # Cannot use direct URL here cause Youtube URLS expire after a minute. s.url = video["webpage_url"] # Cannot use direct URL here cause Youtube URLS expire after a minute.
s.duration = seconds_to_string(video["duration"]) s.duration = seconds_to_string(video["duration"])
self.results.append(s) self.results.append(s)
log.debug("{0} results found.".format(len(self.results))) log.debug("{0} results found.".format(len(self.results)))
def search_from_playlist(self, url): def search_from_playlist(self, url):
id = url.split("=")[1] id = url.split("=")[1]
max_results = 50 max_results = 50
log.debug("Retrieving data from Youtube...") log.debug("Retrieving data from Youtube...")
ydl = youtube_dl.YoutubeDL({'quiet': True, 'ignore_errors': True, 'no_warnings': True, 'logger': log, 'format': 'bestaudio/best', 'outtmpl': u'%(id)s%(ext)s'}) ydl = youtube_dl.YoutubeDL({'quiet': True, 'ignore_errors': True, 'no_warnings': True, 'logger': log, 'format': 'bestaudio/best', 'outtmpl': u'%(id)s%(ext)s'})
with ydl: with ydl:
result = ydl.extract_info(url, download=False) result = ydl.extract_info(url, download=False)
self.results = [] self.results = []
for search_result in result["entries"]: for search_result in result["entries"]:
s = base.song(self) s = base.song(self)
s.title = search_result["title"] s.title = search_result["title"]
s.url = "https://www.youtube.com/watch?v="+search_result["id"] s.url = "https://www.youtube.com/watch?v="+search_result["id"]
s.duration = seconds_to_string(search_result["duration"]) s.duration = seconds_to_string(search_result["duration"])
if search_result.get("track") != None: if search_result.get("track") != None:
s.title = search_result["track"] s.title = search_result["track"]
if search_result.get("album") != None: if search_result.get("album") != None:
s.album = search_result["album"] s.album = search_result["album"]
if search_result.get("artist") != None: if search_result.get("artist") != None:
s.artist = search_result["artist"] s.artist = search_result["artist"]
self.results.append(s) self.results.append(s)
log.debug("{0} results found.".format(len(self.results))) log.debug("{0} results found.".format(len(self.results)))
def get_download_url(self, url): def get_download_url(self, url):
log.debug("Getting download URL for {0}".format(url,)) log.debug("Getting download URL for {0}".format(url,))
ydl = youtube_dl.YoutubeDL({'quiet': True, 'no_warnings': True, 'logger': log, 'prefer_insecure': True, 'format': 'bestaudio/best', 'outtmpl': u'%(id)s%(ext)s'}) ydl = youtube_dl.YoutubeDL({'quiet': True, 'no_warnings': True, 'logger': log, 'prefer_insecure': True, 'format': 'bestaudio/best', 'outtmpl': u'%(id)s%(ext)s'})
with ydl: with ydl:
result = ydl.extract_info(url, download=False) result = ydl.extract_info(url, download=False)
if 'entries' in result: if 'entries' in result:
video = result['entries'][0] video = result['entries'][0]
else: else:
video = result video = result
# From here we should extract the first format so it will contain audio only. # From here we should extract the first format so it will contain audio only.
log.debug("Download URL: {0}".format(video["formats"][0]["url"],)) log.debug("Download URL: {0}".format(video["formats"][0]["url"],))
return video["formats"][0]["url"] return video["formats"][0]["url"]
def format_track(self, item): def format_track(self, item):
return "{0} {1}".format(item.title, item.duration) return "{0} {1}".format(item.title, item.duration)
def transcoder_enabled(self): def transcoder_enabled(self):
return config.app["services"]["youtube"]["transcode"] return config.app["services"]["youtube"]["transcode"]
class settings(base.baseSettings): class settings(base.baseSettings):
name = _("Youtube") name = _("Youtube")
config_section = "youtube" config_section = "youtube"
def __init__(self, parent): def __init__(self, parent):
super(settings, self).__init__(parent=parent) super(settings, self).__init__(parent=parent)
sizer = wx.BoxSizer(wx.VERTICAL) sizer = wx.BoxSizer(wx.VERTICAL)
self.enabled = wx.CheckBox(self, wx.NewId(), _("Enable this service")) self.enabled = wx.CheckBox(self, wx.NewId(), _("Enable this service"))
self.enabled.Bind(wx.EVT_CHECKBOX, self.on_enabled) self.enabled.Bind(wx.EVT_CHECKBOX, self.on_enabled)
self.map.append(("enabled", self.enabled)) self.map.append(("enabled", self.enabled))
sizer.Add(self.enabled, 0, wx.ALL, 5) sizer.Add(self.enabled, 0, wx.ALL, 5)
max_results_label = wx.StaticText(self, wx.NewId(), _("Max results per page")) max_results_label = wx.StaticText(self, wx.NewId(), _("Max results per page"))
self.max_results = wx.SpinCtrl(self, wx.NewId()) self.max_results = wx.SpinCtrl(self, wx.NewId())
self.max_results.SetRange(1, 50) self.max_results.SetRange(1, 50)
max_results_sizer = wx.BoxSizer(wx.HORIZONTAL) max_results_sizer = wx.BoxSizer(wx.HORIZONTAL)
max_results_sizer.Add(max_results_label, 0, wx.ALL, 5) max_results_sizer.Add(max_results_label, 0, wx.ALL, 5)
max_results_sizer.Add(self.max_results, 0, wx.ALL, 5) max_results_sizer.Add(self.max_results, 0, wx.ALL, 5)
self.map.append(("max_results", self.max_results)) self.map.append(("max_results", self.max_results))
# self.transcode = wx.CheckBox(self, wx.NewId(), _("Enable transcode when downloading")) # self.transcode = wx.CheckBox(self, wx.NewId(), _("Enable transcode when downloading"))
# self.map.append(("transcode", self.transcode)) # self.map.append(("transcode", self.transcode))
# sizer.Add(self.transcode, 0, wx.ALL, 5) # sizer.Add(self.transcode, 0, wx.ALL, 5)
self.SetSizer(sizer) self.SetSizer(sizer)
def on_enabled(self, *args, **kwargs): def on_enabled(self, *args, **kwargs):
for i in self.map: for i in self.map:
if i[1] != self.enabled: if i[1] != self.enabled:
if self.enabled.GetValue() == True: if self.enabled.GetValue() == True:
i[1].Enable(True) i[1].Enable(True)
else: else:
i[1].Enable(False) i[1].Enable(False)

View File

@ -12,53 +12,53 @@ from . import base
log = logging.getLogger("extractors.zaycev.net") log = logging.getLogger("extractors.zaycev.net")
class interface(base.baseInterface): class interface(base.baseInterface):
name = "zaycev.net" name = "zaycev.net"
if config.app != None: # Workaround for cx_freeze 6.2 in python 3.7. if config.app != None: # Workaround for cx_freeze 6.2 in python 3.7.
enabled = config.app["services"]["zaycev"].get("enabled") enabled = config.app["services"]["zaycev"].get("enabled")
else: else:
enabled = False enabled = False
def search(self, text, page=1): def search(self, text, page=1):
if text == "" or text == None: if text == "" or text == None:
raise ValueError("Text must be passed and should not be blank.") raise ValueError("Text must be passed and should not be blank.")
site = "http://zaycev.net/search.html?query_search=%s" % (text,) site = "http://zaycev.net/search.html?query_search=%s" % (text,)
log.debug("Retrieving data from {0}...".format(site,)) log.debug("Retrieving data from {0}...".format(site,))
r = requests.get(site) r = requests.get(site)
soup = BeautifulSoup(r.text, 'html.parser') soup = BeautifulSoup(r.text, 'html.parser')
search_results = soup.find_all("div", {"class": "musicset-track__title track-geo__title"}) search_results = soup.find_all("div", {"class": "musicset-track__title track-geo__title"})
self.results = [] self.results = []
for i in search_results: for i in search_results:
# The easiest method to get artist and song names is to fetch links. There are only two links per result here. # The easiest method to get artist and song names is to fetch links. There are only two links per result here.
data = i.find_all("a") data = i.find_all("a")
# from here, data[0] contains artist info and data[1] contains info of the retrieved song. # from here, data[0] contains artist info and data[1] contains info of the retrieved song.
s = base.song(self) s = base.song(self)
s.title = data[1].text s.title = data[1].text
s.artist = data[0].text s.artist = data[0].text
s.url = "http://zaycev.net%s" % (data[1].attrs["href"]) s.url = "http://zaycev.net%s" % (data[1].attrs["href"])
# s.duration = self.hd[i]["duration"] # s.duration = self.hd[i]["duration"]
# s.size = self.hd[i]["size"] # s.size = self.hd[i]["size"]
# s.bitrate = self.hd[i]["bitrate"] # s.bitrate = self.hd[i]["bitrate"]
self.results.append(s) self.results.append(s)
log.debug("{0} results found.".format(len(self.results))) log.debug("{0} results found.".format(len(self.results)))
def get_download_url(self, url): def get_download_url(self, url):
log.debug("Getting download URL for {0}".format(url,)) log.debug("Getting download URL for {0}".format(url,))
soups = BeautifulSoup(requests.get(url).text, 'html.parser') soups = BeautifulSoup(requests.get(url).text, 'html.parser')
data = json.loads(requests.get('http://zaycev.net' + soups.find('div', {'class':"musicset-track"}).get('data-url')).text) data = json.loads(requests.get('http://zaycev.net' + soups.find('div', {'class':"musicset-track"}).get('data-url')).text)
log.debug("Download URL: {0}".format(data["url"])) log.debug("Download URL: {0}".format(data["url"]))
return data["url"] return data["url"]
def format_track(self, item): def format_track(self, item):
return "{0}. {1}. {2}".format(item.title, item.duration, item.size) return "{0}. {1}. {2}".format(item.title, item.duration, item.size)
class settings(base.baseSettings): class settings(base.baseSettings):
name = _("zaycev.net") name = _("zaycev.net")
config_section = "zaycev" config_section = "zaycev"
def __init__(self, parent): def __init__(self, parent):
super(settings, self).__init__(parent=parent) super(settings, self).__init__(parent=parent)
sizer = wx.BoxSizer(wx.VERTICAL) sizer = wx.BoxSizer(wx.VERTICAL)
self.enabled = wx.CheckBox(self, wx.NewId(), _("Enable this service (works only in the Russian Federation)")) self.enabled = wx.CheckBox(self, wx.NewId(), _("Enable this service (works only in the Russian Federation)"))
self.map.append(("enabled", self.enabled)) self.map.append(("enabled", self.enabled))
sizer.Add(self.enabled, 0, wx.ALL, 5) sizer.Add(self.enabled, 0, wx.ALL, 5)
self.SetSizer(sizer) self.SetSizer(sizer)

View File

@ -10,23 +10,23 @@ i18n.setup()
import application import application
def find_accessible_output2_datafiles(): def find_accessible_output2_datafiles():
import accessible_output2 import accessible_output2
path = os.path.join(accessible_output2.__path__[0], 'lib') path = os.path.join(accessible_output2.__path__[0], 'lib')
dest_dir = os.path.join('accessible_output2', 'lib') dest_dir = os.path.join('accessible_output2', 'lib')
return (path, dest_dir) return (path, dest_dir)
base = None base = None
if sys.platform == 'win32': if sys.platform == 'win32':
base = 'Win32GUI' base = 'Win32GUI'
build_exe_options = dict( build_exe_options = dict(
build_exe="dist", build_exe="dist",
optimize=1, optimize=1,
include_msvcr=True, include_msvcr=True,
zip_include_packages=["accessible_output2"], zip_include_packages=["accessible_output2"],
replace_paths = [("*", "")], replace_paths = [("*", "")],
include_files=["bootstrap.exe", "app-configuration.defaults", "cacerts.txt", "locales", "plugins", "libvlc.dll", "libvlccore.dll", find_accessible_output2_datafiles()], include_files=["bootstrap.exe", "app-configuration.defaults", "cacerts.txt", "locales", "plugins", "libvlc.dll", "libvlccore.dll", find_accessible_output2_datafiles()],
) )
executables = [ executables = [
Executable('main.py', base=base, targetName="MusicDL") Executable('main.py', base=base, targetName="MusicDL")

View File

@ -8,14 +8,14 @@ data_directory = None
app_type = "" app_type = ""
def setup(): def setup():
global data_directory, app_type global data_directory, app_type
if len(glob.glob("Uninstall.exe")) > 0: # installed copy if len(glob.glob("Uninstall.exe")) > 0: # installed copy
if os.path.exists(paths.data_path("musicDL")) == False: if os.path.exists(paths.data_path("musicDL")) == False:
os.mkdir(paths.data_path("musicDL")) os.mkdir(paths.data_path("musicDL"))
data_directory = paths.data_path("musicDL") data_directory = paths.data_path("musicDL")
app_type = "installed" app_type = "installed"
else: else:
app_type = "portable" app_type = "portable"
data_directory = os.path.join(paths.app_path(), "data") data_directory = os.path.join(paths.app_path(), "data")
if os.path.exists(data_directory) == False: if os.path.exists(data_directory) == False:
os.mkdir(data_directory) os.mkdir(data_directory)

View File

@ -9,24 +9,24 @@ from fixes import fix_requests
# Let's import the reload function # Let's import the reload function
if sys.version[0] == "3": if sys.version[0] == "3":
from imp import reload from imp import reload
class fixesTestCase(unittest.TestCase): class fixesTestCase(unittest.TestCase):
# def test_winpaths_error_in_python3(self): # def test_winpaths_error_in_python3(self):
# """ Testing the winpaths error happening only in Python 3 due to changes introduced to ctypes. """ # """ Testing the winpaths error happening only in Python 3 due to changes introduced to ctypes. """
# # If this test fails, it means winpaths has been updated to fix the ctypes issue already. # # If this test fails, it means winpaths has been updated to fix the ctypes issue already.
# # Therefore this test and the corresponding issue should be removed. # # Therefore this test and the corresponding issue should be removed.
# if sys.version[0] != "3": # if sys.version[0] != "3":
# return # return
# # A reload of winpaths is needed to rever the fix of winpaths, if has been applied before # # A reload of winpaths is needed to rever the fix of winpaths, if has been applied before
# reload(winpaths) # reload(winpaths)
# self.assertRaises(AttributeError, winpaths.get_appdata) # self.assertRaises(AttributeError, winpaths.get_appdata)
def test_requests_fix(self): def test_requests_fix(self):
""" Testing the requests fix and check if the certificates file exists in the provided path. """ """ Testing the requests fix and check if the certificates file exists in the provided path. """
fix_requests.fix() fix_requests.fix()
self.assertTrue(os.path.exists(os.environ["REQUESTS_CA_BUNDLE"])) self.assertTrue(os.path.exists(os.environ["REQUESTS_CA_BUNDLE"]))
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@ -15,80 +15,80 @@ from services import base
# Pytohn 2/3 compat # Pytohn 2/3 compat
if sys.version[0] == "2": if sys.version[0] == "2":
strtype = unicode strtype = unicode
else: else:
strtype = str strtype = str
class servicesTestCase(unittest.TestCase): class servicesTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
""" Configure i18n functions for avoiding a traceback later. """ """ Configure i18n functions for avoiding a traceback later. """
i18n.setup() i18n.setup()
def search(self, service_name, search_query="piano", skip_validation=False): def search(self, service_name, search_query="piano", skip_validation=False):
""" Search a video in the passed service name. """ """ Search a video in the passed service name. """
# Test basic instance stuff. # Test basic instance stuff.
service_instance = getattr(services, service_name).interface() service_instance = getattr(services, service_name).interface()
service_instance.search(search_query) service_instance.search(search_query)
self.assertIsInstance(service_instance.results, list) self.assertIsInstance(service_instance.results, list)
self.assertNotEqual(len(service_instance.results), 0) self.assertNotEqual(len(service_instance.results), 0)
self.assertIsInstance(len(service_instance.results), int) self.assertIsInstance(len(service_instance.results), int)
# Take and test validity of the first item. # Take and test validity of the first item.
item = service_instance.results[0] item = service_instance.results[0]
self.assertIsInstance(item, base.song) self.assertIsInstance(item, base.song)
self.assertIsInstance(item.title, strtype) self.assertIsInstance(item.title, strtype)
self.assertNotEqual(item.title, "") self.assertNotEqual(item.title, "")
if service_name == "youtube": # Duration is only available for youtube. if service_name == "youtube": # Duration is only available for youtube.
self.assertIsInstance(item.duration, strtype) self.assertIsInstance(item.duration, strtype)
self.assertNotEqual(item.duration, "") self.assertNotEqual(item.duration, "")
self.assertIsInstance(item.url, strtype) self.assertIsInstance(item.url, strtype)
self.assertNotEqual(item.url, "") self.assertNotEqual(item.url, "")
if service_name == "youtube" and skip_validation == False: if service_name == "youtube" and skip_validation == False:
match = re.search("((?<=(v|V)/)|(?<=be/)|(?<=(\?|\&)v=)|(?<=embed/))([\w-]+)", item.url) match = re.search("((?<=(v|V)/)|(?<=be/)|(?<=(\?|\&)v=)|(?<=embed/))([\w-]+)", item.url)
self.assertNotEqual(match, None) self.assertNotEqual(match, None)
formatted_track = item.format_track() formatted_track = item.format_track()
self.assertIsInstance(formatted_track, strtype) self.assertIsInstance(formatted_track, strtype)
self.assertNotEquals(formatted_track, "") self.assertNotEquals(formatted_track, "")
item.get_download_url() item.get_download_url()
self.assertIsInstance(item.download_url, strtype) self.assertIsInstance(item.download_url, strtype)
self.assertNotEquals(item.download_url, "") self.assertNotEquals(item.download_url, "")
def search_blank(self, extractor_name, search_query=""): def search_blank(self, extractor_name, search_query=""):
""" Attempt to search in any extractor by passing a blank string. """ """ Attempt to search in any extractor by passing a blank string. """
extractor_instance = getattr(services, extractor_name).interface() extractor_instance = getattr(services, extractor_name).interface()
self.assertRaises(ValueError, extractor_instance.search, search_query) self.assertRaises(ValueError, extractor_instance.search, search_query)
def test_youtube_search(self): def test_youtube_search(self):
""" Testing a Youtube search. """ """ Testing a Youtube search. """
self.search("youtube") self.search("youtube")
def test_youtube_search_unicode(self): def test_youtube_search_unicode(self):
""" Testing a Youtube search using unicode characters. """ """ Testing a Youtube search using unicode characters. """
self.search("youtube", "Пианино") self.search("youtube", "Пианино")
def test_youtube_search_blank(self): def test_youtube_search_blank(self):
""" Testing a youtube search when text is blank or not passed. """ """ Testing a youtube search when text is blank or not passed. """
self.search_blank("youtube") self.search_blank("youtube")
def test_youtube_direct_link(self): def test_youtube_direct_link(self):
""" Testing a search in youtube by passing a direct link. """ """ Testing a search in youtube by passing a direct link. """
self.search("youtube", "https://www.youtube.com/watch?v=XkeU8w2Y-2Y") self.search("youtube", "https://www.youtube.com/watch?v=XkeU8w2Y-2Y")
def test_youtube_playlist(self): def test_youtube_playlist(self):
""" Testing a youtube search by passing a link to a playlist. """ """ Testing a youtube search by passing a link to a playlist. """
self.search("youtube", "https://www.youtube.com/channel/UCPTYdUGtBMuqGg6ZtvYC1zQ", skip_validation=True) self.search("youtube", "https://www.youtube.com/channel/UCPTYdUGtBMuqGg6ZtvYC1zQ", skip_validation=True)
# Uncomment the following test only if you live or test this in Russia. # Uncomment the following test only if you live or test this in Russia.
# def test_zaycev_search(self): # def test_zaycev_search(self):
# """ Testing a search made in zaycev.net """ # """ Testing a search made in zaycev.net """
# self.search("zaycev") # self.search("zaycev")
# def test_zaycev_search_unicode(self): # def test_zaycev_search_unicode(self):
# """ Testing a search made in zaycev.net with unicode characters. """ # """ Testing a search made in zaycev.net with unicode characters. """
# self.search("zaycev", "Пианино") # self.search("zaycev", "Пианино")
# def test_zaycev_search_blank(self): # def test_zaycev_search_blank(self):
# """ Testing a search in zaycev.net when text is blank. """ # """ Testing a search in zaycev.net when text is blank. """
# self.search_blank("zaycev") # self.search_blank("zaycev")
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@ -11,31 +11,31 @@ from fixes import fix_winpaths
class storageTestCase(unittest.TestCase): class storageTestCase(unittest.TestCase):
def test_portable_path(self): def test_portable_path(self):
""" Testing if paths are generated appropiately. """ """ Testing if paths are generated appropiately. """
storage.setup() storage.setup()
self.assertEquals(storage.app_type, "portable") self.assertEquals(storage.app_type, "portable")
self.assertTrue(os.path.exists(storage.data_directory)) self.assertTrue(os.path.exists(storage.data_directory))
self.assertEquals(storage.data_directory, os.path.join(paths.app_path(), "data")) self.assertEquals(storage.data_directory, os.path.join(paths.app_path(), "data"))
def test_installer_path(self): def test_installer_path(self):
""" Testing if paths are generated appropiately. """ """ Testing if paths are generated appropiately. """
# this is a temporary fix for winpaths. # this is a temporary fix for winpaths.
fake_installer_file = open(os.path.join(paths.app_path(), "Uninstall.exe"), "w") fake_installer_file = open(os.path.join(paths.app_path(), "Uninstall.exe"), "w")
fake_installer_file.close() fake_installer_file.close()
fix_winpaths.fix() fix_winpaths.fix()
storage.setup() storage.setup()
self.assertEquals(storage.app_type, "installed") self.assertEquals(storage.app_type, "installed")
self.assertTrue(os.path.exists(storage.data_directory)) self.assertTrue(os.path.exists(storage.data_directory))
self.assertEquals(storage.data_directory, paths.data_path("musicDL")) self.assertEquals(storage.data_directory, paths.data_path("musicDL"))
def tearDown(self): def tearDown(self):
""" Removes uninstall.exe created for tests and data path.""" """ Removes uninstall.exe created for tests and data path."""
fix_winpaths.fix() fix_winpaths.fix()
if os.path.exists(paths.data_path("musicDL")): if os.path.exists(paths.data_path("musicDL")):
shutil.rmtree(paths.data_path("musicDL")) shutil.rmtree(paths.data_path("musicDL"))
if os.path.exists(os.path.join(paths.app_path(), "uninstall.exe")): if os.path.exists(os.path.join(paths.app_path(), "uninstall.exe")):
os.remove(os.path.join(paths.app_path(), "uninstall.exe")) os.remove(os.path.join(paths.app_path(), "uninstall.exe"))
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@ -3,10 +3,10 @@ import os.path
import platform import platform
def find_datafiles(): def find_datafiles():
system = platform.system() system = platform.system()
if system == 'Windows': if system == 'Windows':
file_ext = '*.exe' file_ext = '*.exe'
else: else:
file_ext = '*.sh' file_ext = '*.sh'
path = os.path.abspath(os.path.join(__path__[0], 'bootstrappers', file_ext)) path = os.path.abspath(os.path.join(__path__[0], 'bootstrappers', file_ext))
return [('', glob.glob(path))] return [('', glob.glob(path))]

View File

@ -11,123 +11,123 @@ import tempfile
import widgetUtils import widgetUtils
import webbrowser import webbrowser
try: try:
import czipfile as zipfile import czipfile as zipfile
except ImportError: except ImportError:
import zipfile import zipfile
from platform_utils import paths from platform_utils import paths
def perform_update(endpoint, current_version, update_type="stable", app_name='', password=None, update_available_callback=None, progress_callback=None, update_complete_callback=None): def perform_update(endpoint, current_version, update_type="stable", app_name='', password=None, update_available_callback=None, progress_callback=None, update_complete_callback=None):
requests_session = create_requests_session(app_name=app_name, version=current_version) requests_session = create_requests_session(app_name=app_name, version=current_version)
available_update = find_update(endpoint, requests_session=requests_session) available_update = find_update(endpoint, requests_session=requests_session)
if not available_update: if not available_update:
logger.debug("No update available") logger.debug("No update available")
return False return False
available_version, available_description, update_url = find_version_data(update_type, current_version, available_update) available_version, available_description, update_url = find_version_data(update_type, current_version, available_update)
if available_version == False: if available_version == False:
return False return False
logger.info("A new update is available. Version %s" % available_version) logger.info("A new update is available. Version %s" % available_version)
if callable(update_available_callback) and not update_available_callback(version=available_version, description=available_description): #update_available_callback should return a falsy value to stop the process if callable(update_available_callback) and not update_available_callback(version=available_version, description=available_description): #update_available_callback should return a falsy value to stop the process
logger.info("User canceled update.") logger.info("User canceled update.")
return return
base_path = tempfile.mkdtemp() base_path = tempfile.mkdtemp()
download_path = os.path.join(base_path, 'update.zip') download_path = os.path.join(base_path, 'update.zip')
update_path = os.path.join(base_path, 'update') update_path = os.path.join(base_path, 'update')
downloaded = download_update(update_url, download_path, requests_session=requests_session, progress_callback=progress_callback) downloaded = download_update(update_url, download_path, requests_session=requests_session, progress_callback=progress_callback)
extracted = extract_update(downloaded, update_path, password=password) extracted = extract_update(downloaded, update_path, password=password)
bootstrap_path = move_bootstrap(extracted) bootstrap_path = move_bootstrap(extracted)
execute_bootstrap(bootstrap_path, extracted) execute_bootstrap(bootstrap_path, extracted)
logger.info("Update prepared for installation.") logger.info("Update prepared for installation.")
if callable(update_complete_callback): if callable(update_complete_callback):
update_complete_callback() update_complete_callback()
def create_requests_session(app_name=None, version=None): def create_requests_session(app_name=None, version=None):
user_agent = '' user_agent = ''
session = requests.session() session = requests.session()
if app_name: if app_name:
user_agent = ' %s/%r' % (app_name, version) user_agent = ' %s/%r' % (app_name, version)
session.headers['User-Agent'] = session.headers['User-Agent'] + user_agent session.headers['User-Agent'] = session.headers['User-Agent'] + user_agent
return session return session
def find_update(endpoint, requests_session): def find_update(endpoint, requests_session):
response = requests_session.get(endpoint) response = requests_session.get(endpoint)
response.raise_for_status() response.raise_for_status()
content = response.json() content = response.json()
return content return content
def find_version_data(update_type, current_version, available_update): def find_version_data(update_type, current_version, available_update):
if update_type == "stable": if update_type == "stable":
available_version = float(available_update['current_version']) available_version = float(available_update['current_version'])
if not float(available_version) > float(current_version) or platform.system()+platform.architecture()[0][:2] not in available_update['downloads']: if not float(available_version) > float(current_version) or platform.system()+platform.architecture()[0][:2] not in available_update['downloads']:
logger.debug("No update for this architecture") logger.debug("No update for this architecture")
return (False, False, False) return (False, False, False)
available_description = available_update.get('description', None) available_description = available_update.get('description', None)
update_url = available_update ['downloads'][platform.system()+platform.architecture()[0][:2]] update_url = available_update ['downloads'][platform.system()+platform.architecture()[0][:2]]
return (available_version, available_description, update_url) return (available_version, available_description, update_url)
else: # Unstable versions, based in commits instead of version numbers. else: # Unstable versions, based in commits instead of version numbers.
available_version = available_update["current_version"] available_version = available_update["current_version"]
if available_version == current_version: if available_version == current_version:
return (False, False, False) return (False, False, False)
available_description = available_update["description"] available_description = available_update["description"]
update_url = available_update ['downloads'][platform.system()+platform.architecture()[0][:2]] update_url = available_update ['downloads'][platform.system()+platform.architecture()[0][:2]]
return (available_version, available_description, update_url) return (available_version, available_description, update_url)
def download_update(update_url, update_destination, requests_session, progress_callback=None, chunk_size=io.DEFAULT_BUFFER_SIZE): def download_update(update_url, update_destination, requests_session, progress_callback=None, chunk_size=io.DEFAULT_BUFFER_SIZE):
total_downloaded = total_size = 0 total_downloaded = total_size = 0
with io.open(update_destination, 'w+b') as outfile: with io.open(update_destination, 'w+b') as outfile:
download = requests_session.get(update_url, stream=True) download = requests_session.get(update_url, stream=True)
total_size = int(download.headers.get('content-length', 0)) total_size = int(download.headers.get('content-length', 0))
logger.debug("Total update size: %d" % total_size) logger.debug("Total update size: %d" % total_size)
download.raise_for_status() download.raise_for_status()
for chunk in download.iter_content(chunk_size): for chunk in download.iter_content(chunk_size):
outfile.write(chunk) outfile.write(chunk)
total_downloaded += len(chunk) total_downloaded += len(chunk)
if callable(progress_callback): if callable(progress_callback):
call_callback(progress_callback, total_downloaded, total_size) call_callback(progress_callback, total_downloaded, total_size)
logger.debug("Update downloaded") logger.debug("Update downloaded")
return update_destination return update_destination
def extract_update(update_archive, destination, password=None): def extract_update(update_archive, destination, password=None):
"""Given an update archive, extracts it. Returns the directory to which it has been extracted""" """Given an update archive, extracts it. Returns the directory to which it has been extracted"""
with contextlib.closing(zipfile.ZipFile(update_archive)) as archive: with contextlib.closing(zipfile.ZipFile(update_archive)) as archive:
if password: if password:
archive.setpassword(password) archive.setpassword(password)
archive.extractall(path=destination) archive.extractall(path=destination)
logger.debug("Update extracted") logger.debug("Update extracted")
return destination return destination
def move_bootstrap(extracted_path): def move_bootstrap(extracted_path):
working_path = os.path.abspath(os.path.join(extracted_path, '..')) working_path = os.path.abspath(os.path.join(extracted_path, '..'))
if platform.system() == 'Darwin': if platform.system() == 'Darwin':
extracted_path = os.path.join(extracted_path, 'Contents', 'Resources') extracted_path = os.path.join(extracted_path, 'Contents', 'Resources')
downloaded_bootstrap = os.path.join(extracted_path, bootstrap_name()) downloaded_bootstrap = os.path.join(extracted_path, bootstrap_name())
new_bootstrap_path = os.path.join(working_path, bootstrap_name()) new_bootstrap_path = os.path.join(working_path, bootstrap_name())
os.rename(downloaded_bootstrap, new_bootstrap_path) os.rename(downloaded_bootstrap, new_bootstrap_path)
return new_bootstrap_path return new_bootstrap_path
def execute_bootstrap(bootstrap_path, source_path): def execute_bootstrap(bootstrap_path, source_path):
arguments = r'"%s" "%s" "%s" "%s"' % (os.getpid(), source_path, paths.app_path(), paths.get_executable()) arguments = r'"%s" "%s" "%s" "%s"' % (os.getpid(), source_path, paths.app_path(), paths.get_executable())
if platform.system() == 'Windows': if platform.system() == 'Windows':
import win32api import win32api
win32api.ShellExecute(0, 'open', bootstrap_path, arguments, '', 5) win32api.ShellExecute(0, 'open', bootstrap_path, arguments, '', 5)
else: else:
import subprocess import subprocess
make_executable(bootstrap_path) make_executable(bootstrap_path)
subprocess.Popen(['%s %s' % (bootstrap_path, arguments)], shell=True) subprocess.Popen(['%s %s' % (bootstrap_path, arguments)], shell=True)
logger.info("Bootstrap executed") logger.info("Bootstrap executed")
def bootstrap_name(): def bootstrap_name():
if platform.system() == 'Windows': return 'bootstrap.exe' if platform.system() == 'Windows': return 'bootstrap.exe'
if platform.system() == 'Darwin': return 'bootstrap-mac.sh' if platform.system() == 'Darwin': return 'bootstrap-mac.sh'
return 'bootstrap-lin.sh' return 'bootstrap-lin.sh'
def make_executable(path): def make_executable(path):
import stat import stat
st = os.stat(path) st = os.stat(path)
os.chmod(path, st.st_mode | stat.S_IEXEC) os.chmod(path, st.st_mode | stat.S_IEXEC)
def call_callback(callback, *args, **kwargs): def call_callback(callback, *args, **kwargs):
# try: # try:
callback(*args, **kwargs) callback(*args, **kwargs)
# except: # except:
# logger.exception("Failed calling callback %r with args %r and kwargs %r" % (callback, args, kwargs)) # logger.exception("Failed calling callback %r with args %r and kwargs %r" % (callback, args, kwargs))

View File

@ -9,12 +9,12 @@ from .wxUpdater import *
logger = logging.getLogger("updater") logger = logging.getLogger("updater")
def do_update(update_type="alpha"): def do_update(update_type="alpha"):
# Updates cannot be performed in the source code version. # Updates cannot be performed in the source code version.
if hasattr(sys, "frozen") == False: if hasattr(sys, "frozen") == False:
return return
endpoint = application.update_url endpoint = application.update_url
version = application.update_next_version version = application.update_next_version
try: try:
return update.perform_update(endpoint=endpoint, current_version=version, app_name=application.name, update_type=update_type, update_available_callback=available_update_dialog, progress_callback=progress_callback, update_complete_callback=update_finished) return update.perform_update(endpoint=endpoint, current_version=version, app_name=application.name, update_type=update_type, update_available_callback=available_update_dialog, progress_callback=progress_callback, update_complete_callback=update_finished)
except ConnectionError: except ConnectionError:
logger.exception("Update failed.") logger.exception("Update failed.")

View File

@ -2,42 +2,42 @@
from __future__ import unicode_literals from __future__ import unicode_literals
def convert_bytes(n): def convert_bytes(n):
K, M, G, T, P = 1 << 10, 1 << 20, 1 << 30, 1 << 40, 1 << 50 K, M, G, T, P = 1 << 10, 1 << 20, 1 << 30, 1 << 40, 1 << 50
if n >= P: if n >= P:
return '%.2fPb' % (float(n) / T) return '%.2fPb' % (float(n) / T)
elif n >= T: elif n >= T:
return '%.2fTb' % (float(n) / T) return '%.2fTb' % (float(n) / T)
elif n >= G: elif n >= G:
return '%.2fGb' % (float(n) / G) return '%.2fGb' % (float(n) / G)
elif n >= M: elif n >= M:
return '%.2fMb' % (float(n) / M) return '%.2fMb' % (float(n) / M)
elif n >= K: elif n >= K:
return '%.2fKb' % (float(n) / K) return '%.2fKb' % (float(n) / K)
else: else:
return '%d' % n return '%d' % n
def seconds_to_string(seconds, precision=0): def seconds_to_string(seconds, precision=0):
day = seconds // 86400 day = seconds // 86400
hour = seconds // 3600 hour = seconds // 3600
min = (seconds // 60) % 60 min = (seconds // 60) % 60
sec = seconds - (hour * 3600) - (min * 60) sec = seconds - (hour * 3600) - (min * 60)
sec_spec = "." + str(precision) + "f" sec_spec = "." + str(precision) + "f"
sec_string = sec.__format__(sec_spec) sec_string = sec.__format__(sec_spec)
string = "" string = ""
if day == 1: if day == 1:
string += _("%d day, ") % day string += _("%d day, ") % day
elif day >= 2: elif day >= 2:
string += _("%d days, ") % day string += _("%d days, ") % day
if (hour == 1): if (hour == 1):
string += _("%d hour, ") % hour string += _("%d hour, ") % hour
elif (hour >= 2): elif (hour >= 2):
string += _("%d hours, ") % hour string += _("%d hours, ") % hour
if (min == 1): if (min == 1):
string += _("%d minute, ") % min string += _("%d minute, ") % min
elif (min >= 2): elif (min >= 2):
string += _("%d minutes, ") % min string += _("%d minutes, ") % min
if sec >= 0 and sec <= 2: if sec >= 0 and sec <= 2:
string += _("%s second") % sec_string string += _("%s second") % sec_string
else: else:
string += _("%s seconds") % sec_string string += _("%s seconds") % sec_string
return string return string

View File

@ -8,27 +8,27 @@ from . import utils
progress_dialog = None progress_dialog = None
def available_update_dialog(version, description): def available_update_dialog(version, description):
dialog = wx.MessageDialog(None, _("There's a new {app_name} version available. Would you like to download it now?\n\n {app_name} version: {app_version}\n\nChanges:\n{changes}").format(app_name=application.name, app_version=version, changes=description), _("New version for %s") % application.name, style=wx.YES|wx.NO|wx.ICON_WARNING) dialog = wx.MessageDialog(None, _("There's a new {app_name} version available. Would you like to download it now?\n\n {app_name} version: {app_version}\n\nChanges:\n{changes}").format(app_name=application.name, app_version=version, changes=description), _("New version for %s") % application.name, style=wx.YES|wx.NO|wx.ICON_WARNING)
if dialog.ShowModal() == wx.ID_YES: if dialog.ShowModal() == wx.ID_YES:
return True return True
else: else:
return False return False
def create_progress_dialog(): def create_progress_dialog():
return wx.ProgressDialog(_("Download in Progress"), _("Downloading the new version..."), parent=None, maximum=100) return wx.ProgressDialog(_("Download in Progress"), _("Downloading the new version..."), parent=None, maximum=100)
def progress_callback(total_downloaded, total_size): def progress_callback(total_downloaded, total_size):
wx.CallAfter(_progress_callback, total_downloaded, total_size) wx.CallAfter(_progress_callback, total_downloaded, total_size)
def _progress_callback(total_downloaded, total_size): def _progress_callback(total_downloaded, total_size):
global progress_dialog global progress_dialog
if progress_dialog == None: if progress_dialog == None:
progress_dialog = create_progress_dialog() progress_dialog = create_progress_dialog()
progress_dialog.Show() progress_dialog.Show()
if total_downloaded == total_size: if total_downloaded == total_size:
progress_dialog.Destroy() progress_dialog.Destroy()
else: else:
progress_dialog.Update((total_downloaded*100)/total_size, _("Updating... {total_transferred} of {total_size}").format(total_transferred=utils.convert_bytes(total_downloaded), total_size=utils.convert_bytes(total_size))) progress_dialog.Update((total_downloaded*100)/total_size, _("Updating... {total_transferred} of {total_size}").format(total_transferred=utils.convert_bytes(total_downloaded), total_size=utils.convert_bytes(total_size)))
def update_finished(): def update_finished():
return wx.MessageDialog(None, _("The update has been downloaded and installed successfully. Press OK to continue."), _("Done!")).ShowModal() return wx.MessageDialog(None, _("The update has been downloaded and installed successfully. Press OK to continue."), _("Done!")).ShowModal()

View File

@ -11,99 +11,99 @@ from pubsub import pub
log = logging.getLogger("utils") log = logging.getLogger("utils")
def call_threaded(func, *args, **kwargs): def call_threaded(func, *args, **kwargs):
#Call the given function in a daemonized thread and return the thread. #Call the given function in a daemonized thread and return the thread.
def new_func(*a, **k): def new_func(*a, **k):
func(*a, **k) func(*a, **k)
thread = threading.Thread(target=new_func, args=args, kwargs=kwargs) thread = threading.Thread(target=new_func, args=args, kwargs=kwargs)
thread.daemon = True thread.daemon = True
thread.start() thread.start()
return thread return thread
class RepeatingTimer(threading.Thread): class RepeatingTimer(threading.Thread):
"""Call a function after a specified number of seconds, it will then repeat again after the specified number of seconds """Call a function after a specified number of seconds, it will then repeat again after the specified number of seconds
Note: If the function provided takes time to execute, this time is NOT taken from the next wait period Note: If the function provided takes time to execute, this time is NOT taken from the next wait period
t = RepeatingTimer(30.0, f, args=[], kwargs={}) t = RepeatingTimer(30.0, f, args=[], kwargs={})
t.start() t.start()
t.cancel() # stop the timer's actions t.cancel() # stop the timer's actions
""" """
def __init__(self, interval, function, daemon=True, *args, **kwargs): def __init__(self, interval, function, daemon=True, *args, **kwargs):
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.daemon = daemon self.daemon = daemon
self.interval = float(interval) self.interval = float(interval)
self.function = function self.function = function
self.args = args self.args = args
self.kwargs = kwargs self.kwargs = kwargs
self.finished = threading.Event() self.finished = threading.Event()
def cancel(self): def cancel(self):
"""Stop the timer if it hasn't finished yet""" """Stop the timer if it hasn't finished yet"""
log.debug("Stopping repeater for %s" % (self.function,)) log.debug("Stopping repeater for %s" % (self.function,))
self.finished.set() self.finished.set()
stop = cancel stop = cancel
def run(self): def run(self):
while not self.finished.is_set(): while not self.finished.is_set():
self.finished.wait(self.interval) self.finished.wait(self.interval)
if not self.finished.is_set(): #In case someone has canceled while waiting if not self.finished.is_set(): #In case someone has canceled while waiting
try: try:
self.function(*self.args, **self.kwargs) self.function(*self.args, **self.kwargs)
except: except:
log.exception("Execution failed. Function: %r args: %r and kwargs: %r" % (self.function, self.args, self.kwargs)) log.exception("Execution failed. Function: %r args: %r and kwargs: %r" % (self.function, self.args, self.kwargs))
def download_file(url, local_filename, metadata=dict()): def download_file(url, local_filename, metadata=dict()):
log.debug("Download started: filename={0}, url={1}".format(local_filename, url)) log.debug("Download started: filename={0}, url={1}".format(local_filename, url))
r = requests.get(url, stream=True) r = requests.get(url, stream=True)
pub.sendMessage("change_status", status=_(u"Downloading {0}.").format(local_filename,)) pub.sendMessage("change_status", status=_(u"Downloading {0}.").format(local_filename,))
total_length = r.headers.get("content-length") total_length = r.headers.get("content-length")
dl = 0 dl = 0
total_length = int(total_length) total_length = int(total_length)
log.debug("Downloading file of {0} bytes".format(total_length)) log.debug("Downloading file of {0} bytes".format(total_length))
with open(local_filename, 'wb') as f: with open(local_filename, 'wb') as f:
for chunk in r.iter_content(chunk_size=512*1024): for chunk in r.iter_content(chunk_size=512*1024):
if chunk: # filter out keep-alive new chunks if chunk: # filter out keep-alive new chunks
dl += len(chunk) dl += len(chunk)
f.write(chunk) f.write(chunk)
done = int(100 * dl / total_length) done = int(100 * dl / total_length)
msg = _(u"Downloading {0} ({1}%).").format(os.path.basename(local_filename), done) msg = _(u"Downloading {0} ({1}%).").format(os.path.basename(local_filename), done)
pub.sendMessage("change_status", status=msg) pub.sendMessage("change_status", status=msg)
pub.sendMessage("update-progress", value=done) pub.sendMessage("update-progress", value=done)
pub.sendMessage("download_finished", file=os.path.basename(local_filename)) pub.sendMessage("download_finished", file=os.path.basename(local_filename))
log.debug("Download finished successfully") log.debug("Download finished successfully")
apply_metadata(local_filename, metadata) apply_metadata(local_filename, metadata)
return local_filename return local_filename
def get_services(import_all=False): def get_services(import_all=False):
""" Function for importing everything wich is located in the services package and has a class named interface.""" """ Function for importing everything wich is located in the services package and has a class named interface."""
module_type = types.ModuleType module_type = types.ModuleType
# first of all, import all classes for the package so we can reload everything if they have changes in config. # first of all, import all classes for the package so we can reload everything if they have changes in config.
_classes = [m for m in services.__dict__.values() if type(m) == module_type and hasattr(m, 'interface')] _classes = [m for m in services.__dict__.values() if type(m) == module_type and hasattr(m, 'interface')]
for cls in _classes: for cls in _classes:
reload(cls) reload(cls)
if not import_all: if not import_all:
classes = [m for m in services.__dict__.values() if type(m) == module_type and hasattr(m, 'interface') and m.interface.enabled != False] classes = [m for m in services.__dict__.values() if type(m) == module_type and hasattr(m, 'interface') and m.interface.enabled != False]
else: else:
classes = [m for m in services.__dict__.values() if type(m) == module_type and hasattr(m, 'interface')] classes = [m for m in services.__dict__.values() if type(m) == module_type and hasattr(m, 'interface')]
return classes return classes
def apply_metadata(local_filename, metadata): def apply_metadata(local_filename, metadata):
if local_filename.endswith(".mp3"): if local_filename.endswith(".mp3"):
from mutagen.easyid3 import EasyID3 as metadataeditor from mutagen.easyid3 import EasyID3 as metadataeditor
elif local_filename.endswith(".flac"): elif local_filename.endswith(".flac"):
from mutagen.flac import FLAC as metadataeditor from mutagen.flac import FLAC as metadataeditor
elif local_filename.endswith(".m4a"): elif local_filename.endswith(".m4a"):
from mutagen.mp4 import MP4 as metadataeditor from mutagen.mp4 import MP4 as metadataeditor
audio = metadataeditor(local_filename) audio = metadataeditor(local_filename)
if local_filename.endswith(".m4a") == False: if local_filename.endswith(".m4a") == False:
for k in metadata.keys(): for k in metadata.keys():
audio[k] = metadata[k] audio[k] = metadata[k]
else: else:
audio["\xa9nam"] = metadata["title"] audio["\xa9nam"] = metadata["title"]
audio["\xa9alb"] = metadata["album"] audio["\xa9alb"] = metadata["album"]
audio["\xa9ART"] = metadata["artist"] audio["\xa9ART"] = metadata["artist"]
audio.save() audio.save()
def safe_filename(filename): def safe_filename(filename):
allowed_symbols = ["_", ".", ",", "-", "(", ")"] allowed_symbols = ["_", ".", ",", "-", "(", ")"]
return "".join([c for c in filename if c.isalpha() or c.isdigit() or c==' ' or c in allowed_symbols]).rstrip() return "".join([c for c in filename if c.isalpha() or c.isdigit() or c==' ' or c in allowed_symbols]).rstrip()

View File

@ -55,122 +55,122 @@ LISTBOX_CHANGED = wx.EVT_LISTBOX
LISTBOX_ITEM_ACTIVATED = wx.EVT_LIST_ITEM_ACTIVATED LISTBOX_ITEM_ACTIVATED = wx.EVT_LIST_ITEM_ACTIVATED
def exit_application(): def exit_application():
""" Closes the current window cleanly. """ """ Closes the current window cleanly. """
wx.GetApp().ExitMainLoop() wx.GetApp().ExitMainLoop()
def connect_event(parent, event, func, menuitem=None, *args, **kwargs): def connect_event(parent, event, func, menuitem=None, *args, **kwargs):
""" Connects an event to a function. """ Connects an event to a function.
parent wx.window: The widget that will listen for the event. parent wx.window: The widget that will listen for the event.
event widgetUtils.event: The event that will be listened for the parent. The event should be one of the widgetUtils events. event widgetUtils.event: The event that will be listened for the parent. The event should be one of the widgetUtils events.
function func: The function that will be connected to the event.""" function func: The function that will be connected to the event."""
if menuitem == None: if menuitem == None:
return getattr(parent, "Bind")(event, func, *args, **kwargs) return getattr(parent, "Bind")(event, func, *args, **kwargs)
else: else:
return getattr(parent, "Bind")(event, func, menuitem, *args, **kwargs) return getattr(parent, "Bind")(event, func, menuitem, *args, **kwargs)
def connectExitFunction(exitFunction): def connectExitFunction(exitFunction):
""" This connect the events in WX when an user is turning off the machine.""" """ This connect the events in WX when an user is turning off the machine."""
wx.GetApp().Bind(wx.EVT_QUERY_END_SESSION, exitFunction) wx.GetApp().Bind(wx.EVT_QUERY_END_SESSION, exitFunction)
wx.GetApp().Bind(wx.EVT_END_SESSION, exitFunction) wx.GetApp().Bind(wx.EVT_END_SESSION, exitFunction)
class BaseDialog(wx.Dialog): class BaseDialog(wx.Dialog):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(BaseDialog, self).__init__(*args, **kwargs) super(BaseDialog, self).__init__(*args, **kwargs)
def get_response(self): def get_response(self):
return self.ShowModal() return self.ShowModal()
def get(self, control): def get(self, control):
if hasattr(self, control): if hasattr(self, control):
control = getattr(self, control) control = getattr(self, control)
if hasattr(control, "GetValue"): return getattr(control, "GetValue")() if hasattr(control, "GetValue"): return getattr(control, "GetValue")()
elif hasattr(control, "GetLabel"): return getattr(control, "GetLabel")() elif hasattr(control, "GetLabel"): return getattr(control, "GetLabel")()
else: return -1 else: return -1
else: return 0 else: return 0
def set(self, control, text): def set(self, control, text):
if hasattr(self, control): if hasattr(self, control):
control = getattr(self, control) control = getattr(self, control)
if hasattr(control, "SetValue"): return getattr(control, "SetValue")(text) if hasattr(control, "SetValue"): return getattr(control, "SetValue")(text)
elif hasattr(control, "SetLabel"): return getattr(control, "SetLabel")(text) elif hasattr(control, "SetLabel"): return getattr(control, "SetLabel")(text)
elif hasattr(control, "ChangeValue"): return getattr(control, "ChangeValue")(text) elif hasattr(control, "ChangeValue"): return getattr(control, "ChangeValue")(text)
else: return -1 else: return -1
else: return 0 else: return 0
def destroy(self): def destroy(self):
self.Destroy() self.Destroy()
def set_title(self, title): def set_title(self, title):
self.SetTitle(title) self.SetTitle(title)
def get_title(self): def get_title(self):
return self.GetTitle() return self.GetTitle()
def enable(self, control): def enable(self, control):
getattr(self, control).Enable(True) getattr(self, control).Enable(True)
def disable(self, control): def disable(self, control):
getattr(self, control).Enable(False) getattr(self, control).Enable(False)
class mainLoopObject(wx.App): class mainLoopObject(wx.App):
def __init__(self): def __init__(self):
self.app = wx.App() self.app = wx.App()
# self.lc = wx.Locale() # self.lc = wx.Locale()
# lang=languageHandler.getLanguage() # lang=languageHandler.getLanguage()
# wxLang=self.lc.FindLanguageInfo(lang) # wxLang=self.lc.FindLanguageInfo(lang)
# if not wxLang and '_' in lang: # if not wxLang and '_' in lang:
# wxLang=self.lc.FindLanguageInfo(lang.split('_')[0]) # wxLang=self.lc.FindLanguageInfo(lang.split('_')[0])
# if hasattr(sys,'frozen'): # if hasattr(sys,'frozen'):
# self.lc.AddCatalogLookupPathPrefix(paths.app_path("locales")) # self.lc.AddCatalogLookupPathPrefix(paths.app_path("locales"))
# if wxLang: # if wxLang:
# self.lc.Init(wxLang.Language) # self.lc.Init(wxLang.Language)
def run(self): def run(self):
self.app.MainLoop() self.app.MainLoop()
class list(object): class list(object):
def __init__(self, parent, *columns, **listArguments): def __init__(self, parent, *columns, **listArguments):
self.columns = columns self.columns = columns
self.listArguments = listArguments self.listArguments = listArguments
self.create_list(parent) self.create_list(parent)
def set_windows_size(self, column, characters_max): def set_windows_size(self, column, characters_max):
self.list.SetColumnWidth(column, characters_max*2) self.list.SetColumnWidth(column, characters_max*2)
def set_size(self): def set_size(self):
self.list.SetSize((self.list.GetBestSize()[0], 1000)) self.list.SetSize((self.list.GetBestSize()[0], 1000))
def create_list(self, parent): def create_list(self, parent):
self.list = wx.ListCtrl(parent, -1, **self.listArguments) self.list = wx.ListCtrl(parent, -1, **self.listArguments)
for i in range(0, len(self.columns)): for i in range(0, len(self.columns)):
self.list.InsertColumn(i, u"%s" % (self.columns[i])) self.list.InsertColumn(i, u"%s" % (self.columns[i]))
def insert_item(self, reversed, *item): def insert_item(self, reversed, *item):
""" Inserts an item on the list.""" """ Inserts an item on the list."""
if reversed == False: items = self.list.GetItemCount() if reversed == False: items = self.list.GetItemCount()
else: items = 0 else: items = 0
self.list.InsertItem(items, item[0]) self.list.InsertItem(items, item[0])
for i in range(1, len(self.columns)): for i in range(1, len(self.columns)):
self.list.SetItem(items, i, item[i]) self.list.SetItem(items, i, item[i])
def remove_item(self, pos): def remove_item(self, pos):
""" Deletes an item from the list.""" """ Deletes an item from the list."""
if pos > 0: self.list.Focus(pos-1) if pos > 0: self.list.Focus(pos-1)
self.list.DeleteItem(pos) self.list.DeleteItem(pos)
def clear(self): def clear(self):
self.list.DeleteAllItems() self.list.DeleteAllItems()
def get_selected(self): def get_selected(self):
return self.list.GetFocusedItem() return self.list.GetFocusedItem()
def select_item(self, pos): def select_item(self, pos):
self.list.Focus(pos) self.list.Focus(pos)
def get_count(self): def get_count(self):
selected = self.list.GetItemCount() selected = self.list.GetItemCount()
if selected == -1: if selected == -1:
return 0 return 0
else: else:
return selected return selected

View File

@ -27,4 +27,4 @@ file.close()
file2 = open("installer.nsi", "w", encoding="utf-8") file2 = open("installer.nsi", "w", encoding="utf-8")
file2.write(contents) file2.write(contents)
file2.close() file2.close()
print("done") print("done")

View File

@ -3,49 +3,49 @@ import wx
import widgetUtils import widgetUtils
class general(wx.Panel, widgetUtils.BaseDialog): class general(wx.Panel, widgetUtils.BaseDialog):
def __init__(self, panel, output_devices=[]): def __init__(self, panel, output_devices=[]):
super(general, self).__init__(panel) super(general, self).__init__(panel)
sizer = wx.BoxSizer(wx.VERTICAL) sizer = wx.BoxSizer(wx.VERTICAL)
output_device_label = wx.StaticText(self, wx.NewId(), _("Output device")) output_device_label = wx.StaticText(self, wx.NewId(), _("Output device"))
self.output_device = wx.ComboBox(self, wx.NewId(), choices=output_devices, value=output_devices[0], style=wx.CB_READONLY) self.output_device = wx.ComboBox(self, wx.NewId(), choices=output_devices, value=output_devices[0], style=wx.CB_READONLY)
output_device_box = wx.BoxSizer(wx.HORIZONTAL) output_device_box = wx.BoxSizer(wx.HORIZONTAL)
output_device_box.Add(output_device_label, 0, wx.ALL, 5) output_device_box.Add(output_device_label, 0, wx.ALL, 5)
output_device_box.Add(self.output_device, 0, wx.ALL, 5) output_device_box.Add(self.output_device, 0, wx.ALL, 5)
sizer.Add(output_device_box, 0, wx.ALL, 5) sizer.Add(output_device_box, 0, wx.ALL, 5)
self.SetSizer(sizer) self.SetSizer(sizer)
class configurationDialog(widgetUtils.BaseDialog): class configurationDialog(widgetUtils.BaseDialog):
def __init__(self, title): def __init__(self, title):
super(configurationDialog, self).__init__(None, -1, title=title) super(configurationDialog, self).__init__(None, -1, title=title)
self.panel = wx.Panel(self) self.panel = wx.Panel(self)
self.sizer = wx.BoxSizer(wx.VERTICAL) self.sizer = wx.BoxSizer(wx.VERTICAL)
self.notebook = wx.Treebook(self.panel) self.notebook = wx.Treebook(self.panel)
def create_general(self, output_devices=[]): def create_general(self, output_devices=[]):
self.general = general(self.notebook, output_devices=output_devices) self.general = general(self.notebook, output_devices=output_devices)
self.notebook.AddPage(self.general, _("General")) self.notebook.AddPage(self.general, _("General"))
self.general.SetFocus() self.general.SetFocus()
def realize(self): def realize(self):
self.notebook.AddPage(wx.Panel(self.notebook, wx.NewId()), _("Services")) self.notebook.AddPage(wx.Panel(self.notebook, wx.NewId()), _("Services"))
self.sizer.Add(self.notebook, 0, wx.ALL, 5) self.sizer.Add(self.notebook, 0, wx.ALL, 5)
ok_cancel_box = wx.BoxSizer(wx.HORIZONTAL) ok_cancel_box = wx.BoxSizer(wx.HORIZONTAL)
ok = wx.Button(self.panel, wx.ID_OK, _("Save")) ok = wx.Button(self.panel, wx.ID_OK, _("Save"))
ok.SetDefault() ok.SetDefault()
cancel = wx.Button(self.panel, wx.ID_CANCEL, _("Close")) cancel = wx.Button(self.panel, wx.ID_CANCEL, _("Close"))
self.SetEscapeId(cancel.GetId()) self.SetEscapeId(cancel.GetId())
ok_cancel_box.Add(ok, 0, wx.ALL, 5) ok_cancel_box.Add(ok, 0, wx.ALL, 5)
ok_cancel_box.Add(cancel, 0, wx.ALL, 5) ok_cancel_box.Add(cancel, 0, wx.ALL, 5)
self.sizer.Add(ok_cancel_box, 0, wx.ALL, 5) self.sizer.Add(ok_cancel_box, 0, wx.ALL, 5)
self.panel.SetSizer(self.sizer) self.panel.SetSizer(self.sizer)
self.SetClientSize(self.sizer.CalcMin()) self.SetClientSize(self.sizer.CalcMin())
def get_value(self, panel, key): def get_value(self, panel, key):
p = getattr(self, panel) p = getattr(self, panel)
return getattr(p, key).GetValue() return getattr(p, key).GetValue()
def set_value(self, panel, key, value): def set_value(self, panel, key, value):
p = getattr(self, panel) p = getattr(self, panel)
control = getattr(p, key) control = getattr(p, key)
getattr(control, "SetValue")(value) getattr(control, "SetValue")(value)

View File

@ -2,123 +2,123 @@
from __future__ import unicode_literals # at top of module from __future__ import unicode_literals # at top of module
import wx import wx
try: try:
import wx.adv import wx.adv
except ImportError: except ImportError:
pass pass
import application import application
import widgetUtils import widgetUtils
class mainWindow(wx.Frame): class mainWindow(wx.Frame):
def makeMenu(self): def makeMenu(self):
mb = wx.MenuBar() mb = wx.MenuBar()
app_ = wx.Menu() app_ = wx.Menu()
self.settings = app_.Append(wx.NewId(), _("Settings")) self.settings = app_.Append(wx.NewId(), _("Settings"))
mb.Append(app_, _("Application")) mb.Append(app_, _("Application"))
player = wx.Menu() player = wx.Menu()
self.player_play = player.Append(wx.NewId(), _(u"Play")) self.player_play = player.Append(wx.NewId(), _(u"Play"))
self.player_stop = player.Append(wx.NewId(), _(u"Stop")) self.player_stop = player.Append(wx.NewId(), _(u"Stop"))
self.player_previous = player.Append(wx.NewId(), _(u"Previous")) self.player_previous = player.Append(wx.NewId(), _(u"Previous"))
self.player_next = player.Append(wx.NewId(), _(u"Next")) self.player_next = player.Append(wx.NewId(), _(u"Next"))
self.player_shuffle = player.AppendCheckItem(wx.NewId(), _(u"Shuffle")) self.player_shuffle = player.AppendCheckItem(wx.NewId(), _(u"Shuffle"))
self.player_volume_down = player.Append(wx.NewId(), _(u"Volume down")) self.player_volume_down = player.Append(wx.NewId(), _(u"Volume down"))
self.player_volume_up = player.Append(wx.NewId(), _(u"Volume up")) self.player_volume_up = player.Append(wx.NewId(), _(u"Volume up"))
self.player_mute = player.Append(wx.NewId(), _(u"Mute")) self.player_mute = player.Append(wx.NewId(), _(u"Mute"))
help_ = wx.Menu() help_ = wx.Menu()
self.about = help_.Append(wx.NewId(), _(u"About {0}").format(application.name,)) self.about = help_.Append(wx.NewId(), _(u"About {0}").format(application.name,))
self.check_for_updates = help_.Append(wx.NewId(), _(u"Check for updates")) self.check_for_updates = help_.Append(wx.NewId(), _(u"Check for updates"))
self.changelog = help_.Append(wx.NewId(), _(u"What's new in this version?")) self.changelog = help_.Append(wx.NewId(), _(u"What's new in this version?"))
self.website = help_.Append(wx.NewId(), _(u"Visit website")) self.website = help_.Append(wx.NewId(), _(u"Visit website"))
self.report = help_.Append(wx.NewId(), _(u"Report an error")) self.report = help_.Append(wx.NewId(), _(u"Report an error"))
mb.Append(player, _(u"Player")) mb.Append(player, _(u"Player"))
mb.Append(help_, _(u"Help")) mb.Append(help_, _(u"Help"))
self.SetMenuBar(mb) self.SetMenuBar(mb)
def __init__(self, extractors=[]): def __init__(self, extractors=[]):
super(mainWindow, self).__init__(parent=None, id=wx.NewId(), title=application.name) super(mainWindow, self).__init__(parent=None, id=wx.NewId(), title=application.name)
self.Maximize(True) self.Maximize(True)
self.makeMenu() self.makeMenu()
self.panel = wx.Panel(self) self.panel = wx.Panel(self)
self.sizer = wx.BoxSizer(wx.VERTICAL) self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sb = self.CreateStatusBar() self.sb = self.CreateStatusBar()
lbl2 = wx.StaticText(self.panel, wx.NewId(), _(u"S&earch")) lbl2 = wx.StaticText(self.panel, wx.NewId(), _(u"S&earch"))
self.text = wx.TextCtrl(self.panel, wx.NewId()) self.text = wx.TextCtrl(self.panel, wx.NewId())
box = wx.BoxSizer(wx.HORIZONTAL) box = wx.BoxSizer(wx.HORIZONTAL)
box.Add(lbl2, 0, wx.GROW) box.Add(lbl2, 0, wx.GROW)
box.Add(self.text, 1, wx.GROW) box.Add(self.text, 1, wx.GROW)
box.Add(wx.StaticText(self.panel, wx.NewId(), _(u"&Search in")), 0, wx.GROW) box.Add(wx.StaticText(self.panel, wx.NewId(), _(u"&Search in")), 0, wx.GROW)
self.extractor = wx.ComboBox(self.panel, wx.NewId(), choices=extractors, value=extractors[0], style=wx.CB_READONLY) self.extractor = wx.ComboBox(self.panel, wx.NewId(), choices=extractors, value=extractors[0], style=wx.CB_READONLY)
box.Add(self.extractor, 1, wx.GROW) box.Add(self.extractor, 1, wx.GROW)
self.search = wx.Button(self.panel, wx.NewId(), _(u"Sear&ch")) self.search = wx.Button(self.panel, wx.NewId(), _(u"Sear&ch"))
self.search.SetDefault() self.search.SetDefault()
box.Add(self.search, 0, wx.GROW) box.Add(self.search, 0, wx.GROW)
self.sizer.Add(box, 0, wx.GROW) self.sizer.Add(box, 0, wx.GROW)
lbl = wx.StaticText(self.panel, wx.NewId(), _(u"&Results")) lbl = wx.StaticText(self.panel, wx.NewId(), _(u"&Results"))
self.list = wx.ListBox(self.panel, wx.NewId()) self.list = wx.ListBox(self.panel, wx.NewId())
self.sizer.Add(lbl, 0, wx.GROW) self.sizer.Add(lbl, 0, wx.GROW)
self.sizer.Add(self.list, 1, wx.GROW) self.sizer.Add(self.list, 1, wx.GROW)
box1 = wx.BoxSizer(wx.HORIZONTAL) box1 = wx.BoxSizer(wx.HORIZONTAL)
box2 = wx.BoxSizer(wx.HORIZONTAL) box2 = wx.BoxSizer(wx.HORIZONTAL)
box1.Add(wx.StaticText(self.panel, wx.NewId(), _(u"P&osition")), 0, wx.GROW) box1.Add(wx.StaticText(self.panel, wx.NewId(), _(u"P&osition")), 0, wx.GROW)
self.time_slider = wx.Slider(self.panel, -1) self.time_slider = wx.Slider(self.panel, -1)
box1.Add(self.time_slider, 1, wx.GROW) box1.Add(self.time_slider, 1, wx.GROW)
box1.Add(wx.StaticText(self.panel, wx.NewId(), _(u"&Volume")), 0, wx.GROW) box1.Add(wx.StaticText(self.panel, wx.NewId(), _(u"&Volume")), 0, wx.GROW)
self.vol_slider = wx.Slider(self.panel, -1, 0, 0, 100, size=(100, -1)) self.vol_slider = wx.Slider(self.panel, -1, 0, 0, 100, size=(100, -1))
box1.Add(self.vol_slider, 1, wx.GROW) box1.Add(self.vol_slider, 1, wx.GROW)
self.previous = wx.Button(self.panel, wx.NewId(), _(u"Pre&vious")) self.previous = wx.Button(self.panel, wx.NewId(), _(u"Pre&vious"))
self.play = wx.Button(self.panel, wx.NewId(), _(u"&Play")) self.play = wx.Button(self.panel, wx.NewId(), _(u"&Play"))
self.stop = wx.Button(self.panel, wx.NewId(), _(u"Stop")) self.stop = wx.Button(self.panel, wx.NewId(), _(u"Stop"))
self.next = wx.Button(self.panel, wx.NewId(), _(u"&Next")) self.next = wx.Button(self.panel, wx.NewId(), _(u"&Next"))
box2.Add(self.previous) box2.Add(self.previous)
box2.Add(self.play, flag=wx.RIGHT, border=5) box2.Add(self.play, flag=wx.RIGHT, border=5)
box2.Add(self.stop) box2.Add(self.stop)
box2.Add(self.next) box2.Add(self.next)
self.sizer.Add(box1, 0, wx.GROW) self.sizer.Add(box1, 0, wx.GROW)
self.sizer.Add(box2, 1, wx.GROW) self.sizer.Add(box2, 1, wx.GROW)
self.progressbar = wx.Gauge(self.panel, wx.NewId(), range=100, style=wx.GA_HORIZONTAL) self.progressbar = wx.Gauge(self.panel, wx.NewId(), range=100, style=wx.GA_HORIZONTAL)
self.sizer.Add(self.progressbar, 0, wx.ALL, 5) self.sizer.Add(self.progressbar, 0, wx.ALL, 5)
self.panel.SetSizerAndFit(self.sizer) self.panel.SetSizerAndFit(self.sizer)
# self.SetClientSize(self.sizer.CalcMin()) # self.SetClientSize(self.sizer.CalcMin())
# self.Layout() # self.Layout()
# self.SetSize(self.GetBestSize()) # self.SetSize(self.GetBestSize())
def change_status(self, status): def change_status(self, status):
self.sb.SetStatusText(status) self.sb.SetStatusText(status)
def about_dialog(self, *args, **kwargs): def about_dialog(self, *args, **kwargs):
try: try:
info = wx.adv.AboutDialogInfo() info = wx.adv.AboutDialogInfo()
except: except:
info = wx.AboutDialogInfo() info = wx.AboutDialogInfo()
info.SetName(application.name) info.SetName(application.name)
info.SetVersion(application.version) info.SetVersion(application.version)
info.SetDescription(application.description) info.SetDescription(application.description)
info.SetCopyright(application.copyright) info.SetCopyright(application.copyright)
info.SetWebSite(application.url) info.SetWebSite(application.url)
info.SetTranslators(application.translators) info.SetTranslators(application.translators)
# info.SetLicence(application.licence) # info.SetLicence(application.licence)
info.AddDeveloper(application.author) info.AddDeveloper(application.author)
try: try:
wx.adv.AboutBox(info) wx.adv.AboutBox(info)
except: except:
wx.AboutBox(info) wx.AboutBox(info)
def get_text(self): def get_text(self):
t = self.text.GetValue() t = self.text.GetValue()
self.text.ChangeValue("") self.text.ChangeValue("")
return t return t
def get_item(self): def get_item(self):
return self.list.GetSelection() return self.list.GetSelection()
def get_destination_path(self, filename): def get_destination_path(self, filename):
saveFileDialog = wx.FileDialog(self, _(u"Save this file"), "", filename, _(u"Audio Files(*.mp3)|*.mp3"), wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) saveFileDialog = wx.FileDialog(self, _(u"Save this file"), "", filename, _(u"Audio Files(*.mp3)|*.mp3"), wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
if saveFileDialog.ShowModal() == wx.ID_OK: if saveFileDialog.ShowModal() == wx.ID_OK:
return saveFileDialog.GetPath() return saveFileDialog.GetPath()
saveFileDialog.Destroy() saveFileDialog.Destroy()
def notify(self, title, text): def notify(self, title, text):
try: try:
self.notification = wx.adv.NotificationMessage(title, text, parent=self) self.notification = wx.adv.NotificationMessage(title, text, parent=self)
except AttributeError: except AttributeError:
self.notification = wx.NotificationMessage(title, text) self.notification = wx.NotificationMessage(title, text)
self.notification.Show() self.notification.Show()

View File

@ -2,9 +2,9 @@
import wx import wx
class contextMenu(wx.Menu): class contextMenu(wx.Menu):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(contextMenu, self).__init__(*args, **kwargs) super(contextMenu, self).__init__(*args, **kwargs)
self.play = wx.MenuItem(self, wx.NewId(), _(u"Play/Pause")) self.play = wx.MenuItem(self, wx.NewId(), _(u"Play/Pause"))
self.download = wx.MenuItem(self, wx.NewId(), _(u"Download")) self.download = wx.MenuItem(self, wx.NewId(), _(u"Download"))
self.Append(self.play) self.Append(self.play)
self.Append(self.download) self.Append(self.download)