Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6280f3ce8 | ||
|
|
43e002fcdd | ||
|
|
fb75fa5a07 | ||
|
|
2664ab4d5a |
@@ -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;">
|
||||||
|
|||||||
@@ -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.16</AssemblyVersion>
|
||||||
<FileVersion>1.0.0.14</FileVersion>
|
<FileVersion>1.0.0.16</FileVersion>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
||||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -170,6 +170,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 +178,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))
|
||||||
|
|||||||
@@ -90,31 +90,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();
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
version: 1.0.0.14
|
version: 1.0.0.16
|
||||||
name: CinemaTrailers4Jellyfins
|
name: CinemaTrailers4Jellyfins
|
||||||
guid: b581493e-1046-40ed-b6dc-cb8027624984
|
guid: b581493e-1046-40ed-b6dc-cb8027624984
|
||||||
description: >
|
description: >
|
||||||
@@ -12,9 +12,9 @@ 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,
|
- The built-in downloader no longer picks YouTube's 360p combined file
|
||||||
separate video/audio) are now deleted instead of being left in the
|
when separate video and audio streams give a better picture. This
|
||||||
trailer folder
|
mostly showed up when yt-dlp failed and the built-in downloader took over
|
||||||
|
|
||||||
dotnetProjects:
|
dotnetProjects:
|
||||||
- name: Jellyfin.Plugin.CinemaTrailers4Jellyfins
|
- name: Jellyfin.Plugin.CinemaTrailers4Jellyfins
|
||||||
|
|||||||
@@ -8,6 +8,22 @@
|
|||||||
"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": "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",
|
||||||
|
|||||||
Reference in New Issue
Block a user