Skip to content

Commit

Permalink
Season support with extended season client (#165)
Browse files Browse the repository at this point in the history
  • Loading branch information
scampower3 authored Jul 17, 2024
1 parent d2e76ba commit efa937d
Show file tree
Hide file tree
Showing 9 changed files with 489 additions and 5 deletions.
5 changes: 5 additions & 0 deletions Jellyfin.Plugin.Tvdb/Configuration/PluginConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ public int MetadataUpdateInHours
/// </summary>
public string FallbackLanguages { get; set; } = string.Empty;

/// <summary>
/// Gets or sets a value indicating whether to import season name.
/// </summary>
public bool ImportSeasonName { get; set; } = false;

/// <summary>
/// Gets or sets a value indicating whether to include missing specials.
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions Jellyfin.Plugin.Tvdb/Configuration/config.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ <h2 class="sectionTitle">TheTVDB Settings:</h2>
If the preferred metadata language is not available, the plugin will attempt to retrieve metadata in these languages (in the order listed). Separate language codes with commas (e.g., en, fr, de, ja).
</div>
</div>
<label class="checkboxContainer">
<input is="emby-checkbox" type="checkbox" id="importSeasonName" />
<span>Import season name from provider.</span>
</label>
<label class="checkboxContainer">
<input is="emby-checkbox" type="checkbox" id="includeMissingSpecials" />
<span>Include missing specials</span>
Expand Down Expand Up @@ -80,6 +84,7 @@ <h2 class="sectionTitle">TheTVDB Settings:</h2>
document.getElementById('cacheDurationInDays').value = config.CacheDurationInDays;
document.getElementById('metadataUpdateInHours').value = config.MetadataUpdateInHours;
document.getElementById('fallbackLanguages').value = config.FallbackLanguages;
document.getElementById('importSeasonName').checked = config.ImportSeasonName
document.getElementById('includeMissingSpecials').checked = config.IncludeMissingSpecials;
document.getElementById('fallbackToOriginalLanguage').checked = config.FallbackToOriginalLanguage;
Dashboard.hideLoadingMsg();
Expand All @@ -95,6 +100,7 @@ <h2 class="sectionTitle">TheTVDB Settings:</h2>
config.CacheDurationInDays = document.getElementById('cacheDurationInDays').value;
config.MetadataUpdateInHours = document.getElementById('metadataUpdateInHours').value;
config.FallbackLanguages = document.getElementById('fallbackLanguages').value;
config.ImportSeasonName = document.getElementById('importSeasonName').checked;
config.IncludeMissingSpecials = document.getElementById('includeMissingSpecials').checked;
config.FallbackToOriginalLanguage = document.getElementById('fallbackToOriginalLanguage').checked;
ApiClient.updatePluginConfiguration(TvdbPluginConfiguration.uniquePluginId, config).then(function (result) {
Expand Down
154 changes: 154 additions & 0 deletions Jellyfin.Plugin.Tvdb/Providers/TvdbSeasonProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Plugin.Tvdb.SeasonClient;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
using Microsoft.Extensions.Logging;
using Tvdb.Sdk;

namespace Jellyfin.Plugin.Tvdb.Providers
{
/// <summary>
/// The Tvdb Season Provider.
/// </summary>
public class TvdbSeasonProvider : IRemoteMetadataProvider<Season, SeasonInfo>
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger<TvdbSeasonProvider> _logger;
private readonly ILibraryManager _libraryManager;
private readonly TvdbClientManager _tvdbClientManager;

/// <summary>
/// Initializes a new instance of the <see cref="TvdbSeasonProvider"/> class.
/// </summary>
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger{TvdbSeasonProvider}"/> interface.</param>
/// <param name="libraryManager">Instance of <see cref="ILibraryManager"/>.</param>
/// <param name="tvdbClientManager">Instance of <see cref="TvdbClientManager"/>.</param>
public TvdbSeasonProvider(IHttpClientFactory httpClientFactory, ILogger<TvdbSeasonProvider> logger, ILibraryManager libraryManager, TvdbClientManager tvdbClientManager)
{
_httpClientFactory = httpClientFactory;
_logger = logger;
_tvdbClientManager = tvdbClientManager;
_libraryManager = libraryManager;
}

/// <inheritdoc/>
public string Name => TvdbPlugin.ProviderName;

private static bool ImportSeasonName => TvdbPlugin.Instance?.Configuration.ImportSeasonName ?? false;

/// <inheritdoc/>
public async Task<MetadataResult<Season>> GetMetadata(SeasonInfo info, CancellationToken cancellationToken)
{
if (info.IndexNumber == null || !info.SeriesProviderIds.IsSupported())
{
_logger.LogDebug("No series identity found for {EpisodeName}", info.Name);
return new MetadataResult<Season>
{
QueriedById = true
};
}

int? seasonId = info.GetTvdbId();
string displayOrder;

// If the seasonId is 0, then we have to get the series metadata to get display order and then get the seasonId.
// If is automated is true, means that display order has changed,do the same as above.
if (seasonId == 0 || info.IsAutomated)
{
info.SeriesProviderIds.TryGetValue(MetadataProvider.Tvdb.ToString(), out var seriesId);
if (string.IsNullOrWhiteSpace(seriesId))
{
_logger.LogDebug("No series identity found for {EpisodeName}", info.Name);
return new MetadataResult<Season>
{
QueriedById = true
};
}

var query = new InternalItemsQuery()
{
HasAnyProviderId = new Dictionary<string, string>
{
{ MetadataProvider.Tvdb.ToString(), seriesId }
}
};

var series = _libraryManager.GetItemList(query).OfType<Series>().FirstOrDefault();
displayOrder = series!.DisplayOrder;
if (string.IsNullOrWhiteSpace(displayOrder))
{
displayOrder = "official";
}

var seriesInfo = await _tvdbClientManager.GetSeriesExtendedByIdAsync(series.GetTvdbId(), string.Empty, cancellationToken, small: true)
.ConfigureAwait(false);
seasonId = seriesInfo.Seasons.FirstOrDefault(s => s.Number == info.IndexNumber && string.Equals(s.Type.Type, displayOrder, StringComparison.OrdinalIgnoreCase))?.Id;

if (seasonId == null)
{
_logger.LogDebug("No season identity found for {SeasonName}", info.Name);
return new MetadataResult<Season>
{
QueriedById = true
};
}
}

var seasonInfo = await _tvdbClientManager.GetSeasonByIdAsync(seasonId ?? 0, string.Empty, cancellationToken)
.ConfigureAwait(false);

return MapSeasonToResult(info, seasonInfo);
}

private MetadataResult<Season> MapSeasonToResult(SeasonInfo id, CustomSeasonExtendedRecord season)
{
var result = new MetadataResult<Season>
{
HasMetadata = true,
Item = new Season
{
IndexNumber = id.IndexNumber,
// Tvdb uses 3 letter code for language (prob ISO 639-2)
// Reverts to OriginalName if no translation is found
Overview = season.Translations.GetTranslatedOverviewOrDefault(id.MetadataLanguage),
}
};

var item = result.Item;
item.SetTvdbId(season.Id);

if (ImportSeasonName)
{
item.Name = season.Translations.GetTranslatedNamedOrDefaultIgnoreAliasProperty(id.MetadataLanguage) ?? TvdbUtils.ReturnOriginalLanguageOrDefault(season.Name);
item.OriginalTitle = season.Name;
}

return result;
}

/// <inheritdoc/>
public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeasonInfo searchInfo, CancellationToken cancellationToken)
{
return Task.FromResult(Enumerable.Empty<RemoteSearchResult>());
}

/// <inheritdoc/>
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(new Uri(url), cancellationToken);
}
}
}
71 changes: 71 additions & 0 deletions Jellyfin.Plugin.Tvdb/SeasonClient/CustomSeasonExtendedRecord.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
#pragma warning disable CA2227 // Collection properties should be read only

