refactored, fixed mp3 length bug, added lossless tag to database
This commit is contained in:
parent
faac93b1f6
commit
6ece2d3ea1
4 changed files with 46 additions and 24 deletions
|
|
@ -3,7 +3,8 @@ let ip;
|
||||||
let alertTime = 2;
|
let alertTime = 2;
|
||||||
let adminPass = "";
|
let adminPass = "";
|
||||||
const ERR_NO_ADMIN = "401"; // gonna use this later to refactor
|
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!") {
|
async function alertText(text="Song Added!") {
|
||||||
alertbox = document.getElementById("alert");
|
alertbox = document.getElementById("alert");
|
||||||
alertbox.innerHTML = text;
|
alertbox.innerHTML = text;
|
||||||
|
|
@ -17,6 +18,7 @@ async function alertText(text="Song Added!") {
|
||||||
async function getFromServer(bodyInfo, source="",password=adminPass) {
|
async function getFromServer(bodyInfo, source="",password=adminPass) {
|
||||||
try{
|
try{
|
||||||
if (bodyInfo != null) {
|
if (bodyInfo != null) {
|
||||||
|
// the currently set password is always included in every request
|
||||||
bodyInfo["password"] = password;
|
bodyInfo["password"] = password;
|
||||||
}
|
}
|
||||||
const response = await fetch("http://"+ip+"/"+source, {
|
const response = await fetch("http://"+ip+"/"+source, {
|
||||||
|
|
@ -142,6 +144,7 @@ async function searchSongs(searchTerm){
|
||||||
document.getElementById("songlist").innerHTML = "<h1>We might not have that one...</h1>";
|
document.getElementById("songlist").innerHTML = "<h1>We might not have that one...</h1>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function alertTimeEnter(e){
|
function alertTimeEnter(e){
|
||||||
if (e.key == "Enter") {
|
if (e.key == "Enter") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -178,6 +181,7 @@ function ipSetter(){
|
||||||
alertText("Your IP is now set to "+ipBox+" at port 19054 (Default)")
|
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()
|
qrCodeGenerate()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ except:
|
||||||
songDatabase.execute("UPDATE meta SET data = ? WHERE id = 'songDirectory'", (soundLocation,))
|
songDatabase.execute("UPDATE meta SET data = ? WHERE id = 'songDirectory'", (soundLocation,))
|
||||||
if args.mode.lower() == "update":
|
if args.mode.lower() == "update":
|
||||||
#Create if not exists
|
#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;")
|
songDatabase.execute("SELECT filename FROM songs;")
|
||||||
dBfilelist = songDatabase.fetchall()
|
dBfilelist = songDatabase.fetchall()
|
||||||
dBfilelistSet = set()
|
dBfilelistSet = set()
|
||||||
|
|
@ -50,7 +50,7 @@ if args.mode.lower() == "update":
|
||||||
print("new songs: " + ", ".join(songFiles))
|
print("new songs: " + ", ".join(songFiles))
|
||||||
elif args.mode.lower()=="new":
|
elif args.mode.lower()=="new":
|
||||||
songDatabase.execute("DROP TABLE IF EXISTS songs;")
|
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:
|
else:
|
||||||
raise ValueError("Must be \"new\" or \"update\"")
|
raise ValueError("Must be \"new\" or \"update\"")
|
||||||
|
|
||||||
|
|
@ -65,37 +65,43 @@ if args.art.lower() == "true" and not(args.apikey == ""):
|
||||||
validFormats = ["mp3","flac","wav"]
|
validFormats = ["mp3","flac","wav"]
|
||||||
|
|
||||||
for i in songFiles:
|
for i in songFiles:
|
||||||
|
# songFiles is the list of filenames, so i is the filename of each song
|
||||||
global song
|
global song
|
||||||
extension = i.split(".")
|
filenamesplit = i.split(".")
|
||||||
extension = extension[len(extension)-1]
|
extension = filenamesplit[len(filenamesplit)-1]
|
||||||
|
lossless = 0 # sqlite doesn't have booleans. what is this, C?
|
||||||
if not(extension.lower() in validFormats):
|
if not(extension.lower() in validFormats):
|
||||||
# skip any non music files (like directories or cover art)
|
# skip any non music files (like directories or cover art)
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
if(extension.lower() == "mp3"):
|
print(extension)
|
||||||
# get the metadata
|
# get the metadata
|
||||||
|
if(extension.lower() == "mp3"):
|
||||||
song = EasyID3(soundLocation+i)
|
song = EasyID3(soundLocation+i)
|
||||||
elif(extension.lower() == "flac"):
|
elif(extension.lower() == "flac"):
|
||||||
song = mutagen.flac.FLAC(soundLocation+i)
|
song = mutagen.flac.FLAC(soundLocation+i)
|
||||||
|
lossless = 1
|
||||||
elif(extension.lower() in ["wav","wave"]):
|
elif(extension.lower() in ["wav","wave"]):
|
||||||
|
# Im actually pretty sure waves can't have metadata, but whatevz
|
||||||
song = mutagen.wave.WAVE(soundLocation+i)
|
song = mutagen.wave.WAVE(soundLocation+i)
|
||||||
|
lossless = 1
|
||||||
title = song['title'][0]
|
title = song['title'][0]
|
||||||
artist = song['artist'][0]
|
artist = song['artist'][0]
|
||||||
except:
|
except:
|
||||||
if "_" in i:
|
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("_")
|
song = i.split("_")
|
||||||
title = song[0]
|
title = song[0]
|
||||||
artist = song[1].split(".")[0]
|
artist = song[1].split(".")[0]
|
||||||
elif "-" in i:
|
elif "-" in i:
|
||||||
# if there's no underscore, try artist - title.mp3
|
# if there's no underscore, try "artist - title.mp3"
|
||||||
song = i.split("-")
|
song = i.split("-")
|
||||||
title = song[1].split(".")[0]
|
title = song[1].split(".")[0]
|
||||||
artist = song[0]
|
artist = song[0]
|
||||||
title = title.strip()
|
title = title.strip()
|
||||||
artist = artist.strip()
|
artist = artist.strip()
|
||||||
else:
|
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
|
title = i
|
||||||
artist = None
|
artist = None
|
||||||
if args.art.lower() == "true" and not(args.apikey == ""):
|
if args.art.lower() == "true" and not(args.apikey == ""):
|
||||||
|
|
@ -112,13 +118,19 @@ for i in songFiles:
|
||||||
else:
|
else:
|
||||||
image=None
|
image=None
|
||||||
try:
|
try:
|
||||||
|
if extension.lower() in ['flac','wave','wav']:
|
||||||
length = math.ceil(song.info.length)
|
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:
|
except:
|
||||||
length = 0
|
length = 0
|
||||||
if len(songFiles) != 1:
|
if len(songFiles) != 1:
|
||||||
index = (songFiles.index(i))%4
|
index = (songFiles.index(i))%4
|
||||||
print("\r" + str(loading[index] + str(math.floor((songFiles.index(i)/(len(songFiles)-1))*100))+ "%"), end='', flush=True)
|
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
|
# each "song" is stored as a SQLite entry following the format seen below
|
||||||
songDatabase.execute(f"INSERT INTO songs (filename, title, artist, art, length) VALUES (?,?,?,?,?)",(i,title,artist,image,length))
|
songDatabase.execute(f"INSERT INTO songs (filename, title, artist, art, length, lossless) VALUES (?,?,?,?,?,?)",(i,title,artist,image,length,lossless))
|
||||||
|
|
||||||
fileOfDB.commit()
|
fileOfDB.commit()
|
||||||
|
|
|
||||||
|
|
@ -40,11 +40,13 @@ elif "/" in soundLocation:
|
||||||
else:
|
else:
|
||||||
soundLocation += "\\"
|
soundLocation += "\\"
|
||||||
#Create Virtual table for searching
|
#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("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;")
|
songDatabase.execute("INSERT INTO virtualSongs SELECT * FROM songs;")
|
||||||
fileofDB.commit()
|
fileofDB.commit()
|
||||||
fileofDB.close()
|
fileofDB.close()
|
||||||
|
|
||||||
#Initializing all the global stuff
|
#Initializing all the global stuff
|
||||||
random.seed()
|
random.seed()
|
||||||
global partyMode
|
global partyMode
|
||||||
|
|
@ -66,6 +68,7 @@ CORS(app)
|
||||||
def queueSong(song):
|
def queueSong(song):
|
||||||
with playlistLock:
|
with playlistLock:
|
||||||
playlist.append(song)
|
playlist.append(song)
|
||||||
|
|
||||||
# this is a loop that plays the songs and checks for playlist changes, skips, ect.
|
# this is a loop that plays the songs and checks for playlist changes, skips, ect.
|
||||||
def playQueuedSongs():
|
def playQueuedSongs():
|
||||||
global skipNow
|
global skipNow
|
||||||
|
|
@ -94,15 +97,11 @@ def playQueuedSongs():
|
||||||
songDatabase.execute("SELECT * FROM songs ORDER BY RANDOM() LIMIT 1;")
|
songDatabase.execute("SELECT * FROM songs ORDER BY RANDOM() LIMIT 1;")
|
||||||
result = songDatabase.fetchall()
|
result = songDatabase.fetchall()
|
||||||
# adds the random songs for party mode
|
# 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])
|
playlist.append(result[0][0])
|
||||||
# check for new songs every second
|
# check for new songs every second
|
||||||
# I just didn't want to eat too much processing looping
|
# I just didn't want to eat too much processing looping
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
# start the media player thread
|
|
||||||
queueThread = threading.Thread(target=playQueuedSongs)
|
|
||||||
queueThread.daemon = True
|
|
||||||
queueThread.start()
|
|
||||||
|
|
||||||
@app.route("/controls", methods=['POST'])
|
@app.route("/controls", methods=['POST'])
|
||||||
def playerControls():
|
def playerControls():
|
||||||
|
|
@ -112,13 +111,13 @@ def playerControls():
|
||||||
recieveData=request.get_json(force=True)
|
recieveData=request.get_json(force=True)
|
||||||
if recieveData["control"] != None:
|
if recieveData["control"] != None:
|
||||||
if recieveData["control"] == "play-pause":
|
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()
|
player.pause()
|
||||||
return "200"
|
return "200"
|
||||||
else:
|
else:
|
||||||
return ERR_NO_ADMIN
|
return ERR_NO_ADMIN
|
||||||
elif recieveData["control"] == "skip":
|
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
|
skipNow = True
|
||||||
return "200"
|
return "200"
|
||||||
else:
|
else:
|
||||||
|
|
@ -136,13 +135,13 @@ def settingsControl():
|
||||||
global player
|
global player
|
||||||
recieveData = request.get_json(force=True)
|
recieveData = request.get_json(force=True)
|
||||||
if recieveData["setting"] == "volume":
|
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"]))
|
volumePassed = player.audio_set_volume(int(recieveData["level"]))
|
||||||
return {"volumePassed":volumePassed}
|
return {"volumePassed":volumePassed}
|
||||||
else:
|
else:
|
||||||
return ERR_NO_ADMIN
|
return ERR_NO_ADMIN
|
||||||
elif recieveData["setting"] == "partymode-toggle":
|
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)
|
partyMode = not(partyMode)
|
||||||
return "200"
|
return "200"
|
||||||
else:
|
else:
|
||||||
|
|
@ -231,5 +230,11 @@ def getPlaylist():
|
||||||
return tempPlaylist
|
return tempPlaylist
|
||||||
|
|
||||||
if __name__ == "__main__":
|
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)
|
app.run(host='0.0.0.0', port=portTheUserPicked)
|
||||||
|
|
||||||
|
|
@ -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
|
- The total set of features that can be restricted is
|
||||||
- Skip track
|
- Skip track
|
||||||
- Play-pause toggle
|
- Play-pause toggle
|
||||||
- Add track
|
- Add track to queue
|
||||||
- Partymode toggle
|
- Partymode toggle
|
||||||
- Change volume
|
- Change volume
|
||||||
- When this argument is left out (or empty string) the admin features aren't used, and everyone can do everything
|
- 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
|
## External Credits
|
||||||
- QR Code Generator: JS file found [here](https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js)
|
- 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)
|
- Cookie Popup: JS file found [here](https://cookieconsent.popupsmart.com/src/js/popper.js)
|
||||||
*See `LICENSE.md` for redistribution details.
|
|
||||||
|
*See `LICENSE.md` for redistribution and editing details.*
|
||||||
Loading…
Add table
Add a link
Reference in a new issue