diff --git a/Jellyfin.Plugin.MetaShark/Api/DoubanApi.cs b/Jellyfin.Plugin.MetaShark/Api/DoubanApi.cs index e93c8e8..ebfc670 100644 --- a/Jellyfin.Plugin.MetaShark/Api/DoubanApi.cs +++ b/Jellyfin.Plugin.MetaShark/Api/DoubanApi.cs @@ -107,10 +107,13 @@ namespace Jellyfin.Plugin.MetaShark.Api httpClient.DefaultRequestHeaders.Add("Referer", "https://movie.douban.com/"); this.LoadLoadDoubanCookie(); - Plugin.Instance!.ConfigurationChanged += (_, _) => + if (Plugin.Instance != null) { - this.LoadLoadDoubanCookie(); - }; + Plugin.Instance.ConfigurationChanged += (_, _) => + { + this.LoadLoadDoubanCookie(); + }; + } } diff --git a/Jellyfin.Plugin.MetaShark/Core/NameParser.cs b/Jellyfin.Plugin.MetaShark/Core/NameParser.cs index 4f9bebb..c0e35f3 100644 --- a/Jellyfin.Plugin.MetaShark/Core/NameParser.cs +++ b/Jellyfin.Plugin.MetaShark/Core/NameParser.cs @@ -16,7 +16,7 @@ namespace Jellyfin.Plugin.MetaShark.Core private static readonly Regex unusedReg = new Regex(@"\[.+?\]|\(.+?\)|【.+?】", RegexOptions.Compiled); - private static readonly Regex extrasReg = new Regex(@"\[(CM|Menu|NCED|NCOP|Drama)[0-9_]*?\]", RegexOptions.Compiled); + private static readonly Regex extrasReg = new Regex(@"\[(OP|ED|PV|CM|Menu|NCED|NCOP|Drama|PreView)[0-9_]*?\]", RegexOptions.Compiled | RegexOptions.IgnoreCase); public static ParseNameResult Parse(string fileName, bool isTvSeries = false) { diff --git a/Jellyfin.Plugin.MetaShark/Providers/BaseProvider.cs b/Jellyfin.Plugin.MetaShark/Providers/BaseProvider.cs index 2e6f86a..bea9e03 100644 --- a/Jellyfin.Plugin.MetaShark/Providers/BaseProvider.cs +++ b/Jellyfin.Plugin.MetaShark/Providers/BaseProvider.cs @@ -149,7 +149,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers item = result.Where(x => x.Category == cat && x.Year == info.Year).FirstOrDefault(); if (item != null) { - this.Log($"GuessByDouban found -> {item.Name}({item.Sid})"); + this.Log($"Found douban [id]: {item.Name}({item.Sid})"); return item.Sid; } } @@ -181,13 +181,13 @@ namespace Jellyfin.Plugin.MetaShark.Providers var suggestItem = suggestResult.Where(x => x.Year == year && x.Name == name).FirstOrDefault(); if (suggestItem != null) { - this.Log($"GuestDoubanSeasonByYear found -> {suggestItem.Name}({suggestItem.Sid}) (suggest)"); + this.Log($"Found douban [id]: {suggestItem.Name}({suggestItem.Sid}) (suggest)"); return suggestItem.Sid; } suggestItem = suggestResult.Where(x => x.Year == year).FirstOrDefault(); if (suggestItem != null) { - this.Log($"GuestDoubanSeasonByYear found -> {suggestItem.Name}({suggestItem.Sid}) (suggest)"); + this.Log($"Found douban [id]: {suggestItem.Name}({suggestItem.Sid}) (suggest)"); return suggestItem.Sid; } } @@ -198,7 +198,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers var item = result.Where(x => x.Category == "电视剧" && x.Year == year).FirstOrDefault(); if (item != null && !string.IsNullOrEmpty(item.Sid)) { - this.Log($"GuestDoubanSeasonByYear found -> {item.Name}({item.Sid})"); + this.Log($"Found douban [id]: {item.Name}({item.Sid})"); return item.Sid; } @@ -207,36 +207,30 @@ namespace Jellyfin.Plugin.MetaShark.Providers } - protected async Task GuestByTmdbAsync(ItemLookupInfo info, CancellationToken cancellationToken) + protected async Task GuestByTmdbAsync(string name, int? year, ItemLookupInfo info, CancellationToken cancellationToken) { - // ParseName is required here. - // Caller provides the filename with extension stripped and NOT the parsed filename var fileName = GetNotParsedName(info); - var parseResult = NameParser.Parse(fileName); - var searchName = !string.IsNullOrEmpty(parseResult.ChineseName) ? parseResult.ChineseName : parseResult.Name; - info.Year = parseResult.Year; // 默认parser对anime年份会解析出错,以anitomy为准 - - this.Log($"GuestByTmdb of [name]: {info.Name} [file_name]: {fileName} [year]: {info.Year} [search name]: {searchName}"); + this.Log($"GuestByTmdb of [name]: {name} [year]: {year}"); switch (info) { case MovieInfo: - var movieResults = await this._tmdbApi.SearchMovieAsync(searchName, info.Year ?? 0, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); + var movieResults = await this._tmdbApi.SearchMovieAsync(name, year ?? 0, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); var movieItem = movieResults.FirstOrDefault(); if (movieItem != null) { // bt种子都是英文名,但电影是中日韩泰印法地区时,都不适用相似匹配,去掉限制 - this.Log($"GuestByTmdb found -> {movieItem.Title}({movieItem.Id})"); + this.Log($"Found tmdb [id]: {movieItem.Title}({movieItem.Id})"); return movieItem.Id.ToString(CultureInfo.InvariantCulture); } break; case SeriesInfo: - var seriesResults = await this._tmdbApi.SearchSeriesAsync(searchName, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); + var seriesResults = await this._tmdbApi.SearchSeriesAsync(name, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); var seriesItem = seriesResults.FirstOrDefault(); if (seriesItem != null) { // bt种子都是英文名,但电影是中日韩泰印法地区时,都不适用相似匹配,去掉限制 - this.Log($"GuestByTmdb found -> {seriesItem.Name}({seriesItem.Id})"); + this.Log($"Found tmdb [id]: -> {seriesItem.Name}({seriesItem.Id})"); return seriesItem.Id.ToString(CultureInfo.InvariantCulture); } break; diff --git a/Jellyfin.Plugin.MetaShark/Providers/EpisodeProvider.cs b/Jellyfin.Plugin.MetaShark/Providers/EpisodeProvider.cs index 616fe23..37f9fde 100644 --- a/Jellyfin.Plugin.MetaShark/Providers/EpisodeProvider.cs +++ b/Jellyfin.Plugin.MetaShark/Providers/EpisodeProvider.cs @@ -1,5 +1,4 @@ -using System.Reflection.Metadata; -using Jellyfin.Plugin.MetaShark.Api; +using Jellyfin.Plugin.MetaShark.Api; using Jellyfin.Plugin.MetaShark.Core; using Jellyfin.Plugin.MetaShark.Model; using MediaBrowser.Common.Net; @@ -58,18 +57,19 @@ namespace Jellyfin.Plugin.MetaShark.Providers /// public async Task> GetMetadata(EpisodeInfo info, CancellationToken cancellationToken) { - // 重新识别时,info的IndexNumber和ParentIndexNumber是从文件路径解析出来的,假如命名不规范,就会导致解析出错误值 - // 刷新元数据不覆盖时,IndexNumber和ParentIndexNumber是从当前的元数据获取 - this.Log($"GetEpisodeMetadata of [name]: {info.Name} number: {info.IndexNumber} ParentIndexNumber: {info.ParentIndexNumber}"); + // 刷新元数据四种模式差别: + // 自动扫描匹配:info的Name、IndexNumber和ParentIndexNumber是从文件名解析出来的,假如命名不规范,就会导致解析出错误值 + // 识别:info的Name、IndexNumber和ParentIndexNumber是从文件名解析出来的,provinceIds有指定选择项的ProvinceId + // 搜索缺少的元数据:info的Name、IndexNumber和ParentIndexNumber是从当前的元数据获取,provinceIds保留所有旧值 + // 覆盖所有元数据:info的Name、IndexNumber和ParentIndexNumber是从当前的元数据获取,provinceIds保留所有旧值 + this.Log($"GetEpisodeMetadata of [name]: {info.Name} number: {info.IndexNumber} ParentIndexNumber: {info.ParentIndexNumber} IsAutomated: {info.IsAutomated}"); var result = new MetadataResult(); // 动画特典和extras处理 - var specialEpisode = this.HandleAnimeSpecialAndExtras(info.Path); - if (specialEpisode != null) + var specialResult = this.HandleAnimeSpecialAndExtras(info); + if (specialResult != null) { - result.HasMetadata = true; - result.Item = specialEpisode; - return result; + return specialResult; } // 剧集信息只有tmdb有 @@ -78,8 +78,9 @@ namespace Jellyfin.Plugin.MetaShark.Providers var episodeNumber = info.IndexNumber; var indexNumberEnd = info.IndexNumberEnd; // 修正anime命名格式导致的seasonNumber错误(从season元数据读取) - var parent = _libraryManager.FindByPath(Path.GetDirectoryName(info.Path), true); - if (parent is Season season && seasonNumber != season.IndexNumber) + var episodeItem = _libraryManager.FindByPath(info.Path, false); + var season = episodeItem != null ? ((Episode)episodeItem).Season : null; + if (season != null && seasonNumber != season.IndexNumber) { this.Log("FixSeasionNumber: old: {0} new: {1}", seasonNumber, season.IndexNumber); seasonNumber = season.IndexNumber; @@ -130,17 +131,15 @@ namespace Jellyfin.Plugin.MetaShark.Providers return result; } - // 判断tmdb剧集信息数目和视频是否一致,不一致不处理 - var videoFilesCount = this.GetVideoFileCount(Path.GetDirectoryName(info.Path)); - if (!info.IsAutomated && parent is Season) + // 自动搜索匹配时,判断tmdb剧集信息数目和视频是否一致,不一致不处理 + if (info.IsAutomated) { - // 刷新元数据时,直接从season拿准确的视频数,并排除特典等没有季号的视频 - videoFilesCount = ((Season)parent).GetEpisodes().Where(x => x.ParentIndexNumber == parent.IndexNumber).Count(); - } - if (videoFilesCount > 0 && seasonResult.Episodes.Count != videoFilesCount) - { - this.Log("Tmdb episode number not match. Name: {0} tmdb episode count: {1} video files count: {2}", info.Name, seasonResult.Episodes.Count, videoFilesCount); - return result; + var videoFilesCount = this.GetVideoFileCount(Path.GetDirectoryName(info.Path)); + if (videoFilesCount > 0 && seasonResult.Episodes.Count != videoFilesCount) + { + this.Log("Tmdb episode number not match. Name: {0} tmdb episode count: {1} video files count: {2}", info.Name, seasonResult.Episodes.Count, videoFilesCount); + return result; + } } var episodeResult = seasonResult.Episodes[episodeNumber.Value - 1]; @@ -224,32 +223,47 @@ namespace Jellyfin.Plugin.MetaShark.Providers return guessInfo; } - private Episode? HandleAnimeSpecialAndExtras(string filePath) + private MetadataResult? HandleAnimeSpecialAndExtras(EpisodeInfo info) { - var fileName = Path.GetFileNameWithoutExtension(filePath) ?? string.Empty; + var fileName = Path.GetFileNameWithoutExtension(info.Path) ?? string.Empty; if (NameParser.IsExtra(fileName)) { this.Log($"Found anime extra of [name]: {fileName}"); - return new Episode + var result = new MetadataResult(); + result.HasMetadata = true; + + // 假如已有ParentIndexNumber,设为特典覆盖掉 + if (info.ParentIndexNumber.HasValue) + { + result.Item = new Episode + { + ParentIndexNumber = 0, + IndexNumber = null, + Name = fileName + }; + return result; + } + + // 没ParentIndexNumber时使用文件名 + result.Item = new Episode { Name = fileName }; + return result; } - if (NameParser.IsSpecial(filePath)) + if (NameParser.IsSpecial(info.Path)) { this.Log($"Found anime sp of [name]: {fileName}"); - var guessInfo = this.GuessEpisodeNumber(fileName); - var ep = new Episode + var result = new MetadataResult(); + result.HasMetadata = true; + result.Item = new Episode { ParentIndexNumber = 0, - IndexNumber = guessInfo.episodeNumber, + IndexNumber = null, + Name = fileName }; - if (!string.IsNullOrEmpty(guessInfo.Name)) - { - ep.Name = guessInfo.Name; - } - return ep; + return result; } return null; diff --git a/Jellyfin.Plugin.MetaShark/Providers/MovieProvider.cs b/Jellyfin.Plugin.MetaShark/Providers/MovieProvider.cs index d82dfc2..7bb5af2 100644 --- a/Jellyfin.Plugin.MetaShark/Providers/MovieProvider.cs +++ b/Jellyfin.Plugin.MetaShark/Providers/MovieProvider.cs @@ -146,6 +146,17 @@ namespace Jellyfin.Plugin.MetaShark.Providers } } + // 尝试通过搜索匹配获取tmdbId + if (string.IsNullOrEmpty(tmdbId) && subject.Year > 0) + { + var newTmdbId = await this.GuestByTmdbAsync(subject.Name, subject.Year, info, cancellationToken).ConfigureAwait(false); + if (!string.IsNullOrEmpty(newTmdbId)) + { + tmdbId = newTmdbId; + movie.SetProviderId(MetadataProvider.Tmdb, tmdbId); + } + } + // 通过imdb获取电影系列信息 if (this.config.EnableTmdbCollection && !string.IsNullOrEmpty(tmdbId)) { diff --git a/Jellyfin.Plugin.MetaShark/Providers/SeasonProvider.cs b/Jellyfin.Plugin.MetaShark/Providers/SeasonProvider.cs index a4e5e22..a8a0275 100644 --- a/Jellyfin.Plugin.MetaShark/Providers/SeasonProvider.cs +++ b/Jellyfin.Plugin.MetaShark/Providers/SeasonProvider.cs @@ -53,7 +53,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers 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}"); + this.Log($"GetSeasonMetaData of [name]: {info.Name} number: {info.IndexNumber} seriesTmdbId: {seriesTmdbId} sid: {sid} metaSource: {metaSource} IsAutomated: {info.IsAutomated}"); if (metaSource != MetaSource.Tmdb && !string.IsNullOrEmpty(sid)) { @@ -63,7 +63,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers { return result; } - var seasonName = RemoveSeasonSubfix(series.Name); + var seriesName = RemoveSeasonSubfix(series.Name); // TODO:季文件夹名称不规范,没法拿到seasonNumber,尝试从文件名猜出??? @@ -79,9 +79,9 @@ namespace Jellyfin.Plugin.MetaShark.Providers seasonYear = season?.AirDate?.Year ?? 0; } - if (!string.IsNullOrEmpty(seasonName) && seasonYear > 0) + if (!string.IsNullOrEmpty(seriesName) && seasonYear > 0) { - seasonSid = await this.GuestDoubanSeasonByYearAsync(seasonName, seasonYear, cancellationToken).ConfigureAwait(false); + seasonSid = await this.GuestDoubanSeasonByYearAsync(seriesName, seasonYear, cancellationToken).ConfigureAwait(false); } } diff --git a/Jellyfin.Plugin.MetaShark/Providers/SeriesProvider.cs b/Jellyfin.Plugin.MetaShark/Providers/SeriesProvider.cs index 1be51a4..5797ee8 100644 --- a/Jellyfin.Plugin.MetaShark/Providers/SeriesProvider.cs +++ b/Jellyfin.Plugin.MetaShark/Providers/SeriesProvider.cs @@ -81,7 +81,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers /// public async Task> GetMetadata(SeriesInfo info, CancellationToken cancellationToken) { - this.Log($"GetSeriesMetadata of [name]: {info.Name} [providerIds]: {info.ProviderIds.ToJson()}"); + this.Log($"GetSeriesMetadata of [name]: {info.Name} [providerIds]: {info.ProviderIds.ToJson()} IsAutomated: {info.IsAutomated}"); var result = new MetadataResult(); // 使用刷新元数据时,providerIds会保留旧有值,只有识别/新增才会没值 @@ -95,10 +95,6 @@ namespace Jellyfin.Plugin.MetaShark.Providers { // 自动扫描搜索匹配元数据 sid = await this.GuessByDoubanAsync(info, cancellationToken).ConfigureAwait(false); - // if (string.IsNullOrEmpty(sid)) - // { - // tmdbId = await this.GuestByTmdbAsync(info, cancellationToken).ConfigureAwait(false); - // } } if (metaSource != MetaSource.Tmdb && !string.IsNullOrEmpty(sid)) @@ -111,10 +107,11 @@ namespace Jellyfin.Plugin.MetaShark.Providers } subject.Celebrities = await this._doubanApi.GetCelebritiesBySidAsync(sid, cancellationToken).ConfigureAwait(false); + var seriesName = RemoveSeasonSubfix(subject.Name); var item = new Series { ProviderIds = new Dictionary { { DoubanProviderId, subject.Sid }, { Plugin.ProviderId, MetaSource.Douban } }, - Name = RemoveSeasonSubfix(subject.Name), + Name = seriesName, OriginalTitle = subject.OriginalName, CommunityRating = subject.Rating, Overview = subject.Intro, @@ -142,7 +139,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers // 尝试通过搜索匹配获取tmdbId if (string.IsNullOrEmpty(tmdbId)) { - var newTmdbId = await this.GuestByTmdbAsync(info, cancellationToken).ConfigureAwait(false); + var newTmdbId = await this.GuestByTmdbAsync(seriesName, subject.Year, info, cancellationToken).ConfigureAwait(false); if (!string.IsNullOrEmpty(newTmdbId)) { tmdbId = newTmdbId;