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"
update_url = "https://files.manuelcortez.net/music_dl/update/latest.json"
version = "2020.07.23"
update_next_version = "14775226"
update_next_version = "14775226"

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
import sys
if sys.version[0] == "3":
raise ImportError()
raise ImportError()
import os
import paths
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
def setup ():
global app
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))
global app
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))

View File

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

View File

@ -6,47 +6,47 @@ from . import player
class configuration(object):
def __init__(self):
self.view = configurationDialog(_("Settings"))
self.create_config()
self.view.get_response()
self.save()
def __init__(self):
self.view = configurationDialog(_("Settings"))
self.create_config()
self.view.get_response()
self.save()
def create_config(self):
self.output_devices = player.player.get_output_devices()
self.view.create_general(output_devices=[i["name"] for i in self.output_devices])
current_output_device = config.app["main"]["output_device"]
for i in self.output_devices:
# here we must compare against the str version of the vlc's device identifier.
if str(i["id"]) == current_output_device:
self.view.set_value("general", "output_device", i["name"])
break
self.view.realize()
extractors = get_services(import_all=True)
for i in extractors:
if hasattr(i, "settings"):
panel = getattr(i, "settings")(self.view.notebook)
self.view.notebook.InsertSubPage(1, panel, panel.name)
panel.load()
if hasattr(panel, "on_enabled"):
panel.on_enabled()
def create_config(self):
self.output_devices = player.player.get_output_devices()
self.view.create_general(output_devices=[i["name"] for i in self.output_devices])
current_output_device = config.app["main"]["output_device"]
for i in self.output_devices:
# here we must compare against the str version of the vlc's device identifier.
if str(i["id"]) == current_output_device:
self.view.set_value("general", "output_device", i["name"])
break
self.view.realize()
extractors = get_services(import_all=True)
for i in extractors:
if hasattr(i, "settings"):
panel = getattr(i, "settings")(self.view.notebook)
self.view.notebook.InsertSubPage(1, panel, panel.name)
panel.load()
if hasattr(panel, "on_enabled"):
panel.on_enabled()
def save(self):
selected_output_device = self.view.get_value("general", "output_device")
selected_device_id = None
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
# 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.
if i["name"] == bytes(selected_output_device, "utf-8"):
selected_device_id = i["id"]
break
if 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"])
for i in range(0, self.view.notebook.GetPageCount()):
page = self.view.notebook.GetPage(i)
if hasattr(page, "save"):
page.save()
config.app.write()
def save(self):
selected_output_device = self.view.get_value("general", "output_device")
selected_device_id = None
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
# 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.
if i["name"] == bytes(selected_output_device, "utf-8"):
selected_device_id = i["id"]
break
if 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"])
for i in range(0, self.view.notebook.GetPageCount()):
page = self.view.notebook.GetPage(i)
if hasattr(page, "save"):
page.save()
config.app.write()

View File

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

View File

@ -12,158 +12,158 @@ player = None
log = logging.getLogger("controller.player")
def setup():
global player
if player == None:
player = audioPlayer()
global player
if player == None:
player = audioPlayer()
class audioPlayer(object):
def __init__(self):
self.is_playing = False
self.vol = config.app["main"]["volume"]
self.is_working = False
self.queue = []
self.stopped = True
self.queue_pos = 0
self.shuffle = False
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__))
self.player = self.instance.media_player_new()
log.debug("Media player instantiated.")
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.MediaPlayerEncounteredError, self.playback_error)
log.debug("Bound media playback events.")
# configure output device
self.set_output_device(config.app["main"]["output_device"])
def __init__(self):
self.is_playing = False
self.vol = config.app["main"]["volume"]
self.is_working = False
self.queue = []
self.stopped = True
self.queue_pos = 0
self.shuffle = False
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__))
self.player = self.instance.media_player_new()
log.debug("Media player instantiated.")
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.MediaPlayerEncounteredError, self.playback_error)
log.debug("Bound media playback events.")
# configure output device
self.set_output_device(config.app["main"]["output_device"])
def get_output_devices(self):
""" Retrieve enabled output devices so we can switch or use those later. """
log.debug("Retrieving output devices...")
devices = []
mods = self.player.audio_output_device_enum()
if mods:
mod = mods
while mod:
mod = mod.contents
devices.append(dict(id=mod.device, name=mod.description))
mod = mod.next
vlc.libvlc_audio_output_device_list_release(mods)
return devices
def get_output_devices(self):
""" Retrieve enabled output devices so we can switch or use those later. """
log.debug("Retrieving output devices...")
devices = []
mods = self.player.audio_output_device_enum()
if mods:
mod = mods
while mod:
mod = mod.contents
devices.append(dict(id=mod.device, name=mod.description))
mod = mod.next
vlc.libvlc_audio_output_device_list_release(mods)
return devices
def set_output_device(self, device_id):
""" Set Output device to be used in LibVLC"""
log.debug("Setting output audio device to {device}...".format(device=device_id,))
self.player.audio_output_device_set(None, device_id)
def set_output_device(self, device_id):
""" Set Output device to be used in LibVLC"""
log.debug("Setting output audio device to {device}...".format(device=device_id,))
self.player.audio_output_device_set(None, device_id)
def play(self, item):
self.stopped = True
if self.is_working == False:
self.is_working = True
if item.download_url == "":
item.get_download_url()
log.debug("playing {0}...".format(item.download_url,))
self.stream_new = self.instance.media_new(item.download_url)
self.player.set_media(self.stream_new)
if self.player.play() == -1:
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))
self.stopped = True
self.is_working = False
self.next()
return
self.player.audio_set_volume(self.vol)
pub.sendMessage("change_status", status=_("Playing {0}.").format(item.title))
self.stopped = False
self.is_working = False
def play(self, item):
self.stopped = True
if self.is_working == False:
self.is_working = True
if item.download_url == "":
item.get_download_url()
log.debug("playing {0}...".format(item.download_url,))
self.stream_new = self.instance.media_new(item.download_url)
self.player.set_media(self.stream_new)
if self.player.play() == -1:
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))
self.stopped = True
self.is_working = False
self.next()
return
self.player.audio_set_volume(self.vol)
pub.sendMessage("change_status", status=_("Playing {0}.").format(item.title))
self.stopped = False
self.is_working = False
def next(self):
if len(self.queue) > 0:
if self.shuffle:
self.queue_pos = random.randint(0, len(self.queue)-1)
else:
if self.queue_pos < len(self.queue)-1:
self.queue_pos += 1
else:
self.queue_pos = 0
self.play(self.queue[self.queue_pos])
def next(self):
if len(self.queue) > 0:
if self.shuffle:
self.queue_pos = random.randint(0, len(self.queue)-1)
else:
if self.queue_pos < len(self.queue)-1:
self.queue_pos += 1
else:
self.queue_pos = 0
self.play(self.queue[self.queue_pos])
def previous(self):
if len(self.queue) > 0:
if self.shuffle:
self.queue_pos = random.randint(0, len(self.queue)-1)
else:
if self.queue_pos > 0:
self.queue_pos -= 1
else:
self.queue_pos = len(self.queue)-1
self.play(self.queue[self.queue_pos])
def previous(self):
if len(self.queue) > 0:
if self.shuffle:
self.queue_pos = random.randint(0, len(self.queue)-1)
else:
if self.queue_pos > 0:
self.queue_pos -= 1
else:
self.queue_pos = len(self.queue)-1
self.play(self.queue[self.queue_pos])
def stop(self):
self.player.stop()
self.stopped = True
def stop(self):
self.player.stop()
self.stopped = True
def pause(self):
self.player.pause()
if self.stopped == True:
self.stopped = False
else:
self.stopped = True
def pause(self):
self.player.pause()
if self.stopped == True:
self.stopped = False
else:
self.stopped = True
@property
def volume(self):
return self.vol
@property
def volume(self):
return self.vol
@volume.setter
def volume(self, vol):
if vol <= 100 and vol >= 0:
config.app["main"]["volume"] = vol
self.vol = vol
self.player.audio_set_volume(self.vol)
@volume.setter
def volume(self, vol):
if vol <= 100 and vol >= 0:
config.app["main"]["volume"] = vol
self.vol = vol
self.player.audio_set_volume(self.vol)
def play_all(self, list_of_items, playing=0, shuffle=False):
if list_of_items != self.queue:
self.queue = list_of_items
self.shuffle = shuffle
self.queue_pos = playing
self.play(self.queue[self.queue_pos])
def play_all(self, list_of_items, playing=0, shuffle=False):
if list_of_items != self.queue:
self.queue = list_of_items
self.shuffle = shuffle
self.queue_pos = playing
self.play(self.queue[self.queue_pos])
def end_callback(self, event, *args, **kwargs):
#https://github.com/ZeBobo5/Vlc.DotNet/issues/4
call_threaded(self.next)
def end_callback(self, event, *args, **kwargs):
#https://github.com/ZeBobo5/Vlc.DotNet/issues/4
call_threaded(self.next)
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."""
if item.download_url == "":
item.get_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_path = os.path.join(os.path.dirname(path), temporary_filename)
# 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,)])
transcoder = transcoding_instance.media_player_new()
transcoder.set_mrl(item.download_url)
pub.sendMessage("change_status", status=_(u"Downloading {0}.").format(item.title,))
media = transcoder.get_media()
transcoder.play()
while True:
state = media.get_state()
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))
if str(state) == 'State.Ended':
break
elif str(state) == 'state.error':
os.remove(temporary_path)
break
transcoder.release()
os.rename(temporary_path, path)
log.debug("Download finished sucsessfully.")
pub.sendMessage("download_finished", file=os.path.basename(path))
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."""
if item.download_url == "":
item.get_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_path = os.path.join(os.path.dirname(path), temporary_filename)
# 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,)])
transcoder = transcoding_instance.media_player_new()
transcoder.set_mrl(item.download_url)
pub.sendMessage("change_status", status=_(u"Downloading {0}.").format(item.title,))
media = transcoder.get_media()
transcoder.play()
while True:
state = media.get_state()
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))
if str(state) == 'State.Ended':
break
elif str(state) == 'state.error':
os.remove(temporary_path)
break
transcoder.release()
os.rename(temporary_path, path)
log.debug("Download finished sucsessfully.")
pub.sendMessage("download_finished", file=os.path.basename(path))
def playback_error(self, event):
pub.sendMessage("notify", title=_("Error"), message=_("There was an error while trying to access the file you have requested."))
def playback_error(self, event):
pub.sendMessage("notify", title=_("Error"), message=_("There was an error while trying to access the file you have requested."))
def __del__(self):
self.event_manager.event_detach(vlc.EventType.MediaPlayerEndReached)
if hasattr(self, "event_manager"):
self.event_manager.event_detach(vlc.EventType.MediaPlayerEncounteredError, self.playback_error)
def __del__(self):
self.event_manager.event_detach(vlc.EventType.MediaPlayerEndReached)
if hasattr(self, "event_manager"):
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
def setup():
fix_requests.fix()
fix_requests.fix()

