Merge branch 'master' of code.manuelcortez.net:manuelcortez/music-dl

This commit is contained in:
Manuel Cortez 2020-07-17 21:56:06 -05:00
commit 693a9f474e
9 changed files with 140 additions and 62 deletions

View File

@ -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.

View File

@ -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

View File

@ -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)

View File

@ -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))]

View File

@ -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:

View File

@ -1,14 +1,24 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import sys
import application import application
import platform import platform
import logging import logging
from requests.exceptions import ConnectionError from requests.exceptions import ConnectionError
from .import update 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"):
try: # Updates cannot be performed in the source code version.
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) if hasattr(sys, "frozen") == False:
except ConnectionError: return
logger.exception("Update failed.") if update_type == "stable":
endpoint = application.update_stable_url
version = application.version
else:
endpoint = application.update_next_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

@ -1,42 +1,43 @@
# -*- 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
if n >= P: if n >= P:
return '%.2fPb' % (float(n) / T) return '%.2fPb' % (float(n) / T)
elif n >= T: elif n >= T:
return '%.2fTb' % (float(n) / T) return '%.2fTb' % (float(n) / T)
elif n >= G: elif n >= G:
return '%.2fGb' % (float(n) / G) return '%.2fGb' % (float(n) / G)
elif n >= M: elif n >= M:
return '%.2fMb' % (float(n) / M) return '%.2fMb' % (float(n) / M)
elif n >= K: elif n >= K:
return '%.2fKb' % (float(n) / K) return '%.2fKb' % (float(n) / K)
else: else:
return '%d' % n return '%d' % n
def seconds_to_string(seconds, precision=0): def seconds_to_string(seconds, precision=0):
day = seconds // 86400 day = seconds // 86400
hour = seconds // 3600 hour = seconds // 3600
min = (seconds // 60) % 60 min = (seconds // 60) % 60
sec = seconds - (hour * 3600) - (min * 60) sec = seconds - (hour * 3600) - (min * 60)
sec_spec = "." + str(precision) + "f" sec_spec = "." + str(precision) + "f"
sec_string = sec.__format__(sec_spec) sec_string = sec.__format__(sec_spec)
string = "" string = ""
if day == 1: if day == 1:
string += _(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

View File

@ -1,22 +1,26 @@
# -*- 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
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()

View File

@ -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)
for k in metadata.keys(): if local_filename.endswith(".m4a") == False:
audio[k] = metadata[k] for k in metadata.keys():
audio[k] = metadata[k]
else:
audio["\xa9nam"] = metadata["title"]
audio["\xa9alb"] = metadata["album"]
audio["\xa9ART"] = metadata["artist"]
audio.save() audio.save()