Compare commits

..

11 Commits

Author SHA1 Message Date
cwhzy 2f44ddf6ec
update README.md.
Signed-off-by: cwhzy <cwhzy@foxmail.com>
2024-07-01 04:51:16 +00:00
cxfksword f337406ac9 tweak: optimize identify 2024-06-07 21:45:01 +08:00
cxfksword cb00027b77 fix: episode number not correctly identified. #82 2024-05-25 16:00:19 +08:00
cxfksword 9e9074bb5a build: update github action 2024-05-22 20:38:52 +08:00
cxfksword 747e69f3bf tweak: read attribute from season folder name 2024-05-18 16:12:52 +08:00
cxfksword 104d200e7b fix: actor lack of overview. close #80 2024-05-18 16:04:05 +08:00
cxfksword 8ded89422f
Merge pull request #78 from cxfksword/10.9/preview
tweak: update generate manifest version
2024-05-12 18:04:12 +08:00
cxfksword 25a844a2c4 tweak: update generate manifest version 2024-05-12 18:03:34 +08:00
cxfksword f1f35a6d32
Merge pull request #77 from cxfksword/10.9/preview
Compatible with Jellyfin 10.9
2024-05-12 18:00:02 +08:00
cxfksword 96be3222f9 feat: support path name attribute. close #75 2024-05-12 17:57:07 +08:00
cxfksword 1b547f7aaf Compatible with Jellyfin 10.9. #76 2024-05-12 17:15:46 +08:00
16 changed files with 185 additions and 115 deletions

View File

@ -4,7 +4,7 @@ on:
workflow_dispatch:
env:
dotnet-version: 6.0.x
dotnet-version: 8.0.x
python-version: 3.8
project: Jellyfin.Plugin.MetaShark/Jellyfin.Plugin.MetaShark.csproj
artifact: metashark
@ -36,9 +36,9 @@ jobs:
- name: Build
run: |
dotnet restore ${{ env.project }} --no-cache
dotnet publish --nologo --no-restore --configuration=Release --framework=net6.0 ${{ env.project }}
dotnet publish --nologo --no-restore --configuration=Release --framework=net8.0 ${{ env.project }}
mkdir -p artifacts
cp ./Jellyfin.Plugin.MetaShark/bin/Release/net6.0/Jellyfin.Plugin.MetaShark.dll ./artifacts/
cp ./Jellyfin.Plugin.MetaShark/bin/Release/net8.0/Jellyfin.Plugin.MetaShark.dll ./artifacts/
- name: Upload artifact
uses: actions/upload-artifact@v3
with:

View File

@ -14,7 +14,7 @@ jobs:
- uses: actions/setup-dotnet@v3
id: dotnet
with:
dotnet-version: 6.0.x
dotnet-version: 8.0.x
- name: Change default dotnet version
run: |
echo '{"sdk":{"version": "${{ steps.dotnet.outputs.dotnet-version }}"}}' > ./global.json

View File

