Skip to content

Commit

Permalink
merge from dev
Browse files Browse the repository at this point in the history
  • Loading branch information
root committed Oct 6, 2024
2 parents ac11671 + 7107bef commit 7b9da35
Show file tree
Hide file tree
Showing 15 changed files with 1,408 additions and 110 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ RUN pip install --no-cache-dir -r requirements.txt
EXPOSE 4000

# Run the application with Gunicorn
CMD ["gunicorn", "--worker-class", "eventlet", "-w", "1", "-b", "0.0.0.0:4000", "movie_selector:app"]
CMD ["gunicorn", "-k", "eventlet", "-w", "1", "-b", "0.0.0.0:4000", "movie_selector:app"]
58 changes: 48 additions & 10 deletions movie_selector.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,32 @@
import traceback
import threading
import time
from datetime import datetime, timedelta
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)
logger = logging.getLogger(__name__)

app = Flask(__name__, static_folder='static', template_folder='web')
app.secret_key = 'your_secret_key_here' # Replace with a real secret key
socketio = SocketIO(app)
init_socket(socketio)

# Initialize the default poster manager
default_poster_manager = init_default_poster_manager(socketio)

# Add the default_poster_manager to the app config
app.config['DEFAULT_POSTER_MANAGER'] = default_poster_manager

# Check which services are available
PLEX_AVAILABLE = all([os.getenv('PLEX_URL'), os.getenv('PLEX_TOKEN'), os.getenv('MOVIES_LIBRARY_NAME')])
PLEX_AVAILABLE = all([os.getenv('PLEX_URL'), os.getenv('PLEX_TOKEN'), os.getenv('PLEX_MOVIE_LIBRARIES')])
JELLYFIN_AVAILABLE = all([os.getenv('JELLYFIN_URL'), os.getenv('JELLYFIN_API_KEY')])
HOMEPAGE_MODE = os.getenv('HOMEPAGE_MODE', 'FALSE').upper() == 'TRUE'

Expand All @@ -33,13 +46,15 @@
plex = PlexService()
cache_manager = CacheManager(plex, cache_file_path)
cache_manager.start()
app.config['PLEX_SERVICE'] = plex
else:
plex = None
cache_manager = None

if JELLYFIN_AVAILABLE:
from utils.jellyfin_service import JellyfinService
jellyfin = JellyfinService()
app.config['JELLYFIN_SERVICE'] = jellyfin
else:
jellyfin = None

Expand All @@ -51,6 +66,13 @@
movies_loaded_from_cache = False
loading_in_progress = False

# Register the poster blueprint
app.register_blueprint(poster_bp)

# Start the PlaybackMonitor
playback_monitor = PlaybackMonitor(app, interval=10)
playback_monitor.start()

def get_available_service():
if PLEX_AVAILABLE:
return 'plex'
Expand Down Expand Up @@ -121,12 +143,12 @@ def get_available_services():
def get_current_service():
if 'current_service' not in session or session['current_service'] not in ['plex', 'jellyfin']:
session['current_service'] = get_available_service()

# Check if the current service is still available
if (session['current_service'] == 'plex' and not PLEX_AVAILABLE) or \
(session['current_service'] == 'jellyfin' and not JELLYFIN_AVAILABLE):
session['current_service'] = get_available_service()

return jsonify({"service": session['current_service']})

@app.route('/switch_service')
Expand Down Expand Up @@ -156,12 +178,12 @@ def random_movie():
movie_data = jellyfin.get_random_movie()
else:
return jsonify({"error": "No available media service"}), 400

if movie_data:
movie_data = enrich_movie_data(movie_data)
return jsonify({
"service": current_service,
"movie": movie_data,
"service": current_service,
"movie": movie_data,
"cache_loaded": movies_loaded_from_cache,
"loading_in_progress": loading_in_progress
})
Expand All @@ -187,7 +209,7 @@ def filter_movies():
movie_data = jellyfin.filter_movies(genre, year, pg_rating)
else:
return jsonify({"error": "No available media service"}), 400

if movie_data:
logger.debug(f"Filtered movie: {movie_data['title']} ({movie_data['year']}) - PG Rating: {movie_data.get('contentRating', 'N/A')}")
movie_data = enrich_movie_data(movie_data)
Expand Down Expand Up @@ -230,7 +252,7 @@ def next_movie():
movie_data = jellyfin.filter_movies(genre, year, pg_rating)
else:
return jsonify({"error": "No available media service"}), 400

if movie_data:
logger.debug(f"Next movie selected: {movie_data['title']} ({movie_data['year']}) - PG Rating: {movie_data.get('contentRating', 'N/A')}")
movie_data = enrich_movie_data(movie_data)
Expand All @@ -246,14 +268,14 @@ def enrich_movie_data(movie_data):
current_service = session.get('current_service', get_available_service())
tmdb_url, trakt_url, imdb_url = fetch_movie_links(movie_data, current_service)
trailer_url = search_youtube_trailer(movie_data['title'], movie_data['year'])

movie_data.update({
"tmdb_url": tmdb_url,
"trakt_url": trakt_url,
"imdb_url": imdb_url,
"trailer_url": trailer_url
})

return movie_data

@app.route('/get_genres')
Expand Down Expand Up @@ -335,10 +357,18 @@ def play_movie(client_id):
try:
if current_service == 'plex' and PLEX_AVAILABLE:
result = plex.play_movie(movie_id, client_id)
if result.get("status") == "playing":
movie_data = plex.get_movie_by_id(movie_id)
elif current_service == 'jellyfin' and JELLYFIN_AVAILABLE:
result = jellyfin.play_movie(movie_id, client_id)
if result.get("status") == "playing":
movie_data = jellyfin.get_movie_by_id(movie_id)
else:
return jsonify({"error": "No available media service"}), 400

if result.get("status") == "playing" and movie_data:
set_current_movie(movie_data, current_service)

logger.debug(f"Play movie result for {current_service}: {result}")
return jsonify(result)
except Exception as e:
Expand Down Expand Up @@ -418,6 +448,14 @@ def trigger_resync():
resync_cache()
return jsonify({"status": "Cache resync completed"})

@socketio.on('connect', namespace='/poster')
def poster_connect():
print('Client connected to poster namespace')

@socketio.on('disconnect', namespace='/poster')
def poster_disconnect():
print('Client disconnected from poster namespace')

if __name__ == '__main__':
logger.info("Application starting")
logger.info("Application setup complete")
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ gunicorn
pywebostv
flask-socketio
eventlet
pytz
Binary file added static/images/default_poster.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
206 changes: 206 additions & 0 deletions static/js/poster.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
const posterContainer = document.getElementById('posterContainer');
const progressBar = document.getElementById('progress-bar');
const startTimeElement = document.getElementById('start-time');
const endTimeElement = document.getElementById('end-time');
const playbackStatusElement = document.getElementById('playback-status');
const posterImage = document.getElementById('poster-image');
const contentRatingElement = document.getElementById('content-rating');
const videoFormatElement = document.getElementById('video-format');
const audioFormatElement = document.getElementById('audio-format');
const customTextContainer = document.getElementById('custom-text-container');
const customText = document.getElementById('custom-text');

let startTime;
let currentStatus = 'UNKNOWN'; // Track current status
let playbackInterval;

// Socket.IO connection
const socket = io('/poster');

socket.on('connect', function() {
console.log('Connected to WebSocket');
});

socket.on('movie_changed', function(data) {
console.log('Movie changed:', data);
isDefaultPoster = false;
updatePoster(data);
updatePosterDisplay();
});

socket.on('set_default_poster', function(data) {
console.log('Setting default poster:', data);
posterImage.src = data.poster;
isDefaultPoster = true;
updatePosterDisplay();
clearMovieInfo();
});

function updatePosterDisplay() {
if (isDefaultPoster) {
posterContainer.classList.add('default-poster');
customTextContainer.style.display = 'flex';
adjustCustomText();
} else {
posterContainer.classList.remove('default-poster');
customTextContainer.style.display = 'none';
}
}

function adjustCustomText() {
const container = customTextContainer;
const textElement = customText;
let fontSize = 100; // Start with a large font size

textElement.style.fontSize = fontSize + 'px';

// Reduce font size until text fits within the container
while ((textElement.scrollWidth > container.clientWidth || textElement.scrollHeight > container.clientHeight) && fontSize > 10) {
fontSize -= 1;
textElement.style.fontSize = fontSize + 'px';
}
}

