< 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
49%
Covered lines: 44
Uncovered lines: 45
Coverable lines: 89
Total lines: 133
Line coverage: 49.4%
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 5/1/2026 - 12:13:05 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: 133 5/1/2026 - 12:13:05 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: 133

Coverage delta

Coverage delta 1 -1

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;
 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)
 022    {
 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
 045        {
 046            process.Start();
 47            try
 048            {
 049                process.PriorityClass = ProcessPriorityClass.BelowNormal;
 050            }
 051            catch
 052            {
 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)
 060        {
 61            try
 062            {
 063                if (!process.HasExited)
 064                {
 065                    process.Kill();
 066                }
 067            }
 068            catch
 069            {
 70                // We do not care if this fails
 071            }
 72
 073            throw;
 74        }
 075    }
 76
 77    internal static KeyframeData ParseStream(StreamReader reader)
 278    {
 279        var keyframes = new List<long>();
 280        double streamDuration = 0;
 281        double formatDuration = 0;
 82
 283        using (reader)
 284        {
 1933785            while (!reader.EndOfStream)
 1933586            {
 1933587                var line = reader.ReadLine().AsSpan();
 1933588                if (line.IsEmpty)
 089                {
 090                    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))
 1933197                {
 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_"))
 222103                    {
 222104                        if (double.TryParse(ptsTime, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out v
 222105                        {
 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));
 222108                        }
 222109                    }
 19331110                }
 4111                else if (lineType.Equals("stream", StringComparison.OrdinalIgnoreCase))
 2112                {
 2113                    if (double.TryParse(rest, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var stre
 1114                    {
 1115                        streamDuration = streamDurationResult;
 1116                    }
 2117                }
 2118                else if (lineType.Equals("format", StringComparison.OrdinalIgnoreCase))
 2119                {
 2120                    if (double.TryParse(rest, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var form
 2121                    {
 2122                        formatDuration = formatDurationResult;
 2123                    }
 2124                }
 19335125            }
 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}