aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Server.Implementations
diff options
context:
space:
mode:
authorJPVenson <JPVenson@users.noreply.github.com>2024-09-07 22:56:51 +0200
committerGitHub <noreply@github.com>2024-09-07 14:56:51 -0600
commit5ceedced1c4a8bac5b5b7a5f2bd0913783bd427b (patch)
treee655f4307f550761b855c2acaddb83dd6ecfa11c /Jellyfin.Server.Implementations
parentfc247dab9243b3de42193c4be351636204dbc2f2 (diff)
Feature/media segments plugin api (#12359)
Diffstat (limited to 'Jellyfin.Server.Implementations')
-rw-r--r--Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs102
1 files changed, 101 insertions, 1 deletions
diff --git a/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs b/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs
index 7916d15c9..9953c05be 100644
--- a/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs
+++ b/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs
@@ -1,14 +1,23 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
+using System.Globalization;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model;
using MediaBrowser.Model.MediaSegments;
using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
namespace Jellyfin.Server.Implementations.MediaSegments;
@@ -17,15 +26,89 @@ namespace Jellyfin.Server.Implementations.MediaSegments;
/// </summary>
public class MediaSegmentManager : IMediaSegmentManager
{
+ private readonly ILogger<MediaSegmentManager> _logger;
private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
+ private readonly IMediaSegmentProvider[] _segmentProviders;
+ private readonly ILibraryManager _libraryManager;
/// <summary>
/// Initializes a new instance of the <see cref="MediaSegmentManager"/> class.
/// </summary>
+ /// <param name="logger">Logger.</param>
/// <param name="dbProvider">EFCore Database factory.</param>
- public MediaSegmentManager(IDbContextFactory<JellyfinDbContext> dbProvider)
+ /// <param name="segmentProviders">List of all media segment providers.</param>
+ /// <param name="libraryManager">Library manager.</param>
+ public MediaSegmentManager(
+ ILogger<MediaSegmentManager> logger,
+ IDbContextFactory<JellyfinDbContext> dbProvider,
+ IEnumerable<IMediaSegmentProvider> segmentProviders,
+ ILibraryManager libraryManager)
{
+ _logger = logger;
_dbProvider = dbProvider;
+
+ _segmentProviders = segmentProviders
+ .OrderBy(i => i is IHasOrder hasOrder ? hasOrder.Order : 0)
+ .ToArray();
+ _libraryManager = libraryManager;
+ }
+
+ /// <inheritdoc/>
+ public async Task RunSegmentPluginProviders(BaseItem baseItem, bool overwrite, CancellationToken cancellationToken)
+ {
+ var libraryOptions = _libraryManager.GetLibraryOptions(baseItem);
+ var providers = _segmentProviders
+ .Where(e => !libraryOptions.DisabledMediaSegmentProviders.Contains(GetProviderId(e.Name)))
+ .OrderBy(i =>
+ {
+ var index = libraryOptions.MediaSegmentProvideOrder.IndexOf(i.Name);
+ return index == -1 ? int.MaxValue : index;
+ })
+ .ToList();
+
+ _logger.LogInformation("Start media segment extraction from providers with {CountProviders} enabled", providers.Count);
+ using var db = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
+
+ if (!overwrite && (await db.MediaSegments.AnyAsync(e => e.ItemId.Equals(baseItem.Id), cancellationToken).ConfigureAwait(false)))
+ {
+ _logger.LogInformation("Skip {MediaPath} as it already contains media segments", baseItem.Path);
+ return;
+ }
+
+ _logger.LogInformation("Clear existing Segments for {MediaPath}", baseItem.Path);
+
+ await db.MediaSegments.Where(e => e.ItemId.Equals(baseItem.Id)).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
+
+ // no need to recreate the request object every time.
+ var requestItem = new MediaSegmentGenerationRequest() { ItemId = baseItem.Id };
+
+ foreach (var provider in providers)
+ {
+ if (!await provider.Supports(baseItem).ConfigureAwait(false))
+ {
+ _logger.LogDebug("Media Segment provider {ProviderName} does not support item with path {Path}", provider.Name, baseItem.Path);
+ continue;
+ }
+
+ _logger.LogDebug("Run Media Segment provider {ProviderName}", provider.Name);
+ try
+ {
+ var segments = await provider.GetMediaSegments(requestItem, cancellationToken)
+ .ConfigureAwait(false);
+
+ _logger.LogInformation("Media Segment provider {ProviderName} found {CountSegments} for {MediaPath}", provider.Name, segments.Count, baseItem.Path);
+ var providerId = GetProviderId(provider.Name);
+ foreach (var segment in segments)
+ {
+ segment.ItemId = baseItem.Id;
+ await CreateSegmentAsync(segment, providerId).ConfigureAwait(false);
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Provider {ProviderName} failed to extract segments from {MediaPath}", provider.Name, baseItem.Path);
+ }
+ }
}
/// <inheritdoc />
@@ -103,4 +186,21 @@ public class MediaSegmentManager : IMediaSegmentManager
{
return baseItem.MediaType is Data.Enums.MediaType.Video or Data.Enums.MediaType.Audio;
}
+
+ /// <inheritdoc/>
+ public IEnumerable<(string Name, string Id)> GetSupportedProviders(BaseItem item)
+ {
+ if (item is not (Video or Audio))
+ {
+ return [];
+ }
+
+ return _segmentProviders
+ .Select(p => (p.Name, GetProviderId(p.Name)));
+ }
+
+ private string GetProviderId(string name)
+ => name.ToLowerInvariant()
+ .GetMD5()
+ .ToString("N", CultureInfo.InvariantCulture);
}