< Summary - Jellyfin

Information
Class: Jellyfin.Server.Implementations.MediaSegments.MediaSegmentManager
Assembly: Jellyfin.Server.Implementations
File(s): /srv/git/jellyfin/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs
Line coverage
20%
Covered lines: 7
Uncovered lines: 28
Coverable lines: 35
Total lines: 206
Line coverage: 20%
Branch coverage
0%
Covered branches: 0
Total branches: 8
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
Map(...)100%210%
Map(...)100%210%
HasSegments(...)100%210%
IsTypeSupported(...)0%620%
GetSupportedProviders(...)0%4260%
GetProviderId(...)100%210%

File(s)

/srv/git/jellyfin/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Collections.Immutable;
 4using System.Globalization;
 5using System.Linq;
 6using System.Threading;
 7using System.Threading.Tasks;
 8using Jellyfin.Data.Entities;
 9using Jellyfin.Data.Enums;
 10using Jellyfin.Extensions;
 11using MediaBrowser.Common.Extensions;
 12using MediaBrowser.Controller;
 13using MediaBrowser.Controller.Entities;
 14using MediaBrowser.Controller.Entities.Audio;
 15using MediaBrowser.Controller.Library;
 16using MediaBrowser.Controller.Providers;
 17using MediaBrowser.Model;
 18using MediaBrowser.Model.MediaSegments;
 19using Microsoft.EntityFrameworkCore;
 20using Microsoft.Extensions.Logging;
 21
 22namespace Jellyfin.Server.Implementations.MediaSegments;
 23
 24/// <summary>
 25/// Manages media segments retrival and storage.
 26/// </summary>
 27public class MediaSegmentManager : IMediaSegmentManager
 28{
 29    private readonly ILogger<MediaSegmentManager> _logger;
 30    private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
 31    private readonly IMediaSegmentProvider[] _segmentProviders;
 32    private readonly ILibraryManager _libraryManager;
 33
 34    /// <summary>
 35    /// Initializes a new instance of the <see cref="MediaSegmentManager"/> class.
 36    /// </summary>
 37    /// <param name="logger">Logger.</param>
 38    /// <param name="dbProvider">EFCore Database factory.</param>
 39    /// <param name="segmentProviders">List of all media segment providers.</param>
 40    /// <param name="libraryManager">Library manager.</param>
 41    public MediaSegmentManager(
 42        ILogger<MediaSegmentManager> logger,
 43        IDbContextFactory<JellyfinDbContext> dbProvider,
 44        IEnumerable<IMediaSegmentProvider> segmentProviders,
 45        ILibraryManager libraryManager)
 46    {
 2247        _logger = logger;
 2248        _dbProvider = dbProvider;
 49
 2250        _segmentProviders = segmentProviders
 2251            .OrderBy(i => i is IHasOrder hasOrder ? hasOrder.Order : 0)
 2252            .ToArray();
 2253        _libraryManager = libraryManager;
 2254    }
 55
 56    /// <inheritdoc/>
 57    public async Task RunSegmentPluginProviders(BaseItem baseItem, bool overwrite, CancellationToken cancellationToken)
 58    {
 59        var libraryOptions = _libraryManager.GetLibraryOptions(baseItem);
 60        var providers = _segmentProviders
 61            .Where(e => !libraryOptions.DisabledMediaSegmentProviders.Contains(GetProviderId(e.Name)))
 62            .OrderBy(i =>
 63                {
 64                    var index = libraryOptions.MediaSegmentProvideOrder.IndexOf(i.Name);
 65                    return index == -1 ? int.MaxValue : index;
 66                })
 67            .ToList();
 68
 69        _logger.LogInformation("Start media segment extraction from providers with {CountProviders} enabled", providers.
 70        using var db = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
 71
 72        if (!overwrite && (await db.MediaSegments.AnyAsync(e => e.ItemId.Equals(baseItem.Id), cancellationToken).Configu
 73        {
 74            _logger.LogInformation("Skip {MediaPath} as it already contains media segments", baseItem.Path);
 75            return;
 76        }
 77
 78        _logger.LogInformation("Clear existing Segments for {MediaPath}", baseItem.Path);
 79
 80        await db.MediaSegments.Where(e => e.ItemId.Equals(baseItem.Id)).ExecuteDeleteAsync(cancellationToken).ConfigureA
 81
 82        // no need to recreate the request object every time.
 83        var requestItem = new MediaSegmentGenerationRequest() { ItemId = baseItem.Id };
 84
 85        foreach (var provider in providers)
 86        {
 87            if (!await provider.Supports(baseItem).ConfigureAwait(false))
 88            {
 89                _logger.LogDebug("Media Segment provider {ProviderName} does not support item with path {Path}", provide
 90                continue;
 91            }
 92
 93            _logger.LogDebug("Run Media Segment provider {ProviderName}", provider.Name);
 94            try
 95            {
 96                var segments = await provider.GetMediaSegments(requestItem, cancellationToken)
 97                    .ConfigureAwait(false);
 98
 99                _logger.LogInformation("Media Segment provider {ProviderName} found {CountSegments} for {MediaPath}", pr
 100                var providerId = GetProviderId(provider.Name);
 101                foreach (var segment in segments)
 102                {
 103                    segment.ItemId = baseItem.Id;
 104                    await CreateSegmentAsync(segment, providerId).ConfigureAwait(false);
 105                }
 106            }
 107            catch (Exception ex)
 108            {
 109                _logger.LogError(ex, "Provider {ProviderName} failed to extract segments from {MediaPath}", provider.Nam
 110            }
 111        }
 112    }
 113
 114    /// <inheritdoc />
 115    public async Task<MediaSegmentDto> CreateSegmentAsync(MediaSegmentDto mediaSegment, string segmentProviderId)
 116    {
 117        ArgumentOutOfRangeException.ThrowIfLessThan(mediaSegment.EndTicks, mediaSegment.StartTicks);
 118
 119        using var db = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 120        db.MediaSegments.Add(Map(mediaSegment, segmentProviderId));
 121        await db.SaveChangesAsync().ConfigureAwait(false);
 122        return mediaSegment;
 123    }
 124
 125    /// <inheritdoc />
 126    public async Task DeleteSegmentAsync(Guid segmentId)
 127    {
 128        using var db = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 129        await db.MediaSegments.Where(e => e.Id.Equals(segmentId)).ExecuteDeleteAsync().ConfigureAwait(false);
 130    }
 131
 132    /// <inheritdoc />
 133    public async Task<IEnumerable<MediaSegmentDto>> GetSegmentsAsync(Guid itemId, IEnumerable<MediaSegmentType>? typeFil
 134    {
 135        using var db = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 136
 137        var query = db.MediaSegments
 138            .Where(e => e.ItemId.Equals(itemId));
 139
 140        if (typeFilter is not null)
 141        {
 142            query = query.Where(e => typeFilter.Contains(e.Type));
 143        }
 144
 145        return query
 146            .OrderBy(e => e.StartTicks)
 147            .AsNoTracking()
 148            .ToImmutableList()
 149            .Select(Map);
 150    }
 151
 152    private static MediaSegmentDto Map(MediaSegment segment)
 153    {
 0154        return new MediaSegmentDto()
 0155        {
 0156            Id = segment.Id,
 0157            EndTicks = segment.EndTicks,
 0158            ItemId = segment.ItemId,
 0159            StartTicks = segment.StartTicks,
 0160            Type = segment.Type
 0161        };
 162    }
 163
 164    private static MediaSegment Map(MediaSegmentDto segment, string segmentProviderId)
 165    {
 0166        return new MediaSegment()
 0167        {
 0168            Id = segment.Id,
 0169            EndTicks = segment.EndTicks,
 0170            ItemId = segment.ItemId,
 0171            StartTicks = segment.StartTicks,
 0172            Type = segment.Type,
 0173            SegmentProviderId = segmentProviderId
 0174        };
 175    }
 176
 177    /// <inheritdoc />
 178    public bool HasSegments(Guid itemId)
 179    {
 0180        using var db = _dbProvider.CreateDbContext();
 0181        return db.MediaSegments.Any(e => e.ItemId.Equals(itemId));
 0182    }
 183
 184    /// <inheritdoc/>
 185    public bool IsTypeSupported(BaseItem baseItem)
 186    {
 0187        return baseItem.MediaType is Data.Enums.MediaType.Video or Data.Enums.MediaType.Audio;
 188    }
 189
 190    /// <inheritdoc/>
 191    public IEnumerable<(string Name, string Id)> GetSupportedProviders(BaseItem item)
 192    {
 0193        if (item is not (Video or Audio))
 194        {
 0195            return [];
 196        }
 197
 0198        return _segmentProviders
 0199            .Select(p => (p.Name, GetProviderId(p.Name)));
 200    }
 201
 202    private string GetProviderId(string name)
 0203        => name.ToLowerInvariant()
 0204            .GetMD5()
 0205            .ToString("N", CultureInfo.InvariantCulture);
 206}