Optimize season & episode metadata fetch

This commit is contained in:
cxfksword 2022-11-01 13:19:47 +08:00
parent be91a0af8a
commit 20f753a826
7 changed files with 111 additions and 31 deletions

View File

@ -175,6 +175,7 @@ namespace Jellyfin.Plugin.MetaShark.Api
movie.Sid = sid;
movie.Name = name;
movie.Genre = cat;
movie.Category = cat;
movie.Img = img;
movie.Rating = rating.ToFloat();
movie.Year = year.ToInt();
@ -230,6 +231,7 @@ namespace Jellyfin.Plugin.MetaShark.Api
var img = contentNode.GetAttr("a.nbgnbg>img", "src") ?? string.Empty;
var intro = contentNode.GetText("div.indent>span") ?? string.Empty;
intro = intro.Replace("©豆瓣", string.Empty);
var category = contentNode.QuerySelector("div.episode_list") == null ? "电影" : "电视剧";
var info = contentNode.GetText("#info") ?? string.Empty;
var director = info.GetMatchGroup(this.regDirector);
@ -254,6 +256,7 @@ namespace Jellyfin.Plugin.MetaShark.Api
movie.Subname = subname;
movie.Director = director;
movie.Genre = genre;
movie.Category = category;
movie.Country = country;
movie.Language = language;
movie.Duration = duration;

View File

