| 
					
				 | 
			
			
				@@ -0,0 +1,236 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+#!/usr/bin/python3.8 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import sys 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import getopt 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import json 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import pymongo 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+from bson.objectid import ObjectId 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import youtube_dl 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import ffmpy 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import os 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import eyed3 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+from urllib import request 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import unicodedata 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import re 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+from PIL import Image 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import PIL 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+from alive_progress import alive_bar 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+class bcolors: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    HEADER = "\033[95m" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    OKBLUE = "\033[94m" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    OKCYAN = "\033[96m" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    OKGREEN = "\033[92m" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    WARNING = "\033[93m" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    FAIL = "\033[91m" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    ENDC = "\033[0m" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    BOLD = "\033[1m" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    UNDERLINE = "\033[4m" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+def keys_exists(element, *keys): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if not isinstance(element, dict): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        raise AttributeError("keys_exists() expects dict as first argument.") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if len(keys) == 0: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        raise AttributeError("keys_exists() expects at least two arguments, one given.") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    _element = element 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    for key in keys: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        try: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            _element = _element[key] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        except: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    return True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+def usage(e=False): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    print(f"""{bcolors.HEADER}Usage{bcolors.ENDC} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    -h,--help | Help 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    -p playlistId,--playlist-id=playlistId | Musare Playlist ID 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    -P playlistFile,--playlist-file=playlistFile | Musare Playlist JSON file 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    -o outputPath,--output=outputPath | Output directory 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    -f outputFormat,--format=outputFormat | Format of download, audio or video 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    -i downloadImages,--images=downloadImages | Whether to download images, true or false 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    --max-songs=maxSongs | Maximum number of songs to download 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    """) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if e: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        sys.exit(2) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+def slugify(value, allow_unicode=False): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    value = unicodedata.normalize("NFKD", str(value)).encode("ascii", "ignore").decode("ascii") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    value = re.sub(r"[^(?!+)\w\s-]", "", value) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    return re.sub(r"[\s]+", "_", value).strip("-_") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+playlistId= None 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+playlistFile= None 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+outputDir = os.path.join(os.getcwd(), "") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+outputFormat = None 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+downloadImages = True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+maxSongs = 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+print(f"{bcolors.BOLD}{bcolors.OKCYAN}musare-dl{bcolors.ENDC}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+os.chdir(outputDir) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+try: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    opts, args = getopt.getopt(sys.argv[1:], "p:P:o:f:i:h", ["playlist-id=", "playlist-file=", "output=", "format=", "images=", "help", "max-songs="]) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+except getopt.GetoptError: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    usage(True) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+for opt, arg in opts: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if opt in ("-h", "--help"): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        usage(True) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    elif opt in ("-p", "--playlist-id"): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if len(arg) == 24: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            playlistId = arg 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            sys.exit(f"{bcolors.FAIL}Error: Invalid Musare Playlist ID{bcolors.ENDC}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    elif opt in ("-P", "--playlist-file"): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        playlistFile = arg 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if not os.path.exists(playlistFile): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            sys.exit(f"{bcolors.FAIL}Error: Musare Playlist File does not exist{bcolors.ENDC}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if os.path.isdir(playlistFile): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            sys.exit(f"{bcolors.FAIL}Error: Musare Playlist File is a directory{bcolors.ENDC}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    elif opt in ("-o", "--output"): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        outputDir = arg 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if os.path.exists(outputDir): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if not os.path.isdir(outputDir): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                sys.exit(f"{bcolors.FAIL}Error: Output path is not a directory{bcolors.ENDC}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            outputDir = os.path.join(outputDir, "") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            sys.exit(f"{bcolors.FAIL}Error: Output directory does not exist{bcolors.ENDC}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    elif opt in ("-f", "--format"): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        outputFormat = arg.lower() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if outputFormat != "audio" and outputFormat != "video": 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            sys.exit(f"{bcolors.FAIL}Error: Invalid format, audio or video only{bcolors.ENDC}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    elif opt in ("-i", "--images"): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if arg.lower() == "true": 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            downloadImages = True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        elif arg.lower() == "false": 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            downloadImages = False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            sys.exit(f"{bcolors.FAIL}Error: Invalid images, must be True or False{bcolors.ENDC}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    elif opt == "--max-songs": 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if arg.isdigit() == False: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            sys.exit(f"{bcolors.FAIL}Error: Invalid max-songs, must be int{bcolors.ENDC}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        maxSongs = int(arg) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        usage(True) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+if not playlistId and not playlistFile: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    print(f"{bcolors.FAIL}Error: Playlist ID or Playlist File need to be specified{bcolors.ENDC}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    usage(True) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+if not outputFormat: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    outputFormat = "audio" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+if playlistId and not playlistFile: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    try: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        mongo = pymongo.MongoClient("localhost", username="musare", password="musare", authSource="musare") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        mydb = mongo["musare"] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        songs = [] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        for playlist in mydb["playlists"].find({ "_id": ObjectId(playlistId) }, { "songs._id" }): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            for song in playlist["songs"]: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                songs.append(song["_id"]) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        songsCount = mydb["songs"].count_documents({ "_id": { "$in": songs } }) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        songs = mydb["songs"].find({ "_id": { "$in": songs } }) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    except: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        sys.exit(f"{bcolors.FAIL}Error: Could not load songs from Mongo{bcolors.ENDC}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+elif not playlistId and playlistFile: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    try: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        with open(playlistFile, "r") as playlistJson: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            songs = json.load(playlistJson) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        songs = songs["playlist"]["songs"] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        songsCount = len(songs) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    except: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        sys.exit(f"{bcolors.FAIL}Error: Could not load songs from playlist JSON file{bcolors.ENDC}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+if not os.access(outputDir, os.W_OK): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    sys.exit(f"{bcolors.FAIL}Error: Unable to write to output directory{bcolors.ENDC}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+if os.getcwd() != outputDir: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    os.chdir(outputDir) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+if downloadImages and not os.path.exists("images"): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    os.makedirs("images") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+i = 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+completeSongs = [] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+failedSongs = [] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+if maxSongs > 0 and songsCount > maxSongs: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    songsCount = maxSongs 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+with alive_bar(songsCount) as bar: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    for song in songs: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        i = i + 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if maxSongs != 0 and i > maxSongs: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            i = i - 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            break 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        try: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            bar.text(f"{bcolors.OKBLUE}Downloading ({song['_id']}) {','.join(song['artists'])} - {song['title']}..{bcolors.ENDC}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if outputFormat == "audio": 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                outputExtension = "mp3" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                ydl_opts = { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    "outtmpl": f"{song['_id']}.tmp", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    "format": "bestaudio[ext=m4a]", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    "quiet": True, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    "no_warnings": True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            elif outputFormat == "video": 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                outputExtension = "mp4" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                ydl_opts = { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    "outtmpl": f"{song['_id']}.tmp", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    "format": "best[height<=1080]/bestaudio", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    "quiet": True, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    "no_warnings": True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            ffmpegOpts = ["-hide_banner", "-loglevel", "error"] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if keys_exists(song, "duration") and keys_exists(song, "skipDuration"): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                ffmpegOpts.append("-t") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                ffmpegOpts.append(str(song["duration"])) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                ffmpegOpts.append("-ss") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                ffmpegOpts.append(str(song["skipDuration"])) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            with youtube_dl.YoutubeDL(ydl_opts) as ydl: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                ydl.download([f"https://www.youtube.com/watch?v={song['youtubeId']}"]) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            bar.text(f"{bcolors.OKBLUE}Converting ({song['_id']}) {','.join(song['artists'])} - {song['title']}..{bcolors.ENDC}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            ff = ffmpy.FFmpeg( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                inputs={ f"{song['_id']}.tmp": None }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                outputs={ f"{song['_id']}.{outputExtension}": ffmpegOpts } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            ff.run() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            os.remove(f"{song['_id']}.tmp") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if outputFormat == "audio": 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                track = eyed3.load(f"{song['_id']}.{outputExtension}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                track.tag.artist = ";".join(song["artists"]) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                track.tag.title = str(song["title"]) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if keys_exists(song, "discogs", "album", "title"): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    track.tag.album = str(song["discogs"]["album"]["title"]) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            fileName = slugify(f"{'+'.join(song['artists'])}-{song['title']}-{song['_id']}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            bar.text(f"{bcolors.OKBLUE}Downloading Images for ({song['_id']}) {','.join(song['artists'])} - {song['title']}..{bcolors.ENDC}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if downloadImages: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                try: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    imgRequest = request.Request( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        song["thumbnail"], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        headers={ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    img = Image.open(request.urlopen(imgRequest)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    img.save(f"images/{fileName}.jpg", "JPEG") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if outputFormat == "audio": 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        thumb = img.resize((32, 32), Image.ANTIALIAS) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        thumb.save(f"images/{fileName}.thumb.jpg", "JPEG") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        imgData = open(f"images/{fileName}.jpg", "rb").read() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        thumbData = open(f"images/{fileName}.thumb.jpg", "rb").read() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        track.tag.images.set(1, thumbData, "image/jpeg", u"icon") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        track.tag.images.set(3, imgData, "image/jpeg", u"cover") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                except: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    print(f"{bcolors.FAIL}Error downloading album art for ({song['_id']}) {','.join(song['artists'])} - {song['title']}, skipping.{bcolors.ENDC}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if outputFormat == "audio": 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                track.tag.save() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            os.rename(f"{song['_id']}.{outputExtension}", f"{fileName}.{outputExtension}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            completeSongs.append(str(song["_id"])) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            print(f"{bcolors.OKGREEN}Downloaded ({song['_id']}) {','.join(song['artists'])} - {song['title']}{bcolors.ENDC}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        except KeyboardInterrupt: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            print(f"{bcolors.FAIL}Cancelled downloads{bcolors.ENDC}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            break 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        except: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            failedSongs.append(str(song["_id"])) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            print(f"{bcolors.FAIL}Error downloading ({song['_id']}) {','.join(song['artists'])} - {song['title']}, skipping.{bcolors.ENDC}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        bar() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+if len(failedSongs) > 0: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    print(f"\n{bcolors.FAIL}Failed Songs ({len(failedSongs)}): {', '.join(failedSongs)}{bcolors.ENDC}") 
			 |