FF-1128 - added video is interlaced flow element

This commit is contained in:
John Andrews
2023-10-04 12:25:02 +13:00
parent fb487c971c
commit 954fdb54d8
6 changed files with 350 additions and 9 deletions

View File

@@ -0,0 +1,70 @@
namespace FileFlows.VideoNodes.FfmpegBuilderNodes;
/// <summary>
/// Flow element that adds a deinterlace filter to the video stream
/// </summary>
public class FfmpegBuilderDeinterlace : FfmpegBuilderNode
{
/// <summary>
/// Gets the number of outputs
/// </summary>
public override int Outputs => 1;
/// <summary>
/// Get the help URL
/// </summary>
public override string HelpUrl => "https://fileflows.com/docs/plugins/video-nodes/ffmpeg-builder/deinterlace";
/// <summary>
/// Gets or sets the mode for the deinterlacing
/// </summary>
[Select(nameof(ModeOptions), 1)]
[DefaultValue(1)]
public int Mode { get; set; }
private static List<ListOption> _ModeOptions;
/// <summary>
/// Gets the mode options
/// </summary>
public static List<ListOption> ModeOptions
{
get
{
if (_ModeOptions == null)
{
_ModeOptions = new List<ListOption>
{
new () { Label = "Send Frame", Value = 0 },
new () { Label = "Send Field", Value = 1 },
new () { Label = "Send Frame No Spatial", Value = 2 },
new () { Label = "Send Field No Spatial", Value = 3}
};
}
return _ModeOptions;
}
}
/// <summary>
/// Executes the flow element
/// </summary>
/// <param name="args">the parameters</param>
/// <returns>the output to call next</returns>
public override int Execute(NodeParameters args)
{
var videoInfo = GetVideoInfo(args);
if (videoInfo == null || videoInfo.VideoStreams?.Any() != true)
return -1;
var vidStream = Model.VideoStreams?.FirstOrDefault(x => x.Deleted == false);
if (vidStream == null)
{
args.Logger.ILog("No video stream found");
return 2;
}
vidStream.Filter.Add($"yadif={Mode}");
return 1;
}
}

View File

@@ -232,10 +232,10 @@ public class CanUseHardwareEncoding:Node
if (DisabledByVariables(args, encodingParams))
return false;
string ffmpeg = args.GetToolPath("FFMpeg");
string ffmpeg = args.GetToolPath("FFmpeg");
if (string.IsNullOrEmpty(ffmpeg))
{
args.Logger.ELog("FFMpeg tool not found.");
args.Logger.ELog("FFmpeg tool not found.");
return false;
}

View File

