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 @@ namespace FFMpegCore.FFMPEG.Argument
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 @@ namespace FFMpegCore.FFMPEG.Argument
{ {
return GetEnumerator(); return GetEnumerator();
} }
public VideoInfo[] GetAsVideoInfo()
{
return Value.Select(v => new VideoInfo(v)).ToArray();
}
} }
} }

View file

@ -13,6 +13,7 @@ using System.IO;
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 @@ namespace FFMpegCore.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 @@ namespace FFMpegCore.FFMPEG
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 @@ namespace FFMpegCore.FFMPEG
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 @@ namespace FFMpegCore.FFMPEG
{ {
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
{ {
@ -314,7 +279,7 @@ namespace FFMpegCore.FFMPEG
throw new FFMpegException(FFMpegExceptionType.Operation, throw new FFMpegException(FFMpegExceptionType.Operation,
"Could not join the provided image sequence."); "Could not join the provided image sequence.");
} }
return new VideoInfo(output); return new VideoInfo(output);
} }
finally finally
@ -335,18 +300,10 @@ namespace FFMpegCore.FFMPEG
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 @@ namespace FFMpegCore.FFMPEG
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 @@ namespace FFMpegCore.FFMPEG
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 @@ namespace FFMpegCore.FFMPEG
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;
} }