7 Commits

Author SHA1 Message Date
gitea-actions
174578e8d6 chore: update manifest.json for v1.0.0.17 2026-10-06 04:00:50 +00:00
Martin
1de082b96e feat: TMDB Discover source + retry yt-dlp once before fallback (v1.0.0.17)
All checks were successful
Publish Release / release (push) Successful in 14s
The fixed TV lists are mostly long-running shows whose first_air_date is
outside ReleaseDateRangeMonths, and paging stops at the first page with no
in-range title, so only ~7 TV candidates were found (5 then dropped by
language). Add a "discover" source per media type that asks /discover for
recent titles in AllowedLanguages, sorted by popularity (1600+ TV results
for en|fr today). TV Discover defaults on, movie Discover off.

yt-dlp failures were mostly passing HTTP 403s from back-to-back requests;
the built-in fallback is sometimes only offered 360p. Retry yt-dlp once
after 15s before falling back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 00:00:32 -04:00
gitea-actions
45405c815d chore: update manifest.json for v1.0.0.16 2026-10-06 01:40:16 +00:00
Martin
d6280f3ce8 fix: built-in downloader prefers separate streams over 360p muxed (v1.0.0.16)
All checks were successful
Publish Release / release (push) Successful in 24s
When YouTube offered a muxed stream, it was always taken, but muxed streams
are 360p only. Use muxed only when the separate video+audio streams are
missing or not better.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 21:39:49 -04:00
gitea-actions
43e002fcdd chore: update manifest.json for v1.0.0.15 2026-10-05 22:28:03 +00:00
Martin
fb75fa5a07 fix: language filter also checks the title's original language (v1.0.0.15)
All checks were successful
Publish Release / release (push) Successful in 9s
AllowedLanguages only filtered on each video's iso_639_1 tag, and TMDB
often tags subtitled foreign trailers as "en". With en,fr selected, 6 of
27 downloaded trailers were Korean, Chinese or Zulu titles. Candidates
now also need an allowed original_language (from the list results, so
no extra API calls).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 18:27:50 -04:00
gitea-actions
2664ab4d5a chore: update manifest.json for v1.0.0.14 2026-10-05 22:19:38 +00:00
8 changed files with 156 additions and 28 deletions

View File

@@ -14,6 +14,8 @@ namespace Jellyfin.Plugin.CinemaTrailers4Jellyfins.Configuration
public bool SourceUpcoming { get; set; } = true; public bool SourceUpcoming { get; set; } = true;
public bool SourcePopular { get; set; } = false; public bool SourcePopular { get; set; } = false;
public bool SourceTopRated { get; set; } = false; public bool SourceTopRated { get; set; } = false;
// TMDB Discover: newest titles in the allowed languages, most popular first.
public bool SourceDiscover { get; set; } = false;
// ── Sources (TV Shows) ─────────────────────────────────────────────── // ── Sources (TV Shows) ───────────────────────────────────────────────
@@ -21,6 +23,9 @@ namespace Jellyfin.Plugin.CinemaTrailers4Jellyfins.Configuration
public bool SourceTvOnTheAir { get; set; } = true; public bool SourceTvOnTheAir { get; set; } = true;
public bool SourceTvPopular { get; set; } = false; public bool SourceTvPopular { get; set; } = false;
public bool SourceTvTopRated { get; set; } = false; public bool SourceTvTopRated { get; set; } = false;
// TMDB Discover: newest shows in the allowed languages, most popular first. On by default:
// the TV lists above are mostly long-running shows the date range filters out.
public bool SourceTvDiscover { get; set; } = true;
// ── Date Range ──────────────────────────────────────────────────────── // ── Date Range ────────────────────────────────────────────────────────

View File

@@ -45,7 +45,9 @@
<fieldset class="verticalSection verticalSection-extrabottompadding"> <fieldset class="verticalSection verticalSection-extrabottompadding">
<legend><h3 class="sectionTitle">Trailer Languages</h3></legend> <legend><h3 class="sectionTitle">Trailer Languages</h3></legend>
<p class="fieldDescription"> <p class="fieldDescription">
Only download trailers in the selected languages. Only download trailers in the selected languages. Both the trailer and the
movie/show itself (its original language) must be in one of them, so a
subtitled foreign trailer is skipped.
Leave everything unchecked to allow all languages. Leave everything unchecked to allow all languages.
</p> </p>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:0 2em;"> <div style="display:grid;grid-template-columns:1fr 1fr;gap:0 2em;">
@@ -118,6 +120,16 @@
Highest rated movies on TMDB, filtered by the date range below. Highest rated movies on TMDB, filtered by the date range below.
</div> </div>
</div> </div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="source-discover" />
<span>Discover</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
Newest movies in your selected languages, most popular first.
</div>
</div>
</fieldset> </fieldset>
<!-- TV Show Sources --> <!-- TV Show Sources -->
@@ -167,6 +179,16 @@
Highest rated TV shows on TMDB, filtered by the date range below. Highest rated TV shows on TMDB, filtered by the date range below.
</div> </div>
</div> </div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="source-tv-discover" />
<span>Discover (recommended)</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
Newest TV shows in your selected languages, most popular first. The lists above are mostly long-running shows that the date range drops.
</div>
</div>
</fieldset> </fieldset>
<!-- Date Range --> <!-- Date Range -->
@@ -430,10 +452,12 @@
document.getElementById('source-upcoming').checked = config.SourceUpcoming !== false; document.getElementById('source-upcoming').checked = config.SourceUpcoming !== false;
document.getElementById('source-popular').checked = !!config.SourcePopular; document.getElementById('source-popular').checked = !!config.SourcePopular;
document.getElementById('source-top-rated').checked = !!config.SourceTopRated; document.getElementById('source-top-rated').checked = !!config.SourceTopRated;
document.getElementById('source-discover').checked = !!config.SourceDiscover;
document.getElementById('source-tv-airing-today').checked = config.SourceTvAiringToday !== false; 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-on-the-air').checked = config.SourceTvOnTheAir !== false;
document.getElementById('source-tv-popular').checked = !!config.SourceTvPopular; document.getElementById('source-tv-popular').checked = !!config.SourceTvPopular;
document.getElementById('source-tv-top-rated').checked = !!config.SourceTvTopRated; document.getElementById('source-tv-top-rated').checked = !!config.SourceTvTopRated;
document.getElementById('source-tv-discover').checked = config.SourceTvDiscover !== false;
document.getElementById('date-range').value = String(config.ReleaseDateRangeMonths ?? 6); document.getElementById('date-range').value = String(config.ReleaseDateRangeMonths ?? 6);
document.getElementById('download-folder').value = config.DownloadFolder || ''; document.getElementById('download-folder').value = config.DownloadFolder || '';
document.getElementById('max-trailers').value = config.MaxTrailersToDownload ?? 20; document.getElementById('max-trailers').value = config.MaxTrailersToDownload ?? 20;
@@ -466,10 +490,12 @@
config.SourceUpcoming = document.getElementById('source-upcoming').checked; config.SourceUpcoming = document.getElementById('source-upcoming').checked;
config.SourcePopular = document.getElementById('source-popular').checked; config.SourcePopular = document.getElementById('source-popular').checked;
config.SourceTopRated = document.getElementById('source-top-rated').checked; config.SourceTopRated = document.getElementById('source-top-rated').checked;
config.SourceDiscover = document.getElementById('source-discover').checked;
config.SourceTvAiringToday = document.getElementById('source-tv-airing-today').checked; config.SourceTvAiringToday = document.getElementById('source-tv-airing-today').checked;
config.SourceTvOnTheAir = document.getElementById('source-tv-on-the-air').checked; config.SourceTvOnTheAir = document.getElementById('source-tv-on-the-air').checked;
config.SourceTvPopular = document.getElementById('source-tv-popular').checked; config.SourceTvPopular = document.getElementById('source-tv-popular').checked;
config.SourceTvTopRated = document.getElementById('source-tv-top-rated').checked; config.SourceTvTopRated = document.getElementById('source-tv-top-rated').checked;
config.SourceTvDiscover = document.getElementById('source-tv-discover').checked;
config.ReleaseDateRangeMonths = parseInt(document.getElementById('date-range').value, 10); config.ReleaseDateRangeMonths = parseInt(document.getElementById('date-range').value, 10);
config.DownloadFolder = document.getElementById('download-folder').value; config.DownloadFolder = document.getElementById('download-folder').value;
config.MaxTrailersToDownload = parseIntOrDefault(document.getElementById('max-trailers').value, 20); config.MaxTrailersToDownload = parseIntOrDefault(document.getElementById('max-trailers').value, 20);

View File

@@ -3,8 +3,8 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<RootNamespace>Jellyfin.Plugin.CinemaTrailers4Jellyfins</RootNamespace> <RootNamespace>Jellyfin.Plugin.CinemaTrailers4Jellyfins</RootNamespace>
<AssemblyVersion>1.0.0.14</AssemblyVersion> <AssemblyVersion>1.0.0.17</AssemblyVersion>
<FileVersion>1.0.0.14</FileVersion> <FileVersion>1.0.0.17</FileVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<GenerateDocumentationFile>false</GenerateDocumentationFile> <GenerateDocumentationFile>false</GenerateDocumentationFile>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors> <TreatWarningsAsErrors>false</TreatWarningsAsErrors>

View File

@@ -177,6 +177,14 @@ namespace Jellyfin.Plugin.CinemaTrailers4Jellyfins.ScheduledTasks
continue; continue;
} }
// A trailer's own language tag is often "en" just because it has English
// subtitles, so also require the title itself to be in an allowed language.
if (allowedLanguages != null && !allowedLanguages.Contains(movie.OriginalLanguage))
{
_logger.LogDebug("|CinemaTrailers4Jellyfins| Skipping '{Title}': original language '{Lang}' is not allowed", movie.Title, movie.OriginalLanguage);
continue;
}
var trailers = await _tmdbService.GetTrailersAsync( var trailers = await _tmdbService.GetTrailersAsync(
"movie", movie.Id.ToString(), config.TmdbApiKey, allowedLanguages, cancellationToken).ConfigureAwait(false); "movie", movie.Id.ToString(), config.TmdbApiKey, allowedLanguages, cancellationToken).ConfigureAwait(false);
@@ -284,6 +292,14 @@ namespace Jellyfin.Plugin.CinemaTrailers4Jellyfins.ScheduledTasks
continue; continue;
} }
// A trailer's own language tag is often "en" just because it has English
// subtitles, so also require the title itself to be in an allowed language.
if (allowedLanguages != null && !allowedLanguages.Contains(show.OriginalLanguage))
{
_logger.LogDebug("|CinemaTrailers4Jellyfins| Skipping '{Title}': original language '{Lang}' is not allowed", show.Title, show.OriginalLanguage);
continue;
}
var trailers = await _tmdbService.GetTrailersAsync( var trailers = await _tmdbService.GetTrailersAsync(
"tv", show.Id.ToString(), config.TmdbApiKey, allowedLanguages, cancellationToken).ConfigureAwait(false); "tv", show.Id.ToString(), config.TmdbApiKey, allowedLanguages, cancellationToken).ConfigureAwait(false);

View File

@@ -14,7 +14,7 @@ namespace Jellyfin.Plugin.CinemaTrailers4Jellyfins.Services
{ {
public record TmdbVideo(string Key, string Name, string Language, bool Official, int Size); public record TmdbVideo(string Key, string Name, string Language, bool Official, int Size);
public record TmdbMediaResult(int Id, string Title, string ReleaseDate) public record TmdbMediaResult(int Id, string Title, string ReleaseDate, string OriginalLanguage)
{ {
public int? Year => DateTime.TryParse(ReleaseDate, out var d) ? d.Year : (int?)null; public int? Year => DateTime.TryParse(ReleaseDate, out var d) ? d.Year : (int?)null;
} }
@@ -84,6 +84,7 @@ namespace Jellyfin.Plugin.CinemaTrailers4Jellyfins.Services
if (config.SourceUpcoming) sources.Add("upcoming"); if (config.SourceUpcoming) sources.Add("upcoming");
if (config.SourcePopular) sources.Add("popular"); if (config.SourcePopular) sources.Add("popular");
if (config.SourceTopRated) sources.Add("top_rated"); if (config.SourceTopRated) sources.Add("top_rated");
if (config.SourceDiscover) sources.Add(DiscoverSource);
return GetCandidatesAsync("movie", sources, config, ct); return GetCandidatesAsync("movie", sources, config, ct);
} }
@@ -101,6 +102,7 @@ namespace Jellyfin.Plugin.CinemaTrailers4Jellyfins.Services
if (config.SourceTvOnTheAir) sources.Add("on_the_air"); if (config.SourceTvOnTheAir) sources.Add("on_the_air");
if (config.SourceTvPopular) sources.Add("popular"); if (config.SourceTvPopular) sources.Add("popular");
if (config.SourceTvTopRated) sources.Add("top_rated"); if (config.SourceTvTopRated) sources.Add("top_rated");
if (config.SourceTvDiscover) sources.Add(DiscoverSource);
return GetCandidatesAsync("tv", sources, config, ct); return GetCandidatesAsync("tv", sources, config, ct);
} }
@@ -120,7 +122,10 @@ namespace Jellyfin.Plugin.CinemaTrailers4Jellyfins.Services
foreach (var endpoint in sourceEndpoints) foreach (var endpoint in sourceEndpoints)
{ {
var items = await FetchSourcePagesAsync(mediaType, endpoint, config.TmdbApiKey, releasedAfter, config.MaxPagesPerSource, ct) var path = endpoint == DiscoverSource
? DiscoverPath(mediaType, releasedAfter, config.AllowedLanguages)
: $"{mediaType}/{endpoint}";
var items = await FetchSourcePagesAsync(mediaType, path, config.TmdbApiKey, releasedAfter, config.MaxPagesPerSource, ct)
.ConfigureAwait(false); .ConfigureAwait(false);
foreach (var item in items) foreach (var item in items)
@@ -133,9 +138,31 @@ namespace Jellyfin.Plugin.CinemaTrailers4Jellyfins.Services
return results; return results;
} }
private const string DiscoverSource = "discover";
// /discover asks TMDB directly for recent titles in the allowed languages, sorted by
// popularity, unlike the fixed lists, which are mostly older titles the date range drops.
private static string DiscoverPath(string mediaType, DateTime? releasedAfter, string allowedLanguages)
{
var query = new List<string> { "sort_by=popularity.desc" };
if (releasedAfter.HasValue)
{
var dateParam = mediaType == "movie" ? "primary_release_date.gte" : "first_air_date.gte";
query.Add($"{dateParam}={releasedAfter.Value:yyyy-MM-dd}");
}
var languages = (allowedLanguages ?? string.Empty)
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (languages.Length > 0)
query.Add("with_original_language=" + Uri.EscapeDataString(string.Join("|", languages)));
return $"discover/{mediaType}?" + string.Join("&", query);
}
// path is relative to the API root, e.g. "tv/popular" or "discover/tv?sort_by=...".
private async Task<List<TmdbMediaResult>> FetchSourcePagesAsync( private async Task<List<TmdbMediaResult>> FetchSourcePagesAsync(
string mediaType, string mediaType,
string endpoint, string path,
string apiKey, string apiKey,
DateTime? releasedAfter, DateTime? releasedAfter,
int maxPages, int maxPages,
@@ -153,7 +180,7 @@ namespace Jellyfin.Plugin.CinemaTrailers4Jellyfins.Services
try try
{ {
var url = $"{BaseUrl}/{mediaType}/{endpoint}?language=en-US&page={page}"; var url = $"{BaseUrl}/{path}{(path.Contains('?') ? '&' : '?')}language=en-US&page={page}";
using var request = new HttpRequestMessage(HttpMethod.Get, url); using var request = new HttpRequestMessage(HttpMethod.Get, url);
ApplyAuth(request, apiKey); ApplyAuth(request, apiKey);
using var response = await _httpClient.SendAsync(request, ct).ConfigureAwait(false); using var response = await _httpClient.SendAsync(request, ct).ConfigureAwait(false);
@@ -170,6 +197,7 @@ namespace Jellyfin.Plugin.CinemaTrailers4Jellyfins.Services
var releaseDate = entry.TryGetProperty(dateField, out var rd) ? rd.GetString() ?? string.Empty : string.Empty; var releaseDate = entry.TryGetProperty(dateField, out var rd) ? rd.GetString() ?? string.Empty : string.Empty;
var title = entry.TryGetProperty(titleField, out var t) ? t.GetString() ?? string.Empty : string.Empty; var title = entry.TryGetProperty(titleField, out var t) ? t.GetString() ?? string.Empty : string.Empty;
var id = entry.GetProperty("id").GetInt32(); var id = entry.GetProperty("id").GetInt32();
var originalLanguage = entry.TryGetProperty("original_language", out var ol) ? ol.GetString() ?? string.Empty : string.Empty;
if (releasedAfter.HasValue && DateTime.TryParse(releaseDate, out var parsed)) if (releasedAfter.HasValue && DateTime.TryParse(releaseDate, out var parsed))
{ {
@@ -177,7 +205,7 @@ namespace Jellyfin.Plugin.CinemaTrailers4Jellyfins.Services
} }
anyInRange = true; anyInRange = true;
results.Add(new TmdbMediaResult(id, title, releaseDate)); results.Add(new TmdbMediaResult(id, title, releaseDate, originalLanguage));
} }
if (page >= totalPages || (releasedAfter.HasValue && !anyInRange)) if (page >= totalPages || (releasedAfter.HasValue && !anyInRange))
@@ -189,7 +217,7 @@ namespace Jellyfin.Plugin.CinemaTrailers4Jellyfins.Services
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "|CinemaTrailers4Jellyfins| Failed to fetch TMDB source '{MediaType}/{Endpoint}' page {Page}", mediaType, endpoint, page); _logger.LogError(ex, "|CinemaTrailers4Jellyfins| Failed to fetch TMDB source '{Path}' page {Page}", path, page);
break; break;
} }
} }

View File

@@ -45,6 +45,8 @@ namespace Jellyfin.Plugin.CinemaTrailers4Jellyfins.Services
_httpClient = new HttpClient(handler) { Timeout = TimeSpan.FromMinutes(10) }; _httpClient = new HttpClient(handler) { Timeout = TimeSpan.FromMinutes(10) };
} }
private static readonly TimeSpan YtDlpRetryDelay = TimeSpan.FromSeconds(15);
public void Dispose() => _httpClient.Dispose(); public void Dispose() => _httpClient.Dispose();
/// <summary> /// <summary>
@@ -66,6 +68,14 @@ namespace Jellyfin.Plugin.CinemaTrailers4Jellyfins.Services
if (await DownloadWithYtDlpAsync(youtubeKey, outputPath, preferredHeight, ytDlpPath, ct).ConfigureAwait(false)) if (await DownloadWithYtDlpAsync(youtubeKey, outputPath, preferredHeight, ytDlpPath, ct).ConfigureAwait(false))
return true; return true;
// YouTube answers back-to-back downloads with occasional HTTP 403s that clear on
// their own. One more try after a pause usually gets full quality, where the
// built-in fallback may only be offered a 360p stream.
_logger.LogWarning("|CinemaTrailers4Jellyfins| yt-dlp failed for {Key}; retrying in {Seconds}s.", youtubeKey, YtDlpRetryDelay.TotalSeconds);
await Task.Delay(YtDlpRetryDelay, ct).ConfigureAwait(false);
if (await DownloadWithYtDlpAsync(youtubeKey, outputPath, preferredHeight, ytDlpPath, ct).ConfigureAwait(false))
return true;
_logger.LogWarning("|CinemaTrailers4Jellyfins| yt-dlp failed for {Key}; falling back to the built-in downloader.", youtubeKey); _logger.LogWarning("|CinemaTrailers4Jellyfins| yt-dlp failed for {Key}; falling back to the built-in downloader.", youtubeKey);
} }
@@ -90,31 +100,37 @@ namespace Jellyfin.Plugin.CinemaTrailers4Jellyfins.Services
.GetManifestAsync($"https://www.youtube.com/watch?v={key}", ct) .GetManifestAsync($"https://www.youtube.com/watch?v={key}", ct)
.ConfigureAwait(false); .ConfigureAwait(false);
// Muxed streams include audio+video in one file. YouTube rarely serves them // Best video-only stream at or below the height limit. MP4 (H.264) is preferred for
// anymore, but use one when it exists since it needs no merge. // client compatibility; any container is the fallback.
var videoStreams = manifest.GetVideoOnlyStreams().ToList();
var audioStreams = manifest.GetAudioOnlyStreams().ToList();
var mp4Video = videoStreams.Where(s => s.Container == Container.Mp4).ToList();
var video = videoStreams.Count > 0
? PickByHeight(mp4Video.Count > 0 ? mp4Video : videoStreams, preferredHeight)
: null;
// Muxed streams (audio+video in one file) need no merge, but YouTube only serves
// them at 360p. Use one only when the separate streams can't do better.
var muxedStreams = manifest.GetMuxedStreams().ToList(); var muxedStreams = manifest.GetMuxedStreams().ToList();
if (muxedStreams.Count > 0) if (muxedStreams.Count > 0)
{ {
var muxed = PickByHeight(muxedStreams, preferredHeight); var muxed = PickByHeight(muxedStreams, preferredHeight);
if (video == null || audioStreams.Count == 0
|| muxed.VideoQuality.MaxHeight >= video.VideoQuality.MaxHeight)
{
_logger.LogInformation( _logger.LogInformation(
"|CinemaTrailers4Jellyfins| Downloading {Key} at {Quality} (muxed) to {Path}", "|CinemaTrailers4Jellyfins| Downloading {Key} at {Quality} (muxed) to {Path}",
key, muxed.VideoQuality.Label, outputPath); key, muxed.VideoQuality.Label, outputPath);
await youtube.Videos.Streams.DownloadAsync(muxed, outputPath, cancellationToken: ct).ConfigureAwait(false); await youtube.Videos.Streams.DownloadAsync(muxed, outputPath, cancellationToken: ct).ConfigureAwait(false);
return true; return true;
} }
}
// Otherwise download the best video-only and audio-only streams and merge them. if (video == null || audioStreams.Count == 0)
// MP4 (H.264/AAC) is preferred for client compatibility; any container is the fallback.
var videoStreams = manifest.GetVideoOnlyStreams().ToList();
var audioStreams = manifest.GetAudioOnlyStreams().ToList();
if (videoStreams.Count == 0 || audioStreams.Count == 0)
{ {
_logger.LogWarning("|CinemaTrailers4Jellyfins| No usable video/audio streams for {Key}.", key); _logger.LogWarning("|CinemaTrailers4Jellyfins| No usable video/audio streams for {Key}.", key);
return false; return false;
} }
var mp4Video = videoStreams.Where(s => s.Container == Container.Mp4).ToList();
var video = PickByHeight(mp4Video.Count > 0 ? mp4Video : videoStreams, preferredHeight);
var mp4Audio = audioStreams.Where(s => s.Container == Container.Mp4).ToList(); var mp4Audio = audioStreams.Where(s => s.Container == Container.Mp4).ToList();
var audio = (mp4Audio.Count > 0 ? mp4Audio : audioStreams).GetWithHighestBitrate(); var audio = (mp4Audio.Count > 0 ? mp4Audio : audioStreams).GetWithHighestBitrate();

