mirror of
https://github.com/metatube-community/jellyfin-plugin-metatube.git
synced 2026-02-03 02:25:02 +08:00
198 lines
6.9 KiB
C#
198 lines
6.9 KiB
C#
using System.Text.RegularExpressions;
|
|
using Jellyfin.Plugin.MetaTube.Extensions;
|
|
using MediaBrowser.Controller.Entities;
|
|
using MediaBrowser.Controller.Library;
|
|
using MediaBrowser.Model.Entities;
|
|
using MediaBrowser.Model.Tasks;
|
|
#if __EMBY__
|
|
using MediaBrowser.Controller.Entities.Movies;
|
|
using MediaBrowser.Model.Logging;
|
|
|
|
#else
|
|
using MediaBrowser.Controller.Sorting;
|
|
using Microsoft.Extensions.Logging;
|
|
using Jellyfin.Data.Enums;
|
|
#endif
|
|
|
|
namespace Jellyfin.Plugin.MetaTube.ScheduledTasks;
|
|
|
|
public class OrganizeMetadataTask : IScheduledTask
|
|
{
|
|
private readonly ILibraryManager _libraryManager;
|
|
private readonly ILogger _logger;
|
|
|
|
#if __EMBY__
|
|
public OrganizeMetadataTask(ILogManager logManager, ILibraryManager libraryManager)
|
|
{
|
|
_logger = logManager.CreateLogger<OrganizeMetadataTask>();
|
|
_libraryManager = libraryManager;
|
|
}
|
|
#else
|
|
public OrganizeMetadataTask(ILogger<OrganizeMetadataTask> logger, ILibraryManager libraryManager)
|
|
{
|
|
_logger = logger;
|
|
_libraryManager = libraryManager;
|
|
}
|
|
#endif
|
|
|
|
public string Key => $"{Plugin.ProviderName}OrganizeMetadata";
|
|
|
|
public string Name => "Organize Metadata";
|
|
|
|
public string Description => $"Organizes video metadata provided by {Plugin.ProviderName} in library.";
|
|
|
|
public string Category => Plugin.ProviderName;
|
|
|
|
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
|
{
|
|
yield return new TaskTriggerInfo
|
|
{
|
|
#if __EMBY__
|
|
Type = TaskTriggerInfo.TriggerDaily,
|
|
#else
|
|
Type = TaskTriggerInfoType.DailyTrigger,
|
|
#endif
|
|
TimeOfDayTicks = TimeSpan.FromHours(3).Ticks
|
|
};
|
|
}
|
|
|
|
#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);
|
|
|
|
var items = _libraryManager.GetItemList(new InternalItemsQuery
|
|
{
|
|
MediaTypes = new[] { MediaType.Video },
|
|
#if __EMBY__
|
|
HasAnyProviderId = new[] { Plugin.ProviderId },
|
|
IncludeItemTypes = new[] { nameof(Movie) },
|
|
#else
|
|
HasAnyProviderId = new Dictionary<string, string> { { Plugin.ProviderId, string.Empty } },
|
|
IncludeItemTypes = new[] { BaseItemKind.Movie }
|
|
#endif
|
|
}).ToList();
|
|
|
|
foreach (var (idx, item) in items.WithIndex())
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
progress?.Report((double)idx / items.Count * 100);
|
|
|
|
var genres = item.Genres?.ToList() ?? new List<string>();
|
|
|
|
try
|
|
{
|
|
switch (HasEmbeddedChineseSubtitle(item.FileNameWithoutExtension) ||
|
|
HasExternalChineseSubtitle(item.Path))
|
|
{
|
|
// Add `ChineseSubtitle` genre.
|
|
case true when !genres.Contains(ChineseSubtitle):
|
|
{
|
|
genres.Add(ChineseSubtitle);
|
|
if (Plugin.Instance.Configuration.EnableBadges)
|
|
await SetPrimaryImage(item, Plugin.Instance.Configuration.BadgeUrl, cancellationToken);
|
|
break;
|
|
}
|
|
// Remove `ChineseSubtitle` genre.
|
|
case false when genres.Contains(ChineseSubtitle):
|
|
{
|
|
genres.RemoveAll(s => s.Equals(ChineseSubtitle));
|
|
if (Plugin.Instance.Configuration.EnableBadges)
|
|
await SetPrimaryImage(item, string.Empty, cancellationToken);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_logger.Error("Update ChineseSubtitle for video {0}: {1}", item.Name, e.Message);
|
|
}
|
|
|
|
// Remove duplicates.
|
|
var orderedGenres =
|
|
(Plugin.Instance.Configuration.EnableGenreSubstitution
|
|
// Substitute genres.
|
|
? Plugin.Instance.Configuration.GetGenreSubstitutionTable().Substitute(genres)
|
|
: genres).Distinct().OrderByString(genre => genre).ToList();
|
|
|
|
// Skip updating item if equal.
|
|
if (!orderedGenres.Any() ||
|
|
(item.Genres?.SequenceEqual(orderedGenres, StringComparer.OrdinalIgnoreCase)).GetValueOrDefault(false))
|
|
continue;
|
|
|
|
item.Genres = orderedGenres.ToArray();
|
|
|
|
_logger.Info("Organize metadata for video: {0}", item.Name);
|
|
|
|
#if __EMBY__
|
|
_libraryManager.UpdateItem(item, item, ItemUpdateType.MetadataEdit, null);
|
|
#else
|
|
await _libraryManager
|
|
.UpdateItemAsync(item, item, ItemUpdateType.MetadataEdit, cancellationToken)
|
|
.ConfigureAwait(false);
|
|
#endif
|
|
}
|
|
|
|
progress?.Report(100);
|
|
}
|
|
|
|
#region Helper
|
|
|
|
private const string ChineseSubtitle = "中文字幕";
|
|
|
|
private static bool HasTag(string filename, string tag)
|
|
{
|
|
var r = new Regex(@"[-_\s]", RegexOptions.Compiled);
|
|
return r.Split(filename).Contains(tag, StringComparer.OrdinalIgnoreCase);
|
|
}
|
|
|
|
private static bool HasTag(string filename, params string[] tags)
|
|
{
|
|
return tags.Any(tag => HasTag(filename, tag));
|
|
}
|
|
|
|
private static bool HasEmbeddedChineseSubtitle(string filename)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(filename))
|
|
return false;
|
|
|
|
return filename.Contains(ChineseSubtitle) || HasTag(filename, "C", "UC", "ch");
|
|
}
|
|
|
|
private static bool HasExternalChineseSubtitle(string path)
|
|
{
|
|
return HasExternalChineseSubtitle(Path.GetFileNameWithoutExtension(path),
|
|
Directory.GetParent(path)?.GetFiles().Select(info => info.Name));
|
|
}
|
|
|
|
private static bool HasExternalChineseSubtitle(string basename, IEnumerable<string> files)
|
|
{
|
|
var r = new Regex(@"\.(ch[ist]|zho?(-(cn|hk|sg|tw))?)\.(ass|srt|ssa|smi|sub|idx|psb|vtt)$",
|
|
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
|
|
return files.Any(name => r.IsMatch(name) &&
|
|
r.Replace(name, string.Empty)
|
|
.Equals(basename, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
private static async Task SetPrimaryImage(BaseItem item, string badge, CancellationToken cancellationToken)
|
|
{
|
|
var pid = item.GetPid(Plugin.ProviderId);
|
|
if (string.IsNullOrWhiteSpace(pid.Id) || string.IsNullOrWhiteSpace(pid.Provider))
|
|
return;
|
|
|
|
var m = await ApiClient.GetMovieInfoAsync(pid.Provider, pid.Id, cancellationToken);
|
|
// Set first primary image.
|
|
item.SetImage(new ItemImageInfo
|
|
{
|
|
Path = ApiClient.GetPrimaryImageApiUrl(m.Provider, m.Id, pid.Position ?? -1, badge),
|
|
Type = ImageType.Primary
|
|
}, 0);
|
|
}
|
|
|
|
#endregion
|
|
} |