inital version, simple layout and functionality
This commit is contained in:
BIN
__pycache__/yamlcon.cpython-312.pyc
Normal file
BIN
__pycache__/yamlcon.cpython-312.pyc
Normal file
Binary file not shown.
1
code
Normal file
1
code
Normal file
@@ -0,0 +1 @@
|
|||||||
|
AQA8p4IgPYH0j9TIUFR4m5zAKPgvPzhvW07iCLAUYq-7sWyhxTTDRiWZaocH01HomhYoGZnVarc2SGUH4Buu9ox22lQ0e7z4AnglirOqF8I24v4zZveuiOPuLjet4YFY9rCY9TdtGmaUE1Y710tdgF7_ZBT4V2fuFSM0i9qXML37rZBS_DVmWlbVyLi-RKOeT-EIktYsTNfV45ajoe-GCB7eL5eFit8nGGjBokIXM0w14m004GtnvjfcazBaJqS6XlqU80VB82OTQauN7fyckTEYMUalhfM20ZE2p8D7qCg
|
||||||
150
spotifycontroller.py
Normal file
150
spotifycontroller.py
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
from flask import Flask, render_template, request, url_for, redirect
|
||||||
|
import time
|
||||||
|
import requests
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
import webbrowser
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
client_id = '1cb8bc27872c4bcaaad0e95f123b4f7d'
|
||||||
|
client_secret = '9893dfb6d9eb43eebf082f9173ce937c'
|
||||||
|
redirect_uri = 'http://127.0.0.1:8888/callback'
|
||||||
|
encoded_creds = base64.b64encode(client_id.encode() + b':' + client_secret.encode()).decode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"client_id": client_id,
|
||||||
|
"response_type": "code",
|
||||||
|
"redirect_uri": redirect_uri,
|
||||||
|
"scope": "user-read-playback-state,user-modify-playback-state,user-library-read,user-library-modify"
|
||||||
|
}
|
||||||
|
|
||||||
|
token_headers = {
|
||||||
|
"Authorization": "Basic " + encoded_creds,
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded"
|
||||||
|
}
|
||||||
|
|
||||||
|
song_info = {
|
||||||
|
'name': "None",
|
||||||
|
'artist': "None",
|
||||||
|
'album': "None",
|
||||||
|
'image': "None"
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
global code
|
||||||
|
if os.path.exists('code'):
|
||||||
|
with open('code', 'r') as file:
|
||||||
|
code = file.read()
|
||||||
|
return redirect(url_for('webapp'))
|
||||||
|
else:
|
||||||
|
return redirect("https://accounts.spotify.com/authorize?" + urlencode(headers))
|
||||||
|
# return render_template('index.html')
|
||||||
|
|
||||||
|
# @app.route('/update')
|
||||||
|
# def update():
|
||||||
|
# # Generate new information
|
||||||
|
# new_info = generate_new_info()
|
||||||
|
|
||||||
|
# # Wait for 1 second
|
||||||
|
# time.sleep(1)
|
||||||
|
|
||||||
|
# return new_info
|
||||||
|
|
||||||
|
@app.route('/webapp', methods=['GET', 'POST'])
|
||||||
|
def webapp():
|
||||||
|
global access_token
|
||||||
|
# NEED TO FIND A BETTER WAY THAT DOESN'T INVOLVE A TRY EXCEPT BLOCK
|
||||||
|
if 'code' not in globals() or not code:
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
token_data = {
|
||||||
|
"grant_type": "authorization_code",
|
||||||
|
"code": code,
|
||||||
|
"redirect_uri": redirect_uri
|
||||||
|
}
|
||||||
|
|
||||||
|
access_object = requests.post("https://accounts.spotify.com/api/token", data=token_data, headers=token_headers)
|
||||||
|
if "error" in access_object.json():
|
||||||
|
if os.path.exists('code'):
|
||||||
|
os.remove('code')
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
access_token = access_object.json()["access_token"]
|
||||||
|
|
||||||
|
return render_template('webapp.html', song_info=song_info)
|
||||||
|
|
||||||
|
@app.route('/callback', methods=['GET'])
|
||||||
|
def callback():
|
||||||
|
global code
|
||||||
|
code = request.args.get('code')
|
||||||
|
with open('code', 'w') as file:
|
||||||
|
file.write(code)
|
||||||
|
return redirect(url_for('webapp'))
|
||||||
|
|
||||||
|
@app.route('/appdata')
|
||||||
|
def appdata():
|
||||||
|
global access_token
|
||||||
|
user_headers = {
|
||||||
|
"Authorization": "Bearer " + access_token,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
currently_playing = requests.get("https://api.spotify.com/v1/me/player/currently-playing", headers=user_headers)
|
||||||
|
|
||||||
|
|
||||||
|
if currently_playing.json()["is_playing"] == True:
|
||||||
|
return { 'id': currently_playing.json()["item"]["id"],
|
||||||
|
'name': currently_playing.json()["item"]["name"],
|
||||||
|
'artist': currently_playing.json()["item"]["artists"][0]["name"],
|
||||||
|
'album': currently_playing.json()["item"]["album"]["name"],
|
||||||
|
'image': currently_playing.json()["item"]["album"]["images"][0]["url"],
|
||||||
|
'is_playing': currently_playing.json()["is_playing"],
|
||||||
|
'progress_ms': currently_playing.json()["progress_ms"],
|
||||||
|
'duration_ms': currently_playing.json()["item"]["duration_ms"],
|
||||||
|
'is_liked': requests.get("https://api.spotify.com/v1/me/tracks/contains?ids=" + currently_playing.json()["item"]["id"], headers=user_headers).json()[0]
|
||||||
|
}
|
||||||
|
elif currently_playing.json()["is_playing"] == False:
|
||||||
|
return { 'name': "Not Playing",
|
||||||
|
'artist': "Not Playing",
|
||||||
|
'album': "Not Playing",
|
||||||
|
'image': "Not Playing"
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return { 'name': "Error",
|
||||||
|
'artist': "Error",
|
||||||
|
'album': "Error",
|
||||||
|
'image': "Error"
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.route('/control', methods=['POST'])
|
||||||
|
def control():
|
||||||
|
global access_token
|
||||||
|
user_headers = {
|
||||||
|
"Authorization": "Bearer " + access_token,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.form.get('command') == "pause":
|
||||||
|
requests.put("https://api.spotify.com/v1/me/player/pause", headers=user_headers)
|
||||||
|
elif request.form.get('command') == "play":
|
||||||
|
requests.put("https://api.spotify.com/v1/me/player/play", headers=user_headers)
|
||||||
|
elif request.form.get('command') == "next":
|
||||||
|
requests.post("https://api.spotify.com/v1/me/player/next", headers=user_headers)
|
||||||
|
elif request.form.get('command') == "previous":
|
||||||
|
requests.post("https://api.spotify.com/v1/me/player/previous", headers=user_headers)
|
||||||
|
elif request.form.get('command') == "restart":
|
||||||
|
requests.put("https://api.spotify.com/v1/me/player/seek?position_ms=0", headers=user_headers)
|
||||||
|
elif request.form.get('command') == "like":
|
||||||
|
requests.put("https://api.spotify.com/v1/me/tracks?ids=" + request.form.get('id'), headers=user_headers)
|
||||||
|
elif request.form.get('command') == "unlike":
|
||||||
|
requests.delete("https://api.spotify.com/v1/me/tracks?ids=" + request.form.get('id'), headers=user_headers)
|
||||||
|
|
||||||
|
print(request.form.get('command'))
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(port=8888, debug=True)
|
||||||
158
templates/webapp.html
Normal file
158
templates/webapp.html
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Updating Value</title>
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||||
|
<style>
|
||||||
|
.progress-container {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
width: 0%;
|
||||||
|
height: 4px;
|
||||||
|
background-color: #d734e9;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 4px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.song-text {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
.artist-text {
|
||||||
|
font-size: 15px;
|
||||||
|
text-align: center;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
.middle {
|
||||||
|
text-align: center;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<img id="image" src="{{ song_info['image'] }}" alt="Song Image" type="image/jpeg" style="max-width: 300px; max-height: 300px;">
|
||||||
|
<div class="middle">
|
||||||
|
<p id="name" class="song-text">{{ song_info['name'] }}</p>
|
||||||
|
<p id="artist" class="artist-text">{{ song_info['artist'] }}</p>
|
||||||
|
<!-- <p id="album" class="song-info">{{ song_info['album'] }}</p>
|
||||||
|
<p id="progress_ms">{{ song_info['progress_ms'] }}</p>
|
||||||
|
<p id="duration_ms">{{ song_info['duration_ms'] }}</p> -->
|
||||||
|
<p>
|
||||||
|
<button id="playpause">Play/Pause</button>
|
||||||
|
<button id="previous">Previous</button>
|
||||||
|
<button id="next">Next</button>
|
||||||
|
<button id="like">Like/Unlike</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="progress-container">
|
||||||
|
<div id="progress_bar" class="progress-bar"></div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
var is_playing = false
|
||||||
|
var id = ''
|
||||||
|
// Function to update the values every second
|
||||||
|
function updateValues() {
|
||||||
|
$.ajax({
|
||||||
|
url: '/appdata',
|
||||||
|
success: function(data) {
|
||||||
|
$('#name').text(data['name']);
|
||||||
|
$('#artist').text(data['artist']);
|
||||||
|
$('#album').text(data['album']);
|
||||||
|
$('#image').attr('src', data['image']);
|
||||||
|
$('#is_playing').text(data['is_playing']);
|
||||||
|
$('#progress_ms').text(data['progress_ms']);
|
||||||
|
$('#duration_ms').text(data['duration_ms']);
|
||||||
|
// $('#progress_min').text(Math.floor(data['progress_ms'] / (1000 * 60)) % 60);
|
||||||
|
// $('#progress_sec').text(Math.floor((data['progress_ms'] / 1000) % 60));
|
||||||
|
// $('#duration_min').text(Math.floor(data['duration_ms'] / (1000 * 60) % 60));
|
||||||
|
// $('#duration_sec').text(Math.floor(data['duration_ms'] / 1000) % 60);
|
||||||
|
$('#is_liked').text(data['is_liked']);
|
||||||
|
|
||||||
|
id = data['id'];
|
||||||
|
is_playing = data['is_playing'];
|
||||||
|
is_liked = data['is_liked'];
|
||||||
|
|
||||||
|
document.getElementById('progress_bar').style.width = (data['progress_ms'] / data['duration_ms']) * 100 + '%';
|
||||||
|
// document.getElementById('progress_bar').textContent = (Math.floor(data['progress_ms'] / (1000 * 60)) % 60) + ':' + (Math.floor((data['progress_ms'] / 1000) % 60)).toString().padStart(2, '0');
|
||||||
|
|
||||||
|
if (is_playing) {
|
||||||
|
// Change button to play icon
|
||||||
|
document.getElementById('playpause').textContent = 'Pause';
|
||||||
|
} else {
|
||||||
|
// Change button to pause icon
|
||||||
|
document.getElementById('playpause').textContent = 'Play';
|
||||||
|
}
|
||||||
|
if (is_liked) {
|
||||||
|
// Change button to unlike text
|
||||||
|
document.getElementById('like').textContent = 'Unlike';
|
||||||
|
} else {
|
||||||
|
// Change button to like text
|
||||||
|
document.getElementById('like').textContent = 'Like';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Update values every second
|
||||||
|
setInterval(updateValues, 3000);
|
||||||
|
|
||||||
|
// BUTTONS
|
||||||
|
$('#playpause').click(function() {
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: '/control',
|
||||||
|
data: {command: (is_playing ? 'pause' : 'play')}, // Predefined command
|
||||||
|
success: function(response) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$('#pause').click(function() {
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: '/control',
|
||||||
|
data: {command: 'pause'}, // Predefined command
|
||||||
|
success: function(response) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$('#next').click(function() {
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: '/control',
|
||||||
|
data: {command: 'next'}, // Predefined command
|
||||||
|
success: function(response) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$('#previous').click(function() {
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: '/control',
|
||||||
|
data: {command: (progress_ms < 5000) ? 'restart' : 'previous'},
|
||||||
|
success: function(response) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$('#like').click(function() {
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: '/control',
|
||||||
|
data: {command: (is_liked ? 'unlike' : 'like'), id: id},
|
||||||
|
success: function(response) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
12
todo.txt
Normal file
12
todo.txt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
song name is successfully on the website.
|
||||||
|
next task:
|
||||||
|
have this update every 1 or 2 seconds.
|
||||||
|
|
||||||
|
|
||||||
|
04-18-24:
|
||||||
|
make it so when the js app calls the /appdata endpoint it sends the song id,
|
||||||
|
ONLY if the song id is different then the fetched one should the endpoint
|
||||||
|
provide updated song info, otherwise have it return just song progress ONLY.
|
||||||
|
|
||||||
|
include color calculation, probably in python then give color value
|
||||||
|
to js app, but maybe have js app do it from album art, explore which is better.
|
||||||
19
yamlcon.py
Normal file
19
yamlcon.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import yaml
|
||||||
|
|
||||||
|
def load(path):
|
||||||
|
with open(path, 'r') as file:
|
||||||
|
try:
|
||||||
|
data = yaml.safe_load(file)
|
||||||
|
return data
|
||||||
|
except yaml.YAMLError as e:
|
||||||
|
print(f"Error reading YAML file {path}: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def save(path, data):
|
||||||
|
with open(path, 'w') as file:
|
||||||
|
try:
|
||||||
|
yaml.dump(data, file)
|
||||||
|
return True
|
||||||
|
except yaml.YAMLError as e:
|
||||||
|
print(f"Error writing to YAML file {path}: {e}")
|
||||||
|
return None
|
||||||
Reference in New Issue
Block a user