Optimize identity

This commit is contained in:
cxfksword 2023-02-22 21:02:02 +08:00
parent 1cb72f488a
commit a754686a5a
12 changed files with 217 additions and 131 deletions

View File

@ -48,6 +48,12 @@ namespace Jellyfin.Plugin.MetaShark.Test
Assert.AreEqual(parseResult.Name, "Roman Holiday");
Assert.AreEqual(parseResult.Year, 1953);
fileName = "【更多蓝光电影访问】红辣椒[简繁中文字幕].Paprika.2006.RERiP.1080p.BluRay.x264.DTS-WiKi";
parseResult = NameParser.Parse(fileName);
Assert.AreEqual(parseResult.ChineseName, "红辣椒");
Assert.AreEqual(parseResult.Name, "Paprika");
Assert.AreEqual(parseResult.Year, 2006);
// 只英文
fileName = "A.Chinese.Odyssey.Part.1.1995.BluRay.1080p.x265.10bit.2Audio-MiniHD";
parseResult = NameParser.Parse(fileName);
@ -170,6 +176,13 @@ namespace Jellyfin.Plugin.MetaShark.Test
Assert.AreEqual(parseResult.ParentIndexNumber, 1);
Assert.AreEqual(parseResult.IndexNumber, 1);
// 只中文
fileName = "齊天大聖 第02集";
parseResult = NameParser.Parse(fileName);
Assert.AreEqual(parseResult.Name, "齊天大聖 第02集");
Assert.AreEqual(parseResult.ParentIndexNumber, null);
Assert.AreEqual(parseResult.IndexNumber, 2);
// anime
fileName = "[YYDM-11FANS][THERMAE_ROMAE][02][BDRIP][720P][X264-10bit_AAC][7FF2269F]";
parseResult = NameParser.Parse(fileName);

View File

@ -63,23 +63,43 @@ namespace Jellyfin.Plugin.MetaShark.Test
var httpContextAccessorStub = new Mock<IHttpContextAccessor>();
var provider = new SeasonProvider(httpClientFactory, loggerFactory, libraryManagerStub.Object, httpContextAccessorStub.Object, doubanApi, tmdbApi, omdbApi);
var result = provider.GuessSeasonNumberByFileName("/data/downloads/jellyfin/tv/向往的生活/第2季");
var result = provider.GuessSeasonNumberByDirectoryName("/data/downloads/jellyfin/tv/向往的生活/第2季");
Assert.AreEqual(result, 2);
result = provider.GuessSeasonNumberByFileName("/data/downloads/jellyfin/tv/向往的生活 第2季");
result = provider.GuessSeasonNumberByDirectoryName("/data/downloads/jellyfin/tv/向往的生活 第2季");
Assert.AreEqual(result, 2);
result = provider.GuessSeasonNumberByFileName("/data/downloads/jellyfin/tv/向往的生活/第三季");
result = provider.GuessSeasonNumberByDirectoryName("/data/downloads/jellyfin/tv/向往的生活/第三季");
Assert.AreEqual(result, 3);
result = provider.GuessSeasonNumberByFileName("/data/downloads/jellyfin/tv/攻壳机动队Ghost_in_The_Shell_S.A.C._2nd_GIG");
result = provider.GuessSeasonNumberByDirectoryName("/data/downloads/jellyfin/tv/攻壳机动队Ghost_in_The_Shell_S.A.C._2nd_GIG");
Assert.AreEqual(result, 2);
result = provider.GuessSeasonNumberByFileName("/data/downloads/jellyfin/tv/Spice and Wolf/Spice and Wolf 2");
result = provider.GuessSeasonNumberByDirectoryName("/data/downloads/jellyfin/tv/Spice and Wolf/Spice and Wolf 2");
Assert.AreEqual(result, 2);
result = provider.GuessSeasonNumberByFileName("/data/downloads/jellyfin/tv/Spice and Wolf/Spice and Wolf 2 test");
result = provider.GuessSeasonNumberByDirectoryName("/data/downloads/jellyfin/tv/Spice and Wolf/Spice and Wolf 2 test");
Assert.AreEqual(result, null);
result = provider.GuessSeasonNumberByDirectoryName("/data/downloads/jellyfin/tv/[BDrip] Made in Abyss S02 [7鲁ACG x Sakurato]");
Assert.AreEqual(result, 2);
}
[TestMethod]
public void TestGuestDoubanSeasonByYearAsync()
{
var doubanApi = new DoubanApi(loggerFactory);
var tmdbApi = new TmdbApi(loggerFactory);
var omdbApi = new OmdbApi(loggerFactory);
var httpClientFactory = new DefaultHttpClientFactory();
var libraryManagerStub = new Mock<ILibraryManager>();
var httpContextAccessorStub = new Mock<IHttpContextAccessor>();
Task.Run(async () =>
{
var provider = new SeasonProvider(httpClientFactory, loggerFactory, libraryManagerStub.Object, httpContextAccessorStub.Object, doubanApi, tmdbApi, omdbApi);
var result = await provider.GuestDoubanSeasonByYearAsync("机动战士高达0083 星尘的回忆", 1991, CancellationToken.None);
Assert.AreEqual(result, "1766564");
}).GetAwaiter().GetResult();
}
}

