Files
CinemaTrailers4Jellyfins/Jellyfin.Plugin.CinemaTrailers4Jellyfins/Configuration/config.html
Martin a0bddac48d
All checks were successful
Publish Release / release (push) Successful in 14s
feat: add Trailer Pre-Roll and Feature Pre-Roll bumpers
Adds two optional IIntroProvider bumper slots, mirroring CherryFloors'
cinema mode plugin: a "Trailer Pre-Roll" played before the trailer block
and a "Feature Pre-Roll" played right before the movie/episode. Each is
configured by picking an existing Jellyfin Movie library, from which a
random Movie is injected as the bumper.

Bump version to 1.0.0.5.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 01:13:52 -04:00

505 lines
34 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<title>CinemaTrailers4Jellyfins</title>
</head>
<body>
<div data-role="page" class="page type-interior pluginConfigurationPage cinemaTrailers4JellyfinsConfigPage"
data-require="emby-input,emby-button,emby-checkbox,emby-select">
<div data-role="content">
<div class="content-primary">
<form class="cinemaTrailers4JellyfinsConfigPage">
<div class="sectionTitleContainer flex align-items-center">
<h2 class="sectionTitle">CinemaTrailers4Jellyfins</h2>
<a is="emby-linkbutton" class="raised button-alt headerHelpButton emby-button"
target="_blank" href="https://www.git.quarantinedstudio.com/mvezina/CinemaTrailers4Jellyfins#readme">Help</a>
</div>
<div class="verticalSection">
<p>
Downloads trailers for <strong>upcoming and recently released movies not in your library</strong>
from TMDB/YouTube and stores each one inside its own fake-movie folder, ready to be
picked up by a Cinema Mode / trailer pre-roll plugin.
</p>
<p style="margin-top:0">
A free TMDB API key is required.
<a href="https://www.themoviedb.org/settings/api" target="_blank" rel="noopener">Get one here →</a>
</p>
</div>
<!-- TMDB -->
<fieldset class="verticalSection verticalSection-extrabottompadding">
<legend><h3 class="sectionTitle">TMDB</h3></legend>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="tmdb-api-key">TMDB API Key</label>
<input type="password" id="tmdb-api-key" is="emby-input" autocomplete="off" />
<div class="fieldDescription">
Your TMDB Read Access Token (JWT) or v3 API key from
<a href="https://www.themoviedb.org/settings/api" target="_blank" rel="noopener">themoviedb.org/settings/api</a>.
</div>
</div>
</fieldset>
<!-- Languages -->
<fieldset class="verticalSection verticalSection-extrabottompadding">
<legend><h3 class="sectionTitle">Trailer Languages</h3></legend>
<p class="fieldDescription">
Only download trailers in the selected languages.
Leave everything unchecked to allow all languages.
</p>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:0 2em;">
<div class="checkboxContainer"><label><input is="emby-checkbox" type="checkbox" id="lang-en" /><span>English</span></label></div>
<div class="checkboxContainer"><label><input is="emby-checkbox" type="checkbox" id="lang-es" /><span>Spanish</span></label></div>
<div class="checkboxContainer"><label><input is="emby-checkbox" type="checkbox" id="lang-fr" /><span>French</span></label></div>
<div class="checkboxContainer"><label><input is="emby-checkbox" type="checkbox" id="lang-de" /><span>German</span></label></div>
<div class="checkboxContainer"><label><input is="emby-checkbox" type="checkbox" id="lang-it" /><span>Italian</span></label></div>
<div class="checkboxContainer"><label><input is="emby-checkbox" type="checkbox" id="lang-pt" /><span>Portuguese</span></label></div>
<div class="checkboxContainer"><label><input is="emby-checkbox" type="checkbox" id="lang-nl" /><span>Dutch</span></label></div>
<div class="checkboxContainer"><label><input is="emby-checkbox" type="checkbox" id="lang-ru" /><span>Russian</span></label></div>
<div class="checkboxContainer"><label><input is="emby-checkbox" type="checkbox" id="lang-pl" /><span>Polish</span></label></div>
<div class="checkboxContainer"><label><input is="emby-checkbox" type="checkbox" id="lang-sv" /><span>Swedish</span></label></div>
<div class="checkboxContainer"><label><input is="emby-checkbox" type="checkbox" id="lang-no" /><span>Norwegian</span></label></div>
<div class="checkboxContainer"><label><input is="emby-checkbox" type="checkbox" id="lang-da" /><span>Danish</span></label></div>
<div class="checkboxContainer"><label><input is="emby-checkbox" type="checkbox" id="lang-ja" /><span>Japanese</span></label></div>
<div class="checkboxContainer"><label><input is="emby-checkbox" type="checkbox" id="lang-ko" /><span>Korean</span></label></div>
<div class="checkboxContainer"><label><input is="emby-checkbox" type="checkbox" id="lang-zh" /><span>Chinese</span></label></div>
<div class="checkboxContainer"><label><input is="emby-checkbox" type="checkbox" id="lang-ar" /><span>Arabic</span></label></div>
<div class="checkboxContainer"><label><input is="emby-checkbox" type="checkbox" id="lang-hi" /><span>Hindi</span></label></div>
<div class="checkboxContainer"><label><input is="emby-checkbox" type="checkbox" id="lang-tr" /><span>Turkish</span></label></div>
<div class="checkboxContainer"><label><input is="emby-checkbox" type="checkbox" id="lang-th" /><span>Thai</span></label></div>
<div class="checkboxContainer"><label><input is="emby-checkbox" type="checkbox" id="lang-id" /><span>Indonesian</span></label></div>
</div>
</fieldset>
<!-- Movie Sources -->
<fieldset class="verticalSection verticalSection-extrabottompadding">
<legend><h3 class="sectionTitle">Movie Trailer Sources</h3></legend>
<p class="fieldDescription">
Choose which TMDB lists to pull movie trailers from. Enable multiple for more variety.
</p>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="source-now-playing" />
<span>Now Playing</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
Movies currently in theatres. Refreshes weekly on TMDB.
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="source-upcoming" />
<span>Upcoming</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
Movies coming soon to theatres. Great for seeing what's on the horizon.
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="source-popular" />
<span>Popular</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
Most popular movies on TMDB right now, filtered by the date range below.
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="source-top-rated" />
<span>Top Rated</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
Highest rated movies on TMDB, filtered by the date range below.
</div>
</div>
</fieldset>
<!-- TV Show Sources -->
<fieldset class="verticalSection verticalSection-extrabottompadding">
<legend><h3 class="sectionTitle">TV Show Trailer Sources</h3></legend>
<p class="fieldDescription">
Choose which TMDB lists to pull TV show trailers from. Enable multiple for more variety.
Has no effect if "Max TV show trailers per run" below is set to 0.
</p>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="source-tv-airing-today" />
<span>Airing Today</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
TV shows airing today.
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="source-tv-on-the-air" />
<span>On The Air</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
TV shows airing in the next 7 days.
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="source-tv-popular" />
<span>Popular</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
Most popular TV shows on TMDB right now, filtered by the date range below.
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="source-tv-top-rated" />
<span>Top Rated</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
Highest rated TV shows on TMDB, filtered by the date range below.
</div>
</div>
</fieldset>
<!-- Date Range -->
<fieldset class="verticalSection verticalSection-extrabottompadding">
<legend><h3 class="sectionTitle">Date Range</h3></legend>
<div class="selectContainer">
<label class="selectLabel" for="date-range">Only include titles released within</label>
<select is="emby-select" id="date-range" class="emby-select-withcolor emby-select">
<option value="3">Last 3 months</option>
<option value="6">Last 6 months</option>
<option value="12">Last 1 year</option>
<option value="24">Last 2 years</option>
<option value="0">All time (no limit)</option>
</select>
<div class="fieldDescription">
Applies to all sources (movie release date / TV show first-air date). "Now Playing",
"Upcoming", "Airing Today" and "On The Air" already have tight date windows set by
TMDB, but this provides an additional filter.
</div>
</div>
</fieldset>
<!-- Download Settings -->
<fieldset class="verticalSection verticalSection-extrabottompadding">
<legend><h3 class="sectionTitle">Download Settings</h3></legend>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="download-folder">Output Folder</label>
<input type="text" id="download-folder" is="emby-input" placeholder="/media/trailers" />
<div class="fieldDescription">
Where the fake-movie/trailer folders are created. Add this as a Jellyfin Movies
library and scan it so a Cinema Mode / trailer pre-roll plugin can use the trailers.
</div>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="max-trailers">Max movie trailers per run</label>
<input type="number" id="max-trailers" is="emby-input" min="0" max="200" />
<div class="fieldDescription">
Maximum number of movie trailers to download each time the task runs.
Set to 0 to not download any movie trailers. Default: 20.
</div>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="max-tv-trailers">Max TV show trailers per run</label>
<input type="number" id="max-tv-trailers" is="emby-input" min="0" max="200" />
<div class="fieldDescription">
Maximum number of TV show trailers to download each time the task runs.
Set to 0 to not download any TV show trailers. Default: 0.
</div>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="max-pages">Pages per source</label>
<input type="number" id="max-pages" is="emby-input" min="1" max="10" />
<div class="fieldDescription">
How many pages to fetch from each TMDB source (20 results per page). Default: 3.
</div>
</div>
<div class="selectContainer">
<label class="selectLabel" for="video-quality">Video quality</label>
<select is="emby-select" id="video-quality" class="emby-select-withcolor emby-select">
<option value="720">720p (built-in, no extra tools)</option>
<option value="480">480p (built-in, no extra tools)</option>
<option value="1080">1080p (requires yt-dlp + ffmpeg)</option>
</select>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="skip-library" />
<span>Skip movies/shows already in my Jellyfin library</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
Trailers for movies and TV shows you already own won't be downloaded.
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="skip-downloaded" />
<span>Skip trailers already downloaded</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
If a folder already exists for a movie or TV show, don't re-download it.
</div>
</div>
</fieldset>
<!-- Trailer Rotation -->
<fieldset class="verticalSection verticalSection-extrabottompadding">
<legend><h3 class="sectionTitle">Trailer Rotation</h3></legend>
<p class="fieldDescription">
Keep your trailer library fresh by automatically removing the oldest entries
each time the download task runs.
</p>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="max-total-trailers">Max trailers to keep</label>
<input type="number" id="max-total-trailers" is="emby-input" min="0" max="500" />
<div class="fieldDescription">
Maximum number of trailer folders to keep on disk at once. When this limit is exceeded,
the oldest are deleted first to make room for new downloads. Set to 0 for unlimited.
Default: 50.
</div>
</div>
</fieldset>
<!-- Cinema Mode Integration -->
<fieldset class="verticalSection verticalSection-extrabottompadding">
<legend><h3 class="sectionTitle">Cinema Mode Integration</h3></legend>
<p class="fieldDescription">
When enabled, this plugin registers as an <strong>IIntroProvider</strong> and
injects downloaded trailers before movies — compatible with Jellyfin's built-in
cinema mode support and clients like Wholphin.
The output folder must be added as a Jellyfin <strong>Movies</strong> library
and scanned before trailers appear.
</p>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="trailers-per-movie">Trailers per movie</label>
<input type="number" id="trailers-per-movie" is="emby-input" min="0" max="10" />
<div class="fieldDescription">
Number of trailers to play before each movie. Set to 0 to disable.
Default: 1.
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="filter-genre" />
<span>Match genre to the movie being played</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
Only pick trailers whose genre overlaps with the movie you are watching.
Falls back to any trailer if no match is found.
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="filter-rating" />
<span>Limit to same age rating or lower</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
Never show a trailer rated higher than the movie being played.
Falls back to any trailer if no match is found.
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="avoid-repeats" />
<span>Avoid repeating trailers</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
Cycle through all available trailers before playing any again.
Resets automatically once every trailer has been shown. Default: on.
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="trailers-for-episodes" />
<span>Also play trailers before TV episodes</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
Only plays before the first episode a user watches each day —
if you watch several episodes back-to-back, only the first gets trailers.
</div>
</div>
</fieldset>
<!-- Pre-Roll Bumpers -->
<fieldset class="verticalSection verticalSection-extrabottompadding">
<legend><h3 class="sectionTitle">Pre-Roll Bumpers</h3></legend>
<p class="fieldDescription">
Optional: pick existing Jellyfin Movie libraries to pull random bumper
videos from, bookending the trailer block above. Each is independent —
leave either set to "None" to disable it.
</p>
<div class="selectContainer">
<label class="selectLabel" for="trailer-preroll-library">Trailer Pre-Roll library</label>
<select is="emby-select" id="trailer-preroll-library" class="emby-select-withcolor emby-select">
<option value="">— None (disabled) —</option>
</select>
<div class="fieldDescription">
A Movie library to pick a random "Now Playing" style bumper from,
played before the trailer block.
</div>
</div>
<div class="selectContainer">
<label class="selectLabel" for="feature-preroll-library">Feature Pre-Roll library</label>
<select is="emby-select" id="feature-preroll-library" class="emby-select-withcolor emby-select">
<option value="">— None (disabled) —</option>
</select>
<div class="fieldDescription">
A Movie library to pick a random "Feature Presentation" style bumper
from, played right before the movie/episode (after trailers).
</div>
</div>
</fieldset>
<!-- Advanced -->
<fieldset class="verticalSection verticalSection-extrabottompadding">
<legend><h3 class="sectionTitle">Advanced</h3></legend>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="ytdlp-path">yt-dlp path (optional)</label>
<input type="text" id="ytdlp-path" is="emby-input" placeholder="/usr/local/bin/yt-dlp" />
<div class="fieldDescription">
Full path to <a href="https://github.com/yt-dlp/yt-dlp" target="_blank" rel="noopener">yt-dlp</a>.
Required for 1080p quality. Also needs <strong>ffmpeg</strong> on the system PATH.
Leave blank to use the built-in downloader (720p max, zero extra tools).
</div>
</div>
</fieldset>
<br />
<button is="emby-button" type="submit" class="raised button-submit block">
<span>${Save}</span>
</button>
</form>
</div>
</div>
<script type="text/javascript">
var pluginId = "b581493e-1046-40ed-b6dc-cb8027624984";
// parseInt(...) || fallback treats 0 as falsy, which breaks fields where
// 0 is a meaningful value (e.g. "don't download any").
function parseIntOrDefault(value, fallback) {
var n = parseInt(value, 10);
return isNaN(n) ? fallback : n;
}
$('.cinemaTrailers4JellyfinsConfigPage').on('pageshow', function () {
Dashboard.showLoadingMsg();
Promise.all([
ApiClient.getPluginConfiguration(pluginId),
ApiClient.getJSON(ApiClient.getUrl('Library/VirtualFolders'))
]).then(function (results) {
var config = results[0];
var movieFolders = results[1].filter(function (f) { return f.CollectionType === 'movies'; });
[['trailer-preroll-library', config.TrailerPreRollLibraryId],
['feature-preroll-library', config.FeaturePreRollLibraryId]]
.forEach(function (entry) {
var select = document.getElementById(entry[0]);
movieFolders.forEach(function (f) {
var opt = document.createElement('option');
opt.value = f.ItemId;
opt.text = f.Name;
select.appendChild(opt);
});
select.value = entry[1] || '';
});
document.getElementById('tmdb-api-key').value = config.TmdbApiKey || '';
document.getElementById('source-now-playing').checked = config.SourceNowPlaying !== false;
document.getElementById('source-upcoming').checked = config.SourceUpcoming !== false;
document.getElementById('source-popular').checked = !!config.SourcePopular;
document.getElementById('source-top-rated').checked = !!config.SourceTopRated;
document.getElementById('source-tv-airing-today').checked = config.SourceTvAiringToday !== false;
document.getElementById('source-tv-on-the-air').checked = config.SourceTvOnTheAir !== false;
document.getElementById('source-tv-popular').checked = !!config.SourceTvPopular;
document.getElementById('source-tv-top-rated').checked = !!config.SourceTvTopRated;
document.getElementById('date-range').value = String(config.ReleaseDateRangeMonths ?? 6);
document.getElementById('download-folder').value = config.DownloadFolder || '';
document.getElementById('max-trailers').value = config.MaxTrailersToDownload ?? 20;
document.getElementById('max-tv-trailers').value = config.MaxTvTrailersToDownload ?? 0;
document.getElementById('max-pages').value = config.MaxPagesPerSource ?? 3;
document.getElementById('video-quality').value = String(config.PreferredVideoHeight ?? 720);
document.getElementById('skip-library').checked = config.SkipMoviesInLibrary !== false;
document.getElementById('skip-downloaded').checked = config.SkipAlreadyDownloaded !== false;
document.getElementById('ytdlp-path').value = config.YtDlpPath || '';
var langs = (config.AllowedLanguages || '').split(',').map(l => l.trim()).filter(Boolean);
['en','es','fr','de','it','pt','nl','ru','pl','sv','no','da','ja','ko','zh','ar','hi','tr','th','id']
.forEach(function(code) {
document.getElementById('lang-' + code).checked = langs.length === 0 || langs.indexOf(code) !== -1;
});
document.getElementById('max-total-trailers').value = config.MaxTotalTrailers ?? 50;
document.getElementById('trailers-per-movie').value = config.TrailersPerMovie ?? 1;
document.getElementById('filter-genre').checked = !!config.FilterByGenre;
document.getElementById('filter-rating').checked = !!config.FilterByRating;
document.getElementById('avoid-repeats').checked = config.AvoidRepeats !== false;
document.getElementById('trailers-for-episodes').checked = !!config.TrailersForEpisodes;
Dashboard.hideLoadingMsg();
});
});
$('.cinemaTrailers4JellyfinsConfigPage').on('submit', function () {
Dashboard.showLoadingMsg();
ApiClient.getPluginConfiguration(pluginId).then(function (config) {
config.TmdbApiKey = document.getElementById('tmdb-api-key').value;
config.SourceNowPlaying = document.getElementById('source-now-playing').checked;
config.SourceUpcoming = document.getElementById('source-upcoming').checked;
config.SourcePopular = document.getElementById('source-popular').checked;
config.SourceTopRated = document.getElementById('source-top-rated').checked;
config.SourceTvAiringToday = document.getElementById('source-tv-airing-today').checked;
config.SourceTvOnTheAir = document.getElementById('source-tv-on-the-air').checked;
config.SourceTvPopular = document.getElementById('source-tv-popular').checked;
config.SourceTvTopRated = document.getElementById('source-tv-top-rated').checked;
config.ReleaseDateRangeMonths = parseInt(document.getElementById('date-range').value, 10);
config.DownloadFolder = document.getElementById('download-folder').value;
config.MaxTrailersToDownload = parseIntOrDefault(document.getElementById('max-trailers').value, 20);
config.MaxTvTrailersToDownload = parseIntOrDefault(document.getElementById('max-tv-trailers').value, 0);
config.MaxPagesPerSource = parseInt(document.getElementById('max-pages').value, 10) || 3;
config.PreferredVideoHeight = parseInt(document.getElementById('video-quality').value, 10) || 720;
config.SkipMoviesInLibrary = document.getElementById('skip-library').checked;
config.SkipAlreadyDownloaded = document.getElementById('skip-downloaded').checked;
config.YtDlpPath = document.getElementById('ytdlp-path').value;
var allLangCodes = ['en','es','fr','de','it','pt','nl','ru','pl','sv','no','da','ja','ko','zh','ar','hi','tr','th','id'];
var checkedLangs = allLangCodes.filter(function(code) {
return document.getElementById('lang-' + code).checked;
});
// If all are checked treat it as "no preference" (empty string)
config.AllowedLanguages = checkedLangs.length === allLangCodes.length ? '' : checkedLangs.join(',');
config.MaxTotalTrailers = parseIntOrDefault(document.getElementById('max-total-trailers').value, 50);
config.TrailersPerMovie = parseInt(document.getElementById('trailers-per-movie').value, 10) || 0;
config.FilterByGenre = document.getElementById('filter-genre').checked;
config.FilterByRating = document.getElementById('filter-rating').checked;
config.AvoidRepeats = document.getElementById('avoid-repeats').checked;
config.TrailersForEpisodes = document.getElementById('trailers-for-episodes').checked;
config.TrailerPreRollLibraryId = document.getElementById('trailer-preroll-library').value;
config.FeaturePreRollLibraryId = document.getElementById('feature-preroll-library').value;
ApiClient.updatePluginConfiguration(pluginId, config)
.then(Dashboard.processPluginConfigurationUpdateResult);
});
return false;
});
</script>
</div>
</body>
</html>