diff --git a/Client/scripts.js b/Client/scripts.js index f0fea6c..dd8870e 100644 --- a/Client/scripts.js +++ b/Client/scripts.js @@ -3,7 +3,8 @@ let ip; let alertTime = 2; let adminPass = ""; const ERR_NO_ADMIN = "401"; // gonna use this later to refactor -const VALID_FILE_EXT = ["mp3","flac","wav"] +const VALID_FILE_EXT = ["mp3","flac","wav"]; + async function alertText(text="Song Added!") { alertbox = document.getElementById("alert"); alertbox.innerHTML = text; @@ -17,6 +18,7 @@ async function alertText(text="Song Added!") { async function getFromServer(bodyInfo, source="",password=adminPass) { try{ if (bodyInfo != null) { + // the currently set password is always included in every request bodyInfo["password"] = password; } const response = await fetch("http://"+ip+"/"+source, { @@ -142,6 +144,7 @@ async function searchSongs(searchTerm){ document.getElementById("songlist").innerHTML = "

We might not have that one...

"; } } + function alertTimeEnter(e){ if (e.key == "Enter") { e.preventDefault(); @@ -178,6 +181,7 @@ function ipSetter(){ alertText("Your IP is now set to "+ipBox+" at port 19054 (Default)") } } + // anytime the server ip changes the qrcode should change to use it qrCodeGenerate() } diff --git a/Server/databaseGenerator.py b/Server/databaseGenerator.py index e8c1915..7c879cb 100644 --- a/Server/databaseGenerator.py +++ b/Server/databaseGenerator.py @@ -35,7 +35,7 @@ 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);") + 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() @@ -50,7 +50,7 @@ if args.mode.lower() == "update": 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);") + 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\"") @@ -65,37 +65,43 @@ if args.art.lower() == "true" and not(args.apikey == ""): validFormats = ["mp3","flac","wav"] for i in songFiles: + # songFiles is the list of filenames, so i is the filename of each song global song - extension = i.split(".") - extension = extension[len(extension)-1] + 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: + print(extension) + # get the metadata if(extension.lower() == "mp3"): - # get the metadata 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 + # 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 + # 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, the title is the file name + #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(args.apikey == ""): @@ -112,13 +118,19 @@ for i in songFiles: else: image=None try: - length = math.ceil(song.info.length) + 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 in the readME - songDatabase.execute(f"INSERT INTO songs (filename, title, artist, art, length) VALUES (?,?,?,?,?)",(i,title,artist,image,length)) + # 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)) fileOfDB.commit() diff --git a/Server/webbyBits.py b/Server/webbyBits.py index 07d0332..9e38d3b 100644 --- a/Server/webbyBits.py +++ b/Server/webbyBits.py @@ -40,11 +40,13 @@ elif "/" in soundLocation: else: soundLocation += "\\" #Create Virtual table for searching +#I'm not sure why i don't do this in the databaseGenerator, but it also takes like 3 seconds so i'm not messing with it rn songDatabase.execute("DROP TABLE virtualSongs;") -songDatabase.execute("CREATE VIRTUAL TABLE virtualSongs USING fts5(filename, title, artist, art, length);") +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() + #Initializing all the global stuff random.seed() global partyMode @@ -66,6 +68,7 @@ CORS(app) def queueSong(song): with playlistLock: playlist.append(song) + # this is a loop that plays the songs and checks for playlist changes, skips, ect. def playQueuedSongs(): global skipNow @@ -94,15 +97,11 @@ def playQueuedSongs(): songDatabase.execute("SELECT * FROM songs ORDER BY RANDOM() LIMIT 1;") result = songDatabase.fetchall() # adds the random songs for party mode - # the above 2 means this only applies if (a song is playing (or paused)) and the queue is empty + # the above 2 means this only applies if (a song is playing or paused) and (the queue is empty) playlist.append(result[0][0]) # check for new songs every second # I just didn't want to eat too much processing looping time.sleep(1) -# start the media player thread -queueThread = threading.Thread(target=playQueuedSongs) -queueThread.daemon = True -queueThread.start() @app.route("/controls", methods=['POST']) def playerControls(): @@ -112,13 +111,13 @@ def playerControls(): recieveData=request.get_json(force=True) if recieveData["control"] != None: if recieveData["control"] == "play-pause": - if ADMIN_PASS == recieveData['password'] or not(ADMIN_PASS) or controlPerms["PP"]: + if ADMIN_PASS == recieveData['password'] or controlPerms["PP"]: player.pause() return "200" else: return ERR_NO_ADMIN elif recieveData["control"] == "skip": - if ADMIN_PASS == recieveData['password'] or not(ADMIN_PASS) or controlPerms["SK"]: + if ADMIN_PASS == recieveData['password'] or controlPerms["SK"]: skipNow = True return "200" else: @@ -136,13 +135,13 @@ def settingsControl(): global player recieveData = request.get_json(force=True) if recieveData["setting"] == "volume": - if ADMIN_PASS == recieveData['password'] or not(ADMIN_PASS) or controlPerms["VOL"]: + if ADMIN_PASS == recieveData['password'] or controlPerms["VOL"]: volumePassed = player.audio_set_volume(int(recieveData["level"])) return {"volumePassed":volumePassed} else: return ERR_NO_ADMIN elif recieveData["setting"] == "partymode-toggle": - if ADMIN_PASS == recieveData['password'] or not(ADMIN_PASS) or controlPerms["PM"]: + if ADMIN_PASS == recieveData['password'] or controlPerms["PM"]: partyMode = not(partyMode) return "200" else: @@ -231,5 +230,11 @@ def getPlaylist(): return tempPlaylist if __name__ == "__main__": + # There's not really a whole lot of point to a main function for something like this, you'd never use any of these methods + # elsewhere, but its just good practice i guess + # start the media player thread + queueThread = threading.Thread(target=playQueuedSongs) + queueThread.daemon = True + queueThread.start() app.run(host='0.0.0.0', port=portTheUserPicked) \ No newline at end of file diff --git a/readme.md b/readme.md index 6fbe6ed..e894e44 100644 --- a/readme.md +++ b/readme.md @@ -65,7 +65,7 @@ These are specific details on each section of the app, and how to use them - The total set of features that can be restricted is - Skip track - Play-pause toggle - - Add track + - Add track to queue - Partymode toggle - Change volume - When this argument is left out (or empty string) the admin features aren't used, and everyone can do everything @@ -90,4 +90,5 @@ From left to right: ## External Credits - QR Code Generator: JS file found [here](https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js) - Cookie Popup: JS file found [here](https://cookieconsent.popupsmart.com/src/js/popper.js) -*See `LICENSE.md` for redistribution details. \ No newline at end of file + +*See `LICENSE.md` for redistribution and editing details.* \ No newline at end of file