using Jellyfin.Plugin.MetaShark.Api; using Jellyfin.Plugin.MetaShark.Core; using Jellyfin.Plugin.MetaShark.Model; 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 System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using TMDbLib.Objects.Find; using TMDbLib.Objects.TvShows; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Http; namespace Jellyfin.Plugin.MetaShark.Providers { public class SeasonProvider : BaseProvider, IRemoteMetadataProvider { public SeasonProvider(IHttpClientFactory httpClientFactory, ILoggerFactory loggerFactory, ILibraryManager libraryManager, IHttpContextAccessor httpContextAccessor, DoubanApi doubanApi, TmdbApi tmdbApi, OmdbApi omdbApi) : base(httpClientFactory, loggerFactory.CreateLogger(), libraryManager, httpContextAccessor, doubanApi, tmdbApi, omdbApi) { } public string Name => Plugin.PluginName; /// public async Task> GetSearchResults(SeasonInfo info, CancellationToken cancellationToken) { this.Log($"GetSeasonSearchResults of [name]: {info.Name}"); return await Task.FromResult(Enumerable.Empty()); } /// public async Task> GetMetadata(SeasonInfo info, CancellationToken cancellationToken) { var result = new MetadataResult(); info.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out var seriesTmdbId); info.SeriesProviderIds.TryGetValue(Plugin.ProviderId, out var metaSource); info.SeriesProviderIds.TryGetValue(DoubanProviderId, out var sid); var seasonNumber = info.IndexNumber; // S00/Season 00特典目录会为0 var seasonSid = info.GetProviderId(DoubanProviderId); this.Log($"GetSeasonMetaData of [name]: {info.Name} number: {info.IndexNumber} seriesTmdbId: {seriesTmdbId} sid: {sid} metaSource: {metaSource}"); if (metaSource != MetaSource.Tmdb && !string.IsNullOrEmpty(sid)) { // 从sereis获取正确名称,info.Name当是标准格式如S01等时,会变成第x季,非标准名称没法识别时默认文件名 var series = await this._doubanApi.GetMovieAsync(sid, cancellationToken).ConfigureAwait(false); if (series == null) { return result; } var seasonName = RemoveSeasonSubfix(series.Name); // TODO:季文件夹名称不规范,没法拿到seasonNumber,尝试从文件名猜出??? // 没有季id,但存在tmdbid,尝试从tmdb获取对应季的年份信息,用于从豆瓣搜索对应季数据 if (string.IsNullOrEmpty(seasonSid)) { var seasonYear = 0; if (!string.IsNullOrEmpty(seriesTmdbId) && (seasonNumber.HasValue && seasonNumber > 0)) { var season = await this._tmdbApi .GetSeasonAsync(seriesTmdbId.ToInt(), seasonNumber.Value, info.MetadataLanguage, info.MetadataLanguage, cancellationToken) .ConfigureAwait(false); seasonYear = season?.AirDate?.Year ?? 0; } if (!string.IsNullOrEmpty(seasonName) && seasonYear > 0) { seasonSid = await this.GuestDoubanSeasonByYearAsync(seasonName, seasonYear, cancellationToken).ConfigureAwait(false); } } // 获取季豆瓣数据 if (!string.IsNullOrEmpty(seasonSid)) { var subject = await this._doubanApi.GetMovieAsync(seasonSid, cancellationToken).ConfigureAwait(false); if (subject != null) { subject.Celebrities = await this._doubanApi.GetCelebritiesBySidAsync(seasonSid, cancellationToken).ConfigureAwait(false); var movie = new Season { ProviderIds = new Dictionary { { DoubanProviderId, subject.Sid } }, Name = subject.Name, OriginalTitle = subject.OriginalName, CommunityRating = subject.Rating, Overview = subject.Intro, ProductionYear = subject.Year, Genres = subject.Genres, PremiereDate = subject.ScreenTime, // 发行日期 IndexNumber = info.IndexNumber, }; result.Item = movie; result.HasMetadata = true; subject.LimitDirectorCelebrities.Take(this.config.MaxCastMembers).ToList().ForEach(c => result.AddPerson(new PersonInfo { Name = c.Name, Type = c.RoleType, Role = c.Role, ImageUrl = c.Img, ProviderIds = new Dictionary { { DoubanProviderId, c.Id } }, })); return result; } } else { this.Log($"GetSeasonMetaData of [name]: {info.Name} not found douban season id!"); } // tmdb有数据,豆瓣找不到,尝试获取tmdb的季数据 if (string.IsNullOrEmpty(seasonSid) && !string.IsNullOrWhiteSpace(seriesTmdbId) && (seasonNumber.HasValue && seasonNumber > 0)) { var tmdbResult = await this.GetMetadataByTmdb(info, seriesTmdbId, seasonNumber.Value, cancellationToken).ConfigureAwait(false); if (tmdbResult != null) { return tmdbResult; } } // 从豆瓣获取不到季信息 return result; } // series使用TMDB元数据来源 // tmdb季级没有对应id,只通过indexNumber区分 if (!string.IsNullOrWhiteSpace(seriesTmdbId) && (seasonNumber.HasValue && seasonNumber > 0)) { var tmdbResult = await this.GetMetadataByTmdb(info, seriesTmdbId, seasonNumber.Value, cancellationToken).ConfigureAwait(false); if (tmdbResult != null) { return tmdbResult; } } return result; } public async Task?> GetMetadataByTmdb(SeasonInfo info, string seriesTmdbId, int seasonNumber, CancellationToken cancellationToken) { var seasonResult = await this._tmdbApi .GetSeasonAsync(seriesTmdbId.ToInt(), seasonNumber, info.MetadataLanguage, info.MetadataLanguage, cancellationToken) .ConfigureAwait(false); if (seasonResult == null) { this.Log($"Not found season from TMDB. {info.Name} seriesTmdbId: {seriesTmdbId} seasonNumber: {seasonNumber}"); return null; } var result = new MetadataResult(); result.HasMetadata = true; result.Item = new Season { Name = seasonResult.Name, IndexNumber = seasonNumber, Overview = seasonResult.Overview, PremiereDate = seasonResult.AirDate, ProductionYear = seasonResult.AirDate?.Year, }; if (!string.IsNullOrEmpty(seasonResult.ExternalIds?.TvdbId)) { result.Item.SetProviderId(MetadataProvider.Tvdb, seasonResult.ExternalIds.TvdbId); } foreach (var person in GetPersons(seasonResult)) { result.AddPerson(person); } return result; } private IEnumerable GetPersons(TvSeason item) { // 演员 if (item.Credits?.Cast != null) { foreach (var actor in item.Credits.Cast.OrderBy(a => a.Order).Take(this.config.MaxCastMembers)) { var personInfo = new PersonInfo { Name = actor.Name.Trim(), Role = actor.Character, Type = PersonType.Actor, SortOrder = actor.Order, }; if (!string.IsNullOrWhiteSpace(actor.ProfilePath)) { personInfo.ImageUrl = this._tmdbApi.GetProfileUrl(actor.ProfilePath); } if (actor.Id > 0) { personInfo.SetProviderId(MetadataProvider.Tmdb, actor.Id.ToString(CultureInfo.InvariantCulture)); } yield return personInfo; } } // 导演 if (item.Credits?.Crew != null) { var keepTypes = new[] { PersonType.Director, PersonType.Writer, PersonType.Producer }; foreach (var person in item.Credits.Crew) { // Normalize this var type = MapCrewToPersonType(person); if (!keepTypes.Contains(type, StringComparer.OrdinalIgnoreCase) && !keepTypes.Contains(person.Job ?? string.Empty, StringComparer.OrdinalIgnoreCase)) { continue; } var personInfo = new PersonInfo { Name = person.Name.Trim(), Role = person.Job, Type = type }; if (!string.IsNullOrWhiteSpace(person.ProfilePath)) { personInfo.ImageUrl = this._tmdbApi.GetPosterUrl(person.ProfilePath); } if (person.Id > 0) { personInfo.SetProviderId(MetadataProvider.Tmdb, person.Id.ToString(CultureInfo.InvariantCulture)); } yield return personInfo; } } } /// public async Task GetImageResponse(string url, CancellationToken cancellationToken) { this.Log("GetImageResponse url: {0}", url); return await this._httpClientFactory.CreateClient().GetAsync(new Uri(url), cancellationToken).ConfigureAwait(false); } private void Log(string? message, params object?[] args) { this._logger.LogInformation($"[MetaShark] {message}", args); } } }