@@ -1,23 +1,37 @@
namespace FileFlows.VideoNodes;
using System.Linq;
using System.ComponentModel;
using FileFlows.Plugin;
using FileFlows.Plugin.Attributes;
using System.ComponentModel.DataAnnotations;
/// <summary>
/// Flow element to test if a video has a stream
/// </summary>
public class VideoHasStream : VideoNode
{
/// <summary>
/// Gets the number of inputs
/// </summary>
public override int Inputs => 1;
/// <summary>
/// Gets the number of outputs
/// </summary>
public override int Outputs => 2;
/// <summary>
/// Gets the type of flow element
/// </summary>
public override FlowElementType Type => FlowElementType.Logic;
/// <summary>
/// Gets the help URL
/// </summary>
public override string HelpUrl => "https://fileflows.com/docs/plugins/video-nodes/logical-nodes/video-has-stream";
/// <summary>
/// Gets or sets the type of stream to check for
/// </summary>
[Select(nameof(StreamTypeOptions), 1)]
public string Stream { get; set; }
private static List<ListOption> _StreamTypeOptions;
/// <summary>
/// Gets the types of streams available to check for
/// </summary>
public static List<ListOption> StreamTypeOptions
{
get
@@ -35,20 +49,37 @@ public class VideoHasStream : VideoNode
}
}
/// <summary>
/// Gets or sets the title to look for
/// </summary>
[TextVariable(2)]
public string Title { get; set; }
/// <summary>
/// Gets or sets the codec to look for
/// </summary>
[TextVariable(3)]
public string Codec { get; set; }
/// <summary>
/// Gets or sets the language to look for
/// </summary>
[ConditionEquals(nameof(Stream), "Video", inverse: true)]
[TextVariable(4)]
public string Language { get; set; }
/// <summary>
/// Gets or sets the number of channels to look for
/// </summary>
[ConditionEquals(nameof(Stream), "Audio")]
[NumberFloat(5)]
public float Channels { get; set; }
/// <summary>
/// Executes the flow element
/// </summary>
/// <param name="args">the arguments</param>
/// <returns>the output to call next</returns>
public override int Execute(NodeParameters args)
{
var videoInfo = GetVideoInfo(args);
@@ -104,6 +135,12 @@ public class VideoHasStream : VideoNode
return found ? 1 : 2;
}
/// <summary>
/// Tests if a value matches the pattern
/// </summary>
/// <param name="pattern">the pattern</param>
/// <param name="value">the value</param>
/// <returns>the result</returns>
private MatchResult ValueMatch(string pattern, string value)
{
if (string.IsNullOrWhiteSpace(pattern))
@@ -128,10 +165,22 @@ public class VideoHasStream : VideoNode
}
}
/// <summary>
/// Match results
/// </summary>
private enum MatchResult
{
/// <summary>
/// No Match
/// </summary>
NoMatch = 0,
/// <summary>
/// Matched
/// </summary>
Matched = 1,
/// <summary>
/// Skipped
/// </summary>
Skipped = 2
}
}

View File

@@ -0,0 +1,127 @@
namespace FileFlows.VideoNodes;
/// <summary>
/// Flow element to test if a video is interlaced
/// </summary>
public class VideoIsInterlaced : VideoNode
{
/// <summary>
/// Gets the number of inputs
/// </summary>
public override int Inputs => 1;
/// <summary>
/// Gets the number of outputs
/// </summary>
public override int Outputs => 2;
/// <summary>
/// Gets the type of flow element
/// </summary>
public override FlowElementType Type => FlowElementType.Logic;
/// <summary>
/// Gets the icon
/// </summary>
public override string Icon => "fas fa-question";
/// <summary>
/// Gets the help URL
/// </summary>
public override string HelpUrl => "https://fileflows.com/docs/plugins/video-nodes/logical-nodes/video-is-interlaced";
/// <summary>
/// Gets or sets the threshold
/// </summary>
[Range(1, 100)]
[Slider(1)]
[DefaultValue(10)]
public int Threshold { get; set; }
/// <summary>
/// Executes the flow element
/// </summary>
/// <param name="args">the arguments</param>
/// <returns>the output to call next</returns>
public override int Execute(NodeParameters args)
{
string ffmpeg = args.GetToolPath("FFmpeg");
if (string.IsNullOrEmpty(ffmpeg))
{
args.Logger?.ELog("FFmpeg tool not found.");
return 2;
}
var ffOutput = args.Execute(new()
{
Command = ffmpeg,
Silent = true,
ArgumentList = new[]
{
"-hide_banner",
"-i", args.WorkingFile,
"-vf", "idet",
"-f", "null", "-"
}
});
if (string.IsNullOrEmpty(ffOutput.StandardOutput))
{
args.Logger?.ELog("Failed to get standard output from FFmpeg");
return 2;
}
bool isInterlaced = IsVideoInterlaced(args.Logger, ffOutput.StandardOutput, Math.Max(Threshold, 1));
args.Logger?.ILog("Is interlaced: " + isInterlaced);
return isInterlaced ? 1 : 2;
}
/// <summary>
/// Determines if a video is interlaced based on FFmpeg output.
/// </summary>
/// <param name="ffmpegOutput">The FFmpeg output as a string.</param>
/// <param name="interlaceThreshold">An optional threshold for the number of interlaced frames to consider.</param>
/// <returns>True if the video is interlaced, false if it's not.</returns>
public static bool IsVideoInterlaced(ILogger logger, string ffmpegOutput, int interlaceThreshold = 10)
{
// Define regular expressions to match interlaced and progressive frame counts
Regex tffRegex = new Regex(@"TFF:[\s]*(\d+)");
Regex bffRegex = new Regex(@"BFF:[\s]*(\d+)");
Regex progressiveRegex = new Regex(@"Progressive:[\s]*(\d+)");
int tffCount = 0;
int bffCount = 0;
int progressiveCount = 0;
// Use regular expressions to extract counts of interlaced and progressive frames
MatchCollection tffMatches = tffRegex.Matches(ffmpegOutput);
MatchCollection bffMatches = bffRegex.Matches(ffmpegOutput);
MatchCollection progressiveMatches = progressiveRegex.Matches(ffmpegOutput);
foreach (Match match in tffMatches)
{
tffCount += int.Parse(match.Groups[1].Value);
}
foreach (Match match in bffMatches)
{
bffCount += int.Parse(match.Groups[1].Value);
}
foreach (Match match in progressiveMatches)
{
progressiveCount += int.Parse(match.Groups[1].Value);
}
// Calculate the total count of interlaced frames
int totalInterlacedCount = tffCount + bffCount;
int totalFrames = totalInterlacedCount + progressiveCount;
float percent = ((float)totalInterlacedCount) / totalFrames * 100f;
logger?.ILog("Interlaced threshold percentage: " + interlaceThreshold);
logger?.ILog("Total Interlaced Frames: " + totalInterlacedCount);
logger?.ILog("Total Progressive Frames: " + progressiveCount);
logger?.ILog("Total Frames: " + totalFrames);
logger?.ILog("Interlaced Percent: " + percent);
// Check if the total count of interlaced frames exceeds the threshold
return percent >= interlaceThreshold;
}
}