View File

@ -7,6 +7,6 @@ import paths
log = logging.getLogger("fixes.fix_requests")
def fix():
log.debug("Applying fix for requests...")
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("Applying fix for requests...")
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"],))

View File

@ -4,9 +4,9 @@ import winpaths
from ctypes import wintypes
def _get_path_buf(csidl):
path_buf = ctypes.create_unicode_buffer(wintypes.MAX_PATH)
result = winpaths._SHGetFolderPath(0, csidl, 0, 0, path_buf)
return path_buf.value
path_buf = ctypes.create_unicode_buffer(wintypes.MAX_PATH)
result = winpaths._SHGetFolderPath(0, csidl, 0, 0, path_buf)
return path_buf.value
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")
def setup():
lang = locale.getdefaultlocale()[0]
os.environ["lang"] = lang
log.debug("System detected language: {0}".format(lang,))
if sys.version[0] == "3":
gettext.install("musicdl", localedir=os.path.join(paths.app_path(), "locales"))
else:
gettext.install("musicdl", localedir=os.path.join(paths.app_path(), "locales"), unicode=True)
lang = locale.getdefaultlocale()[0]
os.environ["lang"] = lang
log.debug("System detected language: {0}".format(lang,))
if sys.version[0] == "3":
gettext.install("musicdl", localedir=os.path.join(paths.app_path(), "locales"))
else:
gettext.install("musicdl", localedir=os.path.join(paths.app_path(), "locales"), unicode=True)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
############################################################
# Copyright (c) 2018 Manuel Cortez <manuel@manuelcortez.net>
#
#
# 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
# the Free Software Foundation, either version 2 of the License, or
@ -27,37 +27,37 @@ from utils import call_threaded
from . import wx_ui
class reportBug(object):
def __init__(self):
self.dialog = wx_ui.reportBugDialog()
widgetUtils.connect_event(self.dialog.ok, widgetUtils.BUTTON_PRESSED, self.send)
self.dialog.get_response()
def __init__(self):
self.dialog = wx_ui.reportBugDialog()
widgetUtils.connect_event(self.dialog.ok, widgetUtils.BUTTON_PRESSED, self.send)
self.dialog.get_response()
def do_report(self, *args, **kwargs):
r = requests.post(*args, **kwargs)
if r.status_code > 300:
wx.CallAfter(self.dialog.error)
wx.CallAfter(self.dialog.progress.Destroy)
wx.CallAfter(self.dialog.success, r.json()["data"]["issue"]["id"])
def do_report(self, *args, **kwargs):
r = requests.post(*args, **kwargs)
if r.status_code > 300:
wx.CallAfter(self.dialog.error)
wx.CallAfter(self.dialog.progress.Destroy)
wx.CallAfter(self.dialog.success, r.json()["data"]["issue"]["id"])
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") == "":
self.dialog.no_filled()
return
if self.dialog.get("agree") == False:
self.dialog.no_checkbox()
return
title = self.dialog.get("summary")
body = self.dialog.get("description")
issue_type = "issue" # for now just have issue
app_type = storage.app_type
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_contact_type = "email" # For now just email is supported in the issue reporter
reporter_contact_handle = self.dialog.get("email")
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)
auth=HTTPBasicAuth(application.bts_name, application.bts_access_token)
url = "{bts_url}/issue/new".format(bts_url=application.bts_url)
call_threaded(self.do_report, url, json=json, auth=auth)
self.dialog.show_progress()
self.dialog.EndModal(wx.ID_OK)
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") == "":
self.dialog.no_filled()
return
if self.dialog.get("agree") == False:
self.dialog.no_checkbox()
return
title = self.dialog.get("summary")
body = self.dialog.get("description")
issue_type = "issue" # for now just have issue
app_type = storage.app_type
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_contact_type = "email" # For now just email is supported in the issue reporter
reporter_contact_handle = self.dialog.get("email")
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)
auth=HTTPBasicAuth(application.bts_name, application.bts_access_token)
url = "{bts_url}/issue/new".format(bts_url=application.bts_url)
call_threaded(self.do_report, url, json=json, auth=auth)
self.dialog.show_progress()
self.dialog.EndModal(wx.ID_OK)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
############################################################
# Copyright (c) 2018 Manuel cortez <manuel@manuelcortez.net>
#
#
# 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
# the Free Software Foundation, either version 2 of the License, or
@ -21,89 +21,89 @@ import widgetUtils
import application
class reportBugDialog(widgetUtils.BaseDialog):
def __init__(self):
super(reportBugDialog, self).__init__(parent=None, id=wx.NewId())
self.SetTitle(_(u"Report an error"))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
def __init__(self):
super(reportBugDialog, self).__init__(parent=None, id=wx.NewId())
self.SetTitle(_(u"Report an error"))
panel = wx.Panel(self)
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)
self.summary = wx.TextCtrl(panel, -1)
dc = wx.WindowDC(self.summary)
dc.SetFont(self.summary.GetFont())
self.summary.SetSize(dc.GetTextExtent("a"*80))
summaryB = wx.BoxSizer(wx.HORIZONTAL)
summaryB.Add(summaryLabel, 0, wx.ALL, 5)
summaryB.Add(self.summary, 0, wx.ALL, 5)
sizer.Add(summaryB, 0, wx.ALL, 5)
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)
dc = wx.WindowDC(self.summary)
dc.SetFont(self.summary.GetFont())
self.summary.SetSize(dc.GetTextExtent("a"*80))
summaryB = wx.BoxSizer(wx.HORIZONTAL)
summaryB.Add(summaryLabel, 0, wx.ALL, 5)
summaryB.Add(self.summary, 0, wx.ALL, 5)
sizer.Add(summaryB, 0, wx.ALL, 5)
first_nameLabel = wx.StaticText(panel, -1, _(u"First Name"), size=wx.DefaultSize)
self.first_name = wx.TextCtrl(panel, -1)
dc = wx.WindowDC(self.first_name)
dc.SetFont(self.first_name.GetFont())
self.first_name.SetSize(dc.GetTextExtent("a"*40))
first_nameB = wx.BoxSizer(wx.HORIZONTAL)
first_nameB.Add(first_nameLabel, 0, wx.ALL, 5)
first_nameB.Add(self.first_name, 0, wx.ALL, 5)
sizer.Add(first_nameB, 0, wx.ALL, 5)
first_nameLabel = wx.StaticText(panel, -1, _(u"First Name"), size=wx.DefaultSize)
self.first_name = wx.TextCtrl(panel, -1)
dc = wx.WindowDC(self.first_name)
dc.SetFont(self.first_name.GetFont())
self.first_name.SetSize(dc.GetTextExtent("a"*40))
first_nameB = wx.BoxSizer(wx.HORIZONTAL)
first_nameB.Add(first_nameLabel, 0, wx.ALL, 5)
first_nameB.Add(self.first_name, 0, wx.ALL, 5)
sizer.Add(first_nameB, 0, wx.ALL, 5)
last_nameLabel = wx.StaticText(panel, -1, _(u"Last Name"), size=wx.DefaultSize)
self.last_name = wx.TextCtrl(panel, -1)
dc = wx.WindowDC(self.last_name)
dc.SetFont(self.last_name.GetFont())
self.last_name.SetSize(dc.GetTextExtent("a"*40))
last_nameB = wx.BoxSizer(wx.HORIZONTAL)
last_nameB.Add(last_nameLabel, 0, wx.ALL, 5)
last_nameB.Add(self.last_name, 0, wx.ALL, 5)
sizer.Add(last_nameB, 0, wx.ALL, 5)
last_nameLabel = wx.StaticText(panel, -1, _(u"Last Name"), size=wx.DefaultSize)
self.last_name = wx.TextCtrl(panel, -1)
dc = wx.WindowDC(self.last_name)
dc.SetFont(self.last_name.GetFont())
self.last_name.SetSize(dc.GetTextExtent("a"*40))
last_nameB = wx.BoxSizer(wx.HORIZONTAL)
last_nameB.Add(last_nameLabel, 0, wx.ALL, 5)
last_nameB.Add(self.last_name, 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)
self.email = wx.TextCtrl(panel, -1)
dc = wx.WindowDC(self.email)
dc.SetFont(self.email.GetFont())
self.email.SetSize(dc.GetTextExtent("a"*30))
emailB = wx.BoxSizer(wx.HORIZONTAL)
emailB.Add(emailLabel, 0, wx.ALL, 5)
emailB.Add(self.email, 0, wx.ALL, 5)
sizer.Add(emailB, 0, wx.ALL, 5)
emailLabel = wx.StaticText(panel, -1, _(u"Email address (Will not be public)"), size=wx.DefaultSize)
self.email = wx.TextCtrl(panel, -1)
dc = wx.WindowDC(self.email)
dc.SetFont(self.email.GetFont())
self.email.SetSize(dc.GetTextExtent("a"*30))
emailB = wx.BoxSizer(wx.HORIZONTAL)
emailB.Add(emailLabel, 0, wx.ALL, 5)
emailB.Add(self.email, 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)
self.description = wx.TextCtrl(panel, -1, style=wx.TE_MULTILINE)
dc = wx.WindowDC(self.description)
dc.SetFont(self.description.GetFont())
(x, y) = dc.GetMultiLineTextExtent("0"*2000)
self.description.SetSize((x, y))
descBox = wx.BoxSizer(wx.HORIZONTAL)
descBox.Add(descriptionLabel, 0, wx.ALL, 5)
descBox.Add(self.description, 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.SetValue(False)
sizer.Add(self.agree, 0, wx.ALL, 5)
self.ok = wx.Button(panel, wx.ID_OK, _(u"Send report"))
self.ok.SetDefault()
cancel = wx.Button(panel, wx.ID_CANCEL, _(u"Cancel"))
btnBox = wx.BoxSizer(wx.HORIZONTAL)
btnBox.Add(self.ok, 0, wx.ALL, 5)
btnBox.Add(cancel, 0, wx.ALL, 5)
sizer.Add(btnBox, 0, wx.ALL, 5)
panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin())
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)
dc = wx.WindowDC(self.description)
dc.SetFont(self.description.GetFont())
(x, y) = dc.GetMultiLineTextExtent("0"*2000)
self.description.SetSize((x, y))
descBox = wx.BoxSizer(wx.HORIZONTAL)
descBox.Add(descriptionLabel, 0, wx.ALL, 5)
descBox.Add(self.description, 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.SetValue(False)
sizer.Add(self.agree, 0, wx.ALL, 5)
self.ok = wx.Button(panel, wx.ID_OK, _(u"Send report"))
self.ok.SetDefault()
cancel = wx.Button(panel, wx.ID_CANCEL, _(u"Cancel"))
btnBox = wx.BoxSizer(wx.HORIZONTAL)
btnBox.Add(self.ok, 0, wx.ALL, 5)
btnBox.Add(cancel, 0, wx.ALL, 5)
sizer.Add(btnBox, 0, wx.ALL, 5)
panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin())
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()
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()
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()
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()
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()
self.Destroy()
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()
self.Destroy()
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()
self.Destroy()
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()
self.Destroy()
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.ShowModal()
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.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("Using Python version {0}".format(sys.version,))
if sys.version[0] == "2":
if hasattr(sys, "frozen"):
log.debug("Applying fixes for Python 2 frozen executables.")
import fixes
fixes.setup()
if hasattr(sys, "frozen"):
log.debug("Applying fixes for Python 2 frozen executables.")
import fixes
fixes.setup()
import i18n
i18n.setup()
config.setup()
@ -31,12 +31,12 @@ import widgetUtils
import paths
def setup():
log.debug("Starting music-dl %s" % (application.version,))
log.debug("Application path is %s" % (paths.app_path(),))
from controller import mainController
app = widgetUtils.mainLoopObject()
log.debug("Created Application mainloop object")
r = mainController.Controller()
app.run()
log.debug("Starting music-dl %s" % (application.version,))
log.debug("Application path is %s" % (paths.app_path(),))
from controller import mainController
app = widgetUtils.mainLoopObject()
log.debug("Created Application mainloop object")
r = mainController.Controller()
app.run()
setup()

