From 5b9812756162b0f72f0a41f1952dc740f6021885 Mon Sep 17 00:00:00 2001 From: Max Value Date: Mon, 9 Dec 2024 05:08:12 +0000 Subject: [PATCH] Did a lot :3 --- .gitignore | 1 + index.py | 308 +++++++++++++++++++++++++++++++++++++++++++ schema | 13 ++ static/admin.js | 122 +++++++++++++++++ static/pause.svg | 33 +++++ static/play.svg | 33 +++++ static/player.html | 198 ++++++++++++++++++++++++++++ static/style.css | 25 ++++ templates/admin.html | 133 +++++++++++++++++++ templates/home.html | 13 ++ test.txt | 0 11 files changed, 879 insertions(+) create mode 100644 .gitignore create mode 100755 index.py create mode 100644 schema create mode 100644 static/admin.js create mode 100644 static/pause.svg create mode 100644 static/play.svg create mode 100644 static/player.html create mode 100644 static/style.css create mode 100644 templates/admin.html create mode 100644 templates/home.html delete mode 100644 test.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..21d0b89 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.venv/ diff --git a/index.py b/index.py new file mode 100755 index 0000000..9e46087 --- /dev/null +++ b/index.py @@ -0,0 +1,308 @@ +#! .venv/bin/python3 + +from flask import Flask, Response, request, render_template, redirect, send_from_directory, url_for +from flask_httpauth import HTTPBasicAuth +from werkzeug.security import generate_password_hash, check_password_hash +from multiprocessing import Value +from os import listdir +import sqlite3 +import random +import json +import os.path + +app = Flask(__name__) +auth = HTTPBasicAuth() +db_keys = ["title", "album", "artist", "year", "link", "info", "category", "playing", "intime", "outtime"] +allowed_audio = ["mp3", "wav", "ogg", "flac"] +allowed_image = ["png", "jpg", "jpeg"] + +current_track_id = Value("I") # This is the global track ID +with current_track_id.get_lock(): + current_track_id.value = 1 + +audio_path = "../audio/" +cover_path = "../cover/" +icon_path = "../icon/" +upload_path = "../upload/" + +def check_files(track_id): + return { + "track_path": url_for("get_audio", track_id=track_id), + "track_exists": os.path.isfile(f"{audio_path}{track_id}.mp3"), + "front_anno_path": url_for("get_audio", track_id=track_id, position="front"), + "front_anno_exists": os.path.isfile(f"{audio_path}{track_id}-front.mp3"), + "back_anno_path": url_for("get_audio", track_id=track_id, position="back"), + "back_anno_exists": os.path.isfile(f"{audio_path}{track_id}-back.mp3"), + "cover_path": url_for("get_cover", track_id=track_id), + "cover_exists": os.path.isfile(f"{cover_path}{track_id}.png"), + "icon_path": url_for("get_icon", track_id=track_id) + } + +with open("../secrets.json", "r") as f: + users = json.loads(f.read()) +users = {k: generate_password_hash(v) for k: v in users} + +@auth.verify_password +def verify_password(username, password): + if username in users and \ + check_password_hash(users.get(username), password): + return username + +# Homepage +@app.route("/") +def homepage(): + page = render_template("home.html") + return Response(page, mimetype="text/html") + +# Admin pannel +@app.route("/admin", methods=['POST', 'GET']) +@auth.login_required +def admin(): + if request.method == "POST": + con = sqlite3.connect("../../metadata.db") + cur = con.cursor() + + try: commit_id = int(request.form["id"]) + except: commit_id = 0 + + if "playing" in request.form: + playing = "true" + else: + playing = "false" + + print(playing) + + # Get the new track id if neccicary + if commit_id == 0: + tracks = cur.execute(f"SELECT id FROM critters").fetchall() + tracks = [track_id[0] for track_id in tracks] + commit_id = 1 + while commit_id in tracks: + commit_id += 1 + + cur.execute(f""" + INSERT INTO critters + (id, title, album, artist, year, link, info, category, playing, intime, outtime) + VALUES ( + {commit_id}, + '{request.form["title"]}', + '{request.form["album"]}', + '{request.form["artist"]}', + '{request.form["year"]}', + '{request.form["link"]}', + '{request.form["info"]}', + '{request.form["category"]}', + '{playing}', + '{request.form["intime"]}', + '{request.form["outtime"]}' + ); + """) + con.commit() + + else: + if "delete" in request.form: + cur.execute(f"DELETE FROM critters WHERE id={request.form["id"]};") + con.commit() + + con.close() + return "" + + else: + cur.execute(f""" + UPDATE critters SET + title = '{request.form["title"]}', + album = '{request.form["album"]}', + artist = '{request.form["artist"]}', + year = '{request.form["year"]}', + link = '{request.form["link"]}', + info = '{request.form["info"]}', + category = '{request.form["category"]}', + playing = '{playing}', + intime = '{request.form["intime"]}', + outtime = '{request.form["outtime"]}' + WHERE id = {commit_id}; + """) + con.commit() + + if "audio" in request.files: + upload = request.files["audio"] + if upload.filename != "": + extention = upload.filename.rsplit('.', 1)[1].lower() + if extention in allowed_audio: + file_path = f"{upload_path}{commit_id}.{extention}" + upload.save(file_path) + os.system(f"sox {file_path} -r 22050 {audio_path}{commit_id}.mp3 &") + + if "front" in request.files: + upload = request.files["front"] + if upload.filename != "": + extention = upload.filename.rsplit('.', 1)[1].lower() + if extention in allowed_audio: + file_path = f"{upload_path}{commit_id}-front.{extention}" + upload.save(file_path) + os.system(f"sox {file_path} -r 22050 {audio_path}{commit_id}-front.mp3 &") + + if "back" in request.files: + upload = request.files["back"] + if upload.filename != "": + extention = upload.filename.rsplit('.', 1)[1].lower() + if extention in allowed_audio: + file_path = f"{upload_path}{commit_id}-back.{extention}" + upload.save(file_path) + os.system(f"sox {file_path} -r 22050 {audio_path}{commit_id}-back.mp3 &") + + if "cover" in request.files: + upload = request.files["cover"] + if upload.filename != "": + extention = upload.filename.rsplit('.')[-1].lower() + if extention in allowed_image: + file_path = f"{upload_path}{commit_id}.{extention}" + upload.save(file_path) + os.system(f"convert {file_path} -scale 32x32! {icon_path}{commit_id}.png &") + os.system(f"convert {file_path} -scale 600x600! {cover_path}{commit_id}.png &") + + page = render_template("admin.html", user = auth.current_user()) + return Response(page, mimetype="text/html") + +# Get the next track +@app.route("/api/next") +@auth.login_required +def set_current_id(): + server_data = { # The value for each of the keys is the position in the database columns that peice of data can be found + "id": 0, # (Note 1) + "title": 1, + "artist": 3, + "intime": 9, + "outtime": 10 + } + + with current_track_id.get_lock(): # Lock for entire function + track_id = current_track_id.value + + current_album = "" + current_artist = "" + + con = sqlite3.connect("../../metadata.db") + cur = con.cursor() + track = cur.execute(f"SELECT album, artist FROM critters WHERE id={track_id}").fetchone() + + if track is not None: + current_album, current_artist = track + + category_secifier = "" + if "c" in request.args: + category = request.args.get("c") + category_secifier = f" AND category = '{category}'" + + tracks = cur.execute(f""" + SELECT * FROM critters WHERE + playing = 'true' AND + id != {track_id} AND + album != '{current_album}' AND + artist != '{current_artist}'{category_secifier} + """).fetchall() + + if tracks is not None: + target_index = random.randint(0, len(tracks)-1) + + current_track_id.value = tracks[target_index][0] + + for key, i in server_data.items(): + server_data[key] = tracks[target_index][i] # See note 1 + + return Response(json.dumps(server_data), mimetype="text/json") + +@app.route("/api/get", methods=['GET']) +def get_tracks(): + track_data = { + "id": 0, + "title": "Unknown", + "album": "Unknown", + "artist": "Unknown", + "year": "Unknown", + "link": "https://treecritters.bandcamp.com", + "info": "No info", + "category":"None", + "playing":"false" + } + responce_data = [track_data] + + con = sqlite3.connect("../../metadata.db") + cur = con.cursor() + tracks = cur.execute(f"SELECT * FROM critters").fetchall() + if tracks is not None: + responce_data = [{key: track[i] for (i, key) in enumerate(track_data)} | check_files(track[0]) for track in tracks] + + return Response(json.dumps(responce_data), mimetype="text/json") + +# Call for getting single track details with all feilds in JSON +@app.route("/api/") +def get_single_track(track_id): + if track_id == 0: + with current_track_id.get_lock(): + track_id = current_track_id.value + con = sqlite3.connect("../../metadata.db") + cur = con.cursor() + track = cur.execute(f"SELECT * FROM critters WHERE id={track_id}").fetchone() + track_data = { + "id": 0, + "title": "Unknown", + "album": "Unknown", + "artist": "Unknown", + "year": "Unknown", + "link": "https://treecritters.bandcamp.com", + "info": "No info", + "category": "None", + "playing": "false", + } + cover_path = {"cover_path": url_for("get_cover", track_id=track_id)} + if track is not None: + for (i, key) in enumerate(track_data): + track_data[key] = track[i] + return Response(json.dumps(track_data | cover_path), mimetype="text/json") + +# Call for getting single track details with a single feild +@app.route("/api/.") +def get_single_field(track_id, feild): + if track_id == 0: + with current_track_id.get_lock(): + track_id = current_track_id.value + result = "Unknown" + if feild in db_keys: + con = sqlite3.connect("../../metadata.db") + cur = con.cursor() + track = cur.execute(f"SELECT {feild} FROM critters WHERE id={track_id}").fetchone() + if track is not None: + result = track[0] + if feild == "cover_path": + result = url_for("get_cover", track_id=track_id) + + return Response(result, mimetype="text/plaintext") + +@app.route("/api/audio/") +@auth.login_required +def get_audio(track_id): + if "position" in request.args: + position = request.args.get('position') + if position in ["front", "back"]: + return send_from_directory(audio_path, f"{track_id}-{position}.mp3") + + return send_from_directory(audio_path, f"{track_id}.mp3") + +@app.route("/api/icon/") +@auth.login_required +def get_icon(track_id): + if os.path.isfile(f"{icon_path}{track_id}.png"): + return send_from_directory(icon_path, f"{track_id}.png") + else: + return send_from_directory(icon_path, "default.png") + +@app.route("/api/cover/") +def get_cover(track_id): + if os.path.isfile(f"{cover_path}{track_id}.png"): + return send_from_directory(cover_path, f"{track_id}.png") + else: + return send_from_directory(cover_path, "default.png") + +if __name__ == "__main__": + app.run(host='127.0.0.1', port=5000, debug=True) diff --git a/schema b/schema new file mode 100644 index 0000000..e9b93f8 --- /dev/null +++ b/schema @@ -0,0 +1,13 @@ +CREATE TABLE critters ( + id INTEGER PRIMARY KEY, + title TEXT, + album TEXT, + artist TEXT, + year TEXT, + link TEXT, + info TEXT, + category TEXT, + playing TEXT, + intime TEXT, + outtime TEXT +); diff --git a/static/admin.js b/static/admin.js new file mode 100644 index 0000000..7693cf3 --- /dev/null +++ b/static/admin.js @@ -0,0 +1,122 @@ +const goodSymbol = "✔"; +const badSymbol = "✘"; + +function getTrackList () { + fetch("/api/get") + .then(data => data.text()) + .then(data => JSON.parse(data)) + .then(data => { + var html = ` + + + # + Title + Album + Artist + Category + Playing + Track + Front + Back + Cover + + + `; + for (let track of data) { + + if (track.playing == "true") { + trackPlaying = goodSymbol; + } else { + trackPlaying = badSymbol; + } + + if (track.track_exists) { + trackExists = `${goodSymbol}`; + } else { + trackExists = badSymbol; + } + + if (track.front_anno_exists) { + frontAnnoExists = `${goodSymbol}`; + } else { + frontAnnoExists = badSymbol; + } + + if (track.back_anno_exists) { + backAnnoExists = `${goodSymbol}`; + } else { + backAnnoExists = badSymbol; + } + + if (track.cover_exists) { + coverExists = `${goodSymbol}`; + } else { + coverExists = badSymbol; + } + + html += ` + + + ${track.id} + ${track.title} + ${track.album} + ${track.artist} + ${track.category} + ${trackPlaying} + ${trackExists} + ${frontAnnoExists} + ${backAnnoExists} + ${coverExists} + + + + + + ` + }; + return html + }) + .then(data => document.getElementById("trackListFull").innerHTML = data); +} + +function newTrack () { + const elements = document.getElementsByClassName("mainForm"); + console.log(elements) + for (const e of elements) { + e.value = ""; + } + document.getElementById("id").value = "New track"; + document.getElementById("playing").checked = true; + document.getElementById("thumbnail").src = `/api/cover/0`; +} + +function editTrack (id) { + document.getElementById("thumbnail").src = `/api/cover/${id}` + fetch(`/api/${id}`) + .then(data => data.text()) + .then(data => JSON.parse(data)) + .then(data => { + for (let x in data) { + document.getElementById(x).value = data[x]; + } + if (data.playing == "true") { + document.getElementById("playing").checked = true; + } else { + document.getElementById("playing").checked = false; + } + }); +} + +function deleteTrack (id) { + const form = new FormData(); + form.append("id", id); + form.append("delete", 1); + + fetch("/admin", { + method: "POST", + body: form + }) + .then(document.getElementById(`track${id}`).remove()); +} + +getTrackList(); diff --git a/static/pause.svg b/static/pause.svg new file mode 100644 index 0000000..d48e265 --- /dev/null +++ b/static/pause.svg @@ -0,0 +1,33 @@ + + + + + + diff --git a/static/play.svg b/static/play.svg new file mode 100644 index 0000000..7ba5940 --- /dev/null +++ b/static/play.svg @@ -0,0 +1,33 @@ + + + + + + diff --git a/static/player.html b/static/player.html new file mode 100644 index 0000000..9cee960 --- /dev/null +++ b/static/player.html @@ -0,0 +1,198 @@ + + + + + + + +
+ + +

