< 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
4%
Covered lines: 6
Uncovered lines: 139
Coverable lines: 145
Total lines: 312
Line coverage: 4.1%
Branch coverage
0%
Covered branches: 0
Total branches: 48
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 2/15/2026 - 12:13:43 AM Line coverage: 17.6% (6/34) Branch coverage: 0% (0/8) Total lines: 2894/3/2026 - 12:13:45 AM Line coverage: 17.6% (6/34) Branch coverage: 0% (0/8) Total lines: 3014/19/2026 - 12:14:27 AM Line coverage: 4.3% (6/139) Branch coverage: 0% (0/48) Total lines: 3015/18/2026 - 12:15:49 AM Line coverage: 4.1% (6/145) Branch coverage: 0% (0/48) Total lines: 312 2/15/2026 - 12:13:43 AM Line coverage: 17.6% (6/34) Branch coverage: 0% (0/8) Total lines: 2894/3/2026 - 12:13:45 AM Line coverage: 17.6% (6/34) Branch coverage: 0% (0/8) Total lines: 3014/19/2026 - 12:14:27 AM Line coverage: 4.3% (6/139) Branch coverage: 0% (0/48) Total lines: 3015/18/2026 - 12:15:49 AM Line coverage: 4.1% (6/145) Branch coverage: 0% (0/48) Total lines: 312

Coverage delta