View File

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

View File

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

View File

@ -1,3 +1,3 @@
#!/usr/bin/env python
# -*- 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")
class baseInterface(object):
name = "base"
enabled = False
needs_transcode = False
results = []
name = "base"
enabled = False
needs_transcode = False
results = []
def __init__(self):
super(baseInterface, self).__init__()
log.debug("started extraction service for {0}".format(self.name,))
def __init__(self):
super(baseInterface, self).__init__()
log.debug("started extraction service for {0}".format(self.name,))
def search(self, text, *args, **kwargs):
raise NotImplementedError()
def search(self, text, *args, **kwargs):
raise NotImplementedError()
def get_download_url(self, url):
raise NotImplementedError()
def get_download_url(self, url):
raise NotImplementedError()
def format_track(self, item):
raise NotImplementedError()
def format_track(self, item):
raise NotImplementedError()
def get_file_format(self):
return "mp3"
def get_file_format(self):
return "mp3"
def transcoder_enabled(self):
return False
def transcoder_enabled(self):
return False
def get_metadata(self, item):
data = dict()
keys = ["title", "album", "artist", "tracknumber"]
for k in keys:
if hasattr(item, k):
data[k] = getattr(item, k)
return data
def get_metadata(self, item):
data = dict()
keys = ["title", "album", "artist", "tracknumber"]
for k in keys:
if hasattr(item, k):
data[k] = getattr(item, k)
return data
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):
self.extractor = extractor
self.bitrate = 0
self.title = ""
self.artist = ""
self.duration = ""
self.size = 0
self.url = ""
self.download_url = ""
self.info = None
def __init__(self, extractor):
self.extractor = extractor
self.bitrate = 0
self.title = ""
self.artist = ""
self.duration = ""
self.size = 0
self.url = ""
self.download_url = ""
self.info = None
def format_track(self):
return self.extractor.format_track(self)
def format_track(self):
return self.extractor.format_track(self)
def get_download_url(self):
self.download_url = self.extractor.get_download_url(self.url)
def get_download_url(self):
self.download_url = self.extractor.get_download_url(self.url)
def get_metadata(self):
return self.extractor.get_metadata(self)
def get_metadata(self):
return self.extractor.get_metadata(self)
class baseSettings(wx.Panel):
config_section = "base"
config_section = "base"
def __init__(self, *args, **kwargs):
super(baseSettings, self).__init__(*args, **kwargs)
self.map = []
def __init__(self, *args, **kwargs):
super(baseSettings, self).__init__(*args, **kwargs)
self.map = []
def save(self):
for i in self.map:
config.app["services"][self.config_section][i[0]] = i[1].GetValue()
def save(self):
for i in self.map:
config.app["services"][self.config_section][i[0]] = i[1].GetValue()
def load(self):
for i in self.map:
if i[0] in config.app["services"][self.config_section]:
i[1].SetValue(config.app["services"][self.config_section][i[0]])
else:
log.error("No key available: {key} on extractor {extractor}".format(key=i[0], extractor=self.config_section))
def load(self):
for i in self.map:
if i[0] in config.app["services"][self.config_section]:
i[1].SetValue(config.app["services"][self.config_section][i[0]])
else:
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")
class interface(base.baseInterface):
name = "tidal"
if config.app != None: # Workaround for cx_freeze 6.2 in python 3.7.
enabled = config.app["services"]["tidal"].get("enabled")
# This should not be enabled if credentials are not set in config.
if config.app["services"]["tidal"]["username"] == "" or config.app["services"]["tidal"]["password"] == "":
enabled = False
else:
enabled = False
name = "tidal"
if config.app != None: # Workaround for cx_freeze 6.2 in python 3.7.
enabled = config.app["services"]["tidal"].get("enabled")
# This should not be enabled if credentials are not set in config.
if config.app["services"]["tidal"]["username"] == "" or config.app["services"]["tidal"]["password"] == "":
enabled = False
else:
enabled = False
def __init__(self):
super(interface, self).__init__()
self.setup()
def __init__(self):
super(interface, self).__init__()
self.setup()
def setup(self):
# Assign quality or switch to high if not specified/not found.
if hasattr(tidalapi.Quality, config.app["services"]["tidal"]["quality"]):
quality = getattr(tidalapi.Quality, config.app["services"]["tidal"]["quality"])
else:
quality = tidalapi.Quality.high
# We need to instantiate a config object to pass quality settings.
_config = tidalapi.Config(quality=quality)
username = config.app["services"]["tidal"]["username"]
password = config.app["services"]["tidal"]["password"]
log.debug("Using quality: %s" % (quality,))
self.session = tidalapi.Session(config=_config)
self.session.login(username=username, password=password)
def setup(self):
# Assign quality or switch to high if not specified/not found.
if hasattr(tidalapi.Quality, config.app["services"]["tidal"]["quality"]):
quality = getattr(tidalapi.Quality, config.app["services"]["tidal"]["quality"])
else:
quality = tidalapi.Quality.high
# We need to instantiate a config object to pass quality settings.
_config = tidalapi.Config(quality=quality)
username = config.app["services"]["tidal"]["username"]
password = config.app["services"]["tidal"]["password"]
log.debug("Using quality: %s" % (quality,))
self.session = tidalapi.Session(config=_config)
self.session.login(username=username, password=password)
def get_file_format(self):
""" Returns the file format (mp3 or flac) depending in quality set. """
if config.app["services"]["tidal"]["quality"] == "lossless":
self.file_extension = "flac"
elif config.app["services"]["tidal"]["avoid_transcoding"] == True:
self.file_extension = "m4a"
else:
self.file_extension = "mp3"
return self.file_extension
def get_file_format(self):
""" Returns the file format (mp3 or flac) depending in quality set. """
if config.app["services"]["tidal"]["quality"] == "lossless":
self.file_extension = "flac"
elif config.app["services"]["tidal"]["avoid_transcoding"] == True:
self.file_extension = "m4a"
else:
self.file_extension = "mp3"
return self.file_extension
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.
# toDo: Shall this be a setting and allow MusicDL to spit out the m4a file directly?
if config.app["services"]["tidal"]["quality"] == "lossless":
return False
elif config.app["services"]["tidal"]["avoid_transcoding"]:
return False
else:
return True
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.
# toDo: Shall this be a setting and allow MusicDL to spit out the m4a file directly?
if config.app["services"]["tidal"]["quality"] == "lossless":
return False
elif config.app["services"]["tidal"]["avoid_transcoding"]:
return False
else:
return True
def search(self, text, page=1):
if text == "" or text == None:
raise ValueError("Text must be passed and should not be blank.")
log.debug("Retrieving data from Tidal...")
# Check for top:// protocol.
if text.startswith("top://"):
text = text.replace("top://", "")
return self.search_for_top(text)
fieldtypes = ["artist", "album", "playlist"]
field = "track"
for i in fieldtypes:
if text.startswith(i+"://"):
field = i
text = text.replace(i+"://", "")
log.debug("Searching for %s..." % (field))
search_response = self.session.search(value=text, field=field)
self.results = []
if field == "track":
data = search_response.tracks
elif field == "artist":
data = []
artist = search_response.artists[0].id
if config.app["services"]["tidal"]["include_albums"]:
albums = self.session.get_artist_albums(artist)
for album in albums:
tracks = self.session.get_album_tracks(album.id)
for track in tracks:
track.album = album
data.append(track)
if config.app["services"]["tidal"]["include_compilations"]:
compilations = self.session.get_artist_albums_other(artist)
for album in compilations:
tracks = self.session.get_album_tracks(album.id)
for track in tracks:
data.append(track)
if config.app["services"]["tidal"]["include_singles"]:
singles = self.session.get_artist_albums_ep_singles(artist)
for album in singles:
tracks = self.session.get_album_tracks(album.id)
for track in tracks:
# track.single = True
data.append(track)
for search_result in data:
s = base.song(self)
s.title = search_result.name
s.artist = search_result.artist.name
s.duration = seconds_to_string(search_result.duration)
s.url = search_result.id
s.tracknumber = str(search_result.track_num)
s.album = search_result.album.name
if search_result.album.num_tracks == None:
s.single = True
s.info = search_result
self.results.append(s)
log.debug("{0} results found.".format(len(self.results)))
def search(self, text, page=1):
if text == "" or text == None:
raise ValueError("Text must be passed and should not be blank.")
log.debug("Retrieving data from Tidal...")
# Check for top:// protocol.
if text.startswith("top://"):
text = text.replace("top://", "")
return self.search_for_top(text)
fieldtypes = ["artist", "album", "playlist"]
field = "track"
for i in fieldtypes:
if text.startswith(i+"://"):
field = i
text = text.replace(i+"://", "")
log.debug("Searching for %s..." % (field))
search_response = self.session.search(value=text, field=field)
self.results = []
if field == "track":
data = search_response.tracks
elif field == "artist":
data = []
artist = search_response.artists[0].id
if config.app["services"]["tidal"]["include_albums"]:
albums = self.session.get_artist_albums(artist)
for album in albums:
tracks = self.session.get_album_tracks(album.id)
for track in tracks:
track.album = album
data.append(track)
if config.app["services"]["tidal"]["include_compilations"]:
compilations = self.session.get_artist_albums_other(artist)
for album in compilations:
tracks = self.session.get_album_tracks(album.id)
for track in tracks:
data.append(track)
if config.app["services"]["tidal"]["include_singles"]:
singles = self.session.get_artist_albums_ep_singles(artist)
for album in singles:
tracks = self.session.get_album_tracks(album.id)
for track in tracks:
# track.single = True
data.append(track)
for search_result in data:
s = base.song(self)
s.title = search_result.name
s.artist = search_result.artist.name
s.duration = seconds_to_string(search_result.duration)
s.url = search_result.id
s.tracknumber = str(search_result.track_num)
s.album = search_result.album.name
if search_result.album.num_tracks == None:
s.single = True
s.info = search_result
self.results.append(s)
log.debug("{0} results found.".format(len(self.results)))
def search_for_top(self, artist):
search_response = self.session.search(value=artist, field="artist")
self.results = []
artist = search_response.artists[0].id
results = self.session.get_artist_top_tracks(artist)
for search_result in results:
s = base.song(self)
s.title = search_result.name
s.artist = search_result.artist.name
s.duration = seconds_to_string(search_result.duration)
s.url = search_result.id
s.tracknumber = str(search_result.track_num)
s.album = search_result.album.name
if search_result.album.num_tracks == None:
s.single = True
s.info = search_result
self.results.append(s)
log.debug("{0} results found.".format(len(self.results)))
def search_for_top(self, artist):
search_response = self.session.search(value=artist, field="artist")
self.results = []
artist = search_response.artists[0].id
results = self.session.get_artist_top_tracks(artist)
for search_result in results:
s = base.song(self)
s.title = search_result.name
s.artist = search_result.artist.name
s.duration = seconds_to_string(search_result.duration)
s.url = search_result.id
s.tracknumber = str(search_result.track_num)
s.album = search_result.album.name
if search_result.album.num_tracks == None:
s.single = True
s.info = search_result
self.results.append(s)
log.debug("{0} results found.".format(len(self.results)))
def format_number(self, number):
if number < 10:
return "0%d" % (number)
else:
return number
def format_number(self, number):
if number < 10:
return "0%d" % (number)
else:
return number
def get_download_url(self, url):
url = self.session.get_media_url(url)
if url.startswith("https://") or url.startswith("http://") == False:
url = "rtmp://"+url
return url
def get_download_url(self, url):
url = self.session.get_media_url(url)
if url.startswith("https://") or url.startswith("http://") == False:
url = "rtmp://"+url
return url
def format_track(self, item):
if not hasattr(item, "single"):
return "{0}. {1}".format(self.format_number(int(item.tracknumber)), item.title)
else:
return "{title}. {artist}. {duration}".format(title=item.title, duration=item.duration, artist=item.artist)
def format_track(self, item):
if not hasattr(item, "single"):
return "{0}. {1}".format(self.format_number(int(item.tracknumber)), item.title)
else:
return "{title}. {artist}. {duration}".format(title=item.title, duration=item.duration, artist=item.artist)
class settings(base.baseSettings):
name = _("Tidal")
config_section = "tidal"
name = _("Tidal")
config_section = "tidal"
def get_quality_list(self):
results = dict(low=_("Low"), high=_("High"), lossless=_("Lossless"))
return results
def get_quality_list(self):
results = dict(low=_("Low"), high=_("High"), lossless=_("Lossless"))
return results
def get_quality_value(self, *args, **kwargs):
q = self.get_quality_list()
for i in q.keys():
if q.get(i) == self.quality.GetStringSelection():
return i
def get_quality_value(self, *args, **kwargs):
q = self.get_quality_list()
for i in q.keys():
if q.get(i) == self.quality.GetStringSelection():
return i
def set_quality_value(self, value, *args, **kwargs):
q = self.get_quality_list()
for i in q.keys():
if i == value:
self.quality.SetStringSelection(q.get(i))
break
def set_quality_value(self, value, *args, **kwargs):
q = self.get_quality_list()
for i in q.keys():
if i == value:
self.quality.SetStringSelection(q.get(i))
break
def __init__(self, parent):
super(settings, self).__init__(parent=parent)
sizer = wx.BoxSizer(wx.VERTICAL)
self.enabled = wx.CheckBox(self, wx.NewId(), _("Enable this service"))
self.enabled.Bind(wx.EVT_CHECKBOX, self.on_enabled)
self.map.append(("enabled", self.enabled))
sizer.Add(self.enabled, 0, wx.ALL, 5)
self.avoid_transcoding = wx.CheckBox(self, wx.NewId(), _("Avoid transcoding when downloading"))
self.map.append(("avoid_transcoding", self.avoid_transcoding))
sizer.Add(self.avoid_transcoding, 0, wx.ALL, 5)
username = wx.StaticText(self, wx.NewId(), _("Tidal username or email address"))
self.username = wx.TextCtrl(self, wx.NewId())
usernamebox = wx.BoxSizer(wx.HORIZONTAL)
usernamebox.Add(username, 0, wx.ALL, 5)
usernamebox.Add(self.username, 0, wx.ALL, 5)
sizer.Add(usernamebox, 0, wx.ALL, 5)
self.map.append(("username", self.username))
password = wx.StaticText(self, wx.NewId(), _("Password"))
self.password = wx.TextCtrl(self, wx.NewId(), style=wx.TE_PASSWORD)
passwordbox = wx.BoxSizer(wx.HORIZONTAL)
passwordbox.Add(password, 0, wx.ALL, 5)
passwordbox.Add(self.password, 0, wx.ALL, 5)
sizer.Add(passwordbox, 0, wx.ALL, 5)
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.Bind(wx.EVT_BUTTON, self.on_get_account)
sizer.Add(self.get_account, 0, wx.ALL, 5)
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)
qualitybox = wx.BoxSizer(wx.HORIZONTAL)
qualitybox.Add(quality, 0, wx.ALL, 5)
qualitybox.Add(self.quality, 0, wx.ALL, 5)
sizer.Add(qualitybox, 0, wx.ALL, 5)
# Monkeypatch for getting the right quality value here.
self.quality.GetValue = self.get_quality_value
self.quality.SetValue = self.set_quality_value
self.map.append(("quality", self.quality))
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_compilations = wx.CheckBox(include.GetStaticBox(), wx.NewId(), _("Include compilations"))
self.include_singles = wx.CheckBox(include.GetStaticBox(), wx.NewId(), _("Include singles"))
sizer.Add(include, 0, wx.ALL, 5)
self.map.append(("include_albums", self.include_albums))
self.map.append(("include_compilations", self.include_compilations))
self.map.append(("include_singles", self.include_singles))
self.SetSizer(sizer)
def __init__(self, parent):
super(settings, self).__init__(parent=parent)
sizer = wx.BoxSizer(wx.VERTICAL)
self.enabled = wx.CheckBox(self, wx.NewId(), _("Enable this service"))
self.enabled.Bind(wx.EVT_CHECKBOX, self.on_enabled)
self.map.append(("enabled", self.enabled))
sizer.Add(self.enabled, 0, wx.ALL, 5)
self.avoid_transcoding = wx.CheckBox(self, wx.NewId(), _("Avoid transcoding when downloading"))
self.map.append(("avoid_transcoding", self.avoid_transcoding))
sizer.Add(self.avoid_transcoding, 0, wx.ALL, 5)
username = wx.StaticText(self, wx.NewId(), _("Tidal username or email address"))
self.username = wx.TextCtrl(self, wx.NewId())
usernamebox = wx.BoxSizer(wx.HORIZONTAL)
usernamebox.Add(username, 0, wx.ALL, 5)
usernamebox.Add(self.username, 0, wx.ALL, 5)
sizer.Add(usernamebox, 0, wx.ALL, 5)
self.map.append(("username", self.username))
password = wx.StaticText(self, wx.NewId(), _("Password"))
self.password = wx.TextCtrl(self, wx.NewId(), style=wx.TE_PASSWORD)
passwordbox = wx.BoxSizer(wx.HORIZONTAL)
passwordbox.Add(password, 0, wx.ALL, 5)
passwordbox.Add(self.password, 0, wx.ALL, 5)
sizer.Add(passwordbox, 0, wx.ALL, 5)
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.Bind(wx.EVT_BUTTON, self.on_get_account)
sizer.Add(self.get_account, 0, wx.ALL, 5)
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)
qualitybox = wx.BoxSizer(wx.HORIZONTAL)
qualitybox.Add(quality, 0, wx.ALL, 5)
qualitybox.Add(self.quality, 0, wx.ALL, 5)
sizer.Add(qualitybox, 0, wx.ALL, 5)
# Monkeypatch for getting the right quality value here.
self.quality.GetValue = self.get_quality_value
self.quality.SetValue = self.set_quality_value
self.map.append(("quality", self.quality))
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_compilations = wx.CheckBox(include.GetStaticBox(), wx.NewId(), _("Include compilations"))
self.include_singles = wx.CheckBox(include.GetStaticBox(), wx.NewId(), _("Include singles"))
sizer.Add(include, 0, wx.ALL, 5)
self.map.append(("include_albums", self.include_albums))
self.map.append(("include_compilations", self.include_compilations))
self.map.append(("include_singles", self.include_singles))
self.SetSizer(sizer)
def on_enabled(self, *args, **kwargs):
for i in self.map:
if i[1] != self.enabled:
if self.enabled.GetValue() == True:
i[1].Enable(True)
else:
i[1].Enable(False)
def on_enabled(self, *args, **kwargs):
for i in self.map:
if i[1] != self.enabled:
if self.enabled.GetValue() == True:
i[1].Enable(True)
else:
i[1].Enable(False)
def on_get_account(self, *args, **kwargs):
webbrowser.open_new_tab("https://tidal.com")
def on_get_account(self, *args, **kwargs):
webbrowser.open_new_tab("https://tidal.com")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,24 +9,24 @@ from fixes import fix_requests
# Let's import the reload function
if sys.version[0] == "3":
from imp import reload
from imp import reload
class fixesTestCase(unittest.TestCase):
# def test_winpaths_error_in_python3(self):
# """ 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.
# # Therefore this test and the corresponding issue should be removed.
# if sys.version[0] != "3":
# return
# # A reload of winpaths is needed to rever the fix of winpaths, if has been applied before
# reload(winpaths)
# self.assertRaises(AttributeError, winpaths.get_appdata)
# def test_winpaths_error_in_python3(self):
# """ 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.
# # Therefore this test and the corresponding issue should be removed.
# if sys.version[0] != "3":
# return
# # A reload of winpaths is needed to rever the fix of winpaths, if has been applied before
# reload(winpaths)
# self.assertRaises(AttributeError, winpaths.get_appdata)
def test_requests_fix(self):
""" Testing the requests fix and check if the certificates file exists in the provided path. """
fix_requests.fix()
self.assertTrue(os.path.exists(os.environ["REQUESTS_CA_BUNDLE"]))
def test_requests_fix(self):
""" Testing the requests fix and check if the certificates file exists in the provided path. """
fix_requests.fix()
self.assertTrue(os.path.exists(os.environ["REQUESTS_CA_BUNDLE"]))
if __name__ == "__main__":
unittest.main()
unittest.main()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,27 +8,27 @@ from . import utils
progress_dialog = None
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)
if dialog.ShowModal() == wx.ID_YES:
return True
else:
return False
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:
return True
else:
return False
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):
wx.CallAfter(_progress_callback, total_downloaded, total_size)
wx.CallAfter(_progress_callback, total_downloaded, total_size)
def _progress_callback(total_downloaded, total_size):
global progress_dialog
if progress_dialog == None:
progress_dialog = create_progress_dialog()
progress_dialog.Show()
if total_downloaded == total_size:
progress_dialog.Destroy()
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)))
global progress_dialog
if progress_dialog == None:
progress_dialog = create_progress_dialog()
progress_dialog.Show()
if total_downloaded == total_size:
progress_dialog.Destroy()
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)))
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")
def call_threaded(func, *args, **kwargs):
#Call the given function in a daemonized thread and return the thread.
def new_func(*a, **k):
func(*a, **k)
thread = threading.Thread(target=new_func, args=args, kwargs=kwargs)
thread.daemon = True
thread.start()
return thread
#Call the given function in a daemonized thread and return the thread.
def new_func(*a, **k):
func(*a, **k)
thread = threading.Thread(target=new_func, args=args, kwargs=kwargs)
thread.daemon = True
thread.start()
return 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
Note: If the function provided takes time to execute, this time is NOT taken from the next wait period
"""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
t = RepeatingTimer(30.0, f, args=[], kwargs={})
t.start()
t.cancel() # stop the timer's actions
"""
t = RepeatingTimer(30.0, f, args=[], kwargs={})
t.start()
t.cancel() # stop the timer's actions
"""
def __init__(self, interval, function, daemon=True, *args, **kwargs):
threading.Thread.__init__(self)
self.daemon = daemon
self.interval = float(interval)
self.function = function
self.args = args
self.kwargs = kwargs
self.finished = threading.Event()
def __init__(self, interval, function, daemon=True, *args, **kwargs):
threading.Thread.__init__(self)
self.daemon = daemon
self.interval = float(interval)
self.function = function
self.args = args
self.kwargs = kwargs
self.finished = threading.Event()
def cancel(self):
"""Stop the timer if it hasn't finished yet"""
log.debug("Stopping repeater for %s" % (self.function,))
self.finished.set()
stop = cancel
def cancel(self):
"""Stop the timer if it hasn't finished yet"""
log.debug("Stopping repeater for %s" % (self.function,))
self.finished.set()
stop = cancel
def run(self):
while not self.finished.is_set():
self.finished.wait(self.interval)
if not self.finished.is_set(): #In case someone has canceled while waiting
try:
self.function(*self.args, **self.kwargs)
except:
log.exception("Execution failed. Function: %r args: %r and kwargs: %r" % (self.function, self.args, self.kwargs))
def run(self):
while not self.finished.is_set():
self.finished.wait(self.interval)
if not self.finished.is_set(): #In case someone has canceled while waiting
try:
self.function(*self.args, **self.kwargs)
except:
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()):
log.debug("Download started: filename={0}, url={1}".format(local_filename, url))
r = requests.get(url, stream=True)
pub.sendMessage("change_status", status=_(u"Downloading {0}.").format(local_filename,))
total_length = r.headers.get("content-length")
dl = 0
total_length = int(total_length)
log.debug("Downloading file of {0} bytes".format(total_length))
with open(local_filename, 'wb') as f:
for chunk in r.iter_content(chunk_size=512*1024):
if chunk: # filter out keep-alive new chunks
dl += len(chunk)
f.write(chunk)
done = int(100 * dl / total_length)
msg = _(u"Downloading {0} ({1}%).").format(os.path.basename(local_filename), done)
pub.sendMessage("change_status", status=msg)
pub.sendMessage("update-progress", value=done)
pub.sendMessage("download_finished", file=os.path.basename(local_filename))
log.debug("Download finished successfully")
apply_metadata(local_filename, metadata)
return local_filename
log.debug("Download started: filename={0}, url={1}".format(local_filename, url))
r = requests.get(url, stream=True)
pub.sendMessage("change_status", status=_(u"Downloading {0}.").format(local_filename,))
total_length = r.headers.get("content-length")
dl = 0
total_length = int(total_length)
log.debug("Downloading file of {0} bytes".format(total_length))
with open(local_filename, 'wb') as f:
for chunk in r.iter_content(chunk_size=512*1024):
if chunk: # filter out keep-alive new chunks
dl += len(chunk)
f.write(chunk)
done = int(100 * dl / total_length)
msg = _(u"Downloading {0} ({1}%).").format(os.path.basename(local_filename), done)
pub.sendMessage("change_status", status=msg)
pub.sendMessage("update-progress", value=done)
pub.sendMessage("download_finished", file=os.path.basename(local_filename))
log.debug("Download finished successfully")
apply_metadata(local_filename, metadata)
return local_filename
def get_services(import_all=False):
""" Function for importing everything wich is located in the services package and has a class named interface."""
module_type = types.ModuleType
# 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')]
for cls in _classes:
reload(cls)
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]
else:
classes = [m for m in services.__dict__.values() if type(m) == module_type and hasattr(m, 'interface')]
return classes
""" Function for importing everything wich is located in the services package and has a class named interface."""
module_type = types.ModuleType
# 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')]
for cls in _classes:
reload(cls)
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]
else:
classes = [m for m in services.__dict__.values() if type(m) == module_type and hasattr(m, 'interface')]
return classes
def apply_metadata(local_filename, metadata):
if local_filename.endswith(".mp3"):
from mutagen.easyid3 import EasyID3 as metadataeditor
elif local_filename.endswith(".flac"):
from mutagen.flac import FLAC as metadataeditor
elif local_filename.endswith(".m4a"):
from mutagen.mp4 import MP4 as metadataeditor
audio = metadataeditor(local_filename)
if local_filename.endswith(".m4a") == False:
for k in metadata.keys():
audio[k] = metadata[k]
else:
audio["\xa9nam"] = metadata["title"]
audio["\xa9alb"] = metadata["album"]
audio["\xa9ART"] = metadata["artist"]
audio.save()
if local_filename.endswith(".mp3"):
from mutagen.easyid3 import EasyID3 as metadataeditor
elif local_filename.endswith(".flac"):
from mutagen.flac import FLAC as metadataeditor
elif local_filename.endswith(".m4a"):
from mutagen.mp4 import MP4 as metadataeditor
audio = metadataeditor(local_filename)
if local_filename.endswith(".m4a") == False:
for k in metadata.keys():
audio[k] = metadata[k]
else:
audio["\xa9nam"] = metadata["title"]
audio["\xa9alb"] = metadata["album"]
audio["\xa9ART"] = metadata["artist"]
audio.save()
def safe_filename(filename):
allowed_symbols = ["_", ".", ",", "-", "(", ")"]
return "".join([c for c in filename if c.isalpha() or c.isdigit() or c==' ' or c in allowed_symbols]).rstrip()
allowed_symbols = ["_", ".", ",", "-", "(", ")"]
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
def exit_application():
""" Closes the current window cleanly. """
wx.GetApp().ExitMainLoop()
""" Closes the current window cleanly. """
wx.GetApp().ExitMainLoop()
def connect_event(parent, event, func, menuitem=None, *args, **kwargs):
""" Connects an event to a function.
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.
function func: The function that will be connected to the event."""
if menuitem == None:
return getattr(parent, "Bind")(event, func, *args, **kwargs)
else:
return getattr(parent, "Bind")(event, func, menuitem, *args, **kwargs)
""" Connects an event to a function.
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.
function func: The function that will be connected to the event."""
if menuitem == None:
return getattr(parent, "Bind")(event, func, *args, **kwargs)
else:
return getattr(parent, "Bind")(event, func, menuitem, *args, **kwargs)
def connectExitFunction(exitFunction):
""" 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_END_SESSION, exitFunction)
""" 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_END_SESSION, exitFunction)
class BaseDialog(wx.Dialog):
def __init__(self, *args, **kwargs):
super(BaseDialog, self).__init__(*args, **kwargs)
def __init__(self, *args, **kwargs):
super(BaseDialog, self).__init__(*args, **kwargs)
def get_response(self):
return self.ShowModal()
def get_response(self):
return self.ShowModal()
def get(self, control):
if hasattr(self, control):
control = getattr(self, control)
if hasattr(control, "GetValue"): return getattr(control, "GetValue")()
elif hasattr(control, "GetLabel"): return getattr(control, "GetLabel")()
else: return -1
else: return 0
def get(self, control):
if hasattr(self, control):
control = getattr(self, control)
if hasattr(control, "GetValue"): return getattr(control, "GetValue")()
elif hasattr(control, "GetLabel"): return getattr(control, "GetLabel")()
else: return -1
else: return 0
def set(self, control, text):
if hasattr(self, control):
control = getattr(self, control)
if hasattr(control, "SetValue"): return getattr(control, "SetValue")(text)
elif hasattr(control, "SetLabel"): return getattr(control, "SetLabel")(text)
elif hasattr(control, "ChangeValue"): return getattr(control, "ChangeValue")(text)
else: return -1
else: return 0
def set(self, control, text):
if hasattr(self, control):
control = getattr(self, control)
if hasattr(control, "SetValue"): return getattr(control, "SetValue")(text)
elif hasattr(control, "SetLabel"): return getattr(control, "SetLabel")(text)
elif hasattr(control, "ChangeValue"): return getattr(control, "ChangeValue")(text)
else: return -1
else: return 0
def destroy(self):
self.Destroy()
def destroy(self):
self.Destroy()
def set_title(self, title):
self.SetTitle(title)
def set_title(self, title):
self.SetTitle(title)
def get_title(self):
return self.GetTitle()
def get_title(self):
return self.GetTitle()
def enable(self, control):
getattr(self, control).Enable(True)
def enable(self, control):
getattr(self, control).Enable(True)
def disable(self, control):
getattr(self, control).Enable(False)
def disable(self, control):
getattr(self, control).Enable(False)
class mainLoopObject(wx.App):
def __init__(self):
self.app = wx.App()
# self.lc = wx.Locale()
# lang=languageHandler.getLanguage()
# wxLang=self.lc.FindLanguageInfo(lang)
# if not wxLang and '_' in lang:
# wxLang=self.lc.FindLanguageInfo(lang.split('_')[0])
# if hasattr(sys,'frozen'):
# self.lc.AddCatalogLookupPathPrefix(paths.app_path("locales"))
# if wxLang:
# self.lc.Init(wxLang.Language)
def __init__(self):
self.app = wx.App()
# self.lc = wx.Locale()
# lang=languageHandler.getLanguage()
# wxLang=self.lc.FindLanguageInfo(lang)
# if not wxLang and '_' in lang:
# wxLang=self.lc.FindLanguageInfo(lang.split('_')[0])
# if hasattr(sys,'frozen'):
# self.lc.AddCatalogLookupPathPrefix(paths.app_path("locales"))
# if wxLang:
# self.lc.Init(wxLang.Language)
def run(self):
self.app.MainLoop()
def run(self):
self.app.MainLoop()
class list(object):
def __init__(self, parent, *columns, **listArguments):
self.columns = columns
self.listArguments = listArguments
self.create_list(parent)
def __init__(self, parent, *columns, **listArguments):
self.columns = columns
self.listArguments = listArguments
self.create_list(parent)
def set_windows_size(self, column, characters_max):
self.list.SetColumnWidth(column, characters_max*2)
def set_windows_size(self, column, characters_max):
self.list.SetColumnWidth(column, characters_max*2)
def set_size(self):
self.list.SetSize((self.list.GetBestSize()[0], 1000))
def set_size(self):
self.list.SetSize((self.list.GetBestSize()[0], 1000))
def create_list(self, parent):
self.list = wx.ListCtrl(parent, -1, **self.listArguments)
for i in range(0, len(self.columns)):
self.list.InsertColumn(i, u"%s" % (self.columns[i]))
def create_list(self, parent):
self.list = wx.ListCtrl(parent, -1, **self.listArguments)
for i in range(0, len(self.columns)):
self.list.InsertColumn(i, u"%s" % (self.columns[i]))
def insert_item(self, reversed, *item):
""" Inserts an item on the list."""
if reversed == False: items = self.list.GetItemCount()
else: items = 0
self.list.InsertItem(items, item[0])
for i in range(1, len(self.columns)):
self.list.SetItem(items, i, item[i])
def insert_item(self, reversed, *item):
""" Inserts an item on the list."""
if reversed == False: items = self.list.GetItemCount()
else: items = 0
self.list.InsertItem(items, item[0])
for i in range(1, len(self.columns)):
self.list.SetItem(items, i, item[i])
def remove_item(self, pos):
""" Deletes an item from the list."""
if pos > 0: self.list.Focus(pos-1)
self.list.DeleteItem(pos)
def remove_item(self, pos):
""" Deletes an item from the list."""
if pos > 0: self.list.Focus(pos-1)
self.list.DeleteItem(pos)
def clear(self):
self.list.DeleteAllItems()
def clear(self):
self.list.DeleteAllItems()
def get_selected(self):
return self.list.GetFocusedItem()
def get_selected(self):
return self.list.GetFocusedItem()
def select_item(self, pos):
self.list.Focus(pos)
def select_item(self, pos):
self.list.Focus(pos)
def get_count(self):
selected = self.list.GetItemCount()
if selected == -1:
return 0
else:
return selected
def get_count(self):
selected = self.list.GetItemCount()
if selected == -1:
return 0
else:
return selected

View File

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

View File

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

View File

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

View File

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