< 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
43%
Covered lines: 26
Uncovered lines: 34
Coverable lines: 60
Total lines: 133
Line coverage: 43.3%
Branch coverage
90%
Covered branches: 20
Total branches: 22
Branch coverage: 90.9%
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: 50% (44/88) Branch coverage: 86.3% (19/22) Total lines: 1317/18/2026 - 12:15:19 AM Line coverage: 49.4% (44/89) Branch coverage: 86.3% (19/22) Total lines: 13310/29/2026 - 12:17:11 AM Line coverage: 43.3% (26/60) Branch coverage: 90.9% (20/22) Total lines: 133 5/16/2026 - 12:15:55 AM Line coverage: 50% (44/88) Branch coverage: 86.3% (19/22) Total lines: 1317/18/2026 - 12:15:19 AM Line coverage: 49.4% (44/89) Branch coverage: 86.3% (19/22) Total lines: 13310/29/2026 - 12:17:11 AM Line coverage: 43.3% (26/60) Branch coverage: 90.9% (20/22) Total lines: 133

Coverage delta

Coverage delta 7 -7

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
GetKeyframeData(...)0%620%
ParseStream(...)100%2020100%

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;
 6using System.Text;
 7
 8namespace Jellyfin.MediaEncoding.Keyframes.FfProbe;
 9
 10/// <summary>
 11/// FfProbe based keyframe extractor.
 12/// </summary>
 13public static class FfProbeKeyframeExtractor
 14{
 15    /// <summary>
 16    /// Extracts the keyframes using the ffprobe executable at the specified path.
 17    /// </summary>
 18    /// <param name="ffProbePath">The path to the ffprobe executable.</param>
 19    /// <param name="filePath">The file path.</param>
 20    /// <returns>An instance of <see cref="KeyframeData"/>.</returns>
 21    public static KeyframeData GetKeyframeData(string ffProbePath, string filePath)
 22    {
 023        using var process = new Process
 024        {
 025            StartInfo = new ProcessStartInfo
 026            {
 027                FileName = ffProbePath,
 028                Arguments = string.Format(
 029                    CultureInfo.InvariantCulture,
 030                    "-fflags +genpts -v error -skip_frame nokey -show_entries format=duration -show_entries stream=durat
 031                    filePath),
 032
 033                CreateNoWindow = true,
 034                UseShellExecute = false,
 035                StandardOutputEncoding = Encoding.UTF8,
 036                RedirectStandardOutput = true,
 037
 038                WindowStyle = ProcessWindowStyle.Hidden,
 039                ErrorDialog = false,
 040            },
 041            EnableRaisingEvents = true
 042        };
 43
 44        try
 45        {
 046            process.Start();
 47            try
 48            {
 049                process.PriorityClass = ProcessPriorityClass.BelowNormal;
 050            }
 051            catch
 52            {
 53                // We do not care if process priority setting fails
 54                // Ideally log a warning but this does not have a logger available
 055            }
 56
 057            return ParseStream(process.StandardOutput);
 58        }
 059        catch (Exception)
 60        {
 61            try
 62            {
 063                if (!process.HasExited)
 64                {
 065                    process.Kill();
 66                }
 067            }
 068            catch
 69            {
 70                // We do not care if this fails
 071            }
 72
 073            throw;
 74        }
 075    }
 76
 77    internal static KeyframeData ParseStream(StreamReader reader)
 78    {
 279        var keyframes = new List<long>();
 280        double streamDuration = 0;
 281        double formatDuration = 0;
 82
 283        using (reader)
 84        {
 1933785            while (!reader.EndOfStream)
 86            {
 1933587                var line = reader.ReadLine().AsSpan();
 1933588                if (line.IsEmpty)
 89                {
 90                    continue;
 91                }
 92
 1933593                var firstComma = line.IndexOf(',');
 1933594                var lineType = line[..firstComma];
 1933595                var rest = line[(firstComma + 1)..];
 1933596                if (lineType.Equals("packet", StringComparison.OrdinalIgnoreCase))
 97                {
 98                    // Split time and flags from the packet line. Example line: packet,7169.079000,K_
 1933199                    var secondComma = rest.IndexOf(',');
 19331100                    var ptsTime = rest[..secondComma];
 19331101                    var flags = rest[(secondComma + 1)..];
 19331102                    if (flags.StartsWith("K_"))
 103                    {
 222104                        if (double.TryParse(ptsTime, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out v
 105                        {
 106                            // Have to manually convert to ticks to avoid rounding errors as TimeSpan is only precise do
 222107                            keyframes.Add(Convert.ToInt64(keyframe * TimeSpan.TicksPerSecond));
 108                        }
 109                    }
 110                }
 4111                else if (lineType.Equals("stream", StringComparison.OrdinalIgnoreCase))
 112                {
 2113                    if (double.TryParse(rest, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var stre
 114                    {
 1115                        streamDuration = streamDurationResult;
 116                    }
 117                }
 2118                else if (lineType.Equals("format", StringComparison.OrdinalIgnoreCase))
 119                {
 2120                    if (double.TryParse(rest, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var form
 121                    {
 2122                        formatDuration = formatDurationResult;
 123                    }
 124                }
 125            }
 126
 127            // Prefer the stream duration as it should be more accurate
 2128            var duration = streamDuration > 0 ? streamDuration : formatDuration;
 129
 2130            return new KeyframeData(TimeSpan.FromSeconds(duration).Ticks, keyframes);
 131        }
 2132    }
 133}