From 2f205044a9b834773becce4bf358558462fabb72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=9D=E7=A5=9E?= <45256288+chu-shen@users.noreply.github.com> Date: Mon, 7 Oct 2024 21:15:11 +0800 Subject: [PATCH 1/2] feat: add collection by subject's relation --- Jellyfin.Plugin.Bangumi/BangumiApi.cs | 52 +++++++- .../ScheduledTask/AddCollection.cs | 117 ++++++++++++++++++ 2 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 Jellyfin.Plugin.Bangumi/ScheduledTask/AddCollection.cs diff --git a/Jellyfin.Plugin.Bangumi/BangumiApi.cs b/Jellyfin.Plugin.Bangumi/BangumiApi.cs index 254ddfa..5553fd0 100644 --- a/Jellyfin.Plugin.Bangumi/BangumiApi.cs +++ b/Jellyfin.Plugin.Bangumi/BangumiApi.cs @@ -60,7 +60,7 @@ public async Task> SearchSubject(string keyword, SubjectType? type var searchResult = await SendRequest>(url, token); var list = searchResult?.List ?? new List(); - if (Plugin.Instance!.Configuration.SortByFuzzScore && list.Count > 2) + if (Plugin.Instance!.Configuration.SortByFuzzScore && list.Count > 1) { // 仅使用前 5 个条目 var tasks = list.Take(5).Select(subject => GetSubject(subject.Id, token)); @@ -191,6 +191,56 @@ bool SeriesSequelUnqualified(Subject subject) return null; } + /// + /// 获取此条目的所有关联条目 + /// + /// + /// + /// + public async Task> GetAllSeriesSubjectIds(int seriesId, CancellationToken token) + { + HashSet allSubjectIds = new HashSet(); + var queue = new Queue(); + queue.Enqueue(seriesId); + + int requestCount = 0; + int maxRequestCount = 1024; // 最多请求数 + + + while (queue.Count > 0 && requestCount < maxRequestCount) + { + var currentSeriesId = queue.Dequeue(); + if (!allSubjectIds.Contains(currentSeriesId)) + { + // 将 id 添加进集合 + allSubjectIds.Add(currentSeriesId); + + // 获取关联条目 + var results = await GetSubjectRelations(currentSeriesId, token); + if (results is null) + continue; + + // 遍历条目,判断关系 + foreach (var result in results) + { + if (result.Relation == SubjectRelation.Sequel || result.Relation == SubjectRelation.Prequel) // 衍生、主线故事 + { + // 加入队列 + queue.Enqueue(result.Id); + } + else if (result.Relation == SubjectRelation.Extra || result.Relation == "总集篇") + { + // 无更多关联条目,直接添加进列表 + allSubjectIds.Add(result.Id); + } + } + requestCount++; + } + + } + return allSubjectIds.ToList(); + } + public async Task> GetSubjectCharacters(int id, CancellationToken token) { var characters = await SendRequest>($"{BaseUrl}/v0/subjects/{id}/characters", token); diff --git a/Jellyfin.Plugin.Bangumi/ScheduledTask/AddCollection.cs b/Jellyfin.Plugin.Bangumi/ScheduledTask/AddCollection.cs new file mode 100644 index 0000000..e88c1b2 --- /dev/null +++ b/Jellyfin.Plugin.Bangumi/ScheduledTask/AddCollection.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Plugin.Bangumi.Utils; +using MediaBrowser.Controller.Collections; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Tasks; + + +namespace Jellyfin.Plugin.Bangumi.ScheduledTask; + +/// +/// inspired by https://github.com/DirtyRacer1337/Jellyfin.Plugin.PhoenixAdult +/// +public class AddCollection(BangumiApi api, ILibraryManager libraryManager, ICollectionManager collectionManager) : IScheduledTask +{ + public string Key => Constants.PluginName + "AddCollection"; + + public string Name => "添加合集"; + + public string Description => "根据关联条目创建合集"; + + public string Category => Constants.PluginName; + +#if EMBY + public async Task Execute(CancellationToken cancellationToken, IProgress progress) +#else + public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) +#endif + { + await Task.Yield(); + progress?.Report(0); + + // 获取所有使用 Bangumi 插件的条目(电影/电视剧) + var query = new InternalItemsQuery { }; + var subjects = libraryManager.GetItemList(query) + .Where(o => o.ProviderIds.ContainsKey(Constants.PluginName) && (o.GetClientTypeName() == "Series" || o.GetClientTypeName() == "Movie")) + .Distinct() + .ToList(); + + var processed = new HashSet(); + foreach (var (idx, subject) in subjects.WithIndex()) + { + progress?.Report((double)idx / subjects.Count * 100); + + // 跳过已添加至合集的 + if (processed.Contains(subject.Id)) + continue; + + var providerIds = subject.ProviderIds.GetValueOrDefault(Constants.PluginName); + if (providerIds is null) + continue; + + // 获取此 id 对应的系列所有 id + var bangumiSeriesIds = await api.GetAllSeriesSubjectIds(int.Parse(providerIds), cancellationToken); + + await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); + + // 取出在 subjects 中出现的所有 id + var collections = subjects + .Where(o => bangumiSeriesIds.Contains(int.Parse(o.ProviderIds.GetValueOrDefault(Constants.PluginName) ?? "-1"))) + .Distinct() + .ToList(); + + // 跳过数量小于 2 的 + if (collections.Count < 2) + continue; + + // 使用系列中最小 id 对应的名字作为合集名 + // 不一定是第一部,与 Bangumi 数据录入先后有关 + var firstSeries = await api.GetSubject(bangumiSeriesIds.Min(), cancellationToken); + if (firstSeries is null) + continue; + + // 创建合集 + var option = new CollectionCreationOptions + { + Name = (firstSeries.ChineseName ?? firstSeries.OriginalName) + "(系列)", +#if EMBY + ItemIdList = collections.Select(o => o.InternalId).ToArray(), +#else + ItemIdList = collections.Select(o => o.Id.ToString()).ToArray(), +#endif + }; + +#if EMBY + var collection = await collectionManager.CreateCollection(option).ConfigureAwait(false); +#else + var collection = await collectionManager.CreateCollectionAsync(option).ConfigureAwait(false); +#endif + + // 添加已处理的 subjects,避免后面重复处理 + processed.UnionWith(collections.Select(c => c.Id)); + Logger.Info("add collection: " + subject.Name+", " + providerIds); + + // 随机设置合集封面 + var moviesImages = collections.Where(o => o.HasImage(ImageType.Primary)); + if (moviesImages.Any()) + { + collection.SetImage(moviesImages.Random().GetImageInfo(ImageType.Primary, 0), 0); + } + + if (cancellationToken.IsCancellationRequested) + { + return; + } + } + + progress?.Report(100); + } + + public IEnumerable GetDefaultTriggers() => Enumerable.Empty(); +} \ No newline at end of file From 6f1dd98efbd393ab2f0c1b26b7d7c24abd2813c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=9D=E7=A5=9E?= <45256288+chu-shen@users.noreply.github.com> Date: Tue, 8 Oct 2024 12:30:11 +0800 Subject: [PATCH 2/2] style: change name: GetSubjectRelations -> GetRelatedSubjects --- Jellyfin.Plugin.Bangumi/BangumiApi.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Plugin.Bangumi/BangumiApi.cs b/Jellyfin.Plugin.Bangumi/BangumiApi.cs index 5553fd0..64ea0ae 100644 --- a/Jellyfin.Plugin.Bangumi/BangumiApi.cs +++ b/Jellyfin.Plugin.Bangumi/BangumiApi.cs @@ -147,7 +147,7 @@ public async Task> SearchSubject(string keyword, SubjectType? type return await SendRequest>(url, token); } - public async Task?> GetSubjectRelations(int id, CancellationToken token) + public async Task?> GetRelatedSubjects(int id, CancellationToken token) { return await SendRequest>($"{BaseUrl}/v0/subjects/{id}/subjects", token); } @@ -164,7 +164,7 @@ bool SeriesSequelUnqualified(Subject subject) var requestCount = 0; //What would happen in Emby if I use `_plugin`? int maxRequestCount = Plugin.Instance?.Configuration?.SeasonGuessMaxSearchCount ?? 2; - var relatedSubjects = await GetSubjectRelations(id, token); + var relatedSubjects = await GetRelatedSubjects(id, token); var subjectsQueue = new Queue(relatedSubjects?.Where(item => item.Relation == SubjectRelation.Sequel) ?? []); while (subjectsQueue.Any() && requestCount < maxRequestCount) { @@ -173,7 +173,7 @@ bool SeriesSequelUnqualified(Subject subject) requestCount++; if (subjectCandidate != null && SeriesSequelUnqualified(subjectCandidate)) { - var nextRelatedSubjects = await GetSubjectRelations(subjectCandidate.Id, token); + var nextRelatedSubjects = await GetRelatedSubjects(subjectCandidate.Id, token); foreach (var nextRelatedSubject in nextRelatedSubjects?.Where(item => item.Relation == SubjectRelation.Sequel) ?? []) { subjectsQueue.Enqueue(nextRelatedSubject); @@ -216,7 +216,7 @@ public async Task> GetAllSeriesSubjectIds(int seriesId, CancellationTo allSubjectIds.Add(currentSeriesId); // 获取关联条目 - var results = await GetSubjectRelations(currentSeriesId, token); + var results = await GetRelatedSubjects(currentSeriesId, token); if (results is null) continue;