Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Season support with extended season client #165

Merged
merged 8 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.GetTranslatedNamedOrDefaultIgnoreAlias(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);
}
}
}
72 changes: 72 additions & 0 deletions Jellyfin.Plugin.Tvdb/SeasonClient/CustomSeasonExtendedRecord.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#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