View File

@@ -1,5 +1,5 @@
--- ---
version: 1.0.0.14 version: 1.0.0.17
name: CinemaTrailers4Jellyfins name: CinemaTrailers4Jellyfins
guid: b581493e-1046-40ed-b6dc-cb8027624984 guid: b581493e-1046-40ed-b6dc-cb8027624984
description: > description: >
@@ -12,9 +12,14 @@ category: General
owner: 514mart owner: 514mart
targetAbi: 10.11.0.0 targetAbi: 10.11.0.0
changelog: changelog:
- When yt-dlp fails part-way, its partial download files (.part, .ytdl, - New "Discover" source for TV shows (on by default) and movies (off by
separate video/audio) are now deleted instead of being left in the default). It asks TMDB for the newest titles in your selected
trailer folder languages, most popular first. The TV lists (Popular, Top Rated, On The
Air, Airing Today) are mostly long-running shows that the date range
drops, so very few TV trailers were found before
- When yt-dlp fails (often a passing HTTP 403 from YouTube), it is tried
once more after 15 seconds before the built-in downloader takes over,
which YouTube sometimes only offers 360p
dotnetProjects: dotnetProjects:
- name: Jellyfin.Plugin.CinemaTrailers4Jellyfins - name: Jellyfin.Plugin.CinemaTrailers4Jellyfins

