< 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: 133
Coverable lines: 139
Total lines: 301
Line coverage: 4.3%
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 1/23/2026 - 12:11:06 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: 301 1/23/2026 - 12:11:06 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: 301

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(GetProviderId(e.Name)))
 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                if (!await provider.Supports(baseItem).ConfigureAwait(false))
 85                {
 086                    _logger.LogDebug("Media Segment provider {ProviderName} does not support item with path {MediaPath}"
 087                    continue;
 88                }
 89
 90                IQueryable<MediaSegment> existingSegments;
 091                if (forceOverwrite)
 92                {
 093                    existingSegments = Array.Empty<MediaSegment>().AsQueryable();
 94                }
 95                else
 96                {
 097                    existingSegments = db.MediaSegments.Where(e => e.ItemId.Equals(baseItem.Id) && e.SegmentProviderId =
 98                }
 99
 0100                var requestItem = new MediaSegmentGenerationRequest()
 0101                {
 0102                    ItemId = baseItem.Id,
 0103                    ExistingSegments = existingSegments.Select(e => Map(e)).ToArray()
 0104                };
 105
 106                try
 107                {
 0108                    var segments = await provider.GetMediaSegments(requestItem, cancellationToken)
 0109                        .ConfigureAwait(false);
 110
 0111                    if (!forceOverwrite)
 112                    {
 0113                        var existingSegmentsList = existingSegments.ToArray(); // Cannot use requestItem's list, as the 
 0114                        if (segments.Count == requestItem.ExistingSegments.Count && segments.All(e => existingSegmentsLi
 0115                        {
 0116                            return
 0117                                e.StartTicks == f.StartTicks &&
 0118                                e.EndTicks == f.EndTicks &&
 0119                                e.Type == f.Type;
 0120                        })))
 121                        {
 0122                            _logger.LogDebug("Media Segment provider {ProviderName} did not modify any segments for {Med
 0123                            continue;
 124                        }
 125
 126                        // delete existing media segments that were re-generated.
 0127                        await existingSegments.ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
 128                    }
 129
 0130                    if (segments.Count == 0 && !requestItem.ExistingSegments.Any())
 131                    {
 0132                        _logger.LogDebug("Media Segment provider {ProviderName} did not find any segments for {MediaPath
 0133                        continue;
 134                    }
 0135                    else if (segments.Count == 0 && requestItem.ExistingSegments.Any())
 136                    {
 0137                        _logger.LogDebug("Media Segment provider {ProviderName} deleted all segments for {MediaPath}", p
 0138                        continue;
 139                    }
 140
 0141                    _logger.LogInformation("Media Segment provider {ProviderName} found {CountSegments} for {MediaPath}"
 0142                    var providerId = GetProviderId(provider.Name);
 0143                    foreach (var segment in segments)
 144                    {
 0145                        segment.ItemId = baseItem.Id;
 0146                        await CreateSegmentAsync(segment, providerId).ConfigureAwait(false);
 147                    }
 0148                }
 0149                catch (Exception ex)
 150                {
 0151                    _logger.LogError(ex, "Provider {ProviderName} failed to extract segments from {MediaPath}", provider
 0152                }
 0153            }
 154        }
 0155    }
 156
 157    /// <inheritdoc />
 158    public async Task<MediaSegmentDto> CreateSegmentAsync(MediaSegmentDto mediaSegment, string segmentProviderId)
 159    {
 0160        ArgumentOutOfRangeException.ThrowIfLessThan(mediaSegment.EndTicks, mediaSegment.StartTicks);
 161
 0162        var db = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 0163        await using (db.ConfigureAwait(false))
 164        {
 0165            db.MediaSegments.Add(Map(mediaSegment, segmentProviderId));
 0166            await db.SaveChangesAsync().ConfigureAwait(false);
 167        }
 168
 0169        return mediaSegment;
 0170    }
 171
 172    /// <inheritdoc />
 173    public async Task DeleteSegmentAsync(Guid segmentId)
 174    {
 0175        var db = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 0176        await using (db.ConfigureAwait(false))
 177        {
 0178            await db.MediaSegments.Where(e => e.Id.Equals(segmentId)).ExecuteDeleteAsync().ConfigureAwait(false);
 179        }
 0180    }
 181
 182    /// <inheritdoc />
 183    public async Task DeleteSegmentsAsync(Guid itemId, CancellationToken cancellationToken)
 184    {
 0185        foreach (var provider in _segmentProviders)
 186        {
 187            try
 188            {
 0189                await provider.CleanupExtractedData(itemId, cancellationToken).ConfigureAwait(false);
 0190            }
 0191            catch (Exception ex)
 192            {
 0193                _logger.LogError(ex, "Provider {ProviderName} failed to clean up extracted data for item {ItemId}", prov
 0194            }
 0195        }
 196
 0197        var db = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
 0198        await using (db.ConfigureAwait(false))
 199        {
 0200            await db.MediaSegments.Where(e => e.ItemId.Equals(itemId)).ExecuteDeleteAsync(cancellationToken).ConfigureAw
 201        }
 0202    }
 203
 204    /// <inheritdoc />
 205    public async Task<IEnumerable<MediaSegmentDto>> GetSegmentsAsync(BaseItem? item, IEnumerable<MediaSegmentType>? type
 206    {
 0207        if (item is null)
 208        {
 0209            _logger.LogError("Tried to request segments for an invalid item");
 0210            return [];
 211        }
 212
 0213        var db = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 0214        await using (db.ConfigureAwait(false))
 215        {
 0216            var query = db.MediaSegments
 0217                .Where(e => e.ItemId.Equals(item.Id));
 218
 0219            if (typeFilter is not null)
 220            {
 0221                query = query.Where(e => typeFilter.Contains(e.Type));
 222            }
 223
 0224            if (filterByProvider)
 225            {
 0226                var providerIds = _segmentProviders
 0227                    .Where(e => !libraryOptions.DisabledMediaSegmentProviders.Contains(GetProviderId(e.Name)))
 0228                    .Select(f => GetProviderId(f.Name))
 0229                    .ToArray();
 0230                if (providerIds.Length == 0)
 231                {
 0232                    return [];
 233                }
 234
 0235                query = query.Where(e => providerIds.Contains(e.SegmentProviderId));
 236            }
 237
 0238            return query
 0239                .OrderBy(e => e.StartTicks)
 0240                .AsNoTracking()
 0241                .AsEnumerable()
 0242                .Select(Map)
 0243                .ToArray();
 244        }
 0245    }
 246
 247    private static MediaSegmentDto Map(MediaSegment segment)
 248    {
 0249        return new MediaSegmentDto()
 0250        {
 0251            Id = segment.Id,
 0252            EndTicks = segment.EndTicks,
 0253            ItemId = segment.ItemId,
 0254            StartTicks = segment.StartTicks,
 0255            Type = segment.Type
 0256        };
 257    }
 258
 259    private static MediaSegment Map(MediaSegmentDto segment, string segmentProviderId)
 260    {
 0261        return new MediaSegment()
 0262        {
 0263            Id = segment.Id,
 0264            EndTicks = segment.EndTicks,
 0265            ItemId = segment.ItemId,
 0266            StartTicks = segment.StartTicks,
 0267            Type = segment.Type,
 0268            SegmentProviderId = segmentProviderId
 0269        };
 270    }
 271
 272    /// <inheritdoc />
 273    public bool HasSegments(Guid itemId)
 274    {
 0275        using var db = _dbProvider.CreateDbContext();
 0276        return db.MediaSegments.Any(e => e.ItemId.Equals(itemId));
 0277    }
 278
 279    /// <inheritdoc/>
 280    public bool IsTypeSupported(BaseItem baseItem)
 281    {
 0282        return baseItem.MediaType is Data.Enums.MediaType.Video or Data.Enums.MediaType.Audio;
 283    }
 284
 285    /// <inheritdoc/>
 286    public IEnumerable<(string Name, string Id)> GetSupportedProviders(BaseItem item)
 287    {
 0288        if (item is not (Video or Audio))
 289        {
 0290            return [];
 291        }
 292
 0293        return _segmentProviders
 0294            .Select(p => (p.Name, GetProviderId(p.Name)));
 295    }
 296
 297    private string GetProviderId(string name)
 0298        => name.ToLowerInvariant()
 0299            .GetMD5()
 0300            .ToString("N", CultureInfo.InvariantCulture);
 301}