< Summary - Jellyfin

Information
Class: Jellyfin.MediaEncoding.Keyframes.FfProbe.FfProbeKeyframeExtractor
Assembly: Jellyfin.MediaEncoding.Keyframes
File(s): /srv/git/jellyfin/src/Jellyfin.MediaEncoding.Keyframes/FfProbe/FfProbeKeyframeExtractor.cs
Line coverage
50%
Covered lines: 44
Uncovered lines: 44
Coverable lines: 88
Total lines: 131
Line coverage: 50%
Branch coverage
86%
Covered branches: 19
Total branches: 22
Branch coverage: 86.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 7/22/2025 - 12:11:20 AM Line coverage: 53.6% (44/82) Branch coverage: 86.3% (19/22) Total lines: 1228/23/2025 - 12:11:16 AM Line coverage: 53% (44/83) Branch coverage: 86.3% (19/22) Total lines: 12310/28/2025 - 12:11:27 AM Line coverage: 50% (44/88) Branch coverage: 86.3% (19/22) Total lines: 131 7/22/2025 - 12:11:20 AM Line coverage: 53.6% (44/82) Branch coverage: 86.3% (19/22) Total lines: 1228/23/2025 - 12:11:16 AM Line coverage: 53% (44/83) Branch coverage: 86.3% (19/22) Total lines: 12310/28/2025 - 12:11:27 AM Line coverage: 50% (44/88) Branch coverage: 86.3% (19/22) Total lines: 131

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
GetKeyframeData(...)0%620%
ParseStream(...)95%202095.65%

File(s)

/srv/git/jellyfin/src/Jellyfin.MediaEncoding.Keyframes/FfProbe/FfProbeKeyframeExtractor.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Diagnostics;
 4using System.Globalization;
 5using System.IO;
 6
 7namespace Jellyfin.MediaEncoding.Keyframes.FfProbe;
 8
 9/// <summary>
 10/// FfProbe based keyframe extractor.
 11/// </summary>
 12public static class FfProbeKeyframeExtractor
 13{
 14    /// <summary>
 15    /// Extracts the keyframes using the ffprobe executable at the specified path.
 16    /// </summary>
 17    /// <param name="ffProbePath">The path to the ffprobe executable.</param>
 18    /// <param name="filePath">The file path.</param>
 19    /// <returns>An instance of <see cref="KeyframeData"/>.</returns>
 20    public static KeyframeData GetKeyframeData(string ffProbePath, string filePath)
 021    {
 022        using var process = new Process
 023        {
 024            StartInfo = new ProcessStartInfo
 025            {
 026                FileName = ffProbePath,
 027                Arguments = string.Format(
 028                    CultureInfo.InvariantCulture,
 029                    "-fflags +genpts -v error -skip_frame nokey -show_entries format=duration -show_entries stream=durat
 030                    filePath),
 031
 032                CreateNoWindow = true,
 033                UseShellExecute = false,
 034                RedirectStandardOutput = true,
 035
 036                WindowStyle = ProcessWindowStyle.Hidden,
 037                ErrorDialog = false,
 038            },
 039            EnableRaisingEvents = true
 040        };
 41
 42        try
 043        {
 044            process.Start();
 45            try
 046            {
 047                process.PriorityClass = ProcessPriorityClass.BelowNormal;
 048            }
 049            catch
 050            {
 51                // We do not care if process priority setting fails
 52                // Ideally log a warning but this does not have a logger available
 053            }
 54
 055            return ParseStream(process.StandardOutput);
 56        }
 057        catch (Exception)
 058        {
 59            try
 060            {
 061                if (!process.HasExited)
 062                {
 063                    process.Kill();
 064                }
 065            }
 066            catch
 067            {
 68                // We do not care if this fails
 069            }
 70
 071            throw;
 72        }
 073    }
 74
 75    internal static KeyframeData ParseStream(StreamReader reader)
 276    {
 277        var keyframes = new List<long>();
 278        double streamDuration = 0;
 279        double formatDuration = 0;
 80
 281        using (reader)
 282        {
 1933783            while (!reader.EndOfStream)
 1933584            {
 1933585                var line = reader.ReadLine().AsSpan();
 1933586                if (line.IsEmpty)
 087                {
 088                    continue;
 89                }
 90
 1933591                var firstComma = line.IndexOf(',');
 1933592                var lineType = line[..firstComma];
 1933593                var rest = line[(firstComma + 1)..];
 1933594                if (lineType.Equals("packet", StringComparison.OrdinalIgnoreCase))
 1933195                {
 96                    // Split time and flags from the packet line. Example line: packet,7169.079000,K_
 1933197                    var secondComma = rest.IndexOf(',');
 1933198                    var ptsTime = rest[..secondComma];
 1933199                    var flags = rest[(secondComma + 1)..];
 19331100                    if (flags.StartsWith("K_"))
 222101                    {
 222102                        if (double.TryParse(ptsTime, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out v
 222103                        {
 104                            // Have to manually convert to ticks to avoid rounding errors as TimeSpan is only precise do
 222105                            keyframes.Add(Convert.ToInt64(keyframe * TimeSpan.TicksPerSecond));
 222106                        }
 222107                    }
 19331108                }
 4109                else if (lineType.Equals("stream", StringComparison.OrdinalIgnoreCase))
 2110                {
 2111                    if (double.TryParse(rest, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var stre
 1112                    {
 1113                        streamDuration = streamDurationResult;
 1114                    }
 2115                }
 2116                else if (lineType.Equals("format", StringComparison.OrdinalIgnoreCase))
 2117                {
 2118                    if (double.TryParse(rest, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var form
 2119                    {
 2120                        formatDuration = formatDurationResult;
 2121                    }
 2122                }
 19335123            }
 124
 125            // Prefer the stream duration as it should be more accurate
 2126            var duration = streamDuration > 0 ? streamDuration : formatDuration;
 127
 2128            return new KeyframeData(TimeSpan.FromSeconds(duration).Ticks, keyframes);
 129        }
 2130    }
 131}