Forráskód Böngészése

Add poster display for most watched content in JellyStat plugin

- Add toggle settings for movies, TV shows, and music poster displays
- Add configurable count setting for number of items to show
- Implement poster cards with responsive grid layout
- Add play count badges and fallback placeholders
- Include getPosterUrl helper for image URL generation
- Support for movies (2:3 aspect), shows (2:3 aspect), and music (1:1 aspect)
mgomon 11 hónapja
szülő
commit
fb872f6287
1 módosított fájl, 202 hozzáadás és 1 törlés
  1. 202 1
      api/homepage/jellystat.php

+ 202 - 1
api/homepage/jellystat.php

@@ -47,6 +47,12 @@ trait JellyStatHomepageItem
                     $this->settingsOption('switch', 'homepageJellyStatShowRecentActivity', ['label' => 'Show Recent Activity']),
                     $this->settingsOption('number', 'homepageJellyStatMaxItems', ['label' => 'Maximum Items to Display', 'min' => 5, 'max' => 50]),
                 ],
+                'Most Watched Content' => [
+                    $this->settingsOption('switch', 'homepageJellyStatShowMostWatchedMovies', ['label' => 'Show Most Watched Movies with Posters']),
+                    $this->settingsOption('switch', 'homepageJellyStatShowMostWatchedShows', ['label' => 'Show Most Watched TV Shows with Posters']),
+                    $this->settingsOption('switch', 'homepageJellyStatShowMostListenedMusic', ['label' => 'Show Most Listened Music with Cover Art']),
+                    $this->settingsOption('number', 'homepageJellyStatMostWatchedCount', ['label' => 'Number of Most Watched Items to Display', 'min' => 1, 'max' => 50]),
+                ],
                 'Iframe Mode Options' => [
                     $this->settingsOption('number', 'homepageJellyStatIframeHeight', ['label' => 'Iframe Height (pixels)', 'min' => 300, 'max' => 2000]),
                     $this->settingsOption('switch', 'homepageJellyStatIframeScrolling', ['label' => 'Allow Scrolling in Iframe']),
@@ -230,6 +236,10 @@ trait JellyStatHomepageItem
         $showUsers = ($this->config['homepageJellyStatShowUsers'] ?? true) ? 'true' : 'false';
         $showMostWatched = ($this->config['homepageJellyStatShowMostWatched'] ?? true) ? 'true' : 'false';
         $showRecentActivity = ($this->config['homepageJellyStatShowRecentActivity'] ?? true) ? 'true' : 'false';
+        $showMostWatchedMovies = ($this->config['homepageJellyStatShowMostWatchedMovies'] ?? true) ? 'true' : 'false';
+        $showMostWatchedShows = ($this->config['homepageJellyStatShowMostWatchedShows'] ?? true) ? 'true' : 'false';
+        $showMostListenedMusic = ($this->config['homepageJellyStatShowMostListenedMusic'] ?? true) ? 'true' : 'false';
+        $mostWatchedCount = $this->config['homepageJellyStatMostWatchedCount'] ?? 10;
 
         return '
         <div id="' . __FUNCTION__ . '">
@@ -476,6 +486,132 @@ trait JellyStatHomepageItem
                 html += "</tbody></table></div></div>";
             }
             
