mirror of
https://github.com/revenz/FileFlowsPlugins.git
synced 2026-05-08 00:39:20 -05:00
384 lines
13 KiB
C#
384 lines
13 KiB
C#
using FileFlows.VideoNodes.FfmpegBuilderNodes.Models;
|
|
using FileFlows.VideoNodes.Helpers;
|
|
|
|
namespace FileFlows.VideoNodes.FfmpegBuilderNodes;
|
|
|
|
/// <summary>
|
|
/// FFmpeg Builder flow element that converts audio
|
|
/// </summary>
|
|
public class FfmpegBuilderAudioConverter : FfmpegBuilderNode
|
|
{
|
|
/// <inheritdoc />
|
|
public override string HelpUrl => "https://fileflows.com/docs/plugins/video-nodes/ffmpeg-builder/audio-converter";
|
|
/// <inheritdoc />
|
|
public override string Icon => "fas fa-comments";
|
|
/// <inheritdoc />
|
|
public override int Outputs => 2;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the codec to use
|
|
/// </summary>
|
|
[DefaultValue("aac")]
|
|
[Select(nameof(CodecOptions), 1)]
|
|
public string Codec { get; set; }
|
|
|
|
/// <summary>
|
|
/// The available codec options
|
|
/// </summary>
|
|
private static List<ListOption> _CodecOptions;
|
|
/// <summary>
|
|
/// Gets the available codec options
|
|
/// </summary>
|
|
public static List<ListOption> CodecOptions
|
|
{
|
|
get
|
|
{
|
|
if (_CodecOptions == null)
|
|
{
|
|
_CodecOptions = new List<ListOption>
|
|
{
|
|
new () { Label = "AAC", Value = "aac"},
|
|
new () { Label = "AC3", Value = "ac3"},
|
|
new () { Label = "DTS", Value = "dts" },
|
|
new () { Label = "EAC3", Value = "eac3" },
|
|
new () { Label = "FLAC", Value ="flac" },
|
|
new () { Label = "MP3", Value = "mp3"},
|
|
new () { Label = "PCM", Value ="pcm" },
|
|
new () { Label = "OPUS", Value = "opus"},
|
|
new () { Label = "Vorbis", Value ="libvorbis" },
|
|
};
|
|
}
|
|
return _CodecOptions;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the PCM format
|
|
/// </summary>
|
|
[DefaultValue("pcm_s16le")]
|
|
[Select(nameof(PcmFormats), 2)]
|
|
[ConditionEquals(nameof(Codec), "pcm")]
|
|
public string PcmFormat { get; set; }
|
|
|
|
/// <summary>
|
|
/// The PCM options
|
|
/// </summary>
|
|
private static List<ListOption> _PcmFormats;
|
|
/// <summary>
|
|
/// Gets the PCM options
|
|
/// </summary>
|
|
public static List<ListOption> PcmFormats
|
|
{
|
|
get
|
|
{
|
|
if (_PcmFormats == null)
|
|
{
|
|
_PcmFormats = new List<ListOption>
|
|
{
|
|
new () { Label = "Common", Value = "###GROUP###" },
|
|
new () { Label = "Signed 16-bit Little Endian", Value = "pcm_s16le" },
|
|
new () { Label = "Signed 24-bit Little Endian", Value = "pcm_s24le"},
|
|
|
|
new () { Label = "Signed", Value = "###GROUP###" },
|
|
new () { Label = "8-bit", Value = "pcm_s8"},
|
|
new () { Label = "16-bit Little Endian", Value = "pcm_s16le" },
|
|
new () { Label = "16-bit Big Endian", Value = "pcm_s16be"},
|
|
new () { Label = "24-bit Little Endian", Value = "pcm_s24le"},
|
|
new () { Label = "24-bit Big Endian", Value = "pcm_s24be"},
|
|
new () { Label = "32-bit Little Endian", Value = "pcm_s32le"},
|
|
new () { Label = "32-bit Big Endian", Value = "pcm_s32be"},
|
|
new () { Label = "64-bit Little Endian", Value = "pcm_s64le"},
|
|
new () { Label = "64-bit Big Endian", Value = "pcm_s64be"},
|
|
new () { Label = "Floating-point", Value = "###GROUP###" },
|
|
new () { Label = "32-bit Little Endian", Value = "pcm_f32le"},
|
|
new () { Label = "32-bit Big Endian", Value = "pcm_f32be"},
|
|
new () { Label = "16-bit Little Endian", Value = "pcm_f16le"},
|
|
new () { Label = "24-bit Little Endian", Value = "pcm_f24le"},
|
|
new () { Label = "64-bit Little Endian", Value = "pcm_f64le"},
|
|
new () { Label = "64-bit Big Endian", Value = "pcm_f64be"},
|
|
};
|
|
|
|
}
|
|
return _PcmFormats;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the number of channels for the converted audio
|
|
/// </summary>
|
|
[DefaultValue(0)]
|
|
[Select(nameof(ChannelsOptions), 3)]
|
|
public float Channels { get; set; }
|
|
/// <summary>
|
|
/// The channel options
|
|
/// </summary>
|
|
private static List<ListOption> _ChannelsOptions;
|
|
/// <summary>
|
|
/// Gets the channel options
|
|
/// </summary>
|
|
public static List<ListOption> ChannelsOptions
|
|
{
|
|
get
|
|
{
|
|
if (_ChannelsOptions == null)
|
|
{
|
|
_ChannelsOptions = new List<ListOption>
|
|
{
|
|
new () { Label = "Automatic", Value = 1000f},
|
|
new () { Label = "Same as source", Value = 0},
|
|
new () { Label = "Mono", Value = 1f},
|
|
new () { Label = "Stereo", Value = 2f},
|
|
new () { Label = "5.1", Value = 6},
|
|
new () { Label = "7.1", Value = 8}
|
|
};
|
|
}
|
|
return _ChannelsOptions;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the bitrate
|
|
/// </summary>
|
|
[Select(nameof(BitrateOptions), 4)]
|
|
public int Bitrate { get; set; }
|
|
/// <summary>
|
|
/// The bitrate options
|
|
/// </summary>
|
|
private static List<ListOption> _BitrateOptions;
|
|
/// <summary>
|
|
/// Gets the bitrate options
|
|
/// </summary>
|
|
public static List<ListOption> BitrateOptions
|
|
{
|
|
get
|
|
{
|
|
if (_BitrateOptions == null)
|
|
{
|
|
_BitrateOptions = new List<ListOption>
|
|
{
|
|
new () { Label = "Automatic", Value = 0},
|
|
new () { Label = "Same as source", Value = 1},
|
|
};
|
|
for (int i = 64; i <= 2048; i += 32)
|
|
{
|
|
_BitrateOptions.Add(new ListOption { Label = i + " Kbps", Value = i });
|
|
}
|
|
}
|
|
return _BitrateOptions;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets if the bitrate specified should be per channel
|
|
/// </summary>
|
|
[Boolean(5)]
|
|
public bool BitratePerChannel { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the field to match again
|
|
/// </summary>
|
|
[DefaultValue("")]
|
|
[Select(nameof(FieldOptions), 6)]
|
|
public string Field { get; set; }
|
|
/// <summary>
|
|
/// The field options
|
|
/// </summary>
|
|
private static List<ListOption> _FieldOptions;
|
|
|
|
/// <summary>
|
|
/// Constant for the Title field
|
|
/// </summary>
|
|
internal const string FIELD_TITLE = "Title";
|
|
/// <summary>
|
|
/// Constant for the Language field
|
|
/// </summary>
|
|
internal const string FIELD_LANGUAGE = "Language";
|
|
/// <summary>
|
|
/// Constant for the Codec field
|
|
/// </summary>
|
|
internal const string FIELD_CODEC = "Codec";
|
|
|
|
/// <summary>
|
|
/// Gets the field options
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the pattern to match against
|
|
/// </summary>
|
|
[TextVariable(7)]
|
|
[ConditionEquals(nameof(Field), "", true)]
|
|
public string Pattern { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets if the pattern should not match
|
|
/// </summary>
|
|
[Boolean(8)]
|
|
[ConditionEquals(nameof(Field), "", true)]
|
|
public bool NotMatching { get; set; }
|
|
|
|
/// <inheritdoc />
|
|
public override int Execute(NodeParameters args)
|
|
{
|
|
bool converting = false;
|
|
Regex? regex = null;
|
|
|
|
|
|
foreach (var track in Model.AudioStreams)
|
|
{
|
|
if (track.Deleted)
|
|
{
|
|
args.Logger?.ILog($"Stream {track} is deleted, skipping");
|
|
continue;
|
|
}
|
|
|
|
bool convert = false;
|
|
if (string.IsNullOrEmpty(this.Field))
|
|
{
|
|
convert = true;
|
|
}
|
|
else
|
|
{
|
|
string? testValue = Field switch
|
|
{
|
|
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
|
|
{
|
|
convert = string.Equals(pattern, testValue, StringComparison.InvariantCultureIgnoreCase);
|
|
if (NotMatching)
|
|
convert = !convert;
|
|
}
|
|
}
|
|
|
|
if (convert == false)
|
|
{
|
|
args.Logger?.ILog("Stream does not match conditions: " + track);
|
|
continue;
|
|
}
|
|
|
|
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");
|
|
}
|
|
}
|
|
return converting ? 1 : 2;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts and audio track
|
|
/// </summary>
|
|
/// <param name="args">the node arguments</param>
|
|
/// <param name="stream">teh stream to convert</param>
|
|
/// <returns>if the stream had to be converted or not</returns>
|
|
private bool ConvertTrack(NodeParameters args, FfmpegAudioStream stream)
|
|
{
|
|
string codec = Codec?.ToLowerInvariant() ?? string.Empty;
|
|
if (codec == "pcm")
|
|
codec = PcmFormat;
|
|
|
|
bool codecSame = stream.Stream.Codec?.ToLowerInvariant() == codec;
|
|
|
|
|
|
int originalChannels = FfmpegBuilderAudioAddTrack.GetAudioBitrateChannels(args.Logger, stream.Channels, string.Empty);
|
|
|
|
var channels = this.Channels > 999f ? 0 : this.Channels == 0 ? originalChannels : this.Channels;
|
|
|
|
int totalChannels = FfmpegBuilderAudioAddTrack.GetAudioBitrateChannels(args.Logger, channels, codec);
|
|
|
|
bool channelsSame = originalChannels == totalChannels;
|
|
|
|
int bitrate = Bitrate;
|
|
if (BitratePerChannel && bitrate > 0)
|
|
{
|
|
args.Logger?.ILog("Total channels: " + totalChannels);
|
|
args.Logger?.ILog("Bitrate Per Channel: " + bitrate);
|
|
|
|
if (bitrate == 1)
|
|
{
|
|
// same as source
|
|
// so we need to get the bitrate of the original, and divide that by the number of channels
|
|
// then times it by the number of channels we are now using
|
|
bitrate = (int)(totalChannels * ((stream.Stream.Bitrate / 1000) / stream.Stream.Channels));
|
|
}
|
|
else
|
|
{
|
|
bitrate = totalChannels * bitrate;
|
|
}
|
|
|
|
args.Logger?.ILog("Total Bitrate: " + bitrate);
|
|
}
|
|
|
|
bool bitrateSame = Bitrate < 2 || stream.Stream.Bitrate == 0 ||
|
|
Math.Abs(stream.Stream.Bitrate - Bitrate) < 0.05f;
|
|
|
|
if (codecSame && channelsSame && bitrateSame)
|
|
{
|
|
args.Logger.ILog($"Stream {stream} matches codec, channels, and bitrate, skipping conversion");
|
|
return false;
|
|
}
|
|
|
|
stream.Codec = Codec.ToLowerInvariant();
|
|
|
|
stream.EncodingParameters.AddRange(FfmpegBuilderAudioAddTrack.GetNewAudioTrackParameters(args, stream, codec, channels, bitrate, 0));
|
|
return true;
|
|
}
|
|
}
|