Complete backend redesign to using SQLite #4
5 changed files with 107 additions and 68 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,3 +1,3 @@
|
|||
server/sound/
|
||||
server/songDatabase.json
|
||||
*.db
|
||||
start.bat
|
||||
|
|
@ -314,9 +314,9 @@ document.addEventListener('keydown', function(e){
|
|||
document.getElementById("playlist-mode").style.display = "none";
|
||||
document.getElementById("settings-mode").style.display = "none";
|
||||
//.ontouch for mobile??
|
||||
document.getElementById("volumerange").onchange = function() {
|
||||
let returnValue = getFromServer({setting:"volume",level:this.value}, "settings")
|
||||
if (returnValue !=0) {
|
||||
document.getElementById("volumerange").onchange = async function() {
|
||||
let returnValue = await getFromServer({setting:"volume",level:this.value}, "settings")
|
||||
if (returnValue["volumePassed"] !=0) {
|
||||
alertText("Nothing is playing")
|
||||
document.getElementById("volumerange").value = -1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import os
|
||||
from mutagen.easyid3 import EasyID3
|
||||
from mutagen.mp3 import MP3
|
||||
import requests, ast, time, math, argparse, json
|
||||
import sqlite3 as sql
|
||||
import requests, ast, time, math, argparse
|
||||
|
||||
loading = ["-","\\","|","/"]
|
||||
|
||||
|
|
@ -22,32 +23,35 @@ else:
|
|||
# apikeylastfm = "KeyHere"
|
||||
# soundLocation = "directoryHere"
|
||||
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":
|
||||
try:
|
||||
with open('songDatabase.json', 'r') as handle:
|
||||
songDatabaseList = json.load(handle)
|
||||
except:
|
||||
songDatabaseList={"songDirectory":soundLocation,'songData':{}}
|
||||
deleteySongs = []
|
||||
for i in songDatabaseList["songData"]:
|
||||
try:
|
||||
if songFiles.index(i) == -1:
|
||||
deleteySongs.append(i)
|
||||
except:
|
||||
deleteySongs.append(i)
|
||||
if deleteySongs:
|
||||
print("deleted: " + ", ".join(deleteySongs)+ " from database")
|
||||
for i in deleteySongs:
|
||||
songDatabaseList["songData"].pop(i)
|
||||
for i in songDatabaseList["songData"]:
|
||||
songFiles.remove(i)
|
||||
# This prints everything in the directory, including non mp3s
|
||||
# theres not agood way to fix this without looping again.
|
||||
#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("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":
|
||||
songDatabaseList={"songDirectory":soundLocation,'songData':{}}
|
||||
songDatabase.execute("DROP TABLE IF EXISTS songs;")
|
||||
songDatabase.execute("CREATE TABLE songs (filename TEXT PRIMARY KEY, title TEXT, artist TEXT, art TEXT, length INTEGER);")
|
||||
else:
|
||||
raise ValueError("Must be \"new\" or \"update\"")
|
||||
|
||||
if args.art.lower() == "true" and not(args.apikey == ""):
|
||||
x = len(songFiles)*0.25
|
||||
if x > 60:
|
||||
|
|
@ -65,12 +69,19 @@ for i in songFiles:
|
|||
title = song['title'][0]
|
||||
artist = song['artist'][0]
|
||||
except:
|
||||
try:
|
||||
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]
|
||||
except:
|
||||
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, the title is the file name
|
||||
title = i
|
||||
artist = None
|
||||
|
|
@ -95,7 +106,8 @@ for i in songFiles:
|
|||
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 dictionary/JSON entry following the format seen in the readME
|
||||
songDatabaseList["songData"][i] = ({"title":title,"artist":artist,"art":image,"length":length})
|
||||
|
||||
with open('songDatabase.json', 'w') as handle:
|
||||
json.dump(songDatabaseList, handle)
|
||||
songDatabase.execute(f"INSERT INTO songs (filename, title, artist, art, length) VALUES (?,?,?,?,?)",(i,title,artist,image,length))
|
||||
|
||||
|
||||
|
||||
fileOfDB.commit()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
from flask import Flask
|
||||
from flask import request
|
||||
from flask_cors import CORS
|
||||
import json,vlc,threading,time,random, argparse
|
||||
import sqlite3 as sql
|
||||
import vlc,threading,time,random, argparse
|
||||
# Argparse Stuff
|
||||
parser=argparse.ArgumentParser(description="Options for the Webby Bits")
|
||||
# this is no longer needed assuming my file works correctly with the generator
|
||||
|
|
@ -9,10 +10,12 @@ parser=argparse.ArgumentParser(description="Options for the Webby Bits")
|
|||
parser.add_argument('-p','--port',help="Port to host on, not the same as the web (client) port",default='19054')
|
||||
portTheUserPicked=parser.parse_args().port
|
||||
|
||||
# open the json file as a dictionary
|
||||
with open('./songDatabase.json', 'r') as handle:
|
||||
songDatabaseList = json.load(handle)
|
||||
soundLocation = songDatabaseList["songDirectory"]
|
||||
fileofDB = sql.connect("songDatabase.db")
|
||||
songDatabase = fileofDB.cursor()
|
||||
|
||||
#song directory
|
||||
songDatabase.execute("SELECT * FROM meta WHERE id='songDirectory';")
|
||||
soundLocation = songDatabase.fetchall()[0][1]
|
||||
|
||||
if soundLocation[-1] == "/" or soundLocation[-1] == "\\":
|
||||
pass
|
||||
|
|
@ -20,6 +23,13 @@ elif "/" in soundLocation:
|
|||
soundLocation += "/"
|
||||
else:
|
||||
soundLocation += "\\"
|
||||
print(soundLocation)
|
||||
#Create Virtual table for searching
|
||||
songDatabase.execute("DROP TABLE virtualSongs;")
|
||||
songDatabase.execute("CREATE VIRTUAL TABLE virtualSongs USING fts5(filename, title, artist, art, length);")
|
||||
songDatabase.execute("INSERT INTO virtualSongs SELECT * FROM songs;")
|
||||
fileofDB.commit()
|
||||
fileofDB.close()
|
||||
#Initializing all the global stuff
|
||||
random.seed()
|
||||
global partyMode
|
||||
|
|
@ -55,7 +65,7 @@ def playQueuedSongs():
|
|||
player.stop()
|
||||
skipNow = False
|
||||
songNext = playlist.pop(0)
|
||||
media = fakeplayer.media_new("sound/"+songNext)
|
||||
media = fakeplayer.media_new(soundLocation+songNext)
|
||||
player.set_media(media)
|
||||
player.play()
|
||||
elif (skipNow==True or (z == "State.Ended" or z == "State.NothingSpecial" or z=="State.Stopped")):
|
||||
|
|
@ -64,9 +74,13 @@ def playQueuedSongs():
|
|||
songNext = None
|
||||
player.stop()
|
||||
elif len(playlist)<1 and (partyMode == True):
|
||||
fileofDB = sql.connect("songDatabase.db")
|
||||
songDatabase = fileofDB.cursor()
|
||||
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
|
||||
playlist.append(random.choice(songDatabaseList)["file"])
|
||||
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)
|
||||
|
|
@ -115,21 +129,27 @@ def settingsControl():
|
|||
@app.route("/search", methods=['POST'])
|
||||
def searchSongDB():
|
||||
recieveData=request.get_json(force=True)
|
||||
tempData = {}
|
||||
fileofDB = sql.connect("songDatabase.db")
|
||||
songDatabase = fileofDB.cursor()
|
||||
results = []
|
||||
if (recieveData['search'] == ""):
|
||||
tempData = songDatabaseList["songData"].copy()
|
||||
songDatabase.execute("SELECT * FROM virtualSongs")
|
||||
results = songDatabase.fetchall()
|
||||
else:
|
||||
for i in songDatabaseList["songData"]:
|
||||
if ((songDatabaseList["songData"][i]["title"].lower().find(recieveData['search'].lower())) > -1):
|
||||
tempData[i] = songDatabaseList["songData"][i]
|
||||
|
||||
try:
|
||||
if (songDatabaseList["songData"][i]["artist"].lower().find(recieveData['search'].lower()) > -1):
|
||||
tempData[i] = songDatabaseList["songData"][i]
|
||||
except:
|
||||
pass
|
||||
songDatabase.execute("SELECT * FROM virtualSongs WHERE virtualSongs MATCH ?",[recieveData['search']])
|
||||
results = songDatabase.fetchall()
|
||||
tempdata = {}
|
||||
# this is a temporary solution so i dont have to change the
|
||||
for i in results:
|
||||
tempdata[i[0]] = {
|
||||
"title": i[1],
|
||||
"artist": i[2],
|
||||
"art": i[3],
|
||||
"length": i[4]
|
||||
}
|
||||
# print(tempData)
|
||||
return tempData
|
||||
fileofDB.close()
|
||||
return tempdata
|
||||
|
||||
@app.route("/songadd", methods=["POST"])
|
||||
def songadd():
|
||||
|
|
@ -139,17 +159,36 @@ def songadd():
|
|||
@app.route("/playlist", methods=["POST"])
|
||||
def getPlaylist():
|
||||
global songNext
|
||||
fileofDB = sql.connect("songDatabase.db")
|
||||
songDatabase = fileofDB.cursor()
|
||||
tempPlaylist = []
|
||||
if songNext != None:
|
||||
# Adds the currently playing song
|
||||
k = songDatabaseList["songData"][songNext]
|
||||
songDatabase.execute("SELECT * FROM songs WHERE filename = ?",[songNext])
|
||||
result = songDatabase.fetchall()[0]
|
||||
# again, this is still using the old JSON format to avoid client changes
|
||||
k = {
|
||||
"title": result[1],
|
||||
"artist": result[2],
|
||||
"art": result[3],
|
||||
"length": result[4]
|
||||
}
|
||||
temp = k.copy()
|
||||
temp["playing"] = True
|
||||
temp["time"] = player.get_time()/1000
|
||||
tempPlaylist.append({songNext:temp})
|
||||
for i in playlist:
|
||||
tempPlaylist.append({i:songDatabaseList["songData"][i]})
|
||||
songDatabase.execute("SELECT * FROM songs WHERE filename = ?",[i])
|
||||
result = songDatabase.fetchall()[0]
|
||||
k = {
|
||||
"title": result[1],
|
||||
"artist": result[2],
|
||||
"art": result[3],
|
||||
"length": result[4]
|
||||
}
|
||||
tempPlaylist.append({i:k})
|
||||
# print(tempPlaylist)
|
||||
fileofDB.close()
|
||||
return tempPlaylist
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
18
readme.md
18
readme.md
|
|
@ -24,7 +24,7 @@ webbyBits.py
|
|||
1. Place mp3 files in the `sound/` folder
|
||||
2. Open `databaseGenerator.py` and put your LastFM API key in at the top or at runtime using `-k APIKey` (*optional*)
|
||||
3. Run `databaseGenerator.py`
|
||||
* *The `databaseGenerator.py` will index all mp3 files, and save the information to `songDatabase.json`*
|
||||
* *The `databaseGenerator.py` will index all mp3 files, and save the information to `songDatabase.db`*
|
||||
* *If getting images, this process may take a long time with a large amount of mp3 files*
|
||||
4. Run `webbyBits.py`
|
||||
* *The port can be customized at runtime using* `-p portNumber` *as an atribute*
|
||||
|
|
@ -39,7 +39,7 @@ These are specific details on each section of the app, and how to use them
|
|||
- `sound/` contains all mp3 files by default
|
||||
- `databaseGenerator.py` scans through mp3 files and gets information about them
|
||||
- `Filename, Title, Artist, Art, Length` are all saved
|
||||
- *If the title and artist are not in the mp3 metadata, it looks for a format of* `TITLE_ARTIST.mp3` *and otherwise defaults to the file name as the title, and no artist*
|
||||
- *If the title and artist are not in the mp3 metadata, it looks for a format of* `TITLE_ARTIST.mp3` *then of* `ARTIST - TITLE.mp3` *and otherwise defaults to the file name as the title, and no artist*
|
||||
- Art is retrieved from LastFM
|
||||
- Running with `--mode (update/new)` either updates the current database and adds new songs/removes deleted songs, or recreates the entire database (update is default, and is faster in art mode)
|
||||
- Running with `--art (True/False)` retrieves art from LastFM or doesn't (True is default)
|
||||
|
|
@ -50,19 +50,7 @@ These are specific details on each section of the app, and how to use them
|
|||
- Default `"./sound/"`
|
||||
- _This setting might be kinda iffy on Linux. You're on Linux just go and edit it if you have issues_
|
||||
- ~~__Make certain you only use forward slashes in your directory, even on Windows__~~ I think this should be fine now i'll check later
|
||||
- `songDatabase.json` stores all the information about each song in this format:
|
||||
```
|
||||
{
|
||||
"songDirectory": "./sound/",
|
||||
"songData": {
|
||||
"Circus_Fox Szn.mp3":
|
||||
{"title": "Circus",
|
||||
"artist": "Fox Szn",
|
||||
"art": null,
|
||||
"length": 141}
|
||||
}
|
||||
}
|
||||
```
|
||||
- `songDatabase.db` stores all the information about each song in a SQLite database with tables `songs` and `meta`
|
||||
- `webbyBits.py` imports the database, runs all music playing, and accepts all commands from clients
|
||||
- Searches return matching songs
|
||||
- Accepts Play-Pause and Skip commands
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue