Skip to content

Commit ce3b015

Browse files
committed
Fixed series to use series id instea of episode id; implemented scheduled tasks
1 parent 9978490 commit ce3b015

File tree

7 files changed

+373
-68
lines changed

7 files changed

+373
-68
lines changed

Jellyfin.Plugin.DynamicLibrary/Filters/ItemLookupFilter.cs

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,16 +1147,12 @@ private async Task PopulateAIOStreamsMediaSourcesForPersistedItemAsync(
11471147

11481148
/// <summary>
11491149
/// Get the IMDB ID from a library item, checking item and series provider IDs.
1150+
/// For episodes, always prefer the series IMDB ID (required for streaming APIs).
11501151
/// </summary>
11511152
private string? GetImdbIdFromLibraryItem(MediaBrowser.Controller.Entities.BaseItem item)
11521153
{
1153-
// Try item's IMDB ID first
1154-
if (item.ProviderIds?.TryGetValue("Imdb", out var imdbId) == true && !string.IsNullOrEmpty(imdbId))
1155-
{
1156-
return imdbId;
1157-
}
1158-
1159-
// For episodes, try the series IMDB ID
1154+
// For episodes, ALWAYS check series IMDB first (required for streaming APIs)
1155+
// Episode items can have episode-level IMDB IDs which don't work with streaming services
11601156
if (item is Episode episode && episode.SeriesId != Guid.Empty)
11611157
{
11621158
var series = _libraryManager.GetItemById(episode.SeriesId);
@@ -1166,38 +1162,54 @@ private async Task PopulateAIOStreamsMediaSourcesForPersistedItemAsync(
11661162
}
11671163
}
11681164

1165+
// For non-episodes, or if series IMDB not found, use item's own IMDB
1166+
if (item.ProviderIds?.TryGetValue("Imdb", out var imdbId) == true && !string.IsNullOrEmpty(imdbId))
1167+
{
1168+
return imdbId;
1169+
}
1170+
11691171
return null;
11701172
}
11711173

11721174
/// <summary>
11731175
/// Get the IMDB ID for an item, checking both item and series provider IDs.
1176+
/// For episodes, always prefer the series IMDB ID (required for AIOStreams).
11741177
/// </summary>
11751178
private string? GetImdbIdForItem(BaseItemDto item)
11761179
{
1177-
// Try item's IMDB ID first
1178-
if (item.ProviderIds?.TryGetValue("Imdb", out var imdbId) == true && !string.IsNullOrEmpty(imdbId))
1180+
// For episodes, ALWAYS prefer series IMDB ID (required for AIOStreams)
1181+
if (item.Type == BaseItemKind.Episode)
11791182
{
1180-
return imdbId;
1181-
}
1182-
1183-
// For episodes, try the series IMDB ID from cache first
1184-
if (item.Type == BaseItemKind.Episode && item.SeriesId.HasValue)
1185-
{
1186-
// Check cache first
1187-
var series = _itemCache.GetItem(item.SeriesId.Value);
1188-
if (series?.ProviderIds?.TryGetValue("Imdb", out var seriesImdbId) == true && !string.IsNullOrEmpty(seriesImdbId))
1183+
// First check if episode has SeriesImdb stored directly (most reliable)
1184+
if (item.ProviderIds?.TryGetValue("SeriesImdb", out var storedSeriesImdb) == true && !string.IsNullOrEmpty(storedSeriesImdb))
11891185
{
1190-
return seriesImdbId;
1186+
return storedSeriesImdb;
11911187
}
11921188

1193-
// Fall back to library lookup
1194-
var librarySeries = _libraryManager.GetItemById(item.SeriesId.Value);
1195-
if (librarySeries?.ProviderIds?.TryGetValue("Imdb", out var librarySeriesImdbId) == true && !string.IsNullOrEmpty(librarySeriesImdbId))
1189+
// Fall back to cache lookup if episode doesn't have SeriesImdb
1190+
if (item.SeriesId.HasValue)
11961191
{
1197-
return librarySeriesImdbId;
1192+
var series = _itemCache.GetItem(item.SeriesId.Value);
1193+
if (series?.ProviderIds?.TryGetValue("Imdb", out var seriesImdbId) == true && !string.IsNullOrEmpty(seriesImdbId))
1194+
{
1195+
return seriesImdbId;
1196+
}
1197+
1198+
// Fall back to library lookup
1199+
var librarySeries = _libraryManager.GetItemById(item.SeriesId.Value);
1200+
if (librarySeries?.ProviderIds?.TryGetValue("Imdb", out var librarySeriesImdbId) == true && !string.IsNullOrEmpty(librarySeriesImdbId))
1201+
{
1202+
return librarySeriesImdbId;
1203+
}
11981204
}
11991205
}
12001206

1207+
// For non-episodes, or if series IMDB not found, try item's own IMDB ID
1208+
if (item.ProviderIds?.TryGetValue("Imdb", out var imdbId) == true && !string.IsNullOrEmpty(imdbId))
1209+
{
1210+
return imdbId;
1211+
}
1212+
12011213
return null;
12021214
}
12031215

Jellyfin.Plugin.DynamicLibrary/Filters/PlaybackInfoFilter.cs

Lines changed: 69 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionE
102102
_logger.LogInformation("[DynamicLibrary] PlaybackInfoFilter: Found AIOStreams mapping for {MediaSourceId}, returning stream URL",
103103
selectedMediaSourceId);
104104

105-
var response = BuildAIOStreamsDirectResponse(itemId, aioStreamUrl, selectedMediaSourceId);
105+
var response = await BuildAIOStreamsDirectResponseAsync(itemId, aioStreamUrl, selectedMediaSourceId, context.HttpContext.RequestAborted);
106106
context.Result = new OkObjectResult(response);
107107
return;
108108
}
@@ -698,15 +698,14 @@ private string ReplacePlaceholders(string template, BaseItemDto item, int? seaso
698698
/// <summary>
699699
/// Get the series ID for an episode based on preference with fallback.
700700
/// TV/Anime can have: IMDB, TVDB, TMDB
701+
/// For IMDB, we NEVER return episode.ProviderIds["Imdb"] as it could be an episode-specific IMDB ID.
701702
/// </summary>
702703
private (string? Id, string IdType) GetSeriesId(BaseItemDto episode, BaseItemDto? series, PreferredProviderId preference)
703704
{
704-
// Try to get IDs from series first (preferred), then from episode
705-
var providerIds = series?.ProviderIds ?? episode.ProviderIds;
706-
707-
if (providerIds == null)
705+
// For IMDB preference, first check if episode has SeriesImdb stored (most reliable)
706+
if (preference == PreferredProviderId.Imdb && episode.ProviderIds?.TryGetValue("SeriesImdb", out var storedSeriesImdb) == true && !string.IsNullOrEmpty(storedSeriesImdb))
708707
{
709-
return (null, "none");
708+
return (storedSeriesImdb, "IMDB");
710709
}
711710

712711
// Try preferred ID first, then fall back to others
@@ -721,9 +720,32 @@ private string ReplacePlaceholders(string template, BaseItemDto item, int? seaso
721720

722721
foreach (var provider in fallbackOrder)
723722
{
724-
if (providerIds.TryGetValue(provider, out var id) && !string.IsNullOrEmpty(id))
723+
// For IMDB: ONLY use SeriesImdb or series.ProviderIds - NEVER episode.ProviderIds["Imdb"]
724+
// because TVDB sometimes returns episode-level IMDB IDs which we don't want
725+
if (provider == "Imdb")
725726
{
726-
return (id, provider.ToUpperInvariant());
727+
// Check SeriesImdb first (stored in episode during creation)
728+
if (episode.ProviderIds?.TryGetValue("SeriesImdb", out var seriesImdb) == true && !string.IsNullOrEmpty(seriesImdb))
729+
{
730+
return (seriesImdb, "IMDB");
731+
}
732+
// Only check series provider IDs, NOT episode provider IDs
733+
if (series?.ProviderIds?.TryGetValue("Imdb", out var seriesProviderImdb) == true && !string.IsNullOrEmpty(seriesProviderImdb))
734+
{
735+
return (seriesProviderImdb, "IMDB");
736+
}
737+
// Skip to next provider type - explicitly don't check episode.ProviderIds["Imdb"]
738+
continue;
739+
}
740+
741+
// For non-IMDB providers, check series first, then episode
742+
if (series?.ProviderIds?.TryGetValue(provider, out var seriesId) == true && !string.IsNullOrEmpty(seriesId))
743+
{
744+
return (seriesId, provider.ToUpperInvariant());
745+
}
746+
if (episode.ProviderIds?.TryGetValue(provider, out var episodeId) == true && !string.IsNullOrEmpty(episodeId))
747+
{
748+
return (episodeId, provider.ToUpperInvariant());
727749
}
728750
}
729751

@@ -1807,16 +1829,12 @@ private PlaybackInfoResponse BuildAIOStreamsSingleResponseForPersistedItem(
18071829

18081830
/// <summary>
18091831
/// Get the IMDB ID from a library item, checking item and series provider IDs.
1832+
/// For episodes, always prefer the series IMDB ID (required for streaming APIs).
18101833
/// </summary>
18111834
private string? GetImdbIdFromLibraryItem(MediaBrowser.Controller.Entities.BaseItem item)
18121835
{
1813-
// Try item's IMDB ID first
1814-
if (item.ProviderIds?.TryGetValue("Imdb", out var imdbId) == true && !string.IsNullOrEmpty(imdbId))
1815-
{
1816-
return imdbId;
1817-
}
1818-
1819-
// For episodes, try the series IMDB ID
1836+
// For episodes, ALWAYS check series IMDB first (required for streaming APIs)
1837+
// Episode items can have episode-level IMDB IDs which don't work with streaming services
18201838
if (item is Episode episode && episode.SeriesId != Guid.Empty)
18211839
{
18221840
var series = _libraryManager.GetItemById(episode.SeriesId);
@@ -1826,6 +1844,12 @@ private PlaybackInfoResponse BuildAIOStreamsSingleResponseForPersistedItem(
18261844
}
18271845
}
18281846

1847+
// For non-episodes, or if series IMDB not found, use item's own IMDB
1848+
if (item.ProviderIds?.TryGetValue("Imdb", out var imdbId) == true && !string.IsNullOrEmpty(imdbId))
1849+
{
1850+
return imdbId;
1851+
}
1852+
18291853
return null;
18301854
}
18311855

@@ -1880,33 +1904,34 @@ private PlaybackInfoResponse BuildAIOStreamsSingleResponse(BaseItemDto item, str
18801904
/// Build a PlaybackInfoResponse directly from AIOStreams stream URL mapping.
18811905
/// Used when the item isn't in cache but we have the MediaSource → URL mapping.
18821906
/// </summary>
1883-
private PlaybackInfoResponse BuildAIOStreamsDirectResponse(Guid itemId, string streamUrl, string mediaSourceId)
1907+
private async Task<PlaybackInfoResponse> BuildAIOStreamsDirectResponseAsync(Guid itemId, string streamUrl, string mediaSourceId, CancellationToken cancellationToken)
18841908
{
18851909
// Get the mapping to retrieve filename hint for container detection
18861910
var mapping = _itemCache.GetAIOStreamsMapping(mediaSourceId);
18871911
var container = StreamContainerHelper.DetectContainer(streamUrl, mapping?.Filename);
18881912

1889-
// Try to get runtime from library item, otherwise use default
1913+
// Try to get runtime from library item or API
18901914
// This is critical for Android TV which won't play without RunTimeTicks
18911915
long? runTimeTicks = null;
18921916
string itemName = "Stream";
18931917

18941918
var libraryItem = _libraryManager.GetItemById(itemId);
18951919
if (libraryItem != null)
18961920
{
1897-
runTimeTicks = libraryItem.RunTimeTicks;
18981921
itemName = libraryItem.Name;
1899-
_logger.LogInformation("[DynamicLibrary] BuildAIOStreamsDirectResponse: Library item {Name} has RunTimeTicks={Ticks}",
1922+
// Use the async method that queries TVDB/TMDB APIs for runtime
1923+
runTimeTicks = await GetRuntimeForPersistedItemAsync(libraryItem, cancellationToken);
1924+
_logger.LogInformation("[DynamicLibrary] BuildAIOStreamsDirectResponseAsync: Library item {Name} has RunTimeTicks={Ticks} (from API lookup)",
19001925
itemName, runTimeTicks);
19011926
}
19021927

1903-
// Fallback to default duration if we don't have a runtime
1928+
// Fallback to default duration if API lookup failed or no library item
19041929
if (!runTimeTicks.HasValue || runTimeTicks.Value <= 0)
19051930
{
1906-
// Default to 2 hours for movies (most common case for direct mapping)
1907-
var defaultMinutes = 120;
1931+
// Use smart defaults based on item type
1932+
var defaultMinutes = libraryItem is MediaBrowser.Controller.Entities.TV.Episode ? 24 : 120;
19081933
runTimeTicks = defaultMinutes * 60L * 10_000_000L;
1909-
_logger.LogInformation("[DynamicLibrary] BuildAIOStreamsDirectResponse: Using default runtime {Minutes} min for {Name}",
1934+
_logger.LogWarning("[DynamicLibrary] BuildAIOStreamsDirectResponseAsync: Using fallback runtime {Minutes} min for {Name}",
19101935
defaultMinutes, itemName);
19111936
}
19121937

@@ -1950,25 +1975,36 @@ private PlaybackInfoResponse BuildAIOStreamsDirectResponse(Guid itemId, string s
19501975

19511976
/// <summary>
19521977
/// Get the IMDB ID for an item, checking both item and series provider IDs.
1978+
/// For episodes, always prefer the series IMDB ID (required for AIOStreams).
19531979
/// </summary>
19541980
private string? GetImdbIdForItem(BaseItemDto item)
19551981
{
1956-
// Try item's IMDB ID first
1957-
if (item.ProviderIds?.TryGetValue("Imdb", out var imdbId) == true && !string.IsNullOrEmpty(imdbId))
1982+
// For episodes, ALWAYS prefer series IMDB ID (required for AIOStreams)
1983+
if (item.Type == BaseItemKind.Episode)
19581984
{
1959-
return imdbId;
1960-
}
1985+
// First check if episode has SeriesImdb stored directly (most reliable)
1986+
if (item.ProviderIds?.TryGetValue("SeriesImdb", out var storedSeriesImdb) == true && !string.IsNullOrEmpty(storedSeriesImdb))
1987+
{
1988+
return storedSeriesImdb;
1989+
}
19611990

1962-
// For episodes, try the series IMDB ID
1963-
if (item.Type == BaseItemKind.Episode && item.SeriesId.HasValue)
1964-
{
1965-
var series = _itemCache.GetItem(item.SeriesId.Value);
1966-
if (series?.ProviderIds?.TryGetValue("Imdb", out var seriesImdbId) == true && !string.IsNullOrEmpty(seriesImdbId))
1991+
// Fall back to cache lookup if episode doesn't have SeriesImdb
1992+
if (item.SeriesId.HasValue)
19671993
{
1968-
return seriesImdbId;
1994+
var series = _itemCache.GetItem(item.SeriesId.Value);
1995+
if (series?.ProviderIds?.TryGetValue("Imdb", out var seriesImdbId) == true && !string.IsNullOrEmpty(seriesImdbId))
1996+
{
1997+
return seriesImdbId;
1998+
}
19691999
}
19702000
}
19712001

2002+
// For non-episodes, or if series IMDB not found, try item's own IMDB ID
2003+
if (item.ProviderIds?.TryGetValue("Imdb", out var imdbId) == true && !string.IsNullOrEmpty(imdbId))
2004+
{
2005+
return imdbId;
2006+
}
2007+
19722008
return null;
19732009
}
19742010

Jellyfin.Plugin.DynamicLibrary/Jellyfin.Plugin.DynamicLibrary.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
<ImplicitUsings>enable</ImplicitUsings>
66
<AssemblyName>Jellyfin.Plugin.DynamicLibrary</AssemblyName>
77
<RootNamespace>Jellyfin.Plugin.DynamicLibrary</RootNamespace>
8-
<Version>1.1.0</Version>
9-
<AssemblyVersion>1.1.0.0</AssemblyVersion>
10-
<FileVersion>1.1.0.0</FileVersion>
8+
<Version>1.1.1</Version>
9+
<AssemblyVersion>1.1.1.0</AssemblyVersion>
10+
<FileVersion>1.1.1.0</FileVersion>
1111
</PropertyGroup>
1212

1313
<ItemGroup>

0 commit comments

Comments
 (0)