mirror of
https://github.com/rosenbjerg/FFMpegCore.git
synced 2024-11-10 08:34:12 +01:00
parent
418b0e57c7
commit
74c1222b9c
3 changed files with 110 additions and 121 deletions
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -314,7 +279,7 @@ public VideoInfo JoinImageSequence(FileInfo output, double frameRate = 30, param
|
||||||
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 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue