diff --git a/movie_selector.py b/movie_selector.py index d1df113..6277d5f 100644 --- a/movie_selector.py +++ b/movie_selector.py @@ -10,13 +10,12 @@ import pytz from flask import Flask, jsonify, render_template, send_from_directory, request, session from flask_socketio import SocketIO, emit -from utils.cache_manager import CacheManager from utils.poster_view import set_current_movie, poster_bp, init_socket from utils.default_poster_manager import init_default_poster_manager, default_poster_manager from utils.playback_monitor import PlaybackMonitor from utils.fetch_movie_links import fetch_movie_links -logging.basicConfig(level=logging.DEBUG) +logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) app = Flask(__name__, static_folder='static', template_folder='web') @@ -24,6 +23,8 @@ socketio = SocketIO(app) init_socket(socketio) +from utils.cache_manager import CacheManager + # Initialize the default poster manager default_poster_manager = init_default_poster_manager(socketio) @@ -44,7 +45,7 @@ if PLEX_AVAILABLE: from utils.plex_service import PlexService plex = PlexService() - cache_manager = CacheManager(plex, cache_file_path) + cache_manager = CacheManager(plex, cache_file_path, socketio, app) cache_manager.start() app.config['PLEX_SERVICE'] = plex else: @@ -88,7 +89,7 @@ def resync_cache(): loading_in_progress = True try: if cache_manager: - cache_manager.update_cache() + cache_manager.update_cache(emit_progress=True) all_plex_unwatched_movies = cache_manager.get_cached_movies() movies_loaded_from_cache = True update_cache_status() @@ -196,17 +197,17 @@ def random_movie(): @app.route('/filter_movies') def filter_movies(): current_service = session.get('current_service', get_available_service()) - genre = request.args.get('genre') - year = request.args.get('year') - pg_rating = request.args.get('pg_rating') + genres = request.args.get('genres', '').split(',') if request.args.get('genres') else None + years = request.args.get('years', '').split(',') if request.args.get('years') else None + pg_ratings = request.args.get('pg_ratings', '').split(',') if request.args.get('pg_ratings') else None - logger.debug(f"Filtering movies with genre: {genre}, year: {year}, pg_rating: {pg_rating}") + logger.debug(f"Filtering movies with genres: {genres}, years: {years}, pg_ratings: {pg_ratings}") try: if current_service == 'plex' and PLEX_AVAILABLE: - movie_data = plex.filter_movies(genre, year, pg_rating) + movie_data = plex.filter_movies(genres, years, pg_ratings) elif current_service == 'jellyfin' and JELLYFIN_AVAILABLE: - movie_data = jellyfin.filter_movies(genre, year, pg_rating) + movie_data = jellyfin.filter_movies(genres, years, pg_ratings) else: return jsonify({"error": "No available media service"}), 400 @@ -223,33 +224,18 @@ def filter_movies(): @app.route('/next_movie') def next_movie(): - global all_plex_unwatched_movies current_service = session.get('current_service', get_available_service()) - genre = request.args.get('genre') - year = request.args.get('year') - pg_rating = request.args.get('pg_rating') + genres = request.args.get('genres', '').split(',') if request.args.get('genres') else None + years = request.args.get('years', '').split(',') if request.args.get('years') else None + pg_ratings = request.args.get('pg_ratings', '').split(',') if request.args.get('pg_ratings') else None - logger.debug(f"Next movie request with filters - genre: {genre}, year: {year}, pg_rating: {pg_rating}") + logger.debug(f"Next movie request with filters - genres: {genres}, years: {years}, pg_ratings: {pg_ratings}") try: if current_service == 'plex' and PLEX_AVAILABLE: - if not all_plex_unwatched_movies: - all_plex_unwatched_movies = plex.get_all_unwatched_movies() - - filtered_movies = all_plex_unwatched_movies - if genre: - filtered_movies = [m for m in filtered_movies if genre in m['genres']] - if year: - filtered_movies = [m for m in filtered_movies if m['year'] == int(year)] - if pg_rating: - filtered_movies = [m for m in filtered_movies if m['contentRating'] == pg_rating] - - if filtered_movies: - movie_data = random.choice(filtered_movies) - else: - movie_data = None + movie_data = plex.filter_movies(genres, years, pg_ratings) elif current_service == 'jellyfin' and JELLYFIN_AVAILABLE: - movie_data = jellyfin.filter_movies(genre, year, pg_rating) + movie_data = jellyfin.filter_movies(genres, years, pg_ratings) else: return jsonify({"error": "No available media service"}), 400 @@ -435,7 +421,7 @@ def debug_plex(): "cached_movies": cached_movies, "loaded_from_cache": movies_loaded_from_cache, "plex_url": os.getenv('PLEX_URL'), - "movies_library_name": os.getenv('MOVIES_LIBRARY_NAME'), + "movies_library_name": os.getenv('PLEX_MOVIE_LIBRARIES'), "cache_file_exists": os.path.exists(cache_file_path) }) except Exception as e: diff --git a/static/js/script.js b/static/js/script.js index 8f1c190..bf551b4 100644 --- a/static/js/script.js +++ b/static/js/script.js @@ -1,7 +1,7 @@ let currentFilters = { - genre: '', - year: '', - pgRating: '' + genres: [], + years: [], + pgRatings: [] }; let currentService = 'plex'; @@ -16,6 +16,7 @@ document.addEventListener('DOMContentLoaded', async function() { await loadFilterOptions(); setupEventListeners(); checkAndLoadCache(); + checkPowerButtonVisibility(); }); function showLoadingOverlay() { @@ -172,14 +173,23 @@ function setupEventListeners() { } } -async function applyFilter() { - currentFilters.genre = document.getElementById("genreSelect").value; - currentFilters.year = document.getElementById("yearSelect").value; - currentFilters.pgRating = document.getElementById("pgRatingSelect").value; +function applyFilter() { + currentFilters.genres = Array.from(document.getElementById("genreSelect").selectedOptions).map(option => option.value); + currentFilters.years = Array.from(document.getElementById("yearSelect").selectedOptions).map(option => option.value); + currentFilters.pgRatings = Array.from(document.getElementById("pgRatingSelect").selectedOptions).map(option => option.value); + fetchFilteredMovies(); +} + +async function fetchFilteredMovies() { try { - const response = await fetch(`/filter_movies?genre=${encodeURIComponent(currentFilters.genre)}&year=${currentFilters.year}&pg_rating=${encodeURIComponent(currentFilters.pgRating)}`); - + const queryParams = new URLSearchParams(); + if (currentFilters.genres.length) queryParams.append('genres', currentFilters.genres.join(',')); + if (currentFilters.years.length) queryParams.append('years', currentFilters.years.join(',')); + if (currentFilters.pgRatings.length) queryParams.append('pg_ratings', currentFilters.pgRatings.join(',')); + + const response = await fetch(`/filter_movies?${queryParams}`); + if (response.status === 204) { showNoMoviesMessage(); return; @@ -200,6 +210,47 @@ async function applyFilter() { } } +function clearFilter() { + document.getElementById("genreSelect").selectedIndex = -1; + document.getElementById("yearSelect").selectedIndex = -1; + document.getElementById("pgRatingSelect").selectedIndex = -1; + currentFilters = { + genres: [], + years: [], + pgRatings: [] + }; + hideMessage(); + loadRandomMovie(); + document.getElementById("filterDropdown").classList.remove("show"); +} + +async function loadNextMovie() { + try { + const queryParams = new URLSearchParams(); + if (currentFilters.genres.length) queryParams.append('genres', currentFilters.genres.join(',')); + if (currentFilters.years.length) queryParams.append('years', currentFilters.years.join(',')); + if (currentFilters.pgRatings.length) queryParams.append('pg_ratings', currentFilters.pgRatings.join(',')); + + const url = `/next_movie?${queryParams}`; + console.log("Requesting next movie with URL:", url); + const response = await fetch(url); + + if (response.status === 204) { + showNoMoviesMessage(); + return; + } + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + console.log("Loaded next movie from service:", data.service); + currentMovie = data.movie; + updateMovieDisplay(data.movie); + } catch (error) { + console.error("Error fetching next movie:", error); + showErrorMessage(error.message); + } +} function showMessage() { document.getElementById("movieContent").classList.add("hidden"); @@ -235,44 +286,6 @@ function showErrorMessage(message) { showMessage(); } -function clearFilter() { - document.getElementById("genreSelect").value = ''; - document.getElementById("yearSelect").value = ''; - document.getElementById("pgRatingSelect").value = ''; - currentFilters = { - genre: '', - year: '', - pgRating: '' - }; - hideMessage(); - loadRandomMovie(); - document.getElementById("filterDropdown").classList.remove("show"); -} - -async function loadNextMovie() { - try { - let url = `/next_movie`; - if (currentFilters.genre || currentFilters.year || currentFilters.pgRating) { - url += `?genre=${encodeURIComponent(currentFilters.genre || '')}&year=${currentFilters.year || ''}&pg_rating=${encodeURIComponent(currentFilters.pgRating || '')}`; - } - const response = await fetch(url); - if (response.status === 204) { - showNoMoviesMessage(); - return; - } - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - console.log("Loaded next movie from service:", data.service); - currentMovie = data.movie; - updateMovieDisplay(data.movie); - } catch (error) { - console.error("Error fetching next movie:", error); - showErrorMessage(error.message); - } -} - function updateMovieDisplay(movieData) { if (!movieData) { console.error("No movie data to display"); @@ -374,7 +387,7 @@ function setupDescriptionExpander() { function checkTruncation() { description.style.webkitLineClamp = '4'; description.style.display = '-webkit-box'; - + if (description.scrollHeight > description.clientHeight) { description.classList.add('truncated'); description.style.cursor = 'pointer'; @@ -518,6 +531,21 @@ function updateServiceButton() { } } +function checkPowerButtonVisibility() { + const powerButton = document.getElementById("btn_power"); + if (powerButton) { + fetch('/devices') + .then(response => response.json()) + .then(devices => { + powerButton.style.display = devices.length > 0 ? 'flex' : 'none'; + }) + .catch(error => { + console.error("Error checking devices:", error); + powerButton.style.display = 'none'; + }); + } +} + async function switchService() { if (window.HOMEPAGE_MODE) return; diff --git a/static/style/style.css b/static/style/style.css index afc955d..d08dc1d 100644 --- a/static/style/style.css +++ b/static/style/style.css @@ -1,3 +1,4 @@ +static/style/style.css body { margin: 0; padding: 0; @@ -584,14 +585,14 @@ main { /* PWA specific styles */ @media all and (display-mode: standalone) and (max-width: 767px) { body { - padding-top: 0; + padding-top: 0; padding-bottom: env(safe-area-inset-bottom); padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); } #section { - padding-top: env(safe-area-inset-top); + padding-top: env(safe-area-inset-top); } .filter-container { diff --git a/utils/cache_manager.py b/utils/cache_manager.py index ce4bd79..b5726a1 100644 --- a/utils/cache_manager.py +++ b/utils/cache_manager.py @@ -8,9 +8,11 @@ logger = logging.getLogger(__name__) class CacheManager: - def __init__(self, plex_service, cache_file_path, update_interval=600): + def __init__(self, plex_service, cache_file_path, socketio, app, update_interval=600): self.plex_service = plex_service self.cache_file_path = cache_file_path + self.socketio = socketio + self.app = app self.update_interval = update_interval self.running = False @@ -23,30 +25,36 @@ def stop(self): def _update_loop(self): while self.running: - self.update_cache() + self.update_cache(emit_progress=False) time.sleep(self.update_interval) - def update_cache(self): - try: - new_unwatched_movies = self.plex_service.get_all_unwatched_movies() - - # Read the current cache - if os.path.exists(self.cache_file_path): - with open(self.cache_file_path, 'r') as f: - current_cache = json.load(f) - else: - current_cache = [] - - # Check for changes - if len(new_unwatched_movies) != len(current_cache): - logger.info(f"Updating cache. Old count: {len(current_cache)}, New count: {len(new_unwatched_movies)}") - with open(self.cache_file_path, 'w') as f: - json.dump(new_unwatched_movies, f) - else: - logger.info("No changes in unwatched movies. Cache remains the same.") - - except Exception as e: - logger.error(f"Error updating cache: {str(e)}") + def update_cache(self, emit_progress=False): + with self.app.app_context(): + try: + def progress_callback(progress): + if emit_progress: + self.socketio.emit('loading_progress', {'progress': progress}, namespace='/') + logger.debug(f"Emitted loading_progress: {progress}") + + new_unwatched_movies = self.plex_service.get_all_unwatched_movies(progress_callback=progress_callback) + + # Read the current cache + if os.path.exists(self.cache_file_path): + with open(self.cache_file_path, 'r') as f: + current_cache = json.load(f) + else: + current_cache = [] + + # Check for changes + if len(new_unwatched_movies) != len(current_cache): + logger.info(f"Updating cache. Old count: {len(current_cache)}, New count: {len(new_unwatched_movies)}") + with open(self.cache_file_path, 'w') as f: + json.dump(new_unwatched_movies, f) + else: + logger.info("No changes in unwatched movies. Cache remains the same.") + + except Exception as e: + logger.error(f"Error updating cache: {str(e)}") def get_cached_movies(self): if os.path.exists(self.cache_file_path): diff --git a/utils/default_poster_manager.py b/utils/default_poster_manager.py index 127095f..c6407ad 100644 --- a/utils/default_poster_manager.py +++ b/utils/default_poster_manager.py @@ -5,7 +5,7 @@ import logging from flask_socketio import SocketIO -logging.basicConfig(level=logging.DEBUG) +logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class DefaultPosterManager: diff --git a/utils/jellyfin_service.py b/utils/jellyfin_service.py index 425a656..fe4ea18 100644 --- a/utils/jellyfin_service.py +++ b/utils/jellyfin_service.py @@ -2,9 +2,10 @@ import requests import json import logging +import random from datetime import datetime, timedelta -logging.basicConfig(level=logging.DEBUG) +logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class JellyfinService: @@ -43,7 +44,7 @@ def get_random_movie(self): logger.error(f"Error fetching random unwatched movie: {e}") return None - def filter_movies(self, genre=None, year=None, pg_rating=None): + def filter_movies(self, genres=None, years=None, pg_ratings=None): try: movies_url = f"{self.server_url}/Users/{self.user_id}/Items" params = { @@ -54,12 +55,18 @@ def filter_movies(self, genre=None, year=None, pg_rating=None): 'Fields': 'Overview,People,Genres,RunTimeTicks,ProviderIds,UserData,OfficialRating', 'IsPlayed': 'false' } - if genre: - params['Genres'] = genre - if year: - params['Years'] = year - if pg_rating: - params['OfficialRatings'] = pg_rating + + # Handle empty lists as None + genres = genres if genres and genres[0] else None + years = years if years and years[0] else None + pg_ratings = pg_ratings if pg_ratings and pg_ratings[0] else None + + if genres: + params['Genres'] = genres + if years: + params['Years'] = years + if pg_ratings: + params['OfficialRatings'] = pg_ratings logger.debug(f"Jellyfin API request params: {params}") @@ -69,15 +76,27 @@ def filter_movies(self, genre=None, year=None, pg_rating=None): logger.debug(f"Jellyfin API returned {len(movies)} movies") - if movies: - return self.get_movie_data(movies[0]) + # If no movies are returned, we can't proceed + if not movies: + logger.warning("No unwatched movies found matching the criteria") + return None - logger.warning("No unwatched movies found matching the criteria") - return None + # Randomly select a movie from the returned list + chosen_movie = random.choice(movies) + return self.get_movie_data(chosen_movie) except Exception as e: - logger.error(f"Error filtering movies: {e}") + logger.error(f"Error filtering movies: {str(e)}") return None + def movie_matches_criteria(self, movie, genres, years, pg_ratings): + if genres and not any(genre in movie.get('Genres', []) for genre in genres): + return False + if years and str(movie.get('ProductionYear', '')) not in years: + return False + if pg_ratings and movie.get('OfficialRating', '') not in pg_ratings: + return False + return True + def get_movie_data(self, movie): run_time_ticks = movie.get('RunTimeTicks', 0) total_minutes = run_time_ticks // 600000000 # Convert ticks to minutes diff --git a/utils/playback_monitor.py b/utils/playback_monitor.py index 9189c15..7d91108 100644 --- a/utils/playback_monitor.py +++ b/utils/playback_monitor.py @@ -8,17 +8,23 @@ from utils.plex_service import PlexService from utils.poster_view import set_current_movie -logging.basicConfig(level=logging.DEBUG) +logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class PlaybackMonitor(threading.Thread): def __init__(self, app, interval=10): super().__init__() self.interval = interval - self.jellyfin_service = JellyfinService() + self.jellyfin_service = None self.plex_service = None - if 'PLEX_SERVICE' in app.config: + self.jellyfin_available = all([os.getenv('JELLYFIN_URL'), os.getenv('JELLYFIN_API_KEY')]) + self.plex_available = all([os.getenv('PLEX_URL'), os.getenv('PLEX_TOKEN'), os.getenv('PLEX_MOVIE_LIBRARIES')]) + + if self.jellyfin_available: + self.jellyfin_service = JellyfinService() + if self.plex_available and 'PLEX_SERVICE' in app.config: self.plex_service = app.config['PLEX_SERVICE'] + self.current_movie_id = None self.running = True self.app = app @@ -26,6 +32,8 @@ def __init__(self, app, interval=10): self.jellyfin_poster_users = os.getenv('JELLYFIN_POSTER_USERS', '').split(',') logger.info(f"Initialized PlaybackMonitor with Plex poster users: {self.plex_poster_users}") logger.info(f"Initialized PlaybackMonitor with Jellyfin poster users: {self.jellyfin_poster_users}") + logger.info(f"Jellyfin available: {self.jellyfin_available}") + logger.info(f"Plex available: {self.plex_available}") def is_poster_user(self, username, service): if service == 'plex': @@ -45,26 +53,28 @@ def run(self): service = None username = None - # Check Jellyfin - jellyfin_sessions = self.jellyfin_service.get_active_sessions() - for session in jellyfin_sessions: - now_playing = session.get('NowPlayingItem', {}) - if now_playing.get('Type') == 'Movie': - username = session.get('UserName') - if self.is_poster_user(username, 'jellyfin'): - playback_info = { - 'id': now_playing.get('Id'), - 'position': session.get('PlayState', {}).get('PositionTicks', 0) / 10_000_000 - } - service = 'jellyfin' - break + # Check Jellyfin if available + if self.jellyfin_available and self.jellyfin_service: + jellyfin_sessions = self.jellyfin_service.get_active_sessions() + for session in jellyfin_sessions: + now_playing = session.get('NowPlayingItem', {}) + if now_playing.get('Type') == 'Movie': + username = session.get('UserName') + if self.is_poster_user(username, 'jellyfin'): + playback_info = { + 'id': now_playing.get('Id'), + 'position': session.get('PlayState', {}).get('PositionTicks', 0) / 10_000_000 + } + service = 'jellyfin' + logger.debug(f"Authorized Jellyfin user {username} is playing a movie. Updating poster.") + break + else: + logger.debug(f"Jellyfin user {username} is playing a movie but not authorized for poster updates.") else: - logger.info(f"Jellyfin user {username} is playing a movie but not authorized for poster updates.") - else: - logger.debug(f"Jellyfin user is playing non-movie content. Ignoring.") + logger.debug(f"Jellyfin user is playing non-movie content. Ignoring.") - # If no authorized Jellyfin playback, check Plex - if not playback_info and self.plex_service: + # Check Plex if available and no Jellyfin playback was found + if not playback_info and self.plex_available and self.plex_service: sessions = self.plex_service.plex.sessions() for session in sessions: if session.type == 'movie': # Only consider movie sessions @@ -72,9 +82,10 @@ def run(self): if self.is_poster_user(username, 'plex'): playback_info = self.plex_service.get_playback_info(session.ratingKey) service = 'plex' + logger.debug(f"Authorized Plex user {username} is playing a movie. Updating poster.") break else: - logger.info(f"Plex user {username} is playing a movie but not authorized for poster updates.") + logger.debug(f"Plex user {username} is playing a movie but not authorized for poster updates.") else: logger.debug(f"Plex user {session.usernames[0] if session.usernames else 'Unknown'} is playing non-movie content. Ignoring.") diff --git a/utils/plex_service.py b/utils/plex_service.py index 4378b4e..6d9ab24 100644 --- a/utils/plex_service.py +++ b/utils/plex_service.py @@ -6,7 +6,7 @@ from datetime import datetime, timedelta from utils.poster_view import set_current_movie -logging.basicConfig(level=logging.DEBUG) +logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class PlexService: @@ -27,18 +27,21 @@ def get_random_movie(self): chosen_movie = random.choice(all_unwatched) return self.get_movie_data(chosen_movie) - def filter_movies(self, genre=None, year=None, pg_rating=None): + def filter_movies(self, genres=None, years=None, pg_ratings=None): filters = {'unwatched': True} - if genre: - filters['genre'] = genre - if year: - filters['year'] = int(year) - if pg_rating: - filters['contentRating'] = pg_rating - + filtered_movies = [] for library in self.libraries: - filtered_movies.extend(library.search(**filters)) + movies = library.search(unwatched=True) + + for movie in movies: + if genres and not any(genre in [g.tag for g in movie.genres] for genre in genres): + continue + if years and str(movie.year) not in years: + continue + if pg_ratings and movie.contentRating not in pg_ratings: + continue + filtered_movies.append(movie) if filtered_movies: chosen_movie = random.choice(filtered_movies) @@ -58,63 +61,89 @@ def get_movie_data(self, movie): headers = {"X-Plex-Token": self.PLEX_TOKEN, "Accept": "application/json"} response = requests.get(metadata_url, headers=headers) if response.status_code == 200: - metadata = response.json() - media_info = metadata['MediaContainer']['Metadata'][0]['Media'][0]['Part'][0]['Stream'] - - # Video format extraction - video_stream = next((s for s in media_info if s['streamType'] == 1), None) - if video_stream: - # Determine resolution - height = video_stream.get('height', 0) - if height <= 480: - resolution = "SD" - elif height <= 720: - resolution = "HD" - elif height <= 1080: - resolution = "FHD" - elif height > 1080: - resolution = "4K" + try: + metadata = response.json() + media_container = metadata.get('MediaContainer', {}) + metadata_list = media_container.get('Metadata', []) + if metadata_list: + media_info = metadata_list[0] + media_list = media_info.get('Media', []) + if media_list: + media = media_list[0] + part_list = media.get('Part', []) + if part_list: + part = part_list[0] + streams = part.get('Stream', []) + # Now proceed to process streams + media_info = streams + + # Video format extraction + video_stream = next((s for s in media_info if s.get('streamType') == 1), None) + if video_stream: + # Determine resolution + height = video_stream.get('height', 0) + if height <= 480: + resolution = "SD" + elif height <= 720: + resolution = "HD" + elif height <= 1080: + resolution = "FHD" + elif height > 1080: + resolution = "4K" + else: + resolution = "Unknown" + + # Check for HDR and Dolby Vision + hdr_types = [] + if video_stream.get('DOVIPresent'): + hdr_types.append("DV") + if video_stream.get('colorTrc') == 'smpte2084' and video_stream.get('colorSpace') == 'bt2020nc': + hdr_types.append("HDR10") + + # Combine resolution and HDR info + video_format = f"{resolution} {'/'.join(hdr_types)}".strip() + + # Audio format extraction + audio_stream = next((s for s in media_info if s.get('streamType') == 2), None) + if audio_stream: + codec = audio_stream.get('codec', '').lower() + channels = audio_stream.get('channels', 0) + + codec_map = { + 'ac3': 'Dolby Digital', + 'eac3': 'Dolby Digital Plus', + 'truehd': 'Dolby TrueHD', + 'dca': 'DTS', + 'dts': 'DTS', + 'aac': 'AAC', + 'flac': 'FLAC' + } + + audio_format = codec_map.get(codec, codec.upper()) + + if audio_stream.get('audioChannelLayout'): + channel_layout = audio_stream['audioChannelLayout'].split('(')[0] # Remove (side) or similar + audio_format += f" {channel_layout}" + elif channels: + if channels == 8: + audio_format += ' 7.1' + elif channels == 6: + audio_format += ' 5.1' + elif channels == 2: + audio_format += ' 2.0' + else: + logger.warning(f"No 'Part' data for movie '{movie.title}' (ID: {movie.ratingKey})") + else: + logger.warning(f"No 'Media' data for movie '{movie.title}' (ID: {movie.ratingKey})") else: - resolution = "Unknown" - - # Check for HDR and Dolby Vision - hdr_types = [] - if video_stream.get('DOVIPresent'): - hdr_types.append("DV") - if video_stream.get('colorTrc') == 'smpte2084' and video_stream.get('colorSpace') == 'bt2020nc': - hdr_types.append("HDR10") - - # Combine resolution and HDR info - video_format = f"{resolution} {'/'.join(hdr_types)}".strip() - - # Audio format extraction - audio_stream = next((s for s in media_info if s['streamType'] == 2), None) - if audio_stream: - codec = audio_stream.get('codec', '').lower() - channels = audio_stream.get('channels', 0) - - codec_map = { - 'ac3': 'Dolby Digital', - 'eac3': 'Dolby Digital Plus', - 'truehd': 'Dolby TrueHD', - 'dca': 'DTS', - 'dts': 'DTS', - 'aac': 'AAC', - 'flac': 'FLAC' - } - - audio_format = codec_map.get(codec, codec.upper()) - - if audio_stream.get('audioChannelLayout'): - channel_layout = audio_stream['audioChannelLayout'].split('(')[0] # Remove (side) or similar - audio_format += f" {channel_layout}" - elif channels: - if channels == 8: - audio_format += ' 7.1' - elif channels == 6: - audio_format += ' 5.1' - elif channels == 2: - audio_format += ' 2.0' + logger.warning(f"No 'Metadata' in MediaContainer for movie '{movie.title}' (ID: {movie.ratingKey})") + except Exception as e: + logger.error(f"Error processing media info for movie '{movie.title}' (ID: {movie.ratingKey}): {e}") + # Optionally, set default formats or continue without setting them + video_format = "Unknown" + audio_format = "Unknown" + else: + logger.error(f"Failed to fetch metadata for movie '{movie.title}' (ID: {movie.ratingKey}), status code {response.status_code}") return { "id": movie.ratingKey, @@ -196,11 +225,31 @@ def play_movie(self, movie_id, client_id): def get_total_unwatched_movies(self): return sum(len(library.search(unwatched=True)) for library in self.libraries) - def get_all_unwatched_movies(self): + def get_all_unwatched_movies(self, progress_callback=None): all_unwatched = [] + unwatched_movies = [] + + # Collect all unwatched movies from all libraries for library in self.libraries: - all_unwatched.extend(library.search(unwatched=True)) - return [self.get_movie_data(movie) for movie in all_unwatched] + library_unwatched = library.search(unwatched=True) + unwatched_movies.extend(library_unwatched) + + total_movies = len(unwatched_movies) + processed_movies = 0 + + # Process each movie and call the progress callback + for movie in unwatched_movies: + movie_data = self.get_movie_data(movie) + all_unwatched.append(movie_data) + processed_movies += 1 + + # Call the progress callback with the current progress + if progress_callback and total_movies > 0: + progress = processed_movies / total_movies + progress_callback(progress) + + return all_unwatched + def get_movie_by_id(self, movie_id): for library in self.libraries: diff --git a/utils/poster_view.py b/utils/poster_view.py index 16ae723..e6b234f 100644 --- a/utils/poster_view.py +++ b/utils/poster_view.py @@ -8,7 +8,7 @@ import time import logging -logging.basicConfig(level=logging.DEBUG) +logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) poster_bp = Blueprint('poster', __name__) diff --git a/web/index.html b/web/index.html index 17799b5..b130577 100644 --- a/web/index.html +++ b/web/index.html @@ -6,7 +6,7 @@ - +