generated from nathanwoodburn/python-webserver-template
feat: Add a ton of features to make it better
All checks were successful
Build Docker / BuildImage (push) Successful in 2m15s
All checks were successful
Build Docker / BuildImage (push) Successful in 2m15s
This commit is contained in:
1
current_track.json
Normal file
1
current_track.json
Normal file
@@ -0,0 +1 @@
|
||||
{"name": "girls will b girls", "cover": "https://i.scdn.co/image/ab67616d0000b273ea3a18c31fd757d953c7836a"}
|
||||
3
server.log
Normal file
3
server.log
Normal file
@@ -0,0 +1,3 @@
|
||||
Changing Song
|
||||
Song Name: girls will b girls
|
||||
Playing Song
|
||||
62
server.py
62
server.py
@@ -18,9 +18,24 @@ import dotenv
|
||||
|
||||
dotenv.load_dotenv()
|
||||
|
||||
SPEAKER_NAME = os.getenv("SPEAKER_NAME", "Family Room Speakers")
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
def log(message: str):
|
||||
"""
|
||||
Log a message to the server log file.
|
||||
"""
|
||||
log_path = "server.log"
|
||||
if not os.path.exists(log_path):
|
||||
with open(log_path, "w") as f:
|
||||
f.write("")
|
||||
|
||||
with open(log_path, "a") as f:
|
||||
f.write(f"{datetime.now().isoformat()} - {message}\n")
|
||||
print(f"{datetime.now().isoformat()} - {message}") # Also print to console for debugging
|
||||
|
||||
def find(name, path):
|
||||
for root, dirs, files in os.walk(path):
|
||||
if name in files:
|
||||
@@ -85,11 +100,21 @@ def restart():
|
||||
"""
|
||||
# Execute a `pkill spotifyd` command to stop the spotifyd process
|
||||
status = os.system("pkill spotifyd")
|
||||
|
||||
hookPath = os.path.join(os.getcwd(), "song-hook.sh")
|
||||
|
||||
# Clear the server log
|
||||
log_path = "server.log"
|
||||
if os.path.exists(log_path):
|
||||
with open(log_path, "w") as f:
|
||||
f.write("")
|
||||
|
||||
# Start with a new process
|
||||
os.system("spotifyd -d 'Family Room Speakers'")
|
||||
|
||||
|
||||
|
||||
output = os.system(f"spotifyd -d '{SPEAKER_NAME}' --onevent {hookPath}")
|
||||
if output != 0:
|
||||
log("Failed to restart spotifyd")
|
||||
else:
|
||||
log("spotifyd restarted successfully")
|
||||
|
||||
return redirect('/')
|
||||
|
||||
@@ -141,6 +166,35 @@ def api_data():
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@app.route("/api/v1/logs", methods=["GET"])
|
||||
def api_logs():
|
||||
"""
|
||||
Returns the last 100 lines of the server log.
|
||||
"""
|
||||
log_path = "server.log"
|
||||
|
||||
# Check if the log file exists
|
||||
if not os.path.isfile(log_path):
|
||||
return jsonify({"logs": "Server not running"}), 404
|
||||
|
||||
lines = []
|
||||
try:
|
||||
with open(log_path, "r") as f:
|
||||
lines = f.readlines()[-100:]
|
||||
except Exception as e:
|
||||
lines = [f"Error reading log file: {e}"]
|
||||
return jsonify({"logs": "".join(lines)})
|
||||
|
||||
@app.route("/api/v1/current_track", methods=["GET"])
|
||||
def api_current_track():
|
||||
# If the current_track.json file exists, read it and return its contents
|
||||
if os.path.isfile("current_track.json"):
|
||||
with open("current_track.json", "r") as f:
|
||||
track_data = json.load(f)
|
||||
return jsonify(track_data)
|
||||
else:
|
||||
return jsonify({"error": "No current track data available"}), 404
|
||||
|
||||
# endregion
|
||||
|
||||
|
||||
|
||||
44
song-hook.py
Executable file
44
song-hook.py
Executable file
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
from datetime import datetime
|
||||
# "PLAYER_EVENT": "Player Event",
|
||||
# "TRACK_ID": "Track ID",
|
||||
# "TRACK_COVER": "Track Cover",
|
||||
# "endoftrack": "End of Track",
|
||||
ENVLOGS = {
|
||||
"CLIENT_NAME": "Device Connected",
|
||||
"VOLUME": "Volume Level",
|
||||
"AUTOPLAY": "Autoplay Status",
|
||||
"SHUFFLE": "Shuffle Status",
|
||||
"REPEAT": "Repeat Status",
|
||||
"TRACK_NAME": "Song Name",
|
||||
}
|
||||
PLAYER_EVENTS = {
|
||||
"start": "Playing Song",
|
||||
"change": "Changing Song",
|
||||
}
|
||||
|
||||
LOG_FILE = "/home/nathan/Git/spotifyd-webui/server.log"
|
||||
|
||||
def main():
|
||||
with open(LOG_FILE, "a") as f:
|
||||
# Get PLAYER_EVENT from environment variables
|
||||
player_event = os.getenv("PLAYER_EVENT", "Unknown Event")
|
||||
player_event = PLAYER_EVENTS.get(player_event, None)
|
||||
if player_event:
|
||||
f.write(player_event + "\n")
|
||||
for key, value in os.environ.items():
|
||||
# Only log specific environment variables using format {ENVLOGS[key]}: value
|
||||
if key in ENVLOGS:
|
||||
f.write(f"{ENVLOGS[key]}: {value}\n")
|
||||
|
||||
# Save current name and cover
|
||||
track_name = os.getenv("TRACK_NAME", None)
|
||||
track_cover = os.getenv("TRACK_COVER", None)
|
||||
if track_name and track_cover:
|
||||
with open("current_track.json", "w") as f:
|
||||
f.write(f'{{"name": "{track_name}", "cover": "{track_cover}"}}\n')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -8,15 +8,12 @@ h1 {
|
||||
padding: 0;
|
||||
}
|
||||
.centre {
|
||||
margin-top: 10%;
|
||||
margin-top: 6%;
|
||||
text-align: center;
|
||||
}
|
||||
a {
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
|
||||
.spacer {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
/* Mike section styling */
|
||||
@@ -26,32 +23,78 @@ a:hover {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: 20px;
|
||||
background-color: rgba(50, 50, 50, 0.3);
|
||||
border-radius: 8px;
|
||||
background-color: rgba(50, 50, 50, 0.5);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.25);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mike-section h2 {
|
||||
color: #f0f0f0;
|
||||
margin-top: 0;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.mike-section p {
|
||||
line-height: 1.6;
|
||||
margin-bottom: 15px;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.spotify-logo {
|
||||
width: 90%;
|
||||
object-fit: cover;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.18);
|
||||
background: #222;
|
||||
border: 2px solid #444;
|
||||
}
|
||||
|
||||
#current-track-section {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#current-track {
|
||||
font-size: 1.2em;
|
||||
color: #fff;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
#logs-section {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
#server-logs {
|
||||
font-family: monospace;
|
||||
font-size: 14px;
|
||||
background: #222;
|
||||
color: #eee;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
text-align: left;
|
||||
box-shadow: 0 1px 6px rgba(0,0,0,0.12);
|
||||
}
|
||||
|
||||
/* Button improvements */
|
||||
.button {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
background-color: #ffffff;
|
||||
color: #000000;
|
||||
padding: 12px 28px;
|
||||
background-color: #1db954;
|
||||
color: #fff;
|
||||
border-radius: 5px;
|
||||
text-decoration: none;
|
||||
transition: background-color 0.3s ease;
|
||||
border: 1px solid #ffffff;
|
||||
transition: background 0.2s, color 0.2s;
|
||||
border: none;
|
||||
font-size: 1.1em;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 1px 6px rgba(0,0,0,0.10);
|
||||
margin-top: 18px;
|
||||
}
|
||||
.button:hover {
|
||||
background-color: #000000;
|
||||
color: #ffffff;
|
||||
border: 1px solid #ffffff;
|
||||
background-color: #14833b;
|
||||
color: #fff;
|
||||
}
|
||||
@@ -17,7 +17,41 @@
|
||||
</div>
|
||||
|
||||
<div class="spacer"></div>
|
||||
|
||||
|
||||
<!-- Current track -->
|
||||
<div class="mike-section" id="current-track-section">
|
||||
<img src="/assets/img/favicon.png" alt="Song Logo" class="spotify-logo" id="song-logo">
|
||||
<p id="current-track">Loading...</p>
|
||||
</div>
|
||||
|
||||
<div class="mike-section" id="logs-section">
|
||||
<h2>Server Logs</h2>
|
||||
<pre id="server-logs"
|
||||
style="background:#222;color:#eee;padding:10px;border-radius:5px;max-height:400px;overflow:auto;"></pre>
|
||||
</div>
|
||||
<script>
|
||||
async function fetchLogs() {
|
||||
const res = await fetch('/api/v1/logs');
|
||||
const data = await res.json();
|
||||
document.getElementById('server-logs').textContent = data.logs;
|
||||
}
|
||||
fetchLogs();
|
||||
setInterval(fetchLogs, 5000); // Refresh logs every 5 seconds
|
||||
|
||||
async function fetchCurrentTrack() {
|
||||
const res = await fetch('/api/v1/current_track');
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
document.getElementById('current-track').textContent = data.name;
|
||||
document.getElementById('song-logo').src = data.cover || '/assets/img/favicon.png';
|
||||
} else {
|
||||
document.getElementById('current-track').textContent = 'No current track data available';
|
||||
}
|
||||
}
|
||||
fetchCurrentTrack();
|
||||
setInterval(fetchCurrentTrack, 5000); // Refresh current track every 5 seconds
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user