diff --git a/Jellyfin.Plugin.Danmu/Controllers/DanmuController.cs b/Jellyfin.Plugin.Danmu/Controllers/DanmuController.cs index bb7f0e3..0250bdf 100644 --- a/Jellyfin.Plugin.Danmu/Controllers/DanmuController.cs +++ b/Jellyfin.Plugin.Danmu/Controllers/DanmuController.cs @@ -126,7 +126,7 @@ namespace Jellyfin.Plugin.Danmu.Controllers if (item is Movie || item is Series || item is Season || item is Episode) { - _libraryManagerEventsHelper.QueueItem(item, Model.EventType.Refresh); + _libraryManagerEventsHelper.QueueItem(item, Model.EventType.Add); } return "ok"; diff --git a/Jellyfin.Plugin.Danmu/Core/Extensions.cs b/Jellyfin.Plugin.Danmu/Core/ListExtension.cs similarity index 89% rename from Jellyfin.Plugin.Danmu/Core/Extensions.cs rename to Jellyfin.Plugin.Danmu/Core/ListExtension.cs index a7fc92f..d831e6c 100644 --- a/Jellyfin.Plugin.Danmu/Core/Extensions.cs +++ b/Jellyfin.Plugin.Danmu/Core/ListExtension.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace Jellyfin.Plugin.Danmu.Core { - public static class Extensions + public static class ListExtension { public static IEnumerable<(T item, int index)> WithIndex(this IEnumerable self) => self.Select((item, index) => (item, index)); diff --git a/Jellyfin.Plugin.Danmu/Model/EventType.cs b/Jellyfin.Plugin.Danmu/Model/EventType.cs index 3ba292b..2560cb9 100644 --- a/Jellyfin.Plugin.Danmu/Model/EventType.cs +++ b/Jellyfin.Plugin.Danmu/Model/EventType.cs @@ -24,10 +24,5 @@ public enum EventType /// /// The update event. /// - Update, - - /// - /// The refresh event. - /// - Refresh + Update } diff --git a/Jellyfin.Plugin.Danmu/PluginStartup.cs b/Jellyfin.Plugin.Danmu/PluginStartup.cs index d904e80..16df6ea 100644 --- a/Jellyfin.Plugin.Danmu/PluginStartup.cs +++ b/Jellyfin.Plugin.Danmu/PluginStartup.cs @@ -52,7 +52,7 @@ namespace Jellyfin.Plugin.Danmu { _libraryManager.ItemAdded += LibraryManagerItemAdded; _libraryManager.ItemUpdated += LibraryManagerItemUpdated; - _libraryManager.ItemRemoved += LibraryManagerItemRemoved; + // _libraryManager.ItemRemoved += LibraryManagerItemRemoved; return Task.CompletedTask; } @@ -138,7 +138,7 @@ namespace Jellyfin.Plugin.Danmu { _libraryManager.ItemAdded -= LibraryManagerItemAdded; _libraryManager.ItemUpdated -= LibraryManagerItemUpdated; - _libraryManager.ItemRemoved -= LibraryManagerItemRemoved; + // _libraryManager.ItemRemoved -= LibraryManagerItemRemoved; _libraryManagerEventsHelper.Dispose(); } } diff --git a/Jellyfin.Plugin.Danmu/Providers/LibraryManagerEventsHelper.cs b/Jellyfin.Plugin.Danmu/Providers/LibraryManagerEventsHelper.cs index 8182c88..6bf805b 100644 --- a/Jellyfin.Plugin.Danmu/Providers/LibraryManagerEventsHelper.cs +++ b/Jellyfin.Plugin.Danmu/Providers/LibraryManagerEventsHelper.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; +using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -22,6 +23,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; +using MediaBrowser.Controller.Persistence; using Microsoft.Extensions.Logging; namespace Jellyfin.Plugin.Danmu.Providers; @@ -30,6 +32,7 @@ public class LibraryManagerEventsHelper : IDisposable { private readonly List _queuedEvents; + private readonly IProviderManager _providerManager; private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; private readonly BilibiliApi _api; @@ -41,14 +44,16 @@ public class LibraryManagerEventsHelper : IDisposable /// Initializes a new instance of the class. /// /// The . + /// Instance of interface. /// The . /// The . /// Instance of the interface. - public LibraryManagerEventsHelper(ILibraryManager libraryManager, ILoggerFactory loggerFactory, BilibiliApi api, IFileSystem fileSystem) + public LibraryManagerEventsHelper(ILibraryManager libraryManager, IProviderManager providerManager, ILoggerFactory loggerFactory, BilibiliApi api, IFileSystem fileSystem) { _queuedEvents = new List(); _libraryManager = libraryManager; + _providerManager = providerManager; _logger = loggerFactory.CreateLogger(); _api = api; _fileSystem = fileSystem; @@ -105,7 +110,7 @@ public class LibraryManagerEventsHelper : IDisposable /// private async Task OnQueueTimerCallbackInternal() { - _logger.LogInformation("Timer elapsed - processing queued items"); + // _logger.LogInformation("Timer elapsed - processing queued items"); List queue; lock (_queuedEvents) @@ -120,117 +125,75 @@ public class LibraryManagerEventsHelper : IDisposable _queuedEvents.Clear(); } - var queuedMovieDeletes = new List(); var queuedMovieAdds = new List(); var queuedMovieUpdates = new List(); - var queuedMovieRefreshs = new List(); - var queuedEpisodeDeletes = new List(); var queuedEpisodeAdds = new List(); - var queuedEpisodeUpdates = new List(); - var queuedEpisodeRefreshs = new List(); - var queuedShowDeletes = new List(); + var queuedEpisodeUpdates = new List(); ; var queuedShowAdds = new List(); var queuedShowUpdates = new List(); - var queuedShowRefreshs = new List(); - var queuedSeasonDeletes = new List(); var queuedSeasonAdds = new List(); var queuedSeasonUpdates = new List(); - var queuedSeasonRefreshs = new List(); - queuedMovieDeletes.Clear(); queuedMovieAdds.Clear(); queuedMovieUpdates.Clear(); - queuedMovieRefreshs.Clear(); - queuedEpisodeDeletes.Clear(); queuedEpisodeAdds.Clear(); queuedEpisodeUpdates.Clear(); - queuedEpisodeRefreshs.Clear(); - queuedShowDeletes.Clear(); queuedShowAdds.Clear(); queuedShowUpdates.Clear(); - queuedShowRefreshs.Clear(); - queuedSeasonDeletes.Clear(); queuedSeasonAdds.Clear(); queuedSeasonUpdates.Clear(); - queuedSeasonRefreshs.Clear(); foreach (var ev in queue) { switch (ev.Item) { - case Movie when ev.EventType is EventType.Remove: - queuedMovieDeletes.Add(ev); - break; case Movie when ev.EventType is EventType.Add: queuedMovieAdds.Add(ev); break; case Movie when ev.EventType is EventType.Update: queuedMovieUpdates.Add(ev); break; - case Movie when ev.EventType is EventType.Refresh: - queuedMovieRefreshs.Add(ev); - break; - case Episode when ev.EventType is EventType.Remove: - queuedEpisodeDeletes.Add(ev); - break; - case Episode when ev.EventType is EventType.Add: - queuedEpisodeAdds.Add(ev); - break; - case Episode when ev.EventType is EventType.Update: - queuedEpisodeUpdates.Add(ev); - break; - case Episode when ev.EventType is EventType.Refresh: - queuedEpisodeRefreshs.Add(ev); - break; - case Series when ev.EventType is EventType.Remove: - queuedShowDeletes.Add(ev); - break; case Series when ev.EventType is EventType.Add: + _logger.LogInformation("Series add: {0}", ev.Item.Name); queuedShowAdds.Add(ev); break; case Series when ev.EventType is EventType.Update: + _logger.LogInformation("Series update: {0}", ev.Item.Name); queuedShowUpdates.Add(ev); break; - case Series when ev.EventType is EventType.Refresh: - queuedShowRefreshs.Add(ev); - break; - case Season when ev.EventType is EventType.Remove: - queuedSeasonDeletes.Add(ev); - break; case Season when ev.EventType is EventType.Add: + _logger.LogInformation("Season add: {0}", ev.Item.Name); queuedSeasonAdds.Add(ev); break; case Season when ev.EventType is EventType.Update: + _logger.LogInformation("Season update: {0}", ev.Item.Name); queuedSeasonUpdates.Add(ev); break; - case Season when ev.EventType is EventType.Refresh: - queuedSeasonRefreshs.Add(ev); + case Episode when ev.EventType is EventType.Add: + _logger.LogInformation("Episode add: {0}", ev.Item.Name); + queuedEpisodeAdds.Add(ev); + break; + case Episode when ev.EventType is EventType.Update: + _logger.LogInformation("Episode update: {0}", ev.Item.Name); + queuedEpisodeUpdates.Add(ev); break; } } - //await ProcessQueuedMovieEvents(queuedMovieDeletes, EventType.Remove).ConfigureAwait(false); + // 对于剧集,处理顺序也很重要(Add事件后,会刷新元数据,导致会同时推送Update事件) await ProcessQueuedMovieEvents(queuedMovieAdds, EventType.Add).ConfigureAwait(false); await ProcessQueuedMovieEvents(queuedMovieUpdates, EventType.Update).ConfigureAwait(false); - await ProcessQueuedMovieEvents(queuedMovieRefreshs, EventType.Refresh).ConfigureAwait(false); - //await ProcessQueuedEpisodeEvents(queuedEpisodeDeletes, EventType.Remove).ConfigureAwait(false); - await ProcessQueuedEpisodeEvents(queuedEpisodeAdds, EventType.Add).ConfigureAwait(false); - await ProcessQueuedEpisodeEvents(queuedEpisodeUpdates, EventType.Update).ConfigureAwait(false); - await ProcessQueuedEpisodeEvents(queuedEpisodeRefreshs, EventType.Refresh).ConfigureAwait(false); - - //await ProcessQueuedShowEvents(queuedShowDeletes, EventType.Remove).ConfigureAwait(false); await ProcessQueuedShowEvents(queuedShowAdds, EventType.Add).ConfigureAwait(false); - await ProcessQueuedShowEvents(queuedShowUpdates, EventType.Update).ConfigureAwait(false); - await ProcessQueuedShowEvents(queuedShowRefreshs, EventType.Refresh).ConfigureAwait(false); - - //await ProcessQueuedSeasonEvents(queuedSeasonDeletes, EventType.Remove).ConfigureAwait(false); await ProcessQueuedSeasonEvents(queuedSeasonAdds, EventType.Add).ConfigureAwait(false); + await ProcessQueuedEpisodeEvents(queuedEpisodeAdds, EventType.Add).ConfigureAwait(false); + + await ProcessQueuedShowEvents(queuedShowUpdates, EventType.Update).ConfigureAwait(false); await ProcessQueuedSeasonEvents(queuedSeasonUpdates, EventType.Update).ConfigureAwait(false); - await ProcessQueuedSeasonEvents(queuedSeasonRefreshs, EventType.Refresh).ConfigureAwait(false); + await ProcessQueuedEpisodeEvents(queuedEpisodeUpdates, EventType.Update).ConfigureAwait(false); } @@ -257,7 +220,7 @@ public class LibraryManagerEventsHelper : IDisposable { // 新增事件也会触发update,不需要处理Add // 更新,判断是否有bvid,有的话刷新弹幕文件 - if (eventType == EventType.Add || eventType == EventType.Refresh) + if (eventType == EventType.Add) { var queueUpdateMeta = new List(); foreach (var item in movies) @@ -336,7 +299,8 @@ public class LibraryManagerEventsHelper : IDisposable if (epId <= 0) { - this.DeleteOldDanmu(item); + // 用户删除了epid,存在旧弹幕的话,尝试删除 + // this.DeleteOldDanmu(item); continue; } @@ -394,14 +358,14 @@ public class LibraryManagerEventsHelper : IDisposable try { - if (eventType == EventType.Add || eventType == EventType.Refresh || eventType == EventType.Update) + if (eventType == EventType.Update) { foreach (var item in series) { var seasons = item.GetSeasons(null, new DtoOptions(false)); foreach (var season in seasons) { - // 推送刷新season数据 + // 发现season保存元数据,不会推送update事件,这里通过series的update事件推送刷新 QueueItem(season, eventType); } } @@ -435,7 +399,7 @@ public class LibraryManagerEventsHelper : IDisposable try { - if (eventType == EventType.Add || eventType == EventType.Refresh) + if (eventType == EventType.Add) { var queueUpdateMeta = new List(); foreach (var season in seasons) @@ -464,18 +428,19 @@ public class LibraryManagerEventsHelper : IDisposable // 更新seasonId元数据 season.SetProviderId(Plugin.ProviderId, $"{seasonId}"); - - //await _libraryManager.UpdateItemAsync(season, season.GetParent(), ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); queueUpdateMeta.Add(season); } - if (seasonId > 0) - { - await ProcessSeasonEpisodes(season, eventType).ConfigureAwait(false); - } + + //// Add事件后,会自动触发season的Update事件来处理,不需要主动处理 + // if (seasonId > 0) + // { + // await ProcessSeasonEpisodes(season, eventType).ConfigureAwait(false); + // } } } + // 保存元数据 await ProcessQueuedUpdateMeta(queueUpdateMeta).ConfigureAwait(false); } @@ -491,6 +456,7 @@ public class LibraryManagerEventsHelper : IDisposable } else { + // season存在epid,尝试搜索刷新episode的 var seasonId = providerVal.ToLong(); if (seasonId > 0) { @@ -540,7 +506,8 @@ public class LibraryManagerEventsHelper : IDisposable // 新影片,判断是否设置epId,没的话,尝试搜索填充 if (epId <= 0) { - this.DeleteOldDanmu(item); + // 用户删除了epid,存在旧弹幕的话,尝试删除 + // this.DeleteOldDanmu(item); continue; } @@ -575,7 +542,7 @@ public class LibraryManagerEventsHelper : IDisposable // season手动设置了bv号情况 // 判断剧集数目是否一致,根据集号下载对应的弹幕 - if (eventType == EventType.Update || eventType == EventType.Add || eventType == EventType.Refresh) + if (eventType == EventType.Update || eventType == EventType.Add) { var episodes = season.GetEpisodes(null, new DtoOptions(false)); var video = await this._api.GetVideoByBvidAsync(bvid, CancellationToken.None).ConfigureAwait(false); @@ -648,6 +615,7 @@ public class LibraryManagerEventsHelper : IDisposable var indexNumber = episode.IndexNumber ?? 0; if (indexNumber <= 0) { + // TODO: 通过Anitomy检测名称中的集号 _logger.LogInformation("匹配失败,缺少集号. [{0}]{1}}", season.Name, episode.Name); continue; } @@ -685,12 +653,12 @@ public class LibraryManagerEventsHelper : IDisposable private void DeleteOldDanmu(BaseItem item) { // 存在旧弹幕xml文件 - //var oldDanmuPath = Path.Combine(item.ContainingFolderPath, item.FileNameWithoutExtension + ".xml"); - //var fileMeta = _fileSystem.GetFileInfo(oldDanmuPath); - //if (fileMeta.Exists) - //{ - // _fileSystem.DeleteFile(oldDanmuPath); - //} + var oldDanmuPath = Path.Combine(item.ContainingFolderPath, item.FileNameWithoutExtension + ".xml"); + var fileMeta = _fileSystem.GetFileInfo(oldDanmuPath); + if (fileMeta.Exists) + { + _fileSystem.DeleteFile(oldDanmuPath); + } } // 根据名称搜索对应的seasonId @@ -727,7 +695,7 @@ public class LibraryManagerEventsHelper : IDisposable continue; } - _logger.LogInformation("匹配成功. [{0}] seasonId: {1}", title, seasonId); + _logger.LogInformation("匹配成功. [{0}] seasonId: {1} score: {2}", title, seasonId, score); return seasonId; } } @@ -750,8 +718,17 @@ public class LibraryManagerEventsHelper : IDisposable return; } - foreach (var item in queue) + foreach (var queueItem in queue) { + // 获取最新的item数据 + var item = _libraryManager.GetItemById(queueItem.Id); + // 合并新添加的provider id + foreach (var pair in queueItem.ProviderIds) + { + item.ProviderIds[pair.Key] = pair.Value; + } + + // Console.WriteLine(JsonSerializer.Serialize(item)); await item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); } _logger.LogInformation("更新b站epid到元数据完成。item数:{0}", queue.Count); @@ -762,6 +739,13 @@ public class LibraryManagerEventsHelper : IDisposable // 下载弹幕xml文件 try { + // 弹幕一分钟内更新过,忽略处理(有时Update事件会重复执行) + if (IsRepeatAction(item)) + { + _logger.LogInformation("最近1分钟已更新过弹幕xml,忽略处理:{0}", item.Name); + return; + } + var bytes = await this._api.GetDanmuContentAsync(epId, CancellationToken.None).ConfigureAwait(false); await this.DownloadDanmuInternal(item, bytes); } @@ -776,6 +760,13 @@ public class LibraryManagerEventsHelper : IDisposable // 下载弹幕xml文件 try { + // 弹幕一分钟内更新过,忽略处理(有时Update事件会重复执行) + if (IsRepeatAction(item)) + { + _logger.LogInformation("最近1分钟已更新过弹幕xml,忽略处理:{0}", item.Name); + return; + } + var bytes = await this._api.GetDanmuContentAsync(bvid, CancellationToken.None).ConfigureAwait(false); await this.DownloadDanmuInternal(item, bytes); } @@ -790,6 +781,13 @@ public class LibraryManagerEventsHelper : IDisposable // 下载弹幕xml文件 try { + // 弹幕一分钟内更新过,忽略处理(有时Update事件会重复执行) + if (IsRepeatAction(item)) + { + _logger.LogInformation("最近1分钟已更新过弹幕xml,忽略处理:{0}", item.Name); + return; + } + var bytes = await this._api.GetDanmuContentByCidAsync(cid, CancellationToken.None).ConfigureAwait(false); await this.DownloadDanmuInternal(item, bytes); } @@ -799,6 +797,19 @@ public class LibraryManagerEventsHelper : IDisposable } } + private bool IsRepeatAction(BaseItem item) + { + var danmuPath = Path.Combine(item.ContainingFolderPath, item.FileNameWithoutExtension + ".xml"); + if (!File.Exists(danmuPath)) + { + return false; + } + + var lastWriteTime = File.GetLastWriteTime(danmuPath); + var diff = DateTime.Now - lastWriteTime; + return diff.TotalSeconds < 60; + } + private async Task DownloadDanmuInternal(BaseItem item, byte[] bytes) { // 下载弹幕xml文件 diff --git a/Jellyfin.Plugin.Danmu/ScheduledTasks/RefreshDanmakuTask.cs b/Jellyfin.Plugin.Danmu/ScheduledTasks/RefreshDanmakuTask.cs index e3337a9..cb9b6e2 100644 --- a/Jellyfin.Plugin.Danmu/ScheduledTasks/RefreshDanmakuTask.cs +++ b/Jellyfin.Plugin.Danmu/ScheduledTasks/RefreshDanmakuTask.cs @@ -93,7 +93,7 @@ namespace Jellyfin.Plugin.Danmu.ScheduledTasks } - // 推送下载最新的xml (season刷新会同时刷新episode,所以不需要再推送episode) + // 推送下载最新的xml (season刷新会同时刷新episode,所以不需要再推送episode,而且season是bv号的,只能通过season来刷新) switch (item) { case Movie: diff --git a/Jellyfin.Plugin.Danmu/ScheduledTasks/ScanLibraryTask.cs b/Jellyfin.Plugin.Danmu/ScheduledTasks/ScanLibraryTask.cs index 2ff9814..fe585af 100644 --- a/Jellyfin.Plugin.Danmu/ScheduledTasks/ScanLibraryTask.cs +++ b/Jellyfin.Plugin.Danmu/ScheduledTasks/ScanLibraryTask.cs @@ -85,21 +85,24 @@ namespace Jellyfin.Plugin.Danmu.ScheduledTasks continue; } - // 推送刷新(season刷新会同时刷新episode,所以不需要再推送episode) + // 推送刷新 (season刷新会同时刷新episode,所以不需要再推送episode,而且season是bv号的,只能通过season来刷新) switch (item) { case Movie: - await _libraryManagerEventsHelper.ProcessQueuedMovieEvents(new List() { new LibraryEvent { Item = item, EventType = EventType.Refresh } }, EventType.Refresh).ConfigureAwait(false); + await _libraryManagerEventsHelper.ProcessQueuedMovieEvents(new List() { new LibraryEvent { Item = item, EventType = EventType.Add } }, EventType.Add).ConfigureAwait(false); break; case Season: - await _libraryManagerEventsHelper.ProcessQueuedSeasonEvents(new List() { new LibraryEvent { Item = item, EventType = EventType.Refresh } }, EventType.Refresh).ConfigureAwait(false); + // 搜索匹配season的元数据 + await _libraryManagerEventsHelper.ProcessQueuedSeasonEvents(new List() { new LibraryEvent { Item = item, EventType = EventType.Add } }, EventType.Add).ConfigureAwait(false); + // 下载剧集弹幕 + await _libraryManagerEventsHelper.ProcessQueuedSeasonEvents(new List() { new LibraryEvent { Item = item, EventType = EventType.Update } }, EventType.Update).ConfigureAwait(false); break; // case Series: - // await _libraryManagerEventsHelper.ProcessQueuedShowEvents(new List() { new LibraryEvent { Item = item, EventType = EventType.Refresh } }, EventType.Refresh).ConfigureAwait(false); + // await _libraryManagerEventsHelper.ProcessQueuedShowEvents(new List() { new LibraryEvent { Item = item, EventType = EventType.Add } }, EventType.Add).ConfigureAwait(false); // break; // case Episode: - // await _libraryManagerEventsHelper.ProcessQueuedEpisodeEvents(new List() { new LibraryEvent { Item = item, EventType = EventType.Refresh } }, EventType.Refresh).ConfigureAwait(false); + // await _libraryManagerEventsHelper.ProcessQueuedEpisodeEvents(new List() { new LibraryEvent { Item = item, EventType = EventType.Add } }, EventType.Add).ConfigureAwait(false); // break; } } diff --git a/Jellyfin.Plugin.Danmu/ServiceRegistrator.cs b/Jellyfin.Plugin.Danmu/ServiceRegistrator.cs index 5eeee33..ab80bb8 100644 --- a/Jellyfin.Plugin.Danmu/ServiceRegistrator.cs +++ b/Jellyfin.Plugin.Danmu/ServiceRegistrator.cs @@ -5,11 +5,13 @@ using System.Text; using System.Threading.Tasks; using Jellyfin.Plugin.Danmu.Api; using Jellyfin.Plugin.Danmu.Providers; +using MediaBrowser.Controller.Providers; using MediaBrowser.Common.Plugins; using MediaBrowser.Controller.Library; using MediaBrowser.Model.IO; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using MediaBrowser.Controller.Persistence; namespace Jellyfin.Plugin.Danmu { @@ -25,7 +27,7 @@ namespace Jellyfin.Plugin.Danmu }); serviceCollection.AddSingleton((ctx) => { - return new LibraryManagerEventsHelper(ctx.GetRequiredService(), ctx.GetRequiredService(), ctx.GetRequiredService(), ctx.GetRequiredService()); + return new LibraryManagerEventsHelper(ctx.GetRequiredService(), ctx.GetRequiredService(), ctx.GetRequiredService(), ctx.GetRequiredService(), ctx.GetRequiredService()); }); } } diff --git a/README.md b/README.md index 91d3b1a..e9635eb 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ ass格式: ```sh $ dotnet restore -$ dotnet publish -c Release Jellyfin.Plugin.Danmu/Jellyfin.Plugin.Danmu.csproj +$ dotnet publish Jellyfin.Plugin.Danmu/Jellyfin.Plugin.Danmu.csproj ```