@ -5,7 +5,7 @@ on:
tags: ["*"]
env:
dotnet-version: 6.0.x
dotnet-version: 8.0.x
python-version: 3.8
project: Jellyfin.Plugin.MetaShark/Jellyfin.Plugin.MetaShark.csproj
artifact: metashark
@ -41,9 +41,9 @@ jobs:
- name: Build
run: |
dotnet restore ${{ env.project }} --no-cache
dotnet publish --nologo --no-restore --configuration=Release --framework=net6.0 -p:Version=${{steps.vars.outputs.VERSION}} ${{ env.project }}
dotnet publish --nologo --no-restore --configuration=Release --framework=net8.0 -p:Version=${{steps.vars.outputs.VERSION}} ${{ env.project }}
mkdir -p artifacts
zip -j ./artifacts/${{ env.artifact }}_${{steps.vars.outputs.VERSION}}.zip ./Jellyfin.Plugin.MetaShark/bin/Release/net6.0/Jellyfin.Plugin.MetaShark.dll
zip -j ./artifacts/${{ env.artifact }}_${{steps.vars.outputs.VERSION}}.zip ./Jellyfin.Plugin.MetaShark/bin/Release/net8.0/Jellyfin.Plugin.MetaShark.dll
- name: Generate manifest
run: python3 ./scripts/generate_manifest.py ./artifacts/${{ env.artifact }}_${{steps.vars.outputs.VERSION}}.zip ${GITHUB_REF#refs/*/}
env:
@ -54,7 +54,7 @@ jobs:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ./artifacts/${{ env.artifact }}_*.zip
tag: ${{ github.ref }}
release_name: '${{ github.ref_name }}: Jellyfin v10.8'
release_name: '${{ github.ref_name }}: Jellyfin v10.9'
file_glob: true
overwrite: true
- name: Publish manifest

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageId>AnitomySharp.NET6</PackageId>
<PackageVersion>0.4.0</PackageVersion>

View File

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>

View File

@ -64,7 +64,11 @@ namespace Jellyfin.Plugin.MetaShark.Test
var imdbApi = new ImdbApi(loggerFactory);
var provider = new SeasonProvider(httpClientFactory, loggerFactory, libraryManagerStub.Object, httpContextAccessorStub.Object, doubanApi, tmdbApi, omdbApi, imdbApi);
var result = provider.GuessSeasonNumberByDirectoryName("/data/downloads/jellyfin/tv/向往的生活/第2季");
var result = provider.GuessSeasonNumberByDirectoryName("/data/downloads/jellyfin/tv/冰与火之歌S01-S08.Game.of.Thrones.1080p.Blu-ray.x265.10bit.AC3/冰与火之歌S2.列王的纷争.2012.1080p.Blu-ray.x265.10bit.AC3");
Assert.AreEqual(result, 2);
result = provider.GuessSeasonNumberByDirectoryName("/data/downloads/jellyfin/tv/向往的生活/第2季");
Assert.AreEqual(result, 2);
result = provider.GuessSeasonNumberByDirectoryName("/data/downloads/jellyfin/tv/向往的生活 第2季");

View File

@ -246,15 +246,21 @@ namespace Jellyfin.Plugin.MetaShark.Core
return null;
}
public static bool IsSpecialDirectory(string path)
public static bool IsSpecialDirectory(string path, bool isDirectory = false)
{
var folder = Path.GetFileName(Path.GetDirectoryName(path))?.ToUpper() ?? string.Empty;
return folder == "SPS" || folder == "SPECIALS" || folder.Contains("特典");
if (isDirectory) {
folder = Path.GetFileName(path)?.ToUpper() ?? string.Empty;
}
return folder == "SP" || folder == "SPS" || folder == "SPECIALS" || folder.Contains("特典");
}
public static bool IsExtraDirectory(string path)
public static bool IsExtraDirectory(string path, bool isDirectory = false)
{
var folder = Path.GetFileName(Path.GetDirectoryName(path))?.ToUpper() ?? string.Empty;
if (isDirectory) {
folder = Path.GetFileName(path)?.ToUpper() ?? string.Empty;
}
return folder == "EXTRA"
|| folder == "MENU"
|| folder == "MENUS"

View File

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>Jellyfin.Plugin.MetaShark</RootNamespace>
<GenerateDocumentationFile>False</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
@ -21,9 +21,12 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="AngleSharp" Version="1.0.1" />
<PackageReference Include="ILRepack.Lib.MSBuild.Minor" Version="2.1.19-alpha.2" />
<PackageReference Include="Jellyfin.Controller" Version="10.8.0" />
<PackageReference Include="Jellyfin.Model" Version="10.8.0" />
<PackageReference Include="ILRepack.Lib.MSBuild.Task" Version="2.0.32">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Jellyfin.Controller" Version="10.9.0" />
<PackageReference Include="Jellyfin.Model" Version="10.9.0" />
<PackageReference Include="RateLimiter" Version="2.2.0" />
<PackageReference Include="TMDbLib" Version="2.2.0" />
</ItemGroup>

View File

@ -19,6 +19,7 @@ using TMDbLib.Objects.General;
using Jellyfin.Plugin.MetaShark.Configuration;
using Jellyfin.Plugin.MetaShark.Core;
using Microsoft.AspNetCore.Http;
using MediaBrowser.Controller.Entities.TV;
namespace Jellyfin.Plugin.MetaShark.Providers
{
@ -50,6 +51,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
protected Regex regMetaSourcePrefix = new Regex(@"^\[.+\]", RegexOptions.Compiled);
protected Regex regSeasonNameSuffix = new Regex(@"\s第[0-9一二三四五六七八九十]+?季$|\sSeason\s\d+?$|(?<![0-9a-zA-Z])\d$", RegexOptions.Compiled);
protected Regex regDoubanIdAttribute = new Regex(@"\[(?:douban|doubanid)-(\d+?)\]", RegexOptions.Compiled);
protected PluginConfiguration config
{
@ -107,11 +109,17 @@ namespace Jellyfin.Plugin.MetaShark.Providers
protected async Task<string?> GuessByDoubanAsync(ItemLookupInfo info, CancellationToken cancellationToken)
{
var fileName = GetOriginalFileName(info);
// 从文件名属性格式获取,如[douban-12345]或[doubanid-12345]
var doubanId = this.regDoubanIdAttribute.FirstMatchGroup(fileName);
if (!string.IsNullOrWhiteSpace(doubanId))
{
this.Log($"Found douban [id] by attr: {doubanId}");
return doubanId;
}
var parseResult = NameParser.Parse(fileName);
var searchName = !string.IsNullOrEmpty(parseResult.ChineseName) ? parseResult.ChineseName : parseResult.Name;
info.Year = parseResult.Year; // 默认parser对anime年份会解析出错以anitomy为准
this.Log($"GuessByDouban of [name]: {info.Name} [file_name]: {fileName} [year]: {info.Year} [search name]: {searchName}");
List<DoubanSubject> result;
DoubanSubject? item;
@ -125,13 +133,13 @@ namespace Jellyfin.Plugin.MetaShark.Providers
item = result.Where(x => x.Year == info.Year && x.Name == searchName).FirstOrDefault();
if (item != null)
{
this.Log($"GuessByDouban found -> {item.Name}({item.Sid}) (suggest)");
this.Log($"Found douban [id]: {item.Name}({item.Sid}) (suggest)");
return item.Sid;
}
item = result.Where(x => x.Year == info.Year).FirstOrDefault();
if (item != null)
{
this.Log($"GuessByDouban found -> {item.Name}({item.Sid}) (suggest)");
this.Log($"Found douban [id]: {item.Name}({item.Sid}) (suggest)");
return item.Sid;
}
}
@ -169,7 +177,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
item = result.Where(x => x.Category == cat).FirstOrDefault();
if (item != null)
{
this.Log($"GuessByDouban found -> {item.Name}({item.Sid})");
this.Log($"Found douban [id] by first match: {item.Name}({item.Sid})");
return item.Sid;
}
@ -394,8 +402,8 @@ namespace Jellyfin.Plugin.MetaShark.Providers
public int? GuessSeasonNumberByDirectoryName(string path)
{
// TODO: 有时series name中会带有季信息
// 当没有season级目录时path为空直接返回
// TODO: 有时 series name 中会带有季信息
// 当没有 season 级目录时,或 season 文件夹特殊不规范命名时,会解析不到 seasonNumber这时 path 为空,直接返回
if (string.IsNullOrEmpty(path))
{
this.Log($"Season path is empty!");
@ -409,6 +417,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
return null;
}
// 中文季名
var regSeason = new Regex(@"第([0-9零一二三四五六七八九]+?)(季|部)", RegexOptions.Compiled);
var match = regSeason.Match(fileName);
if (match.Success && match.Groups.Count > 1)
@ -425,12 +434,26 @@ namespace Jellyfin.Plugin.MetaShark.Providers
}
}
// SXX 季名
regSeason = new Regex(@"(?<![a-z])S(\d\d?)(?![0-9a-z])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
match = regSeason.Match(fileName);
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;
}
}
// 动漫季特殊命名
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},
{@"[ ._](I|1st)[ ._]", 1},
{@"[ ._](II|2nd)[ ._]", 2},
{@"[ ._](III|3rd)[ ._]", 3},
{@"[ ._](IIII|4th)[ ._]", 3},
};
foreach (var entry in seasonNameMap)
@ -600,7 +623,47 @@ namespace Jellyfin.Plugin.MetaShark.Providers
}
}
protected string RemoveSeasonSubfix(string name)
protected string? GetOriginalSeasonPath(EpisodeInfo info)
{
if (info.Path == null) {
return null;
}
var seasonPath = Path.GetDirectoryName(info.Path);
var item = this._libraryManager.FindByPath(seasonPath, true);
// 没有季文件夹
if (item is Series) {
return null;
}
return seasonPath;
}
protected bool IsVirtualSeason(EpisodeInfo info)
{
if (info.Path == null)
{
return false;
}
var seasonPath = Path.GetDirectoryName(info.Path);
var parent = this._libraryManager.FindByPath(seasonPath, true);
// 没有季文件夹
if (parent is Series) {
return true;
}
var seriesPath = Path.GetDirectoryName(seasonPath);
var series = this._libraryManager.FindByPath(seriesPath, true);
// 季文件夹不规范,没法识别
if (series is Series && parent is not Season) {
return true;
}
return false;
}
protected string RemoveSeasonSuffix(string name)
{
return regSeasonNameSuffix.Replace(name, "");
}

