Optimize season & episode metadata fetch
This commit is contained in:
parent
be91a0af8a
commit
20f753a826
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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("Can‘t 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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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))
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue