Files
jellyplist/app/__init__.py
2024-10-11 15:02:37 +00:00

189 lines
7.7 KiB
Python

import atexit
from logging.handlers import RotatingFileHandler
import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
import spotipy
from spotipy.oauth2 import SpotifyOAuth, SpotifyClientCredentials
from celery import Celery
from config import Config
from jellyfin.client import JellyfinClient
import logging
from spotdl import Spotdl
from spotdl.utils.config import DEFAULT_CONFIG
app = Flask(__name__, template_folder="../templates")
app.config.from_object(Config)
sp = spotipy.Spotify(auth_manager= SpotifyClientCredentials(client_id=app.config['SPOTIPY_CLIENT_ID'], client_secret=app.config['SPOTIPY_CLIENT_SECRET']))
jellyfin = JellyfinClient(app.config['JELLYFIN_SERVER_URL'])
spotdl_config = DEFAULT_CONFIG
spotdl_config['cookie_file'] = '/jellyplist/cookies.txt'
spotdl_config['output'] = '/storage/media/music/_spotify_playlists/{track-id}'
spotdl_config['threads'] = 12
spotdl = Spotdl( app.config['SPOTIPY_CLIENT_ID'],app.config['SPOTIPY_CLIENT_SECRET'], downloader_settings=spotdl_config)
# Configurations
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://jellyplist:jellyplist@192.168.178.14/jellyplist'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
migrate = Migrate(app, db)
# Configure Logging
log_file = 'app.log'
handler = RotatingFileHandler(log_file, maxBytes=100000, backupCount=3) # 100KB per file, with 3 backups
handler.setLevel(logging.INFO)
app.logger.info('Application started')
from app import routes, models
from app.models import JellyfinUser,Track,Playlist
from apscheduler.schedulers.background import BackgroundScheduler
# Initialize the scheduler
scheduler = BackgroundScheduler()
def update_all_playlists_track_status():
"""
Update track_count and tracks_available for all playlists in the database.
For each track, check if the file exists on the filesystem. If not, reset the downloaded flag and filesystem_path.
"""
with app.app_context():
playlists = Playlist.query.all()
if not playlists:
app.logger.info("No playlists found.")
return
for playlist in playlists:
total_tracks = 0
available_tracks = 0
for track in playlist.tracks:
total_tracks += 1
# Check if the file exists
if track.filesystem_path and os.path.exists(track.filesystem_path):
available_tracks += 1
else:
# If the file doesn't exist, reset the 'downloaded' flag and 'filesystem_path'
track.downloaded = False
track.filesystem_path = None
db.session.commit()
# Update playlist fields
playlist.track_count = total_tracks
playlist.tracks_available = available_tracks
db.session.commit()
app.logger.info("All playlists' track statuses updated.")
def download_missing_tracks():
app.logger.info("Starting track download job...")
with app.app_context():
# Get all tracks that are not downloaded
undownloaded_tracks = Track.query.filter_by(downloaded=False).all()
if not undownloaded_tracks:
app.logger.info("No undownloaded tracks found.")
return
for track in undownloaded_tracks:
app.logger.info(f"Trying to downloading track: {track.name} ({track.spotify_track_id})")
try:
# Download track using spotDL
s_url = f"https://open.spotify.com/track/{track.spotify_track_id}"
search = spotdl.search([s_url])
if search:
song = search[0]
dl_request = spotdl.download(song)
# Assuming spotDL downloads files to the './downloads' directory, set the filesystem path
file_path = dl_request[1].__str__() # Adjust according to naming conventions
if os.path.exists(file_path):
# Update the track's downloaded status and filesystem path
track.downloaded = True
track.filesystem_path = file_path
db.session.commit()
app.logger.info(f"Track {track.name} downloaded successfully to {file_path}.")
else:
app.logger.error(f"Download failed for track {track.name}: file not found.")
else:
app.logger.warning(f"{track.name} ({track.spotify_track_id}) not Found")
except Exception as e:
app.logger.error(f"Error downloading track {track.name}: {str(e)}")
app.logger.info("Track download job finished.")
update_all_playlists_track_status()
def check_for_playlist_updates():
app.logger.info('Starting playlist update check...')
with app.app_context():
try:
playlists = Playlist.query.all() # Get all users
for playlist in playlists:
app.logger.info(f'Checking updates for playlist: {playlist.name}')
try:
# Fetch the latest data from the Spotify API
playlist_data = sp.playlist(playlist.spotify_playlist_id)
spotify_tracks = {track['track']['id']: track['track'] for track in playlist_data['tracks']['items']}
existing_tracks = {track.spotify_track_id: track for track in playlist.tracks}
# Tracks to add
tracks_to_add = []
for track_id, track_info in spotify_tracks.items():
if track_id not in existing_tracks:
track = Track.query.filter_by(spotify_track_id=track_id).first()
if not track:
track = Track(name=track_info['name'], spotify_track_id=track_id, spotify_uri=track_info['uri'],downloaded= False)
db.session.add(track)
db.session.commit()
app.logger.info(f'Added new track: {track.name}')
tracks_to_add.append(track)
# Tracks to remove
tracks_to_remove = [existing_tracks[track_id] for track_id in existing_tracks if track_id not in spotify_tracks]
if tracks_to_add:
for track in tracks_to_add:
playlist.tracks.append(track)
db.session.commit()
app.logger.info(f'Added {len(tracks_to_add)} tracks to playlist: {playlist.name}')
if tracks_to_remove:
for track in tracks_to_remove:
playlist.tracks.remove(track)
db.session.commit()
app.logger.info(f'Removed {len(tracks_to_remove)} tracks from playlist: {playlist.name}')
except Exception as e:
app.logger.error(f"Error updating playlist {playlist.name}: {str(e)}")
except Exception as e:
app.logger.error(f"Error in check_for_playlist_updates: {str(e)}")
app.logger.info('Finished playlist update check.')
update_all_playlists_track_status()
# Add the job to run every 10 minutes (customize the interval as needed)
#scheduler.add_job(download_missing_tracks, 'interval', seconds=30, max_instances=1)
#scheduler.add_job(check_for_playlist_updates, 'interval', minutes=10, max_instances=1)
#download_missing_tracks()
#check_for_playlist_updates()
# Start the scheduler
scheduler.start()
# Ensure the scheduler shuts down properly when the app stops
atexit.register(lambda: scheduler.shutdown())