View File

@ -46,7 +46,8 @@ namespace Jellyfin.Plugin.MetaShark.Providers
// 识别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} EnableTmdb: {config.EnableTmdb}");
var fileName = Path.GetFileName(info.Path);
this.Log($"GetEpisodeMetadata of [name]: {info.Name} [fileName]: {fileName} number: {info.IndexNumber} ParentIndexNumber: {info.ParentIndexNumber} EnableTmdb: {config.EnableTmdb}");
var result = new MetadataResult<Episode>();
// 动画特典和extras处理
@ -129,69 +130,59 @@ namespace Jellyfin.Plugin.MetaShark.Providers
/// <summary>
/// 重新解析文件名
/// 注意:这里修改替换ParentIndexNumber值后会重新触发SeasonProvier的GetMetadata方法并带上最新的季数IndexNumber
/// 注意:这里修改替换 ParentIndexNumber 值后,会重新触发 SeasonProvier GetMetadata 方法,并带上最新的季数 IndexNumber
/// </summary>
public EpisodeInfo FixParseInfo(EpisodeInfo info)
{
// 使用AnitomySharp进行重新解析解决anime识别错误
// 使用 AnitomySharp 进行重新解析,解决 anime 识别错误
var fileName = Path.GetFileNameWithoutExtension(info.Path) ?? info.Name;
var parseResult = NameParser.ParseEpisode(fileName);
info.Year = parseResult.Year;
info.Name = parseResult.ChineseName ?? parseResult.Name;
// 修正文件名有特殊命名SXXEPXX时默认解析到错误季数的问题如神探狄仁杰 Detective.Dee.S01EP01.2006.2160p.WEB-DL.x264.AAC-HQC
// 文件名带有季数数据时,从文件名解析出季数进行修正
// 修正文件名有特殊命名 SXXEPXX 时,默认解析到错误季数的问题,如神探狄仁杰 Detective.Dee.S01EP01.2006.2160p.WEB-DL.x264.AAC-HQC
// TODO: 会导致覆盖用户手动修改元数据的季数
if (info.ParentIndexNumber.HasValue && parseResult.ParentIndexNumber.HasValue && parseResult.ParentIndexNumber > 0 && info.ParentIndexNumber != parseResult.ParentIndexNumber)
if (parseResult.ParentIndexNumber.HasValue && parseResult.ParentIndexNumber > 0 && info.ParentIndexNumber != parseResult.ParentIndexNumber)
{
this.Log("FixSeasonNumber by anitomy. old: {0} new: {1}", info.ParentIndexNumber, parseResult.ParentIndexNumber);
info.ParentIndexNumber = parseResult.ParentIndexNumber;
}
// 没有season级目录(即虚拟季)ParentIndexNumber默认是1季文件夹命名不规范时ParentIndexNumber默认是null
if (info.ParentIndexNumber is null)
{
info.ParentIndexNumber = parseResult.ParentIndexNumber;
}
// 修正anime命名格式导致的seasonNumber错误从season元数据读取)
if (info.ParentIndexNumber is null)
{
var episodeItem = _libraryManager.FindByPath(info.Path, false);
var season = episodeItem != null ? ((Episode)episodeItem).Season : null;
if (season != null && season.IndexNumber.HasValue && info.ParentIndexNumber != season.IndexNumber)
{
this.Log("FixSeasonNumber by season. old: {0} new: {1}", info.ParentIndexNumber, season.IndexNumber);
info.ParentIndexNumber = season.IndexNumber;
}
// // 当没有season级目录时默认为1即当成只有一季不需要处理虚拟季jellyfin默认传的ParentIndexNumber=1
// if (info.ParentIndexNumber is null && season != null && season.LocationType == LocationType.Virtual)
// // 修正anime命名格式导致的seasonNumber错误从season元数据读取)
// if (info.ParentIndexNumber is null)
// {
// this.Log("FixSeasonNumber: season is virtual, set to default 1");
// info.ParentIndexNumber = 1;
// var episodeItem = this._libraryManager.FindByPath(info.Path, false);
// var season = episodeItem != null ? ((Episode)episodeItem).Season : null;
// if (season != null && season.IndexNumber.HasValue && info.ParentIndexNumber != season.IndexNumber)
// {
// info.ParentIndexNumber = season.IndexNumber;
// this.Log("FixSeasonNumber by season. old: {0} new: {1}", info.ParentIndexNumber, season.IndexNumber);
// }
// }
}
// 从季文件夹名称猜出season number
var seasonFolderPath = Path.GetDirectoryName(info.Path);
if (info.ParentIndexNumber is null && seasonFolderPath != null)
// 从季文件夹名称猜出 season number
// 没有 season 级目录或部分特殊不规范命名会变成虚拟季ParentIndexNumber 默认设为 1
// https://github.com/jellyfin/jellyfin/blob/926470829d91d93b4c0b22c5b8b89a791abbb434/Emby.Server.Implementations/Library/LibraryManager.cs#L2626
var isVirtualSeason = this.IsVirtualSeason(info);
var seasonFolderPath = this.GetOriginalSeasonPath(info);
if (info.ParentIndexNumber is null or 1 && isVirtualSeason && seasonFolderPath != null)
{
info.ParentIndexNumber = this.GuessSeasonNumberByDirectoryName(seasonFolderPath);
var guestSeasonNumber = this.GuessSeasonNumberByDirectoryName(seasonFolderPath);
if (guestSeasonNumber.HasValue && guestSeasonNumber != info.ParentIndexNumber)
{
this.Log("FixSeasonNumber by season path. old: {0} new: {1}", info.ParentIndexNumber, guestSeasonNumber);
info.ParentIndexNumber = guestSeasonNumber;
}
}
// 识别特典
if (info.ParentIndexNumber is null && NameParser.IsAnime(fileName) && (parseResult.IsSpecial || NameParser.IsSpecialDirectory(info.Path)))
{
this.Log("FixSeasonNumber to special. old: {0} new: 0", info.ParentIndexNumber);
info.ParentIndexNumber = 0;
}
// // 设为默认季数为1问题当同时存在S01和剧场版季文件夹时剧场版的影片会因为默认第一季而在S01也显示出来
// if (info.ParentIndexNumber is null)
// {
// this.Log("FixSeasonNumber: season number is null, set to default 1");
// info.ParentIndexNumber = 1;
// }
// 特典优先使用文件名(特典除了前面特别设置,还有 SXX/Season XX 等默认的)
if (info.ParentIndexNumber.HasValue && info.ParentIndexNumber == 0)
{
@ -205,7 +196,6 @@ namespace Jellyfin.Plugin.MetaShark.Providers
info.IndexNumber = parseResult.IndexNumber;
}
this.Log("FixParseInfo: fileName: {0} seasonNumber: {1} episodeNumber: {2} name: {3}", fileName, info.ParentIndexNumber, info.IndexNumber, info.Name);
return info;
}
@ -241,7 +231,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
return result;
}
//// 特典也有剧集信息,不在这里处理
//// 特典也有 tmdb 剧集信息,不在这里处理
// if (parseResult.IsSpecial || NameParser.IsSpecialDirectory(info.Path))
// {
// this.Log($"Found anime sp of [name]: {fileName}");