@ -31,6 +31,8 @@ namespace Jellyfin.Plugin.MetaShark.Model
public string Actor { get; set; }
// "genre": "奇幻 / 冒险",
public string Genre { get; set; }
// 电影/电视剧
public string Category { get; set; }
// "site": "www.harrypotter.co.uk",
public string Site { get; set; }
// "country": "美国 / 英国",
@ -66,12 +68,18 @@ namespace Jellyfin.Plugin.MetaShark.Model
public List<DoubanCelebrity> Celebrities { get; set; }
[JsonIgnore]
public List<DoubanCelebrity> LimitDirectorCelebrities
{
get
{
// 限制导演最多返回5个
var limitCelebrities = new List<DoubanCelebrity>();
if (Celebrities == null || Celebrities.Count == 0)
{
return limitCelebrities;
}
limitCelebrities.AddRange(Celebrities.Where(x => x.RoleType == MediaBrowser.Model.Entities.PersonType.Director).Take(5));
limitCelebrities.AddRange(Celebrities.Where(x => x.RoleType != MediaBrowser.Model.Entities.PersonType.Director));

View File

@ -11,6 +11,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
@ -19,6 +20,7 @@ using System.Threading.Tasks;
using System.Web;
using TMDbLib.Objects.General;
using Jellyfin.Plugin.MetaShark.Configuration;
using Jellyfin.Plugin.MetaShark.Core;
namespace Jellyfin.Plugin.MetaShark.Providers
{
@ -74,17 +76,26 @@ namespace Jellyfin.Plugin.MetaShark.Providers
this._httpClientFactory = httpClientFactory;
}
protected async Task<string?> GuestByDoubanAsync(ItemLookupInfo info, CancellationToken cancellationToken)
protected async Task<string?> GuessByDoubanAsync(ItemLookupInfo info, CancellationToken cancellationToken)
{
// ParseName is required here.
// Caller provides the filename with extension stripped and NOT the parsed filename
var parsedName = this._libraryManager.ParseName(info.Name);
this.Log($"GuestByDouban of [name]: {info.Name} year: {info.Year} search name: {parsedName.Name}");
this.Log($"GuessByDouban of [name]: {info.Name} year: {info.Year} search name: {parsedName.Name}");
var result = await this._doubanApi.SearchAsync(parsedName.Name, cancellationToken).ConfigureAwait(false);
var jw = new JaroWinkler();
foreach (var item in result)
{
if (info is MovieInfo && item.Category != "电影")
{
continue;
}
if (info is SeriesInfo && item.Category != "电视剧")
{
continue;
}
if (jw.Similarity(parsedName.Name, item.Name) < 0.8)
{
continue;
@ -92,13 +103,13 @@ namespace Jellyfin.Plugin.MetaShark.Providers
if (parsedName.Year == null || parsedName.Year == 0)
{
this.Log($"GuestByDouban of [name] found Sid: \"{item.Sid}\"");
this.Log($"GuessByDouban of [name] found Sid: {item.Sid}");
return item.Sid;
}
if (parsedName.Year == item.Year)
{
this.Log($"GuestByDouban of [name] found Sid: \"{item.Sid}\"");
this.Log($"GuessByDouban of [name] found Sid: {item.Sid}");
return item.Sid;
}
}
@ -106,20 +117,24 @@ namespace Jellyfin.Plugin.MetaShark.Providers
return null;
}
protected async Task<string?> GuestSeasonByDoubanAsync(string name, int? year, CancellationToken cancellationToken)
protected async Task<string?> GuestDoubanSeasonByYearAsync(string name, int? year, CancellationToken cancellationToken)
{
if (year == null || year == 0)
{
return null;
}
this.Log($"GuestSeasonByDouban of [name]: {name} year: {year}");
this.Log($"GuestDoubanSeasonByYear of [name]: {name} year: {year}");
var result = await this._doubanApi.SearchAsync(name, cancellationToken).ConfigureAwait(false);
var jw = new JaroWinkler();
foreach (var item in result)
{
if (item.Category != "电视剧")
{
continue;
}
var score = jw.Similarity(name, item.Name);
this.Log($"GuestSeasonByDouban name: {name} douban_name: {item.Name} douban_sid: {item.Sid} douban_year: {item.Year} score: {score} ");
// this.Log($"GuestDoubanSeasonByYear name: {name} douban_name: {item.Name} douban_sid: {item.Sid} douban_year: {item.Year} score: {score} ");
if (score < 0.8)
{
continue;
@ -127,7 +142,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
if (year == item.Year)
{
this.Log($"GuestSeasonByDouban of [name] found Sid: \"{item.Sid}\"");
this.Log($"GuestDoubanSeasonByYear of [name] found Sid: {item.Sid}");
return item.Sid;
}
}
@ -135,6 +150,45 @@ namespace Jellyfin.Plugin.MetaShark.Providers
return null;
}
protected async Task<string?> GuestDoubanSeasonByNumberAsync(string name, int? seasonNumber, CancellationToken cancellationToken)
{
if (seasonNumber == null || seasonNumber == 0)
{
return null;
}
this.Log($"GuestDoubanSeasonByNumber of [name]: {name} seasonNumber: {seasonNumber}");
var result = await this._doubanApi.SearchAsync(name, cancellationToken).ConfigureAwait(false);
var jw = new JaroWinkler();
var matchList = new List<DoubanSubject>();
foreach (var item in result)
{
if (item.Category != "电视剧")
{
continue;
}
var score = jw.Similarity(name, item.Name);
if (score < 0.8)
{
continue;
}
// this.Log($"GuestDoubanSeasonByNumber name: {name} douban_name: {item.Name} douban_sid: {item.Sid} douban_year: {item.Year} score: {score} ");
matchList.Add(item);
}
matchList.Sort((x, y) => x.Year.CompareTo(y.Year));
if (matchList.Count >= seasonNumber)
{
var matchItem = matchList[seasonNumber.Value - 1];
var sid = matchItem.Sid;
this.Log($"GuestDoubanSeasonByNumber of [name] found Sid: {sid}");
return sid;
}
return null;
}
protected async Task<string?> GuestByTmdbAsync(ItemLookupInfo info, CancellationToken cancellationToken)
{
// ParseName is required here.
@ -173,22 +227,25 @@ namespace Jellyfin.Plugin.MetaShark.Providers
}
protected string AppendMetaSourcePrefix(string name, string source)
protected int GetVideoFileCount(string? dir)
{
if (string.IsNullOrEmpty(name))
if (dir == null)
{
return name;
return 0;
}
return $"[{source}]{name}";
}
protected string RemoveMetaSourcePrefix(string name)
{
if (string.IsNullOrEmpty(name))
var dirInfo = new DirectoryInfo(dir);
var files = dirInfo.GetFiles();
var nameOptions = new Emby.Naming.Common.NamingOptions();
var videoFilesCount = 0;
foreach (var fileInfo in files.Where(f => !f.Attributes.HasFlag(FileAttributes.Hidden)))
{
return name;
if (Emby.Naming.Video.VideoResolver.IsVideoFile(fileInfo.FullName, nameOptions))
{
videoFilesCount++;
}
}
return regMetaSourcePrefix.Replace(name, string.Empty);
return videoFilesCount;
}

View File

@ -18,6 +18,7 @@ using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
namespace Jellyfin.Plugin.MetaShark.Providers
{
@ -65,7 +66,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
var indexNumberEnd = info.IndexNumberEnd;
// 修正anime命名格式导致的seasonNumber错误从season元数据读取)
var parent = _libraryManager.FindByPath(Path.GetDirectoryName(info.Path), true);
if (parent is Season season)
if (parent is Season season && seasonNumber != season.IndexNumber)
{
this.Log("FixSeasionNumber: old: {0} new: {1}", seasonNumber, season.IndexNumber);
seasonNumber = season.IndexNumber;
@ -73,6 +74,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
// 没有season级目录或目录不命名不规范时会为null
if (seasonNumber is null or 0)
{
this.Log("FixSeasionNumber: season number is null, set to default 1");
seasonNumber = 1;
}
// 修正anime命名格式导致的episodeNumber错误
@ -83,7 +85,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
{
seasonNumber = guessInfo.seasonNumber.Value;
}
if (guessInfo.episodeNumber.HasValue && guessInfo.episodeNumber != episodeNumber)
if (guessInfo.episodeNumber.HasValue)
{
episodeNumber = guessInfo.episodeNumber;
@ -95,8 +97,6 @@ namespace Jellyfin.Plugin.MetaShark.Providers
};
}
if (episodeNumber is null or 0 || seasonNumber is null or 0 || string.IsNullOrEmpty(seriesTmdbId))
{
this.Log("Lack meta message. episodeNumber: {0} seasonNumber: {1} seriesTmdbId:{2}", episodeNumber, seasonNumber, seriesTmdbId);
@ -112,6 +112,12 @@ namespace Jellyfin.Plugin.MetaShark.Providers
this.Log("Cant found episode data from tmdb. Name: {0} seriesTmdbId: {1} seasonNumber: {2} episodeNumber: {3}", info.Name, seriesTmdbId, seasonNumber, episodeNumber);
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];
@ -136,7 +142,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
item.ProductionYear = episodeResult.AirDate?.Year;
item.Name = episodeResult.Name;
item.Overview = episodeResult.Overview;
item.CommunityRating = Convert.ToSingle(episodeResult.VoteAverage);
item.CommunityRating = (float)System.Math.Round(episodeResult.VoteAverage, 1);
result.Item = item;

View File

@ -65,7 +65,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
// 从tmdb搜索
if (Plugin.Instance?.Configuration.EnableTmdbSearch ?? false)
if (this.config.EnableTmdbSearch)
{
var tmdbList = await _tmdbApi.SearchMovieAsync(info.Name, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
result.AddRange(tmdbList.Take(this.config.MaxSearchResult).Select(x =>
@ -97,7 +97,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
if (string.IsNullOrEmpty(sid) && string.IsNullOrEmpty(tmdbId))
{
// 刷新元数据匹配搜索
sid = await this.GuestByDoubanAsync(info, cancellationToken).ConfigureAwait(false);
sid = await this.GuessByDoubanAsync(info, cancellationToken).ConfigureAwait(false);
if (string.IsNullOrEmpty(sid))
{
tmdbId = await this.GuestByTmdbAsync(info, cancellationToken).ConfigureAwait(false);
@ -187,7 +187,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
movie.SetProviderId(MetadataProvider.Imdb, movieResult.ImdbId);
movie.SetProviderId(Plugin.ProviderId, MetaSource.Tmdb);
movie.CommunityRating = Convert.ToSingle(movieResult.VoteAverage);
movie.CommunityRating = (float)System.Math.Round(movieResult.VoteAverage, 2);
movie.PremiereDate = movieResult.ReleaseDate;
movie.ProductionYear = movieResult.ReleaseDate?.Year;

View File

@ -78,10 +78,16 @@ namespace Jellyfin.Plugin.MetaShark.Providers
if (!string.IsNullOrEmpty(seriesName) && seasonYear > 0)
{
seasonSid = await this.GuestSeasonByDoubanAsync(seriesName, seasonYear, cancellationToken).ConfigureAwait(false);
seasonSid = await this.GuestDoubanSeasonByYearAsync(seriesName, seasonYear, cancellationToken).ConfigureAwait(false);
}
}
// 尝试通过豆瓣按年份排序后,按季数索引取对应一个
if (string.IsNullOrEmpty(seasonSid) && !string.IsNullOrEmpty(seriesName) && seasonNumber.HasValue)
{
seasonSid = await this.GuestDoubanSeasonByNumberAsync(seriesName, seasonNumber, cancellationToken).ConfigureAwait(false);
}
// 获取季豆瓣数据
if (!string.IsNullOrEmpty(seasonSid))
{

View File

@ -57,7 +57,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
}));
// 尝试从tmdb搜索
if (Plugin.Instance?.Configuration.EnableTmdbSearch ?? false)
if (this.config.EnableTmdbSearch)
{
var tmdbList = await this._tmdbApi.SearchSeriesAsync(info.Name, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
result.AddRange(tmdbList.Take(this.config.MaxSearchResult).Select(x =>
@ -89,7 +89,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
if (string.IsNullOrEmpty(sid) && string.IsNullOrEmpty(tmdbId))
{
// 刷新元数据自动匹配搜索
sid = await this.GuestByDoubanAsync(info, cancellationToken).ConfigureAwait(false);
sid = await this.GuessByDoubanAsync(info, cancellationToken).ConfigureAwait(false);
if (string.IsNullOrEmpty(sid))
{
tmdbId = await this.GuestByTmdbAsync(info, cancellationToken).ConfigureAwait(false);
@ -209,7 +209,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
series.SetProviderId(MetadataProvider.Tmdb, seriesResult.Id.ToString(CultureInfo.InvariantCulture));
series.CommunityRating = Convert.ToSingle(seriesResult.VoteAverage);
series.CommunityRating = (float)System.Math.Round(seriesResult.VoteAverage, 2);
series.Overview = seriesResult.Overview;