Minor refactor and add async Convert

This commit is contained in:
Malte Rosenbjerg 2020-03-02 23:48:52 +01:00
parent 55a7e74817
commit 7406cd0fa2
3 changed files with 110 additions and 121 deletions

View file

@ -23,6 +23,19 @@ public ArgumentContainer(params Argument[] arguments)
public Argument this[Type key] { get => _args[key]; set => _args[key] = value; } public Argument this[Type key] { get => _args[key]; set => _args[key] = value; }
public bool TryGetArgument<T>(out T output)
where T : Argument
{
if (_args.TryGetValue(typeof(T), out var arg))
{
output = (T) arg;
return true;
}
output = default;
return false;
}
public ICollection<Type> Keys => _args.Keys; public ICollection<Type> Keys => _args.Keys;
public ICollection<Argument> Values => _args.Values; public ICollection<Argument> Values => _args.Values;

View file

@ -1,5 +1,6 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace FFMpegCore.FFMPEG.Argument namespace FFMpegCore.FFMPEG.Argument
{ {
@ -29,5 +30,9 @@ IEnumerator IEnumerable.GetEnumerator()
{ {
return GetEnumerator(); return GetEnumerator();
} }
public VideoInfo[] GetAsVideoInfo()
{
return Value.Select(v => new VideoInfo(v)).ToArray();
}
} }
} }

View file