View File

@ -7,6 +7,7 @@ using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using AngleSharp.Text;
using Jellyfin.Data.Enums;
using Jellyfin.Plugin.MetaShark.Api;
using Jellyfin.Plugin.MetaShark.Core;
using Jellyfin.Plugin.MetaShark.Model;
@ -182,7 +183,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
subject.LimitDirectorCelebrities.Take(Configuration.PluginConfiguration.MAX_CAST_MEMBERS).ToList().ForEach(c => result.AddPerson(new PersonInfo
{
Name = c.Name,
Type = c.RoleType,
Type = c.RoleType == PersonType.Director ? PersonKind.Director : PersonKind.Actor,
Role = c.Role,
ImageUrl = this.GetLocalProxyImageUrl(c.Img),
ProviderIds = new Dictionary<string, string> { { DoubanProviderId, c.Id } },
@ -301,7 +302,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
{
Name = actor.Name.Trim(),
Role = actor.Character,
Type = PersonType.Actor,
Type = PersonKind.Actor,
SortOrder = actor.Order,
};
@ -344,7 +345,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
{
Name = person.Name.Trim(),
Role = person.Job,
Type = type
Type = type == PersonType.Director ? PersonKind.Director : (type == PersonType.Producer ? PersonKind.Producer : PersonKind.Actor),
};
if (!string.IsNullOrWhiteSpace(person.ProfilePath))

View File

@ -14,6 +14,8 @@ using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Jellyfin.Data.Enums;
using System.IO;
namespace Jellyfin.Plugin.MetaShark.Providers
{
@ -40,25 +42,27 @@ namespace Jellyfin.Plugin.MetaShark.Providers
{
var result = new MetadataResult<Season>();
// 使用刷新元数据时,之前识别的seasonNumber会保留不会被覆盖
// 使用刷新元数据时,之前识别的 seasonNumber 会保留,不会被覆盖
info.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out var seriesTmdbId);
info.SeriesProviderIds.TryGetMetaSource(Plugin.ProviderId, out var metaSource);
info.SeriesProviderIds.TryGetValue(DoubanProviderId, out var sid);
var seasonNumber = info.IndexNumber; // S00/Season 00特典目录会为0
var seasonSid = info.GetProviderId(DoubanProviderId);
var fileName = this.GetOriginalFileName(info);
var fileName = Path.GetFileName(info.Path);
this.Log($"GetSeasonMetaData of [name]: {info.Name} [fileName]: {fileName} number: {info.IndexNumber} seriesTmdbId: {seriesTmdbId} sid: {sid} metaSource: {metaSource} EnableTmdb: {config.EnableTmdb}");
if (metaSource != MetaSource.Tmdb && !string.IsNullOrEmpty(sid))
{
// 季文件夹名称不规范没法拿到seasonNumber尝试从文件夹名猜出
// 注意:本办法没法处理没有季文件夹的/虚拟季因为path会为空
// seasonNumber 为 null 有三种情况:
// 1. 没有季文件夹时即虚拟季info.Path 为空
// 2. 一般不规范文件夹命名,没法被 EpisodeResolver 解析的info.Path 不为空,如:摇曳露营△
// 3. 特殊不规范文件夹命名,能被 EpisodeResolver 错误解析这时被当成了视频文件相当于没有季文件夹info.Path 为空,如:冰与火之歌 S02.列王的纷争.2012.1080p.Blu-ray.x265.10bit.AC3
// 相关代码https://github.com/jellyfin/jellyfin/blob/dc2eca9f2ca259b46c7b53f59251794903c730a4/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs#L70
if (seasonNumber is null)
{
seasonNumber = this.GuessSeasonNumberByDirectoryName(info.Path);
}
// 搜索豆瓣季id
// 搜索豆瓣季 id
if (string.IsNullOrEmpty(seasonSid))
{
seasonSid = await this.GuessDoubanSeasonId(sid, seriesTmdbId, seasonNumber, info, cancellationToken).ConfigureAwait(false);
@ -89,19 +93,19 @@ namespace Jellyfin.Plugin.MetaShark.Providers
subject.LimitDirectorCelebrities.Take(Configuration.PluginConfiguration.MAX_CAST_MEMBERS).ToList().ForEach(c => result.AddPerson(new PersonInfo
{
Name = c.Name,
Type = c.RoleType,
Type = c.RoleType == PersonType.Director ? PersonKind.Director : PersonKind.Actor,
Role = c.Role,
ImageUrl = this.GetLocalProxyImageUrl(c.Img),
ProviderIds = new Dictionary<string, string> { { DoubanProviderId, c.Id } },
}));
this.Log($"GetSeasonMetaData of douban [sid]: {seasonSid}");
this.Log($"Season [{info.Name}] found douban [sid]: {seasonSid}");
return result;
}
}
else
{
this.Log($"GetSeasonMetaData of [name]: {info.Name} not found douban season id!");
this.Log($"Season [{info.Name}] not found douban season id!");
}
@ -139,10 +143,19 @@ namespace Jellyfin.Plugin.MetaShark.Providers
return null;
}
// 没有季文件夹即虚拟季info.Path会为空直接用series的sid
if (string.IsNullOrEmpty(info.Path))
// 没有季文件夹或季文件夹名不规范时即虚拟季info.Path 会为空seasonNumber 为 null
if (string.IsNullOrEmpty(info.Path) && !seasonNumber.HasValue)
{
return sid;
return null;
}
// 从季文件夹名属性格式获取,如 [douban-12345] 或 [doubanid-12345]
var fileName = this.GetOriginalFileName(info);
var doubanId = this.regDoubanIdAttribute.FirstMatchGroup(fileName);
if (!string.IsNullOrWhiteSpace(doubanId))
{
this.Log($"Found season douban [id] by attr: {doubanId}");
return doubanId;
}
// 从sereis获取正确名称info.Name当是标准格式如S01等时会变成第x季非标准名称默认文件名
@ -151,7 +164,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
{
return null;
}
var seriesName = RemoveSeasonSubfix(series.Name);
var seriesName = this.RemoveSeasonSuffix(series.Name);
// 没有季id但存在tmdbid尝试从tmdb获取对应季的年份信息用于从豆瓣搜索对应季数据
var seasonYear = 0;

View File

@ -1,4 +1,5 @@
using Jellyfin.Plugin.MetaShark.Api;
using Jellyfin.Data.Enums;
using Jellyfin.Plugin.MetaShark.Api;
using Jellyfin.Plugin.MetaShark.Model;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
@ -104,12 +105,12 @@ namespace Jellyfin.Plugin.MetaShark.Providers
}
subject.Celebrities = await this._doubanApi.GetCelebritiesBySidAsync(sid, cancellationToken).ConfigureAwait(false);
var seriesName = RemoveSeasonSubfix(subject.Name);
var seriesName = RemoveSeasonSuffix(subject.Name);
var item = new Series
{
ProviderIds = new Dictionary<string, string> { { DoubanProviderId, subject.Sid }, { Plugin.ProviderId, $"{MetaSource.Douban}_{subject.Sid}" } },
Name = seriesName,
OriginalTitle = RemoveSeasonSubfix(subject.OriginalName),
OriginalTitle = RemoveSeasonSuffix(subject.OriginalName),
CommunityRating = subject.Rating,
Overview = subject.Intro,
ProductionYear = subject.Year,
@ -152,7 +153,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
subject.LimitDirectorCelebrities.Take(Configuration.PluginConfiguration.MAX_CAST_MEMBERS).ToList().ForEach(c => result.AddPerson(new PersonInfo
{
Name = c.Name,
Type = c.RoleType,
Type = c.RoleType == PersonType.Director ? PersonKind.Director : PersonKind.Actor,
Role = c.Role,
ImageUrl = this.GetLocalProxyImageUrl(c.Img),
ProviderIds = new Dictionary<string, string> { { DoubanProviderId, c.Id } },
@ -356,7 +357,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
{
Name = actor.Name.Trim(),
Role = actor.Character,
Type = PersonType.Actor,
Type = PersonKind.Actor,
SortOrder = actor.Order,
};
@ -400,7 +401,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers
{
Name = person.Name.Trim(),
Role = person.Job,
Type = type
Type = type == PersonType.Director ? PersonKind.Director : (type == PersonType.Producer ? PersonKind.Producer : PersonKind.Actor),
};
if (!string.IsNullOrWhiteSpace(person.ProfilePath))

View File

@ -1,18 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Jellyfin.Plugin.MetaShark.Api;
using Jellyfin.Plugin.MetaShark.Providers;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.IO;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Plugins;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using MediaBrowser.Controller.Persistence;
using System.Net.Http;
namespace Jellyfin.Plugin.MetaShark
{
@ -20,21 +10,22 @@ namespace Jellyfin.Plugin.MetaShark
public class ServiceRegistrator : IPluginServiceRegistrator
{
/// <inheritdoc />
public void RegisterServices(IServiceCollection serviceCollection)
public void RegisterServices(IServiceCollection serviceCollection, IServerApplicationHost applicationHost)
{
serviceCollection.AddSingleton<DoubanApi>((ctx) =>
serviceCollection.AddSingleton((ctx) =>
{
return new DoubanApi(ctx.GetRequiredService<ILoggerFactory>());
});
serviceCollection.AddSingleton<TmdbApi>((ctx) =>
serviceCollection.AddSingleton((ctx) =>
{
return new TmdbApi(ctx.GetRequiredService<ILoggerFactory>());
});
serviceCollection.AddSingleton<OmdbApi>((ctx) =>
serviceCollection.AddSingleton((ctx) =>
{
return new OmdbApi(ctx.GetRequiredService<ILoggerFactory>());
});
serviceCollection.AddSingleton<ImdbApi>((ctx) =>
serviceCollection.AddSingleton((ctx) =>
{
return new ImdbApi(ctx.GetRequiredService<ILoggerFactory>());
});

View File

@ -1,7 +1,7 @@
# jellyfin-plugin-metashark
[![release](https://img.shields.io/github/v/release/cxfksword/jellyfin-plugin-metashark)](https://github.com/cxfksword/jellyfin-plugin-metashark/releases)
[![platform](https://img.shields.io/badge/jellyfin-10.8.x-lightgrey?logo=jellyfin)](https://github.com/cxfksword/jellyfin-plugin-metashark/releases)
[![platform](https://img.shields.io/badge/jellyfin-10.8.x|10.9.x-lightgrey?logo=jellyfin)](https://github.com/cxfksword/jellyfin-plugin-metashark/releases)
[![license](https://img.shields.io/github/license/cxfksword/jellyfin-plugin-metashark)](https://github.com/cxfksword/jellyfin-plugin-metashark/main/LICENSE)
jellyfin电影元数据插件影片信息只要从豆瓣获取并由TheMovieDb补全缺失的剧集数据。
@ -14,11 +14,9 @@ jellyfin电影元数据插件影片信息只要从豆瓣获取并由TheMov
## 安装插件
只支持最新的`jellyfin 10.8.x`版本
添加插件存储库:
国内加速https://mirror.ghproxy.com/https://github.com/cxfksword/jellyfin-plugin-metashark/releases/download/manifest/manifest_cn.json
国内加速https://gitee.com/cwhzy/jellyfin-plugin-metashark/releases/download/manifest/manifest_cn.json
国外访问https://github.com/cxfksword/jellyfin-plugin-metashark/releases/download/manifest/manifest.json
@ -42,7 +40,7 @@ jellyfin电影元数据插件影片信息只要从豆瓣获取并由TheMov
1. Clone or download this repository
2. Ensure you have .NET Core SDK 6.0 setup and installed
2. Ensure you have .NET Core SDK 8.0 setup and installed
3. Build plugin with following command.
@ -56,7 +54,7 @@ dotnet publish --configuration=Release Jellyfin.Plugin.MetaShark/Jellyfin.Plugin
1. Build the plugin
2. Create a folder, like `metashark` and copy `./Jellyfin.Plugin.MetaShark/bin/Release/net6.0/Jellyfin.Plugin.MetaShark.dll` into it
2. Create a folder, like `metashark` and copy `./Jellyfin.Plugin.MetaShark/bin/Release/net8.0/Jellyfin.Plugin.MetaShark.dll` into it
3. Move folder `metashark` to jellyfin `data/plugins` folder

View File

@ -26,7 +26,7 @@ def generate_version(filepath, version, changelog):
return {
'version': f"{version}.0",
'changelog': changelog,
'targetAbi': '10.8.0.0',
'targetAbi': '10.9.0.0',
'sourceUrl': f'https://github.com/cxfksword/jellyfin-plugin-metashark/releases/download/v{version}/metashark_{version}.0.zip',
'checksum': md5sum(filepath),
'timestamp': datetime.now().strftime('%Y-%m-%dT%H:%M:%S')