Refactor for clarity; add docs and .editorconfig

Refactored code to consistently use `this.` for instance members, improving clarity and maintainability. Added XML documentation comments to multiple methods and properties for better API documentation. Streamlined `FfProbeKeyframeExtractor` logic and enhanced EBML parsing code with comments and style improvements. Introduced a new `.editorconfig` to disable legacy StyleCop and IDisposableAnalyzers rules. Updated binary files, assembly info, and NuGet cache files due to rebuilds and dependency changes. No functional changes were made.
This commit is contained in:
2026-02-21 16:55:27 -05:00
parent e887356385
commit 48569427a5
58 changed files with 215 additions and 113 deletions
@@ -87,54 +87,51 @@ public static class FfProbeKeyframeExtractor
double streamDuration = 0;
double formatDuration = 0;
using (reader)
while (!reader.EndOfStream)
{
while (!reader.EndOfStream)
var line = reader.ReadLine().AsSpan();
if (line.IsEmpty)
{
var line = reader.ReadLine().AsSpan();
if (line.IsEmpty)
{
continue;
}
continue;
}
var firstComma = line.IndexOf(',');
var lineType = line[..firstComma];
var rest = line[(firstComma + 1)..];
if (lineType.Equals("packet", StringComparison.OrdinalIgnoreCase))
var firstComma = line.IndexOf(',');
var lineType = line[..firstComma];
var rest = line[(firstComma + 1)..];
if (lineType.Equals("packet", StringComparison.OrdinalIgnoreCase))
{
// Split time and flags from the packet line. Example line: packet,7169.079000,K_
var secondComma = rest.IndexOf(',');
var ptsTime = rest[..secondComma];
var flags = rest[(secondComma + 1)..];
if (flags.StartsWith("K_"))
{
// Split time and flags from the packet line. Example line: packet,7169.079000,K_
var secondComma = rest.IndexOf(',');
var ptsTime = rest[..secondComma];
var flags = rest[(secondComma + 1)..];
if (flags.StartsWith("K_"))
if (double.TryParse(ptsTime, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var keyframe))
{
if (double.TryParse(ptsTime, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var keyframe))
{
// Have to manually convert to ticks to avoid rounding errors as TimeSpan is only precise down to 1 ms when converting double.
keyframes.Add(Convert.ToInt64(keyframe * TimeSpan.TicksPerSecond));
}
}
}
else if (lineType.Equals("stream", StringComparison.OrdinalIgnoreCase))
{
if (double.TryParse(rest, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var streamDurationResult))
{
streamDuration = streamDurationResult;
}
}
else if (lineType.Equals("format", StringComparison.OrdinalIgnoreCase))
{
if (double.TryParse(rest, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var formatDurationResult))
{
formatDuration = formatDurationResult;
// Have to manually convert to ticks to avoid rounding errors as TimeSpan is only precise down to 1 ms when converting double.
keyframes.Add(Convert.ToInt64(keyframe * TimeSpan.TicksPerSecond));
}
}
}
// Prefer the stream duration as it should be more accurate
var duration = streamDuration > 0 ? streamDuration : formatDuration;
return new KeyframeData(TimeSpan.FromSeconds(duration).Ticks, keyframes);
else if (lineType.Equals("stream", StringComparison.OrdinalIgnoreCase))
{
if (double.TryParse(rest, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var streamDurationResult))
{
streamDuration = streamDurationResult;
}
}
else if (lineType.Equals("format", StringComparison.OrdinalIgnoreCase))
{
if (double.TryParse(rest, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var formatDurationResult))
{
formatDuration = formatDurationResult;
}
}
}
// Prefer the stream duration as it should be more accurate
var duration = streamDuration > 0 ? streamDuration : formatDuration;
return new KeyframeData(TimeSpan.FromSeconds(duration).Ticks, keyframes);
}
}
@@ -18,8 +18,8 @@ public class KeyframeData
/// <param name="keyframeTicks">The video keyframes in ticks.</param>
public KeyframeData(long totalDuration, IReadOnlyList<long> keyframeTicks)
{
TotalDuration = totalDuration;
KeyframeTicks = keyframeTicks;
this.TotalDuration = totalDuration;
this.KeyframeTicks = keyframeTicks;
}
/// <summary>
@@ -70,6 +70,7 @@ internal static class EbmlReaderExtensions
long? tracksPosition = null;
long? cuesPosition = null;
long? infoPosition = null;
// The first element should be a SeekHead otherwise we'll have to search manually
if (!reader.FindElement(MatroskaConstants.SeekHead))
{
@@ -128,6 +129,7 @@ internal static class EbmlReaderExtensions
double? duration = null;
reader.EnterContainer();
// Mandatory element
reader.FindElement(MatroskaConstants.TimestampScale);
var timestampScale = reader.ReadUInt();
@@ -158,6 +160,7 @@ internal static class EbmlReaderExtensions
while (reader.FindElement(MatroskaConstants.TrackEntry))
{
reader.EnterContainer();
// Mandatory element
reader.FindElement(MatroskaConstants.TrackNumber);
var trackNumber = reader.ReadUInt();
@@ -27,6 +27,7 @@ public static class MatroskaKeyframeExtractor
using var reader = new EbmlReader(stream);
var seekHead = reader.ReadSeekHead();
// External lib does not support seeking backwards (yet)
Info info;
ulong videoTrackNumber;
@@ -49,6 +50,7 @@ public static class MatroskaKeyframeExtractor
{
reader.EnterContainer();
ulong? trackNumber = null;
// Mandatory element
reader.FindElement(MatroskaConstants.CueTime);
var cueTime = reader.ReadUInt();
@@ -16,8 +16,8 @@ internal class Info
/// <param name="duration">The duration of the entire file.</param>
public Info(long timestampScale, double? duration)
{
TimestampScale = timestampScale;
Duration = duration;
this.TimestampScale = timestampScale;
this.Duration = duration;
}
/// <summary>
@@ -17,9 +17,9 @@ internal class SeekHead
/// <param name="cuesPosition">The relative file position of the cues segment.</param>
public SeekHead(long infoPosition, long tracksPosition, long cuesPosition)
{
InfoPosition = infoPosition;
TracksPosition = tracksPosition;
CuesPosition = cuesPosition;
this.InfoPosition = infoPosition;
this.TracksPosition = tracksPosition;
this.CuesPosition = cuesPosition;
}
/// <summary>
@@ -1,6 +1,6 @@
{
"version": 2,
"dgSpecHash": "fWFpFkhudto=",
"dgSpecHash": "mTVFQQSIGo8=",
"success": true,
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\src\\Jellyfin.MediaEncoding.Keyframes\\Jellyfin.MediaEncoding.Keyframes.csproj",
"expectedPackageFiles": [