< Summary - Jellyfin

Information
Class: MediaBrowser.Controller.MediaEncoding.TranscodingSegmentCleaner
Assembly: MediaBrowser.Controller
File(s): /srv/git/jellyfin/MediaBrowser.Controller/MediaEncoding/TranscodingSegmentCleaner.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 63
Coverable lines: 63
Total lines: 176
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 28
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/16/2026 - 12:15:55 AM Line coverage: 0% (0/64) Branch coverage: 0% (0/28) Total lines: 17810/29/2026 - 12:17:11 AM Line coverage: 0% (0/63) Branch coverage: 0% (0/28) Total lines: 176 5/16/2026 - 12:15:55 AM Line coverage: 0% (0/64) Branch coverage: 0% (0/28) Total lines: 17810/29/2026 - 12:17:11 AM Line coverage: 0% (0/63) Branch coverage: 0% (0/28) Total lines: 176

Coverage delta

Coverage delta 1 -1

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
Start()100%210%
Stop()100%210%
Dispose()100%210%
Dispose(...)0%620%
GetOptions()100%210%
TimerCallback()0%156120%
DeleteSegmentFiles()0%2040%
DeleteHlsSegmentFiles(...)0%7280%
DisposeTimer()0%620%

File(s)

/srv/git/jellyfin/MediaBrowser.Controller/MediaEncoding/TranscodingSegmentCleaner.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.IO;
 4using System.Linq;
 5using System.Threading;
 6using System.Threading.Tasks;
 7using MediaBrowser.Common.Configuration;
 8using MediaBrowser.Model.Configuration;
 9using MediaBrowser.Model.IO;
 10using Microsoft.Extensions.Logging;
 11
 12namespace MediaBrowser.Controller.MediaEncoding;
 13
 14/// <summary>
 15/// Transcoding segment cleaner.
 16/// </summary>
 17public class TranscodingSegmentCleaner : IDisposable
 18{
 19    private readonly TranscodingJob _job;
 20    private readonly ILogger<TranscodingSegmentCleaner> _logger;
 21    private readonly IConfigurationManager _config;
 22    private readonly IFileSystem _fileSystem;
 23    private readonly IMediaEncoder _mediaEncoder;
 24    private Timer? _timer;
 25
 26    /// <summary>
 27    /// Initializes a new instance of the <see cref="TranscodingSegmentCleaner"/> class.
 28    /// </summary>
 29    /// <param name="job">Transcoding job dto.</param>
 30    /// <param name="logger">Instance of the <see cref="ILogger{TranscodingSegmentCleaner}"/> interface.</param>
 31    /// <param name="config">Instance of the <see cref="IConfigurationManager"/> interface.</param>
 32    /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
 33    /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
 34    /// <param name="segmentLength">The segment length of this transcoding job. Unused.</param>
 35    public TranscodingSegmentCleaner(TranscodingJob job, ILogger<TranscodingSegmentCleaner> logger, IConfigurationManage
 36    {
 037        _job = job;
 038        _logger = logger;
 039        _config = config;
 040        _fileSystem = fileSystem;
 041        _mediaEncoder = mediaEncoder;
 042    }
 43
 44    /// <summary>
 45    /// Start timer.
 46    /// </summary>
 47    public void Start()
 48    {
 049        _timer = new Timer(TimerCallback, null, 20000, 20000);
 050    }
 51
 52    /// <summary>
 53    /// Stop cleaner.
 54    /// </summary>
 55    public void Stop()
 56    {
 057        DisposeTimer();
 058    }
 59
 60    /// <summary>
 61    /// Dispose cleaner.
 62    /// </summary>
 63    public void Dispose()
 64    {
 065        Dispose(true);
 066        GC.SuppressFinalize(this);
 067    }
 68
 69    /// <summary>
 70    /// Dispose cleaner.
 71    /// </summary>
 72    /// <param name="disposing">Disposing.</param>
 73    protected virtual void Dispose(bool disposing)
 74    {
 075        if (disposing)
 76        {
 077            DisposeTimer();
 78        }
 079    }
 80
 81    private EncodingOptions GetOptions()
 82    {
 083        return _config.GetEncodingOptions();
 84    }
 85
 86    private async void TimerCallback(object? state)
 87    {
 088        if (_job.HasExited)
 89        {
 090            DisposeTimer();
 091            return;
 92        }
 93
 094        var options = GetOptions();
 095        var enableSegmentDeletion = options.EnableSegmentDeletion;
 096        var segmentKeepSeconds = Math.Max(options.SegmentKeepSeconds, 20);
 97
 098        if (enableSegmentDeletion)
 99        {
 0100            var downloadPositionTicks = _job.DownloadPositionTicks ?? 0;
 0101            var downloadPositionSeconds = Convert.ToInt64(TimeSpan.FromTicks(downloadPositionTicks).TotalSeconds);
 102
 0103            if (downloadPositionSeconds > 0 && segmentKeepSeconds > 0 && downloadPositionSeconds > segmentKeepSeconds)
 104            {
 0105                var idxMaxToDelete = _job.GetHighestServedSegmentIndexEndingAtOrBefore(downloadPositionTicks - TimeSpan.
 106
 0107                if (idxMaxToDelete > 0)
 108                {
 0109                    await DeleteSegmentFiles(_job, 0, idxMaxToDelete.Value, 1500).ConfigureAwait(false);
 110                }
 111            }
 112        }
 0113    }
 114
 115    private async Task DeleteSegmentFiles(TranscodingJob job, long idxMin, long idxMax, int delayMs)
 116    {
 0117        var path = job.Path ?? throw new ArgumentException("Path can't be null.");
 118
 0119        _logger.LogDebug("Deleting segment file(s) index {Min} to {Max} from {Path}", idxMin, idxMax, path);
 120
 0121        await Task.Delay(delayMs).ConfigureAwait(false);
 122
 123        try
 124        {
 0125            if (job.Type == TranscodingJobType.Hls)
 126            {
 0127                DeleteHlsSegmentFiles(path, idxMin, idxMax);
 128            }
 0129        }
 0130        catch (Exception ex)
 131        {
 0132            _logger.LogDebug(ex, "Error deleting segment file(s) {Path}", path);
 0133        }
 0134    }
 135
 136    private void DeleteHlsSegmentFiles(string outputFilePath, long idxMin, long idxMax)
 137    {
 0138        var directory = Path.GetDirectoryName(outputFilePath)
 0139                        ?? throw new ArgumentException("Path can't be a root directory.", nameof(outputFilePath));
 140
 0141        var name = Path.GetFileNameWithoutExtension(outputFilePath);
 142
 0143        var filesToDelete = _fileSystem.GetFilePaths(directory)
 0144            .Where(f => long.TryParse(Path.GetFileNameWithoutExtension(f).Replace(name, string.Empty, StringComparison.O
 0145                        && (idx >= idxMin && idx <= idxMax));
 146
 0147        List<Exception>? exs = null;
 0148        foreach (var file in filesToDelete)
 149        {
 150            try
 151            {
 0152                _logger.LogDebug("Deleting HLS segment file {0}", file);
 0153                _fileSystem.DeleteFile(file);
 0154            }
 0155            catch (IOException ex)
 156            {
 0157                (exs ??= new List<Exception>()).Add(ex);
 0158                _logger.LogDebug(ex, "Error deleting HLS segment file {Path}", file);
 0159            }
 160        }
 161
 0162        if (exs is not null)
 163        {
 0164            throw new AggregateException("Error deleting HLS segment files", exs);
 165        }
 0166    }
 167
 168    private void DisposeTimer()
 169    {
 0170        if (_timer is not null)
 171        {
 0172            _timer.Dispose();
 0173            _timer = null;
 174        }
 0175    }
 176}