feat: add TV show trailer downloads, episode trailers, and movie/TV trailer separation
All checks were successful
Publish Release / release (push) Successful in 23s

- Download trailers for TV shows from TMDB with separate sources and an
  independent max-count cap (0 disables a category)
- Play trailers before TV episodes via IIntroProvider, limited to the first
  episode a user watches each day
- Tag TV show trailers in their NFO so movies only get movie trailers and
  episodes only get TV show trailers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Martin
2026-06-10 00:41:37 -04:00
parent f769e33b8d
commit f49c32f181
9 changed files with 542 additions and 127 deletions

View File

@@ -72,11 +72,11 @@
</div>
</fieldset>
<!-- Sources -->
<!-- Movie Sources -->
<fieldset class="verticalSection verticalSection-extrabottompadding">
<legend><h3 class="sectionTitle">Trailer Sources</h3></legend>
<legend><h3 class="sectionTitle">Movie Trailer Sources</h3></legend>
<p class="fieldDescription">
Choose which TMDB lists to pull trailers from. Enable multiple for more variety.
Choose which TMDB lists to pull movie trailers from. Enable multiple for more variety.
</p>
<div class="checkboxContainer checkboxContainer-withDescription">
@@ -120,11 +120,60 @@
</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 movies released within</label>
<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>
@@ -133,8 +182,9 @@
<option value="0">All time (no limit)</option>
</select>
<div class="fieldDescription">
Applies to all sources. "Now Playing" and "Upcoming" already have tight date windows
set by TMDB, but this provides an additional filter.
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>
@@ -153,10 +203,20 @@
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="max-trailers">Max trailers per run</label>
<input type="number" id="max-trailers" is="emby-input" min="1" max="200" />
<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 trailers to download each time the task runs. Default: 20.
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>
@@ -164,7 +224,7 @@
<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 movies per page). Default: 3.
How many pages to fetch from each TMDB source (20 results per page). Default: 3.
</div>
</div>
@@ -180,10 +240,10 @@
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="skip-library" />
<span>Skip movies already in my Jellyfin library</span>
<span>Skip movies/shows already in my Jellyfin library</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
Trailers for movies you already own won't be downloaded.
Trailers for movies and TV shows you already own won't be downloaded.
</div>
</div>
@@ -193,7 +253,7 @@
<span>Skip trailers already downloaded</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
If a folder already exists for a movie, don't re-download it.
If a folder already exists for a movie or TV show, don't re-download it.
</div>
</div>
</fieldset>
@@ -268,6 +328,17 @@
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>
<!-- Advanced -->
@@ -295,6 +366,13 @@
<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();
ApiClient.getPluginConfiguration(pluginId).then(function (config) {
@@ -303,9 +381,14 @@
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;
@@ -321,6 +404,7 @@
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();
});
});
@@ -333,9 +417,14 @@
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 = parseInt(document.getElementById('max-trailers').value, 10) || 20;
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;
@@ -347,11 +436,12 @@
});
// If all are checked treat it as "no preference" (empty string)
config.AllowedLanguages = checkedLangs.length === allLangCodes.length ? '' : checkedLangs.join(',');
config.MaxTotalTrailers = parseInt(document.getElementById('max-total-trailers').value, 10) || 50;
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;
ApiClient.updatePluginConfiguration(pluginId, config)
.then(Dashboard.processPluginConfigurationUpdateResult);
});