diff --git a/Jellyfin.Plugin.MediaBarEnhanced/Api/OverlayImageController.cs b/Jellyfin.Plugin.MediaBarEnhanced/Api/OverlayImageController.cs index c222d0e..0e58f4d 100644 --- a/Jellyfin.Plugin.MediaBarEnhanced/Api/OverlayImageController.cs +++ b/Jellyfin.Plugin.MediaBarEnhanced/Api/OverlayImageController.cs @@ -20,17 +20,13 @@ namespace Jellyfin.Plugin.MediaBarEnhanced.Api public OverlayImageController(IApplicationPaths applicationPaths) { _applicationPaths = applicationPaths; - - // We use the plugin's data folder to store the image - _imageDirectory = MediaBarEnhancedPlugin.Instance?.DataFolderPath ?? Path.Combine(applicationPaths.DataPath, "plugins", "MediaBarEnhanced"); - - // We no longer define the exact path here, just the directory - // The filename is determined per request + _imageDirectory = Path.Combine(applicationPaths.DataPath, "plugins", "configurations", "MediaBarEnhancedAssets"); } /// /// Uploads a new custom overlay image. /// + // [Microsoft.AspNetCore.Authorization.Authorize] [HttpPost("OverlayImage")] [Consumes("multipart/form-data")] public async Task UploadImage([FromForm] IFormFile file, [FromQuery] string? filename = null) @@ -40,10 +36,12 @@ namespace Jellyfin.Plugin.MediaBarEnhanced.Api return BadRequest("No file uploaded."); } - string targetFileName = string.IsNullOrWhiteSpace(filename) - ? "custom_overlay_image.dat" - : $"custom_overlay_image_{filename}.dat"; - string targetPath = Path.Combine(_imageDirectory, targetFileName); + // Extract original extension or fallback to .jpg + string extension = Path.GetExtension(file.FileName); + if (string.IsNullOrWhiteSpace(extension)) extension = ".jpg"; + + // Delete any existing file with this prefix before saving the new one (as extensions might differ) + string prefix = string.IsNullOrWhiteSpace(filename) ? "custom_overlay_image_global" : $"custom_overlay_image_{filename}"; try { @@ -52,6 +50,16 @@ namespace Jellyfin.Plugin.MediaBarEnhanced.Api Directory.CreateDirectory(_imageDirectory); } + // Remove existing + var existingFiles = Directory.GetFiles(_imageDirectory, $"{prefix}.*"); + foreach(var extFile in existingFiles) + { + System.IO.File.Delete(extFile); + } + + string targetFileName = $"{prefix}{extension}"; + string targetPath = Path.Combine(_imageDirectory, targetFileName); + // Delete is not strictly necessary and can cause locking issues if someone is currently reading it. // FileMode.Create will truncate the file if it exists, effectively overwriting it. // We use FileShare.None to ensure we have exclusive write access, but handle potential IOExceptions gracefully. @@ -74,18 +82,20 @@ namespace Jellyfin.Plugin.MediaBarEnhanced.Api /// /// Retrieves the custom overlay image. /// + // [Microsoft.AspNetCore.Authorization.Authorize] [HttpGet("OverlayImage")] public IActionResult GetImage([FromQuery] string? filename = null) { - string targetFileName = string.IsNullOrWhiteSpace(filename) - ? "custom_overlay_image.dat" - : $"custom_overlay_image_{filename}.dat"; - string targetPath = Path.Combine(_imageDirectory, targetFileName); - - if (!System.IO.File.Exists(targetPath)) - { + string prefix = string.IsNullOrWhiteSpace(filename) ? "custom_overlay_image_global" : $"custom_overlay_image_{filename}"; + + if (!Directory.Exists(_imageDirectory)) return NotFound(); - } + + var existingFiles = Directory.GetFiles(_imageDirectory, $"{prefix}.*"); + if (existingFiles.Length == 0) + return NotFound(); + + string targetPath = existingFiles[0]; // Read the file and return as a generic octet stream. // We use FileShare.ReadWrite so that if someone is currently overwriting the file (uploading), we don't block them, @@ -99,25 +109,27 @@ namespace Jellyfin.Plugin.MediaBarEnhanced.Api /// /// Deletes a custom overlay image. /// + // [Microsoft.AspNetCore.Authorization.Authorize] [HttpDelete("OverlayImage")] public IActionResult DeleteImage([FromQuery] string? filename = null) { - string targetFileName = string.IsNullOrWhiteSpace(filename) - ? "custom_overlay_image.dat" - : $"custom_overlay_image_{filename}.dat"; - string targetPath = Path.Combine(_imageDirectory, targetFileName); - - if (System.IO.File.Exists(targetPath)) + string prefix = string.IsNullOrWhiteSpace(filename) ? "custom_overlay_image_global" : $"custom_overlay_image_{filename}"; + + if (Directory.Exists(_imageDirectory)) { - try + var existingFiles = Directory.GetFiles(_imageDirectory, $"{prefix}.*"); + foreach(var file in existingFiles) { - System.IO.File.Delete(targetPath); - return Ok(); - } - catch (Exception ex) - { - return StatusCode(500, $"Error deleting file: {ex.Message}"); + try + { + System.IO.File.Delete(file); + } + catch (Exception ex) + { + return StatusCode(500, $"Error deleting file: {ex.Message}"); + } } + return Ok(); } return NotFound(); @@ -126,6 +138,7 @@ namespace Jellyfin.Plugin.MediaBarEnhanced.Api /// /// Renames a custom overlay image (used when a seasonal section is renamed). /// + // [Microsoft.AspNetCore.Authorization.Authorize] [HttpPut("OverlayImage/Rename")] public IActionResult RenameImage([FromQuery] string oldName, [FromQuery] string newName) { @@ -134,22 +147,23 @@ namespace Jellyfin.Plugin.MediaBarEnhanced.Api return BadRequest("Both oldName and newName must be provided."); } - string oldPath = Path.Combine(_imageDirectory, $"custom_overlay_image_{oldName}.dat"); - string newPath = Path.Combine(_imageDirectory, $"custom_overlay_image_{newName}.dat"); + if (!Directory.Exists(_imageDirectory)) + return Ok(); - if (!System.IO.File.Exists(oldPath)) - { - // If it doesn't exist, there is nothing to rename, but we still consider it a success - // since the end state (file with oldName is gone, file with newName doesn't exist yet) is acceptable. + var oldFiles = Directory.GetFiles(_imageDirectory, $"custom_overlay_image_{oldName}.*"); + if (oldFiles.Length == 0) return Ok(); - } try { + string oldPath = oldFiles[0]; + string extension = Path.GetExtension(oldPath); + string newPath = Path.Combine(_imageDirectory, $"custom_overlay_image_{newName}{extension}"); + // If a file with the new name already exists, delete it first to avoid conflicts - if (System.IO.File.Exists(newPath)) - { - System.IO.File.Delete(newPath); + var existingNewFiles = Directory.GetFiles(_imageDirectory, $"custom_overlay_image_{newName}.*"); + foreach(var existing in existingNewFiles) { + System.IO.File.Delete(existing); } System.IO.File.Move(oldPath, newPath); diff --git a/Jellyfin.Plugin.MediaBarEnhanced/Configuration/configPage.html b/Jellyfin.Plugin.MediaBarEnhanced/Configuration/configPage.html index 1db654d..3af78be 100644 --- a/Jellyfin.Plugin.MediaBarEnhanced/Configuration/configPage.html +++ b/Jellyfin.Plugin.MediaBarEnhanced/Configuration/configPage.html @@ -259,8 +259,8 @@ - - delete + + delete Uploading an image will securely save it to the server and automatically update the URL field above. @@ -273,6 +273,7 @@ + None (Plain White) Classic Shadowed Frosted Glass Pill Cinematic Golden Glow @@ -285,6 +286,9 @@ Cyberpunk Glitch Retro Pop 3D Glowing Shimmer + Liquid Wave + VHS Tracking + Digital Matrix Choose the visual styling animation for your custom text. @@ -300,6 +304,9 @@ Polaroid Frame Vintage Sepia Grayscale + Hologram + CRT Monitor + Floating Float Choose a visual effect to apply to your overlay image. @@ -728,12 +735,26 @@ previewImg.style.display = 'block'; clearBtn.style.display = 'block'; if(icon) icon.style.display = 'none'; - if(text) text.style.display = 'none'; + if(text) { + text.textContent = 'Drag and drop a new image here, or click to select'; + text.style.display = 'block'; + text.style.position = 'absolute'; + text.style.bottom = '10px'; + text.style.zIndex = '3'; + text.style.backgroundColor = 'rgba(0,0,0,0.6)'; + text.style.padding = '4px 8px'; + text.style.borderRadius = '4px'; + } } else { previewImg.style.display = 'none'; clearBtn.style.display = 'none'; if(icon) icon.style.display = 'block'; - if(text) text.style.display = 'block'; + if(text) { + text.textContent = 'Drag and drop an image here, or click to select'; + text.style.display = 'block'; + text.style.position = 'static'; + text.style.backgroundColor = 'transparent'; + } } } @@ -981,7 +1002,7 @@ ' Upload seasonal image' + ' ' + ' ' + - ' delete' + + ' delete' + ' ' + ' ' + ''; @@ -1055,13 +1076,29 @@ previewImg.style.display = 'block'; clearBtn.style.display = 'block'; if(icon) icon.style.display = 'none'; - if(text) text.style.display = 'none'; + if(text) { + text.textContent = 'Drag and drop a new image here...'; + text.style.display = 'block'; + text.style.position = 'absolute'; + text.style.bottom = '5px'; + text.style.zIndex = '3'; + text.style.backgroundColor = 'rgba(0,0,0,0.6)'; + text.style.padding = '2px 6px'; + text.style.borderRadius = '4px'; + text.style.fontSize = '0.7em'; + } } else { previewImg.src = ''; previewImg.style.display = 'none'; clearBtn.style.display = 'none'; if(icon) icon.style.display = 'block'; - if(text) text.style.display = 'block'; + if(text) { + text.textContent = 'Upload seasonal image'; + text.style.display = 'block'; + text.style.position = 'static'; + text.style.backgroundColor = 'transparent'; + text.style.fontSize = '0.85em'; + } } } diff --git a/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.css b/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.css index 5788709..de5e97b 100644 --- a/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.css +++ b/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.css @@ -1031,7 +1031,8 @@ -webkit-backdrop-filter: none; } } -/* Floating Custom Overlay Styling */ + +/* Custom Overlay Styling */ .custom-overlay-container { position: absolute; top: 8vh; @@ -1040,10 +1041,27 @@ display: flex; align-items: center; justify-content: flex-start; - pointer-events: none; /* Let clicks pass through to the slider */ - animation: fadeInOverlay 1.5s ease-in-out forwards; + pointer-events: none; + animation: overlayFadeInGlobal 1.5s ease-in-out forwards; + /* animation: fadeInOverlay 1.5s ease-in-out forwards; */ } +@keyframes overlayFadeInGlobal { + from { opacity: 0; } + to { opacity: 1; } +} + +/* @keyframes fadeInOverlay { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} */ + .custom-overlay-text { font-family: "Archivo Narrow", sans-serif; color: #fff; @@ -1061,21 +1079,10 @@ filter: drop-shadow(2px 4px 6px rgba(0,0,0,0.5)); } -@keyframes fadeInOverlay { - from { - opacity: 0; - transform: translateY(-10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} -/* Make it smaller on mobile portrait */ @media only screen and (max-width: 767px) and (orientation: portrait) { .custom-overlay-container { - top: 5vh; + top: 8%; /* oder 5vh? */ left: 50%; transform: translateX(-50%); width: 90%; @@ -1084,15 +1091,10 @@ } .custom-overlay-text { - font-size: 1.8rem; - } - - .custom-overlay-image { - max-width: 200px; - max-height: 80px; + font-size: 1.5rem; /* 1.5 - 1.8 */ } - @keyframes fadeInOverlay { + /* @keyframes fadeInOverlay { from { opacity: 0; transform: translate(-50%, -10px); @@ -1101,10 +1103,20 @@ opacity: 1; transform: translate(-50%, 0); } + } */ + + .custom-overlay-image { + max-width: 150px; /* oder 200px? */ + max-height: 60px; /* oder 80px? */ } } -/* Custom Overlay Styles */ +/* Custom Overlay Text Styles */ +.custom-overlay-style-None { + color: #fff; + text-shadow: none; +} + .custom-overlay-style-Shadowed { color: #fff; text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.9), -1px -1px 4px rgba(0, 0, 0, 0.8); @@ -1129,7 +1141,7 @@ -webkit-background-clip: text; background-clip: text; color: transparent; - text-shadow: none; /* override default */ + text-shadow: none; filter: drop-shadow(0px 2px 8px rgba(255, 215, 0, 0.4)) drop-shadow(2px 2px 4px rgba(0,0,0,0.8)); animation: shineCinematic 6s linear infinite; background-size: 200% auto; @@ -1155,8 +1167,6 @@ } } - -/* Text Overlay Styles */ .custom-overlay-style-Neon { color: #fff; text-shadow: @@ -1250,39 +1260,6 @@ } } -/* Image Overlay Styles */ -.custom-overlay-img-RoundedShadow { - border-radius: 12px; - filter: drop-shadow(0px 10px 15px rgba(0, 0, 0, 0.6)); -} - -.custom-overlay-img-GlowingBorder { - border-radius: 8px; - box-shadow: 0 0 10px #00a4dc, 0 0 20px #00a4dc, 0 0 30px #00a4dc, inset 0 0 10px #00a4dc; - animation: pulseGlowImg 2s infinite alternate; -} -@keyframes pulseGlowImg { - from { box-shadow: 0 0 10px #00a4dc, 0 0 20px #00a4dc, 0 0 30px #00a4dc; } - to { box-shadow: 0 0 15px #00a4dc, 0 0 30px #00a4dc, 0 0 45px #00a4dc; } -} - -.custom-overlay-img-Polaroid { - background: white; - padding: 10px 10px 25px 10px; - box-shadow: 0 4px 8px rgba(0,0,0,0.4), 0 12px 24px rgba(0,0,0,0.3); - transform: rotate(-3deg); - border-radius: 2px; -} - -.custom-overlay-img-Vintage { - filter: sepia(0.6) contrast(1.2) brightness(0.9) saturate(1.2) drop-shadow(2px 4px 8px rgba(0,0,0,0.6)); -} - -.custom-overlay-img-Grayscale { - filter: grayscale(100%) contrast(1.1) drop-shadow(2px 4px 8px rgba(0,0,0,0.6)); -} - -/* More Text Overlay Styles */ .custom-overlay-style-SteadyNeon { color: #fff; text-shadow: @@ -1338,3 +1315,94 @@ } } +.custom-overlay-style-Wave { + color: #fff; + text-shadow: 2px 2px 8px rgba(0,0,0,0.8); + display: inline-block; + animation: liquidWave 3s ease-in-out infinite; +} +@keyframes liquidWave { + 0%, 100% { transform: translateY(0) skewY(0); } + 25% { transform: translateY(-3px) skewY(1deg); } + 75% { transform: translateY(3px) skewY(-1deg); } +} + +.custom-overlay-style-VHS { + color: #fff; + text-shadow: 3px 0 0 #f00, -3px 0 0 #0ff; + animation: vhsTracking 2s steps(2, start) infinite; +} +@keyframes vhsTracking { + 0%, 100% { text-shadow: 3px 0 0 #f00, -3px 0 0 #0ff; } + 50% { text-shadow: 2px 1px 0 #f00, -2px -1px 0 #0ff; } +} + +.custom-overlay-style-Matrix { + color: #0f0; + font-family: monospace; + text-shadow: 0 0 5px #0f0, 0 0 10px #0f0; + letter-spacing: 2px; + animation: matrixGlow 2s infinite alternate; +} +@keyframes matrixGlow { + to { text-shadow: 0 0 10px #0f0, 0 0 20px #0f0; } +} + +/* Custom Overlay Image Styles */ +.custom-overlay-img-RoundedShadow { + border-radius: 12px; + filter: drop-shadow(0px 10px 15px rgba(0, 0, 0, 0.6)); +} + +.custom-overlay-img-GlowingBorder { + border-radius: 8px; + box-shadow: 0 0 10px #00a4dc, 0 0 20px #00a4dc, 0 0 30px #00a4dc, inset 0 0 10px #00a4dc; + animation: pulseGlowImg 2s infinite alternate; +} +@keyframes pulseGlowImg { + from { box-shadow: 0 0 10px #00a4dc, 0 0 20px #00a4dc, 0 0 30px #00a4dc; } + to { box-shadow: 0 0 15px #00a4dc, 0 0 30px #00a4dc, 0 0 45px #00a4dc; } +} + +.custom-overlay-img-Polaroid { + background: white; + padding: 10px 10px 25px 10px; + box-shadow: 0 4px 8px rgba(0,0,0,0.4), 0 12px 24px rgba(0,0,0,0.3); + transform: rotate(-3deg); + border-radius: 2px; +} + +.custom-overlay-img-Vintage { + filter: sepia(0.6) contrast(1.2) brightness(0.9) saturate(1.2) drop-shadow(2px 4px 8px rgba(0,0,0,0.6)); +} + +.custom-overlay-img-Grayscale { + filter: grayscale(100%) contrast(1.1) drop-shadow(2px 4px 8px rgba(0,0,0,0.6)); +} + +.custom-overlay-img-Hologram { + filter: drop-shadow(0 0 10px #0ff) sepia(0.8) hue-rotate(180deg) saturate(3); + opacity: 0.8; + animation: hologramFlicker 3s infinite; +} +@keyframes hologramFlicker { + 0% { opacity: 0.8; transform: skewX(0); } + 5% { opacity: 0.5; transform: skewX(2deg); } + 10% { opacity: 0.9; transform: skewX(-2deg); } + 15% { opacity: 0.8; transform: skewX(0); } + 100% { opacity: 0.8; } +} + +.custom-overlay-img-CRT { + filter: contrast(1.5) brightness(1.2) drop-shadow(3px 0 0 rgba(255,0,0,0.5)) drop-shadow(-3px 0 0 rgba(0,0,255,0.5)); +} + +.custom-overlay-img-Floating { + filter: drop-shadow(0 15px 20px rgba(0,0,0,0.6)); + animation: floatImg 4s ease-in-out infinite; +} +@keyframes floatImg { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-15px); } +} + diff --git a/configPageExample.html b/configPageExample.html new file mode 100644 index 0000000..3ab30c1 --- /dev/null +++ b/configPageExample.html @@ -0,0 +1,2911 @@ + + + + Jellyfin Enhanced + + + + + + + + Jellyfin Enhanced + + + Help + + + + + + + ⚠️ Highly Recommended + It is highly recommended to have the File Transformation plugin installed. It helps avoid permission issues while modifying index.html on any kind of installation. + Learn more + + Dismiss + Don't show again + + + + + Enhanced Settings + Elsewhere Settings + Seerr Settings + *arr Settings + Other Settings + + + + + Default User Settings + + + ⏯️ Playback Settings + + Auto-pause on tab switch + Auto-resume on tab switch + Auto Picture-in-Picture on tab switch + Long press/hold for 2x speed β + Enable Custom Pause ScreenThis feature is a modified version of the original script by BobHasNoSoul + + + + ↪️ Auto-Skip Settings + + Auto-skip Intro + Auto-skip Outro + + + + 📝 Subtitle Settings + + Disable Custom Subtitle Styles by default + + Default Subtitle Style + + Clean White + Classic Black Box + Netflix Style + Cinema Yellow + Soft Gray + High Contrast + + + + Default Subtitle Size + + Tiny + Small + Normal + Large + Extra Large + Gigantic + + + + Default Subtitle Font + + Default + Noto Sans + Sans Serif + Typewriter + Roboto + + + + + + 🎲 Random Button Settings + + Enable Random Button + Show unwatched only in random selection + Include movies in random selection + Include shows in random selection + + + + 🖥️ UI Settings + + Show watch progress + + Watch Progress Default + + Percentage + Time + + Choose default display for watch progress. + + + Watch Progress Time Format + + h:m + y:mo:d:h:m + + Select how time-based progress is shown. + + Show file sizes + Show available audio languages on item detail page + Enable "Remove from Continue Watching" + Enable Quality TagsThis feature is a slightly modified version of the original script by BobHasNoSoul + Enable Genre Tags + Enable Language Tags + Enable Rating Tags + Enable People Tags + + + + Quality Tags Position + + Top Left + Top Right + Bottom Left + Bottom Right + + + + Genre Tags Position + + Top Left + Top Right + Bottom Left + Bottom Right + + + + Language Tags Position + + Top Left + Top Right + Bottom Left + Bottom Right + + + + Rating Tags Position + + Top Left + Top Right + Bottom Left + Bottom Right + + + + Tags Cache Duration (days) + + How long to cache the above tags data (1-365 days, default: 30) + + + + + Show Rating in Video Player + Display TMDB and Rotten Tomatoes ratings before "Ends at" time in video player + + + + + + Clear All Client Caches + + Forces all clients to clear Language, Quality, Genre and Rating tag caches on next load After the cache is cleared, the clients will re-fetch the tags data to build the cache again, which might cause some slowness on first load. + + + Disable Tags on Search PagePrevents quality/language/genre/rating tags from showing on search results. Recommended for compatibility with Gelato plugin. + + + + 🌐 Language Settings + + + Default UI Language + + System Default + + Sets the default language for all users. Users can still override this in their own settings. + + + + + ⌨️ Disable Keyboard Shortcuts + Disables all keyboard shortcuts, and hides the 'Shortcuts' tab in enhanced panel + + + + + Apply Above Settings to All Users + + + + info + + This will save the current configuration and apply all the above settings to every user on this server. + Note: Users can individually customize their own settings by opening the Enhanced Panel: + • Press ? key (default shortcut) + • Long Press on user profile picture in the top-right corner + • Through "Jellyfin Enhanced" link from the side bar + • Access via playback controls menu during video playback (only on mobile devices) + + + + + + + Icons + + Icon Settings + + + + Use Icons + + + + Display icons throughout the UI (toasts, settings panel headers, etc.) + + + Icon Style + + Emoji + Lucide Icons + Material UI Icons + + Choose between emoji characters, Lucide icons, or Material UI icons. + + + + Shortcut Overrides + + Add Override + + + + + + + + + Add + + + + + Modifier Keys: Use `Shift+`, `Ctrl+`, or `Alt+`. Examples: `Shift+A`, `Ctrl+S`. + + + Configured Overrides + + + + + Bookmarks + + + + + Enable Bookmarks Feature + + + + Save custom bookmarks/timestamps while watching videos to quickly jump back to your favorite scenes or moments. + + + + + Use Plugin Pages for Bookmarks Library + + + Adds a "Bookmarks" link to the sidebar via Plugin Pages.Note: Jellyfin must be restarted after enabling this option for the first time for changes to take effect. + + + info + + How to Use Bookmarks: + + During Playback: Press B or click the bookmark icon bookmark_add in the video player controls to save a bookmark at the current time. + Visual Markers: Bookmarks appear as location pins location_pin on the video progress bar. Click a marker to jump to that timestamp. + Multi-Version Support: Bookmarks track by TMDB/TVDB IDs, so they work across different file versions of the same content. + + To View & Manage All Your Bookmarks: + You can either enable Plugin Pages above to add a sidebar link, or create a custom view using the Custom Tabs plugin. + + Install the Custom Tabs plugin and all its prerequisites. + Navigate to Dashboard > Plugins > Custom Tabs and add a new tab. + Add "Display Text" according to your preference (e.g., "My Bookmarks"). + Copy the code below and paste it into the 'HTML Content' field. + + + + content_copy + Copy + + <div class="sections bookmarks"></div> + + + + Already have Custom Tabs installed? + Click here to configure it. + + + + + + + + Timeout Settings + + + Help Panel Autoclose Delay (in milliseconds) + + How long the help panel stays open before auto-closing. + + + Toast Duration (in milliseconds) + + How long toast notifications are displayed. + + + + + Hidden Content + + + + + Enable Hidden Content + + + Enable or disable the Hidden Content feature server-wide. When disabled, hide buttons, filtering, sidebar navigation, and settings panel options are all removed. User data is preserved and restored when re-enabled. + + + + Use Plugin Pages for Hidden Content + + + Replaces the default Hidden Content page with a Plugin Pages implementation.Note: Jellyfin must be restarted after enabling this option for the first time for changes to take effect. + + + + Use Custom Tabs for Hidden Content (instead of sidebar) + + + + + info + + Embed Hidden Content in Custom Tabs: + Display the Hidden Content page in a Custom Tabs tab instead of the sidebar link. + You need the Custom Tabs plugin. + + Install the Custom Tabs plugin and all its prerequisites. + Navigate to Dashboard > Plugins > Custom Tabs and add a new tab. + Add "Display Text" according to your preference (e.g., "Hidden Content"). + Copy the code below and paste it into the 'HTML Content' field. + + + + content_copy + Copy + + <div class="jellyfinenhanced hidden-content"></div> + + + + Already have Custom Tabs installed? + Click here to configure it. + + + + + + + + + + + Configuration + + + + Enable ElsewhereShow Streaming Provider Lookup on Item Details Page + + + + + TMDB API Key + + + + + Test + + + Your API key from The Movie DB (TMDB). How to? + Shared with Seerr Settings + + + Default Region + + The default two-letter country code for streaming provider lookup. Empty defaults to US. Full List + + + Default Providers + + A list of default streaming providers to show. Leave blank to show all. Full List + + + Ignore Providers + + A list of providers to ignore from the default region (supports regex).Full List + + Elsewhere Custom Branding + + Custom Branding Message + + Custom message to display when content is not available on any streaming providers. Leave blank to disable custom branding. + + + Custom Branding Icon URL + + Optional URL for an icon to display next to the custom message. Leave blank for no icon. + + Extras + + + Show Reviews from TMDB in Item Details PageRequires TMDB API Key to be set + + + + + Expand reviews by defaultWhen enabled, the reviews section opens expanded by default for all users (unless they override in their session). + + + + + + + + Setup + + + Seerr URL(s) + + Enter your Seerr instance URLs, one per line. The script will use the first one that connects successfully. + + + Seerr API Key + + + + + Test + + + API key from Seerr. (Settings > General Settings > API Key) + + + TMDB API Key + + + + + Test + + + Your API key from The Movie DB (TMDB). How to?TMDB API key for automatic movie collection requests and Seerr Collection Results. Shared with Elsewhere Settings + + + + Advanced URL Mappings (Optional) + + + Seerr URL Mappings + + Map your Jellyfin URLs to specific Seerr URLs for displaying links to users. Users will get the appropriate Seerr link when using "Link result titles to Seerr instead of TMDB", based on what link they use to access Jellyfin. Format: JellyfinURL|SeerrURL, one mapping per line.If no mapping matches or this is empty, the first URL from above will be used. + + + info + + URL Mapping Examples: + • Remote access: https://jellyfin.mydomain.com|https://jellyseerr.mydomain.com + • Local access: http://192.168.1.10:8096|http://192.168.1.10:5055 + • With base URL: https://example.com/jellyfin|https://example.com/jellyseerr + + + + phone_android + + Make sure proper mappings are added to appropriate URLs in order for mobile clients to open Seerr links correctly. + + + + + + + info + + "Enable Jellyfin Sign-In" must be enabled in Seerr User Settings (/settings/users) + All users must be imported to Seerr as Jellyfin users for them to be able to request content. + + + + favorite + + This plugin is not affiliated with Jellyseerr. + Seerr is an independent project, and this plugin simply integrates with it to enhance the Jellyfin experience, huge thanks to their API + Please report any issues with this plugin to the plugin repository, not to the Seerr team. + + + + + + Search + + + Search Integration + + + + + Show Seerr Results in Search + + + Enhance Jellyfin search by showing results from Seerr.This allows users to discover and request missing movies and shows directly from Jellyfin. + + + + Show Collections in Seerr Search Results + + + Display TMDB collections (e.g., Harry Potter, Marvel Cinematic Universe) in Seerr search results with an option to request the entire collection at once.Requires TMDB API Key to be configured. + + + + + Request Options + + + + + Enable 4K Requests + + + When enabled, movie request buttons include a dropdown to request a 4K version. + + + + Show advanced request options + + + Enable this if you want users to be able to choose between Servers, Quality and Paths while requesting. Note: This will disregard any Override Rules set in Seerr. + + + + + + Other Features + + + + + Open result titles and posters in "More Info" modal + + + When enabled, clicking a Seerr result title or poster opens an in-app More Info modal.When disabled, clicking will open the item in Seerr. + + + + Show "Report Issue" button on item detail pages + + + When enabled, a small report icon will be added to item action icons allowing users to report playback/content issues to Seerr. + + + + Exclude blocklisted movies/series from suggestions + + + When enabled, items marked as blocklisted in Seerr will not appear in similar/recommended suggestions. + + + + Show Streaming Providers on Seerr Posters + + + Displays icons of available streaming services (from your default region) on Seerr Posters.Requires TMDB API Key to be configured in Elsewhere Settings + + + + Discovery & Recommendations + + + Item Detail Page + + + + + Show similar items on item details page + + + Displays similar movies/shows from Seerr on item details pages. Limited to 20 items. + + + + Show recommended items on item details page + + + Displays recommended movies/shows from Seerr on item details pages. Limited to 20 items. + + + + Exclude items already in library + + + Hide similar/recommended items that already exist in your Jellyfin library from the results.If disabled, the items that are available link to the media item in the library. + + + + + "More" Discovery + + + + + Show "More from [Network]" on studio/network pages + + + When viewing a network page (e.g., Netflix, HBO), displays additional content from that network available in Seerr. + + + + + Show "More [Genre]" on genre pages + + + When viewing a genre page (e.g., Action, Comedy), displays additional content in that genre from Seerr. + + + + + Show "More [Tag]" on tag pages + + + When viewing a tag page (e.g., cooking, superhero), displays additional content with that keyword from Seerr. + + + + + Show "More from [Actor]" on person pages + + + When viewing an actor/person page, displays their filmography from Seerr including content not in your library. + + + + + Show missing collection movies on BoxSet pages + + + When viewing a collection/BoxSet page, shows movies from the collection that are not yet in your library with request buttons. + + + + + + Testing / Debug + + + + + Disable server-side response cache + + + When enabled, all Seerr proxy requests bypass the server-side cache and are fetched fresh from Seerr every time. This is useful for testing but will increase load on your Seerr instance. Not recommended for normal use. + + Response Cache TTL (minutes) + + How long Seerr API responses (search, discovery, recommendations) are cached. Default: 10 minutes. + + + User ID Cache TTL (minutes) + + How long the Jellyfin → Seerr user ID mapping is cached. Default: 30 minutes. + + + + + Automatic Requests + + + Advanced Season Requests + + + + + Enable Automatic Advance Season Requests + + + + Automatically request the next season in Seerr when a user about to finish the current season. + + + + + + Require All Prior Episodes Watched + + + + Require all episodes before the threshold to be watched. For example, if threshold is 2 and there are 10 episodes, when watching episode 8, all episodes 1-7 must be watched or marked as watched. This prevents accidentally triggering requests by jumping to later episodes. This might cause requests to not be triggered if users skip episodes. + + + Episodes Remaining Threshold + + Request the next season when the number of unwatched episodes is at or below this number. Default: 2 episodes. + + + info + + • Requests are triggered when users start or complete watching episodes + • Set threshold to 2-3 episodes to give time for downloading before users finish watching + • Example: With threshold set to 2, when user starts episode 8 of 10 of season 1, season 2 will be requested + • If a user has already triggered a request for the season, it will not be requested again even if another user reaches the threshold + + + + + + + Movie Collection Requests + + + + + Enable Automatic Movie Requests + + + + Automatically request the next movie in a collection based on configured triggers. + + Requirements: TMDB API key must be configured. + + + + Request Triggers: + + + + When movie starts (within first 5 minutes) + + + + + + After watching for a certain amount of time + + + + Select one or both triggers. If both are selected, the request will be triggered if either condition is met. + + + + + Minutes Watched Before Auto-Request Check: + + minutes + + How many minutes a user should watch before triggering a request? Must be between 1 and 180 minutes. + + + + + + + Only request if movie is already released + + + + When enabled, future releases will be skipped. Only movies that have already been released will be requested. + + + info + + • Only works for movies that are part of a TMDB collection (e.g., The Avengers Collection, Star Wars Collection, etc.) + • Automatically requests the next movie in the collection based on release order + • Movies already available or requested will be skipped + + + + + + + + Watchlist Integration + + + + + Automatically add requested media to user's Watchlist + + + + When enabled, any media requested through Seerr Search through Jellyfin Enhanced will automatically be added to the requesting user's Watchlist. + + Note: This feature requires KefinTweaks to be installed to actually view the watchlisted items. + + + + + Sync Seerr Watchlist to Jellyfin + + + + When enabled, items from each user's Seerr watchlist will be automatically synced to their Jellyfin watchlist. + Configure how often the sync should run in Jellyfin Dashboard > Scheduled Tasks > "Sync Watchlist from Seerr to Jellyfin". + + + + + Prevent re-adding removed watchlist items + + + + When enabled, items will only be added to a user's watchlist once. If a user manually removes an item from their watchlist, it won't be automatically re-added during future sync runs. + + + Memory retention period (days): + + + How long to remember that an item was processed before allowing it to be re-added.After this period, manually removed items (if unwatched) will be automatically re-added to watchlist if they're still in Seerr requests or Seerr watchlist. + + Examples: + + 30 days - Short memory, items can be re-added after 1 month + 365 days - Remember for 1 year (recommended) + 3650 days - Remember for 10 years (nearly permanent) + + Note: This setting only applies when "Prevent re-adding removed watchlist items" is enabled. + + + + + + + + Configuration + + + Sonarr URL + + + + Radarr URL + + + + Bazarr URL + + Note: This links to the main movies/series page as Bazarr does not support direct links. + + + Sonarr API Key + + Find this in Sonarr under Settings > General > Security > API Key + + + Radarr API Key + + Find this in Radarr under Settings > General > Security > API Key + + + + + + *arr Links + + + + + Enable *arr Links + + + Show Sonarr, Radarr, and Bazarr links of the items on item detail pages for quick access. Only for admins. If no URL is added for a service below, links for that service are not shown. + + Sonarr URL Mappings + + Map Jellyfin access URLs to Sonarr URLs. Format: jellyfin_url|sonarr_url (one per line). + + + Radarr URL Mappings + + Map Jellyfin access URLs to Radarr URLs. Format: jellyfin_url|radarr_url (one per line). + + + Bazarr URL Mappings + + Map Jellyfin access URLs to Bazarr URLs. Format: jellyfin_url|bazarr_url (one per line).These mappings are useful when accessing Jellyfin via different URLs (e.g., internal network vs. public domain) and you need *arr links to use the appropriate corresponding URL. + + + + + Show links as text instead of icons + + + + + + *arr Tags Sync + + + + + Enable *arr Tags Sync + + + + Fetches all tags from Radarr and Sonarr instances defined above and adds them as Jellyfin tags. + + Note: Run the sync task from Dashboard > Scheduled Tasks > "Sync Tags from *arr to Jellyfin" and configure how often it should run. + + + Tag Prefix + + Prefix to add before each tag (e.g., "JE Arr Tag: " will result in "JE Arr Tag: 1 - Jellyfish") + + Warning: Keep the tag prefix constant - it's used to remove old tags before syncing new ones. Having no prefix will remove ALL tags! You can change it, but make sure it's unique. + + + + + Clear old tags with prefix before syncing + + + When enabled, removes all existing tags starting with the prefix before adding new ones. This keeps tags in sync with your *arr applications. + + + + Show synced tags as clickable links + + + Displays tags with the configured prefix as clickable links on item detail pages. Helpful if your theme hides tags. + + Filter Tags to Sync to Jellyfin (Optional) + + + + Only sync specific tags from *arr to Jellyfin. Enter one tag name per line (without the prefix). + Leave empty to sync all tags. + Example: If you only want to sync "1 - Jellyfish" and "trakt" tags, enter: + 1 - Jellyfish + trakt + + + + Filter Tags to Show as Links (Optional) + + + + Only show specific tags as links. Enter one tag name per line (without the prefix). + Leave empty to show all tags with the prefix. + Example: If you only want "JE Arr Tag: 1 - Jellyfish" and "JE Arr Tag: trakt" as links, enter: + 1 - Jellyfish + trakt + + + + Hide Specific Tags from Links (Optional) + + + + Hide specific tags from being displayed as links. Enter one tag name per line (without the prefix). + This filter takes priority over the "Show" filter above. + Example: To hide "JE Arr Tag: spam_tag" and "JE Arr Tag: test_tag" from links, enter: + spam_tag + test_tag + + + + + + Requests Page + + + + + Enable Requests Page + + + Adds a "Requests" link to the navigation showing active downloads and requests. + + + + Show Seerr Issues Section + + + Shows open/resolved Seerr issues beneath Requests. + + + + Use Plugin Pages for Requests Page + + + Replaces the default Requests page with a Plugin Pages implementation.Note: Jellyfin must be restarted after enabling this option for the first time for changes to take effect. + + + + Use Custom Tabs for Requests (instead of sidebar) + + + + + info + + Embed Requests in Custom Tabs: + Display the Requests page in a Custom Tabs tab instead of the sidebar link. + You need the Custom Tabs plugin. + + Install the Custom Tabs plugin and all its prerequisites. + Navigate to Dashboard > Plugins > Custom Tabs and add a new tab. + Add "Display Text" according to your preference (e.g., "Requests") + Copy the code below and paste it into the 'HTML Content' field. + + + + content_copy + Copy + + <div class="jellyfinenhanced requests"></div> + + + + Already have Custom Tabs installed? + Click here to configure it. + + + + + + + + Enable Auto-Refresh for Downloads + + + + Poll Interval (seconds) + + + How often to refresh download status automatically. Minimum is 30 seconds and applies only when auto-refresh is enabled. + + + + info + + Requests Page shows a Requests Button in sidebar with a dedicated view of active downloads from Sonarr/Radarr and requests from Jellyseerr. + + Requirements: Configure Sonarr/Radarr and Seerr URLs and API keys. + + + + + + Calendar Page + + + + + Enable Calendar Page + + + Adds a "Calendar" link to the navigation showing upcoming releases from Sonarr/Radarr. + + + + Use Plugin Pages for Calendar Page + + + Replaces the default Calendar page with a Plugin Pages implementation.Note: Jellyfin must be restarted after enabling this option for the first time for changes to take effect. + + + + Use Custom Tabs for Calendar (instead of sidebar) + + + + + info + + Embed Calendar in Custom Tabs: + Display the Calendar page in a Custom Tabs tab instead of the sidebar link. + You need the Custom Tabs plugin. + + Install the Custom Tabs plugin and all its prerequisites. + Navigate to Dashboard > Plugins > Custom Tabs and add a new tab. + Add "Display Text" according to your preference (e.g., "Calendar"). + Copy the code below and paste it into the 'HTML Content' field. + + + + content_copy + Copy + + <div class="jellyfinenhanced calendar"></div> + + + + Already have Custom Tabs installed? + Click here to configure it. + + + + + + First Day of Week + + Sunday + Monday + Tuesday + Wednesday + Thursday + Friday + Saturday + + + + Time Format + + 5pm/5:30pm + 17:00/17:30 + + + + + + Highlight Favorites/Watchlist + + + Show a golden border on calendar entries for items in your Jellyfin favorites. + + + + Highlight Watched Series + + + Show a border on calendar entries for series you have watched episodes from. + + + + Show Requested Items Only by Default + + + When enabled, the calendar will load showing only Requested items by default, but users can still change filters. + + + + calendar_today + + Calendar Page adds a Calendar Button in sidebar with a dedicated view that displays upcoming releases from Sonarr and Radarr in a calendar view. + + Requirements: Configure Sonarr and/or Radarr URLs and API keys. + + + + + + + + n00bcodr's Personal Scripts + + + + + Enable Colored Ratings + + + + Applies color-coded backgrounds to media ratings. + + View Screenshot + + + + + If you notice missing or incorrect ratings, please submit a PR to Jellyfin-Enhanced. + + + + + + Enable Theme Selector + + + + Adds a theme selector to quickly switch between Jellyfish color themes. + + View Screenshot + + + + + + + note + + Only changes the color theme of Jellyfish when used natively, not through KefinTweaks. + + + + + + + Enable Colored Activity Icons + + + + Replaces Dashboard Activity Icons with Material Design icons and background color. + + View Screenshot + + + + + + + info + + Currently works with English activity logs only. PRs welcome for additional languages. + + + + + + Enable Login Image + + + + Displays the user's profile picture on the manual login screen instead of the name. When a user is selected, their avatar appears above the password field. + + View Screenshot + + + + + + + warning + + Only use this script if all your users are visible on the login screen. This completely hides the username text input field, making it difficult to manually login. + + + + + + + Enable Plugin Icons + + + + Replaces default plugin folder icons with custom icons on the Dashboard sidebar. + + View Screenshot + + + + + + + note + + Currently replaces icons for (if installed): Jellyfin Enhanced, JavaScript Injector, Intro Skipper, Reports, JellySleep, Home Screen Sections, and File Transformation. + + + + + Custom Plugin Links + + Add custom plugin links to the Dashboard sidebar. Each link should be on a new line in the format: Configuration Page Name | icon_name + + + Custom Plugin Links + + + Format: Configuration Page Name | Material Icon Name + Important: Use the exact name that appears in the configuration page URL (e.g., "Jellyfin%20Tweaks" becomes "Jellyfin Tweaks") + + Browse Material Icons + + + + Test Links + + + Use "Test Links" to preview your custom plugin links in the sidebar.Test links will appear immediately and disappear on page refresh. + + + + + + External Links + + + + + Enable Letterboxd External Links + + + Adds Letterboxd links to item details page. + + + + Show Letterboxd link as text instead of icon + + + When enabled, shows "Letterboxd" as text instead of the Letterboxd icon. + + + + Enable Metadata Icons (by Druidblack) + + + + Shows metadata icons instead of text. From Druidblack/jellyfin-icon-metadata. + When enabled, links from the plugin will also use icons instead of text (Letterboxd, *arr Links.). + + + + + Custom Branding + + + + info + + Requires the File Transformation Plugin to be installed and enabled. Upload custom PNG images to replace Jellyfin's default web logos. + + + + + Icon Transparent + The icon in the top-left beside the server version + + Jellyfin Default: 536x536px (square) + Format: PNG with transparency + + + + + image + + + + Drop image here or click to upload + Max 10MB + + + delete + + + + + Favicon + Favicon in the browser tab + + Jellyfin Default: 16x16px, 32x32px, or 48x48px + Format: ICO, PNG, or SVG + + + + + image + + + + Drop image here or click to upload + Max 10MB + + + delete + + + + + Banner Light + Splash Screen Banner for Dark Mode + + Jellyfin Default: 1302x378px + Format: PNG, JPG, or WebP + + + + + image + + + + Drop image here or click to upload + Max 10MB + + + delete + + + + + Banner Dark + Splash Screen Banner for Light Mode + + Jellyfin Default: 1302x378px + Format: PNG, JPG, or WebP + + + + + image + + + + Drop image here or click to upload + Max 10MB + + + delete + + + + + Apple Touch Icon + Icon shown when adding to iOS Home Screen + + Jellyfin Default: 180x180px + Format: PNG + + + + + image + + + + Drop image here or click to upload + Max 10MB + + + delete + + + + + + + + + + + + Enable Custom Splash Screen + + Splash Screen Image URL + + URL for the splash screen image. Defaults to Jellyfin banner. + + + + info + + Might cause strange behaviour if jellyfin-plugin-media-bar is installed. + + + If you are on Jellyfin version 10.11 and you want the default Jellyfin Banner - Change the splash screen URL to "/web/banner-light.b113d4d1c6c07fcb73f0.png" + + + + + + + info + + All changes require a page refresh to take effect. + If old settings persist, please force clear browser cache. + + + + + Save + + + + + + + + + +
index.html
?
+ Modifier Keys: Use `Shift+`, `Ctrl+`, or `Alt+`. Examples: `Shift+A`, `Ctrl+S`. +
You can either enable Plugin Pages above to add a sidebar link, or create a custom view using the Custom Tabs plugin.
<div class="sections bookmarks"></div> +
Display the Hidden Content page in a Custom Tabs tab instead of the sidebar link.
You need the Custom Tabs plugin.
<div class="jellyfinenhanced hidden-content"></div> +
JellyfinURL|SeerrURL
https://jellyfin.mydomain.com|https://jellyseerr.mydomain.com
http://192.168.1.10:8096|http://192.168.1.10:5055
https://example.com/jellyfin|https://example.com/jellyseerr
jellyfin_url|sonarr_url
jellyfin_url|radarr_url
jellyfin_url|bazarr_url
1 - Jellyfish
trakt
spam_tag
test_tag
Display the Requests page in a Custom Tabs tab instead of the sidebar link.
<div class="jellyfinenhanced requests"></div> +
Display the Calendar page in a Custom Tabs tab instead of the sidebar link.
<div class="jellyfinenhanced calendar"></div> +
Configuration Page Name | icon_name
Configuration Page Name | Material Icon Name