Currently playing track info available with the JS player

+
+
+

Tree Critters Radio

+
+ + +
+ + diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..55cfd75 --- /dev/null +++ b/static/style.css @@ -0,0 +1,25 @@ +:root { + --background: #111111; + --dark: #151515; + --primary: #70b783; + --off-primary: #4d9961; + --secondary: #c6ce73; + --tertiary: #2b5536; + + font-family: sans-serif; + color: var(--primary); + background-color: var(--background); +} + +a:link { + color: var(--secondary); +} +a:visited { + color: var(--secondary); +} +a:hover { + color: var(--primary); +} +a:active { + color: white; +} diff --git a/templates/admin.html b/templates/admin.html new file mode 100644 index 0000000..9951809 --- /dev/null +++ b/templates/admin.html @@ -0,0 +1,133 @@ + + + + Tree Critters + + + + + +

Tree Critters Admin pannel

+

Signed in as {{user}}

+
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + diff --git a/templates/home.html b/templates/home.html new file mode 100644 index 0000000..d7d51b1 --- /dev/null +++ b/templates/home.html @@ -0,0 +1,13 @@ + + + + Tree Critters + + + +

Tree Critters Radio

+

Player:

+ +

Control pannel

+ + diff --git a/test.txt b/test.txt deleted file mode 100644 index e69de29..0000000 -- 2.39.2