Merge branch 'master' of code.manuelcortez.net:manuelcortez/music-dl
This commit is contained in:
commit
693a9f474e
@ -2,11 +2,13 @@
|
|||||||
|
|
||||||
## Version 0.7
|
## Version 0.7
|
||||||
|
|
||||||
* changes in Tidal:
|
* Tidal:
|
||||||
|
* Added a new search mode for the service to retrieve the top rated tracks of a provided artist. The sintax is top://artist.
|
||||||
* In the settings dialog, you can control wether Albums, compilations and singles will be added when searching by artist (by using artist://...).
|
* In the settings dialog, you can control wether Albums, compilations and singles will be added when searching by artist (by using artist://...).
|
||||||
* When searching by artists, results that belong to an album will be numbered.
|
* When searching by artists, results that belong to an album will be numbered.
|
||||||
* Downloads will be tagged with title, album, artist and track number provided by tidal.
|
* Downloads will be tagged with title, album, artist and track number provided by tidal.
|
||||||
* changes in Youtube:
|
* It is possible to download an original version in high and low quality. Before, those versions were encoded to mp3 from an m4a file. Now the M4a file can be retrieved by ticking the checkbox in the tidal settings page.
|
||||||
|
* YouTube:
|
||||||
* Fixed search algorithm for Youtube videos.
|
* Fixed search algorithm for Youtube videos.
|
||||||
* Updated Youtube-Dl to version 2020.6.16.1
|
* Updated Youtube-Dl to version 2020.6.16.1
|
||||||
* re-added VK module. By default, this module searches up to 50 results but you can increase it up to 200 if needed from the services settings.
|
* re-added VK module. By default, this module searches up to 50 results but you can increase it up to 200 if needed from the services settings.
|
||||||
|
@ -8,10 +8,12 @@ authorEmail = "manuel@manuelcortez.net"
|
|||||||
copyright = "Copyright (C) 2019-2020, Manuel Cortez"
|
copyright = "Copyright (C) 2019-2020, Manuel Cortez"
|
||||||
description = name+_(u" Is an application that will allow you to download music from popular sites such as youtube, zaycev.net.")
|
description = name+_(u" Is an application that will allow you to download music from popular sites such as youtube, zaycev.net.")
|
||||||
url = "https://manuelcortez.net/music_dl"
|
url = "https://manuelcortez.net/music_dl"
|
||||||
update_url = "https://manuelcortez.net/music_dl/update"
|
|
||||||
# The short name will be used for detecting translation files. See languageHandler for more details.
|
# The short name will be used for detecting translation files. See languageHandler for more details.
|
||||||
short_name = "musicdl"
|
short_name = "musicdl"
|
||||||
translators = [_(u"Manuel Cortez (Spanish)")]
|
translators = [_(u"Manuel Cortez (Spanish)")]
|
||||||
bts_name = "music_dl"
|
bts_name = "music_dl"
|
||||||
bts_access_token = "fe3j2ijirvevv9"
|
bts_access_token = "fe3j2ijirvevv9"
|
||||||
bts_url = "https://issues.manuelcortez.net"
|
bts_url = "https://issues.manuelcortez.net"
|
||||||
|
update_stable_url = "https://manuelcortez.net/static/files/music_dl/update/stable.json"
|
||||||
|
update_next_url = "https://manuelcortez.net/static/files/music_dl/update/alpha.json"
|
||||||
|
update_next_version=None
|
@ -66,6 +66,10 @@ class interface(base.baseInterface):
|
|||||||
if text == "" or text == None:
|
if text == "" or text == None:
|
||||||
raise ValueError("Text must be passed and should not be blank.")
|
raise ValueError("Text must be passed and should not be blank.")
|
||||||
log.debug("Retrieving data from Tidal...")
|
log.debug("Retrieving data from Tidal...")
|
||||||
|
# Check for top:// protocol.
|
||||||
|
if text.startswith("top://"):
|
||||||
|
text = text.replace("top://", "")
|
||||||
|
return self.search_for_top(text)
|
||||||
fieldtypes = ["artist", "album", "playlist"]
|
fieldtypes = ["artist", "album", "playlist"]
|
||||||
field = "track"
|
field = "track"
|
||||||
for i in fieldtypes:
|
for i in fieldtypes:
|
||||||
@ -114,6 +118,25 @@ class interface(base.baseInterface):
|
|||||||
self.results.append(s)
|
self.results.append(s)
|
||||||
log.debug("{0} results found.".format(len(self.results)))
|
log.debug("{0} results found.".format(len(self.results)))
|
||||||
|
|
||||||
|
def search_for_top(self, artist):
|
||||||
|
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):
|
def format_number(self, number):
|
||||||
if number < 10:
|
if number < 10:
|
||||||
return "0%d" % (number)
|
return "0%d" % (number)
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
import glob
|
||||||
|
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))]
|
@ -1,31 +1,31 @@
|
|||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
logger = getLogger('update')
|
logger = getLogger('update')
|
||||||
|
|
||||||
|
import sys
|
||||||
import contextlib
|
import contextlib
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import requests
|
import requests
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import widgetUtils
|
||||||
|
import webbrowser
|
||||||
try:
|
try:
|
||||||
import czipfile as zipfile
|
import czipfile as zipfile
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
import paths
|
from platform_utils import paths
|
||||||
|
|
||||||
def perform_update(endpoint, current_version, app_name='', password=None, update_available_callback=None, progress_callback=None, update_complete_callback=None):
|
def perform_update(endpoint, current_version, update_type="stable", app_name='', password=None, update_available_callback=None, progress_callback=None, update_complete_callback=None):
|
||||||
requests_session = create_requests_session(app_name=app_name, version=current_version)
|
requests_session = create_requests_session(app_name=app_name, version=current_version)
|
||||||
available_update = find_update(endpoint, requests_session=requests_session)
|
available_update = find_update(endpoint, requests_session=requests_session)
|
||||||
if not available_update:
|
if not available_update:
|
||||||
logger.debug("No update available")
|
logger.debug("No update available")
|
||||||
return False
|
return False
|
||||||
available_version = float(available_update['current_version'])
|
available_version, available_description, update_url = find_version_data(update_type, current_version, available_update)
|
||||||
if not float(available_version) > float(current_version) or platform.system()+platform.architecture()[0][:2] not in available_update['downloads']:
|
if available_version == False:
|
||||||
logger.debug("No update for this architecture")
|
|
||||||
return False
|
return False
|
||||||
available_description = available_update.get('description', None)
|
|
||||||
update_url = available_update ['downloads'][platform.system()+platform.architecture()[0][:2]]
|
|
||||||
logger.info("A new update is available. Version %s" % available_version)
|
logger.info("A new update is available. Version %s" % available_version)
|
||||||
if callable(update_available_callback) and not update_available_callback(version=available_version, description=available_description): #update_available_callback should return a falsy value to stop the process
|
if callable(update_available_callback) and not update_available_callback(version=available_version, description=available_description): #update_available_callback should return a falsy value to stop the process
|
||||||
logger.info("User canceled update.")
|
logger.info("User canceled update.")
|
||||||
@ -55,6 +55,23 @@ def find_update(endpoint, requests_session):
|
|||||||
content = response.json()
|
content = response.json()
|
||||||
return content
|
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)
|
||||||
|
|
||||||
def download_update(update_url, update_destination, requests_session, progress_callback=None, chunk_size=io.DEFAULT_BUFFER_SIZE):
|
def download_update(update_url, update_destination, requests_session, progress_callback=None, chunk_size=io.DEFAULT_BUFFER_SIZE):
|
||||||
total_downloaded = total_size = 0
|
total_downloaded = total_size = 0
|
||||||
with io.open(update_destination, 'w+b') as outfile:
|
with io.open(update_destination, 'w+b') as outfile:
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
import sys
|
||||||
import application
|
import application
|
||||||
import platform
|
import platform
|
||||||
import logging
|
import logging
|
||||||
@ -7,8 +8,17 @@ from .import update
|
|||||||
from .wxUpdater import *
|
from .wxUpdater import *
|
||||||
logger = logging.getLogger("updater")
|
logger = logging.getLogger("updater")
|
||||||
|
|
||||||
def do_update(endpoint=application.update_url):
|
def do_update(update_type="alpha"):
|
||||||
|
# Updates cannot be performed in the source code version.
|
||||||
|
if hasattr(sys, "frozen") == False:
|
||||||
|
return
|
||||||
|
if update_type == "stable":
|
||||||
|
endpoint = application.update_stable_url
|
||||||
|
version = application.version
|
||||||
|
else:
|
||||||
|
endpoint = application.update_next_url
|
||||||
|
version = application.update_next_version
|
||||||
try:
|
try:
|
||||||
return update.perform_update(endpoint=endpoint, current_version=application.version, app_name=application.name, update_available_callback=available_update_dialog, progress_callback=progress_callback, update_complete_callback=update_finished)
|
return update.perform_update(endpoint=endpoint, current_version=version, app_name=application.name, update_type=update_type, update_available_callback=available_update_dialog, progress_callback=progress_callback, update_complete_callback=update_finished)
|
||||||
except ConnectionError:
|
except ConnectionError:
|
||||||
logger.exception("Update failed.")
|
logger.exception("Update failed.")
|
@ -1,4 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
def convert_bytes(n):
|
def convert_bytes(n):
|
||||||
K, M, G, T, P = 1 << 10, 1 << 20, 1 << 30, 1 << 40, 1 << 50
|
K, M, G, T, P = 1 << 10, 1 << 20, 1 << 30, 1 << 40, 1 << 50
|
||||||
@ -24,19 +25,19 @@ def seconds_to_string(seconds, precision=0):
|
|||||||
sec_string = sec.__format__(sec_spec)
|
sec_string = sec.__format__(sec_spec)
|
||||||
string = ""
|
string = ""
|
||||||
if day == 1:
|
if day == 1:
|
||||||
string += _(u"%d day, ") % day
|
string += _("%d day, ") % day
|
||||||
elif day >= 2:
|
elif day >= 2:
|
||||||
string += _(u"%d days, ") % day
|
string += _("%d days, ") % day
|
||||||
if (hour == 1):
|
if (hour == 1):
|
||||||
string += _(u"%d hour, ") % hour
|
string += _("%d hour, ") % hour
|
||||||
elif (hour >= 2):
|
elif (hour >= 2):
|
||||||
string += _("%d hours, ") % hour
|
string += _("%d hours, ") % hour
|
||||||
if (min == 1):
|
if (min == 1):
|
||||||
string += _(u"%d minute, ") % min
|
string += _("%d minute, ") % min
|
||||||
elif (min >= 2):
|
elif (min >= 2):
|
||||||
string += _(u"%d minutes, ") % min
|
string += _("%d minutes, ") % min
|
||||||
if sec >= 0 and sec <= 2:
|
if sec >= 0 and sec <= 2:
|
||||||
string += _(u"%s second") % sec_string
|
string += _("%s second") % sec_string
|
||||||
else:
|
else:
|
||||||
string += _(u"%s seconds") % sec_string
|
string += _("%s seconds") % sec_string
|
||||||
return string
|
return string
|
@ -1,5 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals # at top of module
|
from __future__ import division
|
||||||
|
from __future__ import unicode_literals
|
||||||
import wx
|
import wx
|
||||||
import application
|
import application
|
||||||
from . import utils
|
from . import utils
|
||||||
@ -7,16 +8,19 @@ from .import utils
|
|||||||
progress_dialog = None
|
progress_dialog = None
|
||||||
|
|
||||||
def available_update_dialog(version, description):
|
def available_update_dialog(version, description):
|
||||||
dialog = wx.MessageDialog(None, _(u"There's a new %s version available. Would you like to download it now?\n\n %s version: %s\n\nChanges:\n%s") % (application.name, application.name, version, description), _(u"New version for %s") % application.name, style=wx.YES|wx.NO|wx.ICON_WARNING)
|
dialog = wx.MessageDialog(None, _("There's a new {app_name} version available. Would you like to download it now?\n\n {app_name} version: {app_version}\n\nChanges:\n{changes}").format(app_name=application.name, app_version=version, changes=description), _("New version for %s") % application.name, style=wx.YES|wx.NO|wx.ICON_WARNING)
|
||||||
if dialog.ShowModal() == wx.ID_YES:
|
if dialog.ShowModal() == wx.ID_YES:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def create_progress_dialog():
|
def create_progress_dialog():
|
||||||
return wx.ProgressDialog(_(u"Download in Progress"), _(u"Downloading the new version..."), parent=None, maximum=100)
|
return wx.ProgressDialog(_("Download in Progress"), _("Downloading the new version..."), parent=None, maximum=100)
|
||||||
|
|
||||||
def progress_callback(total_downloaded, total_size):
|
def progress_callback(total_downloaded, total_size):
|
||||||
|
wx.CallAfter(_progress_callback, total_downloaded, total_size)
|
||||||
|
|
||||||
|
def _progress_callback(total_downloaded, total_size):
|
||||||
global progress_dialog
|
global progress_dialog
|
||||||
if progress_dialog == None:
|
if progress_dialog == None:
|
||||||
progress_dialog = create_progress_dialog()
|
progress_dialog = create_progress_dialog()
|
||||||
@ -24,7 +28,7 @@ def progress_callback(total_downloaded, total_size):
|
|||||||
if total_downloaded == total_size:
|
if total_downloaded == total_size:
|
||||||
progress_dialog.Destroy()
|
progress_dialog.Destroy()
|
||||||
else:
|
else:
|
||||||
progress_dialog.Update((total_downloaded*100)/total_size, _(u"Updating... %s of %s") % (str(utils.convert_bytes(total_downloaded)), str(utils.convert_bytes(total_size))))
|
progress_dialog.Update((total_downloaded*100)/total_size, _("Updating... {total_transferred} of {total_size}").format(total_transferred=utils.convert_bytes(total_downloaded), total_size=utils.convert_bytes(total_size)))
|
||||||
|
|
||||||
def update_finished():
|
def update_finished():
|
||||||
ms = wx.MessageDialog(None, _(u"The update has been downloaded and installed successfully. Press OK to continue."), _(u"Done!")).ShowModal()
|
return wx.MessageDialog(None, _("The update has been downloaded and installed successfully. Press OK to continue."), _("Done!")).ShowModal()
|
@ -92,7 +92,14 @@ def apply_metadata(local_filename, metadata):
|
|||||||
from mutagen.easyid3 import EasyID3 as metadataeditor
|
from mutagen.easyid3 import EasyID3 as metadataeditor
|
||||||
elif local_filename.endswith(".flac"):
|
elif local_filename.endswith(".flac"):
|
||||||
from mutagen.flac import FLAC as metadataeditor
|
from mutagen.flac import FLAC as metadataeditor
|
||||||
|
elif local_filename.endswith(".m4a"):
|
||||||
|
from mutagen.mp4 import MP4 as metadataeditor
|
||||||
audio = metadataeditor(local_filename)
|
audio = metadataeditor(local_filename)
|
||||||
|
if local_filename.endswith(".m4a") == False:
|
||||||
for k in metadata.keys():
|
for k in metadata.keys():
|
||||||
audio[k] = metadata[k]
|
audio[k] = metadata[k]
|
||||||
|
else:
|
||||||
|
audio["\xa9nam"] = metadata["title"]
|
||||||
|
audio["\xa9alb"] = metadata["album"]
|
||||||
|
audio["\xa9ART"] = metadata["artist"]
|
||||||
audio.save()
|
audio.save()
|
Loading…
Reference in New Issue
Block a user