View File

@ -50,7 +50,7 @@ namespace Jellyfin.Plugin.MetaShark.Api
Regex regId = new Regex(@"/(\d+?)/", RegexOptions.Compiled);
Regex regSid = new Regex(@"sid: (\d+?),", RegexOptions.Compiled);
Regex regCat = new Regex(@"\[(.+?)\]", RegexOptions.Compiled);
Regex regYear = new Regex(@"(\d{4})", RegexOptions.Compiled);
Regex regYear = new Regex(@"([12][890][0-9][0-9])", RegexOptions.Compiled);
Regex regTitle = new Regex(@"<title>([\w\W]+?)</title>", RegexOptions.Compiled);
Regex regKeywordMeta = new Regex(@"<meta name=""keywords"" content=""(.+?)""", RegexOptions.Compiled);
Regex regOriginalName = new Regex(@"原名[:](.+?)\s*?\/", RegexOptions.Compiled);

View File

@ -25,6 +25,9 @@ public enum SomeOptions
/// </summary>
public class PluginConfiguration : BasePluginConfiguration
{
public const int MAX_CAST_MEMBERS = 15;
public const int MAX_SEARCH_RESULT = 5;
public string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? string.Empty;
public string DoubanCookies { get; set; } = string.Empty;
@ -55,7 +58,7 @@ public class PluginConfiguration : BasePluginConfiguration
public int MaxCastMembers { get; set; } = 15;
public int MaxSearchResult { get; set; } = 3;
public int MaxSearchResult { get; set; } = 5;

View File

@ -20,6 +20,8 @@ namespace Jellyfin.Plugin.MetaShark.Core
private static readonly Regex startWithHyphenCharReg = new Regex(@"^[-~]", RegexOptions.Compiled);
private static readonly Regex chineseIndexNumberReg = new Regex(@"第([0-9零一二三四五六七八九]+?)(集|章|话|話)", RegexOptions.Compiled);
public static ParseNameResult Parse(string fileName, bool isTvSeries = false)
{
var parseResult = new ParseNameResult();
@ -108,6 +110,12 @@ namespace Jellyfin.Plugin.MetaShark.Core
}
}
// 修复纯中文集数
if (parseResult.IndexNumber is null)
{
parseResult.IndexNumber = ParseChineseIndexNumber(fileName);
}
// 解析不到title时使用默认名
if (string.IsNullOrEmpty(parseResult.Name))
{
@ -159,21 +167,36 @@ namespace Jellyfin.Plugin.MetaShark.Core
return 0;
}
private static int? ParseChineseIndexNumber(string fileName)
{
var match = chineseIndexNumberReg.Match(fileName);
if (match.Success && match.Groups.Count > 1)
{
if (int.TryParse(match.Groups[1].Value, out var seasonNumber))
{
return seasonNumber;
}
var number = Utils.ChineseNumberToInt(match.Groups[1].Value);
if (number.HasValue)
{
return number;
}
}
return null;
}
public static bool IsSpecialDirectory(string path)
{
var fileName = Path.GetFileNameWithoutExtension(path) ?? string.Empty;
if (IsAnime(fileName))
if (IsAnime(fileName) && fileName.Contains("[SP]"))
{
if (fileName.Contains("[SP]"))
{
return true;
}
string folder = Path.GetFileName(Path.GetDirectoryName(path)) ?? string.Empty;
return folder == "SPs";
return true;
}
return false;
var folder = Path.GetFileName(Path.GetDirectoryName(path)) ?? string.Empty;
return folder == "SPs" || folder.Contains("特典");
}

