< 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: 38
Coverable lines: 82
Total lines: 122
Line coverage: 53.6%
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%20.032095.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
 046            return ParseStream(process.StandardOutput);
 47        }
 048        catch (Exception)
 049        {
 50            try
 051            {
 052                if (!process.HasExited)
 053                {
 054                    process.Kill();
 055                }
 056            }
 057            catch
 058            {
 59                // We do not care if this fails
 060            }
 61
 062            throw;
 63        }
 064    }
 65
 66    internal static KeyframeData ParseStream(StreamReader reader)
 267    {
 268        var keyframes = new List<long>();
 269        double streamDuration = 0;
 270        double formatDuration = 0;
 71
 272        using (reader)
 273        {
 1933774            while (!reader.EndOfStream)
 1933575            {
 1933576                var line = reader.ReadLine().AsSpan();
 1933577                if (line.IsEmpty)
 078                {
 079                    continue;
 80                }
 81
 1933582                var firstComma = line.IndexOf(',');
 1933583                var lineType = line[..firstComma];
 1933584                var rest = line[(firstComma + 1)..];
 1933585                if (lineType.Equals("packet", StringComparison.OrdinalIgnoreCase))
 1933186                {
 87                    // Split time and flags from the packet line. Example line: packet,7169.079000,K_
 1933188                    var secondComma = rest.IndexOf(',');
 1933189                    var ptsTime = rest[..secondComma];
 1933190                    var flags = rest[(secondComma + 1)..];
 1933191                    if (flags.StartsWith("K_"))
 22292                    {
 22293                        if (double.TryParse(ptsTime, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out v
 22294                        {
 95                            // Have to manually convert to ticks to avoid rounding errors as TimeSpan is only precise do
 22296                            keyframes.Add(Convert.ToInt64(keyframe * TimeSpan.TicksPerSecond));
 22297                        }
 22298                    }
 1933199                }
 4100                else if (lineType.Equals("stream", StringComparison.OrdinalIgnoreCase))
 2101                {
 2102                    if (double.TryParse(rest, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var stre
 1103                    {
 1104                        streamDuration = streamDurationResult;
 1105                    }
 2106                }
 2107                else if (lineType.Equals("format", StringComparison.OrdinalIgnoreCase))
 2108                {
 2109                    if (double.TryParse(rest, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var form
 2110                    {
 2111                        formatDuration = formatDurationResult;
 2112                    }
 2113                }
 19335114            }
 115
 116            // Prefer the stream duration as it should be more accurate
 2117            var duration = streamDuration > 0 ? streamDuration : formatDuration;
 118
 2119            return new KeyframeData(TimeSpan.FromSeconds(duration).Ticks, keyframes);
 120        }
 2121    }
 122}