forked from CopyBot/sptnr
feat: web server
This commit is contained in:
+2
-1
@@ -1,3 +1,4 @@
|
|||||||
logs/*.log
|
data/
|
||||||
.env
|
.env
|
||||||
docker-compose.yml
|
docker-compose.yml
|
||||||
|
__pycache__
|
||||||
+8
-2
@@ -10,5 +10,11 @@ COPY . .
|
|||||||
# Install any needed packages specified in requirements.txt
|
# Install any needed packages specified in requirements.txt
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
# Use an entrypoint script
|
# Expose port 5000 for the Flask app
|
||||||
ENTRYPOINT ["python", "./sptnr.py"]
|
EXPOSE 5000
|
||||||
|
|
||||||
|
# Set the entrypoint to Python
|
||||||
|
ENTRYPOINT ["python"]
|
||||||
|
|
||||||
|
# Leave CMD empty
|
||||||
|
CMD []
|
||||||
|
|||||||
@@ -15,5 +15,9 @@ services:
|
|||||||
- NAV_PASS=your_navidrome_password
|
- NAV_PASS=your_navidrome_password
|
||||||
- SPOTIFY_CLIENT_ID=your_spotify_client_id
|
- SPOTIFY_CLIENT_ID=your_spotify_client_id
|
||||||
- SPOTIFY_CLIENT_SECRET=your_spotify_client_secret
|
- SPOTIFY_CLIENT_SECRET=your_spotify_client_secret
|
||||||
|
- WEB_API_KEY=changeme
|
||||||
volumes:
|
volumes:
|
||||||
- ./logs:/usr/src/app/logs
|
- ./data:/usr/src/app/data
|
||||||
|
ports:
|
||||||
|
- "3333:3333"
|
||||||
|
command: web_server.py
|
||||||
|
|||||||
+4
-1
@@ -1,4 +1,7 @@
|
|||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
python-dotenv==1.0.0
|
python-dotenv==1.0.0
|
||||||
colorama==0.4.6
|
colorama==0.4.6
|
||||||
tqdm==4.66.1
|
tqdm==4.66.1
|
||||||
|
gunicorn==20.1.0
|
||||||
|
Flask==2.1.2
|
||||||
|
Werkzeug==2.0.3
|
||||||
@@ -41,7 +41,7 @@ BOLD = Style.BRIGHT
|
|||||||
RESET = Style.RESET_ALL
|
RESET = Style.RESET_ALL
|
||||||
|
|
||||||
# Setup logs
|
# Setup logs
|
||||||
LOG_DIR = "logs"
|
LOG_DIR = "data/logs"
|
||||||
if not os.path.exists(LOG_DIR):
|
if not os.path.exists(LOG_DIR):
|
||||||
os.makedirs(LOG_DIR)
|
os.makedirs(LOG_DIR)
|
||||||
|
|
||||||
@@ -103,6 +103,9 @@ TOTAL_TRACKS = 0
|
|||||||
FOUND_AND_UPDATED = 0
|
FOUND_AND_UPDATED = 0
|
||||||
NOT_FOUND = 0
|
NOT_FOUND = 0
|
||||||
UNMATCHED_TRACKS = []
|
UNMATCHED_TRACKS = []
|
||||||
|
PROCESSED_ALBUMS_FILE = "data/processed_albums.txt"
|
||||||
|
|
||||||
|
processed_albums = set()
|
||||||
|
|
||||||
# Parse arguments
|
# Parse arguments
|
||||||
description_text = "process command-line flags for sync"
|
description_text = "process command-line flags for sync"
|
||||||
@@ -147,6 +150,13 @@ parser.add_argument(
|
|||||||
"-v", "--version", action="version", version=f"%(prog)s {__version__}"
|
"-v", "--version", action="version", version=f"%(prog)s {__version__}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"-f",
|
||||||
|
"--force",
|
||||||
|
action="store_true",
|
||||||
|
help="force processing of all albums, even if they were processed previously",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
@@ -295,6 +305,13 @@ def process_track(track_id, artist_name, album, track_name):
|
|||||||
|
|
||||||
|
|
||||||
def process_album(album_id):
|
def process_album(album_id):
|
||||||
|
if not args.force:
|
||||||
|
global processed_albums
|
||||||
|
|
||||||
|
if album_id in processed_albums:
|
||||||
|
logging.info(f" {LIGHT_CYAN}Skipping already processed album{RESET}")
|
||||||
|
return
|
||||||
|
|
||||||
nav_url = f"{NAV_BASE_URL}/rest/getAlbum?id={album_id}&u={NAV_USER}&p=enc:{HEX_ENCODED_PASS}&v=1.12.0&c=spotify_sync&f=json"
|
nav_url = f"{NAV_BASE_URL}/rest/getAlbum?id={album_id}&u={NAV_USER}&p=enc:{HEX_ENCODED_PASS}&v=1.12.0&c=spotify_sync&f=json"
|
||||||
response = requests.get(nav_url).json()
|
response = requests.get(nav_url).json()
|
||||||
|
|
||||||
@@ -308,6 +325,10 @@ def process_album(album_id):
|
|||||||
for track in tracks:
|
for track in tracks:
|
||||||
process_track(*track)
|
process_track(*track)
|
||||||
|
|
||||||
|
processed_albums.add(album_id)
|
||||||
|
with open(PROCESSED_ALBUMS_FILE, "a") as file:
|
||||||
|
file.write(f"{album_id}\n")
|
||||||
|
|
||||||
|
|
||||||
def process_artist(artist_id):
|
def process_artist(artist_id):
|
||||||
nav_url = f"{NAV_BASE_URL}/rest/getArtist?id={artist_id}&u={NAV_USER}&p=enc:{HEX_ENCODED_PASS}&v=1.12.0&c=spotify_sync&f=json"
|
nav_url = f"{NAV_BASE_URL}/rest/getArtist?id={artist_id}&u={NAV_USER}&p=enc:{HEX_ENCODED_PASS}&v=1.12.0&c=spotify_sync&f=json"
|
||||||
@@ -360,6 +381,11 @@ def fetch_data(url):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
# Load processed albums
|
||||||
|
if os.path.exists(PROCESSED_ALBUMS_FILE) and not args.force:
|
||||||
|
with open(PROCESSED_ALBUMS_FILE, "r") as file:
|
||||||
|
processed_albums = set(file.read().splitlines())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
validate_url(NAV_BASE_URL)
|
validate_url(NAV_BASE_URL)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
@@ -426,14 +452,26 @@ else:
|
|||||||
|
|
||||||
# Display the results
|
# Display the results
|
||||||
logging.info("")
|
logging.info("")
|
||||||
MATCH_PERCENTAGE = (FOUND_AND_UPDATED / TOTAL_TRACKS) * 100 if TOTAL_TRACKS != 0 else 0
|
|
||||||
FORMATTED_MATCH_PERCENTAGE = round(MATCH_PERCENTAGE, 2) # Rounding to 2 decimal places
|
# Check if TOTAL_TRACKS is zero to avoid division by zero error
|
||||||
|
if TOTAL_TRACKS > 0:
|
||||||
|
MATCH_PERCENTAGE = (FOUND_AND_UPDATED / TOTAL_TRACKS) * 100
|
||||||
|
else:
|
||||||
|
MATCH_PERCENTAGE = 0
|
||||||
|
|
||||||
|
FORMATTED_MATCH_PERCENTAGE = round(MATCH_PERCENTAGE, 2)
|
||||||
TOTAL_BLOCKS = 20
|
TOTAL_BLOCKS = 20
|
||||||
|
|
||||||
color_found = LIGHT_GREEN if FOUND_AND_UPDATED == TOTAL_TRACKS else LIGHT_YELLOW
|
color_found = LIGHT_GREEN if FOUND_AND_UPDATED == TOTAL_TRACKS else LIGHT_YELLOW
|
||||||
color_found_white = LIGHT_GREEN if FOUND_AND_UPDATED == TOTAL_TRACKS else BOLD
|
color_found_white = LIGHT_GREEN if FOUND_AND_UPDATED == TOTAL_TRACKS else BOLD
|
||||||
color_not_found = LIGHT_GREEN if NOT_FOUND == 0 else LIGHT_RED
|
color_not_found = LIGHT_GREEN if NOT_FOUND == 0 else LIGHT_RED
|
||||||
blocks_found = "█" * round(FOUND_AND_UPDATED * TOTAL_BLOCKS / TOTAL_TRACKS)
|
|
||||||
|
# Adjust the progress bar calculation
|
||||||
|
blocks_found = (
|
||||||
|
"█" * round(FOUND_AND_UPDATED * TOTAL_BLOCKS / TOTAL_TRACKS)
|
||||||
|
if TOTAL_TRACKS > 0
|
||||||
|
else ""
|
||||||
|
)
|
||||||
blocks_not_found = "█" * (TOTAL_BLOCKS - len(blocks_found))
|
blocks_not_found = "█" * (TOTAL_BLOCKS - len(blocks_found))
|
||||||
full_blocks_found = f"{color_found_white}{blocks_found}{RESET}"
|
full_blocks_found = f"{color_found_white}{blocks_found}{RESET}"
|
||||||
full_blocks_not_found = f"{color_not_found}{blocks_not_found}{RESET}"
|
full_blocks_not_found = f"{color_not_found}{blocks_not_found}{RESET}"
|
||||||
@@ -453,7 +491,6 @@ if seconds or not parts: # Show seconds if it's the only value, even if it's 0
|
|||||||
|
|
||||||
formatted_elapsed_time = " ".join(parts)
|
formatted_elapsed_time = " ".join(parts)
|
||||||
|
|
||||||
# logging.info(f"Processing completed in {int(hours):02}:{int(minutes):02}:{int(seconds):02}")
|
|
||||||
logging.info(
|
logging.info(
|
||||||
f"Tracks: {LIGHT_PURPLE}{TOTAL_TRACKS}{RESET} | Found: {color_found}{FOUND_AND_UPDATED}{RESET} |{full_blocks_found}{full_blocks_not_found}| Not Found: {color_not_found}{NOT_FOUND}{RESET} | Match: {color_found}{FORMATTED_MATCH_PERCENTAGE}%{RESET} | Time: {LIGHT_PURPLE}{formatted_elapsed_time}{RESET}"
|
f"Tracks: {LIGHT_PURPLE}{TOTAL_TRACKS}{RESET} | Found: {color_found}{FOUND_AND_UPDATED}{RESET} |{full_blocks_found}{full_blocks_not_found}| Not Found: {color_not_found}{NOT_FOUND}{RESET} | Match: {color_found}{FORMATTED_MATCH_PERCENTAGE}%{RESET} | Time: {LIGHT_PURPLE}{formatted_elapsed_time}{RESET}"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
from flask import Flask, request, jsonify
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import threading
|
||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
sptnr_web_server = Flask(__name__)
|
||||||
|
API_KEY = os.getenv("WEB_API_KEY")
|
||||||
|
LOG_DIR = "data/logs"
|
||||||
|
|
||||||
|
|
||||||
|
def run_script(cmd):
|
||||||
|
subprocess.run(cmd)
|
||||||
|
|
||||||
|
|
||||||
|
def log_post_data(data):
|
||||||
|
timestamp = datetime.datetime.now().isoformat()
|
||||||
|
log_filename = os.path.join(LOG_DIR, f"log_{timestamp}.txt")
|
||||||
|
with open(log_filename, "w") as log_file:
|
||||||
|
json.dump(data, log_file, indent=4)
|
||||||
|
|
||||||
|
|
||||||
|
@sptnr_web_server.route("/process", methods=["GET", "POST"])
|
||||||
|
def process_request():
|
||||||
|
if request.args.get("api_key") != API_KEY:
|
||||||
|
return jsonify({"error": "Unauthorized"}), 401
|
||||||
|
|
||||||
|
cmd = ["python3", "sptnr.py"]
|
||||||
|
if request.args.get("preview", "") == "true":
|
||||||
|
cmd.append("--preview")
|
||||||
|
if request.args.get("force", "") == "true":
|
||||||
|
cmd.append("--force")
|
||||||
|
artist_ids = request.args.getlist("artist")
|
||||||
|
for artist_id in artist_ids:
|
||||||
|
cmd.extend(["--artist", artist_id])
|
||||||
|
album_ids = request.args.getlist("album")
|
||||||
|
for album_id in album_ids:
|
||||||
|
cmd.extend(["--album", album_id])
|
||||||
|
start = request.args.get("start")
|
||||||
|
if start:
|
||||||
|
cmd.extend(["--start", start])
|
||||||
|
limit = request.args.get("limit")
|
||||||
|
if limit:
|
||||||
|
cmd.extend(["--limit", limit])
|
||||||
|
|
||||||
|
thread = threading.Thread(target=run_script, args=(cmd,))
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
return jsonify({"message": "Processing started"})
|
||||||
|
|
||||||
|
|
||||||
|
@sptnr_web_server.route("/logs")
|
||||||
|
def list_logs():
|
||||||
|
try:
|
||||||
|
logs = os.listdir(LOG_DIR)
|
||||||
|
logs = [log for log in logs if log.endswith(".log")] # Filter log files
|
||||||
|
|
||||||
|
log_links = "".join(f'<li><a href="/logs/{log}">{log}</a></li>' for log in logs)
|
||||||
|
return f"<h1>Log Files</h1><ul>{log_links}</ul>"
|
||||||
|
except Exception as e:
|
||||||
|
return f"An error occurred: {e}", 500
|
||||||
|
|
||||||
|
|
||||||
|
@sptnr_web_server.route("/logs/<filename>")
|
||||||
|
def view_log(filename):
|
||||||
|
try:
|
||||||
|
full_path = os.path.join(LOG_DIR, filename)
|
||||||
|
if not os.path.exists(full_path) or not os.path.isfile(full_path):
|
||||||
|
return "File not found", 404
|
||||||
|
|
||||||
|
with open(full_path, "r") as file:
|
||||||
|
content = file.read()
|
||||||
|
|
||||||
|
# Convert content to HTML-friendly format
|
||||||
|
content = content.replace("\n", "<br>")
|
||||||
|
return f'<h1>{filename}</h1><div style="white-space: pre-wrap;">{content}</div>'
|
||||||
|
except Exception as e:
|
||||||
|
return f"An error occurred: {e}", 500
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sptnr_web_server.run(debug=False, host="0.0.0.0", port=3333)
|
||||||
Reference in New Issue
Block a user