Skip to content

Commit

Permalink
Optimize handle danmu
Browse files Browse the repository at this point in the history
  • Loading branch information
cxfksword committed Feb 20, 2023
1 parent 6a55e37 commit a1b5e7c
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 40 deletions.
8 changes: 4 additions & 4 deletions Jellyfin.Plugin.Danmu.Test/MgtvTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public void TestAddMovie()
{
var libraryManagerStub = new Mock<ILibraryManager>();
var scraperManager = new ScraperManager(loggerFactory);
scraperManager.register(new Jellyfin.Plugin.Danmu.Scrapers.Mgtv.Mgtv(loggerFactory, libraryManagerStub.Object));
scraperManager.register(new Jellyfin.Plugin.Danmu.Scrapers.Mgtv.Mgtv(loggerFactory));

var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
var directoryServiceStub = new Mock<IDirectoryService>();
Expand Down Expand Up @@ -59,7 +59,7 @@ public void TestUpdateMovie()
{
var libraryManagerStub = new Mock<ILibraryManager>();
var scraperManager = new ScraperManager(loggerFactory);
scraperManager.register(new Jellyfin.Plugin.Danmu.Scrapers.Mgtv.Mgtv(loggerFactory, libraryManagerStub.Object));
scraperManager.register(new Jellyfin.Plugin.Danmu.Scrapers.Mgtv.Mgtv(loggerFactory));

var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
var directoryServiceStub = new Mock<IDirectoryService>();
Expand Down Expand Up @@ -96,7 +96,7 @@ public void TestAddSeason()
{
var libraryManagerStub = new Mock<ILibraryManager>();
var scraperManager = new ScraperManager(loggerFactory);
scraperManager.register(new Jellyfin.Plugin.Danmu.Scrapers.Mgtv.Mgtv(loggerFactory, libraryManagerStub.Object));
scraperManager.register(new Jellyfin.Plugin.Danmu.Scrapers.Mgtv.Mgtv(loggerFactory));

var fileSystemStub = new Mock<Jellyfin.Plugin.Danmu.Core.IFileSystem>();
var directoryServiceStub = new Mock<IDirectoryService>();
Expand Down Expand Up @@ -134,7 +134,7 @@ public void TestGetMedia()
try
{
var libraryManagerStub = new Mock<ILibraryManager>();
var api = new Mgtv(loggerFactory, libraryManagerStub.Object);
var api = new Mgtv(loggerFactory);
var media = await api.GetMedia(new Season(), "514446");
Console.WriteLine(media);
}
Expand Down
82 changes: 64 additions & 18 deletions Jellyfin.Plugin.Danmu/LibraryManagerEventsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ namespace Jellyfin.Plugin.Danmu;
public class LibraryManagerEventsHelper : IDisposable
{
private readonly List<LibraryEvent> _queuedEvents;
private readonly IMemoryCache _pendingAddEventCache;
private readonly MemoryCacheEntryOptions _expiredOption = new MemoryCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) };
private readonly IMemoryCache _memoryCache;
private readonly MemoryCacheEntryOptions _pendingAddExpiredOption = new MemoryCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) };
private readonly MemoryCacheEntryOptions _danmuUpdatedExpiredOption = new MemoryCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5) };

