mirror of
https://github.com/revenz/FileFlowsPlugins.git
synced 2025-12-30 09:19:29 -06:00
398 lines
13 KiB
C#
398 lines
13 KiB
C#
using FileFlows.VideoNodes.FfmpegBuilderNodes;
|
|
using FileFlows.VideoNodes.FfmpegBuilderNodes.Models;
|
|
using FileFlows.VideoNodes.Helpers;
|
|
|
|
namespace FileFlows.VideoNodes;
|
|
|
|
/// <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
|
|
{
|
|
if (_StreamTypeOptions == null)
|
|
{
|
|
_StreamTypeOptions = new List<ListOption>
|
|
{
|
|
new () { Label = "Video", Value = "Video" },
|
|
new () { Label = "Audio", Value = "Audio" },
|
|
new () { Label = "Subtitle", Value = "Subtitle" }
|
|
};
|
|
}
|
|
return _StreamTypeOptions;
|
|
}
|
|
}
|
|
|
|
/// <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
|
|
/// This is a string so math operations can be done
|
|
/// </summary>
|
|
[ConditionEquals(nameof(Stream), "Audio")]
|
|
[MathValue(5)]
|
|
public string Channels { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets if deleted tracks should also be checked
|
|
/// </summary>
|
|
[Boolean(6)]
|
|
public bool CheckDeleted { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the forced status to check for
|
|
/// </summary>
|
|
[Select(nameof(ForcedOptions), 7)]
|
|
[ConditionEquals(nameof(Stream), "Subtitle")]
|
|
public string Forced { get; set; }
|
|
|
|
private static List<ListOption>? _ForcedOptions;
|
|
/// <summary>
|
|
/// Gets the forced options
|
|
/// </summary>
|
|
public static List<ListOption> ForcedOptions
|
|
{
|
|
get
|
|
{
|
|
if (_ForcedOptions == null)
|
|
{
|
|
_ForcedOptions = new List<ListOption>
|
|
{
|
|
new () { Label = "Any", Value = "" },
|
|
new () { Label = "Forced", Value = "Forced" },
|
|
new () { Label = "Not Forced", Value = "Not Forced" }
|
|
};
|
|
}
|
|
return _ForcedOptions;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the default status to check for
|
|
/// </summary>
|
|
[Select(nameof(DefaultOptions), 8)]
|
|
[ConditionEquals(nameof(Stream), "Video", inverse: true)]
|
|
public string Default { get; set; }
|
|
|
|
private static List<ListOption>? _DefaultOptions;
|
|
/// <summary>
|
|
/// Gets the default options
|
|
/// </summary>
|
|
public static List<ListOption> DefaultOptions
|
|
{
|
|
get
|
|
{
|
|
if (_DefaultOptions == null)
|
|
{
|
|
_DefaultOptions = new List<ListOption>
|
|
{
|
|
new () { Label = "Any", Value = "" },
|
|
new () { Label = "Default", Value = "Default" },
|
|
new () { Label = "Not Default", Value = "Not Default" }
|
|
};
|
|
}
|
|
return _DefaultOptions;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets if result should be inverted
|
|
/// </summary>
|
|
[Boolean(10)]
|
|
public bool Invert { get; set; }
|
|
|
|
/// <summary>
|
|
/// Tries to get the FFmpegModel if loaded into variables
|
|
/// </summary>
|
|
/// <param name="args">The node parameters</param>
|
|
/// <returns>the FFmpeg model if exists</returns>
|
|
protected FfmpegModel GetFfmpegModel(NodeParameters args)
|
|
{
|
|
if (args.Variables.TryGetValue(FfmpegBuilderNode.MODEL_KEY, out var variable) &&
|
|
variable is FfmpegModel ffmpegModel)
|
|
{
|
|
args.Logger?.ILog("FFmpeg Model found and will be used.");
|
|
return ffmpegModel;
|
|
}
|
|
args.Logger?.ILog("FFmpeg Model not found, using VideoInfo.");
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <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);
|
|
if (videoInfo == null)
|
|
{
|
|
args.FailureReason = "Failed to retrieve video info";
|
|
args.Logger?.ELog(args.FailureReason);
|
|
return -1;
|
|
}
|
|
|
|
bool found = false;
|
|
string title = args.ReplaceVariables(Title, stripMissing: true);
|
|
string codec = args.ReplaceVariables(Codec, stripMissing: true);
|
|
string lang = args.ReplaceVariables(Language, stripMissing: true);
|
|
var ffmpegModel = GetFfmpegModel(args);
|
|
if (ffmpegModel != null && CheckDeleted)
|
|
{
|
|
args.Logger?.ILog("Checking deleted, ignoring FFmpeg model");
|
|
ffmpegModel = null;
|
|
}
|
|
|
|
if (Channels?.Trim() is "=" or ">" or "<" or "!=" or "<=" or ">=" or "==")
|
|
Channels = string.Empty; // incase the user selected one bu didnt enter anything else
|
|
|
|
args.Logger?.ILog("Title to match: " + title);
|
|
args.Logger?.ILog("Codec to match: " + codec);
|
|
args.Logger?.ILog("Lang to match: " + lang);
|
|
|
|
if (this.Stream == "Video")
|
|
{
|
|
var streams = ffmpegModel == null
|
|
? videoInfo.VideoStreams
|
|
: ffmpegModel.VideoStreams.Where(x => x.Deleted == false).Select(x => x.Stream).ToList();
|
|
|
|
found = streams.Where(x =>
|
|
{
|
|
if (ValueMatch(title, x.Title) == MatchResult.NoMatch)
|
|
{
|
|
args.Logger.ILog("Title does not match: " + x.Title);
|
|
return false;
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(x.CodecTag) == false &&
|
|
ValueMatch(codec, x.CodecTag) == MatchResult.Matched)
|
|
{
|
|
args.Logger.ILog("Codec Tag does not match: " + x.CodecTag);
|
|
return true;
|
|
}
|
|
|
|
if (ValueMatch(codec, x.Codec) == MatchResult.NoMatch)
|
|
{
|
|
args.Logger.ILog("Codec does not match: " + x.Codec);
|
|
return false;
|
|
}
|
|
return true;
|
|
}).Any();
|
|
}
|
|
else if (this.Stream == "Audio")
|
|
{
|
|
args.Logger?.ILog("Channels to match: " + Channels);
|
|
args.Logger?.ILog("Default to match: " + (Default ?? string.Empty));
|
|
var streams = ffmpegModel == null
|
|
? videoInfo.AudioStreams
|
|
: ffmpegModel.AudioStreams.Where(x => x.Deleted == false).Select(x => x.Stream).ToList();
|
|
|
|
found = streams.Where(x =>
|
|
{
|
|
if (ValueMatch(title, x.Title) == MatchResult.NoMatch)
|
|
{
|
|
args.Logger.ILog("Title does not match: " + x.Title);
|
|
return false;
|
|
}
|
|
|
|
if (ValueMatch(codec, x.Codec) == MatchResult.NoMatch)
|
|
{
|
|
args.Logger.ILog("Codec does not match: " + x.Codec);
|
|
return false;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(lang) == false && LanguageHelper.Matches(args, lang, x.Language) == false)
|
|
return false;
|
|
|
|
double xChannels = Math.Round(x.Channels, 1);
|
|
|
|
if (string.IsNullOrWhiteSpace(Channels) == false && args.MathHelper.IsFalse(Channels, xChannels))
|
|
{
|
|
args.Logger.ILog("Channels does not match: " + xChannels + " vs " + Channels);
|
|
return false;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(Default) == false)
|
|
{
|
|
bool wantDefault = Default == "Default";
|
|
if (x.Default != wantDefault)
|
|
{
|
|
args.Logger.ILog("Defaults does not match: " + x.Default + " vs " + wantDefault);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
args.Logger.ILog("Matching audio found: " + x);
|
|
|
|
return true;
|
|
}).Any();
|
|
}
|
|
else if (this.Stream == "Subtitle")
|
|
{
|
|
var streams = ffmpegModel == null
|
|
? videoInfo.SubtitleStreams
|
|
: ffmpegModel.SubtitleStreams.Where(x => x.Deleted == false).Select(x => x.Stream).ToList();
|
|
|
|
found = streams.Where(x =>
|
|
{
|
|
if (ValueMatch(title, x.Title) == MatchResult.NoMatch)
|
|
{
|
|
args.Logger.ILog("Title does not match: " + x.Title);
|
|
return false;
|
|
}
|
|
if (ValueMatch(codec, x.Codec) == MatchResult.NoMatch)
|
|
{
|
|
args.Logger.ILog("Codec does not match: " + x.Codec);
|
|
return false;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(lang) == false && LanguageHelper.Matches(args, lang, x.Language) == false)
|
|
return false;
|
|
|
|
if (string.IsNullOrEmpty(Default) == false)
|
|
{
|
|
bool wantDefault = Default == "Default";
|
|
if (x.Default != wantDefault)
|
|
{
|
|
args.Logger.ILog("Defaults does not match: " + x.Default + " vs " + wantDefault);
|
|
return false;
|
|
}
|
|
}
|
|
if (string.IsNullOrEmpty(Forced) == false)
|
|
{
|
|
bool wantForced = Forced == "Forced";
|
|
if (x.Forced != wantForced)
|
|
{
|
|
args.Logger.ILog("Forced does not match: " + x.Forced + " vs " + Forced);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}).Any();
|
|
}
|
|
|
|
args.Logger?.ILog("Found stream: " + found);
|
|
if (Invert)
|
|
{
|
|
args.Logger?.ILog("Invert result");
|
|
found = !found;
|
|
}
|
|
|
|
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))
|
|
return MatchResult.Skipped;
|
|
if (string.IsNullOrEmpty(value))
|
|
return MatchResult.NoMatch;
|
|
|
|
if (value.ToLowerInvariant() == "hevc" && (pattern.ToLowerInvariant() is "h265" or "265" or "h.265"))
|
|
return MatchResult.Matched; // special case
|
|
|
|
bool matches = Args.StringHelper.Matches(pattern, value);
|
|
return matches ? MatchResult.Matched : MatchResult.NoMatch;
|
|
//
|
|
// try
|
|
// {
|
|
//
|
|
// if (string.IsNullOrEmpty(value))
|
|
// return MatchResult.NoMatch;
|
|
//
|
|
// if (GeneralHelper.IsRegex(pattern))
|
|
// {
|
|
// var rgx = new Regex(pattern, RegexOptions.IgnoreCase);
|
|
// if (rgx.IsMatch(value))
|
|
// return MatchResult.Matched;
|
|
// }
|
|
//
|
|
// if (value.ToLowerInvariant() == "hevc" && (pattern.ToLowerInvariant() is "h265" or "265" or "h.265"))
|
|
// return MatchResult.Matched; // special case
|
|
//
|
|
// return pattern.ToLowerInvariant().Trim() == value.ToLowerInvariant().Trim()
|
|
// ? MatchResult.Matched
|
|
// : MatchResult.NoMatch;
|
|
// }
|
|
// catch (Exception)
|
|
// {
|
|
// return MatchResult.NoMatch;
|
|
// }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Match results
|
|
/// </summary>
|
|
private enum MatchResult
|
|
{
|
|
/// <summary>
|
|
/// No Match
|
|
/// </summary>
|
|
NoMatch = 0,
|
|
/// <summary>
|
|
/// Matched
|
|
/// </summary>
|
|
Matched = 1,
|
|
/// <summary>
|
|
/// Skipped
|
|
/// </summary>
|
|
Skipped = 2
|
|
}
|
|
}
|