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()
|
dotenv.load_dotenv()
|
||||||
|
|
||||||
|
SPEAKER_NAME = os.getenv("SPEAKER_NAME", "Family Room Speakers")
|
||||||
|
|
||||||
app = Flask(__name__)
|
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):
|
def find(name, path):
|
||||||
for root, dirs, files in os.walk(path):
|
for root, dirs, files in os.walk(path):
|
||||||
if name in files:
|
if name in files:
|
||||||
@@ -85,11 +100,21 @@ def restart():
|
|||||||
"""
|
"""
|
||||||
# Execute a `pkill spotifyd` command to stop the spotifyd process
|
# Execute a `pkill spotifyd` command to stop the spotifyd process
|
||||||
status = os.system("pkill spotifyd")
|
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
|
# 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('/')
|
return redirect('/')
|
||||||
|
|
||||||
@@ -141,6 +166,35 @@ def api_data():
|
|||||||
return jsonify(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
|
# 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;
|
padding: 0;
|
||||||
}
|
}
|
||||||
.centre {
|
.centre {
|
||||||
margin-top: 10%;
|
margin-top: 6%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
a {
|
|
||||||
color: #ffffff;
|
.spacer {
|
||||||
text-decoration: none;
|
height: 40px;
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mike section styling */
|
/* Mike section styling */
|
||||||
@@ -26,32 +23,78 @@ a:hover {
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
background-color: rgba(50, 50, 50, 0.3);
|
background-color: rgba(50, 50, 50, 0.5);
|
||||||
border-radius: 8px;
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0,0,0,0.25);
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mike-section h2 {
|
.mike-section h2 {
|
||||||
color: #f0f0f0;
|
color: #f0f0f0;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
font-size: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mike-section p {
|
.mike-section p {
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
margin-bottom: 15px;
|
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 {
|
.button {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 10px 20px;
|
padding: 12px 28px;
|
||||||
background-color: #ffffff;
|
background-color: #1db954;
|
||||||
color: #000000;
|
color: #fff;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: background-color 0.3s ease;
|
transition: background 0.2s, color 0.2s;
|
||||||
border: 1px solid #ffffff;
|
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 {
|
.button:hover {
|
||||||
background-color: #000000;
|
background-color: #14833b;
|
||||||
color: #ffffff;
|
color: #fff;
|
||||||
border: 1px solid #ffffff;
|
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,41 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="spacer"></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>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user