diff --git a/Jellyfin.Plugin.MetaShark.Test/MovieProviderTest.cs b/Jellyfin.Plugin.MetaShark.Test/MovieProviderTest.cs index 3e064ee..4caf027 100644 --- a/Jellyfin.Plugin.MetaShark.Test/MovieProviderTest.cs +++ b/Jellyfin.Plugin.MetaShark.Test/MovieProviderTest.cs @@ -30,7 +30,7 @@ public class MovieProviderTest [TestMethod] - public void TestGetMetadata() + public void TestSearch() { var httpClientFactory = new DefaultHttpClientFactory(); var libraryManagerStub = new Mock(); @@ -52,6 +52,29 @@ public void TestGetMetadata() }).GetAwaiter().GetResult(); } + [TestMethod] + public void TestGetMetadata() + { + var httpClientFactory = new DefaultHttpClientFactory(); + var libraryManagerStub = new Mock(); + var httpContextAccessorStub = new Mock(); + var doubanApi = new DoubanApi(loggerFactory); + var tmdbApi = new TmdbApi(loggerFactory); + var omdbApi = new OmdbApi(loggerFactory); + var imdbApi = new ImdbApi(loggerFactory); + + Task.Run(async () => + { + var info = new MovieInfo() { Name = "姥姥的外孙", MetadataLanguage = "zh" }; + var provider = new MovieProvider(httpClientFactory, loggerFactory, libraryManagerStub.Object, httpContextAccessorStub.Object, doubanApi, tmdbApi, omdbApi, imdbApi); + var result = await provider.GetMetadata(info, CancellationToken.None); + Assert.IsNotNull(result); + + var str = result.ToJson(); + Console.WriteLine(result.ToJson()); + }).GetAwaiter().GetResult(); + } + [TestMethod] public void TestGetMetadataAnime() { diff --git a/Jellyfin.Plugin.MetaShark/Configuration/PluginConfiguration.cs b/Jellyfin.Plugin.MetaShark/Configuration/PluginConfiguration.cs index b07a3c2..75498e9 100644 --- a/Jellyfin.Plugin.MetaShark/Configuration/PluginConfiguration.cs +++ b/Jellyfin.Plugin.MetaShark/Configuration/PluginConfiguration.cs @@ -26,7 +26,7 @@ public class PluginConfiguration : BasePluginConfiguration /// /// 豆瓣海报使用大图 /// - public bool EnableDoubanLargePoster { get; set; } = false; + public bool EnableDoubanLargePoster { get; set; } = true; /// /// 豆瓣背景图使用原图 /// diff --git a/Jellyfin.Plugin.MetaShark/Model/DoubanSubject.cs b/Jellyfin.Plugin.MetaShark/Model/DoubanSubject.cs index d8a8f84..2829b62 100644 --- a/Jellyfin.Plugin.MetaShark/Model/DoubanSubject.cs +++ b/Jellyfin.Plugin.MetaShark/Model/DoubanSubject.cs @@ -112,7 +112,32 @@ public string[] Genres } + [JsonIgnore] + public string PrimaryLanguageCode + { + get + { + var languageCodeMap = new Dictionary() { + { "日语", "ja" }, + { "法语", "fr" }, + { "德语", "de" }, + { "俄语", "ru" }, + { "韩语", "ko" }, + { "泰语", "th" }, + { "泰米尔语", "ta" }, + }; + var primaryLanguage = this.Language.Split("/").Select(x => x.Trim()).Where(x => !string.IsNullOrEmpty(x)).FirstOrDefault(); + if (!string.IsNullOrEmpty(primaryLanguage)) + { + if (languageCodeMap.TryGetValue(primaryLanguage, out var lang)) + { + return lang; + } + } + return string.Empty; + } + } } public class DoubanCelebrity diff --git a/Jellyfin.Plugin.MetaShark/Providers/BaseProvider.cs b/Jellyfin.Plugin.MetaShark/Providers/BaseProvider.cs index c1dea3e..9106170 100644 --- a/Jellyfin.Plugin.MetaShark/Providers/BaseProvider.cs +++ b/Jellyfin.Plugin.MetaShark/Providers/BaseProvider.cs @@ -20,6 +20,8 @@ using Jellyfin.Plugin.MetaShark.Core; using Microsoft.AspNetCore.Http; using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Model.Providers; +using TMDbLib.Objects.Languages; namespace Jellyfin.Plugin.MetaShark.Providers { @@ -563,6 +565,24 @@ protected string AdjustImageLanguage(string imageLanguage, string requestLanguag return imageLanguage; } + // 把第一个备选图片语言设为空,提高图片优先级,保证备选语言图片优先级比英文高 + protected List AdjustImageLanguagePriority(IList images, string preferLanguage, string alternativeLanguage) + { + var imagesOrdered = images.OrderByLanguageDescending(preferLanguage, alternativeLanguage).ToList(); + + // 不存在默认语言图片,且备选语言是日语 + if (alternativeLanguage == "ja" && imagesOrdered.Where(x => x.Language == preferLanguage).Count() == 0) + { + var idx = imagesOrdered.FindIndex(x => x.Language == alternativeLanguage); + if (idx >= 0) + { + imagesOrdered[idx].Language = null; + } + } + + return imagesOrdered; + } + /// /// Maps the TMDB provided roles for crew members to Jellyfin roles. /// diff --git a/Jellyfin.Plugin.MetaShark/Providers/EpisodeImageProvider.cs b/Jellyfin.Plugin.MetaShark/Providers/EpisodeImageProvider.cs index ce10c56..860d074 100644 --- a/Jellyfin.Plugin.MetaShark/Providers/EpisodeImageProvider.cs +++ b/Jellyfin.Plugin.MetaShark/Providers/EpisodeImageProvider.cs @@ -82,7 +82,7 @@ public async Task> GetImages(BaseItem item, Cancell CommunityRating = episodeResult.VoteAverage, VoteCount = episodeResult.VoteCount, ProviderName = Name, - Type = ImageType.Primary + Type = ImageType.Primary, }); } return result; diff --git a/Jellyfin.Plugin.MetaShark/Providers/Extensions/EnumerableExtensions.cs b/Jellyfin.Plugin.MetaShark/Providers/Extensions/EnumerableExtensions.cs new file mode 100644 index 0000000..147a78f --- /dev/null +++ b/Jellyfin.Plugin.MetaShark/Providers/Extensions/EnumerableExtensions.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Model.Providers; + +namespace Jellyfin.Plugin.MetaShark.Providers +{ + public static class EnumerableExtensions + { + private const int MaxPriority = 99; + + public static IEnumerable OrderByLanguageDescending(this IEnumerable remoteImageInfos, params string[] requestedLanguages) + { + if (requestedLanguages.Length <= 0) + { + requestedLanguages = new[] { "en" }; + } + + var requestedLanguagePriorityMap = new Dictionary(); + for (int i = 0; i < requestedLanguages.Length; i++) + { + if (string.IsNullOrEmpty(requestedLanguages[i])) + { + continue; + } + requestedLanguagePriorityMap.Add(NormalizeLanguage(requestedLanguages[i]), MaxPriority - i); + } + + return remoteImageInfos.OrderByDescending(delegate (RemoteImageInfo i) + { + if (string.IsNullOrEmpty(i.Language)) + { + return 3; + } + + if (requestedLanguagePriorityMap.TryGetValue(NormalizeLanguage(i.Language), out int priority)) + { + return priority; + } + + return string.Equals(i.Language, "en", StringComparison.OrdinalIgnoreCase) ? 2 : 0; + }).ThenByDescending((RemoteImageInfo i) => i.CommunityRating.GetValueOrDefault()).ThenByDescending((RemoteImageInfo i) => i.VoteCount.GetValueOrDefault()); + } + + private static string NormalizeLanguage(string language) + { + if (string.IsNullOrEmpty(language)) + { + return language; + } + + return language.Split('-')[0].ToLower(); + } + } +} \ No newline at end of file diff --git a/Jellyfin.Plugin.MetaShark/Providers/MovieImageProvider.cs b/Jellyfin.Plugin.MetaShark/Providers/MovieImageProvider.cs index f555e6d..ab24d2b 100644 --- a/Jellyfin.Plugin.MetaShark/Providers/MovieImageProvider.cs +++ b/Jellyfin.Plugin.MetaShark/Providers/MovieImageProvider.cs @@ -7,7 +7,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Providers; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -53,9 +52,8 @@ public async Task> GetImages(BaseItem item, Cancell { return Enumerable.Empty(); } - var imageLanguages = this.GetImageLanguageParam(item.PreferredMetadataLanguage, primary.Language); - var backdropImgs = await this.GetBackdrop(item, imageLanguages, cancellationToken).ConfigureAwait(false); - var logoImgs = await this.GetLogos(item, imageLanguages, cancellationToken).ConfigureAwait(false); + var backdropImgs = await this.GetBackdrop(item, primary.PrimaryLanguageCode, cancellationToken).ConfigureAwait(false); + var logoImgs = await this.GetLogos(item, primary.PrimaryLanguageCode, cancellationToken).ConfigureAwait(false); var res = new List { new RemoteImageInfo @@ -63,6 +61,7 @@ public async Task> GetImages(BaseItem item, Cancell ProviderName = this.Name, Url = this.GetDoubanPoster(primary), Type = ImageType.Primary, + Language = "zh", }, }; res.AddRange(backdropImgs); @@ -133,7 +132,7 @@ public async Task> GetImages(BaseItem item, Cancell /// Query for a background photo /// /// Instance of the interface. - private async Task> GetBackdrop(BaseItem item, string imageLanguages, CancellationToken cancellationToken) + private async Task> GetBackdrop(BaseItem item, string alternativeImageLanguage, CancellationToken cancellationToken) { var sid = item.GetProviderId(DoubanProviderId); var tmdbId = item.GetProviderId(MetadataProvider.Tmdb); @@ -157,6 +156,7 @@ private async Task> GetBackdrop(BaseItem item, stri Height = x.Height, Width = x.Width, Type = ImageType.Backdrop, + Language = "zh", }; } else @@ -166,6 +166,7 @@ private async Task> GetBackdrop(BaseItem item, stri ProviderName = this.Name, Url = this.GetProxyImageUrl(x.Large), Type = ImageType.Backdrop, + Language = "zh", }; } }).ToList(); @@ -173,22 +174,23 @@ private async Task> GetBackdrop(BaseItem item, stri } } - // 背景图缺失,从TheMovieDb补充背景图 - if (list.Count == 0 && config.EnableTmdbBackdrop && !string.IsNullOrEmpty(tmdbId)) + // 添加 TheMovieDb 背景图为备选 + if (config.EnableTmdbBackdrop && !string.IsNullOrEmpty(tmdbId)) { var language = item.GetPreferredMetadataLanguage(); var movie = await this._tmdbApi - .GetMovieAsync(tmdbId.ToInt(), language, imageLanguages, cancellationToken) + .GetMovieAsync(tmdbId.ToInt(), language, language, cancellationToken) .ConfigureAwait(false); if (movie != null && !string.IsNullOrEmpty(movie.BackdropPath)) { - this.Log("GetBackdrop from tmdb id: {0} lang: {1}", tmdbId, imageLanguages); + this.Log("GetBackdrop from tmdb id: {0} lang: {1}", tmdbId, language); list.Add(new RemoteImageInfo { ProviderName = this.Name, Url = this._tmdbApi.GetBackdropUrl(movie.BackdropPath), Type = ImageType.Backdrop, + Language = language, }); } } @@ -196,16 +198,16 @@ private async Task> GetBackdrop(BaseItem item, stri return list; } - private async Task> GetLogos(BaseItem item, string imageLanguages, CancellationToken cancellationToken) + private async Task> GetLogos(BaseItem item, string alternativeImageLanguage, CancellationToken cancellationToken) { var tmdbId = item.GetProviderId(MetadataProvider.Tmdb); var list = new List(); var language = item.GetPreferredMetadataLanguage(); if (this.config.EnableTmdbLogo && !string.IsNullOrEmpty(tmdbId)) { - this.Log("GetLogos from tmdb id: {0} lang: {1}", tmdbId, imageLanguages); + this.Log("GetLogos from tmdb id: {0}", tmdbId); var movie = await this._tmdbApi - .GetMovieAsync(tmdbId.ToInt(), language, imageLanguages, cancellationToken) + .GetMovieAsync(tmdbId.ToInt(), null, null, cancellationToken) .ConfigureAwait(false); if (movie != null && movie.Images != null) @@ -224,7 +226,9 @@ private async Task> GetLogos(BaseItem item, string } } - return list.OrderByLanguageDescending(language); + // TODO:jellyfin 内部判断取哪个图片时,还会默认使用 OrderByLanguageDescending 排序一次,这里排序没用 + // 默认图片优先级是:默认语言 > 无语言 > en > 其他语言 + return this.AdjustImageLanguagePriority(list, language, alternativeImageLanguage); } } diff --git a/Jellyfin.Plugin.MetaShark/Providers/MovieProvider.cs b/Jellyfin.Plugin.MetaShark/Providers/MovieProvider.cs index bdf1188..f8360bd 100644 --- a/Jellyfin.Plugin.MetaShark/Providers/MovieProvider.cs +++ b/Jellyfin.Plugin.MetaShark/Providers/MovieProvider.cs @@ -85,7 +85,6 @@ public async Task> GetSearchResults(MovieInfo in public async Task> GetMetadata(MovieInfo info, CancellationToken cancellationToken) { var fileName = this.GetOriginalFileName(info); - this.Log($"GetMovieMetadata of [name]: {info.Name} [fileName]: {fileName} EnableTmdb: {config.EnableTmdb}"); var result = new MetadataResult(); // 使用刷新元数据时,providerIds会保留旧有值,只有识别/新增才会没值 @@ -95,6 +94,7 @@ public async Task> GetMetadata(MovieInfo info, Cancellatio // 注意:会存在元数据有tmdbId,但metaSource没值的情况(之前由TMDB插件刮削导致) var hasTmdbMeta = metaSource == MetaSource.Tmdb && !string.IsNullOrEmpty(tmdbId); var hasDoubanMeta = metaSource != MetaSource.Tmdb && !string.IsNullOrEmpty(sid); + this.Log($"GetMovieMetadata of [name]: {info.Name} [fileName]: {fileName} metaSource: {metaSource} EnableTmdb: {config.EnableTmdb}"); if (!hasDoubanMeta && !hasTmdbMeta) { // 处理extras影片 diff --git a/Jellyfin.Plugin.MetaShark/Providers/PersonImageProvider.cs b/Jellyfin.Plugin.MetaShark/Providers/PersonImageProvider.cs index 75e022d..3dcfea5 100644 --- a/Jellyfin.Plugin.MetaShark/Providers/PersonImageProvider.cs +++ b/Jellyfin.Plugin.MetaShark/Providers/PersonImageProvider.cs @@ -49,6 +49,7 @@ public async Task> GetImages(BaseItem item, Cancell ProviderName = this.Name, Url = this.GetProxyImageUrl(celebrity.Img), Type = ImageType.Primary, + Language = "zh", }); } @@ -68,6 +69,7 @@ public async Task> GetImages(BaseItem item, Cancell Width = x.Width, Height = x.Height, Type = ImageType.Primary, + Language = "zh", }); }); } diff --git a/Jellyfin.Plugin.MetaShark/Providers/SeasonImageProvider.cs b/Jellyfin.Plugin.MetaShark/Providers/SeasonImageProvider.cs index ba5e52c..0f6dc5a 100644 --- a/Jellyfin.Plugin.MetaShark/Providers/SeasonImageProvider.cs +++ b/Jellyfin.Plugin.MetaShark/Providers/SeasonImageProvider.cs @@ -5,7 +5,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Providers; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -62,6 +61,7 @@ public async Task> GetImages(BaseItem item, Cancell ProviderName = primary.Name, Url = this.GetDoubanPoster(primary), Type = ImageType.Primary, + Language = "zh", }, }; return res; diff --git a/Jellyfin.Plugin.MetaShark/Providers/SeriesImageProvider.cs b/Jellyfin.Plugin.MetaShark/Providers/SeriesImageProvider.cs index af229f7..252e7cb 100644 --- a/Jellyfin.Plugin.MetaShark/Providers/SeriesImageProvider.cs +++ b/Jellyfin.Plugin.MetaShark/Providers/SeriesImageProvider.cs @@ -7,7 +7,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Providers; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -53,18 +52,18 @@ public async Task> GetImages(BaseItem item, Cancell { return Enumerable.Empty(); } - var imageLanguages = this.GetImageLanguageParam(item.PreferredMetadataLanguage, primary.Language); - var backdropImgs = await this.GetBackdrop(item, imageLanguages, cancellationToken).ConfigureAwait(false); - var logoImgs = await this.GetLogos(item, imageLanguages, cancellationToken).ConfigureAwait(false); - var res = new List { new RemoteImageInfo { ProviderName = this.Name, Url = this.GetDoubanPoster(primary), Type = ImageType.Primary, + Language = "zh", }, }; + + var backdropImgs = await this.GetBackdrop(item, primary.PrimaryLanguageCode, cancellationToken).ConfigureAwait(false); + var logoImgs = await this.GetLogos(item, primary.PrimaryLanguageCode, cancellationToken).ConfigureAwait(false); res.AddRange(backdropImgs); res.AddRange(logoImgs); return res; @@ -133,7 +132,7 @@ public async Task> GetImages(BaseItem item, Cancell /// Query for a background photo /// /// Instance of the interface. - private async Task> GetBackdrop(BaseItem item, string imageLanguages, CancellationToken cancellationToken) + private async Task> GetBackdrop(BaseItem item, string alternativeImageLanguage, CancellationToken cancellationToken) { var sid = item.GetProviderId(DoubanProviderId); var tmdbId = item.GetProviderId(MetadataProvider.Tmdb); @@ -157,6 +156,7 @@ private async Task> GetBackdrop(BaseItem item, stri Height = x.Height, Width = x.Width, Type = ImageType.Backdrop, + Language = "zh", }; } else @@ -166,29 +166,30 @@ private async Task> GetBackdrop(BaseItem item, stri ProviderName = Name, Url = this.GetProxyImageUrl(x.Large), Type = ImageType.Backdrop, + Language = "zh", }; } }).ToList(); - } } - // 背景图缺失,从TheMovieDb补充背景图 - if (list.Count == 0 && config.EnableTmdbBackdrop && !string.IsNullOrEmpty(tmdbId)) + // 添加 TheMovieDb 背景图为备选 + if (config.EnableTmdbBackdrop && !string.IsNullOrEmpty(tmdbId)) { var language = item.GetPreferredMetadataLanguage(); var movie = await _tmdbApi - .GetSeriesAsync(tmdbId.ToInt(), language, imageLanguages, cancellationToken) + .GetSeriesAsync(tmdbId.ToInt(), language, language, cancellationToken) .ConfigureAwait(false); if (movie != null && !string.IsNullOrEmpty(movie.BackdropPath)) { - this.Log("GetBackdrop from tmdb id: {0} lang: {1}", tmdbId, imageLanguages); + this.Log("GetBackdrop from tmdb id: {0} lang: {1}", tmdbId, language); list.Add(new RemoteImageInfo { ProviderName = this.Name, Url = this._tmdbApi.GetBackdropUrl(movie.BackdropPath), Type = ImageType.Backdrop, + Language = language, }); } } @@ -196,16 +197,16 @@ private async Task> GetBackdrop(BaseItem item, stri return list; } - private async Task> GetLogos(BaseItem item, string imageLanguages, CancellationToken cancellationToken) + private async Task> GetLogos(BaseItem item, string alternativeImageLanguage, CancellationToken cancellationToken) { var tmdbId = item.GetProviderId(MetadataProvider.Tmdb); var language = item.GetPreferredMetadataLanguage(); var list = new List(); if (this.config.EnableTmdbLogo && !string.IsNullOrEmpty(tmdbId)) { - this.Log("GetLogos from tmdb id: {0} lang: {1}", tmdbId, imageLanguages); + this.Log("GetLogos from tmdb id: {0}", tmdbId); var movie = await this._tmdbApi - .GetSeriesAsync(tmdbId.ToInt(), language, imageLanguages, cancellationToken) + .GetSeriesAsync(tmdbId.ToInt(), null, null, cancellationToken) .ConfigureAwait(false); if (movie != null && movie.Images != null) @@ -224,7 +225,9 @@ private async Task> GetLogos(BaseItem item, string } } - return list.OrderByLanguageDescending(language); + // TODO:jellyfin 内部判断取哪个图片时,还会默认使用 OrderByLanguageDescending 排序一次,这里排序没用 + // 默认图片优先级是:默认语言 > 无语言 > en > 其他语言 + return this.AdjustImageLanguagePriority(list, language, alternativeImageLanguage); } } diff --git a/Jellyfin.Plugin.MetaShark/Providers/SeriesProvider.cs b/Jellyfin.Plugin.MetaShark/Providers/SeriesProvider.cs index aefb3fc..1770cf3 100644 --- a/Jellyfin.Plugin.MetaShark/Providers/SeriesProvider.cs +++ b/Jellyfin.Plugin.MetaShark/Providers/SeriesProvider.cs @@ -80,7 +80,6 @@ public async Task> GetSearchResults(SeriesInfo i public async Task> GetMetadata(SeriesInfo info, CancellationToken cancellationToken) { var fileName = this.GetOriginalFileName(info); - this.Log($"GetSeriesMetadata of [name]: {info.Name} [fileName]: {fileName} IsAutomated: {info.IsAutomated}"); var result = new MetadataResult(); var sid = info.GetProviderId(DoubanProviderId); @@ -89,6 +88,7 @@ public async Task> GetMetadata(SeriesInfo info, Cancellat // 注意:会存在元数据有tmdbId,但metaSource没值的情况(之前由TMDB插件刮削导致) var hasTmdbMeta = metaSource == MetaSource.Tmdb && !string.IsNullOrEmpty(tmdbId); var hasDoubanMeta = metaSource != MetaSource.Tmdb && !string.IsNullOrEmpty(sid); + this.Log($"GetSeriesMetadata of [name]: {info.Name} [fileName]: {fileName} metaSource: {metaSource} EnableTmdb: {config.EnableTmdb}"); if (!hasDoubanMeta && !hasTmdbMeta) { // 自动扫描搜索匹配元数据