private readonly ILibraryManager _libraryManager;
private readonly ILogger<LibraryManagerEventsHelper> _logger;
Expand All @@ -62,7 +63,7 @@ public PluginConfiguration Config
public LibraryManagerEventsHelper(ILibraryManager libraryManager, ILoggerFactory loggerFactory, Jellyfin.Plugin.Danmu.Core.IFileSystem fileSystem, ScraperManager scraperManager)
{
_queuedEvents = new List<LibraryEvent>();
_pendingAddEventCache = new MemoryCache(new MemoryCacheOptions());
_memoryCache = new MemoryCache(new MemoryCacheOptions());

_libraryManager = libraryManager;
_logger = loggerFactory.CreateLogger<LibraryManagerEventsHelper>();
Expand Down Expand Up @@ -162,14 +163,14 @@ private async Task OnQueueTimerCallbackInternal()
{
case Movie when ev.EventType is EventType.Add:
_logger.LogInformation("Movie add: {0}", ev.Item.Name);
_pendingAddEventCache.Set<LibraryEvent>(ev.Item.Id, ev, _expiredOption);
_memoryCache.Set<LibraryEvent>(ev.Item.Id, ev, _pendingAddExpiredOption);
break;
case Movie when ev.EventType is EventType.Update:
_logger.LogInformation("Movie update: {0}", ev.Item.Name);
if (_pendingAddEventCache.TryGetValue<LibraryEvent>(ev.Item.Id, out LibraryEvent addMovieEv))
if (_memoryCache.TryGetValue<LibraryEvent>(ev.Item.Id, out LibraryEvent addMovieEv))
{
queuedMovieAdds.Add(addMovieEv);
_pendingAddEventCache.Remove(ev.Item.Id);
_memoryCache.Remove(ev.Item.Id);
}
else
{
Expand Down Expand Up @@ -198,14 +199,14 @@ private async Task OnQueueTimerCallbackInternal()
break;
case Season when ev.EventType is EventType.Add:
_logger.LogInformation("Season add: {0}", ev.Item.Name);
_pendingAddEventCache.Set<LibraryEvent>(ev.Item.Id, ev, _expiredOption);
_memoryCache.Set<LibraryEvent>(ev.Item.Id, ev, _pendingAddExpiredOption);
break;
case Season when ev.EventType is EventType.Update:
_logger.LogInformation("Season update: {0}", ev.Item.Name);
if (_pendingAddEventCache.TryGetValue<LibraryEvent>(ev.Item.Id, out LibraryEvent addSeasonEv))
if (_memoryCache.TryGetValue<LibraryEvent>(ev.Item.Id, out LibraryEvent addSeasonEv))
{
queuedSeasonAdds.Add(addSeasonEv);
_pendingAddEventCache.Remove(ev.Item.Id);
_memoryCache.Remove(ev.Item.Id);
}
else
{
Expand Down Expand Up @@ -466,9 +467,15 @@ public async Task ProcessQueuedSeasonEvents(IReadOnlyCollection<LibraryEvent> ev
var queueUpdateMeta = new List<BaseItem>();
foreach (var season in seasons)
{
// // 虚拟季第一次请求忽略
// if (season.LocationType == LocationType.Virtual && season.IndexNumber is null)
// {
// continue;
// }

if (season.IndexNumber.HasValue && season.IndexNumber == 0)
{
_logger.LogInformation("特典不处理:name={0} number={1}", season.Name, season.IndexNumber);
_logger.LogInformation("special特典文件夹不处理:name={0} number={1}", season.Name, season.IndexNumber);
continue;
}

Expand Down Expand Up @@ -518,6 +525,12 @@ public async Task ProcessQueuedSeasonEvents(IReadOnlyCollection<LibraryEvent> ev
{
foreach (var season in seasons)
{
// // 虚拟季第一次请求忽略
// if (season.LocationType == LocationType.Virtual && season.IndexNumber is null)
// {
// continue;
// }

var queueUpdateMeta = new List<BaseItem>();
// GetEpisodes一定要取所有fields,要不然更新会导致重建虚拟season季信息
var episodes = season.GetEpisodes(null, new DtoOptions(true));
Expand All @@ -526,6 +539,14 @@ public async Task ProcessQueuedSeasonEvents(IReadOnlyCollection<LibraryEvent> ev
continue;
}

// 不处理季文件夹下的特典和extras影片(动画经常会混在一起)
var episodesWithoutSP = episodes.Where(x => x.ParentIndexNumber != null && x.ParentIndexNumber > 0).ToList();
if (episodes.Count != episodesWithoutSP.Count)
{
_logger.LogInformation("{0}季存在{1}个特典或extra片段,忽略处理.", season.Name, (episodes.Count - episodesWithoutSP.Count));
episodes = episodesWithoutSP;
}

foreach (var scraper in _scraperManager.All())
{
try
Expand All @@ -545,16 +566,17 @@ public async Task ProcessQueuedSeasonEvents(IReadOnlyCollection<LibraryEvent> ev

foreach (var (episode, idx) in episodes.WithIndex())
{
var fileName = Path.GetFileName(episode.Path);
var indexNumber = episode.IndexNumber ?? 0;
if (indexNumber <= 0)
{
_logger.LogInformation("[{0}]匹配失败,缺少集号. [{1}]{2}", scraper.Name, season.Name, episode.Name);
_logger.LogInformation("[{0}]匹配失败,缺少集号. [{1}]{2}", scraper.Name, season.Name, fileName);
continue;
}

if (indexNumber > media.Episodes.Count)
{
_logger.LogInformation("[{0}]匹配失败,集号超过总集数,可能集号错误. [{1}]{2} indexNumber: {3}", scraper.Name, season.Name, episode.Name, indexNumber);
_logger.LogInformation("[{0}]匹配失败,集号超过总集数,可能识别集号错误. [{1}]{2} indexNumber: {3}", scraper.Name, season.Name, fileName, indexNumber);
continue;
}

Expand Down Expand Up @@ -697,10 +719,20 @@ public async Task ProcessQueuedEpisodeEvents(IReadOnlyCollection<LibraryEvent> e
var episodeList = season.GetEpisodes(null, new DtoOptions(true));
foreach (var (episode, idx) in episodeList.WithIndex())
{
var fileName = Path.GetFileName(episode.Path);

// 没对应剧集号的,忽略处理
var indexNumber = episode.IndexNumber ?? 0;
if (indexNumber < 1 || indexNumber > media.Episodes.Count)
{
_logger.LogInformation("[{0}]缺少集号或集号超过弹幕数,忽略处理. [{1}]{2}", scraper.Name, season.Name, fileName);
continue;
}

// 特典或extras影片不处理(动画经常会放在季文件夹下)
if (episode.ParentIndexNumber is null or 0)
{
_logger.LogInformation("[{0}]缺少季号,可能是特典或extras影片,忽略处理. [{1}]{2}", scraper.Name, season.Name, fileName);
continue;
}

Expand Down Expand Up @@ -757,17 +789,30 @@ public async Task DownloadDanmu(AbstractScraper scraper, BaseItem item, string c
try
{
// 弹幕5分钟内更新过,忽略处理(有时Update事件会重复执行)
if (!ignoreCheck && IsRepeatAction(item))
var checkDownloadedKey = $"{item.Id}_{commentId}";
if (!ignoreCheck && _memoryCache.TryGetValue(checkDownloadedKey, out var latestDownloaded))
{
_logger.LogInformation("[{0}]最近5分钟已更新过弹幕xml,忽略处理:{1}", scraper.Name, item.Name);
_logger.LogInformation("[{0}]最近5分钟已更新过弹幕xml,忽略处理:{1}.{2}", scraper.Name, item.IndexNumber, item.Name);
return;
}

_memoryCache.Set(checkDownloadedKey, true, _danmuUpdatedExpiredOption);
var danmaku = await scraper.GetDanmuContent(item, commentId);
if (danmaku != null)
{
await this.DownloadDanmuInternal(item, danmaku.ToXml());
this._logger.LogInformation("[{0}]弹幕下载成功:name={1}", scraper.Name, item.Name);
var bytes = danmaku.ToXml();
if (bytes.Length < 10 * 1024)
{
_memoryCache.Remove(checkDownloadedKey);
_logger.LogInformation("[{0}]弹幕内容少于10KB,忽略处理:{1}.{2}", scraper.Name, item.IndexNumber, item.Name);
return;
}
await this.SaveDanmu(item, bytes);
this._logger.LogInformation("[{0}]弹幕下载成功:name={1}.{2} commentId={3}", scraper.Name, item.IndexNumber, item.Name, commentId);
}
else
{
_memoryCache.Remove(checkDownloadedKey);
}
}
catch (Exception ex)
Expand All @@ -776,11 +821,12 @@ public async Task DownloadDanmu(AbstractScraper scraper, BaseItem item, string c
}
}

private bool IsRepeatAction(BaseItem item)
private bool IsRepeatAction(BaseItem item, string checkDownloadedKey)
{
// 单元测试时为null
if (item.FileNameWithoutExtension == null) return false;

// 通过xml文件属性判断(多线程时判断有误)
var danmuPath = Path.Combine(item.ContainingFolderPath, item.FileNameWithoutExtension + ".xml");
if (!this._fileSystem.Exists(danmuPath))
{
Expand All @@ -792,7 +838,7 @@ private bool IsRepeatAction(BaseItem item)
return diff.TotalSeconds < 300;
}

private async Task DownloadDanmuInternal(BaseItem item, byte[] bytes)
private async Task SaveDanmu(BaseItem item, byte[] bytes)
{
// 单元测试时为null
if (item.FileNameWithoutExtension == null) return;
Expand Down
4 changes: 2 additions & 2 deletions Jellyfin.Plugin.Danmu/Scrapers/Bilibili/Bilibili.cs
Original file line number Diff line number Diff line change
Expand Up @@ -302,15 +302,15 @@ private async Task<long> GetMatchBiliSeasonId(BaseItem item, string searchName)
var score = searchName.Distance(title);
if (score < 0.7)
{
log.LogInformation("[{0}] 标题差异太大,忽略处理. 搜索词:{1}, score: {2}", title, searchName, score);
log.LogDebug("[{0}] 标题差异太大,忽略处理. 搜索词:{1}, score: {2}", title, searchName, score);
continue;
}

// 检测年份是否一致
var itemPubYear = item.ProductionYear ?? 0;
if (itemPubYear > 0 && pubYear > 0 && itemPubYear != pubYear)
{
log.LogInformation("[{0}] 发行年份不一致,忽略处理. b站:{1} jellyfin: {2}", title, pubYear, itemPubYear);
log.LogDebug("[{0}] 发行年份不一致,忽略处理. b站:{1} jellyfin: {2}", title, pubYear, itemPubYear);
continue;
}

Expand Down
4 changes: 2 additions & 2 deletions Jellyfin.Plugin.Danmu/Scrapers/Dandan/Dandan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,15 @@ public override async Task<List<ScraperSearchInfo>> Search(BaseItem item)
var score = searchName.Distance(title);
if (score < 0.7)
{
log.LogInformation("[{0}] 标题差异太大,忽略处理. 搜索词:{1}, score: {2}", title, searchName, score);
log.LogDebug("[{0}] 标题差异太大,忽略处理. 搜索词:{1}, score: {2}", title, searchName, score);
continue;
}

// 检测年份是否一致
var itemPubYear = item.ProductionYear ?? 0;
if (itemPubYear > 0 && pubYear > 0 && itemPubYear != pubYear)
{
log.LogInformation("[{0}] 发行年份不一致,忽略处理. dandan:{1} jellyfin: {2}", title, pubYear, itemPubYear);
log.LogDebug("[{0}] 发行年份不一致,忽略处理. dandan:{1} jellyfin: {2}", title, pubYear, itemPubYear);
continue;
}

Expand Down
4 changes: 2 additions & 2 deletions Jellyfin.Plugin.Danmu/Scrapers/Iqiyi/Iqiyi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,15 @@ public override async Task<List<ScraperSearchInfo>> Search(BaseItem item)
var score = searchName.Distance(title);
if (score < 0.7)
{
log.LogInformation("[{0}] 标题差异太大,忽略处理. 搜索词:{1}, score: {2}", title, searchName, score);
log.LogDebug("[{0}] 标题差异太大,忽略处理. 搜索词:{1}, score: {2}", title, searchName, score);
continue;
}

// 检测年份是否一致
var itemPubYear = item.ProductionYear ?? 0;
if (itemPubYear > 0 && pubYear > 0 && itemPubYear != pubYear)
{
log.LogInformation("[{0}] 发行年份不一致,忽略处理. Iqiyi:{1} jellyfin: {2}", title, pubYear, itemPubYear);
log.LogDebug("[{0}] 发行年份不一致,忽略处理. Iqiyi:{1} jellyfin: {2}", title, pubYear, itemPubYear);
continue;
}

Expand Down
13 changes: 5 additions & 8 deletions Jellyfin.Plugin.Danmu/Scrapers/Mgtv/Mgtv.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,11 @@ public class Mgtv : AbstractScraper
public const string ScraperProviderId = "MgtvID";

private readonly MgtvApi _api;
private readonly ILibraryManager _libraryManager;

public Mgtv(ILoggerFactory logManager, ILibraryManager libraryManager)
public Mgtv(ILoggerFactory logManager)
: base(logManager.CreateLogger<Mgtv>())
{
_api = new MgtvApi(logManager);
_libraryManager = libraryManager;
}

public override int DefaultOrder => 6;
Expand Down Expand Up @@ -112,15 +110,15 @@ public override async Task<List<ScraperSearchInfo>> Search(BaseItem item)
var score = searchName.Distance(title);
if (score < 0.7)
{
log.LogInformation("[{0}] 标题差异太大,忽略处理. 搜索词:{1}, score: {2}", title, searchName, score);
log.LogDebug("[{0}] 标题差异太大,忽略处理. 搜索词:{1}, score: {2}", title, searchName, score);
continue;
}

// 检测年份是否一致
var itemPubYear = item.ProductionYear ?? 0;
if (itemPubYear > 0 && pubYear > 0 && itemPubYear != pubYear)
{
log.LogInformation("[{0}] 发行年份不一致,忽略处理. year: {1} jellyfin: {2}", title, pubYear, itemPubYear);
log.LogDebug("[{0}] 发行年份不一致,忽略处理. year: {1} jellyfin: {2}", title, pubYear, itemPubYear);
continue;
}

Expand Down Expand Up @@ -181,9 +179,8 @@ public override async Task<List<ScraperSearchInfo>> Search(BaseItem item)


// 从季信息元数据中,获取cid值
// 没SXX季文件夹时,GetParent是Series,有时,GetParent是Season,所以需要通过seasonId中获取
var seasonId = ((MediaBrowser.Controller.Entities.TV.Episode)item).FindSeasonId();
var season = _libraryManager.GetItemById(seasonId);
// 不能通过GetParent获取Season,因为没有SXX季文件夹时,GetParent是Series
var season = ((MediaBrowser.Controller.Entities.TV.Episode)item).Season;
season.ProviderIds.TryGetValue(ScraperProviderId, out var cid);
return new ScraperEpisode() { Id = id, CommentId = $"{cid},{id}" };
}
Expand Down
4 changes: 2 additions & 2 deletions Jellyfin.Plugin.Danmu/Scrapers/Tencent/Tencent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,15 @@ public override async Task<List<ScraperSearchInfo>> Search(BaseItem item)
var score = searchName.Distance(title);
if (score < 0.7)
{
log.LogInformation("[{0}] 标题差异太大,忽略处理. 搜索词:{1}, score: {2}", title, searchName, score);
log.LogDebug("[{0}] 标题差异太大,忽略处理. 搜索词:{1}, score: {2}", title, searchName, score);
continue;
}

// 检测年份是否一致
var itemPubYear = item.ProductionYear ?? 0;
if (itemPubYear > 0 && pubYear > 0 && itemPubYear != pubYear)
{
log.LogInformation("[{0}] 发行年份不一致,忽略处理. year: {1} jellyfin: {2}", title, pubYear, itemPubYear);
log.LogDebug("[{0}] 发行年份不一致,忽略处理. year: {1} jellyfin: {2}", title, pubYear, itemPubYear);
continue;
}

Expand Down
Loading

0 comments on commit a1b5e7c

Please sign in to comment.