from mutagen.easyid3 import EasyID3 from mutagen.mp3 import MP3 import mutagen.flac import mutagen.wave import sqlite3 as sql import requests, ast, time, math, argparse, dotenv, os loading = ["-","\\","|","/"] parser=argparse.ArgumentParser(description="Options for the generation of the song database") # parser.add_argument('-k','--apikey', help='String: LastFM api key', default="") parser.add_argument('-m', '--mode', help='new/update: Remake database or update current', default= "update") parser.add_argument('-a', '--art', help="True/False: Add art to the database using LastFm (takes minimum 0.25s per song)", default="True") parser.add_argument('-d','--directory',help="Directory of the song files", default="./sound/") args = parser.parse_args() dotenv.load_dotenv() apikeylastfm = os.getenv("API_KEY") soundLocation = os.getenv("DIRECTORY") # apikeylastfm = args.apikey if args.directory[-1] == "/" or args.directory[-1] == "\\": soundLocation = args.directory elif "/" in args.directory: soundLocation = args.directory + "/" else: soundLocation = args.directory + "\\" songFiles = os.listdir(soundLocation) fileOfDB = sql.connect("songDatabase.db") songDatabase = fileOfDB.cursor() # setting song directory songDatabase.execute("CREATE TABLE IF NOT EXISTS meta (id TEXT PRIMARY KEY, data TEXT);") try: songDatabase.execute("INSERT INTO meta (id, data) VALUES (?,?)",("songDirectory",soundLocation)) except: songDatabase.execute("UPDATE meta SET data = ? WHERE id = 'songDirectory'", (soundLocation,)) if args.mode.lower() == "update": #Create if not exists songDatabase.execute("CREATE TABLE IF NOT EXISTS songs (filename TEXT PRIMARY KEY, title TEXT, artist TEXT, art TEXT, length INTEGER, lossless INTEGER);") songDatabase.execute("SELECT filename FROM songs;") dBfilelist = songDatabase.fetchall() dBfilelistSet = set() for i in dBfilelist: dBfilelistSet.add(i[0]) # Delete nonexistant files deleteySongs = list(dBfilelistSet - set(songFiles)) songDatabase.executemany("DELETE FROM songs WHERE filename = ?", [(item,) for item in deleteySongs]) # in this line it turns the list of strings into a list of tuples of strings print("Deleted: " + ", ".join(deleteySongs)+ " from database") # only include new files in list to be used songFiles = list(set(songFiles) - dBfilelistSet) print("new songs: " + ", ".join(songFiles)) elif args.mode.lower()=="new": songDatabase.execute("DROP TABLE IF EXISTS songs;") songDatabase.execute("CREATE TABLE songs (filename TEXT PRIMARY KEY, title TEXT, artist TEXT, art TEXT, length INTEGER, lossless INTEGER);") else: raise ValueError("Must be \"new\" or \"update\"") if args.art.lower() == "true" and not(apikeylastfm == ""): eta = len(songFiles)*0.25 if eta > 60: print(f"ETA {eta/60:.2f} minutes") else: print(f"ETA {eta} seconds") # will be used soon validFormats = ["mp3","flac","wav"] for i in songFiles: # songFiles is the list of filenames, so i is the filename of each song global song filenamesplit = i.split(".") extension = filenamesplit[len(filenamesplit)-1] lossless = 0 # sqlite doesn't have booleans. what is this, C? if not(extension.lower() in validFormats): # skip any non music files (like directories or cover art) continue try: # get the metadata if(extension.lower() == "mp3"): song = EasyID3(soundLocation+i) elif(extension.lower() == "flac"): song = mutagen.flac.FLAC(soundLocation+i) lossless = 1 elif(extension.lower() in ["wav","wave"]): # Im actually pretty sure waves can't have metadata, but whatevz song = mutagen.wave.WAVE(soundLocation+i) lossless = 1 title = song['title'][0] artist = song['artist'][0] except: if "_" in i: # if metadata is missing, try to use file name following "title_artist.mp3" song = i.split("_") title = song[0] artist = song[1].split(".")[0] elif "-" in i: # if there's no underscore, try "artist - title.mp3" song = i.split("-") title = song[1].split(".")[0] artist = song[0] title = title.strip() artist = artist.strip() else: #if the file is not formatted with an underscore or hyphen, the title is the file name title = i artist = None if args.art.lower() == "true" and not(apikeylastfm == "") and artist: # and artist just means anything that only has the x.mp3 title won't bother to check since it'll never exist on last fm try: # get the images from last fm, try 2 different sizes image = ast.literal_eval(requests.post(url="http://ws.audioscrobbler.com/2.0/?method=track.getInfo&api_key="+apikeylastfm+"&artist="+artist+"&track="+title+"&format=json").text)["track"]["album"]["image"][2]["#text"] if image == "": image = ast.literal_eval(requests.post(url="http://ws.audioscrobbler.com/2.0/?method=track.getInfo&api_key="+apikeylastfm+"&artist="+artist+"&track="+title+"&format=json").text)["track"]["album"]["image"][1]["#text"] if image == "": image = None time.sleep(0.01) except: image=None else: image=None try: if extension.lower() in ['flac','wave','wav']: length = math.ceil(song.info.length) elif extension.lower() == "mp3": # for some reason ID3 and mutagen.mp3 get different info # artist and title are in id3() and length is in mp3() # I dunno why length = MP3(soundLocation+i).info.length except: length = 0 if len(songFiles) != 1: index = (songFiles.index(i))%4 print("\r" + str(loading[index] + str(math.floor((songFiles.index(i)/(len(songFiles)-1))*100))+ "%"), end='', flush=True) # each "song" is stored as a SQLite entry following the format seen below songDatabase.execute(f"INSERT INTO songs (filename, title, artist, art, length, lossless) VALUES (?,?,?,?,?,?)",(i,title,artist,image,length,lossless)) songDatabase.execute("DROP TABLE IF EXISTS virtualSongs;") songDatabase.execute("CREATE VIRTUAL TABLE virtualSongs USING fts5(filename, title, artist, art, length, lossless);") songDatabase.execute("INSERT INTO virtualSongs SELECT * FROM songs;") fileOfDB.commit() fileOfDB.close()