Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add collection by subject's relation #160

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 54 additions & 4 deletions Jellyfin.Plugin.Bangumi/BangumiApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public async Task<List<Subject>> SearchSubject(string keyword, SubjectType? type
var searchResult = await Get<SearchResult<Subject>>(url, token);
var list = searchResult?.List ?? [];

if (Plugin.Instance!.Configuration.SortByFuzzScore && list.Count > 2)
if (Plugin.Instance!.Configuration.SortByFuzzScore && list.Count > 1)
{
// 仅使用前 5 个条目获取别名并排序
var num = 5;
Expand Down Expand Up @@ -147,7 +147,7 @@ public async Task<List<Subject>> SearchSubject(string keyword, SubjectType? type
return await Get<DataList<Episode>>(url, token);
}

public async Task<List<RelatedSubject>?> GetSubjectRelations(int id, CancellationToken token)
public async Task<List<RelatedSubject>?> GetRelatedSubjects(int id, CancellationToken token)
{
return await Get<List<RelatedSubject>>($"{BaseUrl}/v0/subjects/{id}/subjects", token);
}
Expand All @@ -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<RelatedSubject>(relatedSubjects?.Where(item => item.Relation == SubjectRelation.Sequel) ?? []);
while (subjectsQueue.Any() && requestCount < maxRequestCount)
{
Expand All @@ -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);
Expand All @@ -191,6 +191,56 @@ bool SeriesSequelUnqualified(Subject subject)
return null;
}

/// <summary>
/// 获取此条目的所有关联条目
/// </summary>
/// <param name="seriesId"></param>
/// <param name="token"></param>
/// <returns></returns>
public async Task<List<int>> GetAllSeriesSubjectIds(int seriesId, CancellationToken token)
{
HashSet<int> allSubjectIds = new HashSet<int>();
var queue = new Queue<int>();
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 GetRelatedSubjects(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<List<PersonInfo>> GetSubjectCharacters(int id, CancellationToken token)
{
var characters = await Get<List<RelatedCharacter>>($"{BaseUrl}/v0/subjects/{id}/characters", token);
Expand Down
117 changes: 117 additions & 0 deletions Jellyfin.Plugin.Bangumi/ScheduledTask/AddCollection.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// inspired by https://github.com/DirtyRacer1337/Jellyfin.Plugin.PhoenixAdult
/// </summary>
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<double> progress)
#else
public async Task ExecuteAsync(IProgress<double> 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<Guid>();
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<TaskTriggerInfo> GetDefaultTriggers() => Enumerable.Empty<TaskTriggerInfo>();
}