Merge branch 'master' of code.manuelcortez.net:manuelcortez/music-dl
This commit is contained in:
commit
693a9f474e
@ -2,11 +2,13 @@
|
||||
|
||||
## 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://...).
|
||||
* 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.
|
||||
* 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.
|
||||
* 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.
|
||||
|
@ -8,10 +8,12 @@ authorEmail = "manuel@manuelcortez.net"
|
||||
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.")
|
||||
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.
|
||||
short_name = "musicdl"
|
||||
translators = [_(u"Manuel Cortez (Spanish)")]
|
||||
bts_name = "music_dl"
|
||||
bts_access_token = "fe3j2ijirvevv9"
|
||||
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:
|
||||
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:
|
||||
@ -114,6 +118,25 @@ class interface(base.baseInterface):
|
||||
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)
|
||||
|
@ -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
|
||||
logger = getLogger('update')
|
||||
|
||||
import sys
|
||||
import contextlib
|
||||
import io
|
||||
import os
|
||||
import platform
|
||||
import requests
|
||||
import tempfile
|
||||
import widgetUtils
|
||||
import webbrowser
|
||||
try:
|
||||
import czipfile as zipfile
|
||||
except ImportError:
|
||||
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)
|
||||
available_update = find_update(endpoint, requests_session=requests_session)
|
||||
if not available_update:
|
||||
logger.debug("No update available")
|
||||
return False
|
||||
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")
|
||||
available_version, available_description, update_url = find_version_data(update_type, current_version, available_update)
|
||||
if available_version == 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)
|
||||
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.")
|
||||
@ -55,6 +55,23 @@ def find_update(endpoint, requests_session):
|
||||
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)
|
||||
|
||||
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:
|
||||
|
@ -1,14 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
import application
|
||||
import platform
|
||||
import logging
|
||||
from requests.exceptions import ConnectionError
|
||||
from .import update
|
||||
from . import update
|
||||
from .wxUpdater import *
|
||||
logger = logging.getLogger("updater")
|
||||
|
||||
def do_update(endpoint=application.update_url):
|
||||
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)
|
||||
except ConnectionError:
|
||||
logger.exception("Update failed.")
|
||||
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:
|
||||
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.")
|
@ -1,42 +1,43 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
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 += _(u"%d day, ") % day
|
||||
elif day >= 2:
|
||||
string += _(u"%d days, ") % day
|
||||
if (hour == 1):
|
||||
string += _(u"%d hour, ") % hour
|
||||
elif (hour >= 2):
|
||||
string += _("%d hours, ") % hour
|
||||
if (min == 1):
|
||||
string += _(u"%d minute, ") % min
|
||||
elif (min >= 2):
|
||||
string += _(u"%d minutes, ") % min
|
||||
if sec >= 0 and sec <= 2:
|
||||
string += _(u"%s second") % sec_string
|
||||
else:
|
||||
string += _(u"%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
|
@ -1,22 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals # at top of module
|
||||
from __future__ import division
|
||||
from __future__ import unicode_literals
|
||||
import wx
|
||||
import application
|
||||
from .import utils
|
||||
from . import utils
|
||||
|
||||
progress_dialog = None
|
||||
|
||||
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:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
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):
|
||||
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()
|
||||
@ -24,7 +28,7 @@ def progress_callback(total_downloaded, total_size):
|
||||
if total_downloaded == total_size:
|
||||
progress_dialog.Destroy()
|
||||
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():
|
||||
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()
|
11
src/utils.py
11
src/utils.py
@ -92,7 +92,14 @@ def apply_metadata(local_filename, metadata):
|
||||
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)
|
||||
for k in metadata.keys():
|
||||
audio[k] = metadata[k]
|
||||
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()
|
Loading…
Reference in New Issue
Block a user