Coverage delta 14 -14

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
RunSegmentPluginProviders()0%930300%
CreateSegmentAsync()100%210%
DeleteSegmentAsync()100%210%
DeleteSegmentsAsync()0%620%
GetSegmentsAsync()0%7280%
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    {
 056        var providers = _segmentProviders
 057            .Where(e => !libraryOptions.DisabledMediaSegmentProviders.Contains(e.Name, StringComparer.OrdinalIgnoreCase)
 058            .OrderBy(i =>
 059                {
 060                    var index = libraryOptions.MediaSegmentProviderOrder.IndexOf(i.Name);
 061                    return index == -1 ? int.MaxValue : index;
 062                })
 063            .ToList();
 64
 065        if (providers.Count == 0)
 66        {
 067            _logger.LogDebug("Skipping media segment extraction as no providers are enabled for {MediaPath}", baseItem.P
 068            return;
 69        }
 70
 071        var db = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
 072        await using (db.ConfigureAwait(false))
 73        {
 074            _logger.LogDebug("Start media segment extraction for {MediaPath} with {CountProviders} providers enabled", b
 75
 076            if (forceOverwrite)
 77            {
 78                // delete all existing media segments if forceOverwrite is set.
 079                await db.MediaSegments.Where(e => e.ItemId.Equals(baseItem.Id)).ExecuteDeleteAsync(cancellationToken).Co
 80            }
 81
 082            foreach (var provider in providers)
 83            {
 084                cancellationToken.ThrowIfCancellationRequested();
 85
 086                if (!await provider.Supports(baseItem).ConfigureAwait(false))
 87                {
 088                    _logger.LogDebug("Media Segment provider {ProviderName} does not support item with path {MediaPath}"
 089                    continue;
 90                }
 91
 92                IQueryable<MediaSegment> existingSegments;
 093                if (forceOverwrite)
 94                {
 095                    existingSegments = Array.Empty<MediaSegment>().AsQueryable();
 96                }
 97                else
 98                {
 099                    existingSegments = db.MediaSegments.Where(e => e.ItemId.Equals(baseItem.Id) && e.SegmentProviderId =
 100                }
 101
 0102                var requestItem = new MediaSegmentGenerationRequest()
 0103                {
 0104                    ItemId = baseItem.Id,
 0105                    ExistingSegments = existingSegments.Select(e => Map(e)).ToArray()
 0106                };
 107
 108                try
 109                {
 0110                    var segments = await provider.GetMediaSegments(requestItem, cancellationToken)
 0111                        .ConfigureAwait(false);
 112
 0113                    if (!forceOverwrite)
 114                    {
 0115                        var existingSegmentsList = existingSegments.ToArray(); // Cannot use requestItem's list, as the 
 0116                        if (segments.Count == requestItem.ExistingSegments.Count && segments.All(e => existingSegmentsLi
 0117                        {
 0118                            return
 0119                                e.StartTicks == f.StartTicks &&
 0120                                e.EndTicks == f.EndTicks &&
 0121                                e.Type == f.Type;
 0122                        })))
 123                        {
 0124                            _logger.LogDebug("Media Segment provider {ProviderName} did not modify any segments for {Med
 0125                            continue;
 126                        }
 127
 128                        // delete existing media segments that were re-generated.
 0129                        await existingSegments.ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
 130                    }
 131
 0132                    if (segments.Count == 0 && !requestItem.ExistingSegments.Any())
 133                    {
 0134                        _logger.LogDebug("Media Segment provider {ProviderName} did not find any segments for {MediaPath
 0135                        continue;
 136                    }
 0137                    else if (segments.Count == 0 && requestItem.ExistingSegments.Any())
 138                    {
 0139                        _logger.LogDebug("Media Segment provider {ProviderName} deleted all segments for {MediaPath}", p
 0140                        continue;
 141                    }
 142
 0143                    _logger.LogInformation("Media Segment provider {ProviderName} found {CountSegments} for {MediaPath}"
 0144                    var providerId = GetProviderId(provider.Name);
 0145                    foreach (var segment in segments)
 146                    {
 0147                        segment.ItemId = baseItem.Id;
 0148                        await CreateSegmentAsync(segment, providerId).ConfigureAwait(false);
 149                    }
 0150                }
 0151                catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
 152                {
 0153                    throw;
 154                }
 0155                catch (Exception ex) when (cancellationToken.IsCancellationRequested)
 156                {
 0157                    _logger.LogDebug(ex, "Provider {ProviderName} aborted segment extraction for {MediaPath} due to shut
 0158                    break;
 159                }
 0160                catch (Exception ex)
 161                {
 0162                    _logger.LogError(ex, "Provider {ProviderName} failed to extract segments from {MediaPath}", provider
 0163                }
 0164            }
 165        }
 0166    }
 167
 168    /// <inheritdoc />
 169    public async Task<MediaSegmentDto> CreateSegmentAsync(MediaSegmentDto mediaSegment, string segmentProviderId)
 170    {
 0171        ArgumentOutOfRangeException.ThrowIfLessThan(mediaSegment.EndTicks, mediaSegment.StartTicks);
 172
 0173        var db = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 0174        await using (db.ConfigureAwait(false))
 175        {
 0176            db.MediaSegments.Add(Map(mediaSegment, segmentProviderId));
 0177            await db.SaveChangesAsync().ConfigureAwait(false);
 178        }
 179
 0180        return mediaSegment;
 0181    }
 182
 183    /// <inheritdoc />
 184    public async Task DeleteSegmentAsync(Guid segmentId)
 185    {
 0186        var db = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 0187        await using (db.ConfigureAwait(false))
 188        {
 0189            await db.MediaSegments.Where(e => e.Id.Equals(segmentId)).ExecuteDeleteAsync().ConfigureAwait(false);
 190        }
 0191    }
 192
 193    /// <inheritdoc />
 194    public async Task DeleteSegmentsAsync(Guid itemId, CancellationToken cancellationToken)
 195    {
 0196        foreach (var provider in _segmentProviders)
 197        {
 198            try
 199            {
 0200                await provider.CleanupExtractedData(itemId, cancellationToken).ConfigureAwait(false);
 0201            }
 0202            catch (Exception ex)
 203            {
 0204                _logger.LogError(ex, "Provider {ProviderName} failed to clean up extracted data for item {ItemId}", prov
 0205            }
 0206        }
 207
 0208        var db = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
 0209        await using (db.ConfigureAwait(false))
 210        {
 0211            await db.MediaSegments.Where(e => e.ItemId.Equals(itemId)).ExecuteDeleteAsync(cancellationToken).ConfigureAw
 212        }
 0213    }
 214
 215    /// <inheritdoc />
 216    public async Task<IEnumerable<MediaSegmentDto>> GetSegmentsAsync(BaseItem? item, IEnumerable<MediaSegmentType>? type
 217    {
 0218        if (item is null)
 219        {
 0220            _logger.LogError("Tried to request segments for an invalid item");
 0221            return [];
 222        }
 223
 0224        var db = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 0225        await using (db.ConfigureAwait(false))
 226        {
 0227            var query = db.MediaSegments
 0228                .Where(e => e.ItemId.Equals(item.Id));
 229
 0230            if (typeFilter is not null)
 231            {
 0232                query = query.Where(e => typeFilter.Contains(e.Type));
 233            }
 234
 0235            if (filterByProvider)
 236            {
 0237                var providerIds = _segmentProviders
 0238                    .Where(e => !libraryOptions.DisabledMediaSegmentProviders.Contains(e.Name, StringComparer.OrdinalIgn
 0239                    .Select(f => GetProviderId(f.Name))
 0240                    .ToArray();
 0241                if (providerIds.Length == 0)
 242                {
 0243                    return [];
 244                }
 245
 0246                query = query.Where(e => providerIds.Contains(e.SegmentProviderId));
 247            }
 248
 0249            return query
 0250                .OrderBy(e => e.StartTicks)
 0251                .AsNoTracking()
 0252                .AsEnumerable()
 0253                .Select(Map)
 0254                .ToArray();
 255        }
 0256    }
 257
 258    private static MediaSegmentDto Map(MediaSegment segment)
 259    {
 0260        return new MediaSegmentDto()
 0261        {
 0262            Id = segment.Id,
 0263            EndTicks = segment.EndTicks,
 0264            ItemId = segment.ItemId,
 0265            StartTicks = segment.StartTicks,
 0266            Type = segment.Type
 0267        };
 268    }
 269
 270    private static MediaSegment Map(MediaSegmentDto segment, string segmentProviderId)
 271    {
 0272        return new MediaSegment()
 0273        {
 0274            Id = segment.Id,
 0275            EndTicks = segment.EndTicks,
 0276            ItemId = segment.ItemId,
 0277            StartTicks = segment.StartTicks,
 0278            Type = segment.Type,
 0279            SegmentProviderId = segmentProviderId
 0280        };
 281    }
 282
 283    /// <inheritdoc />
 284    public bool HasSegments(Guid itemId)
 285    {
 0286        using var db = _dbProvider.CreateDbContext();
 0287        return db.MediaSegments.Any(e => e.ItemId.Equals(itemId));
 0288    }
 289
 290    /// <inheritdoc/>
 291    public bool IsTypeSupported(BaseItem baseItem)
 292    {
 0293        return baseItem.MediaType is Data.Enums.MediaType.Video or Data.Enums.MediaType.Audio;
 294    }
 295
 296    /// <inheritdoc/>
 297    public IEnumerable<(string Name, string Id)> GetSupportedProviders(BaseItem item)
 298    {
 0299        if (item is not (Video or Audio))
 300        {
 0301            return [];
 302        }
 303
 0304        return _segmentProviders
 0305            .Select(p => (p.Name, GetProviderId(p.Name)));
 306    }
 307
 308    private string GetProviderId(string name)
 0309        => name.ToLowerInvariant()
 0310            .GetMD5()
 0311            .ToString("N", CultureInfo.InvariantCulture);
 312}