< 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: 246
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.Database.Implementations;
 9using Jellyfin.Database.Implementations.Entities;
 10using Jellyfin.Database.Implementations.Enums;
 11using Jellyfin.Extensions;
 12using MediaBrowser.Common.Extensions;
 13using MediaBrowser.Controller;
 14using MediaBrowser.Controller.Entities;
 15using MediaBrowser.Controller.Entities.Audio;
 16using MediaBrowser.Controller.Library;
 17using MediaBrowser.Controller.Providers;
 18using MediaBrowser.Model;
 19using MediaBrowser.Model.MediaSegments;
 20using Microsoft.EntityFrameworkCore;
 21using Microsoft.Extensions.Logging;
 22
 23namespace Jellyfin.Server.Implementations.MediaSegments;
 24
 25/// <summary>
 26/// Manages media segments retrieval and storage.
 27/// </summary>
 28public class MediaSegmentManager : IMediaSegmentManager
 29{
 30    private readonly ILogger<MediaSegmentManager> _logger;
 31    private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
 32    private readonly IMediaSegmentProvider[] _segmentProviders;
 33    private readonly ILibraryManager _libraryManager;
 34
 35    /// <summary>
 36    /// Initializes a new instance of the <see cref="MediaSegmentManager"/> class.
 37    /// </summary>
 38    /// <param name="logger">Logger.</param>
 39    /// <param name="dbProvider">EFCore Database factory.</param>
 40    /// <param name="segmentProviders">List of all media segment providers.</param>
 41    /// <param name="libraryManager">Library manager.</param>
 42    public MediaSegmentManager(
 43        ILogger<MediaSegmentManager> logger,
 44        IDbContextFactory<JellyfinDbContext> dbProvider,
 45        IEnumerable<IMediaSegmentProvider> segmentProviders,
 46        ILibraryManager libraryManager)
 47    {
 2148        _logger = logger;
 2149        _dbProvider = dbProvider;
 50
 2151        _segmentProviders = segmentProviders
 2152            .OrderBy(i => i is IHasOrder hasOrder ? hasOrder.Order : 0)
 2153            .ToArray();
 2154        _libraryManager = libraryManager;
 2155    }
 56
 57    /// <inheritdoc/>
 58    public async Task RunSegmentPluginProviders(BaseItem baseItem, bool overwrite, CancellationToken cancellationToken)
 59    {
 60        var libraryOptions = _libraryManager.GetLibraryOptions(baseItem);
 61        var providers = _segmentProviders
 62            .Where(e => !libraryOptions.DisabledMediaSegmentProviders.Contains(GetProviderId(e.Name)))
 63            .OrderBy(i =>
 64                {
 65                    var index = libraryOptions.MediaSegmentProviderOrder.IndexOf(i.Name);
 66                    return index == -1 ? int.MaxValue : index;
 67                })
 68            .ToList();
 69
 70        if (providers.Count == 0)
 71        {
 72            _logger.LogDebug("Skipping media segment extraction as no providers are enabled for {MediaPath}", baseItem.P
 73            return;
 74        }
 75
 76        using var db = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
 77
 78        if (!overwrite && (await db.MediaSegments.AnyAsync(e => e.ItemId.Equals(baseItem.Id), cancellationToken).Configu
 79        {
 80            _logger.LogDebug("Skip {MediaPath} as it already contains media segments", baseItem.Path);
 81            return;
 82        }
 83
 84        _logger.LogDebug("Start media segment extraction for {MediaPath} with {CountProviders} providers enabled", baseI
 85
 86        await db.MediaSegments.Where(e => e.ItemId.Equals(baseItem.Id)).ExecuteDeleteAsync(cancellationToken).ConfigureA
 87
 88        // no need to recreate the request object every time.
 89        var requestItem = new MediaSegmentGenerationRequest() { ItemId = baseItem.Id };
 90
 91        foreach (var provider in providers)
 92        {
 93            if (!await provider.Supports(baseItem).ConfigureAwait(false))
 94            {
 95                _logger.LogDebug("Media Segment provider {ProviderName} does not support item with path {MediaPath}", pr
 96                continue;
 97            }
 98
 99            try
 100            {
 101                var segments = await provider.GetMediaSegments(requestItem, cancellationToken)
 102                    .ConfigureAwait(false);
 103                if (segments.Count == 0)
 104                {
 105                    _logger.LogDebug("Media Segment provider {ProviderName} did not find any segments for {MediaPath}", 
 106                    continue;
 107                }
 108
 109                _logger.LogInformation("Media Segment provider {ProviderName} found {CountSegments} for {MediaPath}", pr
 110                var providerId = GetProviderId(provider.Name);
 111                foreach (var segment in segments)
 112                {
 113                    segment.ItemId = baseItem.Id;
 114                    await CreateSegmentAsync(segment, providerId).ConfigureAwait(false);
 115                }
 116            }
 117            catch (Exception ex)
 118            {
 119                _logger.LogError(ex, "Provider {ProviderName} failed to extract segments from {MediaPath}", provider.Nam
 120            }
 121        }
 122    }
 123
 124    /// <inheritdoc />
 125    public async Task<MediaSegmentDto> CreateSegmentAsync(MediaSegmentDto mediaSegment, string segmentProviderId)
 126    {
 127        ArgumentOutOfRangeException.ThrowIfLessThan(mediaSegment.EndTicks, mediaSegment.StartTicks);
 128
 129        using var db = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 130        db.MediaSegments.Add(Map(mediaSegment, segmentProviderId));
 131        await db.SaveChangesAsync().ConfigureAwait(false);
 132        return mediaSegment;
 133    }
 134
 135    /// <inheritdoc />
 136    public async Task DeleteSegmentAsync(Guid segmentId)
 137    {
 138        using var db = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 139        await db.MediaSegments.Where(e => e.Id.Equals(segmentId)).ExecuteDeleteAsync().ConfigureAwait(false);
 140    }
 141
 142    /// <inheritdoc />
 143    public async Task<IEnumerable<MediaSegmentDto>> GetSegmentsAsync(Guid itemId, IEnumerable<MediaSegmentType>? typeFil
 144    {
 145        var baseItem = _libraryManager.GetItemById(itemId);
 146
 147        if (baseItem is null)
 148        {
 149            _logger.LogError("Tried to request segments for an invalid item");
 150            return [];
 151        }
 152
 153        return await GetSegmentsAsync(baseItem, typeFilter, filterByProvider).ConfigureAwait(false);
 154    }
 155
 156    /// <inheritdoc />
 157    public async Task<IEnumerable<MediaSegmentDto>> GetSegmentsAsync(BaseItem item, IEnumerable<MediaSegmentType>? typeF
 158    {
 159        using var db = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 160
 161        var query = db.MediaSegments
 162            .Where(e => e.ItemId.Equals(item.Id));
 163
 164        if (typeFilter is not null)
 165        {
 166            query = query.Where(e => typeFilter.Contains(e.Type));
 167        }
 168
 169        if (filterByProvider)
 170        {
 171            var libraryOptions = _libraryManager.GetLibraryOptions(item);
 172            var providerIds = _segmentProviders
 173                .Where(e => !libraryOptions.DisabledMediaSegmentProviders.Contains(GetProviderId(e.Name)))
 174                .Select(f => GetProviderId(f.Name))
 175                .ToArray();
 176            if (providerIds.Length == 0)
 177            {
 178                return [];
 179            }
 180
 181            query = query.Where(e => providerIds.Contains(e.SegmentProviderId));
 182        }
 183
 184        return query
 185            .OrderBy(e => e.StartTicks)
 186            .AsNoTracking()
 187            .AsEnumerable()
 188            .Select(Map)
 189            .ToArray();
 190    }
 191
 192    private static MediaSegmentDto Map(MediaSegment segment)
 193    {
 0194        return new MediaSegmentDto()
 0195        {
 0196            Id = segment.Id,
 0197            EndTicks = segment.EndTicks,
 0198            ItemId = segment.ItemId,
 0199            StartTicks = segment.StartTicks,
 0200            Type = segment.Type
 0201        };
 202    }
 203
 204    private static MediaSegment Map(MediaSegmentDto segment, string segmentProviderId)
 205    {
 0206        return new MediaSegment()
 0207        {
 0208            Id = segment.Id,
 0209            EndTicks = segment.EndTicks,
 0210            ItemId = segment.ItemId,
 0211            StartTicks = segment.StartTicks,
 0212            Type = segment.Type,
 0213            SegmentProviderId = segmentProviderId
 0214        };
 215    }
 216
 217    /// <inheritdoc />
 218    public bool HasSegments(Guid itemId)
 219    {
 0220        using var db = _dbProvider.CreateDbContext();
 0221        return db.MediaSegments.Any(e => e.ItemId.Equals(itemId));
 0222    }
 223
 224    /// <inheritdoc/>
 225    public bool IsTypeSupported(BaseItem baseItem)
 226    {
 0227        return baseItem.MediaType is Data.Enums.MediaType.Video or Data.Enums.MediaType.Audio;
 228    }
 229
 230    /// <inheritdoc/>
 231    public IEnumerable<(string Name, string Id)> GetSupportedProviders(BaseItem item)
 232    {
 0233        if (item is not (Video or Audio))
 234        {
 0235            return [];
 236        }
 237
 0238        return _segmentProviders
 0239            .Select(p => (p.Name, GetProviderId(p.Name)));
 240    }
 241
 242    private string GetProviderId(string name)
 0243        => name.ToLowerInvariant()
 0244            .GetMD5()
 0245            .ToString("N", CultureInfo.InvariantCulture);
 246}