mirror of
https://github.com/revenz/FileFlowsPlugins.git
synced 2025-12-30 11:19:31 -06:00
FF-1338 - adding way to convert based on codec
FF-1337 - added ability to run comskip
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using FileFlows.VideoNodes.FfmpegBuilderNodes.Models;
|
||||
using FileFlows.VideoNodes.Helpers;
|
||||
|
||||
namespace FileFlows.VideoNodes.FfmpegBuilderNodes;
|
||||
|
||||
@@ -131,20 +132,49 @@ public class FfmpegBuilderAudioConverter : FfmpegBuilderNode
|
||||
}
|
||||
}
|
||||
|
||||
[DefaultValue("")]
|
||||
[Select(nameof(FieldOptions), 5)]
|
||||
public string Field { get; set; }
|
||||
|
||||
[TextVariable(5)]
|
||||
private static List<ListOption> _FieldOptions;
|
||||
|
||||
internal const string FIELD_TITLE = "Title";
|
||||
internal const string FIELD_LANGUAGE = "Language";
|
||||
internal const string FIELD_CODEC = "Codec";
|
||||
|
||||
public static List<ListOption> FieldOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_FieldOptions == null)
|
||||
{
|
||||
_FieldOptions = new List<ListOption>
|
||||
{
|
||||
new() { Label = "Convert All", Value = "" },
|
||||
new() { Label = "Title", Value = FIELD_TITLE },
|
||||
new() { Label = "Language", Value = FIELD_LANGUAGE },
|
||||
new() { Label = "Codec", Value = FIELD_CODEC },
|
||||
};
|
||||
}
|
||||
|
||||
return _FieldOptions;
|
||||
}
|
||||
}
|
||||
|
||||
[TextVariable(6)]
|
||||
[ConditionEquals(nameof(Field), "", true)]
|
||||
public string Pattern { get; set; }
|
||||
|
||||
[Boolean(6)]
|
||||
public bool NotMatching { get; set; }
|
||||
|
||||
[Boolean(7)]
|
||||
public bool UseLanguageCode { get; set; }
|
||||
[ConditionEquals(nameof(Field), "", true)]
|
||||
public bool NotMatching { get; set; }
|
||||
|
||||
public override int Execute(NodeParameters args)
|
||||
{
|
||||
bool converting = false;
|
||||
Regex? regex = null;
|
||||
|
||||
|
||||
foreach (var track in Model.AudioStreams)
|
||||
{
|
||||
if (track.Deleted)
|
||||
@@ -153,44 +183,74 @@ public class FfmpegBuilderAudioConverter : FfmpegBuilderNode
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(this.Pattern))
|
||||
{
|
||||
bool convertResult = ConvertTrack(args, track);
|
||||
if (convertResult)
|
||||
bool convert = false;
|
||||
if (string.IsNullOrEmpty(this.Field))
|
||||
{
|
||||
convert = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
string testValue = Field switch
|
||||
{
|
||||
args.Logger?.ILog($"Stream {track} will be converted");
|
||||
converting = true;
|
||||
FIELD_LANGUAGE => track.Language?.EmptyAsNull() ?? track.Stream?.Language ?? string.Empty,
|
||||
FIELD_TITLE => track.Title?.EmptyAsNull() ?? track.Stream?.Title ?? string.Empty,
|
||||
FIELD_CODEC => track.Codec?.EmptyAsNull() ?? track.Stream?.Codec ?? string.Empty,
|
||||
_ => null
|
||||
};
|
||||
if (testValue == null)
|
||||
{
|
||||
args.Logger?.ILog("Failed to load test value for stream: " + track);
|
||||
continue;
|
||||
}
|
||||
|
||||
string pattern = this.Pattern ?? string.Empty;
|
||||
|
||||
if (GeneralHelper.IsRegex(pattern))
|
||||
{
|
||||
try
|
||||
{
|
||||
convert =
|
||||
new Regex(pattern, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase).IsMatch(
|
||||
testValue);
|
||||
if (NotMatching)
|
||||
convert = !convert;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
args.Logger?.WLog("Invalid pattern: " + ex.Message);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (Field == FIELD_LANGUAGE)
|
||||
{
|
||||
args.Logger?.ILog("Matching language using language helper.");
|
||||
convert = LanguageHelper.AreSame(pattern, testValue);
|
||||
if (NotMatching)
|
||||
convert = !convert;
|
||||
}
|
||||
else
|
||||
{
|
||||
args.Logger?.ILog($"Stream {track} will not be converted");
|
||||
convert = string.Equals(pattern, testValue, StringComparison.InvariantCultureIgnoreCase);
|
||||
if (NotMatching)
|
||||
convert = !convert;
|
||||
}
|
||||
}
|
||||
|
||||
if (convert == false)
|
||||
{
|
||||
args.Logger?.ILog("Stream does not match conditions: " + track);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (regex == null)
|
||||
regex = new Regex(this.Pattern, RegexOptions.IgnoreCase);
|
||||
|
||||
string str = UseLanguageCode ? track.Stream.Language : track.Stream.Title;
|
||||
|
||||
if (string.IsNullOrEmpty(str) == false) // if empty we always use this since we have no info to go on
|
||||
bool convertResult = ConvertTrack(args, track);
|
||||
if (convertResult)
|
||||
{
|
||||
bool matches = regex.IsMatch(str);
|
||||
if (NotMatching)
|
||||
matches = !matches;
|
||||
if (matches)
|
||||
{
|
||||
bool convertResult = ConvertTrack(args, track);
|
||||
if (convertResult)
|
||||
{
|
||||
args.Logger?.ILog($"Stream {track} matches pattern and will be converted");
|
||||
converting = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
args.Logger?.ILog($"Stream {track} matches pattern but will not be converted");
|
||||
}
|
||||
}
|
||||
args.Logger?.ILog($"Stream {track} matches pattern and will be converted");
|
||||
converting = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
args.Logger?.ILog($"Stream {track} matches pattern but will not be converted");
|
||||
}
|
||||
}
|
||||
return converting ? 1 : 2;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using FileFlows.VideoNodes.FfmpegBuilderNodes.Models;
|
||||
using System.Text;
|
||||
using FileFlows.VideoNodes.Helpers;
|
||||
|
||||
namespace FileFlows.VideoNodes.FfmpegBuilderNodes;
|
||||
|
||||
@@ -7,6 +8,13 @@ public class FfmpegBuilderComskipChapters : FfmpegBuilderNode
|
||||
{
|
||||
public override string HelpUrl => "https://fileflows.com/docs/plugins/video-nodes/ffmpeg-builder/comskip-chapters";
|
||||
public override int Outputs => 2;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if comskip should be run if no EDL file is found
|
||||
/// </summary>
|
||||
[Boolean(1)]
|
||||
public bool RunComskipIfNoEdl { get; set; }
|
||||
|
||||
public override int Execute(NodeParameters args)
|
||||
{
|
||||
@@ -37,7 +45,17 @@ public class FfmpegBuilderComskipChapters : FfmpegBuilderNode
|
||||
if (edlFile.IsFailed)
|
||||
{
|
||||
args.Logger.ILog(edlFile.Error);
|
||||
return string.Empty;
|
||||
if (RunComskipIfNoEdl == false)
|
||||
return string.Empty;
|
||||
var csResult = ComskipHelper.RunComskip(args, args.FileService.GetLocalPath(args.WorkingFile));
|
||||
if (csResult.Failed(out string error))
|
||||
{
|
||||
args.Logger.ILog(error);
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
edlFile = csResult;
|
||||
args.Logger?.ILog("Created EDL File: " + edlFile);
|
||||
}
|
||||
|
||||
string text = System.IO.File.ReadAllText(edlFile) ?? string.Empty;
|
||||
|
||||
@@ -102,6 +102,7 @@ public class FfmpegSubtitleStream : FfmpegStream
|
||||
Codec,
|
||||
Title,
|
||||
IsDefault ? "Default" : null,
|
||||
Stream?.Forced == true ? "Forced" : null,
|
||||
Deleted ? "Deleted" : null,
|
||||
HasChange ? "Changed" : null
|
||||
}.Where(x => string.IsNullOrWhiteSpace(x) == false));
|
||||
|
||||
273
VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderHdr10.cs
Normal file
273
VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderHdr10.cs
Normal file
@@ -0,0 +1,273 @@
|
||||
// using System.Text.Json;
|
||||
// using System.Text.Json.Serialization;
|
||||
//
|
||||
// namespace FileFlows.VideoNodes.FfmpegBuilderNodes;
|
||||
//
|
||||
// /// <summary>
|
||||
// /// FFmpeg Builder flow element that encodes a video using HDR 10+
|
||||
// /// </summary>
|
||||
// public class FfmpegBuilderHdr10 : FfmpegBuilderNode
|
||||
// {
|
||||
// /// <inheritdoc />
|
||||
// public override int Inputs => 1;
|
||||
//
|
||||
// /// <inheritdoc />
|
||||
// public override int Outputs => 2;
|
||||
//
|
||||
// /// <inheritdoc />
|
||||
// public override string HelpUrl => "https://fileflows.com/docs/plugins/video-nodes/ffmpeg-builder/hdr-10";
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Gets or sets the quality of the video encode
|
||||
// /// </summary>
|
||||
// [Slider(1, inverse: true)]
|
||||
// [Range(0, 51)]
|
||||
// [DefaultValue(28)]
|
||||
// public int Quality { get; set; }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Gets or sets the speed to encode
|
||||
// /// </summary>
|
||||
// [Select(nameof(SpeedOptions), 2)]
|
||||
// public string Speed { get; set; }
|
||||
//
|
||||
// private static List<ListOption> _SpeedOptions;
|
||||
// /// <summary>
|
||||
// /// Gets or sets the codec options
|
||||
// /// </summary>
|
||||
// public static List<ListOption> SpeedOptions
|
||||
// {
|
||||
// get
|
||||
// {
|
||||
// if (_SpeedOptions == null)
|
||||
// {
|
||||
// _SpeedOptions = new List<ListOption>
|
||||
// {
|
||||
// new () { Label = "Very Slow", Value = "veryslow" },
|
||||
// new () { Label = "Slower", Value = "slower" },
|
||||
// new () { Label = "Slow", Value = "slow" },
|
||||
// new () { Label = "Medium", Value = "medium" },
|
||||
// new () { Label = "Fast", Value = "fast" },
|
||||
// new () { Label = "Faster", Value = "faster" },
|
||||
// new () { Label = "Very Fast", Value = "veryfast" },
|
||||
// new () { Label = "Super Fast", Value = "superfast" },
|
||||
// new () { Label = "Ultra Fast", Value = "ultrafast" },
|
||||
// };
|
||||
// }
|
||||
// return _SpeedOptions;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /// <inheritdoc />
|
||||
// public override int Execute(NodeParameters args)
|
||||
// {
|
||||
// var ffprobeResult = GetFFprobe(args);
|
||||
// if (ffprobeResult.Failed(out string ffprobeError))
|
||||
// {
|
||||
// args.FailureReason = ffprobeError;
|
||||
// args.Logger?.ELog(ffprobeError);
|
||||
// return -1;
|
||||
// }
|
||||
//
|
||||
// var model = GetModel();
|
||||
// var video = model.VideoStreams.FirstOrDefault();
|
||||
// if (video == null)
|
||||
// {
|
||||
// args.Logger?.WLog("No video stream found in FFmpeg Builder");
|
||||
// return 2;
|
||||
// }
|
||||
//
|
||||
// string ffprobe = ffprobeResult.Value;
|
||||
// var sideDataResult = GetColorData(args, ffprobe, args.WorkingFile);
|
||||
// if (sideDataResult.Failed(out string error))
|
||||
// {
|
||||
// args.Logger?.ILog("Failed ot get HDR10 info: " + error);
|
||||
// return 2;
|
||||
// }
|
||||
//
|
||||
// var sd = sideDataResult.Value;
|
||||
// string gx = sd.GreenX.Split('/')[0];
|
||||
// string gy = sd.GreenY.Split('/')[0];
|
||||
// string bx = sd.BlueX.Split('/')[0];
|
||||
// string by = sd.BlueY.Split('/')[0];
|
||||
// string rx = sd.RedX.Split('/')[0];
|
||||
// string ry = sd.RedY.Split('/')[0];
|
||||
// string wpx = sd.WhitePointX.Split('/')[0];
|
||||
// string wpy = sd.WhitePointY.Split('/')[0];
|
||||
// string minLum = sd.MinLuminance.Split('/')[0];
|
||||
// string maxLum = sd.MaxLuminance.Split('/')[0];
|
||||
// string display = $@"G({gx},{gy})B({bx},{by})R({rx},{ry})WP({wpx},{wpy})L({maxLum},{minLum})";
|
||||
//
|
||||
// args.Logger?.ILog("Display Information: " + display);
|
||||
//
|
||||
// video.EncodingParameters = new()
|
||||
// {
|
||||
// "libx265",
|
||||
// "-x265-params",
|
||||
// $"hdr-opt=1:repeat-headers=1:colorprim=bt2020:transfer=smpte2084:colormatrix=bt2020nc:master-display={display}:max-cll=0,0",
|
||||
// "-crf",
|
||||
// Quality.ToString(),
|
||||
// "-preset",
|
||||
// Speed,
|
||||
// "-pix_fmt",
|
||||
// "yuv420p10le"
|
||||
// };
|
||||
// // qsv
|
||||
// // video.EncodingParameters = new()
|
||||
// // {
|
||||
// // "hevc_qsv",
|
||||
// // "-global_quality",
|
||||
// // "28",
|
||||
// // "-colorspace",
|
||||
// // "bt2020nc",
|
||||
// // "-master-display",
|
||||
// // display,
|
||||
// // "-max-cll",
|
||||
// // "0,0"
|
||||
// // };
|
||||
// // nvenc
|
||||
// // video.EncodingParameters = new()
|
||||
// // {
|
||||
// // "hevc_nvenc",
|
||||
// // "-rc",
|
||||
// // "vbr_hq",
|
||||
// // "-preset",
|
||||
// // "slow",
|
||||
// // "-b:v",
|
||||
// // "10M",
|
||||
// // "-colorspace",
|
||||
// // "bt2020nc",
|
||||
// // "master-display",
|
||||
// // display,
|
||||
// // "max-cll",
|
||||
// // "0,0"
|
||||
// // };
|
||||
// video.Codec = "hevc";
|
||||
//
|
||||
// return 1;
|
||||
// }
|
||||
//
|
||||
// private Result<SideData> GetColorData(NodeParameters args, string ffprobe, string file)
|
||||
// {
|
||||
// var result = args.Execute(new()
|
||||
// {
|
||||
// Command = ffprobe,
|
||||
// ArgumentList = new[]
|
||||
// {
|
||||
// "-hide_banner",
|
||||
// "-loglevel",
|
||||
// "warning",
|
||||
// "-select_streams",
|
||||
// "v",
|
||||
// "-print_format",
|
||||
// "json",
|
||||
// "-show_frames",
|
||||
// "-read_intervals",
|
||||
// "%+#1",
|
||||
// "-show_entries",
|
||||
// "frame=color_space,color_primaries,color_transfer,side_data_list,pix_fmt",
|
||||
// "-i",
|
||||
// file
|
||||
// }
|
||||
// });
|
||||
// if (result.ExitCode != 0)
|
||||
// return Result<SideData>.Fail("FFprobe failed to get HDR+ information");
|
||||
//
|
||||
// RootObject? rootObject;
|
||||
// try
|
||||
// {
|
||||
// string json = result.StandardOutput?.EmptyAsNull() ?? result.Output;
|
||||
// rootObject = JsonSerializer.Deserialize<RootObject>(json);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// return Result<SideData>.Fail("Error parsing FFprobe JSON: " + ex.Message);
|
||||
// }
|
||||
//
|
||||
// var sideData = rootObject?.Frames?.FirstOrDefault()?.SideDataList?.FirstOrDefault();
|
||||
// if (sideData == null || sideData.BlueX == null)
|
||||
// return Result<SideData>.Fail("No side data found");
|
||||
//
|
||||
// return sideData;
|
||||
// }
|
||||
//
|
||||
// public bool Hdr10MetadataExist(NodeParameters args, string hdr10plusTool, string file)
|
||||
// {
|
||||
//
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Represents the side data of the frame.
|
||||
// /// </summary>
|
||||
// public class SideData
|
||||
// {
|
||||
// [JsonPropertyName("side_data_type")]
|
||||
// public string SideDataType { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("red_x")]
|
||||
// public string RedX { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("red_y")]
|
||||
// public string RedY { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("green_x")]
|
||||
// public string GreenX { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("green_y")]
|
||||
// public string GreenY { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("blue_x")]
|
||||
// public string BlueX { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("blue_y")]
|
||||
// public string BlueY { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("white_point_x")]
|
||||
// public string WhitePointX { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("white_point_y")]
|
||||
// public string WhitePointY { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("min_luminance")]
|
||||
// public string MinLuminance { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("max_luminance")]
|
||||
// public string MaxLuminance { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("max_content")]
|
||||
// public int MaxContent { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("max_average")]
|
||||
// public int MaxAverage { get; set; }
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Represents a frame.
|
||||
// /// </summary>
|
||||
// public class Frame
|
||||
// {
|
||||
// [JsonPropertyName("pix_fmt")]
|
||||
// public string PixFmt { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("color_space")]
|
||||
// public string ColorSpace { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("color_primaries")]
|
||||
// public string ColorPrimaries { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("color_transfer")]
|
||||
// public string ColorTransfer { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("side_data_list")]
|
||||
// public List<SideData> SideDataList { get; set; }
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Represents the root object of the JSON.
|
||||
// /// </summary>
|
||||
// public class RootObject
|
||||
// {
|
||||
// [JsonPropertyName("frames")]
|
||||
// public List<Frame> Frames { get; set; }
|
||||
// }
|
||||
// }
|
||||
157
VideoNodes/Helpers/ComskipHelper.cs
Normal file
157
VideoNodes/Helpers/ComskipHelper.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
using System.IO;
|
||||
|
||||
namespace FileFlows.VideoNodes.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Helper class for comskip
|
||||
/// </summary>
|
||||
public class ComskipHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Runs comksip against a video file and creates a EDL file
|
||||
/// </summary>
|
||||
/// <param name="args">the NodeParameters</param>
|
||||
/// <param name="file">the video file to run comskip against</param>
|
||||
/// <returns>the ELD filename</returns>
|
||||
public static Result<string> RunComskip(NodeParameters args, string file)
|
||||
{
|
||||
if (System.IO.File.Exists(file) == false)
|
||||
return Result<string>.Fail("File does not exist");
|
||||
try
|
||||
{
|
||||
var csIni = GetComskipIniFile(args, file);
|
||||
|
||||
var comskip = args.GetToolPath("comskip")?.EmptyAsNull() ?? (OperatingSystem.IsWindows()
|
||||
? "comskip.exe"
|
||||
: "comskip");
|
||||
|
||||
|
||||
string edl = FileHelper.ChangeExtension(file, "txt");
|
||||
|
||||
var result = args.Execute(new()
|
||||
{
|
||||
Command = comskip,
|
||||
ArgumentList = new[]
|
||||
{
|
||||
"--ini=" + csIni,
|
||||
file
|
||||
}
|
||||
});
|
||||
|
||||
if (File.Exists(edl) == false)
|
||||
return Result<string>.Fail("Failed to create EDL file");
|
||||
|
||||
string edlContent = File.ReadAllText(edl);
|
||||
args.Logger?.ILog(new string('-', 30) + "\n" + edlContent + new string('-', 30));
|
||||
return edl;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<string>.Fail("Failed running comskip: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static string GetComskipIniFile(NodeParameters args, string file)
|
||||
{
|
||||
var csIni = args.GetToolPath("comskip.ini");
|
||||
if (string.IsNullOrWhiteSpace(csIni) == false)
|
||||
{
|
||||
if (csIni.IndexOf("\n") > 0)
|
||||
{
|
||||
// csini is the contents of the csini, make a file
|
||||
args.Logger?.ILog("Using comskip.ini variable contents");
|
||||
var tempFile = FileHelper.Combine(args.TempPath, "comskip.ini");
|
||||
File.WriteAllText(tempFile, csIni);
|
||||
return tempFile;
|
||||
}
|
||||
args.Logger?.ILog("Using comskip.ini file variable");
|
||||
return csIni;
|
||||
}
|
||||
|
||||
var path = FileHelper.Combine(FileHelper.GetDirectory(file), "comskip.ini");
|
||||
if (File.Exists(path))
|
||||
{
|
||||
args.Logger?.ILog("Using comskip.ini found with input file.");
|
||||
return path;
|
||||
}
|
||||
|
||||
args.Logger?.ILog("Using default comskip.ini file");
|
||||
csIni = FileHelper.Combine(args.TempPath, "comskip.ini");
|
||||
|
||||
// create the default ini file
|
||||
File.WriteAllText(csIni, @"detect_method=111 ;1=black frame, 2=logo, 4=scene change, 8=fuzzy logic, 16=closed captions, 32=aspect ration, 64=silence, 128=cutscenes, 255=all
|
||||
validate_silence=1 ; Default, set to 0 to force using this clues if selected above.
|
||||
validate_uniform=1 ; Default, set to 0 to force using this clues (like pure white frames) if blackframe is selected above.
|
||||
validate_scenechange=1 ; Default, set to 0 to force using this clues if selected above.
|
||||
verbose=10 ;show a lot of extra info, level 5 is also OK, set to 0 to disable
|
||||
max_brightness=60 ;frame not black if any pixels checked are greater than this (scale 0 to 255)
|
||||
test_brightness=40 ;frame not pure black if any pixels checked are greater than this, will check average brightness (scale 0 to 255)
|
||||
max_avg_brightness=25 ;maximum average brightness for a dim frame to be considered black (scale 0 to 255) 0 means autosetting
|
||||
max_commercialbreak=600 ;maximum length in seconds to consider a segment a commercial break
|
||||
min_commercialbreak=25 ;minimum length in seconds to consider a segment a commercial break
|
||||
max_commercial_size=125 ;maximum time in seconds for a single commercial or multiple commercials if no breaks in between
|
||||
min_commercial_size=4 ;mimimum time in seconds for a single commercial
|
||||
min_show_segment_length=125 ; any segment longer than this will be scored towards show.
|
||||
non_uniformity=500 ; Set to 0 to disable cutpoints based on uniform frames
|
||||
max_volume=500 ; any frame with sound volume larger than this will not be regarded as black frame
|
||||
min_silence=12 ; Any deep silence longer than this amount of frames is a possible cutpoint
|
||||
ticker_tape=0 ; Amount of pixels from bottom to ignore in all processing
|
||||
logo_at_bottom=0 ; Set to 1 to search only for logo at the lower half of the video, do not combine with subtitle setting
|
||||
punish=0 ; Compare to average for sum of 1=brightness, 2=uniform 4=volume, 8=silence, 16=schange, set to 0 to disable
|
||||
punish_threshold=1.3 ; Multiply when amount is above average * punish_threshold
|
||||
punish_modifier=2 ; When above average * threshold multiply score by this value
|
||||
intelligent_brightness=0 ; Set to 1 to use a USA specific algorithm to tune some of the settings, not adviced outside the USA
|
||||
logo_percentile=0.92 ; if more then this amount of logo is found then logo detection will be disabled
|
||||
logo_threshold=0.75
|
||||
punish_no_logo=1 ; Default, set to 0 to avoid show segments without logo to be scored towards commercial
|
||||
aggressive_logo_rejection=0
|
||||
connect_blocks_with_logo=1 ; set to 1 if you want successive blocks with logo on the transition to be regarded as connected, set to 0 to disable
|
||||
logo_filter=0 ; set the size of the filter to apply to bad logo detection, 4 seems to be a good value.
|
||||
cut_on_ar_change=1 ; set to 1 if you want to cut also on aspect ratio changes when logo is present, set to 2 to force cuts on aspect ratio changes. set to 0 to disable
|
||||
delete_show_after_last_commercial=0 ; set to 1 if you want to delete the last block if its a show and after a commercial
|
||||
delete_show_before_or_after_current=0 ; set to 1 if you want to delete the previous and the next show in the recording, this can lead to the deletion of trailers of next show
|
||||
delete_block_after_commercial=0 ;set to max size of block in seconds to be discarded, set to 0 to disable
|
||||
remove_before=0 ; amount of seconds of show to be removed before ALL commercials
|
||||
remove_after=0 ; amount of seconds of show to be removed after ALL commercials
|
||||
shrink_logo=5 ; Reduce the duration of the logo with this amount of seconds
|
||||
after_logo=0 ; set to number of seconds after logo disappears comskip should start to search for silence to insert an additional cutpoint
|
||||
padding=0
|
||||
ms_audio_delay=5
|
||||
volume_slip=20
|
||||
max_repair_size=200 ; Will repair maximum 200 missing MPEG frames in the timeline, set to 0 to disable repairing for players that don't use PTS.
|
||||
disable_heuristics=4 bit pattern for disabling heuristics, adding 1 disables heristics 1, adding 2 disables heristics 2, adding 4 disables heristics 3, 255 disables all heuristics
|
||||
delete_logo_file=0 ; set to 1 if you want comskip to tidy up after finishing
|
||||
output_framearray=0 ; create a big excel file for detailed analysis, set to 0 to disable
|
||||
output_videoredo=0
|
||||
output_womble=0
|
||||
output_mls=0 ; set to 1 if you want MPeg Video Wizard bookmark file output
|
||||
output_cuttermaran=0
|
||||
output_mpeg2schnitt=0
|
||||
output_mpgtx=0
|
||||
output_dvrcut=0
|
||||
output_zoomplayer_chapter=0
|
||||
output_zoomplayer_cutlist=0
|
||||
output_edl=1
|
||||
output_edlx=0
|
||||
output_vcf=0
|
||||
output_bsplayer=0
|
||||
output_btv=0 ; set to 1 if you want Beyond TV chapter cutlist output
|
||||
output_projectx=0 ; set to 1 if you want ProjectX cutlist output (Xcl)
|
||||
output_avisynth=0
|
||||
output_vdr=0 ; set to 1 if you want XBMC to skipping commercials
|
||||
output_demux=0 ; set to 1 if you want comskip to demux the mpeg file while scanning
|
||||
sage_framenumber_bug=0
|
||||
sage_minute_bug=0
|
||||
live_tv=0 ; set to 1 if you use parallelprocessing and need the output while recording
|
||||
live_tv_retries=4 ; change to 16 when using live_tv in BTV, used for mpeg PS and TS
|
||||
dvrms_live_tv_retries=300 ; only used for dvr_ms
|
||||
standoff=0 ; change to 8000000 when using live_tv in BTV
|
||||
cuttermaran_options=""cut=\""true\"" unattended=\""true\"" muxResult=\""false\"" snapToCutPoints=\""true\"" closeApp=\""true\""""
|
||||
mpeg2schnitt_options=""mpeg2schnitt.exe /S /E /R25 /Z %2 %1""
|
||||
avisynth_options=""LoadPlugin(\""MPEG2Dec3.dll\"") \nMPEG2Source(\""%s\"")\n""
|
||||
dvrcut_options=""dvrcut \""%s.dvr-ms\"" \""%s_clean.dvr-ms\"" ""
|
||||
windowtitle=""Comskip - %s""");
|
||||
return csIni;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,6 @@ public class GeneralHelper
|
||||
/// <returns>True if the input is a regular expression, otherwise false.</returns>
|
||||
public static bool IsRegex(string input)
|
||||
{
|
||||
return new[] { "?", "|", "^", "$" }.Any(ch => input.Contains(ch));
|
||||
return new[] { "?", "|", "^", "$", "*" }.Any(ch => input.Contains(ch));
|
||||
}
|
||||
}
|
||||
@@ -57,9 +57,27 @@ public class FfmpegBuilder_AudioConverterTests: TestBase
|
||||
Language = "en",
|
||||
Codec = "AAC",
|
||||
Channels = 5.1f
|
||||
},
|
||||
new AudioStream
|
||||
{
|
||||
Index = 6,
|
||||
TypeIndex = 4,
|
||||
IndexString = "0:a:4",
|
||||
Language = "en",
|
||||
Codec = "eac3",
|
||||
Channels = 5.1f
|
||||
},
|
||||
new AudioStream
|
||||
{
|
||||
Index = 7,
|
||||
TypeIndex = 5,
|
||||
IndexString = "0:a:5",
|
||||
Language = "en",
|
||||
Codec = "ac3",
|
||||
Channels = 5.1f
|
||||
}
|
||||
};
|
||||
args = new NodeParameters(file, logger, false, string.Empty, null);
|
||||
args = new NodeParameters(file, logger, false, string.Empty, new LocalFileService());
|
||||
args.GetToolPathActual = (string tool) => FfmpegPath;
|
||||
args.TempPath = TempPath;
|
||||
args.Parameters.Add("VideoInfo", vii);
|
||||
@@ -77,8 +95,8 @@ public class FfmpegBuilder_AudioConverterTests: TestBase
|
||||
|
||||
FfmpegBuilderAudioConverter ffAudioConvert = new();
|
||||
ffAudioConvert.Codec = "aac";
|
||||
ffAudioConvert.Pattern = "fre";
|
||||
ffAudioConvert.UseLanguageCode = true;
|
||||
ffAudioConvert.Pattern = "fre";;
|
||||
ffAudioConvert.Field = FfmpegBuilderAudioConverter.FIELD_LANGUAGE;
|
||||
ffAudioConvert.PreExecute(args);
|
||||
int result = ffAudioConvert.Execute(args);
|
||||
Assert.AreEqual(2, result);
|
||||
@@ -92,7 +110,7 @@ public class FfmpegBuilder_AudioConverterTests: TestBase
|
||||
FfmpegBuilderAudioConverter ffAudioConvert = new();
|
||||
ffAudioConvert.Codec = "ac3";
|
||||
ffAudioConvert.Pattern = "fre";
|
||||
ffAudioConvert.UseLanguageCode = true;
|
||||
ffAudioConvert.Field = FfmpegBuilderAudioConverter.FIELD_LANGUAGE;
|
||||
ffAudioConvert.PreExecute(args);
|
||||
int result = ffAudioConvert.Execute(args);
|
||||
Assert.AreEqual(1, result);
|
||||
@@ -136,11 +154,11 @@ public class FfmpegBuilder_AudioConverterTests: TestBase
|
||||
ffAudioConvert.PreExecute(args);
|
||||
int result = ffAudioConvert.Execute(args);
|
||||
Assert.AreEqual(1, result);
|
||||
var model = args.Variables["FFMPEG_BUILDER_MODEL"] as FfmpegModel;
|
||||
var model = args.Variables[FfmpegBuilderNode.MODEL_KEY] as FfmpegModel;
|
||||
Assert.IsNotNull(model);
|
||||
var audio = model.AudioStreams[2];
|
||||
Assert.AreEqual("-map", audio.EncodingParameters[0]);
|
||||
Assert.AreEqual("0:a:2", audio.EncodingParameters[1]);
|
||||
Assert.AreEqual("0:a:{sourceTypeIndex}", audio.EncodingParameters[1]);
|
||||
Assert.AreEqual("-c:a:{index}", audio.EncodingParameters[2]);
|
||||
Assert.AreEqual("ac3", audio.EncodingParameters[3]);
|
||||
Assert.AreEqual("-ac:a:{index}", audio.EncodingParameters[4]);
|
||||
@@ -148,91 +166,6 @@ public class FfmpegBuilder_AudioConverterTests: TestBase
|
||||
Assert.AreEqual("-b:a:{index}", audio.EncodingParameters[6]);
|
||||
Assert.AreEqual("384k", audio.EncodingParameters[7]);
|
||||
}
|
||||
//[TestMethod]
|
||||
//public void FfmpegBuilder_AudioConverter_AacSameAsSource()
|
||||
//{
|
||||
// Prepare();
|
||||
|
||||
// FfmpegBuilderAudioConverter ffAudioConvert = new();
|
||||
// ffAudioConvert.Codec = "aac";
|
||||
// ffAudioConvert.Channels = 0;
|
||||
// var best = ffAudioConvert.GetBestAudioTrack(args, vii.AudioStreams);
|
||||
|
||||
// Assert.IsNotNull(best);
|
||||
|
||||
// Assert.AreEqual(5, best.Index);
|
||||
// Assert.AreEqual("AAC", best.Codec);
|
||||
// Assert.AreEqual(5.1f, best.Channels);
|
||||
//}
|
||||
|
||||
//[TestMethod]
|
||||
//public void FfmpegBuilder_AudioConverter_Ac3SameAsSource()
|
||||
//{
|
||||
// Prepare();
|
||||
|
||||
// FfmpegBuilderAudioConverter ffAudioConvert = new();
|
||||
// ffAudioConvert.Codec = "ac3";
|
||||
// ffAudioConvert.Channels = 0;
|
||||
// ffAudioConvert.Index = 1;
|
||||
// var best = ffAudioConvert.GetBestAudioTrack(args, vii.AudioStreams);
|
||||
|
||||
// Assert.IsNotNull(best);
|
||||
|
||||
// Assert.AreEqual(2, best.Index);
|
||||
// Assert.AreEqual("AC3", best.Codec);
|
||||
// Assert.AreEqual(5.1f, best.Channels);
|
||||
//}
|
||||
|
||||
//[TestMethod]
|
||||
//public void FfmpegBuilder_AudioConverter_DtsSame()
|
||||
//{
|
||||
// Prepare();
|
||||
|
||||
// FfmpegBuilderAudioConverter ffAudioConvert = new();
|
||||
// ffAudioConvert.Codec = "dts";
|
||||
// ffAudioConvert.Channels = 0;
|
||||
// var best = ffAudioConvert.GetBestAudioTrack(args, vii.AudioStreams);
|
||||
|
||||
// Assert.IsNotNull(best);
|
||||
|
||||
// Assert.AreEqual(2, best.Index);
|
||||
// Assert.AreEqual("AC3", best.Codec);
|
||||
// Assert.AreEqual(5.1f, best.Channels);
|
||||
//}
|
||||
|
||||
//[TestMethod]
|
||||
//public void FfmpegBuilder_AudioConverter_DtsStereo()
|
||||
//{
|
||||
// Prepare();
|
||||
|
||||
// FfmpegBuilderAudioConverter ffAudioConvert = new();
|
||||
// ffAudioConvert.Codec = "dts";
|
||||
// ffAudioConvert.Channels = 2;
|
||||
// var best = ffAudioConvert.GetBestAudioTrack(args, vii.AudioStreams);
|
||||
|
||||
// Assert.IsNotNull(best);
|
||||
|
||||
// Assert.AreEqual(3, best.Index);
|
||||
// Assert.AreEqual("AAC", best.Codec);
|
||||
// Assert.AreEqual(2f, best.Channels);
|
||||
//}
|
||||
|
||||
//[TestMethod]
|
||||
//public void FfmpegBuilder_AudioConverter_DtsMono()
|
||||
//{
|
||||
// Prepare();
|
||||
|
||||
// FfmpegBuilderAudioConverter ffAudioConvert = new();
|
||||
// ffAudioConvert.Codec = "dts";
|
||||
// ffAudioConvert.Channels = 1;
|
||||
// var best = ffAudioConvert.GetBestAudioTrack(args, vii.AudioStreams);
|
||||
|
||||
// Assert.IsNotNull(best);
|
||||
|
||||
// Assert.AreEqual(3, best.Index);
|
||||
// Assert.AreEqual("AAC", best.Codec);
|
||||
// Assert.AreEqual(2f, best.Channels);
|
||||
//}
|
||||
|
||||
[TestMethod]
|
||||
public void FfmpegBuilder_AudioConverter_Opus_All()
|
||||
@@ -241,7 +174,7 @@ public class FfmpegBuilder_AudioConverterTests: TestBase
|
||||
var logger = new TestLogger();
|
||||
var vi = new VideoInfoHelper(FfmpegPath, logger);
|
||||
var vii = vi.Read(file);
|
||||
var args = new NodeParameters(file, logger, false, string.Empty, null);
|
||||
var args = new NodeParameters(file, logger, false, string.Empty, new LocalFileService());
|
||||
args.GetToolPathActual = (string tool) => FfmpegPath;
|
||||
args.TempPath = TempPath;
|
||||
args.Parameters.Add("VideoInfo", vii);
|
||||
@@ -267,6 +200,32 @@ public class FfmpegBuilder_AudioConverterTests: TestBase
|
||||
var newInfo = vi.Read(args.WorkingFile);
|
||||
Assert.AreEqual("opus", newInfo.AudioStreams[0].Codec);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void FfmpegBuilder_AudioConverter_Ac3ToOpus()
|
||||
{
|
||||
Prepare();
|
||||
|
||||
FfmpegBuilderAudioConverter ffAudioConvert = new();
|
||||
ffAudioConvert.Codec = "opus";
|
||||
ffAudioConvert.Field = FfmpegBuilderAudioConverter.FIELD_CODEC;
|
||||
ffAudioConvert.Pattern = "ac3";
|
||||
ffAudioConvert.PreExecute(args);
|
||||
int result = ffAudioConvert.Execute(args);
|
||||
Assert.AreEqual(1, result);
|
||||
|
||||
var model = args.Variables[FfmpegBuilderNode.MODEL_KEY] as FfmpegModel;
|
||||
Assert.IsNotNull(model);
|
||||
|
||||
var firstAc3 = model.AudioStreams[0];
|
||||
var eac3 = model.AudioStreams[4];
|
||||
var secondAc3 = model.AudioStreams[5];
|
||||
Assert.AreEqual(firstAc3.ToString(), "0 / en / opus / 5.1 / Changed");
|
||||
Assert.AreEqual(secondAc3.ToString(), "5 / en / opus / 5.1 / Changed");
|
||||
Assert.AreEqual(eac3.ToString(), "4 / en / eac3 / 5.1");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,51 @@
|
||||
// #if(DEBUG)
|
||||
//
|
||||
// using FileFlows.VideoNodes.FfmpegBuilderNodes;
|
||||
// using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
// using VideoNodes.Tests;
|
||||
// using System.IO;
|
||||
//
|
||||
// namespace FileFlows.VideoNodes.Tests.FfmpegBuilderTests;
|
||||
//
|
||||
// [TestClass]
|
||||
// public class FfmpegBuilder_HdrTests: TestBase
|
||||
// {
|
||||
// [TestMethod]
|
||||
// public void Encode()
|
||||
// {
|
||||
// string file = Path.Combine(TestPath, "HDR10Plus_PA_DTSX.mkv");
|
||||
// if (File.Exists(file) == false)
|
||||
// throw new FileNotFoundException(file);
|
||||
//
|
||||
// var logger = new TestLogger();
|
||||
// string ffmpeg = FfmpegPath;
|
||||
// var vi = new VideoInfoHelper(ffmpeg, logger);
|
||||
// var vii = vi.Read(file);
|
||||
// var args = new NodeParameters(file, logger, false, string.Empty, new LocalFileService());
|
||||
// args.GetToolPathActual = (string tool) =>
|
||||
// {
|
||||
// if (tool.ToLowerInvariant() == "ffprobe")
|
||||
// return "/usr/local/bin/ffprobe";
|
||||
// return ffmpeg;
|
||||
// };
|
||||
// args.TempPath = TempPath;
|
||||
// args.Parameters.Add("VideoInfo", vii);
|
||||
//
|
||||
//
|
||||
// FfmpegBuilderStart ffStart = new();
|
||||
// ffStart.PreExecute(args);
|
||||
// Assert.AreEqual(1, ffStart.Execute(args));
|
||||
//
|
||||
// FfmpegBuilderHdr10 hdr10 = new();
|
||||
// hdr10.PreExecute(args);
|
||||
// hdr10.Execute(args);
|
||||
//
|
||||
// FfmpegBuilderExecutor ffExecutor = new();
|
||||
// ffExecutor.PreExecute(args);
|
||||
// int result = ffExecutor.Execute(args);
|
||||
// string log = logger.ToString();
|
||||
// Assert.AreEqual(1, result);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #endif
|
||||
@@ -74,6 +74,7 @@ public abstract class TestBase
|
||||
protected string TestFile_DefaultIsForcedSub => Path.Combine(TestPath, "sub-default-is-forced.mkv");
|
||||
protected string TestFile_TwoPassNegInifinity => Path.Combine(TestPath, "audio_normal_neg_infinity.mkv");
|
||||
protected string TestFile_4k_h264mov => Path.Combine(TestPath, "4k_h264.mov");
|
||||
protected string TestFile_4k_h264mkv => Path.Combine(TestPath, "4k_h264.mkv");
|
||||
|
||||
protected string TestFile_50_mbps_hd_h264 => Path.Combine(TestPath, "50-mbps-hd-h264.mkv");
|
||||
protected string TestFile_120_mbps_4k_uhd_hevc_10bit => Path.Combine(TestPath, "120-mbps-4k-uhd-hevc-10bit.mkv");
|
||||
|
||||
@@ -411,7 +411,7 @@ public class VideoInfoHelper
|
||||
if (rgxTitle.IsMatch(info))
|
||||
sub.Title = rgxTitle.Match(info).Value.Trim();
|
||||
|
||||
sub.Forced = info.ToLower().Contains("forced");
|
||||
sub.Forced = info.ToLower().Contains("(forced)");
|
||||
return sub;
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,10 @@
|
||||
"Outputs": {
|
||||
"1": "Commercials removed, saved to temporary file",
|
||||
"2": "No commercials detected"
|
||||
},
|
||||
"Fields": {
|
||||
"RunComskipIfNoEdl": "Run Comskip",
|
||||
"RunComskipIfNoEdl-Help":"Run comskip against the file if no comskip (EDL) file is found."
|
||||
}
|
||||
},
|
||||
"VideoFile": {
|
||||
@@ -181,12 +185,12 @@
|
||||
"Bitrate-Help": "Bitrate of the audio track",
|
||||
"Codec": "Codec",
|
||||
"Codec-Help": "The codec to use to encode the audio",
|
||||
"Field": "Field",
|
||||
"Field-Help": "The field to match the pattern against. Leave the pattern empty to match anything with no value set.",
|
||||
"Pattern": "Pattern",
|
||||
"Pattern-Help": "A regular expression to match against, eg \"commentary\" to match commentary tracks",
|
||||
"Pattern-Help": "A string or regular expression to match against, eg \"commentary\" to match commentary exactly or \".*commentary.*\" to match commentary anywhere in the string",
|
||||
"NotMatching": "Not Matching",
|
||||
"NotMatching-Help": "If audio tracks NOT matching the pattern should be converted",
|
||||
"UseLanguageCode": "Use Language Code",
|
||||
"UseLanguageCode-Help": "If the language code of the track should be used instead of the title"
|
||||
"NotMatching-Help": "If audio tracks NOT matching the pattern should be converted"
|
||||
}
|
||||
},
|
||||
"FfmpegBuilderAudioNormalization": {
|
||||
@@ -287,6 +291,10 @@
|
||||
"Outputs": {
|
||||
"1": "Commercials chapters created, added to FFMPEG Builder",
|
||||
"2": "No commercials detected"
|
||||
},
|
||||
"Fields": {
|
||||
"RunComskipIfNoEdl": "Run Comskip",
|
||||
"RunComskipIfNoEdl-Help":"Run comskip against the file if no comskip (EDL) file is found."
|
||||
}
|
||||
},
|
||||
"FfmpegBuilderCustomParameters": {
|
||||
|
||||
@@ -1,197 +1,224 @@
|
||||
namespace FileFlows.VideoNodes
|
||||
using FileFlows.VideoNodes.Helpers;
|
||||
|
||||
namespace FileFlows.VideoNodes;
|
||||
|
||||
public class ComskipRemoveAds: EncodingNode
|
||||
{
|
||||
using FileFlows.Plugin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
/// <inheritdoc />
|
||||
public override int Outputs => 2;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string HelpUrl => "https://fileflows.com/docs/plugins/video-nodes/comskip-remove-ads";
|
||||
|
||||
public class ComskipRemoveAds: EncodingNode
|
||||
/// <summary>
|
||||
/// Gets or sets if comskip should be run if no EDL file is found
|
||||
/// </summary>
|
||||
[Boolean(1)]
|
||||
public bool RunComskipIfNoEdl { get; set; }
|
||||
|
||||
private string GetEdlFile(NodeParameters args)
|
||||
{
|
||||
public override int Outputs => 2;
|
||||
|
||||
private string GetEdlFile(NodeParameters args)
|
||||
string edlFile = args.WorkingFile.Substring(0, args.WorkingFile.LastIndexOf(".", StringComparison.Ordinal) + 1) +
|
||||
"edl";
|
||||
if (args.FileService.FileIsLocal(edlFile))
|
||||
{
|
||||
string edlFile = args.WorkingFile.Substring(0, args.WorkingFile.LastIndexOf(".", StringComparison.Ordinal) + 1) +
|
||||
"edl";
|
||||
if (args.FileService.FileIsLocal(edlFile))
|
||||
{
|
||||
if (System.IO.File.Exists(edlFile))
|
||||
return edlFile;
|
||||
|
||||
return args.WorkingFile.Substring(0,
|
||||
args.WorkingFile.LastIndexOf(".", StringComparison.Ordinal) + 1) + "edl";
|
||||
}
|
||||
if (args.FileService.FileExists(edlFile).Is(true) == false)
|
||||
{
|
||||
edlFile = args.WorkingFile.Substring(0,
|
||||
args.WorkingFile.LastIndexOf(".", StringComparison.Ordinal) + 1) + "edl";
|
||||
|
||||
if (args.FileService.FileExists(edlFile).Is(true) == false)
|
||||
return string.Empty;
|
||||
|
||||
}
|
||||
if (System.IO.File.Exists(edlFile))
|
||||
return edlFile;
|
||||
|
||||
var result = args.FileService.GetLocalPath(edlFile);
|
||||
if (result.IsFailed)
|
||||
{
|
||||
args.Logger.ELog("Failed to download edl file locally: " + result.Error);
|
||||
return null;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
return args.WorkingFile.Substring(0,
|
||||
args.WorkingFile.LastIndexOf(".", StringComparison.Ordinal) + 1) + "edl";
|
||||
}
|
||||
if (args.FileService.FileExists(edlFile).Is(true) == false)
|
||||
{
|
||||
edlFile = args.WorkingFile.Substring(0,
|
||||
args.WorkingFile.LastIndexOf(".", StringComparison.Ordinal) + 1) + "edl";
|
||||
|
||||
if (args.FileService.FileExists(edlFile).Is(true) == false)
|
||||
return string.Empty;
|
||||
|
||||
}
|
||||
|
||||
var result = args.FileService.GetLocalPath(edlFile);
|
||||
if (result.IsFailed)
|
||||
{
|
||||
args.Logger.ELog("Failed to download edl file locally: " + result.Error);
|
||||
return null;
|
||||
}
|
||||
|
||||
public override int Execute(NodeParameters args)
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
public override int Execute(NodeParameters args)
|
||||
{
|
||||
VideoInfo videoInfo = GetVideoInfo(args);
|
||||
if (videoInfo == null)
|
||||
return -1;
|
||||
|
||||
var localFile = args.FileService.GetLocalPath(args.WorkingFile);
|
||||
if (localFile.Failed(out string error))
|
||||
{
|
||||
VideoInfo videoInfo = GetVideoInfo(args);
|
||||
if (videoInfo == null)
|
||||
return -1;
|
||||
float totalTime = (float)videoInfo.VideoStreams[0].Duration.TotalSeconds;
|
||||
args.Logger?.WLog("Failed to get file locally: " + error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (localFile != args.WorkingFile)
|
||||
args.SetWorkingFile(localFile);
|
||||
|
||||
float totalTime = (float)videoInfo.VideoStreams[0].Duration.TotalSeconds;
|
||||
|
||||
string edlFile = GetEdlFile(args);
|
||||
string edlFile = GetEdlFile(args);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(edlFile) || System.IO.File.Exists(edlFile) == false)
|
||||
if (string.IsNullOrWhiteSpace(edlFile) || System.IO.File.Exists(edlFile) == false)
|
||||
{
|
||||
if (RunComskipIfNoEdl)
|
||||
{
|
||||
args.Logger?.ILog("No edl file found, attempting to run comskip");
|
||||
var runComskipResult = ComskipHelper.RunComskip(args, localFile);
|
||||
if (runComskipResult.Failed(out string csError))
|
||||
{
|
||||
args.Logger?.ELog(csError);
|
||||
return 2;
|
||||
}
|
||||
edlFile = runComskipResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
args.Logger?.ILog("No EDL file found for file");
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
string text = System.IO.File.ReadAllText(edlFile) ?? string.Empty;
|
||||
float last = -1;
|
||||
List<BreakPoint> breakPoints = new List<BreakPoint>();
|
||||
foreach(string line in text.Split(new string[] { "\r\n", "\n", "\r"}, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
// 93526.47 93650.13 0
|
||||
string[] parts = line.Split(new[] { " ", "\t" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length < 2)
|
||||
continue;
|
||||
float start = 0;
|
||||
float end = 0;
|
||||
if (float.TryParse(parts[0], out start) == false || float.TryParse(parts[1], out end) == false)
|
||||
continue;
|
||||
string text = System.IO.File.ReadAllText(edlFile) ?? string.Empty;
|
||||
float last = -1;
|
||||
List<BreakPoint> breakPoints = new List<BreakPoint>();
|
||||
foreach(string line in text.Split(new string[] { "\r\n", "\n", "\r"}, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
// 93526.47 93650.13 0
|
||||
string[] parts = line.Split(new[] { " ", "\t" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length < 2)
|
||||
continue;
|
||||
float start = 0;
|
||||
float end = 0;
|
||||
if (float.TryParse(parts[0], out start) == false || float.TryParse(parts[1], out end) == false)
|
||||
continue;
|
||||
|
||||
if (start < last)
|
||||
continue;
|
||||
if (start < last)
|
||||
continue;
|
||||
|
||||
BreakPoint bp = new BreakPoint();
|
||||
bp.Start = start;
|
||||
bp.End = end;
|
||||
breakPoints.Add(bp);
|
||||
}
|
||||
BreakPoint bp = new BreakPoint();
|
||||
bp.Start = start;
|
||||
bp.End = end;
|
||||
breakPoints.Add(bp);
|
||||
}
|
||||
|
||||
if(breakPoints.Any() == false)
|
||||
{
|
||||
args.Logger?.ILog("No break points detected in file");
|
||||
return 2;
|
||||
}
|
||||
if(breakPoints.Any() == false)
|
||||
{
|
||||
args.Logger?.ILog("No break points detected in file");
|
||||
return 2;
|
||||
}
|
||||
|
||||
List<string> segments = new List<string>();
|
||||
List<string> segments = new List<string>();
|
||||
|
||||
float segStart = 0;
|
||||
string extension = args.WorkingFile.Substring(args.WorkingFile.LastIndexOf(".") + 1);
|
||||
string segmentPrefix = System.IO.Path.Combine(args.TempPath, Guid.NewGuid().ToString())+"_";
|
||||
int count = 0;
|
||||
List<string> segmentsInfo = new List<string>();
|
||||
foreach (BreakPoint bp in breakPoints)
|
||||
{
|
||||
if (EncodeSegment(segStart, bp.Start) == false)
|
||||
{
|
||||
args.Logger?.ELog("Failed to create segment: " + count);
|
||||
return 2;
|
||||
}
|
||||
segStart = bp.End;
|
||||
}
|
||||
// add the end
|
||||
if (EncodeSegment(segStart, totalTime) == false)
|
||||
float segStart = 0;
|
||||
string extension = args.WorkingFile[(args.WorkingFile.LastIndexOf(".", StringComparison.Ordinal) + 1)..];
|
||||
string segmentPrefix = System.IO.Path.Combine(args.TempPath, Guid.NewGuid().ToString())+"_";
|
||||
int count = 0;
|
||||
List<string> segmentsInfo = new List<string>();
|
||||
foreach (BreakPoint bp in breakPoints)
|
||||
{
|
||||
if (EncodeSegment(segStart, bp.Start) == false)
|
||||
{
|
||||
args.Logger?.ELog("Failed to create segment: " + count);
|
||||
return 2;
|
||||
}
|
||||
segStart = bp.End;
|
||||
}
|
||||
// add the end
|
||||
if (EncodeSegment(segStart, totalTime) == false)
|
||||
{
|
||||
args.Logger?.ELog("Failed to create segment: " + count);
|
||||
return 2;
|
||||
}
|
||||
|
||||
// stitch file back together
|
||||
string concatList = segmentPrefix + "concatlist.txt";
|
||||
string concatListContents = String.Join(Environment.NewLine, segments.Select(x => $"file '{x}'"));
|
||||
System.IO.File.WriteAllText(concatList, concatListContents);
|
||||
// stitch file back together
|
||||
string concatList = segmentPrefix + "concatlist.txt";
|
||||
string concatListContents = String.Join(Environment.NewLine, segments.Select(x => $"file '{x}'"));
|
||||
System.IO.File.WriteAllText(concatList, concatListContents);
|
||||
|
||||
args.Logger?.ILog("====================================================");
|
||||
foreach (var str in segmentsInfo)
|
||||
args.Logger?.ILog(str);
|
||||
args.Logger?.ILog("Concat list:");
|
||||
foreach (var line in concatListContents.Split(new String[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries))
|
||||
args.Logger?.ILog("====================================================");
|
||||
foreach (var str in segmentsInfo)
|
||||
args.Logger?.ILog(str);
|
||||
args.Logger?.ILog("Concat list:");
|
||||
foreach (var line in concatListContents.Split(new String[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
args.Logger?.ILog(line);
|
||||
}
|
||||
args.Logger?.ILog("====================================================");
|
||||
|
||||
|
||||
List<string> ffArgs = new List<string>
|
||||
{
|
||||
"-f", "concat",
|
||||
"-safe", "0",
|
||||
"-i", concatList,
|
||||
"-c", "copy"
|
||||
};
|
||||
|
||||
bool concatResult = Encode(args, FFMPEG, ffArgs, dontAddInputFile: true, extension: extension);
|
||||
|
||||
foreach(string segment in segments.Union(new[] { concatList }))
|
||||
{
|
||||
try
|
||||
{
|
||||
args.Logger?.ILog(line);
|
||||
System.IO.File.Delete(segment);
|
||||
}
|
||||
args.Logger?.ILog("====================================================");
|
||||
catch (Exception) { }
|
||||
}
|
||||
|
||||
if (concatResult)
|
||||
return 1;
|
||||
args.Logger?.ELog("Failed to stitch file back together");
|
||||
return 2;
|
||||
|
||||
bool EncodeSegment(float start, float end)
|
||||
{
|
||||
string segment = segmentPrefix + (++count).ToString("D2") + "." + extension;
|
||||
float duration = end - start;
|
||||
if (duration < 30)
|
||||
{
|
||||
args.Logger?.ILog("Segment is less than 30 seconds, skipping");
|
||||
return true;
|
||||
}
|
||||
List<string> ffArgs = new List<string>
|
||||
{
|
||||
"-f", "concat",
|
||||
"-safe", "0",
|
||||
"-i", concatList,
|
||||
"-ss", start.ToString(),
|
||||
"-t", duration.ToString(),
|
||||
"-c", "copy"
|
||||
};
|
||||
|
||||
bool concatResult = Encode(args, FFMPEG, ffArgs, dontAddInputFile: true, extension: extension);
|
||||
|
||||
foreach(string segment in segments.Union(new[] { concatList }))
|
||||
if (Encode(args, FFMPEG, ffArgs, outputFile: segment, updateWorkingFile: false))
|
||||
{
|
||||
try
|
||||
{
|
||||
System.IO.File.Delete(segment);
|
||||
}
|
||||
catch (Exception) { }
|
||||
segments.Add(segment);
|
||||
segmentsInfo.Add(DebugString(start, end));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (concatResult)
|
||||
return 1;
|
||||
args.Logger?.ELog("Failed to stitch file back together");
|
||||
return 2;
|
||||
|
||||
bool EncodeSegment(float start, float end)
|
||||
{
|
||||
string segment = segmentPrefix + (++count).ToString("D2") + "." + extension;
|
||||
float duration = end - start;
|
||||
if (duration < 30)
|
||||
{
|
||||
args.Logger?.ILog("Segment is less than 30 seconds, skipping");
|
||||
return true;
|
||||
}
|
||||
List<string> ffArgs = new List<string>
|
||||
{
|
||||
"-ss", start.ToString(),
|
||||
"-t", duration.ToString(),
|
||||
"-c", "copy"
|
||||
};
|
||||
if (Encode(args, FFMPEG, ffArgs, outputFile: segment, updateWorkingFile: false))
|
||||
{
|
||||
segments.Add(segment);
|
||||
segmentsInfo.Add(DebugString(start, end));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private string DebugString(float start, float end)
|
||||
{
|
||||
var tsStart = new TimeSpan((long)start * TimeSpan.TicksPerSecond);
|
||||
var tsEnd= new TimeSpan((long)end * TimeSpan.TicksPerSecond);
|
||||
|
||||
return "Segment: " + tsStart.ToString(@"mm\:ss") + " to " + tsEnd.ToString(@"mm\:ss");
|
||||
}
|
||||
private class BreakPoint
|
||||
{
|
||||
public float Start { get; set; }
|
||||
public float End { get; set; }
|
||||
|
||||
public float Duration => End - Start;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private string DebugString(float start, float end)
|
||||
{
|
||||
var tsStart = new TimeSpan((long)start * TimeSpan.TicksPerSecond);
|
||||
var tsEnd= new TimeSpan((long)end * TimeSpan.TicksPerSecond);
|
||||
|
||||
return "Segment: " + tsStart.ToString(@"mm\:ss") + " to " + tsEnd.ToString(@"mm\:ss");
|
||||
}
|
||||
private class BreakPoint
|
||||
{
|
||||
public float Start { get; set; }
|
||||
public float End { get; set; }
|
||||
|
||||
public float Duration => End - Start;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,24 @@ namespace FileFlows.VideoNodes
|
||||
}
|
||||
return fileInfo.FullName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the FFprobe location
|
||||
/// </summary>
|
||||
/// <param name="args">the node parameters</param>
|
||||
/// <returns>the FFprobe location</returns>
|
||||
protected Result<string> GetFFprobe(NodeParameters args)
|
||||
{
|
||||
string ffmpeg = args.GetToolPath("FFprobe");
|
||||
if (string.IsNullOrEmpty(ffmpeg))
|
||||
return Result<string>.Fail("FFprobe tool not found.");
|
||||
|
||||
var fileInfo = new System.IO.FileInfo(ffmpeg);
|
||||
if (fileInfo.Exists == false)
|
||||
return Result<string>.Fail("FFprobe tool configured by ffmpeg file does not exist.");
|
||||
return fileInfo.FullName;
|
||||
}
|
||||
|
||||
// protected string GetFFMpegPath(NodeParameters args)
|
||||
// {
|
||||
// string ffmpeg = args.GetToolPath("FFMpeg");
|
||||
|
||||
Reference in New Issue
Block a user