< 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
17%
Covered lines: 6
Uncovered lines: 28
Coverable lines: 34
Total lines: 239
Line coverage: 17.6%
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.Entities;
 14using MediaBrowser.Controller.Entities.Audio;
 15using MediaBrowser.Controller.MediaSegments;
 16using MediaBrowser.Controller.Providers;
 17using MediaBrowser.Model;
 18using MediaBrowser.Model.Configuration;
 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
 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    public MediaSegmentManager(
 41        ILogger<MediaSegmentManager> logger,
 42        IDbContextFactory<JellyfinDbContext> dbProvider,
 43        IEnumerable<IMediaSegmentProvider> segmentProviders)
 44    {
 2145        _logger = logger;
 2146        _dbProvider = dbProvider;
 47
 2148        _segmentProviders = segmentProviders
 2149            .OrderBy(i => i is IHasOrder hasOrder ? hasOrder.Order : 0)
 2150            .ToArray();
 2151    }
 52
 53    /// <inheritdoc/>
 54    public async Task RunSegmentPluginProviders(BaseItem baseItem, LibraryOptions libraryOptions, bool overwrite, Cancel
 55    {
 56        var providers = _segmentProviders
 57            .Where(e => !libraryOptions.DisabledMediaSegmentProviders.Contains(GetProviderId(e.Name)))
 58            .OrderBy(i =>
 59                {
 60                    var index = libraryOptions.MediaSegmentProviderOrder.IndexOf(i.Name);
 61                    return index == -1 ? int.MaxValue : index;
 62                })
 63            .ToList();
 64
 65        if (providers.Count == 0)
 66        {
 67            _logger.LogDebug("Skipping media segment extraction as no providers are enabled for {MediaPath}", baseItem.P
 68            return;
 69        }
 70
 71        using var db = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
 72
 73        if (!overwrite && (await db.MediaSegments.AnyAsync(e => e.ItemId.Equals(baseItem.Id), cancellationToken).Configu
 74        {
 75            _logger.LogDebug("Skip {MediaPath} as it already contains media segments", baseItem.Path);
 76            return;
 77        }
 78
 79        _logger.LogDebug("Start media segment extraction for {MediaPath} with {CountProviders} providers enabled", baseI
 80
 81        await db.MediaSegments.Where(e => e.ItemId.Equals(baseItem.Id)).ExecuteDeleteAsync(cancellationToken).ConfigureA
 82
 83        // no need to recreate the request object every time.
 84        var requestItem = new MediaSegmentGenerationRequest() { ItemId = baseItem.Id };
 85
 86        foreach (var provider in providers)
 87        {
 88            if (!await provider.Supports(baseItem).ConfigureAwait(false))
 89            {
 90                _logger.LogDebug("Media Segment provider {ProviderName} does not support item with path {MediaPath}", pr
 91                continue;
 92            }
 93
 94            try
 95            {
 96                var segments = await provider.GetMediaSegments(requestItem, cancellationToken)
 97                    .ConfigureAwait(false);
 98                if (segments.Count == 0)
 99                {
 100                    _logger.LogDebug("Media Segment provider {ProviderName} did not find any segments for {MediaPath}", 
 101                    continue;
 102                }
 103
 104                _logger.LogInformation("Media Segment provider {ProviderName} found {CountSegments} for {MediaPath}", pr
 105                var providerId = GetProviderId(provider.Name);
 106                foreach (var segment in segments)
 107                {
 108                    segment.ItemId = baseItem.Id;
 109                    await CreateSegmentAsync(segment, providerId).ConfigureAwait(false);
 110                }
 111            }
 112            catch (Exception ex)
 113            {
 114                _logger.LogError(ex, "Provider {ProviderName} failed to extract segments from {MediaPath}", provider.Nam
 115            }
 116        }
 117    }
 118
 119    /// <inheritdoc />
 120    public async Task<MediaSegmentDto> CreateSegmentAsync(MediaSegmentDto mediaSegment, string segmentProviderId)
 121    {
 122        ArgumentOutOfRangeException.ThrowIfLessThan(mediaSegment.EndTicks, mediaSegment.StartTicks);
 123
 124        using var db = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 125        db.MediaSegments.Add(Map(mediaSegment, segmentProviderId));
 126        await db.SaveChangesAsync().ConfigureAwait(false);
 127        return mediaSegment;
 128    }
 129
 130    /// <inheritdoc />
 131    public async Task DeleteSegmentAsync(Guid segmentId)
 132    {
 133        using var db = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 134        await db.MediaSegments.Where(e => e.Id.Equals(segmentId)).ExecuteDeleteAsync().ConfigureAwait(false);
 135    }
 136
 137    /// <inheritdoc />
 138    public async Task DeleteSegmentsAsync(Guid itemId, CancellationToken cancellationToken)
 139    {
 140        using var db = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
 141        await db.MediaSegments.Where(e => e.ItemId.Equals(itemId)).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(
 142    }
 143
 144    /// <inheritdoc />
 145    public async Task<IEnumerable<MediaSegmentDto>> GetSegmentsAsync(BaseItem? item, IEnumerable<MediaSegmentType>? type
 146    {
 147        if (item is null)
 148        {
 149            _logger.LogError("Tried to request segments for an invalid item");
 150            return [];
 151        }
 152
 153        using var db = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 154
 155        var query = db.MediaSegments
 156            .Where(e => e.ItemId.Equals(item.Id));
 157
 158        if (typeFilter is not null)
 159        {
 160            query = query.Where(e => typeFilter.Contains(e.Type));
 161        }
 162
 163        if (filterByProvider)
 164        {
 165            var providerIds = _segmentProviders
 166                .Where(e => !libraryOptions.DisabledMediaSegmentProviders.Contains(GetProviderId(e.Name)))
 167                .Select(f => GetProviderId(f.Name))
 168                .ToArray();
 169            if (providerIds.Length == 0)
 170            {
 171                return [];
 172            }
 173
 174            query = query.Where(e => providerIds.Contains(e.SegmentProviderId));
 175        }
 176
 177        return query
 178            .OrderBy(e => e.StartTicks)
 179            .AsNoTracking()
 180            .AsEnumerable()
 181            .Select(Map)
 182            .ToArray();
 183    }
 184
 185    private static MediaSegmentDto Map(MediaSegment segment)
 186    {
 0187        return new MediaSegmentDto()
 0188        {
 0189            Id = segment.Id,
 0190            EndTicks = segment.EndTicks,
 0191            ItemId = segment.ItemId,
 0192            StartTicks = segment.StartTicks,
 0193            Type = segment.Type
 0194        };
 195    }
 196
 197    private static MediaSegment Map(MediaSegmentDto segment, string segmentProviderId)
 198    {
 0199        return new MediaSegment()
 0200        {
 0201            Id = segment.Id,
 0202            EndTicks = segment.EndTicks,
 0203            ItemId = segment.ItemId,
 0204            StartTicks = segment.StartTicks,
 0205            Type = segment.Type,
 0206            SegmentProviderId = segmentProviderId
 0207        };
 208    }
 209
 210    /// <inheritdoc />
 211    public bool HasSegments(Guid itemId)
 212    {
 0213        using var db = _dbProvider.CreateDbContext();
 0214        return db.MediaSegments.Any(e => e.ItemId.Equals(itemId));
 0215    }
 216
 217    /// <inheritdoc/>
 218    public bool IsTypeSupported(BaseItem baseItem)
 219    {
 0220        return baseItem.MediaType is Data.Enums.MediaType.Video or Data.Enums.MediaType.Audio;
 221    }
 222
 223    /// <inheritdoc/>
 224    public IEnumerable<(string Name, string Id)> GetSupportedProviders(BaseItem item)
 225    {
 0226        if (item is not (Video or Audio))
 227        {
 0228            return [];
 229        }
 230
 0231        return _segmentProviders
 0232            .Select(p => (p.Name, GetProviderId(p.Name)));
 233    }
 234
 235    private string GetProviderId(string name)
 0236        => name.ToLowerInvariant()
 0237            .GetMD5()
 0238            .ToString("N", CultureInfo.InvariantCulture);
 239}