function updatePoster(movieData) {
document.title = `Now Playing - ${movieData.movie.title}`;
movieId = movieData.movie.id;
movieDuration = movieData.duration_hours * 3600 + movieData.duration_minutes * 60;
posterImage.src = movieData.movie.poster;
contentRatingElement.textContent = movieData.movie.contentRating;
videoFormatElement.textContent = movieData.movie.videoFormat;
audioFormatElement.textContent = movieData.movie.audioFormat;
startTime = new Date(movieData.start_time);
updateTimes(movieData.start_time, 0, 'PLAYING'); // Assume status is PLAYING initially
updatePlaybackStatus('playing');
clearInterval(playbackInterval);
playbackInterval = setInterval(fetchPlaybackState, 2000);
}

function clearMovieInfo() {
document.title = 'Now Playing';
movieId = null;
movieDuration = 0;
contentRatingElement.textContent = '';
videoFormatElement.textContent = '';
audioFormatElement.textContent = '';
startTimeElement.textContent = '--:--';
endTimeElement.textContent = '--:--';
progressBar.style.width = '0%';
clearInterval(playbackInterval);
}

function updateProgress(position) {
if (movieDuration > 0) {
const progress = (position / movieDuration) * 100;
progressBar.style.width = `${progress}%`;
} else {
progressBar.style.width = '0%';
}
}

function updateTimes(start, position, status) {
if (status === 'STOPPED') {
startTimeElement.textContent = '--:--';
endTimeElement.textContent = '--:--';
return;
}

const formatTime = (time) => new Date(time).toLocaleTimeString([], {hour: 'numeric', minute:'2-digit', hour12: true}).replace('am', 'AM').replace('pm', 'PM');

if (!startTime) {
startTime = new Date(start);
}
startTimeElement.textContent = formatTime(startTime);

const currentTime = new Date();
const remainingDuration = movieDuration - position;
const newEndTime = new Date(currentTime.getTime() + remainingDuration * 1000);
endTimeElement.textContent = formatTime(newEndTime);
}

function updatePlaybackStatus(status) {
playbackStatusElement.classList.remove('paused', 'ended', 'stopped');

switch(status.toLowerCase()) {
case 'playing':
playbackStatusElement.textContent = "NOW PLAYING";
playbackStatusElement.classList.remove('paused', 'ended', 'stopped');
currentStatus = 'PLAYING';
break;
case 'paused':
playbackStatusElement.textContent = "PAUSED";
playbackStatusElement.classList.add('paused');
playbackStatusElement.classList.remove('ended', 'stopped');
currentStatus = 'PAUSED';
break;
case 'ended':
playbackStatusElement.textContent = "ENDED";
playbackStatusElement.classList.add('ended');
playbackStatusElement.classList.remove('paused', 'stopped');
currentStatus = 'ENDED';
break;
case 'stopped':
playbackStatusElement.textContent = "STOPPED";
playbackStatusElement.classList.add('stopped');
playbackStatusElement.classList.remove('paused', 'ended');
currentStatus = 'STOPPED';
break;
default:
playbackStatusElement.textContent = status.toUpperCase();
playbackStatusElement.classList.remove('paused', 'ended', 'stopped');
currentStatus = 'UNKNOWN';
}

// If status is STOPPED, set times to --:--
if (currentStatus === 'STOPPED') {
startTimeElement.textContent = '--:--';
endTimeElement.textContent = '--:--';
}
}

function fetchPlaybackState() {
if (!movieId) {
return;
}
fetch(`/playback_state/${movieId}`)
.then(response => response.json())
.then(data => {
if (data.error) {
console.error('Playback state error:', data.error);
return;
}
updateProgress(data.position);
updatePlaybackStatus(data.status);

if (data.status.toUpperCase() === 'STOPPED') {
// Set times to --:--
startTimeElement.textContent = '--:--';
endTimeElement.textContent = '--:--';
} else {
updateTimes(data.start_time, data.position, data.status.toUpperCase());
}
})
.catch(error => {
console.error('Error:', error);
fetch('/current_poster')
.then(response => response.json())
.then(data => {
posterImage.src = data.poster;
isDefaultPoster = data.poster === defaultPosterUrl;
updatePosterDisplay();
clearMovieInfo();
})
.catch(error => console.error('Error fetching current poster:', error));
});
}

function initialize() {
updatePosterDisplay();
if (!isDefaultPoster && movieId) {
fetchPlaybackState();
playbackInterval = setInterval(fetchPlaybackState, 2000);
}
}

window.addEventListener('load', initialize);
window.addEventListener('resize', adjustCustomText);
Loading

0 comments on commit 7b9da35

Please sign in to comment.