View File

@@ -8,6 +8,38 @@
"owner": "514mart", "owner": "514mart",
"imageUrl": "https://www.git.quarantinedstudio.com/mvezina/CinemaTrailers4Jellyfins/raw/branch/main/Jellyfin.Plugin.CinemaTrailers4Jellyfins/Images/logo.svg", "imageUrl": "https://www.git.quarantinedstudio.com/mvezina/CinemaTrailers4Jellyfins/raw/branch/main/Jellyfin.Plugin.CinemaTrailers4Jellyfins/Images/logo.svg",
"versions": [ "versions": [
{
"checksum": "ac1b7e8c353e308a556ed76687c8d1e9",
"changelog": "- New \"Discover\" source for TV shows (on by default) and movies (off by default). It asks TMDB for the newest titles in your selected languages, most popular first. The TV lists (Popular, Top Rated, On The Air, Airing Today) are mostly long-running shows that the date range drops, so very few TV trailers were found before\n- When yt-dlp fails (often a passing HTTP 403 from YouTube), it is tried once more after 15 seconds before the built-in downloader takes over, which YouTube sometimes only offers 360p\n",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://www.git.quarantinedstudio.com/mvezina/CinemaTrailers4Jellyfins/releases/download/v1.0.0.17/cinematrailers4jellyfins_1.0.0.17.zip",
"timestamp": "2026-10-06T04:00:50Z",
"version": "1.0.0.17"
},
{
"checksum": "5329a3a0daf9f82849b441ca2784f622",
"changelog": "- The built-in downloader no longer picks YouTube's 360p combined file when separate video and audio streams give a better picture. This mostly showed up when yt-dlp failed and the built-in downloader took over\n",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://www.git.quarantinedstudio.com/mvezina/CinemaTrailers4Jellyfins/releases/download/v1.0.0.16/cinematrailers4jellyfins_1.0.0.16.zip",
"timestamp": "2026-10-06T01:40:16Z",
"version": "1.0.0.16"
},
{
"checksum": "211f58d992857f9e91263293306fbd0d",
"changelog": "- Trailer Languages now also checks the original language of the movie or show. Before, only the trailer's own language tag was checked, and TMDB often tags subtitled foreign trailers as English, so Korean, Chinese or Japanese trailers got through\n",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://www.git.quarantinedstudio.com/mvezina/CinemaTrailers4Jellyfins/releases/download/v1.0.0.15/cinematrailers4jellyfins_1.0.0.15.zip",
"timestamp": "2026-10-05T22:28:03Z",
"version": "1.0.0.15"
},
{
"checksum": "10ecbc35b84e98bade61576bbfc41864",
"changelog": "- When yt-dlp fails part-way, its partial download files (.part, .ytdl, separate video/audio) are now deleted instead of being left in the trailer folder\n",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://www.git.quarantinedstudio.com/mvezina/CinemaTrailers4Jellyfins/releases/download/v1.0.0.14/cinematrailers4jellyfins_1.0.0.14.zip",
"timestamp": "2026-10-05T22:19:38Z",
"version": "1.0.0.14"
},
{ {
"checksum": "f1fe201e7b0238fa432ae7fb5dbb1844", "checksum": "f1fe201e7b0238fa432ae7fb5dbb1844",
"changelog": "- yt-dlp now prefers H.264 + AAC (plays or hardware-transcodes on every client; VP9/AV1 often needed a CPU transcode) and uses Jellyfin's own ffmpeg for the merge\n- If yt-dlp fails, the built-in downloader is tried instead\n- Fix a possible hang. yt-dlp's output is now read while it runs, so a full output buffer can no longer block it\n", "changelog": "- yt-dlp now prefers H.264 + AAC (plays or hardware-transcodes on every client; VP9/AV1 often needed a CPU transcode) and uses Jellyfin's own ffmpeg for the merge\n- If yt-dlp fails, the built-in downloader is tried instead\n- Fix a possible hang. yt-dlp's output is now read while it runs, so a full output buffer can no longer block it\n",