+            // Most Watched Movies with Posters
+            if (' . $showMostWatchedMovies . ' && stats.most_watched_movies && stats.most_watched_movies.length > 0) {
+                html += "<div class=\"col-lg-12\" style=\"margin-top: 30px;\">";
+                html += "<h5><i class=\"fa fa-film text-primary\"></i> Most Watched Movies</h5>";
+                html += "<div class=\"row\" style=\"margin-top: 15px;\">";
+                
+                stats.most_watched_movies.forEach(function(movie) {
+                    var posterUrl = getPosterUrl(movie.poster_path, movie.id, movie.server_id);
+                    var playCount = movie.play_count || 0;
+                    var year = movie.year || 'N/A';
+                    var title = movie.title || 'Unknown Movie';
+                    
+                    html += "<div class=\"col-lg-2 col-md-3 col-sm-4 col-xs-6\" style=\"margin-bottom: 20px;\">";
+                    html += "<div class=\"poster-card\" style=\"position: relative; background: #f8f9fa; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1); transition: transform 0.2s;\">";
+                    
+                    // Poster image
+                    html += "<div class=\"poster-image\" style=\"position: relative; padding-top: 150%; background: #e9ecef;\">";
+                    if (posterUrl) {
+                        html += "<img src=\"" + posterUrl + "\" alt=\"" + title + "\" style=\"position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover;\" onerror=\"this.style.display='none'; this.nextElementSibling.style.display='flex';\">";
+                    }
+                    html += "<div class=\"poster-placeholder\" style=\"position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: " + (posterUrl ? 'none' : 'flex') + "; align-items: center; justify-content: center; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;\">";
+                    html += "<i class=\"fa fa-film fa-3x\"></i>";
+                    html += "</div>";
+                    
+                    // Play count badge
+                    html += "<div class=\"play-count-badge\" style=\"position: absolute; top: 8px; right: 8px; background: rgba(0,0,0,0.8); color: white; padding: 4px 8px; border-radius: 12px; font-size: 11px;\">";
+                    html += "<i class=\"fa fa-play\"></i> " + playCount;
+                    html += "</div>";
+                    html += "</div>";
+                    
+                    // Movie info
+                    html += "<div class=\"poster-info\" style=\"padding: 12px; text-align: center;\">";
+                    html += "<h6 style=\"margin: 0 0 4px 0; font-size: 13px; font-weight: bold; line-height: 1.2; height: 32px; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;\" title=\"" + title + "\">" + title + "</h6>";
+                    html += "<small class=\"text-muted\">" + year + "</small>";
+                    html += "</div>";
+                    
+                    html += "</div></div>";
+                });
+                
+                html += "</div></div>";
+            }
+            
+            // Most Watched TV Shows with Posters
+            if (' . $showMostWatchedShows . ' && stats.most_watched_shows && stats.most_watched_shows.length > 0) {
+                html += "<div class=\"col-lg-12\" style=\"margin-top: 30px;\">";
+                html += "<h5><i class=\"fa fa-television text-info\"></i> Most Watched TV Shows</h5>";
+                html += "<div class=\"row\" style=\"margin-top: 15px;\">";
+                
+                stats.most_watched_shows.forEach(function(show) {
+                    var posterUrl = getPosterUrl(show.poster_path, show.id, show.server_id);
+                    var playCount = show.play_count || 0;
+                    var year = show.year || 'N/A';
+                    var title = show.title || 'Unknown Show';
+                    
+                    html += "<div class=\"col-lg-2 col-md-3 col-sm-4 col-xs-6\" style=\"margin-bottom: 20px;\">";
+                    html += "<div class=\"poster-card\" style=\"position: relative; background: #f8f9fa; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1); transition: transform 0.2s;\">";
+                    
+                    // Poster image
+                    html += "<div class=\"poster-image\" style=\"position: relative; padding-top: 150%; background: #e9ecef;\">";
+                    if (posterUrl) {
+                        html += "<img src=\"" + posterUrl + "\" alt=\"" + title + "\" style=\"position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover;\" onerror=\"this.style.display='none'; this.nextElementSibling.style.display='flex';\">";
+                    }
+                    html += "<div class=\"poster-placeholder\" style=\"position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: " + (posterUrl ? 'none' : 'flex') + "; align-items: center; justify-content: center; background: linear-gradient(135deg, #74b9ff 0%, #0984e3 100%); color: white;\">";
+                    html += "<i class=\"fa fa-television fa-3x\"></i>";
+                    html += "</div>";
+                    
+                    // Play count badge
+                    html += "<div class=\"play-count-badge\" style=\"position: absolute; top: 8px; right: 8px; background: rgba(0,0,0,0.8); color: white; padding: 4px 8px; border-radius: 12px; font-size: 11px;\">";
+                    html += "<i class=\"fa fa-play\"></i> " + playCount;
+                    html += "</div>";
+                    html += "</div>";
+                    
+                    // Show info
+                    html += "<div class=\"poster-info\" style=\"padding: 12px; text-align: center;\">";
+                    html += "<h6 style=\"margin: 0 0 4px 0; font-size: 13px; font-weight: bold; line-height: 1.2; height: 32px; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;\" title=\"" + title + "\">" + title + "</h6>";
+                    html += "<small class=\"text-muted\">" + year + "</small>";
+                    html += "</div>";
+                    
+                    html += "</div></div>";
+                });
+                
+                html += "</div></div>";
+            }
+            
+            // Most Listened Music with Cover Art
+            if (' . $showMostListenedMusic . ' && stats.most_listened_music && stats.most_listened_music.length > 0) {
+                html += "<div class=\"col-lg-12\" style=\"margin-top: 30px;\">";
+                html += "<h5><i class=\"fa fa-music text-success\"></i> Most Listened Music</h5>";
+                html += "<div class=\"row\" style=\"margin-top: 15px;\">";
+                
+                stats.most_listened_music.forEach(function(music) {
+                    var posterUrl = getPosterUrl(music.poster_path || music.cover_art, music.id, music.server_id);
+                    var playCount = music.play_count || 0;
+                    var artist = music.artist || 'Unknown Artist';
+                    var title = music.title || music.album || 'Unknown';
+                    
+                    html += "<div class=\"col-lg-2 col-md-3 col-sm-4 col-xs-6\" style=\"margin-bottom: 20px;\">";
+                    html += "<div class=\"poster-card\" style=\"position: relative; background: #f8f9fa; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1); transition: transform 0.2s;\">";
+                    
+                    // Cover art
+                    html += "<div class=\"poster-image\" style=\"position: relative; padding-top: 100%; background: #e9ecef;\">";
+                    if (posterUrl) {
+                        html += "<img src=\"" + posterUrl + "\" alt=\"" + title + "\" style=\"position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover;\" onerror=\"this.style.display='none'; this.nextElementSibling.style.display='flex';\">";
+                    }
+                    html += "<div class=\"poster-placeholder\" style=\"position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: " + (posterUrl ? 'none' : 'flex') + "; align-items: center; justify-content: center; background: linear-gradient(135deg, #fd79a8 0%, #e84393 100%); color: white;\">";
+                    html += "<i class=\"fa fa-music fa-3x\"></i>";
+                    html += "</div>";
+                    
+                    // Play count badge
+                    html += "<div class=\"play-count-badge\" style=\"position: absolute; top: 8px; right: 8px; background: rgba(0,0,0,0.8); color: white; padding: 4px 8px; border-radius: 12px; font-size: 11px;\">";
+                    html += "<i class=\"fa fa-play\"></i> " + playCount;
+                    html += "</div>";
+                    html += "</div>";
+                    
+                    // Music info
+                    html += "<div class=\"poster-info\" style=\"padding: 12px; text-align: center;\">";
+                    html += "<h6 style=\"margin: 0 0 4px 0; font-size: 13px; font-weight: bold; line-height: 1.2; height: 32px; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;\" title=\"" + title + "\">" + title + "</h6>";
+                    html += "<small class=\"text-muted\">" + artist + "</small>";
+                    html += "</div>";
+                    
+                    html += "</div></div>";
+                });
+                
+                html += "</div></div>";
+            }
+            
             if (!html) {
                 html = "<div class=\"col-lg-12 text-center text-muted\">";
                 html += "<i class=\"fa fa-exclamation-circle fa-3x\" style=\"margin-bottom: 10px;\"></i>";
@@ -543,6 +679,36 @@ trait JellyStatHomepageItem
             }
         }
         