@ -13,6 +13,7 @@
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Instances; using Instances;
namespace FFMpegCore.FFMPEG namespace FFMpegCore.FFMPEG
@ -21,7 +22,7 @@ namespace FFMpegCore.FFMPEG
public class FFMpeg public class FFMpeg
{ {
IArgumentBuilder ArgumentBuilder { get; set; } IArgumentBuilder ArgumentBuilder { get; set; } = new FFArgumentBuilder();
/// <summary> /// <summary>
/// Intializes the FFMPEG encoder. /// Intializes the FFMPEG encoder.
@ -29,10 +30,7 @@ public class FFMpeg
public FFMpeg() : base() public FFMpeg() : base()
{ {
FFMpegHelper.RootExceptionCheck(FFMpegOptions.Options.RootDirectory); FFMpegHelper.RootExceptionCheck(FFMpegOptions.Options.RootDirectory);
_ffmpegPath = FFMpegOptions.Options.FFmpegBinary; _ffmpegPath = FFMpegOptions.Options.FFmpegBinary;
ArgumentBuilder = new FFArgumentBuilder();
} }
/// <summary> /// <summary>
@ -137,66 +135,46 @@ public VideoInfo Convert(
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.ForType(type)); FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.ForType(type));
FFMpegHelper.ConversionSizeExceptionCheck(source); FFMpegHelper.ConversionSizeExceptionCheck(source);
_totalTime = source.Duration;
var scale = VideoSize.Original == size ? 1 : (double) source.Height / (int) size; var scale = VideoSize.Original == size ? 1 : (double) source.Height / (int) size;
var outputSize = new Size((int) (source.Width / scale), (int) (source.Height / scale));
var outputSize = new Size(
(int) (source.Width / scale),
(int) (source.Height / scale)
);
if (outputSize.Width % 2 != 0) if (outputSize.Width % 2 != 0)
{
outputSize.Width += 1; outputSize.Width += 1;
}
var container = new ArgumentContainer(); return type switch
switch (type)
{ {
case VideoType.Mp4: VideoType.Mp4 => Convert(new ArgumentContainer(
container.Add( new InputArgument(source),
new InputArgument(source), new ThreadsArgument(multithreaded),
new ThreadsArgument(multithreaded), new ScaleArgument(outputSize),
new ScaleArgument(outputSize), new VideoCodecArgument(VideoCodec.LibX264, 2400),
new VideoCodecArgument(VideoCodec.LibX264, 2400), new SpeedArgument(speed),
new SpeedArgument(speed), new AudioCodecArgument(AudioCodec.Aac, audioQuality),
new AudioCodecArgument(AudioCodec.Aac, audioQuality), new OutputArgument(output))),
new OutputArgument(output) VideoType.Ogv => Convert(new ArgumentContainer(
); new InputArgument(source),
break; new ThreadsArgument(multithreaded),
case VideoType.Ogv: new ScaleArgument(outputSize),
container.Add( new VideoCodecArgument(VideoCodec.LibTheora, 2400),
new InputArgument(source), new SpeedArgument(speed),
new ThreadsArgument(multithreaded), new AudioCodecArgument(AudioCodec.LibVorbis, audioQuality),
new ScaleArgument(outputSize), new OutputArgument(output))),
new VideoCodecArgument(VideoCodec.LibTheora, 2400), VideoType.Ts => Convert(new ArgumentContainer(
new SpeedArgument(speed), new InputArgument(source),
new AudioCodecArgument(AudioCodec.LibVorbis, audioQuality), new CopyArgument(),
new OutputArgument(output) new BitStreamFilterArgument(Channel.Video, Filter.H264_Mp4ToAnnexB),
); new ForceFormatArgument(VideoCodec.MpegTs),
break; new OutputArgument(output))),
case VideoType.Ts: VideoType.WebM => Convert(new ArgumentContainer(
container.Add( new InputArgument(source),
new InputArgument(source), new ThreadsArgument(multithreaded),
new CopyArgument(), new ScaleArgument(outputSize),
new BitStreamFilterArgument(Channel.Video, Filter.H264_Mp4ToAnnexB), new VideoCodecArgument(VideoCodec.LibVpx, 2400),
new ForceFormatArgument(VideoCodec.MpegTs), new SpeedArgument(speed),
new OutputArgument(output) new AudioCodecArgument(AudioCodec.LibVorbis, audioQuality),
); new OutputArgument(output))),
break; _ => throw new ArgumentOutOfRangeException(nameof(type))
} };
if (!RunProcess(container, output))
{
throw new FFMpegException(FFMpegExceptionType.Conversion,
$"The video could not be converted to {Enum.GetName(typeof(VideoType), type)}");
}
_totalTime = TimeSpan.MinValue;
return new VideoInfo(output);
} }
/// <summary> /// <summary>
@ -213,14 +191,13 @@ public VideoInfo PosterWithAudio(FileInfo image, FileInfo audio, FileInfo output
FFMpegHelper.ConversionSizeExceptionCheck(Image.FromFile(image.FullName)); FFMpegHelper.ConversionSizeExceptionCheck(Image.FromFile(image.FullName));
var container = new ArgumentContainer( var container = new ArgumentContainer(
new LoopArgument(1),
new InputArgument(image.FullName, audio.FullName), new InputArgument(image.FullName, audio.FullName),
new LoopArgument(1),
new VideoCodecArgument(VideoCodec.LibX264, 2400), new VideoCodecArgument(VideoCodec.LibX264, 2400),
new AudioCodecArgument(AudioCodec.Aac, AudioQuality.Normal), new AudioCodecArgument(AudioCodec.Aac, AudioQuality.Normal),
new ShortestArgument(true), new ShortestArgument(true),
new OutputArgument(output) new OutputArgument(output)
); );
if (!RunProcess(container, output)) if (!RunProcess(container, output))
{ {
throw new FFMpegException(FFMpegExceptionType.Operation, throw new FFMpegException(FFMpegExceptionType.Operation,
@ -245,30 +222,18 @@ public VideoInfo Join(FileInfo output, params VideoInfo[] videos)
{ {
FFMpegHelper.ConversionSizeExceptionCheck(video); FFMpegHelper.ConversionSizeExceptionCheck(video);
var destinationPath = video.FullName.Replace(video.Extension, FileExtension.Ts); var destinationPath = video.FullName.Replace(video.Extension, FileExtension.Ts);
Convert( Convert(video, new FileInfo(destinationPath), VideoType.Ts);
video,
new FileInfo(destinationPath),
VideoType.Ts
);
return destinationPath; return destinationPath;
}).ToList(); }).ToList();
var container = new ArgumentContainer(
new ConcatArgument(temporaryVideoParts),
new CopyArgument(),
new BitStreamFilterArgument(Channel.Audio, Filter.Aac_AdtstoAsc),
new OutputArgument(output)
);
try try
{ {
if (!RunProcess(container, output)) return Convert(new ArgumentContainer(
{ new ConcatArgument(temporaryVideoParts),
throw new FFMpegException(FFMpegExceptionType.Operation, new CopyArgument(),
"Could not join the provided video files."); new BitStreamFilterArgument(Channel.Audio, Filter.Aac_AdtstoAsc),
} new OutputArgument(output)
));
return new VideoInfo(output);
} }
finally finally
{ {
@ -335,18 +300,10 @@ public VideoInfo SaveM3U8Stream(Uri uri, FileInfo output)
if (uri.Scheme == "http" || uri.Scheme == "https") if (uri.Scheme == "http" || uri.Scheme == "https")
{ {
var container = new ArgumentContainer( return Convert(new ArgumentContainer(
new InputArgument(uri), new InputArgument(uri),
new OutputArgument(output) new OutputArgument(output)
); ));
if (!RunProcess(container, output))
{
throw new FFMpegException(FFMpegExceptionType.Operation,
$"Saving the ${uri.AbsoluteUri} stream failed.");
}
return new VideoInfo(output);
} }
throw new ArgumentException($"Uri: {uri.AbsoluteUri}, does not point to a valid http(s) stream."); throw new ArgumentException($"Uri: {uri.AbsoluteUri}, does not point to a valid http(s) stream.");
@ -364,19 +321,12 @@ public VideoInfo Mute(VideoInfo source, FileInfo output)
FFMpegHelper.ConversionSizeExceptionCheck(source); FFMpegHelper.ConversionSizeExceptionCheck(source);
FFMpegHelper.ExtensionExceptionCheck(output, source.Extension); FFMpegHelper.ExtensionExceptionCheck(output, source.Extension);
var container = new ArgumentContainer( return Convert(new ArgumentContainer(
new InputArgument(source), new InputArgument(source),
new CopyArgument(), new CopyArgument(Channel.Video),
new DisableChannelArgument(Channel.Audio), new DisableChannelArgument(Channel.Audio),
new OutputArgument(output) new OutputArgument(output)
); ));
if (!RunProcess(container, output))
{
throw new FFMpegException(FFMpegExceptionType.Operation, "Could not mute the requested video.");
}
return new VideoInfo(output);
} }
/// <summary> /// <summary>
@ -422,41 +372,50 @@ public VideoInfo ReplaceAudio(VideoInfo source, FileInfo audio, FileInfo output,
FFMpegHelper.ConversionSizeExceptionCheck(source); FFMpegHelper.ConversionSizeExceptionCheck(source);
FFMpegHelper.ExtensionExceptionCheck(output, source.Extension); FFMpegHelper.ExtensionExceptionCheck(output, source.Extension);
var container = new ArgumentContainer( return Convert(new ArgumentContainer(
new InputArgument(source.FullName, audio.FullName), new InputArgument(source.FullName, audio.FullName),
new CopyArgument(), new CopyArgument(),
new AudioCodecArgument(AudioCodec.Aac, AudioQuality.Hd), new AudioCodecArgument(AudioCodec.Aac, AudioQuality.Hd),
new ShortestArgument(stopAtShortest), new ShortestArgument(stopAtShortest),
new OutputArgument(output) new OutputArgument(output)
); ));
if (!RunProcess(container, output))
{
throw new FFMpegException(FFMpegExceptionType.Operation, "Could not replace the video audio.");
}
return new VideoInfo(output);
} }
public VideoInfo Convert(ArgumentContainer arguments) public VideoInfo Convert(ArgumentContainer arguments)
{ {
var output = ((OutputArgument) arguments[typeof(OutputArgument)]).GetAsFileInfo(); var (sources, output) = GetInputOutput(arguments);
var sources = ((InputArgument) arguments[typeof(InputArgument)]).GetAsVideoInfo(); _totalTime = TimeSpan.FromSeconds(sources.Sum(source => source.Duration.TotalSeconds));
// Sum duration of all sources
_totalTime = TimeSpan.Zero;
foreach (var source in sources)
_totalTime += source.Duration;
if (!RunProcess(arguments, output)) if (!RunProcess(arguments, output))
{
throw new FFMpegException(FFMpegExceptionType.Operation, "Could not replace the video audio."); throw new FFMpegException(FFMpegExceptionType.Operation, "Could not replace the video audio.");
}
_totalTime = TimeSpan.MinValue; _totalTime = TimeSpan.MinValue;
return new VideoInfo(output); return new VideoInfo(output);
} }
public async Task<VideoInfo> ConvertAsync(ArgumentContainer arguments)
{
var (sources, output) = GetInputOutput(arguments);
_totalTime = TimeSpan.FromSeconds(sources.Sum(source => source.Duration.TotalSeconds));
if (!await RunProcessAsync(arguments, output))
throw new FFMpegException(FFMpegExceptionType.Operation, "Could not replace the video audio.");
_totalTime = TimeSpan.MinValue;
return new VideoInfo(output);
}
private static (VideoInfo[] Input, FileInfo Output) GetInputOutput(ArgumentContainer arguments)
{
var output = ((OutputArgument) arguments[typeof(OutputArgument)]).GetAsFileInfo();
VideoInfo[] sources;
if (arguments.TryGetArgument<InputArgument>(out var input))
sources = input.GetAsVideoInfo();
else if (arguments.TryGetArgument<ConcatArgument>(out var concat))
sources = concat.GetAsVideoInfo();
else
throw new FFMpegException(FFMpegExceptionType.Operation, "No input or concat argument found");
return (sources, output);
}
/// <summary> /// <summary>
/// Returns true if the associated process is still alive/running. /// Returns true if the associated process is still alive/running.
@ -489,9 +448,21 @@ private bool RunProcess(ArgumentContainer container, FileInfo output)
var exitCode = _instance.BlockUntilFinished(); var exitCode = _instance.BlockUntilFinished();
if (!File.Exists(output.FullName) || new FileInfo(output.FullName).Length == 0) if (!File.Exists(output.FullName) || new FileInfo(output.FullName).Length == 0)
{
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData)); throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData));
}
return exitCode == 0;
}
private async Task<bool> RunProcessAsync(ArgumentContainer container, FileInfo output)
{
_instance?.Dispose();
var arguments = ArgumentBuilder.BuildArguments(container);
_instance = new Instance(_ffmpegPath, arguments);
_instance.DataReceived += OutputData;
var exitCode = await _instance.FinishedRunning();
if (!File.Exists(output.FullName) || new FileInfo(output.FullName).Length == 0)
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData));
return exitCode == 0; return exitCode == 0;
} }