using System.Text.Json.Serialization;
using Tvdb.Sdk;

namespace Jellyfin.Plugin.Tvdb.SeasonClient
{
public sealed class CustomSeasonExtendedRecord
{
private System.Collections.Generic.IDictionary<string, object> _additionalProperties = default!;

[JsonPropertyName("artwork")]
public System.Collections.Generic.IReadOnlyList<ArtworkBaseRecord> Artwork { get; set; } = default!;

[JsonPropertyName("companies")]
public Companies Companies { get; set; } = default!;

[JsonPropertyName("episodes")]
public System.Collections.Generic.IReadOnlyList<EpisodeBaseRecord> Episodes { get; set; } = default!;

[JsonPropertyName("id")]
public int? Id { get; set; } = default!;

[JsonPropertyName("image")]
public string Image { get; set; } = default!;

[JsonPropertyName("imageType")]
public int? ImageType { get; set; }

[JsonPropertyName("lastUpdated")]
public string LastUpdated { get; set; } = default!;

[JsonPropertyName("name")]
public string Name { get; set; } = default!;

[JsonPropertyName("nameTranslations")]
public System.Collections.Generic.IReadOnlyList<string> NameTranslations { get; set; } = default!;

[JsonPropertyName("number")]
public long? Number { get; set; }

[JsonPropertyName("overviewTranslations")]
public System.Collections.Generic.IReadOnlyList<string> OverviewTranslations { get; set; } = default!;

[JsonPropertyName("seriesId")]
public long? SeriesId { get; set; }

[JsonPropertyName("trailers")]
public System.Collections.Generic.IReadOnlyList<Trailer> Trailers { get; set; } = default!;

[JsonPropertyName("type")]
public SeasonType Type { get; set; } = default!;

[JsonPropertyName("tagOptions")]
public System.Collections.Generic.IReadOnlyList<TagOption> TagOptions { get; set; } = default!;

[JsonPropertyName("translations")]
public TranslationExtended Translations { get; set; } = default!;

[JsonPropertyName("year")]
public string Year { get; set; } = default!;

[JsonExtensionData]
public System.Collections.Generic.IDictionary<string, object> AdditionalProperties
{
get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary<string, object>()); }
set { _additionalProperties = value; }
}
}
}
Loading

0 comments on commit efa937d

Please sign in to comment.