2020-09-15 11:04:13 +00:00
import eventlet
2020-10-01 12:30:40 +00:00
from eventlet . green . subprocess import call as execute
2020-10-02 16:57:23 +00:00
requests = eventlet . import_patched ( ' requests ' )
get = requests . get
request_exception = requests . exceptions
2020-10-01 12:30:40 +00:00
2020-09-27 21:44:37 +00:00
from os . path import sep as pathSep
from pathlib import Path
2020-10-02 16:03:01 +00:00
from shlex import quote
2020-08-15 19:34:10 +00:00
import re
2020-09-16 17:24:50 +00:00
import errno
2020-08-15 19:34:10 +00:00
2020-09-16 14:44:15 +00:00
from ssl import SSLError
2020-10-01 12:30:40 +00:00
from os import makedirs
2020-08-15 19:34:10 +00:00
from tempfile import gettempdir
2020-11-30 13:55:38 +00:00
from urllib3 . exceptions import SSLError as u3SSLError
2020-08-15 19:34:10 +00:00
2020-09-12 11:08:28 +00:00
from deemix . app . queueitem import QISingle , QICollection
2020-10-04 09:56:32 +00:00
from deemix . app . track import Track , AlbumDoesntExists
2020-09-20 08:35:05 +00:00
from deemix . utils import changeCase
2020-08-15 19:34:10 +00:00
from deemix . utils . pathtemplates import generateFilename , generateFilepath , settingsRegexAlbum , settingsRegexArtist , settingsRegexPlaylistFile
2020-11-19 21:08:35 +00:00
from deezer import TrackFormats
from deemix import USER_AGENT_HEADER
2020-08-15 19:34:10 +00:00
from deemix . utils . taggers import tagID3 , tagFLAC
2020-11-19 21:08:35 +00:00
from deemix . utils . decryption import generateStreamURL , generateBlowfishKey
2020-10-03 08:56:01 +00:00
from deemix . app . settings import OverwriteOption , FeaturesOption
2020-08-15 19:34:10 +00:00
from Cryptodome . Cipher import Blowfish
2020-09-30 07:33:14 +00:00
from mutagen . flac import FLACNoHeaderError , error as FLACError
2020-08-15 19:34:10 +00:00
import logging
logging . basicConfig ( level = logging . INFO )
logger = logging . getLogger ( ' deemix ' )
2020-09-27 21:44:37 +00:00
TEMPDIR = Path ( gettempdir ( ) ) / ' deemix-imgs '
2020-10-02 16:57:23 +00:00
if not TEMPDIR . is_dir ( ) : makedirs ( TEMPDIR )
2020-08-15 19:34:10 +00:00
extensions = {
2020-10-02 17:20:23 +00:00
TrackFormats . FLAC : ' .flac ' ,
TrackFormats . LOCAL : ' .mp3 ' ,
TrackFormats . MP3_320 : ' .mp3 ' ,
TrackFormats . MP3_128 : ' .mp3 ' ,
TrackFormats . DEFAULT : ' .mp3 ' ,
TrackFormats . MP4_RA3 : ' .mp4 ' ,
TrackFormats . MP4_RA2 : ' .mp4 ' ,
TrackFormats . MP4_RA1 : ' .mp4 '
2020-08-15 19:34:10 +00:00
}
errorMessages = {
' notOnDeezer ' : " Track not available on Deezer! " ,
' notEncoded ' : " Track not yet encoded! " ,
' notEncodedNoAlternative ' : " Track not yet encoded and no alternative found! " ,
' wrongBitrate ' : " Track not found at desired bitrate. " ,
' wrongBitrateNoAlternative ' : " Track not found at desired bitrate and no alternative found! " ,
' no360RA ' : " Track is not available in Reality Audio 360. " ,
2020-08-15 21:03:05 +00:00
' notAvailable ' : " Track not available on deezer ' s servers! " ,
2020-09-16 17:24:50 +00:00
' notAvailableNoAlternative ' : " Track not available on deezer ' s servers and no alternative found! " ,
2020-09-24 17:20:01 +00:00
' noSpaceLeft ' : " No space left on target drive, clean up some space for the tracks " ,
2020-10-04 09:56:32 +00:00
' albumDoesntExists ' : " Track ' s album does not exsist, failed to gather info "
2020-08-15 19:34:10 +00:00
}
2020-09-27 21:44:37 +00:00
2020-10-03 08:56:01 +00:00
def downloadImage ( url , path , overwrite = OverwriteOption . DONT_OVERWRITE ) :
if not path . is_file ( ) or overwrite in [ OverwriteOption . OVERWRITE , OverwriteOption . ONLY_TAGS , OverwriteOption . KEEP_BOTH ] :
2020-08-15 19:34:10 +00:00
try :
image = get ( url , headers = { ' User-Agent ' : USER_AGENT_HEADER } , timeout = 30 )
image . raise_for_status ( )
with open ( path , ' wb ' ) as f :
f . write ( image . content )
return path
2020-09-07 10:09:46 +00:00
except request_exception . HTTPError :
2020-08-15 19:34:10 +00:00
if ' cdns-images.dzcdn.net ' in url :
urlBase = url [ : url . rfind ( " / " ) + 1 ]
pictureUrl = url [ len ( urlBase ) : ]
pictureSize = int ( pictureUrl [ : pictureUrl . find ( " x " ) ] )
if pictureSize > 1200 :
logger . warn ( " Couldn ' t download " + str ( pictureSize ) + " x " + str ( pictureSize ) + " image, falling back to 1200x1200 " )
2020-09-15 11:04:13 +00:00
eventlet . sleep ( 1 )
2020-08-15 19:34:10 +00:00
return downloadImage ( urlBase + pictureUrl . replace ( str ( pictureSize ) + " x " + str ( pictureSize ) , ' 1200x1200 ' ) , path , overwrite )
2020-09-10 09:43:32 +00:00
logger . error ( " Image not found: " + url )
2020-11-30 13:55:38 +00:00
except ( request_exception . ConnectionError , request_exception . ChunkedEncodingError , u3SSLError ) as e :
2020-09-10 10:43:53 +00:00
logger . error ( " Couldn ' t download Image, retrying in 5 seconds...: " + url + " \n " )
2020-09-15 11:04:13 +00:00
eventlet . sleep ( 5 )
2020-08-15 19:34:10 +00:00
return downloadImage ( url , path , overwrite )
2020-09-16 17:24:50 +00:00
except OSError as e :
if e . errno == errno . ENOSPC :
raise DownloadFailed ( " noSpaceLeft " )
else :
logger . exception ( f " Error while downloading an image, you should report this to the developers: { str ( e ) } " )
2020-09-10 09:43:32 +00:00
except Exception as e :
logger . exception ( f " Error while downloading an image, you should report this to the developers: { str ( e ) } " )
2020-09-27 21:44:37 +00:00
if path . is_file ( ) : path . unlink ( )
2020-08-15 19:34:10 +00:00
return None
else :
return path
2020-12-15 12:27:02 +00:00
def generatePictureURL ( pic , size , format ) :
if pic [ ' url ' ] : return pic [ ' url ' ]
if format . startswith ( " jpg " ) :
if ' - ' in format :
quality = format [ 4 : ]
else :
quality = 80
format = ' jpg '
return " https://e-cdns-images.dzcdn.net/images/ {} / {} / {} x {} - {} " . format (
pic [ ' type ' ] ,
pic [ ' md5 ' ] ,
size , size ,
f ' 000000- { quality } -0-0.jpg '
)
if format == ' png ' :
return " https://e-cdns-images.dzcdn.net/images/ {} / {} / {} x {} - {} " . format (
pic [ ' type ' ] ,
pic [ ' md5 ' ] ,
size , size ,
' none-100-0-0.png '
)
2020-08-15 19:34:10 +00:00
def formatDate ( date , template ) :
elements = {
' year ' : [ ' YYYY ' , ' YY ' , ' Y ' ] ,
' month ' : [ ' MM ' , ' M ' ] ,
' day ' : [ ' DD ' , ' D ' ]
}
for element , placeholders in elements . items ( ) :
for placeholder in placeholders :
if placeholder in template :
template = template . replace ( placeholder , str ( date [ element ] ) )
return template
class DownloadJob :
2020-09-12 11:08:28 +00:00
def __init__ ( self , dz , queueItem , interface = None ) :
2020-08-15 19:34:10 +00:00
self . dz = dz
self . interface = interface
2020-08-15 21:03:05 +00:00
self . queueItem = queueItem
2020-08-15 19:34:10 +00:00
self . settings = queueItem . settings
self . bitrate = queueItem . bitrate
self . downloadPercentage = 0
self . lastPercentage = 0
2020-08-17 15:46:21 +00:00
self . extrasPath = None
2020-09-27 21:44:37 +00:00
self . playlistCoverName = None
2020-08-15 21:36:32 +00:00
self . playlistURLs = [ ]
2020-08-15 19:34:10 +00:00
def start ( self ) :
2020-10-02 17:20:23 +00:00
if not self . queueItem . cancel :
if isinstance ( self . queueItem , QISingle ) :
result = self . downloadWrapper ( self . queueItem . single )
if result : self . singleAfterDownload ( result )
elif isinstance ( self . queueItem , QICollection ) :
tracks = [ None ] * len ( self . queueItem . collection )
pool = eventlet . GreenPool ( size = self . settings [ ' queueConcurrency ' ] )
for pos , track in enumerate ( self . queueItem . collection , start = 0 ) :
tracks [ pos ] = pool . spawn ( self . downloadWrapper , track )
pool . waitall ( )
self . collectionAfterDownload ( tracks )
if self . interface :
if self . queueItem . cancel :
self . interface . send ( ' currentItemCancelled ' , self . queueItem . uuid )
self . interface . send ( " removedFromQueue " , self . queueItem . uuid )
else :
self . interface . send ( " finishDownload " , self . queueItem . uuid )
2020-08-15 21:36:32 +00:00
return self . extrasPath
def singleAfterDownload ( self , result ) :
2020-10-02 16:57:23 +00:00
if not self . extrasPath : self . extrasPath = Path ( self . settings [ ' downloadLocation ' ] )
2020-08-15 21:36:32 +00:00
# Save Album Cover
if self . settings [ ' saveArtwork ' ] and ' albumPath ' in result :
for image in result [ ' albumURLs ' ] :
2020-09-27 21:44:37 +00:00
downloadImage ( image [ ' url ' ] , result [ ' albumPath ' ] / f " { result [ ' albumFilename ' ] } . { image [ ' ext ' ] } " , self . settings [ ' overwriteFile ' ] )
2020-10-02 16:57:23 +00:00
2020-08-15 21:36:32 +00:00
# Save Artist Artwork
if self . settings [ ' saveArtworkArtist ' ] and ' artistPath ' in result :
for image in result [ ' artistURLs ' ] :
2020-09-27 21:44:37 +00:00
downloadImage ( image [ ' url ' ] , result [ ' artistPath ' ] / f " { result [ ' artistFilename ' ] } . { image [ ' ext ' ] } " , self . settings [ ' overwriteFile ' ] )
2020-10-02 16:57:23 +00:00
2020-08-15 21:36:32 +00:00
# Create searched logfile
if self . settings [ ' logSearched ' ] and ' searched ' in result :
2020-09-27 21:44:37 +00:00
with open ( self . extrasPath / ' searched.txt ' , ' wb+ ' ) as f :
2020-08-15 21:36:32 +00:00
orig = f . read ( ) . decode ( ' utf-8 ' )
if not result [ ' searched ' ] in orig :
2020-10-02 16:57:23 +00:00
if orig != " " : orig + = " \r \n "
2020-08-15 21:36:32 +00:00
orig + = result [ ' searched ' ] + " \r \n "
f . write ( orig . encode ( ' utf-8 ' ) )
# Execute command after download
if self . settings [ ' executeCommand ' ] != " " :
2020-10-02 16:03:01 +00:00
execute ( self . settings [ ' executeCommand ' ] . replace ( " %f older % " , quote ( str ( self . extrasPath ) ) ) . replace ( " %f ilename % " , quote ( result [ ' filename ' ] ) ) , shell = True )
2020-08-15 21:36:32 +00:00
def collectionAfterDownload ( self , tracks ) :
2020-10-02 16:57:23 +00:00
if not self . extrasPath : self . extrasPath = Path ( self . settings [ ' downloadLocation ' ] )
2020-08-15 21:36:32 +00:00
playlist = [ None ] * len ( tracks )
errors = " "
searched = " "
2020-10-02 16:57:23 +00:00
for i in range ( len ( tracks ) ) :
result = tracks [ i ] . wait ( )
if not result : return None # Check if item is cancelled
2020-08-15 21:36:32 +00:00
# Log errors to file
2020-10-02 16:57:23 +00:00
if result . get ( ' error ' ) :
if not result [ ' error ' ] . get ( ' data ' ) : result [ ' error ' ] [ ' data ' ] = { ' id ' : " 0 " , ' title ' : ' Unknown ' , ' artist ' : ' Unknown ' }
2020-08-15 21:36:32 +00:00
errors + = f " { result [ ' error ' ] [ ' data ' ] [ ' id ' ] } | { result [ ' error ' ] [ ' data ' ] [ ' artist ' ] } - { result [ ' error ' ] [ ' data ' ] [ ' title ' ] } | { result [ ' error ' ] [ ' message ' ] } \r \n "
2020-10-02 16:57:23 +00:00
2020-08-15 21:36:32 +00:00
# Log searched to file
2020-10-02 16:57:23 +00:00
if ' searched ' in result : searched + = result [ ' searched ' ] + " \r \n "
2020-08-15 21:36:32 +00:00
# Save Album Cover
if self . settings [ ' saveArtwork ' ] and ' albumPath ' in result :
for image in result [ ' albumURLs ' ] :
2020-09-27 21:44:37 +00:00
downloadImage ( image [ ' url ' ] , result [ ' albumPath ' ] / f " { result [ ' albumFilename ' ] } . { image [ ' ext ' ] } " , self . settings [ ' overwriteFile ' ] )
2020-10-02 16:57:23 +00:00
2020-08-15 21:36:32 +00:00
# Save Artist Artwork
if self . settings [ ' saveArtworkArtist ' ] and ' artistPath ' in result :
for image in result [ ' artistURLs ' ] :
2020-09-27 21:44:37 +00:00
downloadImage ( image [ ' url ' ] , result [ ' artistPath ' ] / f " { result [ ' artistFilename ' ] } . { image [ ' ext ' ] } " , self . settings [ ' overwriteFile ' ] )
2020-10-02 16:57:23 +00:00
2020-08-15 21:36:32 +00:00
# Save filename for playlist file
2020-10-02 16:57:23 +00:00
playlist [ i ] = result . get ( ' filename ' , " " )
2020-08-15 21:36:32 +00:00
# Create errors logfile
if self . settings [ ' logErrors ' ] and errors != " " :
2020-09-27 21:44:37 +00:00
with open ( self . extrasPath / ' errors.txt ' , ' wb ' ) as f :
2020-08-15 21:36:32 +00:00
f . write ( errors . encode ( ' utf-8 ' ) )
2020-10-02 16:57:23 +00:00
2020-08-15 21:36:32 +00:00
# Create searched logfile
if self . settings [ ' logSearched ' ] and searched != " " :
2020-09-27 21:44:37 +00:00
with open ( self . extrasPath / ' searched.txt ' , ' wb ' ) as f :
2020-08-15 21:36:32 +00:00
f . write ( searched . encode ( ' utf-8 ' ) )
2020-10-02 16:57:23 +00:00
2020-08-15 21:36:32 +00:00
# Save Playlist Artwork
2020-09-27 21:44:37 +00:00
if self . settings [ ' saveArtwork ' ] and self . playlistCoverName and not self . settings [ ' tags ' ] [ ' savePlaylistAsCompilation ' ] :
2020-08-15 21:36:32 +00:00
for image in self . playlistURLs :
2020-09-27 21:44:37 +00:00
downloadImage ( image [ ' url ' ] , self . extrasPath / f " { self . playlistCoverName } . { image [ ' ext ' ] } " , self . settings [ ' overwriteFile ' ] )
2020-10-02 16:57:23 +00:00
2020-08-15 21:36:32 +00:00
# Create M3U8 File
if self . settings [ ' createM3U8File ' ] :
2020-08-16 17:09:32 +00:00
filename = settingsRegexPlaylistFile ( self . settings [ ' playlistFilenameTemplate ' ] , self . queueItem , self . settings ) or " playlist "
2020-09-27 21:44:37 +00:00
with open ( self . extrasPath / f ' { filename } .m3u8 ' , ' wb ' ) as f :
2020-08-15 21:36:32 +00:00
for line in playlist :
f . write ( ( line + " \n " ) . encode ( ' utf-8 ' ) )
2020-10-02 16:57:23 +00:00
2020-08-15 21:36:32 +00:00
# Execute command after download
if self . settings [ ' executeCommand ' ] != " " :
2020-10-02 16:03:01 +00:00
execute ( self . settings [ ' executeCommand ' ] . replace ( " %f older % " , quote ( str ( self . extrasPath ) ) ) , shell = True )
2020-08-15 19:34:10 +00:00
def download ( self , trackAPI_gw , track = None ) :
result = { }
if self . queueItem . cancel : raise DownloadCancelled
2020-10-02 16:57:23 +00:00
if trackAPI_gw [ ' SNG_ID ' ] == " 0 " : raise DownloadFailed ( " notOnDeezer " )
2020-08-15 19:34:10 +00:00
# Create Track object
if not track :
logger . info ( f " [ { trackAPI_gw [ ' ART_NAME ' ] } - { trackAPI_gw [ ' SNG_TITLE ' ] } ] Getting the tags " )
2020-09-24 17:20:01 +00:00
try :
track = Track ( self . dz ,
trackAPI_gw = trackAPI_gw ,
trackAPI = trackAPI_gw [ ' _EXTRA_TRACK ' ] if ' _EXTRA_TRACK ' in trackAPI_gw else None ,
albumAPI = trackAPI_gw [ ' _EXTRA_ALBUM ' ] if ' _EXTRA_ALBUM ' in trackAPI_gw else None
)
2020-10-04 09:56:32 +00:00
except AlbumDoesntExists :
raise DownloadError ( ' albumDoesntExists ' )
2020-08-15 19:34:10 +00:00
if self . queueItem . cancel : raise DownloadCancelled
2020-10-02 16:57:23 +00:00
# Check if track not yet encoded
2020-08-15 21:03:05 +00:00
if track . MD5 == ' ' :
2020-08-15 19:34:10 +00:00
if track . fallbackId != " 0 " :
logger . warn ( f " [ { track . mainArtist [ ' name ' ] } - { track . title } ] Track not yet encoded, using fallback id " )
2020-11-19 21:08:35 +00:00
newTrack = self . dz . gw . get_track_with_fallback ( track . fallbackId )
2020-08-15 19:34:10 +00:00
track . parseEssentialData ( self . dz , newTrack )
return self . download ( trackAPI_gw , track )
elif not track . searched and self . settings [ ' fallbackSearch ' ] :
2020-08-15 21:03:05 +00:00
logger . warn ( f " [ { track . mainArtist [ ' name ' ] } - { track . title } ] Track not yet encoded, searching for alternative " )
2020-11-19 21:08:35 +00:00
searchedId = self . dz . api . get_track_id_from_metadata ( track . mainArtist [ ' name ' ] , track . title , track . album [ ' title ' ] )
2020-08-18 13:13:32 +00:00
if searchedId != " 0 " :
2020-11-19 21:08:35 +00:00
newTrack = self . dz . gw . get_track_with_fallback ( searchedId )
2020-08-15 19:34:10 +00:00
track . parseEssentialData ( self . dz , newTrack )
track . searched = True
2020-09-20 09:29:18 +00:00
if self . interface :
self . interface . send ( ' queueUpdate ' , {
' uuid ' : self . queueItem . uuid ,
' searchFallback ' : True ,
' data ' : {
' id ' : track . id ,
' title ' : track . title ,
' artist ' : track . mainArtist [ ' name ' ]
} ,
} )
2020-08-15 19:34:10 +00:00
return self . download ( trackAPI_gw , track )
else :
raise DownloadFailed ( " notEncodedNoAlternative " )
else :
raise DownloadFailed ( " notEncoded " )
2020-12-15 12:27:02 +00:00
# Choose the target bitrate
2020-10-02 16:57:23 +00:00
try :
selectedFormat = self . getPreferredBitrate ( track )
except PreferredBitrateNotFound :
2020-08-15 19:34:10 +00:00
if track . fallbackId != " 0 " :
logger . warn ( f " [ { track . mainArtist [ ' name ' ] } - { track . title } ] Track not found at desired bitrate, using fallback id " )
2020-11-19 21:08:35 +00:00
newTrack = self . dz . gw . get_track_with_fallback ( track . fallbackId )
2020-08-15 19:34:10 +00:00
track . parseEssentialData ( self . dz , newTrack )
return self . download ( trackAPI_gw , track )
elif not track . searched and self . settings [ ' fallbackSearch ' ] :
2020-08-15 21:03:05 +00:00
logger . warn ( f " [ { track . mainArtist [ ' name ' ] } - { track . title } ] Track not found at desired bitrate, searching for alternative " )
2020-11-19 21:08:35 +00:00
searchedId = self . dz . api . get_track_id_from_metadata ( track . mainArtist [ ' name ' ] , track . title , track . album [ ' title ' ] )
2020-08-18 13:13:32 +00:00
if searchedId != " 0 " :
2020-11-19 21:08:35 +00:00
newTrack = self . dz . gw . get_track_with_fallback ( searchedId )
2020-08-15 19:34:10 +00:00
track . parseEssentialData ( self . dz , newTrack )
track . searched = True
2020-09-20 09:29:18 +00:00
if self . interface :
self . interface . send ( ' queueUpdate ' , {
' uuid ' : self . queueItem . uuid ,
' searchFallback ' : True ,
' data ' : {
' id ' : track . id ,
' title ' : track . title ,
' artist ' : track . mainArtist [ ' name ' ]
} ,
} )
2020-08-15 19:34:10 +00:00
return self . download ( trackAPI_gw , track )
else :
raise DownloadFailed ( " wrongBitrateNoAlternative " )
else :
raise DownloadFailed ( " wrongBitrate " )
2020-10-02 16:57:23 +00:00
except TrackNot360 :
2020-08-15 19:34:10 +00:00
raise DownloadFailed ( " no360RA " )
track . selectedFormat = selectedFormat
2020-12-15 12:27:02 +00:00
track . album [ ' bitrate ' ] = selectedFormat
2020-08-15 19:34:10 +00:00
2020-12-15 12:27:02 +00:00
# Generate covers URLs
embeddedImageFormat = f ' jpg- { self . settings [ " jpegImageQuality " ] } '
if self . settings [ ' embeddedArtworkPNG ' ] : imageFormat = ' png '
2020-10-02 16:57:23 +00:00
2020-08-15 19:34:10 +00:00
if self . settings [ ' tags ' ] [ ' savePlaylistAsCompilation ' ] and track . playlist :
track . trackNumber = track . position
track . discNumber = " 1 "
track . album = { * * track . album , * * track . playlist }
2020-12-15 12:27:02 +00:00
track . album [ ' embeddedCoverURL ' ] = generatePictureURL ( track . playlist [ ' pic ' ] , self . settings [ ' embeddedArtworkSize ' ] , embeddedImageFormat )
2020-10-02 16:57:23 +00:00
2020-12-15 12:27:02 +00:00
ext = track . album [ ' embeddedCoverURL ' ] [ - 4 : ]
if ext [ 0 ] != " . " : ext = " .jpg " # Check for Spotify images
2020-10-02 16:57:23 +00:00
2020-12-15 12:27:02 +00:00
track . album [ ' embeddedCoverPath ' ] = TEMPDIR / f " pl { trackAPI_gw [ ' _EXTRA_PLAYLIST ' ] [ ' id ' ] } _ { self . settings [ ' embeddedArtworkSize ' ] } { ext } "
2020-08-15 19:34:10 +00:00
else :
2020-10-02 16:57:23 +00:00
if track . album [ ' date ' ] : track . date = track . album [ ' date ' ]
2020-12-15 12:27:02 +00:00
track . album [ ' embeddedCoverURL ' ] = generatePictureURL ( track . album [ ' pic ' ] , self . settings [ ' embeddedArtworkSize ' ] , embeddedImageFormat )
2020-10-02 16:57:23 +00:00
2020-12-15 12:27:02 +00:00
ext = track . album [ ' embeddedCoverURL ' ] [ - 4 : ]
track . album [ ' embeddedCoverPath ' ] = TEMPDIR / f " alb { track . album [ ' id ' ] } _ { self . settings [ ' embeddedArtworkSize ' ] } { ext } "
2020-08-15 19:34:10 +00:00
2020-08-15 21:03:05 +00:00
track . dateString = formatDate ( track . date , self . settings [ ' dateFormat ' ] )
track . album [ ' dateString ' ] = formatDate ( track . album [ ' date ' ] , self . settings [ ' dateFormat ' ] )
2020-09-27 21:44:37 +00:00
if track . playlist : track . playlist [ ' dateString ' ] = formatDate ( track . playlist [ ' date ' ] , self . settings [ ' dateFormat ' ] )
2020-08-15 19:34:10 +00:00
2020-12-24 15:28:12 +00:00
# Check various artist option
if self . settings [ ' albumVariousArtists ' ] and track . album [ ' variousArtists ' ] :
artist = track . album [ ' variousArtists ' ]
isMainArtist = artist [ ' role ' ] == " Main "
if artist [ ' name ' ] not in track . album [ ' artists ' ] :
track . album [ ' artists ' ] . insert ( 0 , artist [ ' name ' ] )
if isMainArtist or artist [ ' name ' ] not in track . album [ ' artist ' ] [ ' Main ' ] and not isMainArtist :
if not artist [ ' role ' ] in track . album [ ' artist ' ] :
track . album [ ' artist ' ] [ artist [ ' role ' ] ] = [ ]
track . album [ ' artist ' ] [ artist [ ' role ' ] ] . insert ( 0 , artist [ ' name ' ] )
track . album [ ' mainArtist ' ] [ ' save ' ] = not track . album [ ' mainArtist ' ] [ ' isVariousArtists ' ] or self . settings [ ' albumVariousArtists ' ] and track . album [ ' mainArtist ' ] [ ' isVariousArtists ' ]
# Check removeDuplicateArtists
if self . settings [ ' removeDuplicateArtists ' ] : track . removeDuplicateArtists ( )
2020-08-15 19:34:10 +00:00
# Check if user wants the feat in the title
2020-10-03 08:56:01 +00:00
if str ( self . settings [ ' featuredToTitle ' ] ) == FeaturesOption . REMOVE_TITLE :
2020-08-15 19:34:10 +00:00
track . title = track . getCleanTitle ( )
2020-10-03 08:56:01 +00:00
elif str ( self . settings [ ' featuredToTitle ' ] ) == FeaturesOption . MOVE_TITLE :
2020-08-15 19:34:10 +00:00
track . title = track . getFeatTitle ( )
2020-10-03 08:56:01 +00:00
elif str ( self . settings [ ' featuredToTitle ' ] ) == FeaturesOption . REMOVE_TITLE_ALBUM :
2020-08-15 19:34:10 +00:00
track . title = track . getCleanTitle ( )
track . album [ ' title ' ] = track . getCleanAlbumTitle ( )
# Remove (Album Version) from tracks that have that
if self . settings [ ' removeAlbumVersion ' ] :
if " Album Version " in track . title :
track . title = re . sub ( r ' ? \ (Album Version \ ) ' , " " , track . title ) . strip ( )
2020-08-24 11:07:38 +00:00
# Change Title and Artists casing if needed
if self . settings [ ' titleCasing ' ] != " nothing " :
track . title = changeCase ( track . title , self . settings [ ' titleCasing ' ] )
if self . settings [ ' artistCasing ' ] != " nothing " :
track . mainArtist [ ' name ' ] = changeCase ( track . mainArtist [ ' name ' ] , self . settings [ ' artistCasing ' ] )
for i , artist in enumerate ( track . artists ) :
track . artists [ i ] = changeCase ( artist , self . settings [ ' artistCasing ' ] )
for type in track . artist :
for i , artist in enumerate ( track . artist [ type ] ) :
track . artist [ type ] [ i ] = changeCase ( artist , self . settings [ ' artistCasing ' ] )
track . generateMainFeatStrings ( )
2020-10-03 08:56:01 +00:00
# Generate artist tag
2020-09-27 21:44:37 +00:00
if self . settings [ ' tags ' ] [ ' multiArtistSeparator ' ] == " default " :
2020-10-03 08:56:01 +00:00
if str ( self . settings [ ' featuredToTitle ' ] ) == FeaturesOption . MOVE_TITLE :
track . artistsString = " , " . join ( track . artist [ ' Main ' ] )
else :
track . artistsString = " , " . join ( track . artists )
2020-09-27 21:44:37 +00:00
elif self . settings [ ' tags ' ] [ ' multiArtistSeparator ' ] == " andFeat " :
track . artistsString = track . mainArtistsString
2020-10-03 08:56:01 +00:00
if track . featArtistsString and str ( self . settings [ ' featuredToTitle ' ] ) != FeaturesOption . MOVE_TITLE :
2020-09-27 21:44:37 +00:00
track . artistsString + = " " + track . featArtistsString
else :
2020-10-03 08:56:01 +00:00
separator = self . settings [ ' tags ' ] [ ' multiArtistSeparator ' ]
if str ( self . settings [ ' featuredToTitle ' ] ) == FeaturesOption . MOVE_TITLE :
track . artistsString = separator . join ( track . artist [ ' Main ' ] )
else :
track . artistsString = separator . join ( track . artists )
2020-08-15 19:34:10 +00:00
# Generate filename and filepath from metadata
2020-09-27 21:44:37 +00:00
filename = generateFilename ( track , self . settings , trackAPI_gw [ ' FILENAME_TEMPLATE ' ] )
( filepath , artistPath , coverPath , extrasPath ) = generateFilepath ( track , self . settings )
2020-08-15 19:34:10 +00:00
if self . queueItem . cancel : raise DownloadCancelled
# Download and cache coverart
logger . info ( f " [ { track . mainArtist [ ' name ' ] } - { track . title } ] Getting the album cover " )
2020-12-15 12:27:02 +00:00
track . album [ ' embeddedCoverPath ' ] = downloadImage ( track . album [ ' embeddedCoverURL ' ] , track . album [ ' embeddedCoverPath ' ] )
2020-08-15 19:34:10 +00:00
# Save local album art
if coverPath :
result [ ' albumURLs ' ] = []
for format in self . settings [ ' localArtworkFormat ' ] . split ( " , " ) :
if format in [ " png " , " jpg " ] :
2020-12-15 12:27:02 +00:00
extendedFormat = format
if extendedFormat == " jpg " : extendedFormat + = f " - { self . settings [ ' jpegImageQuality ' ] } "
url = generatePictureURL ( track . album [ ' pic ' ] , self . settings [ ' localArtworkSize ' ] , extendedFormat )
if self . settings [ ' tags ' ] [ ' savePlaylistAsCompilation ' ] \
and track . playlist \
and track . playlist [ ' pic ' ] [ ' url ' ] \
and not format . startswith ( " jpg " ) :
continue
2020-08-15 19:34:10 +00:00
result [ ' albumURLs ' ] . append ( { ' url ' : url , ' ext ' : format } )
2020-09-27 21:44:37 +00:00
result [ ' albumPath ' ] = coverPath
result [ ' albumFilename ' ] = f " { settingsRegexAlbum ( self . settings [ ' coverImageTemplate ' ] , track . album , self . settings , track . playlist ) } "
2020-08-15 19:34:10 +00:00
# Save artist art
if artistPath :
result [ ' artistURLs ' ] = [ ]
for format in self . settings [ ' localArtworkFormat ' ] . split ( " , " ) :
if format in [ " png " , " jpg " ] :
2020-12-15 12:27:02 +00:00
extendedFormat = format
if extendedFormat == " jpg " : extendedFormat + = f " - { self . settings [ ' jpegImageQuality ' ] } "
url = generatePictureURL ( track . album [ ' mainArtist ' ] [ ' pic ' ] , self . settings [ ' localArtworkSize ' ] , extendedFormat )
if track . album [ ' mainArtist ' ] [ ' pic ' ] [ ' md5 ' ] == " " and not format . startswith ( " jpg " ) : continue
result [ ' artistURLs ' ] . append ( { ' url ' : url , ' ext ' : format } )
2020-09-27 21:44:37 +00:00
result [ ' artistPath ' ] = artistPath
2020-11-21 15:45:40 +00:00
result [ ' artistFilename ' ] = f " { settingsRegexArtist ( self . settings [ ' artistImageTemplate ' ] , track . album [ ' mainArtist ' ] , self . settings , rootArtist = track . album [ ' rootArtist ' ] ) } "
2020-08-15 19:34:10 +00:00
2020-12-15 12:27:02 +00:00
# Save playlist cover
if track . playlist :
if not len ( self . playlistURLs ) :
for format in self . settings [ ' localArtworkFormat ' ] . split ( " , " ) :
if format in [ " png " , " jpg " ] :
extendedFormat = format
if extendedFormat == " jpg " : extendedFormat + = f " - { self . settings [ ' jpegImageQuality ' ] } "
url = generatePictureURL ( track . playlist [ ' pic ' ] , self . settings [ ' localArtworkSize ' ] , extendedFormat )
if track . playlist [ ' pic ' ] [ ' url ' ] and not format . startswith ( " jpg " ) : continue
self . playlistURLs . append ( { ' url ' : url , ' ext ' : format } )
if not self . playlistCoverName :
track . playlist [ ' id ' ] = " pl_ " + str ( trackAPI_gw [ ' _EXTRA_PLAYLIST ' ] [ ' id ' ] )
track . playlist [ ' genre ' ] = [ " Compilation " , ]
track . playlist [ ' bitrate ' ] = selectedFormat
track . playlist [ ' dateString ' ] = formatDate ( track . playlist [ ' date ' ] , self . settings [ ' dateFormat ' ] )
self . playlistCoverName = f " { settingsRegexAlbum ( self . settings [ ' coverImageTemplate ' ] , track . playlist , self . settings , track . playlist ) } "
2020-08-15 19:34:10 +00:00
# Remove subfolders from filename and add it to filepath
2020-09-27 21:44:37 +00:00
if pathSep in filename :
tempPath = filename [ : filename . rfind ( pathSep ) ]
filepath = filepath / tempPath
filename = filename [ filename . rfind ( pathSep ) + len ( pathSep ) : ]
2020-08-15 19:34:10 +00:00
2020-10-04 09:56:32 +00:00
# Make sure the filepath exists
2020-08-15 19:34:10 +00:00
makedirs ( filepath , exist_ok = True )
2020-09-27 21:44:37 +00:00
writepath = filepath / f " { filename } { extensions [ track . selectedFormat ] } "
2020-08-15 19:34:10 +00:00
# Save lyrics in lrc file
2020-08-16 11:25:25 +00:00
if self . settings [ ' syncedLyrics ' ] and track . lyrics [ ' sync ' ] :
2020-10-03 08:56:01 +00:00
if not ( filepath / f " { filename } .lrc " ) . is_file ( ) or self . settings [ ' overwriteFile ' ] in [ OverwriteOption . OVERWRITE , OverwriteOption . ONLY_TAGS ] :
2020-09-27 21:44:37 +00:00
with open ( filepath / f " { filename } .lrc " , ' wb ' ) as f :
2020-08-15 19:34:10 +00:00
f . write ( track . lyrics [ ' sync ' ] . encode ( ' utf-8 ' ) )
2020-09-27 21:44:37 +00:00
trackAlreadyDownloaded = writepath . is_file ( )
2020-10-02 16:57:23 +00:00
# Don't overwrite and don't mind extension
2020-10-03 08:56:01 +00:00
if not trackAlreadyDownloaded and self . settings [ ' overwriteFile ' ] == OverwriteOption . DONT_CHECK_EXT :
2020-08-21 12:46:34 +00:00
exts = [ ' .mp3 ' , ' .flac ' , ' .opus ' , ' .m4a ' ]
2020-09-27 21:44:37 +00:00
baseFilename = str ( filepath / filename )
2020-08-21 12:46:34 +00:00
for ext in exts :
2020-09-27 21:44:37 +00:00
trackAlreadyDownloaded = Path ( baseFilename + ext ) . is_file ( )
2020-10-02 16:57:23 +00:00
if trackAlreadyDownloaded : break
# Don't overwrite and keep both files
2020-10-03 08:56:01 +00:00
if trackAlreadyDownloaded and self . settings [ ' overwriteFile ' ] == OverwriteOption . KEEP_BOTH :
2020-09-27 21:44:37 +00:00
baseFilename = str ( filepath / filename )
2020-08-15 19:34:10 +00:00
i = 1
currentFilename = baseFilename + ' ( ' + str ( i ) + ' ) ' + extensions [ track . selectedFormat ]
2020-09-27 21:44:37 +00:00
while Path ( currentFilename ) . is_file ( ) :
2020-08-15 19:34:10 +00:00
i + = 1
currentFilename = baseFilename + ' ( ' + str ( i ) + ' ) ' + extensions [ track . selectedFormat ]
trackAlreadyDownloaded = False
2020-09-27 21:44:37 +00:00
writepath = Path ( currentFilename )
2020-08-15 19:34:10 +00:00
if extrasPath :
2020-09-27 21:44:37 +00:00
if not self . extrasPath : self . extrasPath = extrasPath
2020-10-01 16:42:41 +00:00
result [ ' filename ' ] = str ( writepath ) [ len ( str ( extrasPath ) ) + len ( pathSep ) : ]
2020-08-15 19:34:10 +00:00
2020-10-03 08:56:01 +00:00
if not trackAlreadyDownloaded or self . settings [ ' overwriteFile ' ] == OverwriteOption . OVERWRITE :
2020-09-18 20:17:58 +00:00
logger . info ( f " [ { track . mainArtist [ ' name ' ] } - { track . title } ] Downloading the track " )
2020-11-19 21:08:35 +00:00
track . downloadUrl = generateStreamURL ( track . id , track . MD5 , track . mediaVersion , track . selectedFormat )
2020-09-18 20:17:58 +00:00
def downloadMusic ( track , trackAPI_gw ) :
try :
with open ( writepath , ' wb ' ) as stream :
self . streamTrack ( stream , track )
except DownloadCancelled :
2020-09-27 21:44:37 +00:00
if writepath . is_file ( ) : writepath . unlink ( )
2020-09-18 20:17:58 +00:00
raise DownloadCancelled
except ( request_exception . HTTPError , DownloadEmpty ) :
2020-09-27 21:44:37 +00:00
if writepath . is_file ( ) : writepath . unlink ( )
2020-09-18 20:17:58 +00:00
if track . fallbackId != " 0 " :
logger . warn ( f " [ { track . mainArtist [ ' name ' ] } - { track . title } ] Track not available, using fallback id " )
2020-11-19 21:08:35 +00:00
newTrack = self . dz . gw . get_track_with_fallback ( track . fallbackId )
2020-09-18 20:17:58 +00:00
track . parseEssentialData ( self . dz , newTrack )
return False
elif not track . searched and self . settings [ ' fallbackSearch ' ] :
logger . warn ( f " [ { track . mainArtist [ ' name ' ] } - { track . title } ] Track not available, searching for alternative " )
2020-11-19 21:08:35 +00:00
searchedId = self . dz . api . get_track_id_from_metadata ( track . mainArtist [ ' name ' ] , track . title , track . album [ ' title ' ] )
2020-09-18 20:17:58 +00:00
if searchedId != " 0 " :
2020-11-19 21:08:35 +00:00
newTrack = self . dz . gw . get_track_with_fallback ( searchedId )
2020-08-15 19:34:10 +00:00
track . parseEssentialData ( self . dz , newTrack )
2020-09-18 20:17:58 +00:00
track . searched = True
2020-09-20 09:29:18 +00:00
if self . interface :
self . interface . send ( ' queueUpdate ' , {
' uuid ' : self . queueItem . uuid ,
' searchFallback ' : True ,
' data ' : {
' id ' : track . id ,
' title ' : track . title ,
' artist ' : track . mainArtist [ ' name ' ]
} ,
} )
2020-08-15 19:34:10 +00:00
return False
else :
2020-09-18 20:17:58 +00:00
raise DownloadFailed ( " notAvailableNoAlternative " )
else :
raise DownloadFailed ( " notAvailable " )
except ( request_exception . ConnectionError , request_exception . ChunkedEncodingError ) as e :
2020-09-27 21:44:37 +00:00
if writepath . is_file ( ) : writepath . unlink ( )
2020-09-18 20:17:58 +00:00
logger . warn ( f " [ { track . mainArtist [ ' name ' ] } - { track . title } ] Error while downloading the track, trying again in 5s... " )
eventlet . sleep ( 5 )
return downloadMusic ( track , trackAPI_gw )
except OSError as e :
if e . errno == errno . ENOSPC :
raise DownloadFailed ( " noSpaceLeft " )
else :
2020-09-27 21:44:37 +00:00
if writepath . is_file ( ) : writepath . unlink ( )
2020-09-10 09:43:32 +00:00
logger . exception ( f " [ { track . mainArtist [ ' name ' ] } - { track . title } ] Error while downloading the track, you should report this to the developers: { str ( e ) } " )
2020-08-15 19:34:10 +00:00
raise e
except Exception as e :
2020-09-27 21:44:37 +00:00
if writepath . is_file ( ) : writepath . unlink ( )
2020-09-18 20:17:58 +00:00
logger . exception ( f " [ { track . mainArtist [ ' name ' ] } - { track . title } ] Error while downloading the track, you should report this to the developers: { str ( e ) } " )
2020-08-15 19:34:10 +00:00
raise e
2020-09-18 20:17:58 +00:00
return True
try :
trackDownloaded = downloadMusic ( track , trackAPI_gw )
except Exception as e :
raise e
2020-08-15 19:34:10 +00:00
2020-10-02 16:57:23 +00:00
if not trackDownloaded : return self . download ( trackAPI_gw , track )
2020-09-18 20:17:58 +00:00
else :
logger . info ( f " [ { track . mainArtist [ ' name ' ] } - { track . title } ] Skipping track as it ' s already downloaded " )
self . completeTrackPercentage ( )
# Adding tags
2020-10-03 08:56:01 +00:00
if ( not trackAlreadyDownloaded or self . settings [ ' overwriteFile ' ] in [ OverwriteOption . ONLY_TAGS , OverwriteOption . OVERWRITE ] ) and not track . localTrack :
2020-09-18 20:17:58 +00:00
logger . info ( f " [ { track . mainArtist [ ' name ' ] } - { track . title } ] Applying tags to the track " )
2020-10-02 17:20:23 +00:00
if track . selectedFormat in [ TrackFormats . MP3_320 , TrackFormats . MP3_128 , TrackFormats . DEFAULT ] :
2020-09-18 20:17:58 +00:00
tagID3 ( writepath , track , self . settings [ ' tags ' ] )
2020-10-02 17:20:23 +00:00
elif track . selectedFormat == TrackFormats . FLAC :
2020-09-18 20:17:58 +00:00
try :
tagFLAC ( writepath , track , self . settings [ ' tags ' ] )
2020-09-30 07:33:14 +00:00
except ( FLACNoHeaderError , FLACError ) :
2020-09-27 21:44:37 +00:00
if writepath . is_file ( ) : writepath . unlink ( )
2020-09-18 20:17:58 +00:00
logger . warn ( f " [ { track . mainArtist [ ' name ' ] } - { track . title } ] Track not available in FLAC, falling back if necessary " )
self . removeTrackPercentage ( )
track . filesizes [ ' FILESIZE_FLAC ' ] = " 0 "
track . filesizes [ ' FILESIZE_FLAC_TESTED ' ] = True
2020-08-15 19:34:10 +00:00
return self . download ( trackAPI_gw , track )
2020-09-18 20:17:58 +00:00
2020-10-02 16:57:23 +00:00
if track . searched : result [ ' searched ' ] = f " { track . mainArtist [ ' name ' ] } - { track . title } "
2020-09-27 21:44:37 +00:00
logger . info ( f " [ { track . mainArtist [ ' name ' ] } - { track . title } ] Track download completed \n { str ( writepath ) } " )
2020-09-18 20:17:58 +00:00
self . queueItem . downloaded + = 1
2020-09-27 21:44:37 +00:00
self . queueItem . files . append ( str ( writepath ) )
2020-10-01 10:56:51 +00:00
self . queueItem . extrasPath = str ( self . extrasPath )
2020-09-18 20:17:58 +00:00
if self . interface :
2020-10-01 10:56:51 +00:00
self . interface . send ( " updateQueue " , { ' uuid ' : self . queueItem . uuid , ' downloaded ' : True , ' downloadPath ' : str ( writepath ) , ' extrasPath ' : str ( self . extrasPath ) } )
2020-09-18 20:17:58 +00:00
return result
2020-08-15 19:34:10 +00:00
def getPreferredBitrate ( self , track ) :
2020-10-02 17:20:23 +00:00
if track . localTrack : return TrackFormats . LOCAL
2020-08-15 19:34:10 +00:00
2020-10-02 16:57:23 +00:00
shouldFallback = self . settings [ ' fallbackBitrate ' ]
2020-09-20 09:29:18 +00:00
falledBack = False
2020-08-15 19:34:10 +00:00
formats_non_360 = {
2020-10-02 17:20:23 +00:00
TrackFormats . FLAC : " FLAC " ,
TrackFormats . MP3_320 : " MP3_320 " ,
TrackFormats . MP3_128 : " MP3_128 " ,
2020-08-15 19:34:10 +00:00
}
formats_360 = {
2020-10-02 17:20:23 +00:00
TrackFormats . MP4_RA3 : " MP4_RA3 " ,
TrackFormats . MP4_RA2 : " MP4_RA2 " ,
TrackFormats . MP4_RA1 : " MP4_RA1 " ,
2020-08-15 19:34:10 +00:00
}
2020-10-02 16:57:23 +00:00
is360format = int ( self . bitrate ) in formats_360
if not shouldFallback :
2020-08-15 19:34:10 +00:00
formats = formats_360
formats . update ( formats_non_360 )
2020-10-02 16:57:23 +00:00
elif is360format :
2020-08-15 19:34:10 +00:00
formats = formats_360
else :
formats = formats_non_360
2020-10-02 16:57:23 +00:00
for formatNumber , formatName in formats . items ( ) :
if formatNumber < = int ( self . bitrate ) :
if f " FILESIZE_ { formatName } " in track . filesizes :
if int ( track . filesizes [ f " FILESIZE_ { formatName } " ] ) != 0 : return formatNumber
if not track . filesizes [ f " FILESIZE_ { formatName } _TESTED " ] :
request = requests . head (
2020-11-19 21:08:35 +00:00
generateStreamURL ( track . id , track . MD5 , track . mediaVersion , formatNumber ) ,
2020-10-02 16:57:23 +00:00
headers = { ' User-Agent ' : USER_AGENT_HEADER } ,
timeout = 30
)
2020-08-22 11:35:24 +00:00
try :
request . raise_for_status ( )
2020-10-02 16:57:23 +00:00
return formatNumber
2020-09-07 10:09:46 +00:00
except request_exception . HTTPError : # if the format is not available, Deezer returns a 403 error
2020-08-22 11:35:24 +00:00
pass
2020-10-02 16:57:23 +00:00
if not shouldFallback :
raise PreferredBitrateNotFound
else :
2020-09-20 09:29:18 +00:00
if not falledBack :
falledBack = True
logger . info ( f " [ { track . mainArtist [ ' name ' ] } - { track . title } ] Fallback to lower bitrate " )
if self . interface :
self . interface . send ( ' queueUpdate ' , {
' uuid ' : self . queueItem . uuid ,
' bitrateFallback ' : True ,
' data ' : {
' id ' : track . id ,
' title ' : track . title ,
' artist ' : track . mainArtist [ ' name ' ]
} ,
} )
2020-10-02 16:57:23 +00:00
if is360format : raise TrackNot360
2020-10-02 17:20:23 +00:00
return TrackFormats . DEFAULT
2020-08-15 19:34:10 +00:00
2020-09-29 18:39:17 +00:00
def streamTrack ( self , stream , track , start = 0 ) :
2020-08-15 19:34:10 +00:00
if self . queueItem . cancel : raise DownloadCancelled
2020-09-29 18:39:17 +00:00
headers = dict ( self . dz . http_headers )
2020-10-02 16:57:23 +00:00
if range != 0 : headers [ ' Range ' ] = f ' bytes= { start } - '
2020-09-29 18:39:17 +00:00
chunkLength = start
2020-08-15 19:34:10 +00:00
percentage = 0
2020-09-29 18:39:17 +00:00
2020-10-01 10:56:39 +00:00
itemName = f " [ { track . mainArtist [ ' name ' ] } - { track . title } ] "
2020-09-16 14:44:15 +00:00
try :
2020-09-29 18:39:17 +00:00
with self . dz . session . get ( track . downloadUrl , headers = headers , stream = True , timeout = 10 ) as request :
request . raise_for_status ( )
2020-11-19 21:08:35 +00:00
blowfish_key = str . encode ( generateBlowfishKey ( str ( track . id ) ) )
2020-09-29 18:39:17 +00:00
complete = int ( request . headers [ " Content-Length " ] )
2020-10-02 16:57:23 +00:00
if complete == 0 : raise DownloadEmpty
2020-09-29 18:39:17 +00:00
if start != 0 :
responseRange = request . headers [ " Content-Range " ]
2020-10-01 10:56:39 +00:00
logger . info ( f ' { itemName } downloading range { responseRange } ' )
2020-09-16 14:44:15 +00:00
else :
2020-10-01 10:56:39 +00:00
logger . info ( f ' { itemName } downloading { complete } bytes ' )
2020-09-29 18:39:17 +00:00
for chunk in request . iter_content ( 2048 * 3 ) :
if self . queueItem . cancel : raise DownloadCancelled
2020-10-01 16:42:41 +00:00
2020-09-29 18:39:17 +00:00
if len ( chunk ) > = 2048 :
chunk = Blowfish . new ( blowfish_key , Blowfish . MODE_CBC , b " \x00 \x01 \x02 \x03 \x04 \x05 \x06 \x07 " ) . decrypt ( chunk [ 0 : 2048 ] ) + chunk [ 2048 : ]
stream . write ( chunk )
chunkLength + = len ( chunk )
if isinstance ( self . queueItem , QISingle ) :
percentage = ( chunkLength / ( complete + start ) ) * 100
self . downloadPercentage = percentage
else :
chunkProgres = ( len ( chunk ) / ( complete + start ) ) / self . queueItem . size * 100
self . downloadPercentage + = chunkProgres
self . updatePercentage ( )
2020-11-30 15:45:53 +00:00
except ( SSLError , u3SSLError ) as e :
2020-10-01 10:56:39 +00:00
logger . info ( f ' { itemName } retrying from byte { chunkLength } ' )
2020-09-29 18:39:17 +00:00
return self . streamTrack ( stream , track , chunkLength )
except ( request_exception . ConnectionError , requests . exceptions . ReadTimeout ) :
eventlet . sleep ( 2 )
return self . streamTrack ( stream , track , start )
2020-08-15 19:34:10 +00:00
def updatePercentage ( self ) :
if round ( self . downloadPercentage ) != self . lastPercentage and round ( self . downloadPercentage ) % 2 == 0 :
self . lastPercentage = round ( self . downloadPercentage )
self . queueItem . progress = self . lastPercentage
2020-10-02 16:57:23 +00:00
if self . interface : self . interface . send ( " updateQueue " , { ' uuid ' : self . queueItem . uuid , ' progress ' : self . lastPercentage } )
2020-08-15 19:34:10 +00:00
def completeTrackPercentage ( self ) :
if isinstance ( self . queueItem , QISingle ) :
self . downloadPercentage = 100
else :
self . downloadPercentage + = ( 1 / self . queueItem . size ) * 100
self . updatePercentage ( )
def removeTrackPercentage ( self ) :
if isinstance ( self . queueItem , QISingle ) :
self . downloadPercentage = 0
else :
self . downloadPercentage - = ( 1 / self . queueItem . size ) * 100
self . updatePercentage ( )
def downloadWrapper ( self , trackAPI_gw ) :
track = {
2020-08-15 21:03:05 +00:00
' id ' : trackAPI_gw [ ' SNG_ID ' ] ,
2020-10-02 16:57:23 +00:00
' title ' : trackAPI_gw [ ' SNG_TITLE ' ] . strip ( ) ,
2020-08-15 21:03:05 +00:00
' artist ' : trackAPI_gw [ ' ART_NAME ' ]
2020-08-15 19:34:10 +00:00
}
2020-10-02 16:57:23 +00:00
if trackAPI_gw . get ( ' VERSION ' ) and trackAPI_gw [ ' VERSION ' ] not in trackAPI_gw [ ' SNG_TITLE ' ] :
track [ ' title ' ] + = f " { trackAPI_gw [ ' VERSION ' ] } " . strip ( )
2020-08-15 19:34:10 +00:00
try :
result = self . download ( trackAPI_gw )
except DownloadCancelled :
return None
except DownloadFailed as error :
2020-08-15 21:03:05 +00:00
logger . error ( f " [ { track [ ' artist ' ] } - { track [ ' title ' ] } ] { error . message } " )
2020-08-15 19:34:10 +00:00
result = { ' error ' : {
' message ' : error . message ,
' errid ' : error . errid ,
' data ' : track
} }
except Exception as e :
2020-08-15 21:03:05 +00:00
logger . exception ( f " [ { track [ ' artist ' ] } - { track [ ' title ' ] } ] { str ( e ) } " )
2020-08-15 19:34:10 +00:00
result = { ' error ' : {
' message ' : str ( e ) ,
' data ' : track
} }
if ' error ' in result :
self . completeTrackPercentage ( )
self . queueItem . failed + = 1
2020-08-16 10:29:51 +00:00
self . queueItem . errors . append ( result [ ' error ' ] )
2020-08-15 21:03:05 +00:00
if self . interface :
2020-08-15 19:34:10 +00:00
error = result [ ' error ' ]
2020-08-15 21:03:05 +00:00
self . interface . send ( " updateQueue " , {
2020-08-15 19:34:10 +00:00
' uuid ' : self . queueItem . uuid ,
' failed ' : True ,
' data ' : error [ ' data ' ] ,
' error ' : error [ ' message ' ] ,
' errid ' : error [ ' errid ' ] if ' errid ' in error else None
} )
return result
class DownloadError ( Exception ) :
""" Base class for exceptions in this module. """
pass
class DownloadFailed ( DownloadError ) :
def __init__ ( self , errid ) :
self . errid = errid
self . message = errorMessages [ self . errid ]
class DownloadCancelled ( DownloadError ) :
pass
2020-08-15 21:36:32 +00:00
class DownloadEmpty ( DownloadError ) :
pass
2020-10-02 16:57:23 +00:00
class PreferredBitrateNotFound ( DownloadError ) :
pass
class TrackNot360 ( DownloadError ) :
pass