+        // Helper function to generate poster URLs from JellyStat/Jellyfin
+        function getPosterUrl(posterPath, itemId, serverId) {
+            if (!posterPath && !itemId) return null;
+            
+            // If we have a poster path from JellyStat, use it directly
+            if (posterPath) {
+                // Check if it's already a full URL
+                if (posterPath.startsWith('http://') || posterPath.startsWith('https://')) {
+                    return posterPath;
+                }
+                
+                // If it's a relative path, construct the full URL via JellyStat
+                var jellyStatUrl = "' . $this->qualifyURL($this->config['jellyStatURL'] ?? '') . '";
+                if (jellyStatUrl && posterPath.startsWith('/')) {
+                    return jellyStatUrl + posterPath;
+                }
+            }
+            
+            // Fallback: Try to construct Jellyfin/Emby image URL if we have itemId
+            if (itemId && serverId) {
+                var jellyStatUrl = "' . $this->qualifyURL($this->config['jellyStatURL'] ?? '') . '";
+                if (jellyStatUrl) {
+                    // Try JellyStat's image proxy endpoint (if available)
+                    return jellyStatUrl + '/api/image/' + itemId + '/Primary';
+                }
+            }
+            
+            return null;
+        }
+        
         </script>
         ';
     }
@@ -597,9 +763,14 @@ trait JellyStatHomepageItem
             'period' => "{$days} days",
             'libraries' => [],
             'library_totals' => [],
-            'server_info' => []
+            'server_info' => [],
+            'most_watched_movies' => [],
+            'most_watched_shows' => [],
+            'most_listened_music' => []
         ];
         
+        $mostWatchedCount = $this->config['homepageJellyStatMostWatchedCount'] ?? 10;
+        
         try {
             // Get Library Statistics - use query parameter authentication
             $librariesUrl = $baseUrl . '/api/getLibraries?apiKey=' . urlencode($token);
@@ -662,6 +833,36 @@ trait JellyStatHomepageItem
                 }
             }
             
+            // Get Most Watched Movies (if endpoint available)
+            $moviesUrl = $baseUrl . '/api/getMostWatchedMovies?apiKey=' . urlencode($token) . '&count=' . $mostWatchedCount;
+            $response = Requests::get($moviesUrl, [], $options);
+            if ($response->success) {
+                $data = json_decode($response->body, true);
+                if (is_array($data) && !isset($data['error'])) {
+                    $stats['most_watched_movies'] = $data;
+                }
+            }
+            
+            // Get Most Watched Shows (if endpoint available)
+            $showsUrl = $baseUrl . '/api/getMostWatchedShows?apiKey=' . urlencode($token) . '&count=' . $mostWatchedCount;
+            $response = Requests::get($showsUrl, [], $options);
+            if ($response->success) {
+                $data = json_decode($response->body, true);
+                if (is_array($data) && !isset($data['error'])) {
+                    $stats['most_watched_shows'] = $data;
+                }
+            }
+            
+            // Get Most Listened Music (if endpoint available)
+            $musicUrl = $baseUrl . '/api/getMostListenedMusic?apiKey=' . urlencode($token) . '&count=' . $mostWatchedCount;
+            $response = Requests::get($musicUrl, [], $options);
+            if ($response->success) {
+                $data = json_decode($response->body, true);
+                if (is_array($data) && !isset($data['error'])) {
+                    $stats['most_listened_music'] = $data;
+                }
+            }
+            
         } catch (Exception $e) {
             return ['error' => true, 'message' => 'Failed to fetch JellyStat data: ' . $e->getMessage()];
         }