< 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: 289
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 forceOverwrite, C
 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        var db = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
 72        await using (db.ConfigureAwait(false))
 73        {
 74            _logger.LogDebug("Start media segment extraction for {MediaPath} with {CountProviders} providers enabled", b
 75
 76            if (forceOverwrite)
 77            {
 78                // delete all existing media segments if forceOverwrite is set.
 79                await db.MediaSegments.Where(e => e.ItemId.Equals(baseItem.Id)).ExecuteDeleteAsync(cancellationToken).Co
 80            }
 81
 82            foreach (var provider in providers)
 83            {
 84                if (!await provider.Supports(baseItem).ConfigureAwait(false))
 85                {
 86                    _logger.LogDebug("Media Segment provider {ProviderName} does not support item with path {MediaPath}"
 87                    continue;
 88                }
 89
 90                IQueryable<MediaSegment> existingSegments;
 91                if (forceOverwrite)
 92                {
 93                    existingSegments = Array.Empty<MediaSegment>().AsQueryable();
 94                }
 95                else
 96                {
 97                    existingSegments = db.MediaSegments.Where(e => e.ItemId.Equals(baseItem.Id) && e.SegmentProviderId =
 98                }
 99
 100                var requestItem = new MediaSegmentGenerationRequest()
 101                {
 102                    ItemId = baseItem.Id,
 103                    ExistingSegments = existingSegments.Select(e => Map(e)).ToArray()
 104                };
 105
 106                try
 107                {
 108                    var segments = await provider.GetMediaSegments(requestItem, cancellationToken)
 109                        .ConfigureAwait(false);
 110
 111                    if (!forceOverwrite)
 112                    {
 113                        var existingSegmentsList = existingSegments.ToArray(); // Cannot use requestItem's list, as the 
 114                        if (segments.Count == requestItem.ExistingSegments.Count && segments.All(e => existingSegmentsLi
 115                        {
 116                            return
 117                                e.StartTicks == f.StartTicks &&
 118                                e.EndTicks == f.EndTicks &&
 119                                e.Type == f.Type;
 120                        })))
 121                        {
 122                            _logger.LogDebug("Media Segment provider {ProviderName} did not modify any segments for {Med
 123                            continue;
 124                        }
 125
 126                        // delete existing media segments that were re-generated.
 127                        await existingSegments.ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
 128                    }
 129
 130                    if (segments.Count == 0 && !requestItem.ExistingSegments.Any())
 131                    {
 132                        _logger.LogDebug("Media Segment provider {ProviderName} did not find any segments for {MediaPath
 133                        continue;
 134                    }
 135                    else if (segments.Count == 0 && requestItem.ExistingSegments.Any())
 136                    {
 137                        _logger.LogDebug("Media Segment provider {ProviderName} deleted all segments for {MediaPath}", p
 138                        continue;
 139                    }
 140
 141                    _logger.LogInformation("Media Segment provider {ProviderName} found {CountSegments} for {MediaPath}"
 142                    var providerId = GetProviderId(provider.Name);
 143                    foreach (var segment in segments)
 144                    {
 145                        segment.ItemId = baseItem.Id;
 146                        await CreateSegmentAsync(segment, providerId).ConfigureAwait(false);
 147                    }
 148                }
 149                catch (Exception ex)
 150                {
 151                    _logger.LogError(ex, "Provider {ProviderName} failed to extract segments from {MediaPath}", provider
 152                }
 153            }
 154        }
 155    }
 156
 157    /// <inheritdoc />
 158    public async Task<MediaSegmentDto> CreateSegmentAsync(MediaSegmentDto mediaSegment, string segmentProviderId)
 159    {
 160        ArgumentOutOfRangeException.ThrowIfLessThan(mediaSegment.EndTicks, mediaSegment.StartTicks);
 161
 162        var db = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 163        await using (db.ConfigureAwait(false))
 164        {
 165            db.MediaSegments.Add(Map(mediaSegment, segmentProviderId));
 166            await db.SaveChangesAsync().ConfigureAwait(false);
 167        }
 168
 169        return mediaSegment;
 170    }
 171
 172    /// <inheritdoc />
 173    public async Task DeleteSegmentAsync(Guid segmentId)
 174    {
 175        var db = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 176        await using (db.ConfigureAwait(false))
 177        {
 178            await db.MediaSegments.Where(e => e.Id.Equals(segmentId)).ExecuteDeleteAsync().ConfigureAwait(false);
 179        }
 180    }
 181
 182    /// <inheritdoc />
 183    public async Task DeleteSegmentsAsync(Guid itemId, CancellationToken cancellationToken)
 184    {
 185        var db = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
 186        await using (db.ConfigureAwait(false))
 187        {
 188            await db.MediaSegments.Where(e => e.ItemId.Equals(itemId)).ExecuteDeleteAsync(cancellationToken).ConfigureAw
 189        }
 190    }
 191
 192    /// <inheritdoc />
 193    public async Task<IEnumerable<MediaSegmentDto>> GetSegmentsAsync(BaseItem? item, IEnumerable<MediaSegmentType>? type
 194    {
 195        if (item is null)
 196        {
 197            _logger.LogError("Tried to request segments for an invalid item");
 198            return [];
 199        }
 200
 201        var db = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 202        await using (db.ConfigureAwait(false))
 203        {
 204            var query = db.MediaSegments
 205                .Where(e => e.ItemId.Equals(item.Id));
 206
 207            if (typeFilter is not null)
 208            {
 209                query = query.Where(e => typeFilter.Contains(e.Type));
 210            }
 211
 212            if (filterByProvider)
 213            {
 214                var providerIds = _segmentProviders
 215                    .Where(e => !libraryOptions.DisabledMediaSegmentProviders.Contains(GetProviderId(e.Name)))
 216                    .Select(f => GetProviderId(f.Name))
 217                    .ToArray();
 218                if (providerIds.Length == 0)
 219                {
 220                    return [];
 221                }
 222
 223                query = query.Where(e => providerIds.Contains(e.SegmentProviderId));
 224            }
 225
 226            return query
 227                .OrderBy(e => e.StartTicks)
 228                .AsNoTracking()
 229                .AsEnumerable()
 230                .Select(Map)
 231                .ToArray();
 232        }
 233    }
 234
 235    private static MediaSegmentDto Map(MediaSegment segment)
 236    {
 0237        return new MediaSegmentDto()
 0238        {
 0239            Id = segment.Id,
 0240            EndTicks = segment.EndTicks,
 0241            ItemId = segment.ItemId,
 0242            StartTicks = segment.StartTicks,
 0243            Type = segment.Type
 0244        };
 245    }
 246
 247    private static MediaSegment Map(MediaSegmentDto segment, string segmentProviderId)
 248    {
 0249        return new MediaSegment()
 0250        {
 0251            Id = segment.Id,
 0252            EndTicks = segment.EndTicks,
 0253            ItemId = segment.ItemId,
 0254            StartTicks = segment.StartTicks,
 0255            Type = segment.Type,
 0256            SegmentProviderId = segmentProviderId
 0257        };
 258    }
 259
 260    /// <inheritdoc />
 261    public bool HasSegments(Guid itemId)
 262    {
 0263        using var db = _dbProvider.CreateDbContext();
 0264        return db.MediaSegments.Any(e => e.ItemId.Equals(itemId));
 0265    }
 266
 267    /// <inheritdoc/>
 268    public bool IsTypeSupported(BaseItem baseItem)
 269    {
 0270        return baseItem.MediaType is Data.Enums.MediaType.Video or Data.Enums.MediaType.Audio;
 271    }
 272
 273    /// <inheritdoc/>
 274    public IEnumerable<(string Name, string Id)> GetSupportedProviders(BaseItem item)
 275    {
 0276        if (item is not (Video or Audio))
 277        {
 0278            return [];
 279        }
 280
 0281        return _segmentProviders
 0282            .Select(p => (p.Name, GetProviderId(p.Name)));
 283    }
 284
 285    private string GetProviderId(string name)
 0286        => name.ToLowerInvariant()
 0287            .GetMD5()
 0288            .ToString("N", CultureInfo.InvariantCulture);
 289}