View File

@@ -0,0 +1,73 @@
#if(DEBUG)
using FileFlows.VideoNodes;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace VideoNodes.Tests;
[TestClass]
public class VideoIsInterlacedTests
{
[TestMethod]
public void NotInterlaced()
{
var logger = new TestLogger();
string ffmpegOutput = @"
NUMBER_OF_BYTES : 3632640
_STATISTICS_WRITING_APP: mkvmerge v60.0.0 ('Are We Copies?') 64-bit
_STATISTICS_WRITING_DATE_UTC: 2022-12-07 21:09:16
DURATION : 00:00:45.438000000
encoder : Lavc60.3.100 pcm_s16le
frame= 1 fps=0.0 q=-0.0 size=N/A time=00:00:00.63 bitrate=N/A speed=12.1x
frame= 196 fps=0.0 q=-0.0 size=N/A time=00:00:08.83 bitrate=N/A speed=15.9x
frame= 403 fps=382 q=-0.0 size=N/A time=00:00:17.50 bitrate=N/A speed=16.6x
frame= 607 fps=390 q=-0.0 size=N/A time=00:00:25.98 bitrate=N/A speed=16.7x
frame= 807 fps=392 q=-0.0 size=N/A time=00:00:34.33 bitrate=N/A speed=16.7x
frame= 1012 fps=395 q=-0.0 size=N/A time=00:00:42.84 bitrate=N/A speed=16.7x
frame= 1089 fps=397 q=-0.0 Lsize=N/A time=00:00:45.40 bitrate=N/A speed=16.5x
video:510kB audio:25542kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
[Parsed_idet_0 @ 0x5618bf1fba40] Repeated Fields: Neither: 1089 Top: 0 Bottom: 0
[Parsed_idet_0 @ 0x5618bf1fba40] Single frame detection: TFF: 0 BFF: 0 Progressive: 374 Undetermined: 715
[Parsed_idet_0 @ 0x5618bf1fba40] Multi frame detection: TFF: 0 BFF: 0 Progressive: 1089 Undetermined: 0";
bool interlaced = VideoIsInterlaced.IsVideoInterlaced(logger, ffmpegOutput, 10);
var log = logger.ToString();
Assert.IsFalse(interlaced);
Assert.IsTrue(log.Contains("Total Progressive Frames: " + (374 + 1089)));
}
[TestMethod]
public void IsInterlaced()
{
var logger = new TestLogger();
string ffmpegOutput = @"
NUMBER_OF_BYTES : 3632640
_STATISTICS_WRITING_APP: mkvmerge v60.0.0 ('Are We Copies?') 64-bit
_STATISTICS_WRITING_DATE_UTC: 2022-12-07 21:09:16
DURATION : 00:00:45.438000000
encoder : Lavc60.3.100 pcm_s16le
frame= 1 fps=0.0 q=-0.0 size=N/A time=00:00:00.63 bitrate=N/A speed=12.1x
frame= 196 fps=0.0 q=-0.0 size=N/A time=00:00:08.83 bitrate=N/A speed=15.9x
frame= 403 fps=382 q=-0.0 size=N/A time=00:00:17.50 bitrate=N/A speed=16.6x
frame= 607 fps=390 q=-0.0 size=N/A time=00:00:25.98 bitrate=N/A speed=16.7x
frame= 807 fps=392 q=-0.0 size=N/A time=00:00:34.33 bitrate=N/A speed=16.7x
frame= 1012 fps=395 q=-0.0 size=N/A time=00:00:42.84 bitrate=N/A speed=16.7x
frame= 1089 fps=397 q=-0.0 Lsize=N/A time=00:00:45.40 bitrate=N/A speed=16.5x
video:510kB audio:25542kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
[Parsed_idet_0 @ 0x5618bf1fba40] Repeated Fields: Neither: 1089 Top: 0 Bottom: 0
[Parsed_idet_0 @ 0x5618bf1fba40] Single frame detection: TFF: 420 BFF: 0 Progressive: 374 Undetermined: 715
[Parsed_idet_0 @ 0x5618bf1fba40] Multi frame detection: TFF: 65 BFF: 0 Progressive: 1089 Undetermined: 0";
bool interlaced = VideoIsInterlaced.IsVideoInterlaced(logger, ffmpegOutput, 10);
var log = logger.ToString();
Assert.IsTrue(interlaced);
Assert.IsTrue(log.Contains("Total Progressive Frames: " + (374 + 1089)));
Assert.IsTrue(log.Contains("Total Interlaced Frames: " + (420 + 65)));
}
}
#endif

View File

@@ -81,6 +81,16 @@
},
"Description": "Creates an instance of the FFMPEG Builder which can build a FFMPEG argument to then execute with the FFMPEG Executor."
},
"FfmpegBuilderDeinterlace": {
"Label": "FFMPEG Builder: Deinterlace",
"Outputs": {
"1": "Added deinterlace filter to video stream"
},
"Fields": {
"Mode": "Mode",
"Mode-Help": "The deinterlacing method to use. See help for more information."
}
},
"FfmpegBuilderExecutor": {
"Label": "FFMPEG Builder: Executor",
"Outputs": {
@@ -579,6 +589,18 @@
"OnlyTextSubtitles-Help": "If only text subtitles should be extracted, all image based subtitles will be skipped."
}
},
"VideoIsInterlaced": {
"Label": "Video Is Interlaced",
"Description": "Tests if a video file is interlaced",
"Outputs": {
"1": "Video is interlaced",
"2": "Video is not interlaced"
},
"Fields": {
"Threshold": "Threshold",
"Threshold-Help": "The percentage of frames detected as interlaced the video must have to be considered interlaced"
}
},
"VideoHasStream": {
"Label": "Video Has Stream",
"Description": "Tests if a video file contains a track",