161 lines
6.0 KiB
Python
161 lines
6.0 KiB
Python
import time
|
|
import os
|
|
import threading
|
|
import subprocess
|
|
from database import SessionLocal, Track
|
|
from navidrome import navidrome_client
|
|
from analyzer import analyze_audio
|
|
from queue_manager import get_active_profile, set_active_profile
|
|
import json
|
|
import datetime
|
|
|
|
def sync_tracks_loop():
|
|
while True:
|
|
try:
|
|
if not navidrome_client.url:
|
|
time.sleep(10)
|
|
continue
|
|
|
|
print("Syncing tracks from Navidrome...")
|
|
songs = navidrome_client.get_random_songs(count=200)
|
|
db = SessionLocal()
|
|
added = 0
|
|
for song in songs:
|
|
# Check if song exists
|
|
existing = db.query(Track).filter(Track.id == song["id"]).first()
|
|
if not existing:
|
|
new_track = Track(
|
|
id=song["id"],
|
|
title=song.get("title", "Unknown"),
|
|
artist=song.get("artist", "Unknown"),
|
|
album=song.get("album", "Unknown"),
|
|
duration=song.get("duration", 0),
|
|
path=song.get("path", "")
|
|
)
|
|
db.add(new_track)
|
|
added += 1
|
|
if added > 0:
|
|
db.commit()
|
|
db.close()
|
|
print(f"Sync complete. Added {added} new tracks.")
|
|
except Exception as e:
|
|
print(f"Error syncing tracks: {e}")
|
|
|
|
# Run every hour
|
|
time.sleep(3600)
|
|
|
|
def analyze_tracks_loop():
|
|
while True:
|
|
try:
|
|
if not navidrome_client.url:
|
|
time.sleep(10)
|
|
continue
|
|
|
|
db = SessionLocal()
|
|
# Find an un-analyzed track
|
|
track = db.query(Track).filter(Track.analyzed == False).first()
|
|
if track:
|
|
print(f"Analyzing track: {track.artist} - {track.title}")
|
|
temp_path = f"/tmp/{track.id}.audio"
|
|
|
|
# Download
|
|
if navidrome_client.download_song(track.id, temp_path):
|
|
# Analyze
|
|
metadata = analyze_audio(temp_path)
|
|
if metadata:
|
|
track.bpm = metadata["bpm"]
|
|
track.key = metadata["key"]
|
|
track.energy = metadata["energy"]
|
|
track.spectral_centroid = metadata["spectral_centroid"]
|
|
track.onset_density = metadata["onset_density"]
|
|
track.lufs = metadata["lufs"]
|
|
track.analyzed = True
|
|
db.commit()
|
|
print("Analysis successful.")
|
|
else:
|
|
# Mark as analyzed anyway so we don't get stuck in a loop, or maybe add an error flag
|
|
track.analyzed = True
|
|
db.commit()
|
|
print("Analysis failed, marked as analyzed to skip.")
|
|
|
|
# Cleanup
|
|
if os.path.exists(temp_path):
|
|
os.remove(temp_path)
|
|
else:
|
|
print("Download failed.")
|
|
else:
|
|
# No tracks to analyze, sleep for a while
|
|
time.sleep(60)
|
|
|
|
db.close()
|
|
except Exception as e:
|
|
print(f"Error in analysis loop: {e}")
|
|
time.sleep(60)
|
|
|
|
def scheduler_loop():
|
|
"""
|
|
Checks the auto_pilot_schedule every minute and changes the profile if it matches the current time.
|
|
"""
|
|
while True:
|
|
try:
|
|
# Wait until the start of the next minute
|
|
now = datetime.datetime.now()
|
|
sleep_seconds = 60 - now.second
|
|
time.sleep(sleep_seconds)
|
|
|
|
# Now it's a new minute
|
|
now = datetime.datetime.now()
|
|
current_time_str = now.strftime("%H:%M")
|
|
|
|
db = SessionLocal()
|
|
from database import get_setting
|
|
schedule_str = get_setting(db, "auto_pilot_schedule", "[]")
|
|
db.close()
|
|
|
|
schedule = json.loads(schedule_str)
|
|
for rule in schedule:
|
|
if rule.get("time") == current_time_str:
|
|
target_profile = rule.get("profile")
|
|
if target_profile:
|
|
print(f"[Scheduler] Time is {current_time_str}. Switching profile to: {target_profile}")
|
|
set_active_profile(target_profile)
|
|
|
|
except Exception as e:
|
|
print(f"Error in scheduler loop: {e}")
|
|
time.sleep(60)
|
|
|
|
def startup_manager_loop():
|
|
"""
|
|
Waits until at least 10 tracks have been analyzed before starting Liquidsoap.
|
|
This prevents Liquidsoap from rapidly requesting /next and queuing the exact same track
|
|
repeatedly when the database is empty or only has 1 track during initial setup.
|
|
"""
|
|
while True:
|
|
try:
|
|
db = SessionLocal()
|
|
count = db.query(Track).filter(Track.analyzed == True).count()
|
|
db.close()
|
|
|
|
if count >= 10:
|
|
print(f"[Startup Manager] {count} tracks analyzed. Starting Liquidsoap...")
|
|
# Run supervisorctl to start liquidsoap
|
|
subprocess.run(["supervisorctl", "start", "liquidsoap"], check=False)
|
|
break
|
|
else:
|
|
print(f"[Startup Manager] Waiting for initial analysis... ({count}/10 tracks analyzed)")
|
|
time.sleep(10)
|
|
except Exception as e:
|
|
print(f"Error in startup manager: {e}")
|
|
time.sleep(10)
|
|
|
|
def start_workers():
|
|
t1 = threading.Thread(target=sync_tracks_loop, daemon=True)
|
|
t2 = threading.Thread(target=analyze_tracks_loop, daemon=True)
|
|
t3 = threading.Thread(target=scheduler_loop, daemon=True)
|
|
t4 = threading.Thread(target=startup_manager_loop, daemon=True)
|
|
t1.start()
|
|
t2.start()
|
|
t3.start()
|
|
t4.start()
|
|
|