Merge pull request 'Refactored code to be Object Oriented where possible' (#22) from dev into main
Reviewed-on: https://codeberg.org/RemixDev/deemix/pulls/22
This commit is contained in:
		| @ -1,3 +1,3 @@ | ||||
| #!/usr/bin/env python3 | ||||
|  | ||||
| __version__ = "1.1.31" | ||||
| __version__ = "1.2.0" | ||||
|  | ||||
| @ -1,30 +1,25 @@ | ||||
| #!/usr/bin/env python3 | ||||
| import click | ||||
|  | ||||
| import deemix.app.cli as app | ||||
| from deemix.app.settings import initSettings | ||||
| from deemix.app.cli import cli | ||||
| from os.path import isfile | ||||
|  | ||||
|  | ||||
| @click.command() | ||||
| @click.option('-b', '--bitrate', default=None, help='Overwrites the default bitrate selected') | ||||
| @click.option('-l', '--local', is_flag=True, help='Downloads in a local folder insted of using the default') | ||||
| @click.argument('url', nargs=-1, required=True) | ||||
| def download(bitrate, local, url): | ||||
|     settings = initSettings(local) | ||||
|     app = cli(local) | ||||
|     app.login() | ||||
|     url = list(url) | ||||
|     if isfile(url[0]): | ||||
|         filename = url[0] | ||||
|         with open(filename) as f: | ||||
|             url = f.readlines() | ||||
|     app.downloadLink(url, settings, bitrate) | ||||
|     app.downloadLink(url, bitrate) | ||||
|     click.echo("All done!") | ||||
|     if local: | ||||
|         click.echo(settings['downloadLocation']) #folder name output | ||||
|  | ||||
| def main(): | ||||
|     download() | ||||
|         click.echo(app.set.settings['downloadLocation']) #folder name output | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     main() | ||||
|     download() | ||||
|  | ||||
| @ -257,7 +257,35 @@ class Deezer: | ||||
|         return self.gw_api_call('deezer.pageArtist', {'art_id': art_id}) | ||||
|  | ||||
|     def get_playlist_gw(self, playlist_id): | ||||
|         return self.gw_api_call('deezer.pagePlaylist', {'playlist_id': playlist_id, 'lang': 'en'}) | ||||
|         playlistAPI = self.gw_api_call('deezer.pagePlaylist', {'playlist_id': playlist_id, 'lang': 'en'})['results']['DATA'] | ||||
|         return { | ||||
|             'id': playlistAPI['PLAYLIST_ID'], | ||||
|             'title': playlistAPI['TITLE'], | ||||
|             'description': playlistAPI['DESCRIPTION'], | ||||
|             'duration': playlistAPI['DURATION'], | ||||
|             'public': playlistAPI['STATUS'] == 1, | ||||
|             'is_loved_track': playlistAPI['TYPE'] == 4, | ||||
|             'collaborative': playlistAPI['STATUS'] == 2, | ||||
|             'nb_tracks': playlistAPI['NB_SONG'], | ||||
|             'fans': playlistAPI['NB_FAN'], | ||||
|             'link': "https://www.deezer.com/playlist/"+playlistAPI['PLAYLIST_ID'], | ||||
|             'share': "https://www.deezer.com/playlist/"+playlistAPI['PLAYLIST_ID'], | ||||
|             'picture': "https://api.deezer.com/playlist/"+playlistAPI['PLAYLIST_ID']+"/image", | ||||
|             'picture_small': "https://cdns-images.dzcdn.net/images/"+playlistAPI['PICTURE_TYPE']+"/"+playlistAPI['PLAYLIST_PICTURE']+"/56x56-000000-80-0-0.jpg", | ||||
|             'picture_medium': "https://cdns-images.dzcdn.net/images/"+playlistAPI['PICTURE_TYPE']+"/"+playlistAPI['PLAYLIST_PICTURE']+"/250x250-000000-80-0-0.jpg", | ||||
|             'picture_big': "https://cdns-images.dzcdn.net/images/"+playlistAPI['PICTURE_TYPE']+"/"+playlistAPI['PLAYLIST_PICTURE']+"/500x500-000000-80-0-0.jpg", | ||||
|             'picture_xl': "https://cdns-images.dzcdn.net/images/"+playlistAPI['PICTURE_TYPE']+"/"+playlistAPI['PLAYLIST_PICTURE']+"/1000x1000-000000-80-0-0.jpg", | ||||
|             'checksum': playlistAPI['CHECKSUM'], | ||||
|             'tracklist': "https://api.deezer.com/playlist/"+playlistAPI['PLAYLIST_ID']+"/tracks", | ||||
|             'creation_date': playlistAPI['DATE_ADD'], | ||||
|             'creator': { | ||||
|                 'id': playlistAPI['PARENT_USER_ID'], | ||||
|                 'name': playlistAPI['PARENT_USERNAME'], | ||||
|                 'tracklist': "https://api.deezer.com/user/"+playlistAPI['PARENT_USER_ID']+"/flow", | ||||
|                 'type': "user" | ||||
|             }, | ||||
|             'type': "playlist" | ||||
|         } | ||||
|  | ||||
|     def get_playlist_tracks_gw(self, playlist_id): | ||||
|         tracks_array = [] | ||||
| @ -449,37 +477,37 @@ class Deezer: | ||||
|         for track in data: | ||||
|             item = { | ||||
|                 'id': track['SNG_ID'], | ||||
| 				'title': track['SNG_TITLE'], | ||||
| 				'link': 'https://www.deezer.com/track/'+str(track['SNG_ID']), | ||||
| 				'duration': track['DURATION'], | ||||
| 				'rank': track['RANK_SNG'], | ||||
| 				'explicit_lyrics': int(track['EXPLICIT_LYRICS']) > 0, | ||||
| 				'explicit_content_lyrics': track['EXPLICIT_TRACK_CONTENT']['EXPLICIT_COVER_STATUS'], | ||||
| 				'explicit_content_cover': track['EXPLICIT_TRACK_CONTENT']['EXPLICIT_LYRICS_STATUS'], | ||||
| 				'time_add': track['DATE_ADD'], | ||||
| 				'album': { | ||||
| 						'id': track['ALB_ID'], | ||||
| 						'title': track['ALB_TITLE'], | ||||
| 						'cover': 'https://api.deezer.com/album/'+str(track['ALB_ID'])+'/image', | ||||
| 						'cover_small': 'https://e-cdns-images.dzcdn.net/images/cover/'+str(track['ALB_PICTURE'])+'/56x56-000000-80-0-0.jpg', | ||||
| 						'cover_medium': 'https://e-cdns-images.dzcdn.net/images/cover/'+str(track['ALB_PICTURE'])+'/250x250-000000-80-0-0.jpg', | ||||
| 						'cover_big': 'https://e-cdns-images.dzcdn.net/images/cover/'+str(track['ALB_PICTURE'])+'/500x500-000000-80-0-0.jpg', | ||||
| 						'cover_xl': 'https://e-cdns-images.dzcdn.net/images/cover/'+str(track['ALB_PICTURE'])+'/1000x1000-000000-80-0-0.jpg', | ||||
| 						'tracklist': 'https://api.deezer.com/album/'+str(track['ALB_ID'])+'/tracks', | ||||
| 						'type': 'album' | ||||
| 				}, | ||||
| 				'artist': { | ||||
| 						'id': track['ART_ID'], | ||||
| 						'name': track['ART_NAME'], | ||||
| 						'picture': 'https://api.deezer.com/artist/'+str(track['ART_ID'])+'/image', | ||||
| 						'picture_small': 'https://e-cdns-images.dzcdn.net/images/artist/'+str(track['ART_PICTURE'])+'/56x56-000000-80-0-0.jpg', | ||||
| 						'picture_medium': 'https://e-cdns-images.dzcdn.net/images/artist/'+str(track['ART_PICTURE'])+'/250x250-000000-80-0-0.jpg', | ||||
| 						'picture_big': 'https://e-cdns-images.dzcdn.net/images/artist/'+str(track['ART_PICTURE'])+'/500x500-000000-80-0-0.jpg', | ||||
| 						'picture_xl': 'https://e-cdns-images.dzcdn.net/images/artist/'+str(track['ART_PICTURE'])+'/1000x1000-000000-80-0-0.jpg', | ||||
| 						'tracklist': 'https://api.deezer.com/artist/'+str(track['ART_ID'])+'/top?limit=50', | ||||
| 						'type': 'artist' | ||||
| 				}, | ||||
| 				'type': 'track' | ||||
|                 'title': track['SNG_TITLE'], | ||||
|                 'link': 'https://www.deezer.com/track/'+str(track['SNG_ID']), | ||||
|                 'duration': track['DURATION'], | ||||
|                 'rank': track['RANK_SNG'], | ||||
|                 'explicit_lyrics': int(track['EXPLICIT_LYRICS']) > 0, | ||||
|                 'explicit_content_lyrics': track['EXPLICIT_TRACK_CONTENT']['EXPLICIT_COVER_STATUS'], | ||||
|                 'explicit_content_cover': track['EXPLICIT_TRACK_CONTENT']['EXPLICIT_LYRICS_STATUS'], | ||||
|                 'time_add': track['DATE_ADD'], | ||||
|                 'album': { | ||||
|                         'id': track['ALB_ID'], | ||||
|                         'title': track['ALB_TITLE'], | ||||
|                         'cover': 'https://api.deezer.com/album/'+str(track['ALB_ID'])+'/image', | ||||
|                         'cover_small': 'https://e-cdns-images.dzcdn.net/images/cover/'+str(track['ALB_PICTURE'])+'/56x56-000000-80-0-0.jpg', | ||||
|                         'cover_medium': 'https://e-cdns-images.dzcdn.net/images/cover/'+str(track['ALB_PICTURE'])+'/250x250-000000-80-0-0.jpg', | ||||
|                         'cover_big': 'https://e-cdns-images.dzcdn.net/images/cover/'+str(track['ALB_PICTURE'])+'/500x500-000000-80-0-0.jpg', | ||||
|                         'cover_xl': 'https://e-cdns-images.dzcdn.net/images/cover/'+str(track['ALB_PICTURE'])+'/1000x1000-000000-80-0-0.jpg', | ||||
|                         'tracklist': 'https://api.deezer.com/album/'+str(track['ALB_ID'])+'/tracks', | ||||
|                         'type': 'album' | ||||
|                 }, | ||||
|                 'artist': { | ||||
|                         'id': track['ART_ID'], | ||||
|                         'name': track['ART_NAME'], | ||||
|                         'picture': 'https://api.deezer.com/artist/'+str(track['ART_ID'])+'/image', | ||||
|                         'picture_small': 'https://e-cdns-images.dzcdn.net/images/artist/'+str(track['ART_PICTURE'])+'/56x56-000000-80-0-0.jpg', | ||||
|                         'picture_medium': 'https://e-cdns-images.dzcdn.net/images/artist/'+str(track['ART_PICTURE'])+'/250x250-000000-80-0-0.jpg', | ||||
|                         'picture_big': 'https://e-cdns-images.dzcdn.net/images/artist/'+str(track['ART_PICTURE'])+'/500x500-000000-80-0-0.jpg', | ||||
|                         'picture_xl': 'https://e-cdns-images.dzcdn.net/images/artist/'+str(track['ART_PICTURE'])+'/1000x1000-000000-80-0-0.jpg', | ||||
|                         'tracklist': 'https://api.deezer.com/artist/'+str(track['ART_ID'])+'/top?limit=50', | ||||
|                         'type': 'artist' | ||||
|                 }, | ||||
|                 'type': 'track' | ||||
|             } | ||||
|             result.append(item) | ||||
|         return result | ||||
|  | ||||
| @ -1,2 +1,12 @@ | ||||
| #!/usr/bin/env python3 | ||||
| # Empty File | ||||
| from deemix.api.deezer import Deezer | ||||
| from deemix.app.settings import Settings | ||||
| from deemix.app.queuemanager import QueueManager | ||||
| from deemix.app.spotifyhelper import SpotifyHelper | ||||
|  | ||||
| class deemix: | ||||
|     def __init__(self, configFolder=None): | ||||
|         self.set = Settings(configFolder) | ||||
|         self.dz = Deezer() | ||||
|         self.sp = SpotifyHelper(configFolder) | ||||
|         self.qm = QueueManager() | ||||
|  | ||||
| @ -1,43 +1,47 @@ | ||||
| #!/usr/bin/env python3 | ||||
| import os.path as path | ||||
| import string | ||||
| import random | ||||
| from os import mkdir | ||||
|  | ||||
| from deemix.utils import localpaths | ||||
| from deemix.api.deezer import Deezer | ||||
| from deemix.app.queuemanager import addToQueue | ||||
| from deemix.app.spotify import SpotifyHelper | ||||
| from deemix.app import deemix | ||||
|  | ||||
| dz = Deezer() | ||||
| sp = SpotifyHelper() | ||||
| def randomString(stringLength=8): | ||||
|     letters = string.ascii_lowercase | ||||
|     return ''.join(random.choice(letters) for i in range(stringLength)) | ||||
|  | ||||
| class cli(deemix): | ||||
|     def __init__(self, local, configFolder=None): | ||||
|         super().__init__(configFolder) | ||||
|         if local: | ||||
|             self.set.settings['downloadLocation'] = randomString(12) | ||||
|             print("Using a local download folder: "+settings['downloadLocation']) | ||||
|  | ||||
| def requestValidArl(): | ||||
|     while True: | ||||
|         arl = input("Paste here your arl:") | ||||
|         if dz.login_via_arl(arl): | ||||
|             break | ||||
|     return arl | ||||
|     def downloadLink(self, url, bitrate=None): | ||||
|         for link in url: | ||||
|             if ';' in link: | ||||
|                 for l in link.split(";"): | ||||
|                     self.qm.addToQueue(self.dz, self.sp, l, self.set.settings, bitrate) | ||||
|             else: | ||||
|                 self.qm.addToQueue(self.dz, self.sp, link, self.set.settings, bitrate) | ||||
|  | ||||
|     def requestValidArl(self): | ||||
|         while True: | ||||
|             arl = input("Paste here your arl:") | ||||
|             if self.dz.login_via_arl(arl): | ||||
|                 break | ||||
|         return arl | ||||
|  | ||||
| def login(): | ||||
|     configFolder = localpaths.getConfigFolder() | ||||
|     if not path.isdir(configFolder): | ||||
|         mkdir(configFolder) | ||||
|     if path.isfile(path.join(configFolder, '.arl')): | ||||
|         with open(path.join(configFolder, '.arl'), 'r') as f: | ||||
|             arl = f.readline().rstrip("\n") | ||||
|         if not dz.login_via_arl(arl): | ||||
|             arl = requestValidArl() | ||||
|     else: | ||||
|         arl = requestValidArl() | ||||
|     with open(path.join(configFolder, '.arl'), 'w') as f: | ||||
|         f.write(arl) | ||||
|  | ||||
|  | ||||
| def downloadLink(url, settings, bitrate=None): | ||||
|     for link in url: | ||||
|         if ';' in link: | ||||
|             for l in link.split(";"): | ||||
|                 addToQueue(dz, sp, l, settings, bitrate) | ||||
|     def login(self): | ||||
|         configFolder = self.set.configFolder | ||||
|         if not path.isdir(configFolder): | ||||
|             mkdir(configFolder) | ||||
|         if path.isfile(path.join(configFolder, '.arl')): | ||||
|             with open(path.join(configFolder, '.arl'), 'r') as f: | ||||
|                 arl = f.readline().rstrip("\n") | ||||
|             if not self.dz.login_via_arl(arl): | ||||
|                 arl = self.requestValidArl() | ||||
|         else: | ||||
|             addToQueue(dz, sp, link, settings, bitrate) | ||||
|             arl = self.requestValidArl() | ||||
|         with open(path.join(configFolder, '.arl'), 'w') as f: | ||||
|             f.write(arl) | ||||
|  | ||||
| @ -1,75 +0,0 @@ | ||||
| { | ||||
|   "downloadLocation": "", | ||||
|   "tracknameTemplate": "%artist% - %title%", | ||||
|   "albumTracknameTemplate": "%tracknumber% - %title%", | ||||
|   "playlistTracknameTemplate": "%position% - %artist% - %title%", | ||||
|   "createPlaylistFolder": true, | ||||
|   "playlistNameTemplate": "%playlist%", | ||||
|   "createArtistFolder": false, | ||||
|   "artistNameTemplate": "%artist%", | ||||
|   "createAlbumFolder": true, | ||||
|   "albumNameTemplate": "%artist% - %album%", | ||||
|   "createCDFolder": true, | ||||
|   "createStructurePlaylist": false, | ||||
|   "createSingleFolder": false, | ||||
|   "padTracks": true, | ||||
|   "paddingSize": "0", | ||||
|   "illegalCharacterReplacer": "_", | ||||
|   "queueConcurrency": 3, | ||||
|   "maxBitrate": "3", | ||||
|   "fallbackBitrate": true, | ||||
|   "fallbackSearch": false, | ||||
|   "logErrors": true, | ||||
|   "logSearched": false, | ||||
|   "saveDownloadQueue": false, | ||||
|   "overwriteFile": "n", | ||||
|   "createM3U8File": false, | ||||
|   "playlistFilenameTemplate": "playlist", | ||||
|   "syncedLyrics": false, | ||||
|   "embeddedArtworkSize": 800, | ||||
|   "localArtworkSize": 1400, | ||||
|   "localArtworkFormat": "jpg", | ||||
|   "saveArtwork": true, | ||||
|   "coverImageTemplate": "cover", | ||||
|   "saveArtworkArtist": false, | ||||
|   "artistImageTemplate": "folder", | ||||
|   "jpegImageQuality": 80, | ||||
|   "dateFormat": "Y-M-D", | ||||
|   "albumVariousArtists": true, | ||||
|   "removeAlbumVersion": false, | ||||
|   "removeDuplicateArtists": false, | ||||
|   "featuredToTitle": "0", | ||||
|   "titleCasing": "nothing", | ||||
|   "artistCasing": "nothing", | ||||
|   "executeCommand": "", | ||||
|   "tags": { | ||||
|     "title": true, | ||||
|     "artist": true, | ||||
|     "album": true, | ||||
|     "cover": true, | ||||
|     "trackNumber": true, | ||||
|     "trackTotal": false, | ||||
|     "discNumber": true, | ||||
|     "discTotal": false, | ||||
|     "albumArtist": true, | ||||
|     "genre": true, | ||||
|     "year": true, | ||||
|     "date": true, | ||||
|     "explicit": false, | ||||
|     "isrc": true, | ||||
|     "length": true, | ||||
|     "barcode": true, | ||||
|     "bpm": true, | ||||
|     "replayGain": false, | ||||
|     "label": true, | ||||
|     "lyrics": false, | ||||
|     "copyright": false, | ||||
|     "composer": false, | ||||
|     "involvedPeople": false, | ||||
|     "savePlaylistAsCompilation": false, | ||||
|     "useNullSeparator": false, | ||||
|     "saveID3v1": true, | ||||
|     "multiArtistSeparator": "default", | ||||
|     "singleAlbumArtist": false | ||||
|   } | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										637
									
								
								deemix/app/downloadjob.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										637
									
								
								deemix/app/downloadjob.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,637 @@ | ||||
| #!/usr/bin/env python3 | ||||
| import os.path | ||||
| import re | ||||
|  | ||||
| from requests import get | ||||
| from requests.exceptions import HTTPError, ConnectionError | ||||
|  | ||||
| from concurrent.futures import ThreadPoolExecutor | ||||
| from os import makedirs, remove, system as execute | ||||
| from tempfile import gettempdir | ||||
| from time import sleep | ||||
|  | ||||
| from deemix.app.queueitem import QIConvertable, QISingle, QICollection | ||||
| from deemix.app.track import Track | ||||
| from deemix.utils.misc import changeCase | ||||
| from deemix.utils.pathtemplates import generateFilename, generateFilepath, settingsRegexAlbum, settingsRegexArtist, settingsRegexPlaylistFile | ||||
| from deemix.api.deezer import USER_AGENT_HEADER | ||||
| from deemix.utils.taggers import tagID3, tagFLAC | ||||
|  | ||||
| from Cryptodome.Cipher import Blowfish | ||||
| from mutagen.flac import FLACNoHeaderError | ||||
| import logging | ||||
|  | ||||
| logging.basicConfig(level=logging.INFO) | ||||
| logger = logging.getLogger('deemix') | ||||
|  | ||||
| TEMPDIR = os.path.join(gettempdir(), 'deemix-imgs') | ||||
| if not os.path.isdir(TEMPDIR): | ||||
|     makedirs(TEMPDIR) | ||||
|  | ||||
| extensions = { | ||||
|     9: '.flac', | ||||
|     0: '.mp3', | ||||
|     3: '.mp3', | ||||
|     1: '.mp3', | ||||
|     8: '.mp3', | ||||
|     15: '.mp4', | ||||
|     14: '.mp4', | ||||
|     13: '.mp4' | ||||
| } | ||||
|  | ||||
| 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.", | ||||
|     'notAvailable': "Track not available on deezer's servers!", | ||||
|     'notAvailableNoAlternative': "Track not available on deezer's servers and no alternative found!" | ||||
| } | ||||
| def downloadImage(url, path, overwrite="n"): | ||||
|     if not os.path.isfile(path) or overwrite in ['y', 't', 'b']: | ||||
|         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 | ||||
|         except HTTPError: | ||||
|             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") | ||||
|                     sleep(1) | ||||
|                     return  downloadImage(urlBase+pictureUrl.replace(str(pictureSize)+"x"+str(pictureSize), '1200x1200'), path, overwrite) | ||||
|             logger.error("Couldn't download Image: "+url) | ||||
|         except: | ||||
|             sleep(1) | ||||
|             return downloadImage(url, path, overwrite) | ||||
|         remove(path) | ||||
|         return None | ||||
|     else: | ||||
|         return path | ||||
|  | ||||
| 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: | ||||
|     def __init__(self, dz, sp, queueItem, interface=None): | ||||
|         self.dz = dz | ||||
|         self.sp = sp | ||||
|         self.interface = interface | ||||
|         if isinstance(queueItem, QIConvertable) and queueItem.extra: | ||||
|             self.sp.convert_spotify_playlist(self.dz, queueItem, interface=self.interface) | ||||
|         self.queueItem = queueItem | ||||
|         self.settings = queueItem.settings | ||||
|         self.bitrate = queueItem.bitrate | ||||
|         self.downloadPercentage = 0 | ||||
|         self.lastPercentage = 0 | ||||
|         self.extrasPath = self.settings['downloadLocation'] | ||||
|         self.playlistPath = None | ||||
|         self.playlistURLs = [] | ||||
|  | ||||
|     def start(self): | ||||
|         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) | ||||
|             with ThreadPoolExecutor(self.settings['queueConcurrency']) as executor: | ||||
|                 for pos, track in enumerate(self.queueItem.collection, start=0): | ||||
|                     tracks[pos] = executor.submit(self.downloadWrapper, track) | ||||
|             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) | ||||
|         return self.extrasPath | ||||
|  | ||||
|     def singleAfterDownload(self, result): | ||||
|         # Save Album Cover | ||||
|         if self.settings['saveArtwork'] and 'albumPath' in result: | ||||
|             for image in result['albumURLs']: | ||||
|                 downloadImage(image['url'], f"{result['albumPath']}.{image['ext']}", self.settings['overwriteFile']) | ||||
|         # Save Artist Artwork | ||||
|         if self.settings['saveArtworkArtist'] and 'artistPath' in result: | ||||
|             for image in result['artistURLs']: | ||||
|                 downloadImage(image['url'], f"{result['artistPath']}.{image['ext']}", self.settings['overwriteFile']) | ||||
|         # Create searched logfile | ||||
|         if self.settings['logSearched'] and 'searched' in result: | ||||
|             with open(os.path.join(self.extrasPath, 'searched.txt'), 'wb+') as f: | ||||
|                 orig = f.read().decode('utf-8') | ||||
|                 if not result['searched'] in orig: | ||||
|                     if orig != "": | ||||
|                         orig += "\r\n" | ||||
|                     orig += result['searched'] + "\r\n" | ||||
|                 f.write(orig.encode('utf-8')) | ||||
|         # Execute command after download | ||||
|         if self.settings['executeCommand'] != "": | ||||
|             execute(self.settings['executeCommand'].replace("%folder%", self.extrasPath).replace("%filename%", result['filename'])) | ||||
|  | ||||
|     def collectionAfterDownload(self, tracks): | ||||
|         playlist = [None] * len(tracks) | ||||
|         errors = "" | ||||
|         searched = "" | ||||
|  | ||||
|         for index in range(len(tracks)): | ||||
|             result = tracks[index].result() | ||||
|             # Check if queue is cancelled | ||||
|             if not result: | ||||
|                 return None | ||||
|             # Log errors to file | ||||
|             if 'error' in result: | ||||
|                 if not 'data' in result['error']: | ||||
|                     result['error']['data'] = {'id': 0, 'title': 'Unknown', 'artist': 'Unknown'} | ||||
|                 errors += f"{result['error']['data']['id']} | {result['error']['data']['artist']} - {result['error']['data']['title']} | {result['error']['message']}\r\n" | ||||
|             # Log searched to file | ||||
|             if 'searched' in result: | ||||
|                 searched += result['searched'] + "\r\n" | ||||
|             # Save Album Cover | ||||
|             if self.settings['saveArtwork'] and 'albumPath' in result: | ||||
|                 for image in result['albumURLs']: | ||||
|                     downloadImage(image['url'], f"{result['albumPath']}.{image['ext']}", self.settings['overwriteFile']) | ||||
|             # Save Artist Artwork | ||||
|             if self.settings['saveArtworkArtist'] and 'artistPath' in result: | ||||
|                 for image in result['artistURLs']: | ||||
|                     downloadImage(image['url'], f"{result['artistPath']}.{image['ext']}", self.settings['overwriteFile']) | ||||
|             # Save filename for playlist file | ||||
|             playlist[index] = "" | ||||
|             if 'filename' in result: | ||||
|                 playlist[index] = result['filename'] | ||||
|  | ||||
|         # Create errors logfile | ||||
|         if self.settings['logErrors'] and errors != "": | ||||
|             with open(os.path.join(self.extrasPath, 'errors.txt'), 'wb') as f: | ||||
|                 f.write(errors.encode('utf-8')) | ||||
|         # Create searched logfile | ||||
|         if self.settings['logSearched'] and searched != "": | ||||
|             with open(os.path.join(self.extrasPath, 'searched.txt'), 'wb') as f: | ||||
|                 f.write(searched.encode('utf-8')) | ||||
|         # Save Playlist Artwork | ||||
|         if self.settings['saveArtwork'] and self.playlistPath and not self.settings['tags']['savePlaylistAsCompilation']: | ||||
|             for image in self.playlistURLs: | ||||
|                 downloadImage(image['url'], os.path.join(self.extrasPath, self.playlistPath)+f".{image['ext']}", self.settings['overwriteFile']) | ||||
|         # Create M3U8 File | ||||
|         if self.settings['createM3U8File']: | ||||
|             filename = settingsRegexPlaylistFile(self.settings['playlistFilenameTemplate'], queueItem, self.settings) or "playlist" | ||||
|             with open(os.path.join(self.extrasPath, filename+'.m3u8'), 'wb') as f: | ||||
|                 for line in playlist: | ||||
|                     f.write((line + "\n").encode('utf-8')) | ||||
|         # Execute command after download | ||||
|         if self.settings['executeCommand'] != "": | ||||
|             execute(self.settings['executeCommand'].replace("%folder%", self.extrasPath)) | ||||
|  | ||||
|     def download(self, trackAPI_gw, track=None): | ||||
|         result = {} | ||||
|         if self.queueItem.cancel: raise DownloadCancelled | ||||
|  | ||||
|         if trackAPI_gw['SNG_ID'] == "0": | ||||
|             raise DownloadFailed("notOnDeezer") | ||||
|  | ||||
|         # Create Track object | ||||
|         if not track: | ||||
|             logger.info(f"[{trackAPI_gw['ART_NAME']} - {trackAPI_gw['SNG_TITLE']}] Getting the tags") | ||||
|             track = Track(self.dz, | ||||
|                           settings=self.settings, | ||||
|                           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 | ||||
|                           ) | ||||
|             if self.queueItem.cancel: raise DownloadCancelled | ||||
|  | ||||
|         if track.MD5 == '': | ||||
|             if track.fallbackId != "0": | ||||
|                 logger.warn(f"[{track.mainArtist['name']} - {track.title}] Track not yet encoded, using fallback id") | ||||
|                 newTrack = self.dz.get_track_gw(track.fallbackId) | ||||
|                 track.parseEssentialData(self.dz, newTrack) | ||||
|                 return self.download(trackAPI_gw, track) | ||||
|             elif not track.searched and self.settings['fallbackSearch']: | ||||
|                 logger.warn(f"[{track.mainArtist['name']} - {track.title}] Track not yet encoded, searching for alternative") | ||||
|                 searchedId = self.dz.get_track_from_metadata(track.mainArtist['name'], track.title, track.album['title']) | ||||
|                 if searchedId != 0: | ||||
|                     newTrack = self.dz.get_track_gw(searchedId) | ||||
|                     track.parseEssentialData(self.dz, newTrack) | ||||
|                     track.searched = True | ||||
|                     return self.download(trackAPI_gw, track) | ||||
|                 else: | ||||
|                     raise DownloadFailed("notEncodedNoAlternative") | ||||
|             else: | ||||
|                 raise DownloadFailed("notEncoded") | ||||
|  | ||||
|         selectedFormat = self.getPreferredBitrate(track) | ||||
|         if selectedFormat == -100: | ||||
|             if track.fallbackId != "0": | ||||
|                 logger.warn(f"[{track.mainArtist['name']} - {track.title}] Track not found at desired bitrate, using fallback id") | ||||
|                 newTrack = self.dz.get_track_gw(track.fallbackId) | ||||
|                 track.parseEssentialData(self.dz, newTrack) | ||||
|                 return self.download(trackAPI_gw, track) | ||||
|             elif not track.searched and self.settings['fallbackSearch']: | ||||
|                 logger.warn(f"[{track.mainArtist['name']} - {track.title}] Track not found at desired bitrate, searching for alternative") | ||||
|                 searchedId = self.dz.get_track_from_metadata(track.mainArtist['name'], track.title, track.album['title']) | ||||
|                 if searchedId != 0: | ||||
|                     newTrack = self.dz.get_track_gw(searchedId) | ||||
|                     track.parseEssentialData(self.dz, newTrack) | ||||
|                     track.searched = True | ||||
|                     return self.download(trackAPI_gw, track) | ||||
|                 else: | ||||
|                     raise DownloadFailed("wrongBitrateNoAlternative") | ||||
|             else: | ||||
|                 raise DownloadFailed("wrongBitrate") | ||||
|         elif selectedFormat == -200: | ||||
|             raise DownloadFailed("no360RA") | ||||
|         track.selectedFormat = selectedFormat | ||||
|  | ||||
|         if self.settings['tags']['savePlaylistAsCompilation'] and track.playlist: | ||||
|             track.trackNumber = track.position | ||||
|             track.discNumber = "1" | ||||
|             track.album = {**track.album, **track.playlist} | ||||
|             track.album['picPath'] = os.path.join(TEMPDIR, f"pl{trackAPI_gw['_EXTRA_PLAYLIST']['id']}_{self.settings['embeddedArtworkSize']}.jpg") | ||||
|         else: | ||||
|             if track.album['date']: | ||||
|                 track.date = track.album['date'] | ||||
|             track.album['picUrl'] = "https://e-cdns-images.dzcdn.net/images/cover/{}/{}x{}-{}".format( | ||||
|                 track.album['pic'], | ||||
|                 self.settings['embeddedArtworkSize'], self.settings['embeddedArtworkSize'], | ||||
|                 f'000000-{self.settings["jpegImageQuality"]}-0-0.jpg' | ||||
|                 ) | ||||
|             track.album['picPath'] = os.path.join(TEMPDIR, f"alb{track.album['id']}_{self.settings['embeddedArtworkSize']}.jpg") | ||||
|         track.album['bitrate'] = selectedFormat | ||||
|  | ||||
|         track.dateString = formatDate(track.date, self.settings['dateFormat']) | ||||
|         track.album['dateString'] = formatDate(track.album['date'], self.settings['dateFormat']) | ||||
|  | ||||
|         # Check if user wants the feat in the title | ||||
|         # 0 => do not change | ||||
|         # 1 => remove from title | ||||
|         # 2 => add to title | ||||
|         # 3 => remove from title and album title | ||||
|         if self.settings['featuredToTitle'] == "1": | ||||
|             track.title = track.getCleanTitle() | ||||
|         elif self.settings['featuredToTitle'] == "2": | ||||
|             track.title = track.getFeatTitle() | ||||
|         elif self.settings['featuredToTitle'] == "3": | ||||
|             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() | ||||
|  | ||||
|         # Generate artist tag if needed | ||||
|         if self.settings['tags']['multiArtistSeparator'] != "default": | ||||
|             if self.settings['tags']['multiArtistSeparator'] == "andFeat": | ||||
|                 track.artistsString = track.mainArtistsString | ||||
|                 if track.featArtistsString and self.settings['featuredToTitle'] != "2": | ||||
|                     track.artistsString += " " + track.featArtistsString | ||||
|             else: | ||||
|                 track.artistsString = self.settings['tags']['multiArtistSeparator'].join(track.artists) | ||||
|         else: | ||||
|             track.artistsString = ", ".join(track.artists) | ||||
|  | ||||
|         # 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.artistsString = changeCase(track.artistsString, self.settings['artistCasing']) | ||||
|             for i, artist in enumerate(track.artists): | ||||
|                 track.artists[i] = changeCase(artist, self.settings['artistCasing']) | ||||
|  | ||||
|         # Generate filename and filepath from metadata | ||||
|         filename = generateFilename(track, trackAPI_gw, self.settings) | ||||
|         (filepath, artistPath, coverPath, extrasPath) = generateFilepath(track, trackAPI_gw, self.settings) | ||||
|  | ||||
|         if self.queueItem.cancel: raise DownloadCancelled | ||||
|  | ||||
|         # Download and cache coverart | ||||
|         logger.info(f"[{track.mainArtist['name']} - {track.title}] Getting the album cover") | ||||
|         track.album['picPath'] = downloadImage(track.album['picUrl'], track.album['picPath']) | ||||
|  | ||||
|         # Save local album art | ||||
|         if coverPath: | ||||
|             result['albumURLs'] = [] | ||||
|             for format in self.settings['localArtworkFormat'].split(","): | ||||
|                 if format in ["png","jpg"]: | ||||
|                     url = track.album['picUrl'].replace( | ||||
|                         f"{self.settings['embeddedArtworkSize']}x{self.settings['embeddedArtworkSize']}", | ||||
|                         f"{self.settings['localArtworkSize']}x{self.settings['localArtworkSize']}") | ||||
|                     if format == "png": | ||||
|                         url = url[:url.find("000000-")]+"none-100-0-0.png" | ||||
|                     result['albumURLs'].append({'url': url, 'ext': format}) | ||||
|             result['albumPath'] = os.path.join(coverPath, | ||||
|                                                f"{settingsRegexAlbum(self.settings['coverImageTemplate'], track.album, self.settings, trackAPI_gw['_EXTRA_PLAYLIST'] if'_EXTRA_PLAYLIST' in trackAPI_gw else None)}") | ||||
|  | ||||
|         # Save artist art | ||||
|         if artistPath: | ||||
|             result['artistURLs'] = [] | ||||
|             for format in self.settings['localArtworkFormat'].split(","): | ||||
|                 if format in ["png","jpg"]: | ||||
|                     url = "" | ||||
|                     if track.album['mainArtist']['pic'] != "": | ||||
|                         url = "https://e-cdns-images.dzcdn.net/images/artist/{}/{}x{}-{}".format( | ||||
|                             track.album['mainArtist']['pic'], self.settings['localArtworkSize'], self.settings['localArtworkSize'], | ||||
|                             'none-100-0-0.png' if format == "png" else f'000000-{self.settings["jpegImageQuality"]}-0-0.jpg') | ||||
|                     elif format == "jpg": | ||||
|                         url = "https://e-cdns-images.dzcdn.net/images/artist//{}x{}-{}".format( | ||||
|                             self.settings['localArtworkSize'], self.settings['localArtworkSize'], f'000000-{self.settings["jpegImageQuality"]}-0-0.jpg') | ||||
|                     if url: | ||||
|                         result['artistURLs'].append({'url': url, 'ext': format}) | ||||
|             result['artistPath'] = os.path.join(artistPath, | ||||
|                 f"{settingsRegexArtist(self.settings['artistImageTemplate'], track.album['mainArtist'], self.settings)}") | ||||
|  | ||||
|         # Remove subfolders from filename and add it to filepath | ||||
|         if os.path.sep in filename: | ||||
|             tempPath = filename[:filename.rfind(os.path.sep)] | ||||
|             filepath = os.path.join(filepath, tempPath) | ||||
|             filename = filename[filename.rfind(os.path.sep) + len(os.path.sep):] | ||||
|  | ||||
|         # Make sure the filepath exsists | ||||
|         makedirs(filepath, exist_ok=True) | ||||
|         writepath = os.path.join(filepath, filename + extensions[track.selectedFormat]) | ||||
|  | ||||
|         # Save lyrics in lrc file | ||||
|         if self.settings['syncedLyrics'] and 'sync' in track.lyrics: | ||||
|             if not os.path.isfile(os.path.join(filepath, filename + '.lrc')) or settings['overwriteFile'] in ['y', 't']: | ||||
|                 with open(os.path.join(filepath, filename + '.lrc'), 'wb') as f: | ||||
|                     f.write(track.lyrics['sync'].encode('utf-8')) | ||||
|  | ||||
|         trackAlreadyDownloaded = os.path.isfile(writepath) | ||||
|         if trackAlreadyDownloaded and self.settings['overwriteFile'] == 'b': | ||||
|             baseFilename = os.path.join(filepath, filename) | ||||
|             i = 1 | ||||
|             currentFilename = baseFilename+' ('+str(i)+')'+ extensions[track.selectedFormat] | ||||
|             while os.path.isfile(currentFilename): | ||||
|                 i += 1 | ||||
|                 currentFilename = baseFilename+' ('+str(i)+')'+ extensions[track.selectedFormat] | ||||
|             trackAlreadyDownloaded = False | ||||
|             writepath = currentFilename | ||||
|  | ||||
|  | ||||
|         if extrasPath: | ||||
|             if not self.extrasPath: | ||||
|                 self.extrasPath = extrasPath | ||||
|             result['extrasPath'] = extrasPath | ||||
|  | ||||
|             # Data for m3u file | ||||
|             result['filename'] = writepath[len(extrasPath):] | ||||
|  | ||||
|             # Save playlist cover | ||||
|             if track.playlist: | ||||
|                 if not len(self.playlistURLs): | ||||
|                     if 'dzcdn.net' in track.playlist['picUrl']: | ||||
|                         for format in self.settings['localArtworkFormat'].split(","): | ||||
|                             if format in ["png","jpg"]: | ||||
|                                 url = track.playlist['picUrl'].replace( | ||||
|                                     f"{self.settings['embeddedArtworkSize']}x{self.settings['embeddedArtworkSize']}", | ||||
|                                     f"{self.settings['localArtworkSize']}x{self.settings['localArtworkSize']}") | ||||
|                                 if format == "png": | ||||
|                                     url = url[:url.find("000000-")]+"none-100-0-0.png" | ||||
|                                 self.playlistURLs.append({'url': url, 'ext': format}) | ||||
|                     else: | ||||
|                         self.playlistURLs.append({'url': track.playlist['picUrl'], 'ext': 'jpg'}) | ||||
|                 if not self.playlistPath: | ||||
|                     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.playlistPath = f"{settingsRegexAlbum(self.settings['coverImageTemplate'], track.playlist, self.settings, trackAPI_gw['_EXTRA_PLAYLIST'])}" | ||||
|  | ||||
|             if not trackAlreadyDownloaded or self.settings['overwriteFile'] == 'y': | ||||
|                 logger.info(f"[{track.mainArtist['name']} - {track.title}] Downloading the track") | ||||
|                 track.downloadUrl = self.dz.get_track_stream_url(track.id, track.MD5, track.mediaVersion, track.selectedFormat) | ||||
|  | ||||
|                 def downloadMusic(track, trackAPI_gw): | ||||
|                     try: | ||||
|                         with open(writepath, 'wb') as stream: | ||||
|                             self.streamTrack(stream, track) | ||||
|                     except DownloadCancelled: | ||||
|                         remove(writepath) | ||||
|                         raise DownloadCancelled | ||||
|                     except (HTTPError, DownloadEmpty): | ||||
|                         remove(writepath) | ||||
|                         if track.fallbackId != "0": | ||||
|                             logger.warn(f"[{track.mainArtist['name']} - {track.title}] Track not available, using fallback id") | ||||
|                             newTrack = self.dz.get_track_gw(track.fallbackId) | ||||
|                             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") | ||||
|                             searchedId = self.dz.get_track_from_metadata(track.mainArtist['name'], track.title, track.album['title']) | ||||
|                             if searchedId != 0: | ||||
|                                 newTrack = self.dz.get_track_gw(searchedId) | ||||
|                                 track.parseEssentialData(self.dz, newTrack) | ||||
|                                 track.searched = True | ||||
|                                 return False | ||||
|                             else: | ||||
|                                 raise DownloadFailed("notAvailableNoAlternative") | ||||
|                         else: | ||||
|                             raise DownloadFailed("notAvailable") | ||||
|                     except ConnectionError as e: | ||||
|                         logger.exception(str(e)) | ||||
|                         logger.warn(f"[{track.mainArtist['name']} - {track.title}] Error while downloading the track, trying again in 5s...") | ||||
|                         sleep(5) | ||||
|                         return downloadMusic(track, trackAPI_gw) | ||||
|                     except Exception as e: | ||||
|                         logger.exception(str(e)) | ||||
|                         logger.warn(f"[{track.mainArtist['name']} - {track.title}] Error while downloading the track, you should report this to the developers") | ||||
|                         raise e | ||||
|                     return True | ||||
|  | ||||
|                 try: | ||||
|                     trackDownloaded = downloadMusic(track, trackAPI_gw) | ||||
|                 except DownloadFailed as e: | ||||
|                     raise e | ||||
|                 except Exception as e: | ||||
|                     raise e | ||||
|  | ||||
|                 if not trackDownloaded: | ||||
|                     return self.download(trackAPI_gw, track) | ||||
|             else: | ||||
|                 logger.info(f"[{track.mainArtist['name']} - {track.title}] Skipping track as it's already downloaded") | ||||
|                 self.completeTrackPercentage() | ||||
|  | ||||
|             # Adding tags | ||||
|             if (not trackAlreadyDownloaded or self.settings['overwriteFile'] in ['t', 'y']) and not track.localTrack: | ||||
|                 logger.info(f"[{track.mainArtist['name']} - {track.title}] Applying tags to the track") | ||||
|                 if track.selectedFormat in [3, 1, 8]: | ||||
|                     tagID3(writepath, track, self.settings['tags']) | ||||
|                 elif track.selectedFormat == 9: | ||||
|                     try: | ||||
|                         tagFLAC(writepath, track, self.settings['tags']) | ||||
|                     except FLACNoHeaderError: | ||||
|                         remove(writepath) | ||||
|                         logger.warn(f"[{track.mainArtist['name']} - {track.title}] Track not available in FLAC, falling back if necessary") | ||||
|                         self.removeTrackPercentage() | ||||
|                         track.filesizes['FILESIZE_FLAC'] = "0" | ||||
|                         return self.download(trackAPI_gw, track) | ||||
|                 if track.searched: | ||||
|                     result['searched'] = f"{track.mainArtist['name']} - {track.title}" | ||||
|  | ||||
|             logger.info(f"[{track.mainArtist['name']} - {track.title}] Track download completed") | ||||
|             self.queueItem.downloaded += 1 | ||||
|             if self.interface: | ||||
|                 self.interface.send("updateQueue", {'uuid': self.queueItem.uuid, 'downloaded': True, 'downloadPath': writepath}) | ||||
|             return result | ||||
|  | ||||
|     def getPreferredBitrate(self, track): | ||||
|         if track.localTrack: | ||||
|             return 0 | ||||
|  | ||||
|         fallback = self.settings['fallbackBitrate'] | ||||
|  | ||||
|         formats_non_360 = { | ||||
|             9: "FLAC", | ||||
|             3: "MP3_320", | ||||
|             1: "MP3_128", | ||||
|         } | ||||
|         formats_360 = { | ||||
|             15: "MP4_RA3", | ||||
|             14: "MP4_RA2", | ||||
|             13: "MP4_RA1", | ||||
|         } | ||||
|  | ||||
|         if not fallback: | ||||
|             error_num = -100 | ||||
|             formats = formats_360 | ||||
|             formats.update(formats_non_360) | ||||
|         elif int(self.bitrate) in formats_360: | ||||
|             error_num = -200 | ||||
|             formats = formats_360 | ||||
|         else: | ||||
|             error_num = 8 | ||||
|             formats = formats_non_360 | ||||
|  | ||||
|         for format_num, format in formats.items(): | ||||
|             if format_num <= int(self.bitrate): | ||||
|                 if f"FILESIZE_{format}" in track.filesizes and int(track.filesizes[f"FILESIZE_{format}"]) != 0: | ||||
|                     return format_num | ||||
|                 else: | ||||
|                     if fallback: | ||||
|                         continue | ||||
|                     else: | ||||
|                         return error_num | ||||
|  | ||||
|         return error_num # fallback is enabled and loop went through all formats | ||||
|  | ||||
|     def streamTrack(self, stream, track): | ||||
|         if self.queueItem.cancel: raise DownloadCancelled | ||||
|  | ||||
|         try: | ||||
|             request = get(track.downloadUrl, headers=self.dz.http_headers, stream=True, timeout=30) | ||||
|         except ConnectionError: | ||||
|             sleep(2) | ||||
|             return self.streamTrack(stream, track) | ||||
|         request.raise_for_status() | ||||
|         blowfish_key = str.encode(self.dz._get_blowfish_key(str(track.id))) | ||||
|         complete = int(request.headers["Content-Length"]) | ||||
|         if complete == 0: | ||||
|             raise DownloadEmpty | ||||
|         chunkLength = 0 | ||||
|         percentage = 0 | ||||
|         i = 0 | ||||
|         for chunk in request.iter_content(2048): | ||||
|             if self.queueItem.cancel: raise DownloadCancelled | ||||
|             if i % 3 == 0 and len(chunk) == 2048: | ||||
|                 chunk = Blowfish.new(blowfish_key, Blowfish.MODE_CBC, b"\x00\x01\x02\x03\x04\x05\x06\x07").decrypt(chunk) | ||||
|             stream.write(chunk) | ||||
|             chunkLength += len(chunk) | ||||
|             if isinstance(self.queueItem, QISingle): | ||||
|                 percentage = (chunkLength / complete) * 100 | ||||
|                 self.downloadPercentage = percentage | ||||
|             else: | ||||
|                 chunkProgres = (len(chunk) / complete) / self.queueItem.size * 100 | ||||
|                 self.downloadPercentage += chunkProgres | ||||
|             self.updatePercentage() | ||||
|             i += 1 | ||||
|  | ||||
|     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 | ||||
|             if self.interface: | ||||
|                 self.interface.send("updateQueue", {'uuid': self.queueItem.uuid, 'progress': self.lastPercentage}) | ||||
|  | ||||
|     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 = { | ||||
|             'id': trackAPI_gw['SNG_ID'], | ||||
|             'title': trackAPI_gw['SNG_TITLE'] + (trackAPI_gw['VERSION'] if 'VERSION' in trackAPI_gw and trackAPI_gw['VERSION'] and not trackAPI_gw['VERSION'] in trackAPI_gw['SNG_TITLE'] else ""), | ||||
|             'artist': trackAPI_gw['ART_NAME'] | ||||
|         } | ||||
|  | ||||
|         try: | ||||
|             result = self.download(trackAPI_gw) | ||||
|         except DownloadCancelled: | ||||
|             return None | ||||
|         except DownloadFailed as error: | ||||
|             logger.error(f"[{track['artist']} - {track['title']}] {error.message}") | ||||
|             result = {'error': { | ||||
|                         'message': error.message, | ||||
|                         'errid': error.errid, | ||||
|                         'data': track | ||||
|                     }} | ||||
|         except Exception as e: | ||||
|             logger.exception(f"[{track['artist']} - {track['title']}] {str(e)}") | ||||
|             result = {'error': { | ||||
|                         'message': str(e), | ||||
|                         'data': track | ||||
|                     }} | ||||
|  | ||||
|         if 'error' in result: | ||||
|             self.completeTrackPercentage() | ||||
|             self.queueItem.failed += 1 | ||||
|             self.queueItem.errors.append(result['error']) | ||||
|             if self.interface: | ||||
|                 error = result['error'] | ||||
|                 self.interface.send("updateQueue", { | ||||
|                     '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 | ||||
|  | ||||
| class DownloadEmpty(DownloadError): | ||||
|     pass | ||||
							
								
								
									
										108
									
								
								deemix/app/queueitem.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								deemix/app/queueitem.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,108 @@ | ||||
| #!/usr/bin/env python3 | ||||
|  | ||||
| class QueueItem: | ||||
|     def __init__(self, id=None, bitrate=None, title=None, artist=None, cover=None, size=None, type=None, settings=None, queueItemDict=None): | ||||
|         if queueItemDict: | ||||
|             self.title = queueItemDict['title'] | ||||
|             self.artist = queueItemDict['artist'] | ||||
|             self.cover = queueItemDict['cover'] | ||||
|             self.size = queueItemDict['size'] | ||||
|             self.type = queueItemDict['type'] | ||||
|             self.id = queueItemDict['id'] | ||||
|             self.bitrate = queueItemDict['bitrate'] | ||||
|             self.downloaded = queueItemDict['downloaded'] | ||||
|             self.failed = queueItemDict['failed'] | ||||
|             self.errors = queueItemDict['errors'] | ||||
|             self.progress = queueItemDict['progress'] | ||||
|             self.settings = None | ||||
|             if 'settings' in queueItemDict: | ||||
|                 self.settings = queueItemDict['settings'] | ||||
|         else: | ||||
|             self.title = title | ||||
|             self.artist = artist | ||||
|             self.cover = cover | ||||
|             self.size = size | ||||
|             self.type = type | ||||
|             self.id = id | ||||
|             self.bitrate = bitrate | ||||
|             self.settings = settings | ||||
|             self.downloaded = 0 | ||||
|             self.failed = 0 | ||||
|             self.errors = [] | ||||
|             self.progress = 0 | ||||
|         self.uuid = f"{self.type}_{self.id}_{self.bitrate}" | ||||
|         self.cancel = False | ||||
|  | ||||
|     def toDict(self): | ||||
|         return { | ||||
|             'title': self.title, | ||||
|             'artist': self.artist, | ||||
|             'cover': self.cover, | ||||
|             'size': self.size, | ||||
|             'downloaded': self.downloaded, | ||||
|             'failed': self.failed, | ||||
|             'errors': self.errors, | ||||
|             'progress': self.progress, | ||||
|             'type': self.type, | ||||
|             'id': self.id, | ||||
|             'bitrate': self.bitrate, | ||||
|             'uuid': self.uuid | ||||
|         } | ||||
|  | ||||
|     def getResettedItem(self): | ||||
|         item = self.toDict() | ||||
|         item['downloaded'] = 0 | ||||
|         item['failed'] = 0 | ||||
|         item['progress'] = 0 | ||||
|         item['errors'] = [] | ||||
|         return item | ||||
|  | ||||
|     def getSlimmedItem(self): | ||||
|         light = self.toDict() | ||||
|         propertiesToDelete = ['single', 'collection', '_EXTRA', 'settings'] | ||||
|         for property in propertiesToDelete: | ||||
|             if property in light: | ||||
|                 del light[property] | ||||
|         return light | ||||
|  | ||||
| class QISingle(QueueItem): | ||||
|     def __init__(self, id=None, bitrate=None, title=None, artist=None, cover=None, type=None, settings=None, single=None, queueItemDict=None): | ||||
|         if queueItemDict: | ||||
|             super().__init__(queueItemDict=queueItemDict) | ||||
|             self.single = queueItemDict['single'] | ||||
|         else: | ||||
|             super().__init__(id, bitrate, title, artist, cover, 1, type, settings) | ||||
|             self.single = single | ||||
|  | ||||
|     def toDict(self): | ||||
|         queueItem = super().toDict() | ||||
|         queueItem['single'] = self.single | ||||
|         return queueItem | ||||
|  | ||||
| class QICollection(QueueItem): | ||||
|     def __init__(self, id=None, bitrate=None, title=None, artist=None, cover=None, size=None, type=None, settings=None, collection=None, queueItemDict=None): | ||||
|         if queueItemDict: | ||||
|             super().__init__(queueItemDict=queueItemDict) | ||||
|             self.collection = queueItemDict['collection'] | ||||
|         else: | ||||
|             super().__init__(id, bitrate, title, artist, cover, size, type, settings) | ||||
|             self.collection = collection | ||||
|  | ||||
|     def toDict(self): | ||||
|         queueItem = super().toDict() | ||||
|         queueItem['collection'] = self.collection | ||||
|         return queueItem | ||||
|  | ||||
| class QIConvertable(QICollection): | ||||
|     def __init__(self, id=None, bitrate=None, title=None, artist=None, cover=None, size=None, type=None, settings=None, extra=None, queueItemDict=None): | ||||
|         if queueItemDict: | ||||
|             super().__init__(queueItemDict=queueItemDict) | ||||
|             self.extra = queueItemDict['_EXTRA'] | ||||
|         else: | ||||
|             super().__init__(id, bitrate, title, artist, cover, size, type, settings, []) | ||||
|             self.extra = extra | ||||
|  | ||||
|     def toDict(self): | ||||
|         queueItem = super().toDict() | ||||
|         queueItem['_EXTRA'] = self.extra | ||||
|         return queueItem | ||||
| @ -1,533 +1,511 @@ | ||||
| #!/usr/bin/env python3 | ||||
| from deemix.app.downloader import download | ||||
| from deemix.app.downloadjob import DownloadJob | ||||
| from deemix.utils.misc import getIDFromLink, getTypeFromLink, getBitrateInt | ||||
| from deemix.api.deezer import APIError | ||||
| from spotipy.exceptions import SpotifyException | ||||
| from deemix.app.queueitem import QISingle, QICollection, QIConvertable | ||||
| import logging | ||||
| import os.path as path | ||||
| import json | ||||
| from os import remove | ||||
| from time import sleep | ||||
|  | ||||
| logging.basicConfig(level=logging.INFO) | ||||
| logger = logging.getLogger('deemix') | ||||
|  | ||||
| queue = [] | ||||
| queueList = {} | ||||
| queueComplete = [] | ||||
| currentItem = "" | ||||
| class QueueManager: | ||||
|     def __init__(self): | ||||
|         self.queue = [] | ||||
|         self.queueList = {} | ||||
|         self.queueComplete = [] | ||||
|         self.currentItem = "" | ||||
|  | ||||
| """ | ||||
| queueItem base structure | ||||
| 	title | ||||
| 	artist | ||||
| 	cover | ||||
| 	size | ||||
| 	downloaded | ||||
| 	failed | ||||
|     errors | ||||
| 	progress | ||||
| 	type | ||||
| 	id | ||||
| 	bitrate | ||||
| 	uuid: type+id+bitrate | ||||
| if its a single track | ||||
| 	single | ||||
| if its an album/playlist | ||||
| 	collection | ||||
| """ | ||||
|     def generateQueueItem(self, dz, sp, url, settings, bitrate=None, albumAPI=None, interface=None): | ||||
|         forcedBitrate = getBitrateInt(bitrate) | ||||
|         bitrate = forcedBitrate if forcedBitrate else settings['maxBitrate'] | ||||
|         type = getTypeFromLink(url) | ||||
|         id = getIDFromLink(url, type) | ||||
|  | ||||
| def resetQueueItems(items, q): | ||||
|     result = {} | ||||
|     for item in items.keys(): | ||||
|         result[item] = items[item].copy() | ||||
|         if item in q: | ||||
|             result[item]['downloaded'] = 0 | ||||
|             result[item]['failed'] = 0 | ||||
|             result[item]['progress'] = 0 | ||||
|             result[item]['errors'] = [] | ||||
|     return result | ||||
|  | ||||
| def slimQueueItems(items): | ||||
|     result = {} | ||||
|     for item in items.keys(): | ||||
|         result[item] = slimQueueItem(items[item]) | ||||
|     return result | ||||
|         if type == None or id == None: | ||||
|             logger.warn("URL not recognized") | ||||
|             return QueueError(url, "URL not recognized", "invalidURL") | ||||
|  | ||||
| def slimQueueItem(item): | ||||
|     light = item.copy() | ||||
|     if 'single' in light: | ||||
|         del light['single'] | ||||
|     if 'collection' in light: | ||||
|         del light['collection'] | ||||
|     return light | ||||
|  | ||||
| def generateQueueItem(dz, sp, url, settings, bitrate=None, albumAPI=None, interface=None): | ||||
|     forcedBitrate = getBitrateInt(bitrate) | ||||
|     bitrate = forcedBitrate if forcedBitrate else settings['maxBitrate'] | ||||
|     type = getTypeFromLink(url) | ||||
|     id = getIDFromLink(url, type) | ||||
|     result = {} | ||||
|     result['link'] = url | ||||
|     if type == None or id == None: | ||||
|         logger.warn("URL not recognized") | ||||
|         result['error'] = "URL not recognized" | ||||
|         result['errid'] = "invalidURL" | ||||
|     elif type == "track": | ||||
|         if id.startswith("isrc"): | ||||
|         elif type == "track": | ||||
|             if id.startswith("isrc"): | ||||
|                 try: | ||||
|                     trackAPI = dz.get_track(id) | ||||
|                     if 'id' in trackAPI and 'title' in trackAPI: | ||||
|                         id = trackAPI['id'] | ||||
|                     else: | ||||
|                         return QueueError(url, "Track ISRC is not available on deezer", "ISRCnotOnDeezer") | ||||
|                 except APIError as e: | ||||
|                     e = json.loads(str(e)) | ||||
|                     return QueueError(url, f"Wrong URL: {e['type']+': ' if 'type' in e else ''}{e['message'] if 'message' in e else ''}") | ||||
|             try: | ||||
|                 trackAPI = dz.get_track(id) | ||||
|                 if 'id' in trackAPI and 'title' in trackAPI: | ||||
|                     id = trackAPI['id'] | ||||
|                 else: | ||||
|                     result['error'] = "Track ISRC is not available on deezer" | ||||
|                     result['errid'] = "ISRCnotOnDeezer" | ||||
|                     return result | ||||
|                 trackAPI = dz.get_track_gw(id) | ||||
|             except APIError as e: | ||||
|                 e = json.loads(str(e)) | ||||
|                 result['error'] = f"Wrong URL: {e['type']+': ' if 'type' in e else ''}{e['message'] if 'message' in e else ''}" | ||||
|                 return result | ||||
|         try: | ||||
|             trackAPI = dz.get_track_gw(id) | ||||
|         except APIError as e: | ||||
|             e = json.loads(str(e)) | ||||
|             result['error'] = "Wrong URL" | ||||
|             if "DATA_ERROR" in e: | ||||
|                 result['error'] += f": {e['DATA_ERROR']}" | ||||
|             return result | ||||
|         if albumAPI: | ||||
|             trackAPI['_EXTRA_ALBUM'] = albumAPI | ||||
|         if settings['createSingleFolder']: | ||||
|             trackAPI['FILENAME_TEMPLATE'] = settings['albumTracknameTemplate'] | ||||
|         else: | ||||
|             trackAPI['FILENAME_TEMPLATE'] = settings['tracknameTemplate'] | ||||
|         trackAPI['SINGLE_TRACK'] = True | ||||
|  | ||||
|         result['title'] = trackAPI['SNG_TITLE'] | ||||
|         if 'VERSION' in trackAPI and trackAPI['VERSION']: | ||||
|             result['title'] += " " + trackAPI['VERSION'] | ||||
|         result['artist'] = trackAPI['ART_NAME'] | ||||
|         result[ | ||||
|             'cover'] = f"https://e-cdns-images.dzcdn.net/images/cover/{trackAPI['ALB_PICTURE']}/75x75-000000-80-0-0.jpg" | ||||
|         result['size'] = 1 | ||||
|         result['downloaded'] = 0 | ||||
|         result['failed'] = 0 | ||||
|         result['errors'] = [] | ||||
|         result['progress'] = 0 | ||||
|         result['type'] = 'track' | ||||
|         result['id'] = id | ||||
|         result['bitrate'] = bitrate | ||||
|         result['uuid'] = f"{result['type']}_{id}_{bitrate}" | ||||
|         result['settings'] = settings or {} | ||||
|         result['single'] = trackAPI | ||||
|  | ||||
|     elif type == "album": | ||||
|         try: | ||||
|             albumAPI = dz.get_album(id) | ||||
|         except APIError as e: | ||||
|             e = json.loads(str(e)) | ||||
|             result['error'] = f"Wrong URL: {e['type']+': ' if 'type' in e else ''}{e['message'] if 'message' in e else ''}" | ||||
|             return result | ||||
|         if id.startswith('upc'): | ||||
|             id = albumAPI['id'] | ||||
|         albumAPI_gw = dz.get_album_gw(id) | ||||
|         albumAPI['nb_disk'] = albumAPI_gw['NUMBER_DISK'] | ||||
|         albumAPI['copyright'] = albumAPI_gw['COPYRIGHT'] | ||||
|         if albumAPI['nb_tracks'] == 1: | ||||
|             return generateQueueItem(dz, sp, f"https://www.deezer.com/track/{albumAPI['tracks']['data'][0]['id']}", | ||||
|                                      settings, bitrate, albumAPI) | ||||
|         tracksArray = dz.get_album_tracks_gw(id) | ||||
|         if albumAPI['nb_tracks'] == 255: | ||||
|             albumAPI['nb_tracks'] = len(tracksArray) | ||||
|  | ||||
|         result['title'] = albumAPI['title'] | ||||
|         result['artist'] = albumAPI['artist']['name'] | ||||
|         if albumAPI['cover_small'] != None: | ||||
|             result['cover'] = albumAPI['cover_small'][:-24] + '/75x75-000000-80-0-0.jpg' | ||||
|         else: | ||||
|             result['cover'] = f"https://e-cdns-images.dzcdn.net/images/cover/{albumAPI_gw['ALB_PICTURE']}/75x75-000000-80-0-0.jpg" | ||||
|         result['size'] = albumAPI['nb_tracks'] | ||||
|         result['downloaded'] = 0 | ||||
|         result['failed'] = 0 | ||||
|         result['errors'] = [] | ||||
|         result['progress'] = 0 | ||||
|         result['type'] = 'album' | ||||
|         result['id'] = id | ||||
|         result['bitrate'] = bitrate | ||||
|         result['uuid'] = f"{result['type']}_{id}_{bitrate}" | ||||
|         result['settings'] = settings or {} | ||||
|         totalSize = len(tracksArray) | ||||
|         result['collection'] = [] | ||||
|         for pos, trackAPI in enumerate(tracksArray, start=1): | ||||
|             trackAPI['_EXTRA_ALBUM'] = albumAPI | ||||
|             trackAPI['POSITION'] = pos | ||||
|             trackAPI['SIZE'] = totalSize | ||||
|             trackAPI['FILENAME_TEMPLATE'] = settings['albumTracknameTemplate'] | ||||
|             result['collection'].append(trackAPI) | ||||
|  | ||||
|     elif type == "playlist": | ||||
|         try: | ||||
|             playlistAPI = dz.get_playlist(id) | ||||
|         except: | ||||
|             try: | ||||
|                 playlistAPI = dz.get_playlist_gw(id)['results']['DATA'] | ||||
|             except APIError as e: | ||||
|                 e = json.loads(str(e)) | ||||
|                 result['error'] = "Wrong URL" | ||||
|                 message = "Wrong URL" | ||||
|                 if "DATA_ERROR" in e: | ||||
|                     result['error'] += f": {e['DATA_ERROR']}" | ||||
|                 return result | ||||
|             newPlaylist = { | ||||
|                 'id': playlistAPI['PLAYLIST_ID'], | ||||
|                 'title': playlistAPI['TITLE'], | ||||
|                 'description': playlistAPI['DESCRIPTION'], | ||||
|                 'duration': playlistAPI['DURATION'], | ||||
|                 'public': False, | ||||
|                     message += f": {e['DATA_ERROR']}" | ||||
|                 return QueueError(url, message) | ||||
|             if albumAPI: | ||||
|                 trackAPI['_EXTRA_ALBUM'] = albumAPI | ||||
|             if settings['createSingleFolder']: | ||||
|                 trackAPI['FILENAME_TEMPLATE'] = settings['albumTracknameTemplate'] | ||||
|             else: | ||||
|                 trackAPI['FILENAME_TEMPLATE'] = settings['tracknameTemplate'] | ||||
|             trackAPI['SINGLE_TRACK'] = True | ||||
|  | ||||
|             title = trackAPI['SNG_TITLE'] | ||||
|             if 'VERSION' in trackAPI and trackAPI['VERSION']: | ||||
|                 title += " " + trackAPI['VERSION'] | ||||
|             return QISingle( | ||||
|                 id, | ||||
|                 bitrate, | ||||
|                 title, | ||||
|                 trackAPI['ART_NAME'], | ||||
|                 f"https://e-cdns-images.dzcdn.net/images/cover/{trackAPI['ALB_PICTURE']}/75x75-000000-80-0-0.jpg", | ||||
|                 'track', | ||||
|                 settings, | ||||
|                 trackAPI, | ||||
|             ) | ||||
|  | ||||
|         elif type == "album": | ||||
|             try: | ||||
|                 albumAPI = dz.get_album(id) | ||||
|             except APIError as e: | ||||
|                 e = json.loads(str(e)) | ||||
|                 return QueueError(url, f"Wrong URL: {e['type']+': ' if 'type' in e else ''}{e['message'] if 'message' in e else ''}") | ||||
|             if id.startswith('upc'): | ||||
|                 id = albumAPI['id'] | ||||
|             albumAPI_gw = dz.get_album_gw(id) | ||||
|             albumAPI['nb_disk'] = albumAPI_gw['NUMBER_DISK'] | ||||
|             albumAPI['copyright'] = albumAPI_gw['COPYRIGHT'] | ||||
|             if albumAPI['nb_tracks'] == 1: | ||||
|                 return generateQueueItem(dz, sp, f"https://www.deezer.com/track/{albumAPI['tracks']['data'][0]['id']}", | ||||
|                                          settings, bitrate, albumAPI) | ||||
|             tracksArray = dz.get_album_tracks_gw(id) | ||||
|             if albumAPI['nb_tracks'] == 255: | ||||
|                 albumAPI['nb_tracks'] = len(tracksArray) | ||||
|  | ||||
|  | ||||
|             if albumAPI['cover_small'] != None: | ||||
|                 cover = albumAPI['cover_small'][:-24] + '/75x75-000000-80-0-0.jpg' | ||||
|             else: | ||||
|                 cover = f"https://e-cdns-images.dzcdn.net/images/cover/{albumAPI_gw['ALB_PICTURE']}/75x75-000000-80-0-0.jpg" | ||||
|             totalSize = len(tracksArray) | ||||
|             collection = [] | ||||
|             for pos, trackAPI in enumerate(tracksArray, start=1): | ||||
|                 trackAPI['_EXTRA_ALBUM'] = albumAPI | ||||
|                 trackAPI['POSITION'] = pos | ||||
|                 trackAPI['SIZE'] = totalSize | ||||
|                 trackAPI['FILENAME_TEMPLATE'] = settings['albumTracknameTemplate'] | ||||
|                 collection.append(trackAPI) | ||||
|  | ||||
|             return QICollection( | ||||
|                 id, | ||||
|                 bitrate, | ||||
|                 albumAPI['title'], | ||||
|                 albumAPI['artist']['name'], | ||||
|                 cover, | ||||
|                 totalSize, | ||||
|                 'album', | ||||
|                 settings, | ||||
|                 collection, | ||||
|             ) | ||||
|  | ||||
|  | ||||
|         elif type == "playlist": | ||||
|             try: | ||||
|                 playlistAPI = dz.get_playlist(id) | ||||
|             except: | ||||
|                 try: | ||||
|                     playlistAPI = dz.get_playlist_gw(id) | ||||
|                 except APIError as e: | ||||
|                     e = json.loads(str(e)) | ||||
|                     message = "Wrong URL" | ||||
|                     if "DATA_ERROR" in e: | ||||
|                         message += f": {e['DATA_ERROR']}" | ||||
|                     return QueueError(url, message) | ||||
|             if not playlistAPI['public'] and playlistAPI['creator']['id'] != str(dz.user['id']): | ||||
|                 logger.warn("You can't download others private playlists.") | ||||
|                 return QueueError(url, "You can't download others private playlists.", "notYourPrivatePlaylist") | ||||
|  | ||||
|             playlistTracksAPI = dz.get_playlist_tracks_gw(id) | ||||
|             playlistAPI['various_artist'] = dz.get_artist(5080) | ||||
|  | ||||
|             totalSize = len(playlistTracksAPI) | ||||
|             collection = [] | ||||
|             for pos, trackAPI in enumerate(playlistTracksAPI, start=1): | ||||
|                 if 'EXPLICIT_TRACK_CONTENT' in trackAPI and 'EXPLICIT_LYRICS_STATUS' in trackAPI['EXPLICIT_TRACK_CONTENT'] and trackAPI['EXPLICIT_TRACK_CONTENT']['EXPLICIT_LYRICS_STATUS'] in [1,4]: | ||||
|                     playlistAPI['explicit'] = True | ||||
|                 trackAPI['_EXTRA_PLAYLIST'] = playlistAPI | ||||
|                 trackAPI['POSITION'] = pos | ||||
|                 trackAPI['SIZE'] = totalSize | ||||
|                 trackAPI['FILENAME_TEMPLATE'] = settings['playlistTracknameTemplate'] | ||||
|                 collection.append(trackAPI) | ||||
|             if not 'explicit' in playlistAPI: | ||||
|                 playlistAPI['explicit'] = False | ||||
|  | ||||
|             return QICollection( | ||||
|                 id, | ||||
|                 bitrate, | ||||
|                 playlistAPI['title'], | ||||
|                 playlistAPI['creator']['name'], | ||||
|                 playlistAPI['picture_small'][:-24] + '/75x75-000000-80-0-0.jpg', | ||||
|                 totalSize, | ||||
|                 'playlist', | ||||
|                 settings, | ||||
|                 collection, | ||||
|             ) | ||||
|  | ||||
|         elif type == "artist": | ||||
|             try: | ||||
|                 artistAPI = dz.get_artist(id) | ||||
|             except APIError as e: | ||||
|                 e = json.loads(str(e)) | ||||
|                 return QueueError(url, f"Wrong URL: {e['type']+': ' if 'type' in e else ''}{e['message'] if 'message' in e else ''}") | ||||
|  | ||||
|             if interface: | ||||
|                 interface.send("startAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']}) | ||||
|  | ||||
|             artistAPITracks = dz.get_artist_albums(id) | ||||
|             albumList = [] | ||||
|             for album in artistAPITracks['data']: | ||||
|                 albumList.append(generateQueueItem(dz, sp, album['link'], settings, bitrate)) | ||||
|  | ||||
|             if interface: | ||||
|                 interface.send("finishAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']}) | ||||
|  | ||||
|             return albumList | ||||
|  | ||||
|         elif type == "artistdiscography": | ||||
|             try: | ||||
|                 artistAPI = dz.get_artist(id) | ||||
|             except APIError as e: | ||||
|                 e = json.loads(str(e)) | ||||
|                 return QueueError(url, f"Wrong URL: {e['type']+': ' if 'type' in e else ''}{e['message'] if 'message' in e else ''}") | ||||
|  | ||||
|             if interface: | ||||
|                 interface.send("startAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']}) | ||||
|  | ||||
|             artistDiscographyAPI = dz.get_artist_discography_gw(id, 100) | ||||
|             albumList = [] | ||||
|             for type in artistDiscographyAPI: | ||||
|                 if type != 'all': | ||||
|                     for album in artistDiscographyAPI[type]: | ||||
|                         albumList.append(generateQueueItem(dz, sp, album['link'], settings, bitrate)) | ||||
|  | ||||
|             if interface: | ||||
|                 interface.send("finishAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']}) | ||||
|  | ||||
|             return albumList | ||||
|  | ||||
|         elif type == "artisttop": | ||||
|             try: | ||||
|                 artistAPI = dz.get_artist(id) | ||||
|             except APIError as e: | ||||
|                 e = json.loads(str(e)) | ||||
|                 return QueueError(url, f"Wrong URL: {e['type']+': ' if 'type' in e else ''}{e['message'] if 'message' in e else ''}") | ||||
|  | ||||
|             playlistAPI = { | ||||
|                 'id': str(artistAPI['id'])+"_top_track", | ||||
|                 'title': artistAPI['name']+" - Top Tracks", | ||||
|                 'description': "Top Tracks for "+artistAPI['name'], | ||||
|                 'duration': 0, | ||||
|                 'public': True, | ||||
|                 'is_loved_track': False, | ||||
|                 'collaborative': False, | ||||
|                 'nb_tracks': playlistAPI['NB_SONG'], | ||||
|                 'fans': playlistAPI['NB_FAN'], | ||||
|                 'link': "https://www.deezer.com/playlist/"+playlistAPI['PLAYLIST_ID'], | ||||
|                 'nb_tracks': 0, | ||||
|                 'fans': artistAPI['nb_fan'], | ||||
|                 'link': "https://www.deezer.com/artist/"+str(artistAPI['id'])+"/top_track", | ||||
|                 'share': None, | ||||
|                 'picture': "https://api.deezer.com/playlist/"+playlistAPI['PLAYLIST_ID']+"/image", | ||||
|                 'picture_small': "https://cdns-images.dzcdn.net/images/"+playlistAPI['PICTURE_TYPE']+"/"+playlistAPI['PLAYLIST_PICTURE']+"/56x56-000000-80-0-0.jpg", | ||||
|                 'picture_medium': "https://cdns-images.dzcdn.net/images/"+playlistAPI['PICTURE_TYPE']+"/"+playlistAPI['PLAYLIST_PICTURE']+"/250x250-000000-80-0-0.jpg", | ||||
|                 'picture_big': "https://cdns-images.dzcdn.net/images/"+playlistAPI['PICTURE_TYPE']+"/"+playlistAPI['PLAYLIST_PICTURE']+"/500x500-000000-80-0-0.jpg", | ||||
|                 'picture_xl': "https://cdns-images.dzcdn.net/images/"+playlistAPI['PICTURE_TYPE']+"/"+playlistAPI['PLAYLIST_PICTURE']+"/1000x1000-000000-80-0-0.jpg", | ||||
|                 'checksum': playlistAPI['CHECKSUM'], | ||||
|                 'tracklist': "https://api.deezer.com/playlist/"+playlistAPI['PLAYLIST_ID']+"/tracks", | ||||
|                 'creation_date': playlistAPI['DATE_ADD'], | ||||
|                 'picture': artistAPI['picture'], | ||||
|                 'picture_small': artistAPI['picture_small'], | ||||
|                 'picture_medium': artistAPI['picture_medium'], | ||||
|                 'picture_big': artistAPI['picture_big'], | ||||
|                 'picture_xl': artistAPI['picture_xl'], | ||||
|                 'checksum': None, | ||||
|                 'tracklist': "https://api.deezer.com/artist/"+str(artistAPI['id'])+"/top", | ||||
|                 'creation_date': "XXXX-00-00", | ||||
|                 'creator': { | ||||
|                     'id': playlistAPI['PARENT_USER_ID'], | ||||
|                     'name': playlistAPI['PARENT_USERNAME'], | ||||
|                     'tracklist': "https://api.deezer.com/user/"+playlistAPI['PARENT_USER_ID']+"/flow", | ||||
|                     'id': "art_"+str(artistAPI['id']), | ||||
|                     'name': artistAPI['name'], | ||||
|                     'type': "user" | ||||
|                 }, | ||||
|                 'type': "playlist" | ||||
|             } | ||||
|             playlistAPI = newPlaylist | ||||
|         if not playlistAPI['public'] and playlistAPI['creator']['id'] != str(dz.user['id']): | ||||
|             logger.warn("You can't download others private playlists.") | ||||
|             result['error'] = "You can't download others private playlists." | ||||
|             result['errid'] = "notYourPrivatePlaylist" | ||||
|             return result | ||||
|  | ||||
|         playlistTracksAPI = dz.get_playlist_tracks_gw(id) | ||||
|         playlistAPI['various_artist'] = dz.get_artist(5080) | ||||
|             artistTopTracksAPI_gw = dz.get_artist_toptracks_gw(id) | ||||
|             playlistAPI['various_artist'] = dz.get_artist(5080) | ||||
|             playlistAPI['nb_tracks'] = len(artistTopTracksAPI_gw) | ||||
|  | ||||
|         result['title'] = playlistAPI['title'] | ||||
|         result['artist'] = playlistAPI['creator']['name'] | ||||
|         result['cover'] = playlistAPI['picture_small'][:-24] + '/75x75-000000-80-0-0.jpg' | ||||
|         result['size'] = playlistAPI['nb_tracks'] | ||||
|         result['downloaded'] = 0 | ||||
|         result['failed'] = 0 | ||||
|         result['errors'] = [] | ||||
|         result['progress'] = 0 | ||||
|         result['type'] = 'playlist' | ||||
|         result['id'] = id | ||||
|         result['bitrate'] = bitrate | ||||
|         result['uuid'] = f"{result['type']}_{id}_{bitrate}" | ||||
|         result['settings'] = settings or {} | ||||
|         totalSize = len(playlistTracksAPI) | ||||
|         result['collection'] = [] | ||||
|         for pos, trackAPI in enumerate(playlistTracksAPI, start=1): | ||||
|             if 'EXPLICIT_TRACK_CONTENT' in trackAPI and 'EXPLICIT_LYRICS_STATUS' in trackAPI['EXPLICIT_TRACK_CONTENT'] and trackAPI['EXPLICIT_TRACK_CONTENT']['EXPLICIT_LYRICS_STATUS'] in [1,4]: | ||||
|                 playlistAPI['explicit'] = True | ||||
|             trackAPI['_EXTRA_PLAYLIST'] = playlistAPI | ||||
|             trackAPI['POSITION'] = pos | ||||
|             trackAPI['SIZE'] = totalSize | ||||
|             trackAPI['FILENAME_TEMPLATE'] = settings['playlistTracknameTemplate'] | ||||
|             result['collection'].append(trackAPI) | ||||
|         if not 'explicit' in playlistAPI: | ||||
|             playlistAPI['explicit'] = False | ||||
|             totalSize = len(artistTopTracksAPI_gw) | ||||
|             collection = [] | ||||
|             for pos, trackAPI in enumerate(artistTopTracksAPI_gw, start=1): | ||||
|                 if 'EXPLICIT_TRACK_CONTENT' in trackAPI and 'EXPLICIT_LYRICS_STATUS' in trackAPI['EXPLICIT_TRACK_CONTENT'] and trackAPI['EXPLICIT_TRACK_CONTENT']['EXPLICIT_LYRICS_STATUS'] in [1,4]: | ||||
|                     playlistAPI['explicit'] = True | ||||
|                 trackAPI['_EXTRA_PLAYLIST'] = playlistAPI | ||||
|                 trackAPI['POSITION'] = pos | ||||
|                 trackAPI['SIZE'] = totalSize | ||||
|                 trackAPI['FILENAME_TEMPLATE'] = settings['playlistTracknameTemplate'] | ||||
|                 collection.append(trackAPI) | ||||
|             if not 'explicit' in playlistAPI: | ||||
|                 playlistAPI['explicit'] = False | ||||
|  | ||||
|     elif type == "artist": | ||||
|         try: | ||||
|             artistAPI = dz.get_artist(id) | ||||
|         except APIError as e: | ||||
|             e = json.loads(str(e)) | ||||
|             result['error'] = f"Wrong URL: {e['type']+': ' if 'type' in e else ''}{e['message'] if 'message' in e else ''}" | ||||
|             return result | ||||
|         if interface: | ||||
|             interface.send("startAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']}) | ||||
|         artistAPITracks = dz.get_artist_albums(id) | ||||
|         albumList = [] | ||||
|         for album in artistAPITracks['data']: | ||||
|             albumList.append(generateQueueItem(dz, sp, album['link'], settings, bitrate)) | ||||
|         if interface: | ||||
|             interface.send("finishAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']}) | ||||
|         return albumList | ||||
|     elif type == "artistdiscography": | ||||
|         try: | ||||
|             artistAPI = dz.get_artist(id) | ||||
|         except APIError as e: | ||||
|             e = json.loads(str(e)) | ||||
|             result['error'] = f"Wrong URL: {e['type']+': ' if 'type' in e else ''}{e['message'] if 'message' in e else ''}" | ||||
|             return result | ||||
|         if interface: | ||||
|             interface.send("startAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']}) | ||||
|         artistDiscographyAPI = dz.get_artist_discography_gw(id, 100) | ||||
|         albumList = [] | ||||
|         for type in artistDiscographyAPI: | ||||
|             if type != 'all': | ||||
|                 for album in artistDiscographyAPI[type]: | ||||
|                     albumList.append(generateQueueItem(dz, sp, album['link'], settings, bitrate)) | ||||
|         if interface: | ||||
|             interface.send("finishAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']}) | ||||
|         return albumList | ||||
|     elif type == "artisttop": | ||||
|         try: | ||||
|             artistAPI = dz.get_artist(id) | ||||
|         except APIError as e: | ||||
|             e = json.loads(str(e)) | ||||
|             result['error'] = f"Wrong URL: {e['type']+': ' if 'type' in e else ''}{e['message'] if 'message' in e else ''}" | ||||
|             return result | ||||
|             return QICollection( | ||||
|                 id, | ||||
|                 bitrate, | ||||
|                 playlistAPI['title'], | ||||
|                 playlistAPI['creator']['name'], | ||||
|                 playlistAPI['picture_small'][:-24] + '/75x75-000000-80-0-0.jpg', | ||||
|                 totalSize, | ||||
|                 'playlist', | ||||
|                 settings, | ||||
|                 collection, | ||||
|             ) | ||||
|  | ||||
|         playlistAPI = { | ||||
|             'id': str(artistAPI['id'])+"_top_track", | ||||
|             'title': artistAPI['name']+" - Top Tracks", | ||||
|             'description': "Top Tracks for "+artistAPI['name'], | ||||
|             'duration': 0, | ||||
|             'public': True, | ||||
|             'is_loved_track': False, | ||||
|             'collaborative': False, | ||||
|             'nb_tracks': 0, | ||||
|             'fans': artistAPI['nb_fan'], | ||||
|             'link': "https://www.deezer.com/artist/"+str(artistAPI['id'])+"/top_track", | ||||
|             'share': None, | ||||
|             'picture': artistAPI['picture'], | ||||
|             'picture_small': artistAPI['picture_small'], | ||||
|             'picture_medium': artistAPI['picture_medium'], | ||||
|             'picture_big': artistAPI['picture_big'], | ||||
|             'picture_xl': artistAPI['picture_xl'], | ||||
|             'checksum': None, | ||||
|             'tracklist': "https://api.deezer.com/artist/"+str(artistAPI['id'])+"/top", | ||||
|             'creation_date': "XXXX-00-00", | ||||
|             'creator': { | ||||
|                 'id': "art_"+str(artistAPI['id']), | ||||
|                 'name': artistAPI['name'], | ||||
|                 'type': "user" | ||||
|             }, | ||||
|             'type': "playlist" | ||||
|         } | ||||
|         elif type == "spotifytrack": | ||||
|             if not sp.spotifyEnabled: | ||||
|                 logger.warn("Spotify Features is not setted up correctly.") | ||||
|                 return QueueError(url, "Spotify Features is not setted up correctly.", "spotifyDisabled") | ||||
|  | ||||
|         artistTopTracksAPI_gw = dz.get_artist_toptracks_gw(id) | ||||
|         playlistAPI['various_artist'] = dz.get_artist(5080) | ||||
|         playlistAPI['nb_tracks'] = len(artistTopTracksAPI_gw) | ||||
|             try: | ||||
|                 track_id = sp.get_trackid_spotify(dz, id, settings['fallbackSearch']) | ||||
|             except SpotifyException as e: | ||||
|                 return QueueError(url, "Wrong URL: "+e.msg[e.msg.find('\n')+2:]) | ||||
|  | ||||
|             if track_id != 0: | ||||
|                 return generateQueueItem(dz, sp, f'https://www.deezer.com/track/{track_id}', settings, bitrate) | ||||
|             else: | ||||
|                 logger.warn("Track not found on deezer!") | ||||
|                 return QueueError(url, "Track not found on deezer!", "trackNotOnDeezer") | ||||
|  | ||||
|         elif type == "spotifyalbum": | ||||
|             if not sp.spotifyEnabled: | ||||
|                 logger.warn("Spotify Features is not setted up correctly.") | ||||
|                 return QueueError(url, "Spotify Features is not setted up correctly.", "spotifyDisabled") | ||||
|  | ||||
|             try: | ||||
|                 album_id = sp.get_albumid_spotify(dz, id) | ||||
|             except SpotifyException as e: | ||||
|                 return QueueError(url, "Wrong URL: "+e.msg[e.msg.find('\n')+2:]) | ||||
|  | ||||
|             if album_id != 0: | ||||
|                 return generateQueueItem(dz, sp, f'https://www.deezer.com/album/{album_id}', settings, bitrate) | ||||
|             else: | ||||
|                 logger.warn("Album not found on deezer!") | ||||
|                 return QueueError(url, "Album not found on deezer!", "albumNotOnDeezer") | ||||
|  | ||||
|         elif type == "spotifyplaylist": | ||||
|             if not sp.spotifyEnabled: | ||||
|                 logger.warn("Spotify Features is not setted up correctly.") | ||||
|                 return QueueError(url, "Spotify Features is not setted up correctly.", "spotifyDisabled") | ||||
|  | ||||
|             try: | ||||
|                 return sp.generate_playlist_queueitem(dz, id, bitrate, settings) | ||||
|             except SpotifyException as e: | ||||
|                 return QueueError(url, "Wrong URL: "+e.msg[e.msg.find('\n')+2:]) | ||||
|  | ||||
|         result['title'] = playlistAPI['title'] | ||||
|         result['artist'] = playlistAPI['creator']['name'] | ||||
|         result['cover'] = playlistAPI['picture_small'][:-24] + '/75x75-000000-80-0-0.jpg' | ||||
|         result['size'] = playlistAPI['nb_tracks'] | ||||
|         result['downloaded'] = 0 | ||||
|         result['failed'] = 0 | ||||
|         result['errors'] = [] | ||||
|         result['progress'] = 0 | ||||
|         result['type'] = 'playlist' | ||||
|         result['id'] = id | ||||
|         result['bitrate'] = bitrate | ||||
|         result['uuid'] = f"{result['type']}_{id}_{bitrate}" | ||||
|         result['settings'] = settings or {} | ||||
|         totalSize = len(artistTopTracksAPI_gw) | ||||
|         result['collection'] = [] | ||||
|         for pos, trackAPI in enumerate(artistTopTracksAPI_gw, start=1): | ||||
|             if 'EXPLICIT_TRACK_CONTENT' in trackAPI and 'EXPLICIT_LYRICS_STATUS' in trackAPI['EXPLICIT_TRACK_CONTENT'] and trackAPI['EXPLICIT_TRACK_CONTENT']['EXPLICIT_LYRICS_STATUS'] in [1,4]: | ||||
|                 playlistAPI['explicit'] = True | ||||
|             trackAPI['_EXTRA_PLAYLIST'] = playlistAPI | ||||
|             trackAPI['POSITION'] = pos | ||||
|             trackAPI['SIZE'] = totalSize | ||||
|             trackAPI['FILENAME_TEMPLATE'] = settings['playlistTracknameTemplate'] | ||||
|             result['collection'].append(trackAPI) | ||||
|         if not 'explicit' in playlistAPI: | ||||
|             playlistAPI['explicit'] = False | ||||
|     elif type == "spotifytrack": | ||||
|         if not sp.spotifyEnabled: | ||||
|             logger.warn("Spotify Features is not setted up correctly.") | ||||
|             result['error'] = "Spotify Features is not setted up correctly." | ||||
|             result['errid'] = "spotifyDisabled" | ||||
|             return result | ||||
|         try: | ||||
|             track_id = sp.get_trackid_spotify(dz, id, settings['fallbackSearch']) | ||||
|         except SpotifyException as e: | ||||
|             result['error'] = "Wrong URL: "+e.msg[e.msg.find('\n')+2:] | ||||
|             return result | ||||
|         if track_id != 0: | ||||
|             return generateQueueItem(dz, sp, f'https://www.deezer.com/track/{track_id}', settings, bitrate) | ||||
|         else: | ||||
|             logger.warn("Track not found on deezer!") | ||||
|             result['error'] = "Track not found on deezer!" | ||||
|             result['errid'] = "trackNotOnDeezer" | ||||
|     elif type == "spotifyalbum": | ||||
|         if not sp.spotifyEnabled: | ||||
|             logger.warn("Spotify Features is not setted up correctly.") | ||||
|             result['error'] = "Spotify Features is not setted up correctly." | ||||
|             result['errid'] = "spotifyDisabled" | ||||
|             return result | ||||
|         try: | ||||
|             album_id = sp.get_albumid_spotify(dz, id) | ||||
|         except SpotifyException as e: | ||||
|             result['error'] = "Wrong URL: "+e.msg[e.msg.find('\n')+2:] | ||||
|             return result | ||||
|         if album_id != 0: | ||||
|             return generateQueueItem(dz, sp, f'https://www.deezer.com/album/{album_id}', settings, bitrate) | ||||
|         else: | ||||
|             logger.warn("Album not found on deezer!") | ||||
|             result['error'] = "Album not found on deezer!" | ||||
|             result['errid'] = "albumNotOnDeezer" | ||||
|     elif type == "spotifyplaylist": | ||||
|         if not sp.spotifyEnabled: | ||||
|             logger.warn("Spotify Features is not setted up correctly.") | ||||
|             result['error'] = "Spotify Features is not setted up correctly." | ||||
|             result['errid'] = "spotifyDisabled" | ||||
|             return result | ||||
|         if interface: | ||||
|             interface.send("startConvertingSpotifyPlaylist", str(id)) | ||||
|         try: | ||||
|             playlist = sp.convert_spotify_playlist(dz, id, settings) | ||||
|         except SpotifyException as e: | ||||
|             result['error'] = "Wrong URL: "+e.msg[e.msg.find('\n')+2:] | ||||
|             return result | ||||
|         playlist['bitrate'] = bitrate | ||||
|         playlist['uuid'] = f"{playlist['type']}_{id}_{bitrate}" | ||||
|         result = playlist | ||||
|         if interface: | ||||
|             interface.send("finishConvertingSpotifyPlaylist", str(id)) | ||||
|     else: | ||||
|         logger.warn("URL not supported yet") | ||||
|         result['error'] = "URL not supported yet" | ||||
|         result['errid'] = "unsupportedURL" | ||||
|     return result | ||||
|             logger.warn("URL not supported yet") | ||||
|             return QueueError(url, "URL not supported yet", "unsupportedURL") | ||||
|  | ||||
|     def addToQueue(self, dz, sp, url, settings, bitrate=None, interface=None): | ||||
|         if not dz.logged_in: | ||||
|             if interface: | ||||
|                 interface.send("loginNeededToDownload") | ||||
|             return False | ||||
|  | ||||
| def addToQueue(dz, sp, url, settings, bitrate=None, interface=None): | ||||
|     global currentItem, queueList, queue | ||||
|     if not dz.logged_in: | ||||
|         return "Not logged in" | ||||
|     if type(url) is list: | ||||
|         queueItem = [] | ||||
|         for link in url: | ||||
|         def parseLink(link): | ||||
|             link = link.strip() | ||||
|             if link == "": | ||||
|                 continue | ||||
|                 return False | ||||
|             logger.info("Generating queue item for: "+link) | ||||
|             item = generateQueueItem(dz, sp, link, settings, bitrate, interface=interface) | ||||
|             if type(item) is list: | ||||
|                 queueItem += item | ||||
|             else: | ||||
|                 queueItem.append(item) | ||||
|     else: | ||||
|         url = url.strip() | ||||
|         if url == "": | ||||
|             return False | ||||
|         logger.info("Generating queue item for: "+url) | ||||
|         queueItem = generateQueueItem(dz, sp, url, settings, bitrate, interface=interface) | ||||
|     if type(queueItem) is list: | ||||
|         for x in queueItem: | ||||
|             if 'error' in x: | ||||
|                 logger.error(f"[{x['link']}] {x['error']}") | ||||
|                 continue | ||||
|             if x['uuid'] in list(queueList.keys()): | ||||
|                 logger.warn(f"[{x['uuid']}] Already in queue, will not be added again.") | ||||
|                 continue | ||||
|             if interface: | ||||
|                 interface.send("addedToQueue", slimQueueItem(x)) | ||||
|             queue.append(x['uuid']) | ||||
|             queueList[x['uuid']] = x | ||||
|             logger.info(f"[{x['uuid']}] Added to queue.") | ||||
|     else: | ||||
|         if 'error' in queueItem: | ||||
|             logger.error(f"[{queueItem['link']}] {queueItem['error']}") | ||||
|             if interface: | ||||
|                 interface.send("queueError", queueItem) | ||||
|             return False | ||||
|         if queueItem['uuid'] in list(queueList.keys()): | ||||
|             logger.warn(f"[{queueItem['uuid']}] Already in queue, will not be added again.") | ||||
|             if interface: | ||||
|                 interface.send("alreadyInQueue", {'uuid': queueItem['uuid'], 'title': queueItem['title']}) | ||||
|             return False | ||||
|         if interface: | ||||
|             interface.send("addedToQueue", slimQueueItem(queueItem)) | ||||
|         logger.info(f"[{queueItem['uuid']}] Added to queue.") | ||||
|         queue.append(queueItem['uuid']) | ||||
|         queueList[queueItem['uuid']] = queueItem | ||||
|     nextItem(dz, interface) | ||||
|     return True | ||||
|             return self.generateQueueItem(dz, sp, link, settings, bitrate, interface=interface) | ||||
|  | ||||
|  | ||||
| def nextItem(dz, interface=None): | ||||
|     global currentItem, queueList, queue | ||||
|     if currentItem != "": | ||||
|         return None | ||||
|     else: | ||||
|         if len(queue) > 0: | ||||
|             currentItem = queue.pop(0) | ||||
|         if type(url) is list: | ||||
|             queueItem = [] | ||||
|             for link in url: | ||||
|                 item = parseLink(link) | ||||
|                 if not item: | ||||
|                     continue | ||||
|                 elif type(item) is list: | ||||
|                     queueItem += item | ||||
|                 else: | ||||
|                     queueItem.append(item) | ||||
|             if not len(queueItem): | ||||
|                 return False | ||||
|         else: | ||||
|             queueItem = parseLink(url) | ||||
|             if not queueItem: | ||||
|                 return False | ||||
|  | ||||
|         if type(queueItem) is list: | ||||
|             ogLen = len(self.queue) | ||||
|             for x in queueItem: | ||||
|                 if isinstance(x, QueueError): | ||||
|                     logger.error(f"[{x.link}] {x.message}") | ||||
|                     continue | ||||
|                 if x.uuid in list(self.queueList.keys()): | ||||
|                     logger.warn(f"[{x.uuid}] Already in queue, will not be added again.") | ||||
|                     continue | ||||
|                 self.queue.append(x.uuid) | ||||
|                 self.queueList[x.uuid] = x | ||||
|                 logger.info(f"[{x.uuid}] Added to queue.") | ||||
|             if ogLen <= len(self.queue): | ||||
|                 return False | ||||
|         else: | ||||
|             if isinstance(queueItem, QueueError): | ||||
|                 logger.error(f"[{x.link}] {x.message}") | ||||
|                 if interface: | ||||
|                     interface.send("queueError", queueItem.toDict()) | ||||
|                 return False | ||||
|             if queueItem.uuid in list(self.queueList.keys()): | ||||
|                 logger.warn(f"[{queueItem.uuid}] Already in queue, will not be added again.") | ||||
|                 if interface: | ||||
|                     interface.send("alreadyInQueue", {'uuid': queueItem.uuid, 'title': queueItem.title}) | ||||
|                 return False | ||||
|             if interface: | ||||
|                 interface.send("addedToQueue", queueItem.getSlimmedItem()) | ||||
|             logger.info(f"[{queueItem.uuid}] Added to queue.") | ||||
|             self.queue.append(queueItem.uuid) | ||||
|             self.queueList[queueItem.uuid] = queueItem | ||||
|  | ||||
|         self.nextItem(dz, sp, interface) | ||||
|         return True | ||||
|  | ||||
|     def nextItem(self, dz, sp, interface=None): | ||||
|         if self.currentItem != "": | ||||
|             return None | ||||
|         else: | ||||
|             if len(self.queue) > 0: | ||||
|                 self.currentItem = self.queue.pop(0) | ||||
|             else: | ||||
|                 return None | ||||
|             if interface: | ||||
|                 interface.send("startDownload", self.currentItem) | ||||
|             logger.info(f"[{self.currentItem}] Started downloading.") | ||||
|             DownloadJob(dz, sp, self.queueList[self.currentItem], interface).start() | ||||
|             self.afterDownload(dz, sp, interface) | ||||
|  | ||||
|     def afterDownload(self, dz, sp, interface): | ||||
|         if self.queueList[self.currentItem].cancel: | ||||
|             del self.queueList[self.currentItem] | ||||
|         else: | ||||
|             self.queueComplete.append(self.currentItem) | ||||
|         logger.info(f"[{self.currentItem}] Finished downloading.") | ||||
|         self.currentItem = "" | ||||
|         self.nextItem(dz, sp, interface) | ||||
|  | ||||
|  | ||||
|     def getQueue(self): | ||||
|         return (self.queue, self.queueComplete, self.slimQueueList(), self.currentItem) | ||||
|  | ||||
|     def saveQueue(self, configFolder): | ||||
|         if len(self.queueList) > 0: | ||||
|             if self.currentItem != "": | ||||
|                 self.queue.insert(0, self.currentItem) | ||||
|             with open(path.join(configFolder, 'queue.json'), 'w') as f: | ||||
|                 json.dump({ | ||||
|                     'queue': self.queue, | ||||
|                     'queueComplete': self.queueComplete, | ||||
|                     'queueList': self.exportQueueList() | ||||
|                 }, f) | ||||
|  | ||||
|     def exportQueueList(self): | ||||
|         queueList = {} | ||||
|         for uuid in self.queueList: | ||||
|             if uuid in self.queue: | ||||
|                 queueList[uuid] = self.queueList[uuid].getResettedItem() | ||||
|             else: | ||||
|                 queueList[uuid] = self.queueList[uuid].toDict() | ||||
|         return queueList | ||||
|  | ||||
|     def slimQueueList(self): | ||||
|         queueList = {} | ||||
|         for uuid in self.queueList: | ||||
|             queueList[uuid] = self.queueList[uuid].getSlimmedItem() | ||||
|         return queueList | ||||
|  | ||||
|     def loadQueue(self, configFolder, settings, interface=None): | ||||
|         if path.isfile(path.join(configFolder, 'queue.json')) and not len(self.queue): | ||||
|             if interface: | ||||
|                 interface.send('restoringQueue') | ||||
|             with open(path.join(configFolder, 'queue.json'), 'r') as f: | ||||
|                 qd = json.load(f) | ||||
|             remove(path.join(configFolder, 'queue.json')) | ||||
|             self.restoreQueue(qd['queue'], qd['queueComplete'], qd['queueList'], settings) | ||||
|             if interface: | ||||
|                 interface.send('init_downloadQueue', { | ||||
|                     'queue': self.queue, | ||||
|                     'queueComplete': self.queueComplete, | ||||
|                     'queueList': self.slimQueueList(), | ||||
|                     'restored': True | ||||
|                 }) | ||||
|  | ||||
|     def restoreQueue(self, queue, queueComplete, queueList, settings): | ||||
|         self.queue = queue | ||||
|         self.queueComplete = queueComplete | ||||
|         self.queueList = {} | ||||
|         for uuid in queueList: | ||||
|             if 'single' in queueList[uuid]: | ||||
|                 self.queueList[uuid] = QISingle(queueItemDict = queueList[uuid]) | ||||
|             if 'collection' in queueList[uuid]: | ||||
|                 self.queueList[uuid] = QICollection(queueItemDict = queueList[uuid]) | ||||
|             if '_EXTRA' in queueList[uuid]: | ||||
|                 self.queueList[uuid] = QIConvertable(queueItemDict = queueList[uuid]) | ||||
|             self.queueList[uuid].settings = settings | ||||
|  | ||||
|     def removeFromQueue(self, uuid, interface=None): | ||||
|         if uuid == self.currentItem: | ||||
|             if interface: | ||||
|                 interface.send("cancellingCurrentItem", uuid) | ||||
|             self.queueList[uuid].cancel = True | ||||
|         elif uuid in self.queue: | ||||
|             self.queue.remove(uuid) | ||||
|             del self.queueList[uuid] | ||||
|             if interface: | ||||
|                 interface.send("removedFromQueue", uuid) | ||||
|         elif uuid in self.queueComplete: | ||||
|             self.queueComplete.remove(uuid) | ||||
|             del self.queueList[uuid] | ||||
|             if interface: | ||||
|                 interface.send("removedFromQueue", uuid) | ||||
|  | ||||
|  | ||||
|     def cancelAllDownloads(self, interface=None): | ||||
|         self.queue = [] | ||||
|         self.queueComplete = [] | ||||
|         if self.currentItem != "": | ||||
|             if interface: | ||||
|                 interface.send("cancellingCurrentItem", self.currentItem) | ||||
|             self.queueList[self.currentItem].cancel = True | ||||
|         for uuid in list(self.queueList.keys()): | ||||
|             if uuid != self.currentItem: | ||||
|                 del self.queueList[uuid] | ||||
|         if interface: | ||||
|             interface.send("startDownload", currentItem) | ||||
|         logger.info(f"[{currentItem}] Started downloading.") | ||||
|         result = download(dz, queueList[currentItem], interface) | ||||
|         callbackQueueDone(result) | ||||
|             interface.send("removedAllDownloads", self.currentItem) | ||||
|  | ||||
|  | ||||
| def callbackQueueDone(result): | ||||
|     global currentItem, queueList, queueComplete | ||||
|     if 'cancel' in queueList[currentItem]: | ||||
|         del queueList[currentItem] | ||||
|     else: | ||||
|         queueComplete.append(currentItem) | ||||
|     logger.info(f"[{currentItem}] Finished downloading.") | ||||
|     currentItem = "" | ||||
|     nextItem(result['dz'], result['interface']) | ||||
|  | ||||
|  | ||||
| def getQueue(): | ||||
|     global currentItem, queueList, queue, queueComplete | ||||
|     return (queue, queueComplete, queueList, currentItem) | ||||
|  | ||||
|  | ||||
| def restoreQueue(pqueue, pqueueComplete, pqueueList, dz, interface): | ||||
|     global currentItem, queueList, queue, queueComplete | ||||
|     queueComplete = pqueueComplete | ||||
|     queueList = pqueueList | ||||
|     queue = pqueue | ||||
|     nextItem(dz, interface) | ||||
|  | ||||
|  | ||||
| def removeFromQueue(uuid, interface=None): | ||||
|     global currentItem, queueList, queue, queueComplete | ||||
|     if uuid == currentItem: | ||||
|     def removeFinishedDownloads(self, interface=None): | ||||
|         for uuid in self.queueComplete: | ||||
|             del self.queueList[self.uuid] | ||||
|         self.queueComplete = [] | ||||
|         if interface: | ||||
|             interface.send("cancellingCurrentItem", currentItem) | ||||
|         queueList[uuid]['cancel'] = True | ||||
|     elif uuid in queue: | ||||
|         queue.remove(uuid) | ||||
|         del queueList[uuid] | ||||
|         if interface: | ||||
|             interface.send("removedFromQueue", uuid) | ||||
|     elif uuid in queueComplete: | ||||
|         queueComplete.remove(uuid) | ||||
|         del queueList[uuid] | ||||
|         if interface: | ||||
|             interface.send("removedFromQueue", uuid) | ||||
|             interface.send("removedFinishedDownloads") | ||||
|  | ||||
| class QueueError: | ||||
|     def __init__(self, link, message, errid=None): | ||||
|         self.link = link | ||||
|         self.message = message | ||||
|         self.errid = errid | ||||
|  | ||||
| def cancelAllDownloads(interface=None): | ||||
|     global currentItem, queueList, queue, queueComplete | ||||
|     queue = [] | ||||
|     queueComplete = [] | ||||
|     if currentItem != "": | ||||
|         if interface: | ||||
|             interface.send("cancellingCurrentItem", currentItem) | ||||
|         queueList[currentItem]['cancel'] = True | ||||
|     for uuid in list(queueList.keys()): | ||||
|         if uuid != currentItem: | ||||
|             del queueList[uuid] | ||||
|     if interface: | ||||
|         interface.send("removedAllDownloads", currentItem) | ||||
|  | ||||
|  | ||||
| def removeFinishedDownloads(interface=None): | ||||
|     global queueList, queueComplete | ||||
|     for uuid in queueComplete: | ||||
|         del queueList[uuid] | ||||
|     queueComplete = [] | ||||
|     if interface: | ||||
|         interface.send("removedFinishedDownloads") | ||||
|     def toDict(self): | ||||
|         return { | ||||
|             'link': self.link, | ||||
|             'error': self.message, | ||||
|             'errid': self.errid | ||||
|         } | ||||
|  | ||||
| @ -3,8 +3,6 @@ import json | ||||
| import os.path as path | ||||
| from os import makedirs, listdir, remove | ||||
| from deemix import __version__ as deemixVersion | ||||
| import random | ||||
| import string | ||||
| import logging | ||||
| import datetime | ||||
| import platform | ||||
| @ -14,93 +12,151 @@ logger = logging.getLogger('deemix') | ||||
|  | ||||
| import deemix.utils.localpaths as localpaths | ||||
|  | ||||
| settings = {} | ||||
| defaultSettings = {} | ||||
| configDir = "" | ||||
| class Settings: | ||||
|     def __init__(self, configFolder=None): | ||||
|         self.settings = {} | ||||
|         self.configFolder = configFolder | ||||
|         if not self.configFolder: | ||||
|             self.configFolder = localpaths.getConfigFolder() | ||||
|         self.defaultSettings = { | ||||
|           "downloadLocation": path.join(localpaths.getHomeFolder(), 'deemix Music'), | ||||
|           "tracknameTemplate": "%artist% - %title%", | ||||
|           "albumTracknameTemplate": "%tracknumber% - %title%", | ||||
|           "playlistTracknameTemplate": "%position% - %artist% - %title%", | ||||
|           "createPlaylistFolder": True, | ||||
|           "playlistNameTemplate": "%playlist%", | ||||
|           "createArtistFolder": False, | ||||
|           "artistNameTemplate": "%artist%", | ||||
|           "createAlbumFolder": True, | ||||
|           "albumNameTemplate": "%artist% - %album%", | ||||
|           "createCDFolder": True, | ||||
|           "createStructurePlaylist": False, | ||||
|           "createSingleFolder": False, | ||||
|           "padTracks": True, | ||||
|           "paddingSize": "0", | ||||
|           "illegalCharacterReplacer": "_", | ||||
|           "queueConcurrency": 3, | ||||
|           "maxBitrate": "3", | ||||
|           "fallbackBitrate": True, | ||||
|           "fallbackSearch": False, | ||||
|           "logErrors": True, | ||||
|           "logSearched": False, | ||||
|           "saveDownloadQueue": False, | ||||
|           "overwriteFile": "n", | ||||
|           "createM3U8File": False, | ||||
|           "playlistFilenameTemplate": "playlist", | ||||
|           "syncedLyrics": False, | ||||
|           "embeddedArtworkSize": 800, | ||||
|           "localArtworkSize": 1400, | ||||
|           "localArtworkFormat": "jpg", | ||||
|           "saveArtwork": True, | ||||
|           "coverImageTemplate": "cover", | ||||
|           "saveArtworkArtist": False, | ||||
|           "artistImageTemplate": "folder", | ||||
|           "jpegImageQuality": 80, | ||||
|           "dateFormat": "Y-M-D", | ||||
|           "albumVariousArtists": True, | ||||
|           "removeAlbumVersion": False, | ||||
|           "removeDuplicateArtists": False, | ||||
|           "featuredToTitle": "0", | ||||
|           "titleCasing": "nothing", | ||||
|           "artistCasing": "nothing", | ||||
|           "executeCommand": "", | ||||
|           "tags": { | ||||
|             "title": True, | ||||
|             "artist": True, | ||||
|             "album": True, | ||||
|             "cover": True, | ||||
|             "trackNumber": True, | ||||
|             "trackTotal": False, | ||||
|             "discNumber": True, | ||||
|             "discTotal": False, | ||||
|             "albumArtist": True, | ||||
|             "genre": True, | ||||
|             "year": True, | ||||
|             "date": True, | ||||
|             "explicit": False, | ||||
|             "isrc": True, | ||||
|             "length": True, | ||||
|             "barcode": True, | ||||
|             "bpm": True, | ||||
|             "replayGain": False, | ||||
|             "label": True, | ||||
|             "lyrics": False, | ||||
|             "copyright": False, | ||||
|             "composer": False, | ||||
|             "involvedPeople": False, | ||||
|             "savePlaylistAsCompilation": False, | ||||
|             "useNullSeparator": False, | ||||
|             "saveID3v1": True, | ||||
|             "multiArtistSeparator": "default", | ||||
|             "singleAlbumArtist": False | ||||
|           } | ||||
|         } | ||||
|  | ||||
| def initSettings(localFolder = False, configFolder = None): | ||||
|     global settings | ||||
|     global defaultSettings | ||||
|     global configDir | ||||
|     currentFolder = path.abspath(path.dirname(__file__)) | ||||
|     if not configFolder: | ||||
|         configFolder = localpaths.getConfigFolder() | ||||
|     configDir = configFolder | ||||
|     makedirs(configFolder, exist_ok=True) | ||||
|     with open(path.join(currentFolder, 'default.json'), 'r') as d: | ||||
|         defaultSettings = json.load(d) | ||||
|         defaultSettings['downloadLocation'] = path.join(localpaths.getHomeFolder(), 'deemix Music') | ||||
|     if not path.isfile(path.join(configFolder, 'config.json')): | ||||
|         with open(path.join(configFolder, 'config.json'), 'w') as f: | ||||
|             json.dump(defaultSettings, f, indent=2) | ||||
|     with open(path.join(configFolder, 'config.json'), 'r') as configFile: | ||||
|         settings = json.load(configFile) | ||||
|     settingsCheck() | ||||
|         # Create config folder if it doesn't exsist | ||||
|         makedirs(self.configFolder, exist_ok=True) | ||||
|  | ||||
|     if localFolder: | ||||
|         settings['downloadLocation'] = randomString(12) | ||||
|         logger.info("Using a local download folder: "+settings['downloadLocation']) | ||||
|     elif settings['downloadLocation'] == "": | ||||
|         settings['downloadLocation'] = path.join(localpaths.getHomeFolder(), 'deemix Music') | ||||
|         saveSettings(settings) | ||||
|     makedirs(settings['downloadLocation'], exist_ok=True) | ||||
|         # Create config file if it doesn't exsist | ||||
|         if not path.isfile(path.join(self.configFolder, 'config.json')): | ||||
|             with open(path.join(self.configFolder, 'config.json'), 'w') as f: | ||||
|                 json.dump(self.defaultSettings, f, indent=2) | ||||
|  | ||||
|     # logfiles | ||||
|     # logfile name | ||||
|     logspath = path.join(configFolder, 'logs') | ||||
|     now = datetime.datetime.now() | ||||
|     logfile = now.strftime("%Y-%m-%d_%H%M%S")+".log" | ||||
|     makedirs(logspath, exist_ok=True) | ||||
|     # add handler for logfile | ||||
|     fh = logging.FileHandler(path.join(logspath, logfile)) | ||||
|     fh.setLevel(logging.DEBUG) | ||||
|     fh.setFormatter(logging.Formatter('%(asctime)s - [%(levelname)s] %(message)s')) | ||||
|     logger.addHandler(fh) | ||||
|     logger.info(f"{platform.platform(True, True)} - Python {platform.python_version()}, deemix {deemixVersion}") | ||||
|     #delete old logfiles | ||||
|     logslist = listdir(logspath) | ||||
|     logslist.sort() | ||||
|     if len(logslist)>5: | ||||
|         for i in range(len(logslist)-5): | ||||
|             remove(path.join(logspath, logslist[i])) | ||||
|         # Read config file | ||||
|         with open(path.join(self.configFolder, 'config.json'), 'r') as configFile: | ||||
|             self.settings = json.load(configFile) | ||||
|  | ||||
|     return settings | ||||
|         self.settingsCheck() | ||||
|  | ||||
|         # Make sure the download path exsits | ||||
|         makedirs(self.settings['downloadLocation'], exist_ok=True) | ||||
|  | ||||
| def getSettings(): | ||||
|     global settings | ||||
|     return settings | ||||
|         # LOGFILES | ||||
|  | ||||
|         # Create logfile name and path | ||||
|         logspath = path.join(self.configFolder, 'logs') | ||||
|         now = datetime.datetime.now() | ||||
|         logfile = now.strftime("%Y-%m-%d_%H%M%S")+".log" | ||||
|         makedirs(logspath, exist_ok=True) | ||||
|  | ||||
| def getDefaultSettings(): | ||||
|     global defaultSettings | ||||
|     return defaultSettings | ||||
|         # Add handler for logging | ||||
|         fh = logging.FileHandler(path.join(logspath, logfile)) | ||||
|         fh.setLevel(logging.DEBUG) | ||||
|         fh.setFormatter(logging.Formatter('%(asctime)s - [%(levelname)s] %(message)s')) | ||||
|         logger.addHandler(fh) | ||||
|         logger.info(f"{platform.platform(True, True)} - Python {platform.python_version()}, deemix {deemixVersion}") | ||||
|  | ||||
|         # Only keep last 5 logfiles (to preserve disk space) | ||||
|         logslist = listdir(logspath) | ||||
|         logslist.sort() | ||||
|         if len(logslist)>5: | ||||
|             for i in range(len(logslist)-5): | ||||
|                 remove(path.join(logspath, logslist[i])) | ||||
|  | ||||
| def saveSettings(newSettings): | ||||
|     global settings | ||||
|     settings = newSettings | ||||
|     with open(path.join(configDir, 'config.json'), 'w') as configFile: | ||||
|         json.dump(settings, configFile, indent=2) | ||||
|     return True | ||||
|     # Saves the settings | ||||
|     def saveSettings(self, newSettings=None): | ||||
|         if newSettings: | ||||
|             self.settings = newSettings | ||||
|         with open(path.join(self.configFolder, 'config.json'), 'w') as configFile: | ||||
|             json.dump(self.settings, configFile, indent=2) | ||||
|  | ||||
|  | ||||
| def settingsCheck(): | ||||
|     global settings | ||||
|     global defaultSettings | ||||
|     changes = 0 | ||||
|     for x in defaultSettings: | ||||
|         if not x in settings or type(settings[x]) != type(defaultSettings[x]): | ||||
|             settings[x] = defaultSettings[x] | ||||
|     # Checks if the default settings have changed | ||||
|     def settingsCheck(self): | ||||
|         changes = 0 | ||||
|         for x in self.defaultSettings: | ||||
|             if not x in self.settings or type(self.settings[x]) != type(self.defaultSettings[x]): | ||||
|                 self.settings[x] = self.defaultSettings[x] | ||||
|                 changes += 1 | ||||
|         for x in self.defaultSettings['tags']: | ||||
|             if not x in self.settings['tags'] or type(self.settings['tags'][x]) != type(self.defaultSettings['tags'][x]): | ||||
|                 self.settings['tags'][x] = self.defaultSettings['tags'][x] | ||||
|                 changes += 1 | ||||
|         if self.settings['downloadLocation'] == "": | ||||
|             self.settings['downloadLocation'] = path.join(localpaths.getHomeFolder(), 'deemix Music') | ||||
|             changes += 1 | ||||
|     for x in defaultSettings['tags']: | ||||
|         if not x in settings['tags'] or type(settings['tags'][x]) != type(defaultSettings['tags'][x]): | ||||
|             settings['tags'][x] = defaultSettings['tags'][x] | ||||
|             changes += 1 | ||||
|     if changes > 0: | ||||
|         saveSettings(settings) | ||||
|  | ||||
|  | ||||
| def randomString(stringLength=8): | ||||
|     letters = string.ascii_lowercase | ||||
|     return ''.join(random.choice(letters) for i in range(stringLength)) | ||||
|         for template in ['tracknameTemplate', 'albumTracknameTemplate', 'playlistTracknameTemplate', 'playlistNameTemplate', 'artistNameTemplate', 'albumNameTemplate', 'playlistFilenameTemplate', 'coverImageTemplate', 'artistImageTemplate']: | ||||
|             if self.settings[template] == "": | ||||
|                 self.settings[template] = self.defaultSettings[template] | ||||
|                 changes += 1 | ||||
|         if changes > 0: | ||||
|             saveSettings() | ||||
|  | ||||
| @ -6,42 +6,45 @@ from os import mkdir | ||||
| import spotipy | ||||
| from spotipy.oauth2 import SpotifyClientCredentials | ||||
| from deemix.utils.localpaths import getConfigFolder | ||||
| from deemix.app.queueitem import QIConvertable, QICollection | ||||
| 
 | ||||
| emptyPlaylist = { | ||||
|     'collaborative': False, | ||||
|     'description': "", | ||||
|     'external_urls': {'spotify': None}, | ||||
|     'followers': {'total': 0, 'href': None}, | ||||
|     'id': None, | ||||
|     'images': [], | ||||
|     'name': "Something went wrong", | ||||
|     'owner': { | ||||
|         'display_name': "Error", | ||||
|         'id': None | ||||
|     }, | ||||
|     'public': True, | ||||
|     'tracks' : [], | ||||
|     'type': 'playlist', | ||||
|     'uri': None | ||||
| } | ||||
| 
 | ||||
| class SpotifyHelper: | ||||
|     def __init__(self, configFolder=None): | ||||
|         self.credentials = {} | ||||
|         self.spotifyEnabled = False | ||||
|         self.sp = None | ||||
|         if not configFolder: | ||||
|             self.configFolder = getConfigFolder() | ||||
|         else: | ||||
|             self.configFolder = configFolder | ||||
|         self.emptyPlaylist = { | ||||
|             'collaborative': False, | ||||
|             'description': "", | ||||
|             'external_urls': {'spotify': None}, | ||||
|             'followers': {'total': 0, 'href': None}, | ||||
|             'id': None, | ||||
|             'images': [], | ||||
|             'name': "Something went wrong", | ||||
|             'owner': { | ||||
|                 'display_name': "Error", | ||||
|                 'id': None | ||||
|             }, | ||||
|             'public': True, | ||||
|             'tracks' : [], | ||||
|             'type': 'playlist', | ||||
|             'uri': None | ||||
|         } | ||||
|         self.initCredentials() | ||||
|         self.configFolder = configFolder | ||||
| 
 | ||||
|     def initCredentials(self): | ||||
|         # Make sure config folder exsists | ||||
|         if not self.configFolder: | ||||
|             self.configFolder = getConfigFolder() | ||||
|         if not path.isdir(self.configFolder): | ||||
|             mkdir(self.configFolder) | ||||
| 
 | ||||
|         # Make sure authCredentials exsits | ||||
|         if not path.isfile(path.join(self.configFolder, 'authCredentials.json')): | ||||
|             with open(path.join(self.configFolder, 'authCredentials.json'), 'w') as f: | ||||
|                 json.dump({'clientId': "", 'clientSecret': ""}, f, indent=2) | ||||
| 
 | ||||
|         # Load spotify id and secret and check if they are usable | ||||
|         with open(path.join(self.configFolder, 'authCredentials.json'), 'r') as credentialsFile: | ||||
|             self.credentials = json.load(credentialsFile) | ||||
|         self.checkCredentials() | ||||
| @ -51,7 +54,9 @@ class SpotifyHelper: | ||||
|             spotifyEnabled = False | ||||
|         else: | ||||
|             try: | ||||
|                 self.createSpotifyConnection() | ||||
|                 client_credentials_manager = SpotifyClientCredentials(client_id=self.credentials['clientId'], | ||||
|                                                                       client_secret=self.credentials['clientSecret']) | ||||
|                 self.sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager) | ||||
|                 self.sp.user_playlists('spotify') | ||||
|                 self.spotifyEnabled = True | ||||
|             except Exception as e: | ||||
| @ -62,18 +67,19 @@ class SpotifyHelper: | ||||
|         return self.credentials | ||||
| 
 | ||||
|     def setCredentials(self, spotifyCredentials): | ||||
|         # Remove extra spaces, just to be sure | ||||
|         spotifyCredentials['clientId'] = spotifyCredentials['clientId'].strip() | ||||
|         spotifyCredentials['clientSecret'] = spotifyCredentials['clientSecret'].strip() | ||||
| 
 | ||||
|         # Save them to disk | ||||
|         with open(path.join(self.configFolder, 'authCredentials.json'), 'w') as f: | ||||
|             json.dump(spotifyCredentials, f, indent=2) | ||||
| 
 | ||||
|         # Check if they are usable | ||||
|         self.credentials = spotifyCredentials | ||||
|         self.checkCredentials() | ||||
| 
 | ||||
|     def createSpotifyConnection(self): | ||||
|         client_credentials_manager = SpotifyClientCredentials(client_id=self.credentials['clientId'], | ||||
|                                                               client_secret=self.credentials['clientSecret']) | ||||
|         self.sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager) | ||||
| 
 | ||||
|     # Converts spotify API playlist structure to deezer's playlist structure | ||||
|     def _convert_playlist_structure(self, spotify_obj): | ||||
|         if len(spotify_obj['images']): | ||||
|             url = spotify_obj['images'][0]['url'] | ||||
| @ -114,6 +120,7 @@ class SpotifyHelper: | ||||
|             deezer_obj['picture_xl'] = "https://e-cdns-images.dzcdn.net/images/cover/d41d8cd98f00b204e9800998ecf8427e/1000x1000-000000-80-0-0.jpg" | ||||
|         return deezer_obj | ||||
| 
 | ||||
|     # Returns deezer song_id from spotify track_id or track dict | ||||
|     def get_trackid_spotify(self, dz, track_id, fallbackSearch, spotifyTrack=None): | ||||
|         if not self.spotifyEnabled: | ||||
|             raise spotifyFeaturesNotEnabled | ||||
| @ -147,6 +154,7 @@ class SpotifyHelper: | ||||
|                 json.dump(cache, spotifyCache) | ||||
|         return dz_track | ||||
| 
 | ||||
|     # Returns deezer album_id from spotify album_id | ||||
|     def get_albumid_spotify(self, dz, album_id): | ||||
|         if not self.spotifyEnabled: | ||||
|             raise spotifyFeaturesNotEnabled | ||||
| @ -174,50 +182,65 @@ class SpotifyHelper: | ||||
|             json.dump(cache, spotifyCache) | ||||
|         return dz_album | ||||
| 
 | ||||
|     def convert_spotify_playlist(self, dz, playlist_id, settings): | ||||
| 
 | ||||
|     def generate_playlist_queueitem(self, dz, playlist_id, bitrate, settings): | ||||
|         if not self.spotifyEnabled: | ||||
|             raise spotifyFeaturesNotEnabled | ||||
|         spotify_playlist = self.sp.playlist(playlist_id) | ||||
|         result = { | ||||
|             'title': spotify_playlist['name'], | ||||
|             'artist': spotify_playlist['owner']['display_name'], | ||||
|             'size': spotify_playlist['tracks']['total'], | ||||
|             'downloaded': 0, | ||||
|             'failed': 0, | ||||
|             'progress': 0, | ||||
|             'errors': [], | ||||
|             'type': 'spotify_playlist', | ||||
|             'settings': settings or {}, | ||||
|             'id': playlist_id | ||||
|         } | ||||
| 
 | ||||
|         if len(spotify_playlist['images']): | ||||
|             result['cover'] = spotify_playlist['images'][0]['url'] | ||||
|             cover = spotify_playlist['images'][0]['url'] | ||||
|         else: | ||||
|             result[ | ||||
|                 'cover'] = "https://e-cdns-images.dzcdn.net/images/cover/d41d8cd98f00b204e9800998ecf8427e/75x75-000000-80-0-0.jpg" | ||||
|             cover = "https://e-cdns-images.dzcdn.net/images/cover/d41d8cd98f00b204e9800998ecf8427e/75x75-000000-80-0-0.jpg" | ||||
| 
 | ||||
|         playlistAPI = self._convert_playlist_structure(spotify_playlist) | ||||
|         playlistAPI['various_artist'] = dz.get_artist(5080) | ||||
| 
 | ||||
|         extra = {} | ||||
|         extra['unconverted'] = [] | ||||
| 
 | ||||
|         tracklistTmp = spotify_playlist['tracks']['items'] | ||||
|         result['collection'] = [] | ||||
|         tracklist = [] | ||||
|         while spotify_playlist['tracks']['next']: | ||||
|             spotify_playlist['tracks'] = self.sp.next(spotify_playlist['tracks']) | ||||
|             tracklistTmp += spotify_playlist['tracks']['items'] | ||||
|         for item in tracklistTmp: | ||||
|             if item['track']: | ||||
|                 tracklist.append(item['track']) | ||||
|         totalSize = len(tracklist) | ||||
|         result['size'] = totalSize | ||||
|                 if item['track']['explicit']: | ||||
|                     playlistAPI['explicit'] = True | ||||
|                 extra['unconverted'].append(item['track']) | ||||
| 
 | ||||
|         totalSize = len(extra['unconverted']) | ||||
|         if not 'explicit' in playlistAPI: | ||||
|             playlistAPI['explicit'] = False | ||||
|         extra['playlistAPI'] = playlistAPI | ||||
|         return QIConvertable( | ||||
|             playlist_id, | ||||
|             bitrate, | ||||
|             spotify_playlist['name'], | ||||
|             spotify_playlist['owner']['display_name'], | ||||
|             cover, | ||||
|             totalSize, | ||||
|             'spotify_playlist', | ||||
|             settings, | ||||
|             extra, | ||||
|         ) | ||||
| 
 | ||||
|     def convert_spotify_playlist(self, dz, queueItem, interface=None): | ||||
|         convertPercentage = 0 | ||||
|         lastPercentage = 0 | ||||
|         if path.isfile(path.join(self.configFolder, 'spotifyCache.json')): | ||||
|             with open(path.join(self.configFolder, 'spotifyCache.json'), 'r') as spotifyCache: | ||||
|                 cache = json.load(spotifyCache) | ||||
|         else: | ||||
|             cache = {'tracks': {}, 'albums': {}} | ||||
|         for pos, track in enumerate(tracklist, start=1): | ||||
|         if interface: | ||||
|             interface.send("startConversion", queueItem.uuid) | ||||
|         collection = [] | ||||
|         for pos, track in enumerate(queueItem.extra['unconverted'], start=1): | ||||
|             if str(track['id']) in cache['tracks']: | ||||
|                 trackID = cache['tracks'][str(track['id'])] | ||||
|             else: | ||||
|                 trackID = self.get_trackid_spotify(dz, 0, settings['fallbackSearch'], track) | ||||
|                 trackID = self.get_trackid_spotify(dz, 0, queueItem.settings['fallbackSearch'], track) | ||||
|                 cache['tracks'][str(track['id'])] = trackID | ||||
|             if trackID == 0: | ||||
|                 deezerTrack = { | ||||
| @ -234,18 +257,25 @@ class SpotifyHelper: | ||||
|                 } | ||||
|             else: | ||||
|                 deezerTrack = dz.get_track_gw(trackID) | ||||
|             if 'EXPLICIT_LYRICS' in deezerTrack and deezerTrack['EXPLICIT_LYRICS'] == "1": | ||||
|                 playlistAPI['explicit'] = True | ||||
|             deezerTrack['_EXTRA_PLAYLIST'] = playlistAPI | ||||
|             deezerTrack['_EXTRA_PLAYLIST'] = queueItem.extra['playlistAPI'] | ||||
|             deezerTrack['POSITION'] = pos | ||||
|             deezerTrack['SIZE'] = totalSize | ||||
|             deezerTrack['FILENAME_TEMPLATE'] = settings['playlistTracknameTemplate'] | ||||
|             result['collection'].append(deezerTrack) | ||||
|         if not 'explicit' in playlistAPI: | ||||
|             playlistAPI['explicit'] = False | ||||
|             deezerTrack['SIZE'] = queueItem.size | ||||
|             deezerTrack['FILENAME_TEMPLATE'] = queueItem.settings['playlistTracknameTemplate'] | ||||
|             collection.append(deezerTrack) | ||||
| 
 | ||||
|             convertPercentage = (pos / queueItem.size) * 100 | ||||
|             if round(convertPercentage) != lastPercentage and round(convertPercentage) % 5 == 0: | ||||
|                 lastPercentage = round(convertPercentage) | ||||
|                 if interface: | ||||
|                     interface.send("updateQueue", {'uuid': queueItem.uuid, 'conversion': lastPercentage}) | ||||
| 
 | ||||
|         queueItem.extra = None | ||||
|         queueItem.collection = collection | ||||
| 
 | ||||
|         with open(path.join(self.configFolder, 'spotifyCache.json'), 'w') as spotifyCache: | ||||
|             json.dump(cache, spotifyCache) | ||||
|         return result | ||||
|         if interface: | ||||
|             interface.send("startDownload", queueItem.uuid) | ||||
| 
 | ||||
|     def get_user_playlists(self, user): | ||||
|         if not self.spotifyEnabled: | ||||
							
								
								
									
										354
									
								
								deemix/app/track.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										354
									
								
								deemix/app/track.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,354 @@ | ||||
| #!/usr/bin/env python3 | ||||
| import logging | ||||
|  | ||||
| from deemix.api.deezer import APIError | ||||
| from deemix.utils.misc import removeFeatures, andCommaConcat, uniqueArray | ||||
|  | ||||
| logging.basicConfig(level=logging.INFO) | ||||
| logger = logging.getLogger('deemix') | ||||
|  | ||||
| class Track: | ||||
|     def __init__(self, dz, settings, trackAPI_gw, trackAPI=None, albumAPI_gw=None, albumAPI=None): | ||||
|         self.parseEssentialData(dz, trackAPI_gw) | ||||
|  | ||||
|         self.title = trackAPI_gw['SNG_TITLE'].strip() | ||||
|         if 'VERSION' in trackAPI_gw and trackAPI_gw['VERSION'] and not trackAPI_gw['VERSION'] in trackAPI_gw['SNG_TITLE']: | ||||
|             self.title += " " + trackAPI_gw['VERSION'].strip() | ||||
|  | ||||
|         self.position = None | ||||
|         if 'POSITION' in trackAPI_gw: | ||||
|             self.position = trackAPI_gw['POSITION'] | ||||
|  | ||||
|         self.localTrack = int(self.id) < 0 | ||||
|         if self.localTrack: | ||||
|             self.parseLocalTrackData(trackAPI_gw) | ||||
|         else: | ||||
|             self.parseData(dz, settings, trackAPI_gw, trackAPI, albumAPI_gw, albumAPI) | ||||
|  | ||||
|         if not 'Main' in self.artist: | ||||
|             self.artist['Main'] = [self.mainArtist['name']] | ||||
|  | ||||
|         # Fix incorrect day month when detectable | ||||
|         if int(self.date['month']) > 12: | ||||
|             monthTemp = self.date['month'] | ||||
|             self.date['month'] = self.date['day'] | ||||
|             self.date['day'] = monthTemp | ||||
|         if int(self.album['date']['month']) > 12: | ||||
|             monthTemp = self.album['date']['month'] | ||||
|             self.album['date']['month'] = self.album['date']['day'] | ||||
|             self.album['date']['day'] = monthTemp | ||||
|  | ||||
|         # Add playlist data if track is in a playlist | ||||
|         self.playlist = None | ||||
|         if "_EXTRA_PLAYLIST" in trackAPI_gw: | ||||
|             self.playlist = {} | ||||
|             if 'dzcdn.net' in trackAPI_gw["_EXTRA_PLAYLIST"]['picture_small']: | ||||
|                 self.playlist['picUrl'] = trackAPI_gw["_EXTRA_PLAYLIST"]['picture_small'][:-24] + "/{}x{}-{}".format( | ||||
|                     settings['embeddedArtworkSize'], settings['embeddedArtworkSize'], | ||||
|                     f'000000-{settings["jpegImageQuality"]}-0-0.jpg') | ||||
|             else: | ||||
|                 self.playlist['picUrl'] = trackAPI_gw["_EXTRA_PLAYLIST"]['picture_xl'] | ||||
|             self.playlist['title'] = trackAPI_gw["_EXTRA_PLAYLIST"]['title'] | ||||
|             self.playlist['mainArtist'] = { | ||||
|                 'id': trackAPI_gw["_EXTRA_PLAYLIST"]['various_artist']['id'], | ||||
|                 'name': trackAPI_gw["_EXTRA_PLAYLIST"]['various_artist']['name'], | ||||
|                 'pic': trackAPI_gw["_EXTRA_PLAYLIST"]['various_artist']['picture_small'][ | ||||
|                        trackAPI_gw["_EXTRA_PLAYLIST"]['various_artist']['picture_small'].find('artist/') + 7:-24] | ||||
|             } | ||||
|             if settings['albumVariousArtists']: | ||||
|                 self.playlist['artist'] = {"Main": [trackAPI_gw["_EXTRA_PLAYLIST"]['various_artist']['name'], ]} | ||||
|                 self.playlist['artists'] = [trackAPI_gw["_EXTRA_PLAYLIST"]['various_artist']['name'], ] | ||||
|             else: | ||||
|                 self.playlist['artist'] = {"Main": []} | ||||
|                 self.playlist['artists'] = [] | ||||
|             self.playlist['trackTotal'] = trackAPI_gw["_EXTRA_PLAYLIST"]['nb_tracks'] | ||||
|             self.playlist['recordType'] = "Compilation" | ||||
|             self.playlist['barcode'] = "" | ||||
|             self.playlist['label'] = "" | ||||
|             self.playlist['explicit'] = trackAPI_gw['_EXTRA_PLAYLIST']['explicit'] | ||||
|             self.playlist['date'] = { | ||||
|                 'day': trackAPI_gw["_EXTRA_PLAYLIST"]["creation_date"][8:10], | ||||
|                 'month': trackAPI_gw["_EXTRA_PLAYLIST"]["creation_date"][5:7], | ||||
|                 'year': trackAPI_gw["_EXTRA_PLAYLIST"]["creation_date"][0:4] | ||||
|             } | ||||
|             self.playlist['discTotal'] = "1" | ||||
|  | ||||
|         self.mainArtistsString = andCommaConcat(self.artist['Main']) | ||||
|         self.featArtistsString = None | ||||
|         if 'Featured' in self.artist: | ||||
|             self.featArtistsString = "feat. "+andCommaConcat(self.artist['Featured']) | ||||
|  | ||||
|         # Bits useful for later | ||||
|         self.searched = False | ||||
|         self.selectedFormat = 0 | ||||
|         self.dateString = None | ||||
|         self.album['picUrl'] = None | ||||
|         self.album['picPath'] = None | ||||
|         self.album['bitrate'] = 0 | ||||
|         self.album['dateString'] = None | ||||
|  | ||||
|         self.artistsString = "" | ||||
|  | ||||
|     def parseEssentialData(self, dz, trackAPI_gw): | ||||
|         self.id = trackAPI_gw['SNG_ID'] | ||||
|         self.duration = trackAPI_gw['DURATION'] | ||||
|         self.MD5 = trackAPI_gw['MD5_ORIGIN'] | ||||
|         self.mediaVersion = trackAPI_gw['MEDIA_VERSION'] | ||||
|         self.fallbackId = "0" | ||||
|         if 'FALLBACK' in trackAPI_gw: | ||||
|             self.fallbackId = trackAPI_gw['FALLBACK']['SNG_ID'] | ||||
|         self.filesizes = dz.get_track_filesizes(self.id) | ||||
|  | ||||
|     def parseLocalTrackData(self, trackAPI_gw): | ||||
|         self.album = { | ||||
|             'id': "0", | ||||
|             'title': trackAPI_gw['ALB_TITLE'], | ||||
|             'pic': "" | ||||
|         } | ||||
|         if 'ALB_PICTURE' in trackAPI_gw: | ||||
|             self.album['pic'] = trackAPI_gw['ALB_PICTURE'] | ||||
|         self.mainArtist = { | ||||
|             'id': "0", | ||||
|             'name': trackAPI_gw['ART_NAME'], | ||||
|             'pic': "" | ||||
|         } | ||||
|         self.artists = [trackAPI_gw['ART_NAME']] | ||||
|         self.artist = { | ||||
|             'Main': [trackAPI_gw['ART_NAME']] | ||||
|         } | ||||
|         self.date = { | ||||
|             'day': "00", | ||||
|             'month': "00", | ||||
|             'year': "XXXX" | ||||
|         } | ||||
|         # All the missing data | ||||
|         self.ISRC = "" | ||||
|         self.album['artist'] = self.artist | ||||
|         self.album['artists'] = self.artists | ||||
|         self.album['barcode'] = "Unknown" | ||||
|         self.album['date'] = self.date | ||||
|         self.album['discTotal'] = "0" | ||||
|         self.album['explicit'] = False | ||||
|         self.album['genre'] = [] | ||||
|         self.album['label'] = "Unknown" | ||||
|         self.album['mainArtist'] = self.mainArtist | ||||
|         self.album['recordType'] = "Album" | ||||
|         self.album['trackTotal'] = "0" | ||||
|         self.bpm = 0 | ||||
|         self.contributors = {} | ||||
|         self.copyright = "" | ||||
|         self.discNumber = "0" | ||||
|         self.explicit = False | ||||
|         self.lyrics = {} | ||||
|         self.replayGain = "" | ||||
|         self.trackNumber = "0" | ||||
|  | ||||
|     def parseData(self, dz, settings, trackAPI_gw, trackAPI, albumAPI_gw, albumAPI): | ||||
|         self.discNumber = None | ||||
|         if 'DISK_NUMBER' in trackAPI_gw: | ||||
|             self.discNumber = trackAPI_gw['DISK_NUMBER'] | ||||
|         self.explicit = None | ||||
|         if 'EXPLICIT_LYRICS' in trackAPI_gw: | ||||
|             self.explicit = trackAPI_gw['EXPLICIT_LYRICS'] | ||||
|         self.copyright = None | ||||
|         if 'COPYRIGHT' in trackAPI_gw: | ||||
|             self.copyright = trackAPI_gw['COPYRIGHT'] | ||||
|         self.replayGain = "" | ||||
|         if 'GAIN' in trackAPI_gw: | ||||
|             self.replayGain = "{0:.2f} dB".format((float(trackAPI_gw['GAIN']) + 18.4) * -1) | ||||
|         self.ISRC = trackAPI_gw['ISRC'] | ||||
|         self.trackNumber = trackAPI_gw['TRACK_NUMBER'] | ||||
|         self.contributors = trackAPI_gw['SNG_CONTRIBUTORS'] | ||||
|  | ||||
|         self.lyrics = { | ||||
|             'id': None, | ||||
|             'unsync': None, | ||||
|             'sync': None | ||||
|         } | ||||
|         if 'LYRICS_ID' in trackAPI_gw: | ||||
|             self.lyrics['id'] = trackAPI_gw['LYRICS_ID'] | ||||
|         if not "LYRICS" in trackAPI_gw and int(self.lyrics['id']) != 0: | ||||
|             logger.info(f"[{trackAPI_gw['ART_NAME']} - {self.title}] Getting lyrics") | ||||
|             trackAPI_gw["LYRICS"] = dz.get_lyrics_gw(self.id) | ||||
|         if int(self.lyrics['id']) != 0: | ||||
|             if "LYRICS_TEXT" in trackAPI_gw["LYRICS"]: | ||||
|                 self.lyrics['unsync'] = trackAPI_gw["LYRICS"]["LYRICS_TEXT"] | ||||
|             if "LYRICS_SYNC_JSON" in trackAPI_gw["LYRICS"]: | ||||
|                 self.lyrics['sync'] = "" | ||||
|                 lastTimestamp = "" | ||||
|                 for i in range(len(trackAPI_gw["LYRICS"]["LYRICS_SYNC_JSON"])): | ||||
|                     if "lrc_timestamp" in trackAPI_gw["LYRICS"]["LYRICS_SYNC_JSON"][i]: | ||||
|                         self.lyrics['sync'] += trackAPI_gw["LYRICS"]["LYRICS_SYNC_JSON"][i]["lrc_timestamp"] | ||||
|                         lastTimestamp = trackAPI_gw["LYRICS"]["LYRICS_SYNC_JSON"][i]["lrc_timestamp"] | ||||
|                     else: | ||||
|                         self.lyrics['sync'] += lastTimestamp | ||||
|                     self.lyrics['sync'] += trackAPI_gw["LYRICS"]["LYRICS_SYNC_JSON"][i]["line"] + "\r\n" | ||||
|  | ||||
|         self.mainArtist = { | ||||
|             'id': trackAPI_gw['ART_ID'], | ||||
|             'name': trackAPI_gw['ART_NAME'], | ||||
|             'pic': None | ||||
|         } | ||||
|         if 'ART_PICTURE' in trackAPI_gw: | ||||
|             self.mainArtist['pic'] = trackAPI_gw['ART_PICTURE'] | ||||
|  | ||||
|         self.date = None | ||||
|         if 'PHYSICAL_RELEASE_DATE' in trackAPI_gw: | ||||
|             self.date = { | ||||
|                 'day': trackAPI_gw["PHYSICAL_RELEASE_DATE"][8:10], | ||||
|                 'month': trackAPI_gw["PHYSICAL_RELEASE_DATE"][5:7], | ||||
|                 'year': trackAPI_gw["PHYSICAL_RELEASE_DATE"][0:4] | ||||
|             } | ||||
|  | ||||
|         self.album = { | ||||
|             'id': trackAPI_gw['ALB_ID'], | ||||
|             'title': trackAPI_gw['ALB_TITLE'], | ||||
|             'pic': None, | ||||
|             'barcode': "Unknown", | ||||
|             'label': "Unknown", | ||||
|             'explicit': False, | ||||
|             'date': None, | ||||
|             'genre': [] | ||||
|         } | ||||
|         if 'ALB_PICTURE' in trackAPI_gw: | ||||
|             self.album['pic'] = trackAPI_gw['ALB_PICTURE'] | ||||
|         try: | ||||
|             # Try the public API first (as it has more data) | ||||
|             if not albumAPI: | ||||
|                 logger.info(f"[{self.mainArtist['name']} - {self.title}] Getting album infos") | ||||
|                 albumAPI = dz.get_album(self.album['id']) | ||||
|             self.album['title'] = albumAPI['title'] | ||||
|             self.album['mainArtist'] = { | ||||
|                 'id': albumAPI['artist']['id'], | ||||
|                 'name': albumAPI['artist']['name'], | ||||
|                 'pic': albumAPI['artist']['picture_small'][albumAPI['artist']['picture_small'].find('artist/') + 7:-24] | ||||
|             } | ||||
|  | ||||
|             self.album['artist'] = {} | ||||
|             self.album['artists'] = [] | ||||
|             for artist in albumAPI['contributors']: | ||||
|                 if artist['id'] != 5080 or artist['id'] == 5080 and settings['albumVariousArtists']: | ||||
|                     if artist['name'] not in self.album['artists']: | ||||
|                         self.album['artists'].append(artist['name']) | ||||
|                     if artist['role'] == "Main" or artist['role'] != "Main" and artist['name'] not in self.album['artist']['Main']: | ||||
|                         if not artist['role'] in self.album['artist']: | ||||
|                             self.album['artist'][artist['role']] = [] | ||||
|                         self.album['artist'][artist['role']].append(artist['name']) | ||||
|             if settings['removeDuplicateArtists']: | ||||
|                 self.album['artists'] = uniqueArray(self.album['artists']) | ||||
|                 for role in self.album['artist'].keys(): | ||||
|                     self.album['artist'][role] = uniqueArray(self.album['artist'][role]) | ||||
|  | ||||
|             self.album['trackTotal'] = albumAPI['nb_tracks'] | ||||
|             self.album['recordType'] = albumAPI['record_type'] | ||||
|  | ||||
|             if 'upc' in albumAPI: | ||||
|                 self.album['barcode'] = albumAPI['upc'] | ||||
|             if 'label' in albumAPI: | ||||
|                 self.album['label'] = albumAPI['label'] | ||||
|             if 'explicit_lyrics' in albumAPI: | ||||
|                 self.album['explicit'] = albumAPI['explicit_lyrics'] | ||||
|             if 'release_date' in albumAPI: | ||||
|                 self.album['date'] = { | ||||
|                     'day': albumAPI["release_date"][8:10], | ||||
|                     'month': albumAPI["release_date"][5:7], | ||||
|                     'year': albumAPI["release_date"][0:4] | ||||
|                 } | ||||
|             self.album['discTotal'] = None | ||||
|             if 'nb_disk' in albumAPI: | ||||
|                 self.album['discTotal'] = albumAPI['nb_disk'] | ||||
|             self.copyright = None | ||||
|             if 'copyright' in albumAPI: | ||||
|                 self.copyright = albumAPI['copyright'] | ||||
|  | ||||
|             if not self.album['pic']: | ||||
|                 self.album['pic'] = albumAPI['cover_small'][albumAPI['cover_small'].find('cover/') + 6:-24] | ||||
|  | ||||
|             if 'genres' in albumAPI and 'data' in albumAPI['genres'] and len(albumAPI['genres']['data']) > 0: | ||||
|                 for genre in albumAPI['genres']['data']: | ||||
|                     self.album['genre'].append(genre['name']) | ||||
|         except APIError: | ||||
|             if not albumAPI_gw: | ||||
|                 logger.info(f"[{self.mainArtist['name']} - {self.title}] Getting more album infos") | ||||
|                 albumAPI_gw = dz.get_album_gw(self.album['id']) | ||||
|             self.album['title'] = albumAPI_gw['ALB_TITLE'] | ||||
|             self.album['mainArtist'] = { | ||||
|                 'id': albumAPI_gw['ART_ID'], | ||||
|                 'name': albumAPI_gw['ART_NAME'], | ||||
|                 'pic': None | ||||
|             } | ||||
|             logger.info(f"[{self.mainArtist['name']} - {self.title}] Getting artist picture fallback") | ||||
|             artistAPI = dz.get_artist(self.album['mainArtist']['id']) | ||||
|             self.album['artists'] = [albumAPI_gw['ART_NAME']] | ||||
|             self.album['mainArtist']['pic'] = artistAPI['picture_small'][artistAPI['picture_small'].find('artist/') + 7:-24] | ||||
|             self.album['trackTotal'] = albumAPI_gw['NUMBER_TRACK'] | ||||
|             self.album['discTotal'] = albumAPI_gw['NUMBER_DISK'] | ||||
|             self.album['recordType'] = "Album" | ||||
|             if 'LABEL_NAME' in albumAPI_gw: | ||||
|                 self.album['label'] = albumAPI_gw['LABEL_NAME'] | ||||
|             if 'EXPLICIT_ALBUM_CONTENT' in albumAPI_gw and 'EXPLICIT_LYRICS_STATUS' in albumAPI_gw['EXPLICIT_ALBUM_CONTENT']: | ||||
|                 self.album['explicit'] = albumAPI_gw['EXPLICIT_ALBUM_CONTENT']['EXPLICIT_LYRICS_STATUS'] in [1,4] | ||||
|             if not self.album['pic']: | ||||
|                 self.album['pic'] = albumAPI_gw['ALB_PICTURE'] | ||||
|             if 'PHYSICAL_RELEASE_DATE' in albumAPI_gw: | ||||
|                 self.album['date'] = { | ||||
|                     'day': albumAPI_gw["PHYSICAL_RELEASE_DATE"][8:10], | ||||
|                     'month': albumAPI_gw["PHYSICAL_RELEASE_DATE"][5:7], | ||||
|                     'year': albumAPI_gw["PHYSICAL_RELEASE_DATE"][0:4] | ||||
|                 } | ||||
|  | ||||
|         self.album['mainArtist']['save'] = self.album['mainArtist']['id'] != 5080 or self.album['mainArtist']['id'] == 5080 and settings['albumVariousArtists'] | ||||
|  | ||||
|         if self.album['date'] and not self.date: | ||||
|             self.date = self.album['date'] | ||||
|  | ||||
|         if not trackAPI: | ||||
|             logger.info(f"[{self.mainArtist['name']} - {self.title}] Getting extra track infos") | ||||
|             trackAPI = dz.get_track(self.id) | ||||
|         self.bpm = trackAPI['bpm'] | ||||
|  | ||||
|         if not self.replayGain and 'gain' in trackAPI: | ||||
|             self.replayGain = "{0:.2f} dB".format((float(trackAPI['gain']) + 18.4) * -1) | ||||
|         if not self.explicit: | ||||
|             self.explicit = trackAPI['explicit_lyrics'] | ||||
|         if not self.discNumber: | ||||
|             self.discNumber = trackAPI['disk_number'] | ||||
|  | ||||
|         self.artist = {} | ||||
|         self.artists = [] | ||||
|         for artist in trackAPI['contributors']: | ||||
|             if artist['id'] != 5080 or artist['id'] == 5080 and len(trackAPI['contributors']) == 1: | ||||
|                 if artist['name'] not in self.artists: | ||||
|                     self.artists.append(artist['name']) | ||||
|                 if artist['role'] != "Main" and artist['name'] not in self.artist['Main'] or artist['role'] == "Main": | ||||
|                     if not artist['role'] in self.artist: | ||||
|                         self.artist[artist['role']] = [] | ||||
|                     self.artist[artist['role']].append(artist['name']) | ||||
|         if settings['removeDuplicateArtists']: | ||||
|             self.artists = uniqueArray(self.artists) | ||||
|             for role in self.artist.keys(): | ||||
|                 self.artist[role] = uniqueArray(self.artist[role]) | ||||
|  | ||||
|         if not self.album['discTotal']: | ||||
|             if not albumAPI_gw: | ||||
|                 logger.info(f"[{self.mainArtist['name']} - {self.title}] Getting more album infos") | ||||
|                 albumAPI_gw = dz.get_album_gw(self.album['id']) | ||||
|             self.album['discTotal'] = albumAPI_gw['NUMBER_DISK'] | ||||
|         if not self.copyright: | ||||
|             if not albumAPI_gw: | ||||
|                 logger.info(f"[{self.mainArtist['name']} - {self.title}] Getting more album infos") | ||||
|                 albumAPI_gw = dz.get_album_gw(self.album['id']) | ||||
|             self.copyright = albumAPI_gw['COPYRIGHT'] | ||||
|  | ||||
|     # Removes featuring from the title | ||||
|     def getCleanTitle(self): | ||||
|         return removeFeatures(self.title) | ||||
|  | ||||
|     # Removes featuring from the album name | ||||
|     def getCleanAlbumTitle(self): | ||||
|         return removeFeatures(self.album['title']) | ||||
|  | ||||
|     def getFeatTitle(self): | ||||
|         if self.featArtistsString and not "(feat." in self.title.lower(): | ||||
|             return self.title + " ({})".format(self.featArtistsString) | ||||
|         return self.title | ||||
| @ -11,14 +11,12 @@ if getenv("APPDATA"): | ||||
| elif sys.platform.startswith('darwin'): | ||||
|     userdata = homedata + '/Library/Application Support/deemix/' | ||||
| elif getenv("XDG_CONFIG_HOME"): | ||||
|     userdata = getenv("XDG_CONFIG_HOME") + '/deemix/'; | ||||
|     userdata = getenv("XDG_CONFIG_HOME") + '/deemix/' | ||||
| else: | ||||
|     userdata = homedata + '/.config/deemix/'; | ||||
|  | ||||
|     userdata = homedata + '/.config/deemix/' | ||||
|  | ||||
| def getHomeFolder(): | ||||
|     return homedata | ||||
|  | ||||
|  | ||||
| def getConfigFolder(): | ||||
|     return userdata | ||||
|  | ||||
| @ -34,6 +34,30 @@ def changeCase(str, type): | ||||
|         return str | ||||
|  | ||||
|  | ||||
| def removeFeatures(title): | ||||
|     clean = title | ||||
|     if "(feat." in clean.lower(): | ||||
|         pos = clean.lower().find("(feat.") | ||||
|         tempTrack = clean[:pos] | ||||
|         if ")" in clean: | ||||
|             tempTrack += clean[clean.find(")", pos + 1) + 1:] | ||||
|         clean = tempTrack.strip() | ||||
|     return clean | ||||
|  | ||||
|  | ||||
| def andCommaConcat(lst): | ||||
|     tot = len(lst) | ||||
|     result = "" | ||||
|     for i, art in enumerate(lst): | ||||
|         result += art | ||||
|         if tot != i + 1: | ||||
|             if tot - 1 == i + 1: | ||||
|                 result += " & " | ||||
|             else: | ||||
|                 result += ", " | ||||
|     return result | ||||
|  | ||||
|  | ||||
| def getIDFromLink(link, type): | ||||
|     if '?' in link: | ||||
|         link = link[:link.find('?')] | ||||
| @ -94,12 +118,3 @@ def uniqueArray(arr): | ||||
|             if iPrinc!=iRest and namePrinc.lower() in nRest.lower(): | ||||
|                 del arr[iRest] | ||||
|     return arr | ||||
|  | ||||
|  | ||||
| def isValidLink(text): | ||||
|     if text.lower().startswith("http"): | ||||
|         if "deezer.com" in text.lower() or "open.spotify.com" in text.lower(): | ||||
|             return True | ||||
|     elif text.lower().startswith("spotify:"): | ||||
|         return True | ||||
|     return False | ||||
|  | ||||
| @ -94,10 +94,10 @@ def generateFilepath(track, trackAPI, settings): | ||||
|                 'savePlaylistAsCompilation']) or | ||||
|             (settings['createArtistFolder'] and '_EXTRA_PLAYLIST' in trackAPI and settings['createStructurePlaylist']) | ||||
|     ): | ||||
|         if (int(track['id']) < 0 and not 'mainArtist' in track['album']): | ||||
|             track['album']['mainArtist'] = track['mainArtist'] | ||||
|         if (int(track.id) < 0 and not 'mainArtist' in track.album): | ||||
|             track.album['mainArtist'] = track.mainArtist | ||||
|         filepath += antiDot( | ||||
|             settingsRegexArtist(settings['artistNameTemplate'], track['album']['mainArtist'], settings)) + pathSep | ||||
|             settingsRegexArtist(settings['artistNameTemplate'], track.album['mainArtist'], settings)) + pathSep | ||||
|         artistPath = filepath | ||||
|  | ||||
|     if (settings['createAlbumFolder'] and | ||||
| @ -107,7 +107,7 @@ def generateFilepath(track, trackAPI, settings): | ||||
|                      '_EXTRA_PLAYLIST' in trackAPI and settings['createStructurePlaylist'])) | ||||
|     ): | ||||
|         filepath += antiDot( | ||||
|             settingsRegexAlbum(settings['albumNameTemplate'], track['album'], settings, | ||||
|             settingsRegexAlbum(settings['albumNameTemplate'], track.album, settings, | ||||
|                 trackAPI['_EXTRA_PLAYLIST'] if'_EXTRA_PLAYLIST' in trackAPI else None)) + pathSep | ||||
|         coverPath = filepath | ||||
|  | ||||
| @ -115,55 +115,55 @@ def generateFilepath(track, trackAPI, settings): | ||||
|         extrasPath = filepath | ||||
|  | ||||
|     if ( | ||||
|             int(track['album']['discTotal']) > 1 and ( | ||||
|             int(track.album['discTotal']) > 1 and ( | ||||
|             (settings['createAlbumFolder'] and settings['createCDFolder']) and | ||||
|             (not 'SINGLE_TRACK' in trackAPI or ('SINGLE_TRACK' in trackAPI and settings['createSingleFolder'])) and | ||||
|             (not '_EXTRA_PLAYLIST' in trackAPI or ( | ||||
|                     '_EXTRA_PLAYLIST' in trackAPI and settings['tags']['savePlaylistAsCompilation']) or ( | ||||
|                      '_EXTRA_PLAYLIST' in trackAPI and settings['createStructurePlaylist'])) | ||||
|     )): | ||||
|         filepath += 'CD' + str(track['discNumber']) + pathSep | ||||
|         filepath += 'CD' + str(track.discNumber) + pathSep | ||||
|  | ||||
|     return (filepath, artistPath, coverPath, extrasPath) | ||||
|  | ||||
|  | ||||
| def settingsRegex(filename, track, settings, playlist=None): | ||||
|     filename = filename.replace("%title%", fixName(track['title'], settings['illegalCharacterReplacer'])) | ||||
|     filename = filename.replace("%artist%", fixName(track['mainArtist']['name'], settings['illegalCharacterReplacer'])) | ||||
|     filename = filename.replace("%artists%", fixName(track['commaArtistsString'], settings['illegalCharacterReplacer'])) | ||||
|     filename = filename.replace("%allartists%", fixName(track['artistsString'], settings['illegalCharacterReplacer'])) | ||||
|     filename = filename.replace("%mainartists%", fixName(track['mainArtistsString'], settings['illegalCharacterReplacer'])) | ||||
|     filename = filename.replace("%featartists%", fixName('('+track['featArtistsString']+')', settings['illegalCharacterReplacer']) if 'featArtistsString' in track else "") | ||||
|     filename = filename.replace("%album%", fixName(track['album']['title'], settings['illegalCharacterReplacer'])) | ||||
|     filename = filename.replace("%title%", fixName(track.title, settings['illegalCharacterReplacer'])) | ||||
|     filename = filename.replace("%artist%", fixName(track.mainArtist['name'], settings['illegalCharacterReplacer'])) | ||||
|     filename = filename.replace("%artists%", fixName(", ".join(track.artists), settings['illegalCharacterReplacer'])) | ||||
|     filename = filename.replace("%allartists%", fixName(track.artistsString, settings['illegalCharacterReplacer'])) | ||||
|     filename = filename.replace("%mainartists%", fixName(track.mainArtistsString, settings['illegalCharacterReplacer'])) | ||||
|     filename = filename.replace("%featartists%", fixName('('+track.featArtistsString+')', settings['illegalCharacterReplacer']) if track.featArtistsString else "") | ||||
|     filename = filename.replace("%album%", fixName(track.album['title'], settings['illegalCharacterReplacer'])) | ||||
|     filename = filename.replace("%albumartist%", | ||||
|                                 fixName(track['album']['mainArtist']['name'], settings['illegalCharacterReplacer'])) | ||||
|     filename = filename.replace("%tracknumber%", pad(track['trackNumber'], track['album']['trackTotal'] if int( | ||||
|                                 fixName(track.album['mainArtist']['name'], settings['illegalCharacterReplacer'])) | ||||
|     filename = filename.replace("%tracknumber%", pad(track.trackNumber, track.album['trackTotal'] if int( | ||||
|         settings['paddingSize']) == 0 else 10 ** (int(settings['paddingSize']) - 1), settings['padTracks'])) | ||||
|     filename = filename.replace("%tracktotal%", str(track['album']['trackTotal'])) | ||||
|     filename = filename.replace("%discnumber%", str(track['discNumber'])) | ||||
|     filename = filename.replace("%disctotal%", str(track['album']['discTotal'])) | ||||
|     if len(track['album']['genre']) > 0: | ||||
|     filename = filename.replace("%tracktotal%", str(track.album['trackTotal'])) | ||||
|     filename = filename.replace("%discnumber%", str(track.discNumber)) | ||||
|     filename = filename.replace("%disctotal%", str(track.album['discTotal'])) | ||||
|     if len(track.album['genre']) > 0: | ||||
|         filename = filename.replace("%genre%", | ||||
|                                     fixName(track['album']['genre'][0], settings['illegalCharacterReplacer'])) | ||||
|                                     fixName(track.album['genre'][0], settings['illegalCharacterReplacer'])) | ||||
|     else: | ||||
|         filename = filename.replace("%genre%", "Unknown") | ||||
|     filename = filename.replace("%year%", str(track['date']['year'])) | ||||
|     filename = filename.replace("%date%", track['dateString']) | ||||
|     filename = filename.replace("%bpm%", str(track['bpm'])) | ||||
|     filename = filename.replace("%label%", fixName(track['album']['label'], settings['illegalCharacterReplacer'])) | ||||
|     filename = filename.replace("%isrc%", track['ISRC']) | ||||
|     filename = filename.replace("%upc%", track['album']['barcode']) | ||||
|     filename = filename.replace("%explicit%", "(Explicit)" if track['explicit'] else "") | ||||
|     filename = filename.replace("%year%", str(track.date['year'])) | ||||
|     filename = filename.replace("%date%", track.dateString) | ||||
|     filename = filename.replace("%bpm%", str(track.bpm)) | ||||
|     filename = filename.replace("%label%", fixName(track.album['label'], settings['illegalCharacterReplacer'])) | ||||
|     filename = filename.replace("%isrc%", track.ISRC) | ||||
|     filename = filename.replace("%upc%", track.album['barcode']) | ||||
|     filename = filename.replace("%explicit%", "(Explicit)" if track.explicit else "") | ||||
|  | ||||
|     filename = filename.replace("%track_id%", str(track['id'])) | ||||
|     filename = filename.replace("%album_id%", str(track['album']['id'])) | ||||
|     filename = filename.replace("%artist_id%", str(track['mainArtist']['id'])) | ||||
|     filename = filename.replace("%track_id%", str(track.id)) | ||||
|     filename = filename.replace("%album_id%", str(track.album['id'])) | ||||
|     filename = filename.replace("%artist_id%", str(track.mainArtist['id'])) | ||||
|     if playlist: | ||||
|         filename = filename.replace("%playlist_id%", str(playlist['id'])) | ||||
|         filename = filename.replace("%position%", pad(track['position'], playlist['nb_tracks'] if int( | ||||
|         filename = filename.replace("%position%", pad(track.position, playlist['nb_tracks'] if int( | ||||
|             settings['paddingSize']) == 0 else 10 ** (int(settings['paddingSize']) - 1), settings['padTracks'])) | ||||
|     else: | ||||
|         filename = filename.replace("%position%", pad(track['trackNumber'], track['album']['trackTotal'] if int( | ||||
|         filename = filename.replace("%position%", pad(track.trackNumber, track.album['trackTotal'] if int( | ||||
|             settings['paddingSize']) == 0 else 10 ** (int(settings['paddingSize']) - 1), settings['padTracks'])) | ||||
|     filename = filename.replace('\\', pathSep).replace('/', pathSep) | ||||
|     return antiDot(fixLongName(filename)) | ||||
| @ -218,11 +218,11 @@ def settingsRegexPlaylist(foldername, playlist, settings): | ||||
|     return antiDot(fixLongName(foldername)) | ||||
|  | ||||
| def settingsRegexPlaylistFile(foldername, queueItem, settings): | ||||
|     foldername = foldername.replace("%title%", fixName(queueItem['title'], settings['illegalCharacterReplacer'])) | ||||
|     foldername = foldername.replace("%artist%", fixName(queueItem['artist'], settings['illegalCharacterReplacer'])) | ||||
|     foldername = foldername.replace("%size%", str(queueItem['size'])) | ||||
|     foldername = foldername.replace("%type%", fixName(queueItem['type'], settings['illegalCharacterReplacer'])) | ||||
|     foldername = foldername.replace("%id%", fixName(queueItem['id'], settings['illegalCharacterReplacer'])) | ||||
|     foldername = foldername.replace("%bitrate%", bitrateLabels[int(queueItem['bitrate'])]) | ||||
|     foldername = foldername.replace("%title%", fixName(queueItem.title, settings['illegalCharacterReplacer'])) | ||||
|     foldername = foldername.replace("%artist%", fixName(queueItem.artist, settings['illegalCharacterReplacer'])) | ||||
|     foldername = foldername.replace("%size%", str(queueItem.size)) | ||||
|     foldername = foldername.replace("%type%", fixName(queueItem.type, settings['illegalCharacterReplacer'])) | ||||
|     foldername = foldername.replace("%id%", fixName(queueItem.id, settings['illegalCharacterReplacer'])) | ||||
|     foldername = foldername.replace("%bitrate%", bitrateLabels[int(queueItem.bitrate)]) | ||||
|     foldername = foldername.replace('\\', pathSep).replace('/', pathSep).replace(pathSep, settings['illegalCharacterReplacer']) | ||||
|     return antiDot(fixLongName(foldername)) | ||||
|  | ||||
| @ -3,8 +3,9 @@ from mutagen.flac import FLAC, Picture | ||||
| from mutagen.id3 import ID3, ID3NoHeaderError, TXXX, TIT2, TPE1, TALB, TPE2, TRCK, TPOS, TCON, TYER, TDAT, TLEN, TBPM, \ | ||||
|     TPUB, TSRC, USLT, APIC, IPLS, TCOM, TCOP, TCMP | ||||
|  | ||||
|  | ||||
| # Adds tags to a MP3 file | ||||
| def tagID3(stream, track, save): | ||||
|     # Delete exsisting tags | ||||
|     try: | ||||
|         tag = ID3(stream) | ||||
|         tag.delete() | ||||
| @ -12,141 +13,156 @@ def tagID3(stream, track, save): | ||||
|         tag = ID3() | ||||
|  | ||||
|     if save['title']: | ||||
|         tag.add(TIT2(text=track['title'])) | ||||
|     if save['artist'] and len(track['artists']): | ||||
|         tag.add(TIT2(text=track.title)) | ||||
|  | ||||
|     if save['artist'] and len(track.artists): | ||||
|         if save['multiArtistSeparator'] != "default": | ||||
|             if save['multiArtistSeparator'] == "nothing": | ||||
|                 tag.add(TPE1(text=track['mainArtist']['name'])) | ||||
|                 tag.add(TPE1(text=track.mainArtist['name'])) | ||||
|             else: | ||||
|                 tag.add(TPE1(text=track['artistsString'])) | ||||
|             tag.add(TXXX(desc="ARTISTS", text=track['artists'])) | ||||
|                 tag.add(TPE1(text=track.artistsString)) | ||||
|             tag.add(TXXX(desc="ARTISTS", text=track.artists)) | ||||
|         else: | ||||
|             tag.add(TPE1(text=track['artists'])) | ||||
|             tag.add(TPE1(text=track.artists)) | ||||
|  | ||||
|     if save['album']: | ||||
|         tag.add(TALB(text=track['album']['title'])) | ||||
|     if save['albumArtist'] and len(track['album']['artists']): | ||||
|         if save['singleAlbumArtist'] and track['album']['mainArtist']['save']: | ||||
|             tag.add(TPE2(text=track['album']['mainArtist']['name'])) | ||||
|         tag.add(TALB(text=track.album['title'])) | ||||
|  | ||||
|     if save['albumArtist'] and len(track.album['artists']): | ||||
|         if save['singleAlbumArtist'] and track.album['mainArtist']['save']: | ||||
|             tag.add(TPE2(text=track.album['mainArtist']['name'])) | ||||
|         else: | ||||
|             tag.add(TPE2(text=track['album']['artists'])) | ||||
|             tag.add(TPE2(text=track.album['artists'])) | ||||
|  | ||||
|     if save['trackNumber']: | ||||
|         tag.add(TRCK( | ||||
|             text=str(track['trackNumber']) + ("/" + str(track['album']['trackTotal']) if save['trackTotal'] else ""))) | ||||
|             text=str(track.trackNumber) + ("/" + str(track.album['trackTotal']) if save['trackTotal'] else ""))) | ||||
|     if save['discNumber']: | ||||
|         tag.add( | ||||
|             TPOS(text=str(track['discNumber']) + ("/" + str(track['album']['discTotal']) if save['discTotal'] else ""))) | ||||
|             TPOS(text=str(track.discNumber) + ("/" + str(track.album['discTotal']) if save['discTotal'] else ""))) | ||||
|     if save['genre']: | ||||
|         tag.add(TCON(text=track['album']['genre'])) | ||||
|         tag.add(TCON(text=track.album['genre'])) | ||||
|     if save['year']: | ||||
|         tag.add(TYER(text=str(track['date']['year']))) | ||||
|         tag.add(TYER(text=str(track.date['year']))) | ||||
|     if save['date']: | ||||
|         tag.add(TDAT(text=str(track['date']['month']) + str(track['date']['day']))) | ||||
|         tag.add(TDAT(text=str(track.date['month']) + str(track.date['day']))) | ||||
|     if save['length']: | ||||
|         tag.add(TLEN(text=str(int(track['duration'])*1000))) | ||||
|         tag.add(TLEN(text=str(int(track.duration)*1000))) | ||||
|     if save['bpm']: | ||||
|         tag.add(TBPM(text=str(track['bpm']))) | ||||
|         tag.add(TBPM(text=str(track.bpm))) | ||||
|     if save['label']: | ||||
|         tag.add(TPUB(text=track['album']['label'])) | ||||
|         tag.add(TPUB(text=track.album['label'])) | ||||
|     if save['isrc']: | ||||
|         tag.add(TSRC(text=track['ISRC'])) | ||||
|         tag.add(TSRC(text=track.ISRC)) | ||||
|     if save['barcode']: | ||||
|         tag.add(TXXX(desc="BARCODE", text=track['album']['barcode'])) | ||||
|         tag.add(TXXX(desc="BARCODE", text=track.album['barcode'])) | ||||
|     if save['explicit']: | ||||
|         tag.add(TXXX(desc="ITUNESADVISORY", text="1" if track['explicit'] else "0")) | ||||
|         tag.add(TXXX(desc="ITUNESADVISORY", text="1" if track.explicit else "0")) | ||||
|     if save['replayGain']: | ||||
|         tag.add(TXXX(desc="REPLAYGAIN_TRACK_GAIN", text=track['replayGain'])) | ||||
|     if 'unsync' in track['lyrics'] and save['lyrics']: | ||||
|         tag.add(USLT(text=track['lyrics']['unsync'])) | ||||
|         tag.add(TXXX(desc="REPLAYGAIN_TRACK_GAIN", text=track.replayGain)) | ||||
|     if 'unsync' in track.lyrics and save['lyrics']: | ||||
|         tag.add(USLT(text=track.lyrics['unsync'])) | ||||
|  | ||||
|     involved_people = [] | ||||
|     for role in track['contributors']: | ||||
|     for role in track.contributors: | ||||
|         if role in ['author', 'engineer', 'mixer', 'producer', 'writer']: | ||||
|             for person in track['contributors'][role]: | ||||
|             for person in track.contributors[role]: | ||||
|                 involved_people.append([role, person]) | ||||
|         elif role == 'composer' and save['composer']: | ||||
|             tag.add(TCOM(text=track['contributors']['composer'])) | ||||
|             tag.add(TCOM(text=track.contributors['composer'])) | ||||
|     if len(involved_people) > 0 and save['involvedPeople']: | ||||
|         tag.add(IPLS(people=involved_people)) | ||||
|  | ||||
|     if save['copyright']: | ||||
|         tag.add(TCOP(text=track['copyright'])) | ||||
|     if save['savePlaylistAsCompilation'] and "playlist" in track: | ||||
|         tag.add(TCOP(text=track.copyright)) | ||||
|     if save['savePlaylistAsCompilation'] and track.playlist: | ||||
|         tag.add(TCMP(text="1")) | ||||
|     if save['cover'] and track['album']['picPath']: | ||||
|         with open(track['album']['picPath'], 'rb') as f: | ||||
|  | ||||
|     if save['cover'] and track.album['picPath']: | ||||
|         with open(track.album['picPath'], 'rb') as f: | ||||
|             tag.add( | ||||
|                 APIC(3, 'image/jpeg' if track['album']['picPath'].endswith('jpg') else 'image/png', 3, desc='cover', data=f.read())) | ||||
|                 APIC(3, 'image/jpeg' if track.album['picPath'].endswith('jpg') else 'image/png', 3, desc='cover', data=f.read())) | ||||
|  | ||||
|     tag.save(stream, v1=2 if save['saveID3v1'] else 0, v2_version=3, | ||||
|              v23_sep=None if save['useNullSeparator'] else '/') | ||||
|  | ||||
|  | ||||
| # Adds tags to a FLAC file | ||||
| def tagFLAC(stream, track, save): | ||||
|     # Delete exsisting tags | ||||
|     tag = FLAC(stream) | ||||
|     tag.delete() | ||||
|     tag.clear_pictures() | ||||
|  | ||||
|     if save['title']: | ||||
|         tag["TITLE"] = track['title'] | ||||
|     if save['artist'] and len(track['artists']): | ||||
|         tag["TITLE"] = track.title | ||||
|  | ||||
|     if save['artist'] and len(track.artists): | ||||
|         if save['multiArtistSeparator'] != "default": | ||||
|             if save['multiArtistSeparator'] == "nothing": | ||||
|                 tag["ARTIST"] = track['mainArtist']['name'] | ||||
|                 tag["ARTIST"] = track.mainArtist['name'] | ||||
|             else: | ||||
|                 tag["ARTIST"] = track['artistsString'] | ||||
|             tag["ARTISTS"] = track['artists'] | ||||
|                 tag["ARTIST"] = track.artistsString | ||||
|             tag["ARTISTS"] = track.artists | ||||
|         else: | ||||
|             tag["ARTIST"] = track['artists'] | ||||
|             tag["ARTIST"] = track.artists | ||||
|  | ||||
|     if save['album']: | ||||
|         tag["ALBUM"] = track['album']['title'] | ||||
|     if save['albumArtist'] and len(track['album']['artists']): | ||||
|         tag["ALBUM"] = track.album['title'] | ||||
|  | ||||
|     if save['albumArtist'] and len(track.album['artists']): | ||||
|         if save['singleAlbumArtist']: | ||||
|             tag["ALBUMARTIST"] = track['album']['mainArtist']['name'] | ||||
|             tag["ALBUMARTIST"] = track.album['mainArtist']['name'] | ||||
|         else: | ||||
|             tag["ALBUMARTIST"] = track['album']['artists'] | ||||
|             tag["ALBUMARTIST"] = track.album['artists'] | ||||
|  | ||||
|     if save['trackNumber']: | ||||
|         tag["TRACKNUMBER"] = str(track['trackNumber']) | ||||
|         tag["TRACKNUMBER"] = str(track.trackNumber) | ||||
|     if save['trackTotal']: | ||||
|         tag["TRACKTOTAL"] = str(track['album']['trackTotal']) | ||||
|         tag["TRACKTOTAL"] = str(track.album['trackTotal']) | ||||
|     if save['discNumber']: | ||||
|         tag["DISCNUMBER"] = str(track['discNumber']) | ||||
|         tag["DISCNUMBER"] = str(track.discNumber) | ||||
|     if save['discTotal']: | ||||
|         tag["DISCTOTAL"] = str(track['album']['discTotal']) | ||||
|         tag["DISCTOTAL"] = str(track.album['discTotal']) | ||||
|     if save['genre']: | ||||
|         tag["GENRE"] = track['album']['genre'] | ||||
|         tag["GENRE"] = track.album['genre'] | ||||
|     if save['date']: | ||||
|         tag["DATE"] = track['dateString'] | ||||
|         tag["DATE"] = track.dateString | ||||
|     elif save['year']: | ||||
|         tag["YEAR"] = str(track['date']['year']) | ||||
|         tag["YEAR"] = str(track.date['year']) | ||||
|     if save['length']: | ||||
|         tag["LENGTH"] = str(track['duration']) | ||||
|         tag["LENGTH"] = str(track.duration) | ||||
|     if save['bpm']: | ||||
|         tag["BPM"] = str(track['bpm']) | ||||
|         tag["BPM"] = str(track.bpm) | ||||
|     if save['label']: | ||||
|         tag["PUBLISHER"] = track['album']['label'] | ||||
|         tag["PUBLISHER"] = track.album['label'] | ||||
|     if save['isrc']: | ||||
|         tag["ISRC"] = track['ISRC'] | ||||
|         tag["ISRC"] = track.ISRC | ||||
|     if save['barcode']: | ||||
|         tag["BARCODE"] = track['album']['barcode'] | ||||
|         tag["BARCODE"] = track.album['barcode'] | ||||
|     if save['explicit']: | ||||
|         tag["ITUNESADVISORY"] = "1" if track['explicit'] else "0" | ||||
|         tag["ITUNESADVISORY"] = "1" if track.explicit else "0" | ||||
|     if save['replayGain']: | ||||
|         tag["REPLAYGAIN_TRACK_GAIN"] = track['replayGain'] | ||||
|     if 'unsync' in track['lyrics'] and save['lyrics']: | ||||
|         tag["LYRICS"] = track['lyrics']['unsync'] | ||||
|     for role in track['contributors']: | ||||
|         tag["REPLAYGAIN_TRACK_GAIN"] = track.replayGain | ||||
|     if 'unsync' in track.lyrics and save['lyrics']: | ||||
|         tag["LYRICS"] = track.lyrics['unsync'] | ||||
|  | ||||
|     for role in track.contributors: | ||||
|         if role in ['author', 'engineer', 'mixer', 'producer', 'writer', 'composer']: | ||||
|             if save['involvedPeople'] and role != 'composer' or role == 'composer' and save['composer']: | ||||
|                 tag[role] = track['contributors'][role] | ||||
|                 tag[role] = track.contributors[role] | ||||
|         elif role == 'musicpublisher' and save['involvedPeople']: | ||||
|             tag["ORGANIZATION"] = track['contributors']['musicpublisher'] | ||||
|             tag["ORGANIZATION"] = track.contributors['musicpublisher'] | ||||
|  | ||||
|     if save['copyright']: | ||||
|         tag["COPYRIGHT"] = track['copyright'] | ||||
|     if save['savePlaylistAsCompilation'] and "playlist" in track: | ||||
|         tag["COPYRIGHT"] = track.copyright | ||||
|     if save['savePlaylistAsCompilation'] and track.playlist: | ||||
|         tag["COMPILATION"] = "1" | ||||
|  | ||||
|     if save['cover'] and track['album']['picPath']: | ||||
|     if save['cover'] and track.album['picPath']: | ||||
|         image = Picture() | ||||
|         image.type = 3 | ||||
|         image.mime = 'image/jpeg' if track['album']['picPath'].endswith('jpg') else 'image/png' | ||||
|         with open(track['album']['picPath'], 'rb') as f: | ||||
|         image.mime = 'image/jpeg' if track.album['picPath'].endswith('jpg') else 'image/png' | ||||
|         with open(track.album['picPath'], 'rb') as f: | ||||
|             image.data = f.read() | ||||
|         tag.add_picture(image) | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 RemixDev
					RemixDev