View File

@ -21,20 +21,29 @@ namespace Jellyfin.Plugin.MetaShark.Core
/// </summary>
public static int? ChineseNumberToInt(string str)
{
switch (str)
if (string.IsNullOrEmpty(str)) return null;
var chineseNumberMap = new Dictionary<Char, Char>() {
{'一', '1'},
{'二', '2'},
{'三', '3'},
{'四', '4'},
{'五', '5'},
{'六', '6'},
{'七', '7'},
{'八', '8'},
{'九', '9'},
{'零', '0'},
};
var numberArr = str.ToCharArray().Select(x => chineseNumberMap.ContainsKey(x) ? chineseNumberMap[x] : x).ToArray();
var newNumberStr = new string(numberArr);
if (int.TryParse(new string(numberArr), out var number))
{
case "一": return 1;
case "二": return 2;
case "三": return 3;
case "四": return 4;
case "五": return 5;
case "六": return 6;
case "七": return 7;
case "八": return 8;
case "九": return 9;
case "零": return 0;
default: return null;
return number;
}
return null;
}
}
}

View File

@ -171,7 +171,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
return null;
}
protected async Task<string?> GuestDoubanSeasonByYearAsync(string name, int? year, CancellationToken cancellationToken)
public async Task<string?> GuestDoubanSeasonByYearAsync(string name, int? year, CancellationToken cancellationToken)
{
if (year == null || year == 0)
{
@ -280,6 +280,71 @@ namespace Jellyfin.Plugin.MetaShark.Providers
}
public int? GuessSeasonNumberByDirectoryName(string path)
{
// TODO: 有时series name中会带有季信息
// 当没有season级目录时path为空直接返回
if (string.IsNullOrEmpty(path))
{
return null;
}
var fileName = Path.GetFileName(path);
if (string.IsNullOrEmpty(fileName))
{
return null;
}
var regSeason = new Regex(@"第([0-9零一二三四五六七八九]+?)(季|部)", RegexOptions.Compiled);
var match = regSeason.Match(fileName);
if (match.Success && match.Groups.Count > 1)
{
var seasonNumber = match.Groups[1].Value.ToInt();
if (seasonNumber <= 0)
{
seasonNumber = Utils.ChineseNumberToInt(match.Groups[1].Value) ?? 0;
}
if (seasonNumber > 0)
{
this.Log($"Found season number of filename: {fileName} seasonNumber: {seasonNumber}");
return seasonNumber;
}
}
var seasonNameMap = new Dictionary<string, int>() {
{@"[ ._](I|1st|S01|S1)[ ._]", 1},
{@"[ ._](II|2nd|S02|S2)[ ._]", 2},
{@"[ ._](III|3rd|S03|S3)[ ._]", 3},
{@"[ ._](IIII|4th|S04|S4)[ ._]", 3},
};
foreach (var entry in seasonNameMap)
{
if (Regex.IsMatch(fileName, entry.Key))
{
this.Log($"Found season number of filename: {fileName} seasonNumber: {entry.Value}");
return entry.Value;
}
}
// // 带数字末尾的
// match = Regex.Match(fileName, @"[ ._](\d{1,2})$");
// if (match.Success && match.Groups.Count > 1)
// {
// var seasonNumber = match.Groups[1].Value.ToInt();
// if (seasonNumber > 0)
// {
// this.Log($"Found season number of filename: {fileName} seasonNumber: {seasonNumber}");
// return seasonNumber;
// }
// }
return null;
}
/// <summary>
/// 浏览器来源请求返回代理地址no-referer对于background-image不生效其他客户端请求返回原始图片地址
/// </summary>

View File

@ -56,7 +56,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
var result = new MetadataResult<Episode>();
// 动画特典和extras处理
var specialResult = this.HandleAnimeSpecialAndExtras(info);
var specialResult = this.HandleAnimeExtras(info);
if (specialResult != null)
{
return specialResult;
@ -94,15 +94,15 @@ namespace Jellyfin.Plugin.MetaShark.Providers
}
// TODO自动搜索匹配或识别时判断tmdb剧集信息数目和视频是否一致不一致不处理现在通过IsAutomated判断不太准确
if (info.IsAutomated)
{
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;
}
}
// if (info.IsAutomated)
// {
// 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];
@ -133,6 +133,10 @@ namespace Jellyfin.Plugin.MetaShark.Providers
return result;
}
/// <summary>
/// 重新解析文件名
/// 注意这里修改替换ParentIndexNumber值后会重新触发SeasonProvier的GetMetadata方法并带上最新的季数IndexNumber
/// </summary>
public EpisodeInfo FixParseInfo(EpisodeInfo info)
{
// 使用AnitomySharp进行重新解析解决anime识别错误
@ -152,7 +156,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
{
var episodeItem = _libraryManager.FindByPath(info.Path, false);
var season = episodeItem != null ? ((Episode)episodeItem).Season : null;
if (season != null && info.ParentIndexNumber != season.IndexNumber)
if (season != null && season.IndexNumber.HasValue && info.ParentIndexNumber != season.IndexNumber)
{
this.Log("FixSeasonNumber: old: {0} new: {1}", info.ParentIndexNumber, season.IndexNumber);
info.ParentIndexNumber = season.IndexNumber;
@ -167,6 +171,20 @@ namespace Jellyfin.Plugin.MetaShark.Providers
// }
}
// 从series文件夹名称猜出season number 没有季文件夹的在SeasonProvider处理不了因为info.Path会为空只能在这里处理
var seasonFolderPath = Path.GetDirectoryName(info.Path);
if (info.ParentIndexNumber is null && seasonFolderPath != null)
{
info.ParentIndexNumber = this.GuessSeasonNumberByDirectoryName(seasonFolderPath);
}
// 识别特典
if (info.ParentIndexNumber is null && NameParser.IsAnime(fileName) && (parseResult.IsSpecial || NameParser.IsSpecialDirectory(info.Path)))
{
info.ParentIndexNumber = 0;
}
// 设为默认季数为1
if (info.ParentIndexNumber is null)
{
@ -174,13 +192,8 @@ namespace Jellyfin.Plugin.MetaShark.Providers
info.ParentIndexNumber = 1;
}
// 特典
if (NameParser.IsAnime(fileName) && parseResult.IsSpecial)
{
info.ParentIndexNumber = 0;
}
// 特典优先使用文件名
// 特典优先使用文件名特典除了前面特别设置还有SXX/Season XX等默认的
if (info.ParentIndexNumber.HasValue && info.ParentIndexNumber == 0)
{
info.Name = parseResult.SpecialName == info.Name ? fileName : parseResult.SpecialName;
@ -198,7 +211,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
}
private MetadataResult<Episode>? HandleAnimeSpecialAndExtras(EpisodeInfo info)
private MetadataResult<Episode>? HandleAnimeExtras(EpisodeInfo info)
{
// 特典或extra视频可能和正片剧集放在同一目录
var fileName = Path.GetFileNameWithoutExtension(info.Path) ?? info.Name;
@ -229,20 +242,21 @@ namespace Jellyfin.Plugin.MetaShark.Providers
return result;
}
if (parseResult.IsSpecial || NameParser.IsSpecialDirectory(info.Path))
{
this.Log($"Found anime sp of [name]: {fileName}");
var result = new MetadataResult<Episode>();
result.HasMetadata = true;
result.Item = new Episode
{
ParentIndexNumber = 0,
IndexNumber = parseResult.IndexNumber,
Name = parseResult.SpecialName == info.Name ? fileName : parseResult.SpecialName,
};
//// 特典也有剧集信息,不在这里处理
// if (parseResult.IsSpecial || NameParser.IsSpecialDirectory(info.Path))
// {
// this.Log($"Found anime sp of [name]: {fileName}");
// var result = new MetadataResult<Episode>();
// result.HasMetadata = true;
// result.Item = new Episode
// {
// ParentIndexNumber = 0,
// IndexNumber = parseResult.IndexNumber,
// Name = parseResult.SpecialName == info.Name ? fileName : parseResult.SpecialName,
// };
return result;
}
// return result;
// }
return null;
}

View File

@ -52,7 +52,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
// 从douban搜索
var res = await this._doubanApi.SearchAsync(info.Name, cancellationToken).ConfigureAwait(false);
result.AddRange(res.Take(this.config.MaxSearchResult).Select(x =>
result.AddRange(res.Take(Configuration.PluginConfiguration.MAX_SEARCH_RESULT).Select(x =>
{
return new RemoteSearchResult
{
@ -69,7 +69,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
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 =>
result.AddRange(tmdbList.Take(Configuration.PluginConfiguration.MAX_SEARCH_RESULT).Select(x =>
{
return new RemoteSearchResult
{

View File

@ -69,7 +69,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
var res = await this._doubanApi.SearchCelebrityAsync(searchInfo.Name, cancellationToken).ConfigureAwait(false);
result.AddRange(res.Take(this.config.MaxSearchResult).Select(x =>
result.AddRange(res.Take(Configuration.PluginConfiguration.MAX_SEARCH_RESULT).Select(x =>
{
return new RemoteSearchResult
{
@ -84,7 +84,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
}
/// <inheritdoc />
public async Task<MetadataResult<Person>?> GetMetadata(PersonLookupInfo info, CancellationToken cancellationToken)
public async Task<MetadataResult<Person>> GetMetadata(PersonLookupInfo info, CancellationToken cancellationToken)
{
var result = new MetadataResult<Person>();

View File

@ -56,10 +56,11 @@ namespace Jellyfin.Plugin.MetaShark.Providers
if (metaSource != MetaSource.Tmdb && !string.IsNullOrEmpty(sid))
{
// 季文件夹名称不规范没法拿到seasonNumber尝试从文件名猜出
// 季文件夹名称不规范没法拿到seasonNumber尝试从文件夹名猜出
// 注意:本办法没法处理没有季文件夹的/虚拟季因为path会为空
if (seasonNumber is null)
{
seasonNumber = this.GuessSeasonNumberByFileName(info.Path);
seasonNumber = this.GuessSeasonNumberByDirectoryName(info.Path);
}
// 搜索豆瓣季id
@ -130,68 +131,6 @@ namespace Jellyfin.Plugin.MetaShark.Providers
return await this.GetMetadataByTmdb(info, seriesTmdbId, seasonNumber, cancellationToken).ConfigureAwait(false);
}
public int? GuessSeasonNumberByFileName(string path)
{
// 当没有season级目录时path为空直接返回
if (string.IsNullOrEmpty(path))
{
return null;
}
// TODO: 有时series name中会带有季信息
var fileName = Path.GetFileName(path);
if (string.IsNullOrEmpty(fileName))
{
return null;
}
var regSeason = new Regex(@"第(.)(季|部)", RegexOptions.Compiled);
var match = regSeason.Match(fileName);
if (match.Success && match.Groups.Count > 1)
{
var seasonNumber = match.Groups[1].Value.ToInt();
if (seasonNumber <= 0)
{
seasonNumber = Utils.ChineseNumberToInt(match.Groups[1].Value) ?? 0;
}
if (seasonNumber > 0)
{
this.Log($"Found season number of filename: {fileName} seasonNumber: {seasonNumber}");
return seasonNumber;
}
}
var seasonNameMap = new Dictionary<string, int>() {
{@"[ ._](I|1st)[ ._]", 1},
{@"[ ._](II|2nd)[ ._]", 2},
{@"[ ._](III|3rd)[ ._]", 3},
{@"[ ._](IIII|4th)[ ._]", 3},
};
foreach (var entry in seasonNameMap)
{
if (Regex.IsMatch(fileName, entry.Key))
{
this.Log($"Found season number of filename: {fileName} seasonNumber: {entry.Value}");
return entry.Value;
}
}
// 带数字末尾的
match = Regex.Match(fileName, @"[ ._](\d{1,2})$");
if (match.Success && match.Groups.Count > 1)
{
var seasonNumber = match.Groups[1].Value.ToInt();
if (seasonNumber > 0)
{
this.Log($"Found season number of filename: {fileName} seasonNumber: {seasonNumber}");
return seasonNumber;
}
}
return null;
}
public async Task<string?> GuessDoubanSeasonId(string? sid, string? seriesTmdbId, int? seasonNumber, ItemLookupInfo info, CancellationToken cancellationToken)
{

View File

@ -45,7 +45,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
// 从douban搜索
var res = await this._doubanApi.SearchAsync(info.Name, cancellationToken).ConfigureAwait(false);
result.AddRange(res.Take(this.config.MaxSearchResult).Select(x =>
result.AddRange(res.Take(Configuration.PluginConfiguration.MAX_SEARCH_RESULT).Select(x =>
{
return new RemoteSearchResult
{
@ -61,7 +61,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
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 =>
result.AddRange(tmdbList.Take(Configuration.PluginConfiguration.MAX_SEARCH_RESULT).Select(x =>
{
return new RemoteSearchResult
{