< 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
53%
Covered lines: 44
Uncovered lines: 39
Coverable lines: 83
Total lines: 123
Line coverage: 53%
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

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();
 045            process.PriorityClass = ProcessPriorityClass.BelowNormal;
 46
 047            return ParseStream(process.StandardOutput);
 48        }
 049        catch (Exception)
 050        {
 51            try
 052            {
 053                if (!process.HasExited)
 054                {
 055                    process.Kill();
 056                }
 057            }
 058            catch
 059            {
 60                // We do not care if this fails
 061            }
 62
 063            throw;
 64        }
 065    }
 66
 67    internal static KeyframeData ParseStream(StreamReader reader)
 268    {
 269        var keyframes = new List<long>();
 270        double streamDuration = 0;
 271        double formatDuration = 0;
 72
 273        using (reader)
 274        {
 1933775            while (!reader.EndOfStream)
 1933576            {
 1933577                var line = reader.ReadLine().AsSpan();
 1933578                if (line.IsEmpty)
 079                {
 080                    continue;
 81                }
 82
 1933583                var firstComma = line.IndexOf(',');
 1933584                var lineType = line[..firstComma];
 1933585                var rest = line[(firstComma + 1)..];
 1933586                if (lineType.Equals("packet", StringComparison.OrdinalIgnoreCase))
 1933187                {
 88                    // Split time and flags from the packet line. Example line: packet,7169.079000,K_
 1933189                    var secondComma = rest.IndexOf(',');
 1933190                    var ptsTime = rest[..secondComma];
 1933191                    var flags = rest[(secondComma + 1)..];
 1933192                    if (flags.StartsWith("K_"))
 22293                    {
 22294                        if (double.TryParse(ptsTime, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out v
 22295                        {
 96                            // Have to manually convert to ticks to avoid rounding errors as TimeSpan is only precise do
 22297                            keyframes.Add(Convert.ToInt64(keyframe * TimeSpan.TicksPerSecond));
 22298                        }
 22299                    }
 19331100                }
 4101                else if (lineType.Equals("stream", StringComparison.OrdinalIgnoreCase))
 2102                {
 2103                    if (double.TryParse(rest, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var stre
 1104                    {
 1105                        streamDuration = streamDurationResult;
 1106                    }
 2107                }
 2108                else if (lineType.Equals("format", StringComparison.OrdinalIgnoreCase))
 2109                {
 2110                    if (double.TryParse(rest, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var form
 2111                    {
 2112                        formatDuration = formatDurationResult;
 2113                    }
 2114                }
 19335115            }
 116
 117            // Prefer the stream duration as it should be more accurate
 2118            var duration = streamDuration > 0 ? streamDuration : formatDuration;
 119
 2120            return new KeyframeData(TimeSpan.FromSeconds(duration).Ticks, keyframes);
 121        }
 2122    }
 123}