mirror of
https://github.com/rosenbjerg/FFMpegCore.git
synced 2024-11-10 08:34:12 +01:00
Merge pull request #126 from rosenbjerg/master
v.3.1.0
Former-commit-id: 590b2e11d1
This commit is contained in:
commit
ca6f2bcf7f
183 changed files with 4331 additions and 3099 deletions
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
|
@ -1,8 +1,9 @@
|
|||
name: CI
|
||||
on: push
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
ci:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Prepare FFMpeg
|
||||
|
@ -11,7 +12,5 @@ jobs:
|
|||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: 3.1.101
|
||||
- name: Build with dotnet
|
||||
run: dotnet build
|
||||
- name: Test with dotnet
|
||||
run: dotnet test
|
||||
run: dotnet test --logger GitHubActions
|
||||
|
|
7
.github/workflows/release.yml
vendored
7
.github/workflows/release.yml
vendored
|
@ -1,6 +1,6 @@
|
|||
name: NuGet release
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- release
|
||||
jobs:
|
||||
|
@ -9,18 +9,15 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Prepare FFMpeg
|
||||
if: github.event.pull_request.merged == false
|
||||
run: sudo apt-get update && sudo apt-get install -y ffmpeg
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: 3.1.101
|
||||
dotnet-version: 3.1
|
||||
- name: Build solution -c Release
|
||||
run: dotnet build
|
||||
- name: Run unit tests
|
||||
if: github.event.pull_request.merged == false
|
||||
run: dotnet test
|
||||
- name: Publish NuGet package
|
||||
if: github.event.pull_request.merged == true
|
||||
run: NUPKG=`find . -type f -name FFMpegCore*.nupkg` && dotnet nuget push $NUPKG -k ${{ secrets.NUGET_TOKEN }} -s https://api.nuget.org/v3/index.json
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
ccb2979c5e1d27a7a36bde6e89fd874251462810
|
|
@ -1,225 +1,318 @@
|
|||
using FFMpegCore.FFMPEG.Argument;
|
||||
using FFMpegCore.FFMPEG.Enums;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FFMpegCore.Arguments;
|
||||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
[TestClass]
|
||||
public class ArgumentBuilderTest : BaseTest
|
||||
{
|
||||
List<string> concatFiles = new List<string>
|
||||
{ "1.mp4", "2.mp4", "3.mp4", "4.mp4"};
|
||||
|
||||
FFArgumentBuilder builder;
|
||||
|
||||
public ArgumentBuilderTest() : base()
|
||||
{
|
||||
builder = new FFArgumentBuilder();
|
||||
}
|
||||
|
||||
private string GetArgumentsString(params Argument[] args)
|
||||
{
|
||||
var container = new ArgumentContainer();
|
||||
container.Add(new InputArgument("input.mp4"));
|
||||
foreach (var a in args)
|
||||
{
|
||||
container.Add(a);
|
||||
}
|
||||
container.Add(new OutputArgument("output.mp4"));
|
||||
|
||||
return builder.BuildArguments(container);
|
||||
}
|
||||
private readonly string[] _concatFiles = { "1.mp4", "2.mp4", "3.mp4", "4.mp4"};
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_IO_1()
|
||||
{
|
||||
var str = GetArgumentsString();
|
||||
|
||||
Assert.AreEqual(str, "-i \"input.mp4\" \"output.mp4\"");
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4").Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" \"output.mp4\" -y", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_Scale()
|
||||
{
|
||||
var str = GetArgumentsString(new ScaleArgument(VideoSize.Hd));
|
||||
|
||||
Assert.AreEqual(str, "-i \"input.mp4\" -vf scale=-1:720 \"output.mp4\"");
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", true, opt => opt.Scale(VideoSize.Hd)).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -vf scale=-1:720 \"output.mp4\" -y", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_AudioCodec()
|
||||
{
|
||||
var str = GetArgumentsString(new AudioCodecArgument(AudioCodec.Aac, AudioQuality.Normal));
|
||||
Assert.AreEqual(str, "-i \"input.mp4\" -c:a aac -b:a 128k \"output.mp4\"");
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", true, opt => opt.WithAudioCodec(AudioCodec.Aac)).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -c:a aac \"output.mp4\" -y", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_AudioBitrate()
|
||||
{
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", true, opt => opt.WithAudioBitrate(AudioQuality.Normal)).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -b:a 128k \"output.mp4\" -y", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_Quiet()
|
||||
{
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").WithGlobalOptions(opt => opt.WithVerbosityLevel()).OutputToFile("output.mp4", false).Arguments;
|
||||
Assert.AreEqual("-hide_banner -loglevel error -i \"input.mp4\" \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_AudioCodec_Fluent()
|
||||
{
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithAudioCodec(AudioCodec.Aac).WithAudioBitrate(128)).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -c:a aac -b:a 128k \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_BitStream()
|
||||
{
|
||||
var str = GetArgumentsString(new BitStreamFilterArgument(Channel.Audio, Filter.H264_Mp4ToAnnexB));
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithBitStreamFilter(Channel.Audio, Filter.H264_Mp4ToAnnexB)).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -bsf:a h264_mp4toannexb \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
Assert.AreEqual(str, "-i \"input.mp4\" -bsf:a h264_mp4toannexb \"output.mp4\"");
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_HardwareAcceleration_Auto()
|
||||
{
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithHardwareAcceleration()).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -hwaccel \"output.mp4\"", str);
|
||||
}
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_HardwareAcceleration_Specific()
|
||||
{
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithHardwareAcceleration(HardwareAccelerationDevice.CUVID)).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -hwaccel cuvid \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_Concat()
|
||||
{
|
||||
var container = new ArgumentContainer();
|
||||
|
||||
container.Add(new ConcatArgument(concatFiles));
|
||||
container.Add(new OutputArgument("output.mp4"));
|
||||
|
||||
var str = builder.BuildArguments(container);
|
||||
|
||||
Assert.AreEqual(str, "-i \"concat:1.mp4|2.mp4|3.mp4|4.mp4\" \"output.mp4\"");
|
||||
var str = FFMpegArguments.FromConcatInput(_concatFiles).OutputToFile("output.mp4", false).Arguments;
|
||||
Assert.AreEqual("-i \"concat:1.mp4|2.mp4|3.mp4|4.mp4\" \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_Copy_Audio()
|
||||
{
|
||||
var str = GetArgumentsString(new CopyArgument(Channel.Audio));
|
||||
|
||||
Assert.AreEqual(str, "-i \"input.mp4\" -c:a copy \"output.mp4\"");
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.CopyChannel(Channel.Audio)).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -c:a copy \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_Copy_Video()
|
||||
{
|
||||
var str = GetArgumentsString(new CopyArgument(Channel.Video));
|
||||
|
||||
Assert.AreEqual(str, "-i \"input.mp4\" -c:v copy \"output.mp4\"");
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.CopyChannel(Channel.Video)).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -c:v copy \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_Copy_Both()
|
||||
{
|
||||
var str = GetArgumentsString(new CopyArgument(Channel.Both));
|
||||
|
||||
Assert.AreEqual(str, "-i \"input.mp4\" -c copy \"output.mp4\"");
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.CopyChannel()).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -c copy \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_CpuSpeed()
|
||||
public void Builder_BuildString_DisableChannel_Audio()
|
||||
{
|
||||
var str = GetArgumentsString(new CpuSpeedArgument(10));
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.DisableChannel(Channel.Audio)).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -an \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
Assert.AreEqual(str, "-i \"input.mp4\" -quality good -cpu-used 10 -deadline realtime \"output.mp4\"");
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_DisableChannel_Video()
|
||||
{
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.DisableChannel(Channel.Video)).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -vn \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_AudioSamplingRate_Default()
|
||||
{
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithAudioSamplingRate()).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -ar 48000 \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_AudioSamplingRate()
|
||||
{
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithAudioSamplingRate(44000)).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -ar 44000 \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_VariableBitrate()
|
||||
{
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithVariableBitrate(5)).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -vbr 5 \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_Faststart()
|
||||
{
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithFastStart()).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -movflags faststart \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_Overwrite()
|
||||
{
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.OverwriteExisting()).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -y \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_RemoveMetadata()
|
||||
{
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithoutMetadata()).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -map_metadata -1 \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_Transpose()
|
||||
{
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.Transpose(Transposition.CounterClockwise90)).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -vf \"transpose=2\" \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_ForceFormat()
|
||||
{
|
||||
var str = GetArgumentsString(new ForceFormatArgument(VideoCodec.LibX264));
|
||||
|
||||
Assert.AreEqual(str, "-i \"input.mp4\" -f libx264 \"output.mp4\"");
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4", false, opt => opt.ForceFormat(VideoType.Mp4)).OutputToFile("output.mp4", false, opt => opt.ForceFormat(VideoType.Mp4)).Arguments;
|
||||
Assert.AreEqual("-f mp4 -i \"input.mp4\" -f mp4 \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_FrameOutputCount()
|
||||
{
|
||||
var str = GetArgumentsString(new FrameOutputCountArgument(50));
|
||||
|
||||
Assert.AreEqual(str, "-i \"input.mp4\" -vframes 50 \"output.mp4\"");
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithFrameOutputCount(50)).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -vframes 50 \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_FrameRate()
|
||||
{
|
||||
var str = GetArgumentsString(new FrameRateArgument(50));
|
||||
|
||||
Assert.AreEqual(str, "-i \"input.mp4\" -r 50 \"output.mp4\"");
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithFramerate(50)).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -r 50 \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_Loop()
|
||||
{
|
||||
var str = GetArgumentsString(new LoopArgument(50));
|
||||
|
||||
Assert.AreEqual(str, "-i \"input.mp4\" -loop 50 \"output.mp4\"");
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.Loop(50)).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -loop 50 \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_Seek()
|
||||
{
|
||||
var str = GetArgumentsString(new SeekArgument(TimeSpan.FromSeconds(10)));
|
||||
|
||||
Assert.AreEqual(str, "-i \"input.mp4\" -ss 00:00:10 \"output.mp4\"");
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4", false, opt => opt.Seek(TimeSpan.FromSeconds(10))).OutputToFile("output.mp4", false, opt => opt.Seek(TimeSpan.FromSeconds(10))).Arguments;
|
||||
Assert.AreEqual("-ss 00:00:10 -i \"input.mp4\" -ss 00:00:10 \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_Shortest()
|
||||
{
|
||||
var str = GetArgumentsString(new ShortestArgument(true));
|
||||
|
||||
Assert.AreEqual(str, "-i \"input.mp4\" -shortest \"output.mp4\"");
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.UsingShortest()).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -shortest \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_Size()
|
||||
{
|
||||
var str = GetArgumentsString(new SizeArgument(1920, 1080));
|
||||
|
||||
Assert.AreEqual(str, "-i \"input.mp4\" -s 1920x1080 \"output.mp4\"");
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.Resize(1920, 1080)).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -s 1920x1080 \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_Speed()
|
||||
{
|
||||
var str = GetArgumentsString(new SpeedArgument(Speed.Fast));
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithSpeedPreset(Speed.Fast)).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -preset fast \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
Assert.AreEqual(str, "-i \"input.mp4\" -preset fast \"output.mp4\"");
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_DrawtextFilter()
|
||||
{
|
||||
var str = FFMpegArguments
|
||||
.FromFileInput("input.mp4")
|
||||
.OutputToFile("output.mp4", false, opt => opt
|
||||
.DrawText(DrawTextOptions
|
||||
.Create("Stack Overflow", "/path/to/font.ttf")
|
||||
.WithParameter("fontcolor", "white")
|
||||
.WithParameter("fontsize", "24")
|
||||
.WithParameter("box", "1")
|
||||
.WithParameter("boxcolor", "black@0.5")
|
||||
.WithParameter("boxborderw", "5")
|
||||
.WithParameter("x", "(w-text_w)/2")
|
||||
.WithParameter("y", "(h-text_h)/2")))
|
||||
.Arguments;
|
||||
|
||||
Assert.AreEqual("-i \"input.mp4\" -vf drawtext=\"text='Stack Overflow':fontfile=/path/to/font.ttf:fontcolor=white:fontsize=24:box=1:boxcolor=black@0.5:boxborderw=5:x=(w-text_w)/2:y=(h-text_h)/2\" \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_DrawtextFilter_Alt()
|
||||
{
|
||||
var str = FFMpegArguments
|
||||
.FromFileInput("input.mp4")
|
||||
.OutputToFile("output.mp4", false, opt => opt
|
||||
.DrawText(DrawTextOptions
|
||||
.Create("Stack Overflow", "/path/to/font.ttf", ("fontcolor", "white"), ("fontsize", "24"))))
|
||||
.Arguments;
|
||||
|
||||
Assert.AreEqual("-i \"input.mp4\" -vf drawtext=\"text='Stack Overflow':fontfile=/path/to/font.ttf:fontcolor=white:fontsize=24\" \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_StartNumber()
|
||||
{
|
||||
var str = GetArgumentsString(new StartNumberArgument(50));
|
||||
|
||||
Assert.AreEqual(str, "-i \"input.mp4\" -start_number 50 \"output.mp4\"");
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithStartNumber(50)).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -start_number 50 \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_Threads_1()
|
||||
{
|
||||
var str = GetArgumentsString(new ThreadsArgument(50));
|
||||
|
||||
Assert.AreEqual(str, "-i \"input.mp4\" -threads 50 \"output.mp4\"");
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.UsingThreads(50)).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -threads 50 \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_Threads_2()
|
||||
{
|
||||
var str = GetArgumentsString(new ThreadsArgument(true));
|
||||
|
||||
Assert.AreEqual(str, $"-i \"input.mp4\" -threads {Environment.ProcessorCount} \"output.mp4\"");
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.UsingMultithreading(true)).Arguments;
|
||||
Assert.AreEqual($"-i \"input.mp4\" -threads {Environment.ProcessorCount} \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_Codec()
|
||||
{
|
||||
var str = GetArgumentsString(new VideoCodecArgument(VideoCodec.LibX264));
|
||||
|
||||
Assert.AreEqual(str, "-i \"input.mp4\" -c:v libx264 -pix_fmt yuv420p \"output.mp4\"");
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithVideoCodec(VideoCodec.LibX264)).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -c:v libx264 \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_Codec_Override()
|
||||
{
|
||||
var str = GetArgumentsString(new VideoCodecArgument(VideoCodec.LibX264), new OverrideArgument());
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", true, opt => opt.WithVideoCodec(VideoCodec.LibX264).ForcePixelFormat("yuv420p")).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -c:v libx264 -pix_fmt yuv420p \"output.mp4\" -y", str);
|
||||
}
|
||||
|
||||
Assert.AreEqual(str, "-i \"input.mp4\" -c:v libx264 -pix_fmt yuv420p -y \"output.mp4\"");
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_Duration()
|
||||
{
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithDuration(TimeSpan.FromSeconds(20))).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -t 00:00:20 \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_Duration() {
|
||||
var str = GetArgumentsString(new DurationArgument(TimeSpan.FromSeconds(20)));
|
||||
public void Builder_BuildString_Raw()
|
||||
{
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4", false, opt => opt.WithCustomArgument(null!)).OutputToFile("output.mp4", false, opt => opt.WithCustomArgument(null!)).Arguments;
|
||||
Assert.AreEqual(" -i \"input.mp4\" \"output.mp4\"", str);
|
||||
|
||||
Assert.AreEqual(str, "-i \"input.mp4\" -t 00:00:20 \"output.mp4\"");
|
||||
str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithCustomArgument("-acodec copy")).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -acodec copy \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_ForcePixelFormat()
|
||||
{
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.ForcePixelFormat("yuv444p")).Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -pix_fmt yuv444p \"output.mp4\"", str);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using FFMpegCore.Enums;
|
||||
using System;
|
||||
using FFMpegCore.Enums;
|
||||
using FFMpegCore.Test.Resources;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System.IO;
|
||||
|
@ -15,14 +16,12 @@ public void Audio_Remove()
|
|||
|
||||
try
|
||||
{
|
||||
Encoder.Mute(VideoInfo.FromFileInfo(Input), output);
|
||||
|
||||
Assert.IsTrue(File.Exists(output.FullName));
|
||||
FFMpeg.Mute(Input.FullName, output);
|
||||
Assert.IsTrue(File.Exists(output));
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(output.FullName))
|
||||
output.Delete();
|
||||
if (File.Exists(output)) File.Delete(output);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,14 +32,12 @@ public void Audio_Save()
|
|||
|
||||
try
|
||||
{
|
||||
Encoder.ExtractAudio(VideoInfo.FromFileInfo(Input), output);
|
||||
|
||||
Assert.IsTrue(File.Exists(output.FullName));
|
||||
FFMpeg.ExtractAudio(Input.FullName, output);
|
||||
Assert.IsTrue(File.Exists(output));
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(output.FullName))
|
||||
output.Delete();
|
||||
if (File.Exists(output)) File.Delete(output);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,16 +47,17 @@ public void Audio_Add()
|
|||
var output = Input.OutputLocation(VideoType.Mp4);
|
||||
try
|
||||
{
|
||||
var input = VideoInfo.FromFileInfo(VideoLibrary.LocalVideoNoAudio);
|
||||
Encoder.ReplaceAudio(input, VideoLibrary.LocalAudio, output);
|
||||
|
||||
Assert.AreEqual(input.Duration, VideoInfo.FromFileInfo(output).Duration);
|
||||
Assert.IsTrue(File.Exists(output.FullName));
|
||||
var success = FFMpeg.ReplaceAudio(VideoLibrary.LocalVideoNoAudio.FullName, VideoLibrary.LocalAudio.FullName, output);
|
||||
Assert.IsTrue(success);
|
||||
var audioAnalysis = FFProbe.Analyse(VideoLibrary.LocalVideoNoAudio.FullName);
|
||||
var videoAnalysis = FFProbe.Analyse(VideoLibrary.LocalAudio.FullName);
|
||||
var outputAnalysis = FFProbe.Analyse(output);
|
||||
Assert.AreEqual(Math.Max(videoAnalysis.Duration.TotalSeconds, audioAnalysis.Duration.TotalSeconds), outputAnalysis.Duration.TotalSeconds, 0.15);
|
||||
Assert.IsTrue(File.Exists(output));
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(output.FullName))
|
||||
output.Delete();
|
||||
if (File.Exists(output)) File.Delete(output);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,14 +68,14 @@ public void Image_AddAudio()
|
|||
|
||||
try
|
||||
{
|
||||
var result = Encoder.PosterWithAudio(new FileInfo(VideoLibrary.LocalCover.FullName), VideoLibrary.LocalAudio, output);
|
||||
Assert.IsTrue(result.Duration.TotalSeconds > 0);
|
||||
Assert.IsTrue(result.Exists);
|
||||
FFMpeg.PosterWithAudio(VideoLibrary.LocalCover.FullName, VideoLibrary.LocalAudio.FullName, output);
|
||||
var analysis = FFProbe.Analyse(VideoLibrary.LocalAudio.FullName);
|
||||
Assert.IsTrue(analysis.Duration.TotalSeconds > 0);
|
||||
Assert.IsTrue(File.Exists(output));
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(output.FullName))
|
||||
output.Delete();
|
||||
if (File.Exists(output)) File.Delete(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,14 @@
|
|||
using FFMpegCore.FFMPEG;
|
||||
using FFMpegCore.Test.Resources;
|
||||
using FFMpegCore.Test.Resources;
|
||||
using System.IO;
|
||||
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
public class BaseTest
|
||||
{
|
||||
protected FFMpeg Encoder;
|
||||
protected FileInfo Input;
|
||||
|
||||
public BaseTest()
|
||||
{
|
||||
Encoder = new FFMpeg();
|
||||
Input = VideoLibrary.LocalVideo;
|
||||
}
|
||||
}
|
||||
|
|
220
FFMpegCore.Test/BitmapSources.cs
Normal file
220
FFMpegCore.Test/BitmapSources.cs
Normal file
|
@ -0,0 +1,220 @@
|
|||
using FFMpegCore.Extend;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Numerics;
|
||||
using FFMpegCore.Pipes;
|
||||
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
static class BitmapSource
|
||||
{
|
||||
public static IEnumerable<IVideoFrame> CreateBitmaps(int count, PixelFormat fmt, int w, int h)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
using (var frame = CreateVideoFrame(i, fmt, w, h, 0.025f, 0.025f * w * 0.03f))
|
||||
{
|
||||
yield return frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static BitmapVideoFrameWrapper CreateVideoFrame(int index, PixelFormat fmt, int w, int h, float scaleNoise, float offset)
|
||||
{
|
||||
var bitmap = new Bitmap(w, h, fmt);
|
||||
|
||||
offset = offset * index;
|
||||
|
||||
for (int y = 0; y < h; y++)
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
var xf = x / (float)w;
|
||||
var yf = y / (float)h;
|
||||
var nx = x * scaleNoise + offset;
|
||||
var ny = y * scaleNoise + offset;
|
||||
|
||||
var value = (int)((Perlin.Noise(nx, ny) + 1.0f) / 2.0f * 255);
|
||||
|
||||
var color = Color.FromArgb((int)(value * xf), (int)(value * yf), value);
|
||||
|
||||
bitmap.SetPixel(x, y, color);
|
||||
}
|
||||
|
||||
return new BitmapVideoFrameWrapper(bitmap);
|
||||
}
|
||||
|
||||
//
|
||||
// Perlin noise generator for Unity
|
||||
// Keijiro Takahashi, 2013, 2015
|
||||
// https://github.com/keijiro/PerlinNoise
|
||||
//
|
||||
// Based on the original implementation by Ken Perlin
|
||||
// http://mrl.nyu.edu/~perlin/noise/
|
||||
//
|
||||
static class Perlin
|
||||
{
|
||||
#region Noise functions
|
||||
|
||||
public static float Noise(float x)
|
||||
{
|
||||
var X = (int)MathF.Floor(x) & 0xff;
|
||||
x -= MathF.Floor(x);
|
||||
var u = Fade(x);
|
||||
return Lerp(u, Grad(perm[X], x), Grad(perm[X + 1], x - 1)) * 2;
|
||||
}
|
||||
|
||||
public static float Noise(float x, float y)
|
||||
{
|
||||
var X = (int)MathF.Floor(x) & 0xff;
|
||||
var Y = (int)MathF.Floor(y) & 0xff;
|
||||
x -= MathF.Floor(x);
|
||||
y -= MathF.Floor(y);
|
||||
var u = Fade(x);
|
||||
var v = Fade(y);
|
||||
var A = (perm[X] + Y) & 0xff;
|
||||
var B = (perm[X + 1] + Y) & 0xff;
|
||||
return Lerp(v, Lerp(u, Grad(perm[A], x, y), Grad(perm[B], x - 1, y)),
|
||||
Lerp(u, Grad(perm[A + 1], x, y - 1), Grad(perm[B + 1], x - 1, y - 1)));
|
||||
}
|
||||
|
||||
public static float Noise(Vector2 coord)
|
||||
{
|
||||
return Noise(coord.X, coord.Y);
|
||||
}
|
||||
|
||||
public static float Noise(float x, float y, float z)
|
||||
{
|
||||
var X = (int)MathF.Floor(x) & 0xff;
|
||||
var Y = (int)MathF.Floor(y) & 0xff;
|
||||
var Z = (int)MathF.Floor(z) & 0xff;
|
||||
x -= MathF.Floor(x);
|
||||
y -= MathF.Floor(y);
|
||||
z -= MathF.Floor(z);
|
||||
var u = Fade(x);
|
||||
var v = Fade(y);
|
||||
var w = Fade(z);
|
||||
var A = (perm[X] + Y) & 0xff;
|
||||
var B = (perm[X + 1] + Y) & 0xff;
|
||||
var AA = (perm[A] + Z) & 0xff;
|
||||
var BA = (perm[B] + Z) & 0xff;
|
||||
var AB = (perm[A + 1] + Z) & 0xff;
|
||||
var BB = (perm[B + 1] + Z) & 0xff;
|
||||
return Lerp(w, Lerp(v, Lerp(u, Grad(perm[AA], x, y, z), Grad(perm[BA], x - 1, y, z)),
|
||||
Lerp(u, Grad(perm[AB], x, y - 1, z), Grad(perm[BB], x - 1, y - 1, z))),
|
||||
Lerp(v, Lerp(u, Grad(perm[AA + 1], x, y, z - 1), Grad(perm[BA + 1], x - 1, y, z - 1)),
|
||||
Lerp(u, Grad(perm[AB + 1], x, y - 1, z - 1), Grad(perm[BB + 1], x - 1, y - 1, z - 1))));
|
||||
}
|
||||
|
||||
public static float Noise(Vector3 coord)
|
||||
{
|
||||
return Noise(coord.X, coord.Y, coord.Z);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region fBm functions
|
||||
|
||||
public static float Fbm(float x, int octave)
|
||||
{
|
||||
var f = 0.0f;
|
||||
var w = 0.5f;
|
||||
for (var i = 0; i < octave; i++)
|
||||
{
|
||||
f += w * Noise(x);
|
||||
x *= 2.0f;
|
||||
w *= 0.5f;
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
public static float Fbm(Vector2 coord, int octave)
|
||||
{
|
||||
var f = 0.0f;
|
||||
var w = 0.5f;
|
||||
for (var i = 0; i < octave; i++)
|
||||
{
|
||||
f += w * Noise(coord);
|
||||
coord *= 2.0f;
|
||||
w *= 0.5f;
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
public static float Fbm(float x, float y, int octave)
|
||||
{
|
||||
return Fbm(new Vector2(x, y), octave);
|
||||
}
|
||||
|
||||
public static float Fbm(Vector3 coord, int octave)
|
||||
{
|
||||
var f = 0.0f;
|
||||
var w = 0.5f;
|
||||
for (var i = 0; i < octave; i++)
|
||||
{
|
||||
f += w * Noise(coord);
|
||||
coord *= 2.0f;
|
||||
w *= 0.5f;
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
public static float Fbm(float x, float y, float z, int octave)
|
||||
{
|
||||
return Fbm(new Vector3(x, y, z), octave);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private functions
|
||||
|
||||
static float Fade(float t)
|
||||
{
|
||||
return t * t * t * (t * (t * 6 - 15) + 10);
|
||||
}
|
||||
|
||||
static float Lerp(float t, float a, float b)
|
||||
{
|
||||
return a + t * (b - a);
|
||||
}
|
||||
|
||||
static float Grad(int hash, float x)
|
||||
{
|
||||
return (hash & 1) == 0 ? x : -x;
|
||||
}
|
||||
|
||||
static float Grad(int hash, float x, float y)
|
||||
{
|
||||
return ((hash & 1) == 0 ? x : -x) + ((hash & 2) == 0 ? y : -y);
|
||||
}
|
||||
|
||||
static float Grad(int hash, float x, float y, float z)
|
||||
{
|
||||
var h = hash & 15;
|
||||
var u = h < 8 ? x : y;
|
||||
var v = h < 4 ? y : (h == 12 || h == 14 ? x : z);
|
||||
return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
|
||||
}
|
||||
|
||||
static int[] perm = {
|
||||
151,160,137,91,90,15,
|
||||
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
|
||||
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
|
||||
88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
|
||||
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
|
||||
102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
|
||||
135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
|
||||
5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
|
||||
223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
|
||||
129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
|
||||
251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
|
||||
49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
|
||||
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180,
|
||||
151
|
||||
};
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,22 +4,42 @@
|
|||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
|
||||
<Nullable>disable</Nullable>
|
||||
|
||||
<LangVersion>default</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="ffmpeg.config.json" />
|
||||
<None Update="Resources\input.webm">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\input_3sec.mp4">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\input_3sec.webm">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\input_audio_only_10sec.mp4">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\input_video_only_3sec.mp4">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="ffmpeg.config.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="1.4.0" />
|
||||
<PackageReference Include="GitHubActionsTestLogger" Version="1.1.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using FFMpegCore.FFMPEG;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Newtonsoft.Json;
|
||||
using System.IO;
|
||||
|
||||
|
@ -17,7 +16,7 @@ public void Options_Initialized()
|
|||
[TestMethod]
|
||||
public void Options_Defaults_Configured()
|
||||
{
|
||||
Assert.AreEqual(new FFMpegOptions().RootDirectory, $".{Path.DirectorySeparatorChar}FFMPEG{Path.DirectorySeparatorChar}bin");
|
||||
Assert.AreEqual(new FFMpegOptions().RootDirectory, $"");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
@ -25,12 +24,12 @@ public void Options_Loaded_From_File()
|
|||
{
|
||||
Assert.AreEqual(
|
||||
FFMpegOptions.Options.RootDirectory,
|
||||
JsonConvert.DeserializeObject<FFMpegOptions>(File.ReadAllText($".{Path.DirectorySeparatorChar}ffmpeg.config.json")).RootDirectory
|
||||
JsonConvert.DeserializeObject<FFMpegOptions>(File.ReadAllText("ffmpeg.config.json")).RootDirectory
|
||||
);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Options_Overrided()
|
||||
public void Options_Set_Programmatically()
|
||||
{
|
||||
var original = FFMpegOptions.Options;
|
||||
try
|
|
@ -1,19 +0,0 @@
|
|||
using FFMpegCore.FFMPEG;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
[TestClass]
|
||||
public class FFMpegTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void CTOR_Default()
|
||||
{
|
||||
var encoder = new FFMpeg();
|
||||
var probe = new FFProbe();
|
||||
|
||||
Assert.IsNotNull(encoder);
|
||||
Assert.IsNotNull(probe);
|
||||
}
|
||||
}
|
||||
}
|
70
FFMpegCore.Test/FFProbeTests.cs
Normal file
70
FFMpegCore.Test/FFProbeTests.cs
Normal file
|
@ -0,0 +1,70 @@
|
|||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using FFMpegCore.Test.Resources;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
[TestClass]
|
||||
public class FFProbeTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void Probe_TooLongOutput()
|
||||
{
|
||||
Assert.ThrowsException<System.Text.Json.JsonException>(() => FFProbe.Analyse(VideoLibrary.LocalVideo.FullName, 5));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Probe_Success()
|
||||
{
|
||||
var info = FFProbe.Analyse(VideoLibrary.LocalVideo.FullName);
|
||||
Assert.AreEqual(3, info.Duration.Seconds);
|
||||
Assert.AreEqual(".mp4", info.Extension);
|
||||
Assert.AreEqual(VideoLibrary.LocalVideo.FullName, info.Path);
|
||||
|
||||
Assert.AreEqual("5.1", info.PrimaryAudioStream.ChannelLayout);
|
||||
Assert.AreEqual(6, info.PrimaryAudioStream.Channels);
|
||||
Assert.AreEqual("AAC (Advanced Audio Coding)", info.PrimaryAudioStream.CodecLongName);
|
||||
Assert.AreEqual("aac", info.PrimaryAudioStream.CodecName);
|
||||
Assert.AreEqual("LC", info.PrimaryAudioStream.Profile);
|
||||
Assert.AreEqual(377351, info.PrimaryAudioStream.BitRate);
|
||||
Assert.AreEqual(48000, info.PrimaryAudioStream.SampleRateHz);
|
||||
|
||||
Assert.AreEqual(1471810, info.PrimaryVideoStream.BitRate);
|
||||
Assert.AreEqual(16, info.PrimaryVideoStream.DisplayAspectRatio.Width);
|
||||
Assert.AreEqual(9, info.PrimaryVideoStream.DisplayAspectRatio.Height);
|
||||
Assert.AreEqual("yuv420p", info.PrimaryVideoStream.PixelFormat);
|
||||
Assert.AreEqual(1280, info.PrimaryVideoStream.Width);
|
||||
Assert.AreEqual(720, info.PrimaryVideoStream.Height);
|
||||
Assert.AreEqual(25, info.PrimaryVideoStream.AvgFrameRate);
|
||||
Assert.AreEqual(25, info.PrimaryVideoStream.FrameRate);
|
||||
Assert.AreEqual("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10", info.PrimaryVideoStream.CodecLongName);
|
||||
Assert.AreEqual("h264", info.PrimaryVideoStream.CodecName);
|
||||
Assert.AreEqual(8, info.PrimaryVideoStream.BitsPerRawSample);
|
||||
Assert.AreEqual("Main", info.PrimaryVideoStream.Profile);
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Probe_Async_Success()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(VideoLibrary.LocalVideo.FullName);
|
||||
Assert.AreEqual(3, info.Duration.Seconds);
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Probe_Success_FromStream()
|
||||
{
|
||||
using var stream = File.OpenRead(VideoLibrary.LocalVideoWebm.FullName);
|
||||
var info = FFProbe.Analyse(stream);
|
||||
Assert.AreEqual(3, info.Duration.Seconds);
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Probe_Success_FromStream_Async()
|
||||
{
|
||||
await using var stream = File.OpenRead(VideoLibrary.LocalVideoWebm.FullName);
|
||||
var info = await FFProbe.AnalyseAsync(stream);
|
||||
Assert.AreEqual(3, info.Duration.Seconds);
|
||||
}
|
||||
}
|
||||
}
|
41
FFMpegCore.Test/PixelFormatTests.cs
Normal file
41
FFMpegCore.Test/PixelFormatTests.cs
Normal file
|
@ -0,0 +1,41 @@
|
|||
using FFMpegCore.Exceptions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
[TestClass]
|
||||
public class PixelFormatTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void PixelFormats_Enumerate()
|
||||
{
|
||||
var formats = FFMpeg.GetPixelFormats();
|
||||
Assert.IsTrue(formats.Count > 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PixelFormats_TryGetExisting()
|
||||
{
|
||||
Assert.IsTrue(FFMpeg.TryGetPixelFormat("yuv420p", out _));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PixelFormats_TryGetNotExisting()
|
||||
{
|
||||
Assert.IsFalse(FFMpeg.TryGetPixelFormat("yuv420pppUnknown", out _));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PixelFormats_GetExisting()
|
||||
{
|
||||
var fmt = FFMpeg.GetPixelFormat("yuv420p");
|
||||
Assert.IsTrue(fmt.Components == 3 && fmt.BitsPerPixel == 12);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PixelFormats_GetNotExisting()
|
||||
{
|
||||
Assert.ThrowsException<FFMpegException>(() => FFMpeg.GetPixelFormat("yuv420pppUnknown"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,35 +16,36 @@ public enum ImageType
|
|||
|
||||
public static class VideoLibrary
|
||||
{
|
||||
public static readonly FileInfo LocalVideo = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}input.mp4");
|
||||
public static readonly FileInfo LocalVideoAudioOnly = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}audio_only.mp4");
|
||||
public static readonly FileInfo LocalVideoNoAudio = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}mute.mp4");
|
||||
public static readonly FileInfo LocalVideo = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}input_3sec.mp4");
|
||||
public static readonly FileInfo LocalVideoWebm = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}input_3sec.webm");
|
||||
public static readonly FileInfo LocalVideoAudioOnly = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}input_audio_only_10sec.mp4");
|
||||
public static readonly FileInfo LocalVideoNoAudio = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}input_video_only_3sec.mp4");
|
||||
public static readonly FileInfo LocalAudio = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}audio.mp3");
|
||||
public static readonly FileInfo LocalCover = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}cover.png");
|
||||
public static readonly FileInfo ImageDirectory = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}images");
|
||||
public static readonly FileInfo ImageJoinOutput = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}images{Path.DirectorySeparatorChar}output.mp4");
|
||||
|
||||
public static FileInfo OutputLocation(this FileInfo file, VideoType type)
|
||||
public static string OutputLocation(this FileInfo file, ContainerFormat type)
|
||||
{
|
||||
return OutputLocation(file, type, "_converted");
|
||||
return OutputLocation(file, type.Extension, "_converted");
|
||||
}
|
||||
|
||||
public static FileInfo OutputLocation(this FileInfo file, AudioType type)
|
||||
public static string OutputLocation(this FileInfo file, AudioType type)
|
||||
{
|
||||
return OutputLocation(file, type, "_audio");
|
||||
return OutputLocation(file, type.ToString(), "_audio");
|
||||
}
|
||||
|
||||
public static FileInfo OutputLocation(this FileInfo file, ImageType type)
|
||||
public static string OutputLocation(this FileInfo file, ImageType type)
|
||||
{
|
||||
return OutputLocation(file, type, "_screenshot");
|
||||
return OutputLocation(file, type.ToString(), "_screenshot");
|
||||
}
|
||||
|
||||
public static FileInfo OutputLocation(this FileInfo file, Enum type, string keyword)
|
||||
public static string OutputLocation(this FileInfo file, string type, string keyword)
|
||||
{
|
||||
string originalLocation = file.Directory.FullName,
|
||||
outputFile = file.Name.Replace(file.Extension, keyword + "." + type.ToString().ToLower());
|
||||
outputFile = file.Name.Replace(file.Extension, keyword + "." + type.ToLowerInvariant());
|
||||
|
||||
return new FileInfo($"{originalLocation}{Path.DirectorySeparatorChar}{outputFile}");
|
||||
return $"{originalLocation}{Path.DirectorySeparatorChar}{Guid.NewGuid()}_{outputFile}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
Binary file not shown.
BIN
FFMpegCore.Test/Resources/input_3sec.mp4
Normal file
BIN
FFMpegCore.Test/Resources/input_3sec.mp4
Normal file
Binary file not shown.
BIN
FFMpegCore.Test/Resources/input_3sec.webm
Normal file
BIN
FFMpegCore.Test/Resources/input_3sec.webm
Normal file
Binary file not shown.
BIN
FFMpegCore.Test/Resources/input_audio_only_10sec.mp4
Normal file
BIN
FFMpegCore.Test/Resources/input_audio_only_10sec.mp4
Normal file
Binary file not shown.
BIN
FFMpegCore.Test/Resources/input_video_only_3sec.mp4
Normal file
BIN
FFMpegCore.Test/Resources/input_video_only_3sec.mp4
Normal file
Binary file not shown.
Binary file not shown.
10
FFMpegCore.Test/TasksExtensions.cs
Normal file
10
FFMpegCore.Test/TasksExtensions.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
static class TasksExtensions
|
||||
{
|
||||
public static T WaitForResult<T>(this Task<T> task) =>
|
||||
task.ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
|
@ -1,284 +1,528 @@
|
|||
using FFMpegCore.Enums;
|
||||
using FFMpegCore.FFMPEG.Argument;
|
||||
using FFMpegCore.FFMPEG.Enums;
|
||||
using FFMpegCore.Test.Resources;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FFMpegCore.Arguments;
|
||||
using FFMpegCore.Exceptions;
|
||||
using FFMpegCore.Pipes;
|
||||
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
[TestClass]
|
||||
public class VideoTest : BaseTest
|
||||
{
|
||||
public bool Convert(VideoType type, bool multithreaded = false, VideoSize size = VideoSize.Original)
|
||||
public bool Convert(ContainerFormat type, bool multithreaded = false, VideoSize size = VideoSize.Original)
|
||||
{
|
||||
var output = Input.OutputLocation(type);
|
||||
|
||||
try
|
||||
{
|
||||
var input = VideoInfo.FromFileInfo(Input);
|
||||
var input = FFProbe.Analyse(Input.FullName);
|
||||
FFMpeg.Convert(input, output, type, size: size, multithreaded: multithreaded);
|
||||
var outputVideo = FFProbe.Analyse(output);
|
||||
|
||||
Encoder.Convert(input, output, type, size: size, multithreaded: multithreaded);
|
||||
|
||||
var outputVideo = new VideoInfo(output.FullName);
|
||||
|
||||
Assert.IsTrue(File.Exists(output.FullName));
|
||||
Assert.AreEqual(outputVideo.Duration, input.Duration);
|
||||
Assert.IsTrue(File.Exists(output));
|
||||
Assert.AreEqual(outputVideo.Duration.TotalSeconds, input.Duration.TotalSeconds, 0.1);
|
||||
if (size == VideoSize.Original)
|
||||
{
|
||||
Assert.AreEqual(outputVideo.Width, input.Width);
|
||||
Assert.AreEqual(outputVideo.Height, input.Height);
|
||||
Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width);
|
||||
Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreNotEqual(outputVideo.Width, input.Width);
|
||||
Assert.AreNotEqual(outputVideo.Height, input.Height);
|
||||
Assert.AreEqual(outputVideo.Height, (int)size);
|
||||
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width);
|
||||
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height);
|
||||
Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, (int)size);
|
||||
}
|
||||
return File.Exists(output.FullName) &&
|
||||
return File.Exists(output) &&
|
||||
outputVideo.Duration == input.Duration &&
|
||||
(
|
||||
(
|
||||
size == VideoSize.Original &&
|
||||
outputVideo.Width == input.Width &&
|
||||
outputVideo.Height == input.Height
|
||||
outputVideo.PrimaryVideoStream.Width == input.PrimaryVideoStream.Width &&
|
||||
outputVideo.PrimaryVideoStream.Height == input.PrimaryVideoStream.Height
|
||||
) ||
|
||||
(
|
||||
size != VideoSize.Original &&
|
||||
outputVideo.Width != input.Width &&
|
||||
outputVideo.Height != input.Height &&
|
||||
outputVideo.Height == (int)size
|
||||
outputVideo.PrimaryVideoStream.Width != input.PrimaryVideoStream.Width &&
|
||||
outputVideo.PrimaryVideoStream.Height != input.PrimaryVideoStream.Height &&
|
||||
outputVideo.PrimaryVideoStream.Height == (int)size
|
||||
)
|
||||
);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(output.FullName))
|
||||
File.Delete(output.FullName);
|
||||
if (File.Exists(output))
|
||||
File.Delete(output);
|
||||
}
|
||||
}
|
||||
|
||||
public void Convert(VideoType type, ArgumentContainer container)
|
||||
private void ConvertFromStreamPipe(ContainerFormat type, params IArgument[] arguments)
|
||||
{
|
||||
var output = Input.OutputLocation(type);
|
||||
|
||||
try
|
||||
{
|
||||
var input = VideoInfo.FromFileInfo(Input);
|
||||
var input = FFProbe.Analyse(VideoLibrary.LocalVideoWebm.FullName);
|
||||
using var inputStream = File.OpenRead(input.Path);
|
||||
var processor = FFMpegArguments
|
||||
.FromPipeInput(new StreamPipeSource(inputStream))
|
||||
.OutputToFile(output, false, opt =>
|
||||
{
|
||||
foreach (var arg in arguments)
|
||||
opt.WithArgument(arg);
|
||||
});
|
||||
|
||||
var arguments = new ArgumentContainer();
|
||||
arguments.Add(new InputArgument(input));
|
||||
foreach (var arg in container)
|
||||
var scaling = arguments.OfType<ScaleArgument>().FirstOrDefault();
|
||||
|
||||
var success = processor.ProcessSynchronously();
|
||||
|
||||
var outputVideo = FFProbe.Analyse(output);
|
||||
|
||||
Assert.IsTrue(success);
|
||||
Assert.IsTrue(File.Exists(output));
|
||||
Assert.IsTrue(Math.Abs((outputVideo.Duration - input.Duration).TotalMilliseconds) < 1000.0 / input.PrimaryVideoStream.FrameRate);
|
||||
|
||||
if (scaling?.Size == null)
|
||||
{
|
||||
arguments.Add(arg.Value);
|
||||
}
|
||||
arguments.Add(new OutputArgument(output));
|
||||
|
||||
var scaling = container.Find<ScaleArgument>();
|
||||
|
||||
Encoder.Convert(arguments);
|
||||
|
||||
var outputVideo = new VideoInfo(output.FullName);
|
||||
|
||||
Assert.IsTrue(File.Exists(output.FullName));
|
||||
Assert.AreEqual(outputVideo.Duration, input.Duration);
|
||||
|
||||
if (scaling == null)
|
||||
{
|
||||
Assert.AreEqual(outputVideo.Width, input.Width);
|
||||
Assert.AreEqual(outputVideo.Height, input.Height);
|
||||
Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width);
|
||||
Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (scaling.Value.Width != -1)
|
||||
if (scaling.Size.Value.Width != -1)
|
||||
{
|
||||
Assert.AreEqual(outputVideo.Width, scaling.Value.Width);
|
||||
Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, scaling.Size.Value.Width);
|
||||
}
|
||||
|
||||
if (scaling.Value.Height != -1)
|
||||
if (scaling.Size.Value.Height != -1)
|
||||
{
|
||||
Assert.AreEqual(outputVideo.Height, scaling.Value.Height);
|
||||
Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, scaling.Size.Value.Height);
|
||||
}
|
||||
|
||||
Assert.AreNotEqual(outputVideo.Width, input.Width);
|
||||
Assert.AreNotEqual(outputVideo.Height, input.Height);
|
||||
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width);
|
||||
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(output.FullName))
|
||||
File.Delete(output.FullName);
|
||||
if (File.Exists(output))
|
||||
File.Delete(output);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
private void ConvertToStreamPipe(params IArgument[] arguments)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
var processor = FFMpegArguments
|
||||
.FromFileInput(VideoLibrary.LocalVideo)
|
||||
.OutputToPipe(new StreamPipeSink(ms), opt =>
|
||||
{
|
||||
foreach (var arg in arguments)
|
||||
opt.WithArgument(arg);
|
||||
});
|
||||
|
||||
var scaling = arguments.OfType<ScaleArgument>().FirstOrDefault();
|
||||
|
||||
processor.ProcessSynchronously();
|
||||
|
||||
ms.Position = 0;
|
||||
var outputVideo = FFProbe.Analyse(ms);
|
||||
|
||||
var input = FFProbe.Analyse(VideoLibrary.LocalVideo.FullName);
|
||||
// Assert.IsTrue(Math.Abs((outputVideo.Duration - input.Duration).TotalMilliseconds) < 1000.0 / input.PrimaryVideoStream.FrameRate);
|
||||
|
||||
if (scaling?.Size == null)
|
||||
{
|
||||
Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width);
|
||||
Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (scaling.Size.Value.Width != -1)
|
||||
{
|
||||
Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, scaling.Size.Value.Width);
|
||||
}
|
||||
|
||||
if (scaling.Size.Value.Height != -1)
|
||||
{
|
||||
Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, scaling.Size.Value.Height);
|
||||
}
|
||||
|
||||
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width);
|
||||
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height);
|
||||
}
|
||||
}
|
||||
|
||||
public void Convert(ContainerFormat type, Action<IMediaAnalysis> validationMethod, params IArgument[] arguments)
|
||||
{
|
||||
var output = Input.OutputLocation(type);
|
||||
|
||||
try
|
||||
{
|
||||
var input = FFProbe.Analyse(Input.FullName);
|
||||
|
||||
var processor = FFMpegArguments
|
||||
.FromFileInput(VideoLibrary.LocalVideo)
|
||||
.OutputToFile(output, false, opt =>
|
||||
{
|
||||
foreach (var arg in arguments)
|
||||
opt.WithArgument(arg);
|
||||
});
|
||||
|
||||
var scaling = arguments.OfType<ScaleArgument>().FirstOrDefault();
|
||||
processor.ProcessSynchronously();
|
||||
|
||||
var outputVideo = FFProbe.Analyse(output);
|
||||
|
||||
Assert.IsTrue(File.Exists(output));
|
||||
Assert.AreEqual(outputVideo.Duration.TotalSeconds, input.Duration.TotalSeconds, 0.1);
|
||||
validationMethod?.Invoke(outputVideo);
|
||||
if (scaling?.Size == null)
|
||||
{
|
||||
Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width);
|
||||
Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (scaling.Size.Value.Width != -1)
|
||||
{
|
||||
Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, scaling.Size.Value.Width);
|
||||
}
|
||||
|
||||
if (scaling.Size.Value.Height != -1)
|
||||
{
|
||||
Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, scaling.Size.Value.Height);
|
||||
}
|
||||
|
||||
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width);
|
||||
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(output))
|
||||
File.Delete(output);
|
||||
}
|
||||
}
|
||||
|
||||
public void Convert(ContainerFormat type, params IArgument[] inputArguments)
|
||||
{
|
||||
Convert(type, null, inputArguments);
|
||||
}
|
||||
|
||||
public void ConvertFromPipe(ContainerFormat type, System.Drawing.Imaging.PixelFormat fmt, params IArgument[] arguments)
|
||||
{
|
||||
var output = Input.OutputLocation(type);
|
||||
|
||||
try
|
||||
{
|
||||
var videoFramesSource = new RawVideoPipeSource(BitmapSource.CreateBitmaps(128, fmt, 256, 256));
|
||||
var processor = FFMpegArguments.FromPipeInput(videoFramesSource).OutputToFile(output, false, opt =>
|
||||
{
|
||||
foreach (var arg in arguments)
|
||||
opt.WithArgument(arg);
|
||||
});
|
||||
var scaling = arguments.OfType<ScaleArgument>().FirstOrDefault();
|
||||
processor.ProcessSynchronously();
|
||||
|
||||
var outputVideo = FFProbe.Analyse(output);
|
||||
|
||||
Assert.IsTrue(File.Exists(output));
|
||||
|
||||
if (scaling?.Size == null)
|
||||
{
|
||||
Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, videoFramesSource.Width);
|
||||
Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, videoFramesSource.Height);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (scaling.Size.Value.Width != -1)
|
||||
{
|
||||
Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, scaling.Size.Value.Width);
|
||||
}
|
||||
|
||||
if (scaling.Size.Value.Height != -1)
|
||||
{
|
||||
Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, scaling.Size.Value.Height);
|
||||
}
|
||||
|
||||
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Width, videoFramesSource.Width);
|
||||
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Height, videoFramesSource.Height);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(output))
|
||||
File.Delete(output);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_ToMP4()
|
||||
{
|
||||
Convert(VideoType.Mp4);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Video_ToMP4_Args()
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_ToMP4_YUV444p()
|
||||
{
|
||||
var container = new ArgumentContainer();
|
||||
container.Add(new VideoCodecArgument(VideoCodec.LibX264));
|
||||
Convert(VideoType.Mp4, container);
|
||||
Convert(VideoType.Mp4, (a) => Assert.IsTrue(a.VideoStreams.First().PixelFormat == "yuv444p"),
|
||||
new ForcePixelFormat("yuv444p"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_ToMP4_Args()
|
||||
{
|
||||
Convert(VideoType.Mp4, new VideoCodecArgument(VideoCodec.LibX264));
|
||||
}
|
||||
|
||||
[DataTestMethod, Timeout(10000)]
|
||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)]
|
||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)]
|
||||
// [DataRow(PixelFormat.Format48bppRgb)]
|
||||
public void Video_ToMP4_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat)
|
||||
{
|
||||
ConvertFromPipe(VideoType.Mp4, pixelFormat, new VideoCodecArgument(VideoCodec.LibX264));
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_ToMP4_Args_StreamPipe()
|
||||
{
|
||||
ConvertFromStreamPipe(VideoType.Mp4, new VideoCodecArgument(VideoCodec.LibX264));
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Video_ToMP4_Args_StreamOutputPipe_Async_Failure()
|
||||
{
|
||||
await Assert.ThrowsExceptionAsync<FFMpegException>(async () =>
|
||||
{
|
||||
await using var ms = new MemoryStream();
|
||||
var pipeSource = new StreamPipeSink(ms);
|
||||
await FFMpegArguments
|
||||
.FromFileInput(VideoLibrary.LocalVideo)
|
||||
.OutputToPipe(pipeSource, opt => opt.ForceFormat("mkv"))
|
||||
.ProcessAsynchronously();
|
||||
});
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_ToMP4_Args_StreamOutputPipe_Failure()
|
||||
{
|
||||
Assert.ThrowsException<FFMpegException>(() => ConvertToStreamPipe(new ForceFormatArgument("mkv")));
|
||||
}
|
||||
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_ToMP4_Args_StreamOutputPipe_Async()
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
var pipeSource = new StreamPipeSink(ms);
|
||||
FFMpegArguments
|
||||
.FromFileInput(VideoLibrary.LocalVideo)
|
||||
.OutputToPipe(pipeSource, opt => opt
|
||||
.WithVideoCodec(VideoCodec.LibX264)
|
||||
.ForceFormat("matroska"))
|
||||
.ProcessAsynchronously()
|
||||
.WaitForResult();
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task TestDuplicateRun()
|
||||
{
|
||||
FFMpegArguments.FromFileInput(VideoLibrary.LocalVideo)
|
||||
.OutputToFile("temporary.mp4")
|
||||
.ProcessSynchronously();
|
||||
|
||||
await FFMpegArguments.FromFileInput(VideoLibrary.LocalVideo)
|
||||
.OutputToFile("temporary.mp4")
|
||||
.ProcessAsynchronously();
|
||||
|
||||
File.Delete("temporary.mp4");
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_ToMP4_Args_StreamOutputPipe()
|
||||
{
|
||||
ConvertToStreamPipe(new VideoCodecArgument(VideoCodec.LibX264), new ForceFormatArgument("matroska"));
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_ToTS()
|
||||
{
|
||||
Convert(VideoType.Ts);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_ToTS_Args()
|
||||
{
|
||||
var container = new ArgumentContainer();
|
||||
container.Add(new CopyArgument());
|
||||
container.Add(new BitStreamFilterArgument(Channel.Video, Filter.H264_Mp4ToAnnexB));
|
||||
container.Add(new ForceFormatArgument(VideoCodec.MpegTs));
|
||||
Convert(VideoType.Ts, container);
|
||||
Convert(VideoType.Ts,
|
||||
new CopyArgument(),
|
||||
new BitStreamFilterArgument(Channel.Video, Filter.H264_Mp4ToAnnexB),
|
||||
new ForceFormatArgument(VideoType.MpegTs));
|
||||
}
|
||||
|
||||
[DataTestMethod, Timeout(10000)]
|
||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)]
|
||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)]
|
||||
// [DataRow(PixelFormat.Format48bppRgb)]
|
||||
public void Video_ToTS_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat)
|
||||
{
|
||||
ConvertFromPipe(VideoType.Ts, pixelFormat, new ForceFormatArgument(VideoType.Ts));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_ToOGV_Resize()
|
||||
{
|
||||
Convert(VideoType.Ogv, true, VideoSize.Ed);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_ToOGV_Resize_Args()
|
||||
{
|
||||
var container = new ArgumentContainer();
|
||||
container.Add(new ScaleArgument(VideoSize.Ed));
|
||||
container.Add(new VideoCodecArgument(VideoCodec.LibTheora));
|
||||
Convert(VideoType.Ogv, container);
|
||||
Convert(VideoType.Ogv, new ScaleArgument(VideoSize.Ed), new VideoCodecArgument(VideoCodec.LibTheora));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataTestMethod, Timeout(10000)]
|
||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)]
|
||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)]
|
||||
// [DataRow(PixelFormat.Format48bppRgb)]
|
||||
public void Video_ToOGV_Resize_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat)
|
||||
{
|
||||
ConvertFromPipe(VideoType.Ogv, pixelFormat, new ScaleArgument(VideoSize.Ed), new VideoCodecArgument(VideoCodec.LibTheora));
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_ToMP4_Resize()
|
||||
{
|
||||
Convert(VideoType.Mp4, true, VideoSize.Ed);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_ToMP4_Resize_Args()
|
||||
{
|
||||
var container = new ArgumentContainer();
|
||||
container.Add(new ScaleArgument(VideoSize.Ld));
|
||||
container.Add(new VideoCodecArgument(VideoCodec.LibX264));
|
||||
Convert(VideoType.Mp4, container);
|
||||
Convert(VideoType.Mp4, new ScaleArgument(VideoSize.Ld), new VideoCodecArgument(VideoCodec.LibX264));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataTestMethod, Timeout(10000)]
|
||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)]
|
||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)]
|
||||
// [DataRow(PixelFormat.Format48bppRgb)]
|
||||
public void Video_ToMP4_Resize_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat)
|
||||
{
|
||||
ConvertFromPipe(VideoType.Mp4, pixelFormat, new ScaleArgument(VideoSize.Ld), new VideoCodecArgument(VideoCodec.LibX264));
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_ToOGV()
|
||||
{
|
||||
Convert(VideoType.Ogv);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_ToMP4_MultiThread()
|
||||
{
|
||||
Convert(VideoType.Mp4, true);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_ToTS_MultiThread()
|
||||
{
|
||||
Convert(VideoType.Ts, true);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_ToOGV_MultiThread()
|
||||
{
|
||||
Convert(VideoType.Ogv, true);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Video_Snapshot()
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_Snapshot_InMemory()
|
||||
{
|
||||
var output = Input.OutputLocation(ImageType.Png);
|
||||
|
||||
try
|
||||
{
|
||||
var input = VideoInfo.FromFileInfo(Input);
|
||||
var input = FFProbe.Analyse(Input.FullName);
|
||||
|
||||
using (var bitmap = Encoder.Snapshot(input, output))
|
||||
{
|
||||
Assert.AreEqual(input.Width, bitmap.Width);
|
||||
Assert.AreEqual(input.Height, bitmap.Height);
|
||||
Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png);
|
||||
}
|
||||
using var bitmap = FFMpeg.Snapshot(input);
|
||||
Assert.AreEqual(input.PrimaryVideoStream.Width, bitmap.Width);
|
||||
Assert.AreEqual(input.PrimaryVideoStream.Height, bitmap.Height);
|
||||
Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(output.FullName))
|
||||
File.Delete(output.FullName);
|
||||
if (File.Exists(output))
|
||||
File.Delete(output);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_Snapshot_PersistSnapshot()
|
||||
{
|
||||
var output = Input.OutputLocation(ImageType.Png);
|
||||
try
|
||||
{
|
||||
var input = VideoInfo.FromFileInfo(Input);
|
||||
var input = FFProbe.Analyse(Input.FullName);
|
||||
|
||||
using (var bitmap = Encoder.Snapshot(input, output, persistSnapshotOnFileSystem: true))
|
||||
{
|
||||
Assert.AreEqual(input.Width, bitmap.Width);
|
||||
Assert.AreEqual(input.Height, bitmap.Height);
|
||||
Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png);
|
||||
Assert.IsTrue(File.Exists(output.FullName));
|
||||
}
|
||||
FFMpeg.Snapshot(input, output);
|
||||
|
||||
var bitmap = Image.FromFile(output);
|
||||
Assert.AreEqual(input.PrimaryVideoStream.Width, bitmap.Width);
|
||||
Assert.AreEqual(input.PrimaryVideoStream.Height, bitmap.Height);
|
||||
Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png);
|
||||
bitmap.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(output.FullName))
|
||||
File.Delete(output.FullName);
|
||||
if (File.Exists(output))
|
||||
File.Delete(output);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_Join()
|
||||
{
|
||||
var output = Input.OutputLocation(VideoType.Mp4);
|
||||
var newInput = Input.OutputLocation(VideoType.Mp4, "duplicate");
|
||||
var newInput = Input.OutputLocation(VideoType.Mp4.Name, "duplicate");
|
||||
try
|
||||
{
|
||||
var input = VideoInfo.FromFileInfo(Input);
|
||||
File.Copy(input.FullName, newInput.FullName);
|
||||
var input2 = VideoInfo.FromFileInfo(newInput);
|
||||
var input = FFProbe.Analyse(Input.FullName);
|
||||
File.Copy(Input.FullName, newInput);
|
||||
|
||||
var result = Encoder.Join(output, input, input2);
|
||||
var success = FFMpeg.Join(output, Input.FullName, newInput);
|
||||
Assert.IsTrue(success);
|
||||
|
||||
Assert.IsTrue(File.Exists(output.FullName));
|
||||
TimeSpan expectedDuration = input.Duration * 2;
|
||||
Assert.IsTrue(File.Exists(output));
|
||||
var expectedDuration = input.Duration * 2;
|
||||
var result = FFProbe.Analyse(output);
|
||||
Assert.AreEqual(expectedDuration.Days, result.Duration.Days);
|
||||
Assert.AreEqual(expectedDuration.Hours, result.Duration.Hours);
|
||||
Assert.AreEqual(expectedDuration.Minutes, result.Duration.Minutes);
|
||||
Assert.AreEqual(expectedDuration.Seconds, result.Duration.Seconds);
|
||||
Assert.AreEqual(input.Height, result.Height);
|
||||
Assert.AreEqual(input.Width, result.Width);
|
||||
Assert.AreEqual(input.PrimaryVideoStream.Height, result.PrimaryVideoStream.Height);
|
||||
Assert.AreEqual(input.PrimaryVideoStream.Width, result.PrimaryVideoStream.Width);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(output.FullName))
|
||||
File.Delete(output.FullName);
|
||||
if (File.Exists(output))
|
||||
File.Delete(output);
|
||||
|
||||
if (File.Exists(newInput.FullName))
|
||||
File.Delete(newInput.FullName);
|
||||
if (File.Exists(newInput))
|
||||
File.Delete(newInput);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_Join_Image_Sequence()
|
||||
{
|
||||
try
|
||||
|
@ -289,20 +533,22 @@ public void Video_Join_Image_Sequence()
|
|||
.ToList()
|
||||
.ForEach(file =>
|
||||
{
|
||||
for (int i = 0; i < 15; i++)
|
||||
for (var i = 0; i < 15; i++)
|
||||
{
|
||||
imageSet.Add(new ImageInfo(file));
|
||||
}
|
||||
});
|
||||
|
||||
var result = Encoder.JoinImageSequence(VideoLibrary.ImageJoinOutput, images: imageSet.ToArray());
|
||||
var success = FFMpeg.JoinImageSequence(VideoLibrary.ImageJoinOutput.FullName, images: imageSet.ToArray());
|
||||
Assert.IsTrue(success);
|
||||
var result = FFProbe.Analyse(VideoLibrary.ImageJoinOutput.FullName);
|
||||
|
||||
VideoLibrary.ImageJoinOutput.Refresh();
|
||||
|
||||
Assert.IsTrue(VideoLibrary.ImageJoinOutput.Exists);
|
||||
Assert.AreEqual(3, result.Duration.Seconds);
|
||||
Assert.AreEqual(imageSet.First().Width, result.Width);
|
||||
Assert.AreEqual(imageSet.First().Height, result.Height);
|
||||
Assert.AreEqual(imageSet.First().Width, result.PrimaryVideoStream.Width);
|
||||
Assert.AreEqual(imageSet.First().Height, result.PrimaryVideoStream.Height);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -314,40 +560,119 @@ public void Video_Join_Image_Sequence()
|
|||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_With_Only_Audio_Should_Extract_Metadata()
|
||||
{
|
||||
var video = VideoInfo.FromFileInfo(VideoLibrary.LocalVideoAudioOnly);
|
||||
Assert.AreEqual("none", video.VideoFormat);
|
||||
Assert.AreEqual("aac", video.AudioFormat);
|
||||
Assert.AreEqual(79.5, video.Duration.TotalSeconds, 0.5);
|
||||
Assert.AreEqual(1.25, video.Size);
|
||||
var video = FFProbe.Analyse(VideoLibrary.LocalVideoAudioOnly.FullName);
|
||||
Assert.AreEqual(null, video.PrimaryVideoStream);
|
||||
Assert.AreEqual("aac", video.PrimaryAudioStream.CodecName);
|
||||
Assert.AreEqual(10, video.Duration.TotalSeconds, 0.5);
|
||||
// Assert.AreEqual(1.25, video.Size);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Video_Duration() {
|
||||
var video = VideoInfo.FromFileInfo(VideoLibrary.LocalVideo);
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_Duration()
|
||||
{
|
||||
var video = FFProbe.Analyse(VideoLibrary.LocalVideo.FullName);
|
||||
var output = Input.OutputLocation(VideoType.Mp4);
|
||||
|
||||
var arguments = new ArgumentContainer();
|
||||
arguments.Add(new InputArgument(VideoLibrary.LocalVideo));
|
||||
arguments.Add(new DurationArgument(TimeSpan.FromSeconds(video.Duration.TotalSeconds - 5)));
|
||||
arguments.Add(new OutputArgument(output));
|
||||
try
|
||||
{
|
||||
FFMpegArguments
|
||||
.FromFileInput(VideoLibrary.LocalVideo)
|
||||
.OutputToFile(output, false, opt => opt.WithDuration(TimeSpan.FromSeconds(video.Duration.TotalSeconds - 2)))
|
||||
.ProcessSynchronously();
|
||||
|
||||
try {
|
||||
Encoder.Convert(arguments);
|
||||
|
||||
Assert.IsTrue(File.Exists(output.FullName));
|
||||
var outputVideo = new VideoInfo(output.FullName);
|
||||
Assert.IsTrue(File.Exists(output));
|
||||
var outputVideo = FFProbe.Analyse(output);
|
||||
|
||||
Assert.AreEqual(video.Duration.Days, outputVideo.Duration.Days);
|
||||
Assert.AreEqual(video.Duration.Hours, outputVideo.Duration.Hours);
|
||||
Assert.AreEqual(video.Duration.Minutes, outputVideo.Duration.Minutes);
|
||||
Assert.AreEqual(video.Duration.Seconds - 5, outputVideo.Duration.Seconds);
|
||||
} finally {
|
||||
if (File.Exists(output.FullName))
|
||||
output.Delete();
|
||||
Assert.AreEqual(video.Duration.Seconds - 2, outputVideo.Duration.Seconds);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(output))
|
||||
File.Delete(output);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_UpdatesProgress()
|
||||
{
|
||||
var output = Input.OutputLocation(VideoType.Mp4);
|
||||
|
||||
var percentageDone = 0.0;
|
||||
var timeDone = TimeSpan.Zero;
|
||||
void OnPercentageProgess(double percentage) => percentageDone = percentage;
|
||||
void OnTimeProgess(TimeSpan time) => timeDone = time;
|
||||
|
||||
var analysis = FFProbe.Analyse(VideoLibrary.LocalVideo.FullName);
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
var success = FFMpegArguments
|
||||
.FromFileInput(VideoLibrary.LocalVideo)
|
||||
.OutputToFile(output, false, opt => opt
|
||||
.WithDuration(TimeSpan.FromSeconds(2)))
|
||||
.NotifyOnProgress(OnPercentageProgess, analysis.Duration)
|
||||
.NotifyOnProgress(OnTimeProgess)
|
||||
.ProcessSynchronously();
|
||||
|
||||
Assert.IsTrue(success);
|
||||
Assert.IsTrue(File.Exists(output));
|
||||
Assert.AreNotEqual(0.0, percentageDone);
|
||||
Assert.AreNotEqual(TimeSpan.Zero, timeDone);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(output))
|
||||
File.Delete(output);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_TranscodeInMemory()
|
||||
{
|
||||
using var resStream = new MemoryStream();
|
||||
var reader = new StreamPipeSink(resStream);
|
||||
var writer = new RawVideoPipeSource(BitmapSource.CreateBitmaps(128, System.Drawing.Imaging.PixelFormat.Format24bppRgb, 128, 128));
|
||||
|
||||
FFMpegArguments
|
||||
.FromPipeInput(writer)
|
||||
.OutputToPipe(reader, opt => opt
|
||||
.WithVideoCodec("vp9")
|
||||
.ForceFormat("webm"))
|
||||
.ProcessSynchronously();
|
||||
|
||||
resStream.Position = 0;
|
||||
var vi = FFProbe.Analyse(resStream);
|
||||
Assert.AreEqual(vi.PrimaryVideoStream.Width, 128);
|
||||
Assert.AreEqual(vi.PrimaryVideoStream.Height, 128);
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Video_Cancel_Async()
|
||||
{
|
||||
await using var resStream = new MemoryStream();
|
||||
var reader = new StreamPipeSink(resStream);
|
||||
var writer = new RawVideoPipeSource(BitmapSource.CreateBitmaps(512, System.Drawing.Imaging.PixelFormat.Format24bppRgb, 128, 128));
|
||||
|
||||
var task = FFMpegArguments
|
||||
.FromPipeInput(writer)
|
||||
.OutputToPipe(reader, opt => opt
|
||||
.WithVideoCodec("vp9")
|
||||
.ForceFormat("webm"))
|
||||
.CancellableThrough(out var cancel)
|
||||
.ProcessAsynchronously(false);
|
||||
|
||||
await Task.Delay(300);
|
||||
cancel();
|
||||
|
||||
var result = await task;
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"RootDirectory": "/usr/bin"
|
||||
"RootDirectory": ""
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
using FFMpegCore.FFMPEG.Enums;
|
||||
using System;
|
||||
|
||||
namespace FFMpegCore.Enums
|
||||
{
|
||||
public static class FileExtension
|
||||
{
|
||||
public static string ForType(VideoType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case VideoType.Mp4: return Mp4;
|
||||
case VideoType.Ogv: return Ogv;
|
||||
case VideoType.Ts: return Ts;
|
||||
case VideoType.WebM: return WebM;
|
||||
default: throw new Exception("The extension for this video type is not defined.");
|
||||
}
|
||||
}
|
||||
public static string ForCodec(VideoCodec type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case VideoCodec.LibX264: return Mp4;
|
||||
case VideoCodec.LibVpx: return WebM;
|
||||
case VideoCodec.LibTheora: return Ogv;
|
||||
case VideoCodec.MpegTs: return Ts;
|
||||
case VideoCodec.Png: return Png;
|
||||
default: throw new Exception("The extension for this video type is not defined.");
|
||||
}
|
||||
}
|
||||
public static readonly string Mp4 = ".mp4";
|
||||
public static readonly string Mp3 = ".mp3";
|
||||
public static readonly string Ts = ".ts";
|
||||
public static readonly string Ogv = ".ogv";
|
||||
public static readonly string Png = ".png";
|
||||
public static readonly string WebM = ".webm";
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
namespace FFMpegCore.Enums
|
||||
{
|
||||
public enum VideoType
|
||||
{
|
||||
Mp4,
|
||||
Ogv,
|
||||
Ts,
|
||||
WebM
|
||||
}
|
||||
}
|
|
@ -1,26 +1,22 @@
|
|||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using FFMpegCore.FFMPEG;
|
||||
|
||||
namespace FFMpegCore.Extend
|
||||
{
|
||||
public static class BitmapExtensions
|
||||
{
|
||||
public static VideoInfo AddAudio(this Bitmap poster, FileInfo audio, FileInfo output)
|
||||
public static bool AddAudio(this Bitmap poster, string audio, string output)
|
||||
{
|
||||
var destination = $"{Environment.TickCount}.png";
|
||||
|
||||
poster.Save(destination);
|
||||
|
||||
var tempFile = new FileInfo(destination);
|
||||
try
|
||||
{
|
||||
return new FFMpeg().PosterWithAudio(tempFile, audio, output);
|
||||
return FFMpeg.PosterWithAudio(destination, audio, output);
|
||||
}
|
||||
finally
|
||||
{
|
||||
tempFile.Delete();
|
||||
if (File.Exists(destination)) File.Delete(destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
88
FFMpegCore/Extend/BitmapVideoFrameWrapper.cs
Normal file
88
FFMpegCore/Extend/BitmapVideoFrameWrapper.cs
Normal file
|
@ -0,0 +1,88 @@
|
|||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FFMpegCore.Pipes;
|
||||
|
||||
namespace FFMpegCore.Extend
|
||||
{
|
||||
public class BitmapVideoFrameWrapper : IVideoFrame, IDisposable
|
||||
{
|
||||
public int Width => Source.Width;
|
||||
|
||||
public int Height => Source.Height;
|
||||
|
||||
public string Format { get; private set; }
|
||||
|
||||
public Bitmap Source { get; private set; }
|
||||
|
||||
public BitmapVideoFrameWrapper(Bitmap bitmap)
|
||||
{
|
||||
Source = bitmap ?? throw new ArgumentNullException(nameof(bitmap));
|
||||
Format = ConvertStreamFormat(bitmap.PixelFormat);
|
||||
}
|
||||
|
||||
public void Serialize(System.IO.Stream stream)
|
||||
{
|
||||
var data = Source.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadOnly, Source.PixelFormat);
|
||||
|
||||
try
|
||||
{
|
||||
var buffer = new byte[data.Stride * data.Height];
|
||||
Marshal.Copy(data.Scan0, buffer, 0, buffer.Length);
|
||||
stream.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Source.UnlockBits(data);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SerializeAsync(System.IO.Stream stream, CancellationToken token)
|
||||
{
|
||||
var data = Source.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadOnly, Source.PixelFormat);
|
||||
|
||||
try
|
||||
{
|
||||
var buffer = new byte[data.Stride * data.Height];
|
||||
Marshal.Copy(data.Scan0, buffer, 0, buffer.Length);
|
||||
await stream.WriteAsync(buffer, 0, buffer.Length, token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Source.UnlockBits(data);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Source.Dispose();
|
||||
}
|
||||
|
||||
private static string ConvertStreamFormat(PixelFormat fmt)
|
||||
{
|
||||
switch (fmt)
|
||||
{
|
||||
case PixelFormat.Format16bppGrayScale:
|
||||
return "gray16le";
|
||||
case PixelFormat.Format16bppRgb565:
|
||||
return "bgr565le";
|
||||
case PixelFormat.Format24bppRgb:
|
||||
return "bgr24";
|
||||
case PixelFormat.Format32bppArgb:
|
||||
return "bgra";
|
||||
case PixelFormat.Format32bppPArgb:
|
||||
//This is not really same as argb32
|
||||
return "argb";
|
||||
case PixelFormat.Format32bppRgb:
|
||||
return "rgba";
|
||||
case PixelFormat.Format48bppRgb:
|
||||
return "rgb48le";
|
||||
default:
|
||||
throw new NotSupportedException($"Not supported pixel format {fmt}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +1,12 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using FFMpegCore.FFMPEG;
|
||||
|
||||
namespace FFMpegCore.Extend
|
||||
{
|
||||
public static class UriExtensions
|
||||
{
|
||||
public static VideoInfo SaveStream(this Uri uri, FileInfo output)
|
||||
public static bool SaveStream(this Uri uri, string output)
|
||||
{
|
||||
return new FFMpeg().SaveM3U8Stream(uri, output);
|
||||
return FFMpeg.SaveM3U8Stream(uri, output);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract class implements basic functionality of ffmpeg arguments
|
||||
/// </summary>
|
||||
public abstract class Argument
|
||||
{
|
||||
/// <summary>
|
||||
/// String representation of the argument
|
||||
/// </summary>
|
||||
/// <returns>String representation of the argument</returns>
|
||||
public abstract string GetStringValue();
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return GetStringValue();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstract class implements basic functionality of ffmpeg arguments with one value property
|
||||
/// </summary>
|
||||
public abstract class Argument<T> : Argument
|
||||
{
|
||||
private T _value;
|
||||
|
||||
/// <summary>
|
||||
/// Value type of <see cref="T"/>
|
||||
/// </summary>
|
||||
public T Value { get => _value; set { CheckValue(value); _value = value; } }
|
||||
|
||||
public Argument() { }
|
||||
|
||||
public Argument(T value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
protected virtual void CheckValue(T value)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstract class implements basic functionality of ffmpeg arguments with two values properties
|
||||
/// </summary>
|
||||
public abstract class Argument<T1, T2> : Argument
|
||||
{
|
||||
|
||||
private T1 _first;
|
||||
private T2 _second;
|
||||
|
||||
/// <summary>
|
||||
/// First value type of <see cref="T"/>
|
||||
/// </summary>
|
||||
public T1 First { get => _first; set { CheckFirst(_first); _first = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Second value type of <see cref="T"/>
|
||||
/// </summary>
|
||||
public T2 Second { get => _second; set { CheckSecond(_second); _second = value; } }
|
||||
|
||||
public Argument() { }
|
||||
|
||||
public Argument(T1 first, T2 second)
|
||||
{
|
||||
First = first;
|
||||
Second = second;
|
||||
}
|
||||
|
||||
protected virtual void CheckFirst(T1 value)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected virtual void CheckSecond(T2 value)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,160 +0,0 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
/// <summary>
|
||||
/// Container of arguments represented parameters of FFMPEG process
|
||||
/// </summary>
|
||||
public class ArgumentContainer : IDictionary<Type, Argument>
|
||||
{
|
||||
IDictionary<Type, Argument> _args;
|
||||
|
||||
public ArgumentContainer(params Argument[] arguments)
|
||||
{
|
||||
_args = new Dictionary<Type, Argument>();
|
||||
|
||||
foreach(var argument in arguments)
|
||||
{
|
||||
this.Add(argument);
|
||||
}
|
||||
}
|
||||
|
||||
public Argument this[Type key] { get => _args[key]; set => _args[key] = value; }
|
||||
|
||||
public ICollection<Type> Keys => _args.Keys;
|
||||
|
||||
public ICollection<Argument> Values => _args.Values;
|
||||
|
||||
public int Count => _args.Count;
|
||||
|
||||
public bool IsReadOnly => _args.IsReadOnly;
|
||||
|
||||
/// <summary>
|
||||
/// This method is not supported, left for <see cref="{IDictionary<Type, Argument>}"/> interface support
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
[Obsolete]
|
||||
public void Add(Type key, Argument value)
|
||||
{
|
||||
throw new InvalidOperationException("Not supported operation");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is not supported, left for <see cref="{IDictionary<Type, Argument>}"/> interface support
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
[Obsolete]
|
||||
public void Add(KeyValuePair<Type, Argument> item)
|
||||
{
|
||||
this.Add(item.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears collection of arguments
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_args.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if contains item
|
||||
/// </summary>
|
||||
/// <param name="item">Searching item</param>
|
||||
/// <returns>Returns if contains item</returns>
|
||||
public bool Contains(KeyValuePair<Type, Argument> item)
|
||||
{
|
||||
return _args.Contains(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds argument to collection
|
||||
/// </summary>
|
||||
/// <param name="value">Argument that should be added to collection</param>
|
||||
public void Add(params Argument[] values)
|
||||
{
|
||||
foreach(var value in values)
|
||||
{
|
||||
_args.Add(value.GetType(), value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if container contains output and input parameters
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool ContainsInputOutput()
|
||||
{
|
||||
return ((ContainsKey(typeof(InputArgument)) && !ContainsKey(typeof(ConcatArgument))) ||
|
||||
(!ContainsKey(typeof(InputArgument)) && ContainsKey(typeof(ConcatArgument))))
|
||||
&& ContainsKey(typeof(OutputArgument));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if contains argument of type
|
||||
/// </summary>
|
||||
/// <param name="key">Type of argument is seraching</param>
|
||||
/// <returns></returns>
|
||||
public bool ContainsKey(Type key)
|
||||
{
|
||||
return _args.ContainsKey(key);
|
||||
}
|
||||
|
||||
public void CopyTo(KeyValuePair<Type, Argument>[] array, int arrayIndex)
|
||||
{
|
||||
_args.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<Type, Argument>> GetEnumerator()
|
||||
{
|
||||
return _args.GetEnumerator();
|
||||
}
|
||||
|
||||
public bool Remove(Type key)
|
||||
{
|
||||
return _args.Remove(key);
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<Type, Argument> item)
|
||||
{
|
||||
return _args.Remove(item);
|
||||
}
|
||||
|
||||
public bool TryGetValue(Type key, out Argument value)
|
||||
{
|
||||
return _args.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return _args.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for finding arguments inside collection
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of argument</typeparam>
|
||||
/// <returns></returns>
|
||||
public T Find<T>() where T : Argument
|
||||
{
|
||||
if (ContainsKey(typeof(T)))
|
||||
return (T)_args[typeof(T)];
|
||||
return null;
|
||||
}
|
||||
/// <summary>
|
||||
/// Shortcut for checking if contains arguments inside collection
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of argument</typeparam>
|
||||
/// <returns></returns>
|
||||
public bool Contains<T>() where T : Argument
|
||||
{
|
||||
if (ContainsKey(typeof(T)))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,179 +0,0 @@
|
|||
using FFMpegCore.FFMPEG.Enums;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
internal static class ArgumentStringifier
|
||||
{
|
||||
internal static string Speed(Speed speed)
|
||||
{
|
||||
return $"-preset {speed.ToString().ToLower()} ";
|
||||
}
|
||||
|
||||
internal static string Speed(int cpu)
|
||||
{
|
||||
return $"-quality good -cpu-used {cpu} -deadline realtime ";
|
||||
}
|
||||
|
||||
internal static string Audio(AudioCodec codec, int bitrate)
|
||||
{
|
||||
return Audio(codec) + Audio(bitrate);
|
||||
}
|
||||
|
||||
internal static string Audio(AudioCodec codec)
|
||||
{
|
||||
return $"-c:a {codec.ToString().ToLower()} ";
|
||||
}
|
||||
|
||||
internal static string Audio(AudioQuality bitrate)
|
||||
{
|
||||
return Audio((int)bitrate);
|
||||
}
|
||||
|
||||
internal static string Audio(int bitrate)
|
||||
{
|
||||
return $"-b:a {bitrate}k ";
|
||||
}
|
||||
|
||||
internal static string Video(VideoCodec codec, int bitrate = 0)
|
||||
{
|
||||
var video = $"-c:v {codec.ToString().ToLower()} -pix_fmt yuv420p ";
|
||||
|
||||
if (bitrate > 0)
|
||||
{
|
||||
video += $"-b:v {bitrate}k ";
|
||||
}
|
||||
|
||||
return video;
|
||||
}
|
||||
|
||||
internal static string Threads(bool multiThread)
|
||||
{
|
||||
var threadCount = multiThread
|
||||
? Environment.ProcessorCount
|
||||
: 1;
|
||||
|
||||
return Threads(threadCount);
|
||||
}
|
||||
|
||||
internal static string Threads(int threads)
|
||||
{
|
||||
return $"-threads {threads} ";
|
||||
}
|
||||
|
||||
internal static string Disable(Channel type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case Channel.Video:
|
||||
return "-vn ";
|
||||
case Channel.Audio:
|
||||
return "-an ";
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
internal static string Output(string output)
|
||||
{
|
||||
return $"\"{output}\"";
|
||||
}
|
||||
|
||||
internal static string Input(string template)
|
||||
{
|
||||
return $"-i \"{template}\" ";
|
||||
}
|
||||
|
||||
internal static string Scale(VideoSize size, int width =-1)
|
||||
{
|
||||
return size == VideoSize.Original ? string.Empty : Scale(width, (int)size);
|
||||
}
|
||||
|
||||
internal static string Scale(int width, int height)
|
||||
{
|
||||
return $"-vf scale={width}:{height} ";
|
||||
}
|
||||
|
||||
internal static string Size(Size? size)
|
||||
{
|
||||
if (!size.HasValue) return string.Empty;
|
||||
|
||||
var formatedSize = $"{size.Value.Width}x{size.Value.Height}";
|
||||
|
||||
return $"-s {formatedSize} ";
|
||||
}
|
||||
|
||||
internal static string ForceFormat(VideoCodec codec)
|
||||
{
|
||||
return $"-f {codec.ToString().ToLower()} ";
|
||||
}
|
||||
|
||||
internal static string BitStreamFilter(Channel type, Filter filter)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case Channel.Audio:
|
||||
return $"-bsf:a {filter.ToString().ToLower()} ";
|
||||
case Channel.Video:
|
||||
return $"-bsf:v {filter.ToString().ToLower()} ";
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
internal static string Copy(Channel type = Channel.Both)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case Channel.Audio:
|
||||
return "-c:a copy ";
|
||||
case Channel.Video:
|
||||
return "-c:v copy ";
|
||||
default:
|
||||
return "-c copy ";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string Seek(TimeSpan? seek)
|
||||
{
|
||||
return !seek.HasValue ? string.Empty : $"-ss {seek} ";
|
||||
}
|
||||
|
||||
internal static string FrameOutputCount(int number)
|
||||
{
|
||||
return $"-vframes {number} ";
|
||||
}
|
||||
|
||||
internal static string Loop(int count)
|
||||
{
|
||||
return $"-loop {count} ";
|
||||
}
|
||||
|
||||
internal static string FinalizeAtShortestInput(bool applicable)
|
||||
{
|
||||
return applicable ? "-shortest " : string.Empty;
|
||||
}
|
||||
|
||||
internal static string InputConcat(IEnumerable<string> paths)
|
||||
{
|
||||
return $"-i \"concat:{string.Join(@"|", paths)}\" ";
|
||||
}
|
||||
|
||||
internal static string FrameRate(double frameRate)
|
||||
{
|
||||
return $"-r {frameRate} ";
|
||||
}
|
||||
|
||||
internal static string StartNumber(int v)
|
||||
{
|
||||
return $"-start_number {v} ";
|
||||
}
|
||||
|
||||
internal static string Duration(TimeSpan? duration)
|
||||
{
|
||||
return !duration.HasValue ? string.Empty : $"-t {duration} ";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
using FFMpegCore.FFMPEG.Enums;
|
||||
|
||||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents parameter of audio codec and it's quality
|
||||
/// </summary>
|
||||
public class AudioCodecArgument : Argument<AudioCodec>
|
||||
{
|
||||
/// <summary>
|
||||
/// Bitrate of audio channel
|
||||
/// </summary>
|
||||
public int Bitrate { get; protected set; } = (int)AudioQuality.Normal;
|
||||
|
||||
public AudioCodecArgument()
|
||||
{
|
||||
}
|
||||
|
||||
public AudioCodecArgument(AudioCodec value) : base(value)
|
||||
{
|
||||
}
|
||||
|
||||
public AudioCodecArgument(AudioCodec value, AudioQuality bitrate) : base(value)
|
||||
{
|
||||
Bitrate = (int)bitrate;
|
||||
}
|
||||
|
||||
public AudioCodecArgument(AudioCodec value, int bitrate) : base(value)
|
||||
{
|
||||
Bitrate = bitrate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String representation of the argument
|
||||
/// </summary>
|
||||
/// <returns>String representation of the argument</returns>
|
||||
public override string GetStringValue()
|
||||
{
|
||||
return ArgumentStringifier.Audio(Value, Bitrate);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
using FFMpegCore.FFMPEG.Enums;
|
||||
|
||||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents parameter of bitstream filter
|
||||
/// </summary>
|
||||
public class BitStreamFilterArgument : Argument<Channel, Filter>
|
||||
{
|
||||
public BitStreamFilterArgument()
|
||||
{
|
||||
}
|
||||
|
||||
public BitStreamFilterArgument(Channel first, Filter second) : base(first, second)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String representation of the argument
|
||||
/// </summary>
|
||||
/// <returns>String representation of the argument</returns>
|
||||
public override string GetStringValue()
|
||||
{
|
||||
return ArgumentStringifier.BitStreamFilter(First, Second);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Represents parameter of concat argument
|
||||
/// Used for creating video from multiple images or videos
|
||||
/// </summary>
|
||||
public class ConcatArgument : Argument<IEnumerable<string>>, IEnumerable<string>
|
||||
{
|
||||
public ConcatArgument()
|
||||
{
|
||||
Value = new List<string>();
|
||||
}
|
||||
|
||||
public ConcatArgument(IEnumerable<string> value) : base(value)
|
||||
{
|
||||
}
|
||||
|
||||
public IEnumerator<string> GetEnumerator()
|
||||
{
|
||||
return Value.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String representation of the argument
|
||||
/// </summary>
|
||||
/// <returns>String representation of the argument</returns>
|
||||
public override string GetStringValue()
|
||||
{
|
||||
return ArgumentStringifier.InputConcat(Value);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
using FFMpegCore.FFMPEG.Enums;
|
||||
|
||||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents parameter of copy parameter
|
||||
/// Defines if channel (audio, video or both) should be copied to output file
|
||||
/// </summary>
|
||||
public class CopyArgument : Argument<Channel>
|
||||
{
|
||||
public CopyArgument()
|
||||
{
|
||||
Value = Channel.Both;
|
||||
}
|
||||
|
||||
public CopyArgument(Channel value = Channel.Both) : base(value)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String representation of the argument
|
||||
/// </summary>
|
||||
/// <returns>String representation of the argument</returns>
|
||||
public override string GetStringValue()
|
||||
{
|
||||
return ArgumentStringifier.Copy(Value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents cpu speed parameter
|
||||
/// </summary>
|
||||
public class CpuSpeedArgument : Argument<int>
|
||||
{
|
||||
public CpuSpeedArgument()
|
||||
{
|
||||
}
|
||||
|
||||
public CpuSpeedArgument(int value) : base(value)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String representation of the argument
|
||||
/// </summary>
|
||||
/// <returns>String representation of the argument</returns>
|
||||
public override string GetStringValue()
|
||||
{
|
||||
return ArgumentStringifier.Speed(Value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
using FFMpegCore.FFMPEG.Enums;
|
||||
|
||||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents cpu speed parameter
|
||||
/// </summary>
|
||||
public class DisableChannelArgument : Argument<Channel>
|
||||
{
|
||||
public DisableChannelArgument()
|
||||
{
|
||||
}
|
||||
|
||||
public DisableChannelArgument(Channel value) : base(value)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String representation of the argument
|
||||
/// </summary>
|
||||
/// <returns>String representation of the argument</returns>
|
||||
public override string GetStringValue()
|
||||
{
|
||||
return ArgumentStringifier.Disable(Value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents duration parameter
|
||||
/// </summary>
|
||||
public class DurationArgument : Argument<TimeSpan?>
|
||||
{
|
||||
public DurationArgument()
|
||||
{
|
||||
}
|
||||
|
||||
public DurationArgument(TimeSpan? value) : base(value)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String representation of the argument
|
||||
/// </summary>
|
||||
/// <returns>String representation of the argument</returns>
|
||||
public override string GetStringValue()
|
||||
{
|
||||
return ArgumentStringifier.Duration(Value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
using FFMpegCore.FFMPEG.Enums;
|
||||
|
||||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents force format parameter
|
||||
/// </summary>
|
||||
public class ForceFormatArgument : Argument<VideoCodec>
|
||||
{
|
||||
public ForceFormatArgument()
|
||||
{
|
||||
}
|
||||
|
||||
public ForceFormatArgument(VideoCodec value) : base(value)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String representation of the argument
|
||||
/// </summary>
|
||||
/// <returns>String representation of the argument</returns>
|
||||
public override string GetStringValue()
|
||||
{
|
||||
return ArgumentStringifier.ForceFormat(Value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents frame output count parameter
|
||||
/// </summary>
|
||||
public class FrameOutputCountArgument : Argument<int>
|
||||
{
|
||||
public FrameOutputCountArgument()
|
||||
{
|
||||
}
|
||||
|
||||
public FrameOutputCountArgument(int value) : base(value)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String representation of the argument
|
||||
/// </summary>
|
||||
/// <returns>String representation of the argument</returns>
|
||||
public override string GetStringValue()
|
||||
{
|
||||
return ArgumentStringifier.FrameOutputCount(Value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents frame rate parameter
|
||||
/// </summary>
|
||||
public class FrameRateArgument : Argument<double>
|
||||
{
|
||||
public FrameRateArgument()
|
||||
{
|
||||
}
|
||||
|
||||
public FrameRateArgument(double value) : base(value)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String representation of the argument
|
||||
/// </summary>
|
||||
/// <returns>String representation of the argument</returns>
|
||||
public override string GetStringValue()
|
||||
{
|
||||
return ArgumentStringifier.FrameRate(Value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents input parameter
|
||||
/// </summary>
|
||||
public class InputArgument : Argument<string[]>
|
||||
{
|
||||
public InputArgument()
|
||||
{
|
||||
}
|
||||
|
||||
public InputArgument(params string[] values) : base(values)
|
||||
{
|
||||
}
|
||||
|
||||
public InputArgument(params VideoInfo[] values) : base(values.Select(v => v.FullName).ToArray())
|
||||
{
|
||||
}
|
||||
|
||||
public InputArgument(params FileInfo[] values) : base(values.Select(v => v.FullName).ToArray())
|
||||
{
|
||||
}
|
||||
|
||||
public InputArgument(params Uri[] values) : base(values.Select(v => v.AbsoluteUri).ToArray())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String representation of the argument
|
||||
/// </summary>
|
||||
/// <returns>String representation of the argument</returns>
|
||||
public override string GetStringValue()
|
||||
{
|
||||
return string.Join(" ", Value.Select(v => ArgumentStringifier.Input(v)));
|
||||
}
|
||||
public VideoInfo[] GetAsVideoInfo()
|
||||
{
|
||||
return Value.Select(v => new VideoInfo(v)).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents loop parameter
|
||||
/// </summary>
|
||||
public class LoopArgument : Argument<int>
|
||||
{
|
||||
public LoopArgument()
|
||||
{
|
||||
}
|
||||
|
||||
public LoopArgument(int value) : base(value)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String representation of the argument
|
||||
/// </summary>
|
||||
/// <returns>String representation of the argument</returns>
|
||||
public override string GetStringValue()
|
||||
{
|
||||
return ArgumentStringifier.Loop(Value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents output parameter
|
||||
/// </summary>
|
||||
public class OutputArgument : Argument<string>
|
||||
{
|
||||
public OutputArgument()
|
||||
{
|
||||
}
|
||||
|
||||
public OutputArgument(string value) : base(value)
|
||||
{
|
||||
}
|
||||
|
||||
public OutputArgument(VideoInfo value) : base(value.FullName)
|
||||
{
|
||||
}
|
||||
|
||||
public OutputArgument(FileInfo value) : base(value.FullName)
|
||||
{
|
||||
}
|
||||
|
||||
public OutputArgument(Uri value) : base(value.AbsolutePath)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String representation of the argument
|
||||
/// </summary>
|
||||
/// <returns>String representation of the argument</returns>
|
||||
public override string GetStringValue()
|
||||
{
|
||||
return ArgumentStringifier.Output(Value);
|
||||
}
|
||||
|
||||
public FileInfo GetAsFileInfo()
|
||||
{
|
||||
return new FileInfo(Value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents override parameter
|
||||
/// If output file should be overrided if exists
|
||||
/// </summary>
|
||||
public class OverrideArgument : Argument
|
||||
{
|
||||
public OverrideArgument()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String representation of the argument
|
||||
/// </summary>
|
||||
/// <returns>String representation of the argument</returns>
|
||||
public override string GetStringValue()
|
||||
{
|
||||
return "-y";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
using FFMpegCore.FFMPEG.Enums;
|
||||
using System.Drawing;
|
||||
|
||||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents scale parameter
|
||||
/// </summary>
|
||||
public class ScaleArgument : Argument<Size>
|
||||
{
|
||||
public ScaleArgument()
|
||||
{
|
||||
}
|
||||
|
||||
public ScaleArgument(Size value) : base(value)
|
||||
{
|
||||
}
|
||||
|
||||
public ScaleArgument(int width, int heignt) : base(new Size(width, heignt))
|
||||
{
|
||||
}
|
||||
|
||||
public ScaleArgument(VideoSize videosize)
|
||||
{
|
||||
Value = videosize == VideoSize.Original ? new Size(-1, -1) : new Size(-1, (int)videosize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String representation of the argument
|
||||
/// </summary>
|
||||
/// <returns>String representation of the argument</returns>
|
||||
public override string GetStringValue()
|
||||
{
|
||||
return ArgumentStringifier.Scale(Value.Width, Value.Height);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents seek parameter
|
||||
/// </summary>
|
||||
public class SeekArgument : Argument<TimeSpan?>
|
||||
{
|
||||
public SeekArgument()
|
||||
{
|
||||
}
|
||||
|
||||
public SeekArgument(TimeSpan? value) : base(value)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String representation of the argument
|
||||
/// </summary>
|
||||
/// <returns>String representation of the argument</returns>
|
||||
public override string GetStringValue()
|
||||
{
|
||||
return ArgumentStringifier.Seek(Value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents shortest parameter
|
||||
/// </summary>
|
||||
public class ShortestArgument : Argument<bool>
|
||||
{
|
||||
public ShortestArgument()
|
||||
{
|
||||
}
|
||||
|
||||
public ShortestArgument(bool value) : base(value)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String representation of the argument
|
||||
/// </summary>
|
||||
/// <returns>String representation of the argument</returns>
|
||||
public override string GetStringValue()
|
||||
{
|
||||
return ArgumentStringifier.FinalizeAtShortestInput(Value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
using System.Drawing;
|
||||
using FFMpegCore.FFMPEG.Enums;
|
||||
|
||||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents size parameter
|
||||
/// </summary>
|
||||
public class SizeArgument : ScaleArgument
|
||||
{
|
||||
public SizeArgument()
|
||||
{
|
||||
}
|
||||
|
||||
public SizeArgument(Size? value) : base(value ?? new Size())
|
||||
{
|
||||
}
|
||||
|
||||
public SizeArgument(VideoSize videosize) : base(videosize)
|
||||
{
|
||||
}
|
||||
|
||||
public SizeArgument(int width, int heignt) : base(width, heignt)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String representation of the argument
|
||||
/// </summary>
|
||||
/// <returns>String representation of the argument</returns>
|
||||
public override string GetStringValue()
|
||||
{
|
||||
return ArgumentStringifier.Size(Value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
using FFMpegCore.FFMPEG.Enums;
|
||||
|
||||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents speed parameter
|
||||
/// </summary>
|
||||
public class SpeedArgument : Argument<Speed>
|
||||
{
|
||||
public SpeedArgument()
|
||||
{
|
||||
}
|
||||
|
||||
public SpeedArgument(Speed value) : base(value)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String representation of the argument
|
||||
/// </summary>
|
||||
/// <returns>String representation of the argument</returns>
|
||||
public override string GetStringValue()
|
||||
{
|
||||
return ArgumentStringifier.Speed(Value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents start number parameter
|
||||
/// </summary>
|
||||
public class StartNumberArgument : Argument<int>
|
||||
{
|
||||
public StartNumberArgument()
|
||||
{
|
||||
}
|
||||
|
||||
public StartNumberArgument(int value) : base(value)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String representation of the argument
|
||||
/// </summary>
|
||||
/// <returns>String representation of the argument</returns>
|
||||
public override string GetStringValue()
|
||||
{
|
||||
return ArgumentStringifier.StartNumber(Value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents threads parameter
|
||||
/// Number of threads used for video encoding
|
||||
/// </summary>
|
||||
public class ThreadsArgument : Argument<int>
|
||||
{
|
||||
public ThreadsArgument()
|
||||
{
|
||||
}
|
||||
|
||||
public ThreadsArgument(int value) : base(value)
|
||||
{
|
||||
}
|
||||
|
||||
public ThreadsArgument(bool isMultiThreaded) :
|
||||
base(isMultiThreaded
|
||||
? Environment.ProcessorCount
|
||||
: 1)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String representation of the argument
|
||||
/// </summary>
|
||||
/// <returns>String representation of the argument</returns>
|
||||
public override string GetStringValue()
|
||||
{
|
||||
return ArgumentStringifier.Threads(Value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
using FFMpegCore.FFMPEG.Enums;
|
||||
|
||||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents video codec parameter
|
||||
/// </summary>
|
||||
public class VideoCodecArgument : Argument<VideoCodec>
|
||||
{
|
||||
public int Bitrate { get; protected set; } = 0;
|
||||
|
||||
public VideoCodecArgument()
|
||||
{
|
||||
}
|
||||
|
||||
public VideoCodecArgument(VideoCodec value) : base(value)
|
||||
{
|
||||
}
|
||||
|
||||
public VideoCodecArgument(VideoCodec value, int bitrate) : base(value)
|
||||
{
|
||||
Bitrate = bitrate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String representation of the argument
|
||||
/// </summary>
|
||||
/// <returns>String representation of the argument</returns>
|
||||
public override string GetStringValue()
|
||||
{
|
||||
return ArgumentStringifier.Video(Value, Bitrate);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
using FFMpegCore.Enums;
|
||||
using FFMpegCore.Helpers;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds parameters string from <see cref="ArgumentContainer"/> that would be passed to ffmpeg process
|
||||
/// </summary>
|
||||
public class FFArgumentBuilder : IArgumentBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds parameters string from <see cref="ArgumentContainer"/> that would be passed to ffmpeg process
|
||||
/// </summary>
|
||||
/// <param name="container">Container of arguments</param>
|
||||
/// <returns>Parameters string</returns>
|
||||
public string BuildArguments(ArgumentContainer container)
|
||||
{
|
||||
if (!container.ContainsInputOutput())
|
||||
throw new ArgumentException("No input or output parameter found", nameof(container));
|
||||
|
||||
|
||||
return string.Join(" ", container.Select(argument => argument.Value.GetStringValue().Trim()));
|
||||
}
|
||||
|
||||
private void CheckExtensionOfOutputExtension(ArgumentContainer container, FileInfo output)
|
||||
{
|
||||
if(container.ContainsKey(typeof(VideoCodecArgument)))
|
||||
{
|
||||
var codec = (VideoCodecArgument)container[typeof(VideoCodecArgument)];
|
||||
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.ForCodec(codec.Value));
|
||||
}
|
||||
}
|
||||
|
||||
private Argument GetInput(ArgumentContainer container)
|
||||
{
|
||||
if (container.ContainsKey(typeof(InputArgument)))
|
||||
return container[typeof(InputArgument)];
|
||||
else if (container.ContainsKey(typeof(ConcatArgument)))
|
||||
return container[typeof(ConcatArgument)];
|
||||
else
|
||||
throw new ArgumentException("No inputs found");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
namespace FFMpegCore.FFMPEG.Argument
|
||||
{
|
||||
public interface IArgumentBuilder
|
||||
{
|
||||
string BuildArguments(ArgumentContainer container);
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
namespace FFMpegCore.FFMPEG.Enums
|
||||
{
|
||||
public enum VideoCodec
|
||||
{
|
||||
LibX264,
|
||||
LibVpx,
|
||||
LibTheora,
|
||||
Png,
|
||||
MpegTs
|
||||
}
|
||||
|
||||
public enum AudioCodec
|
||||
{
|
||||
Aac,
|
||||
LibVorbis
|
||||
}
|
||||
|
||||
public enum Filter
|
||||
{
|
||||
H264_Mp4ToAnnexB,
|
||||
Aac_AdtstoAsc
|
||||
}
|
||||
|
||||
public enum Channel
|
||||
{
|
||||
Audio,
|
||||
Video,
|
||||
Both
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace FFMpegCore.FFMPEG.Exceptions
|
||||
{
|
||||
public enum FFMpegExceptionType
|
||||
{
|
||||
Dependency,
|
||||
Conversion,
|
||||
File,
|
||||
Operation,
|
||||
Process
|
||||
}
|
||||
|
||||
public class FFMpegException : Exception
|
||||
{
|
||||
public FFMpegException(FFMpegExceptionType type): this(type, null, null) { }
|
||||
|
||||
public FFMpegException(FFMpegExceptionType type, StringBuilder sb): this(type, sb.ToString(), null) { }
|
||||
|
||||
public FFMpegException(FFMpegExceptionType type, string message): this(type, message, null) { }
|
||||
|
||||
public FFMpegException(FFMpegExceptionType type, string message, FFMpegException innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public FFMpegExceptionType Type { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,543 +0,0 @@
|
|||
using FFMpegCore.Enums;
|
||||
using FFMpegCore.FFMPEG.Argument;
|
||||
using FFMpegCore.FFMPEG.Enums;
|
||||
using FFMpegCore.FFMPEG.Exceptions;
|
||||
using FFMpegCore.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Instances;
|
||||
|
||||
namespace FFMpegCore.FFMPEG
|
||||
{
|
||||
public delegate void ConversionHandler(double percentage);
|
||||
|
||||
public class FFMpeg
|
||||
{
|
||||
IArgumentBuilder ArgumentBuilder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Intializes the FFMPEG encoder.
|
||||
/// </summary>
|
||||
public FFMpeg() : base()
|
||||
{
|
||||
FFMpegHelper.RootExceptionCheck(FFMpegOptions.Options.RootDirectory);
|
||||
|
||||
_ffmpegPath = FFMpegOptions.Options.FFmpegBinary;
|
||||
|
||||
ArgumentBuilder = new FFArgumentBuilder();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the percentage of the current conversion progress.
|
||||
/// </summary>
|
||||
public event ConversionHandler OnProgress;
|
||||
|
||||
/// <summary>
|
||||
/// Saves a 'png' thumbnail from the input video.
|
||||
/// </summary>
|
||||
/// <param name="source">Source video file.</param>
|
||||
/// <param name="output">Output video file</param>
|
||||
/// <param name="captureTime">Seek position where the thumbnail should be taken.</param>
|
||||
/// <param name="size">Thumbnail size. If width or height equal 0, the other will be computed automatically.</param>
|
||||
/// <param name="persistSnapshotOnFileSystem">By default, it deletes the created image on disk. If set to true, it won't delete the image</param>
|
||||
/// <returns>Bitmap with the requested snapshot.</returns>
|
||||
public Bitmap Snapshot(VideoInfo source, FileInfo output, Size? size = null, TimeSpan? captureTime = null,
|
||||
bool persistSnapshotOnFileSystem = false)
|
||||
{
|
||||
if (captureTime == null)
|
||||
captureTime = TimeSpan.FromSeconds(source.Duration.TotalSeconds / 3);
|
||||
|
||||
if (output.Extension.ToLower() != FileExtension.Png)
|
||||
output = new FileInfo(output.FullName.Replace(output.Extension, FileExtension.Png));
|
||||
|
||||
if (size == null || (size.Value.Height == 0 && size.Value.Width == 0))
|
||||
{
|
||||
size = new Size(source.Width, source.Height);
|
||||
}
|
||||
|
||||
if (size.Value.Width != size.Value.Height)
|
||||
{
|
||||
if (size.Value.Width == 0)
|
||||
{
|
||||
var ratio = source.Width / (double) size.Value.Width;
|
||||
|
||||
size = new Size((int) (source.Width * ratio), (int) (source.Height * ratio));
|
||||
}
|
||||
|
||||
if (size.Value.Height == 0)
|
||||
{
|
||||
var ratio = source.Height / (double) size.Value.Height;
|
||||
|
||||
size = new Size((int) (source.Width * ratio), (int) (source.Height * ratio));
|
||||
}
|
||||
}
|
||||
|
||||
FFMpegHelper.ConversionExceptionCheck(source.ToFileInfo(), output);
|
||||
var container = new ArgumentContainer(
|
||||
new InputArgument(source),
|
||||
new VideoCodecArgument(VideoCodec.Png),
|
||||
new FrameOutputCountArgument(1),
|
||||
new SeekArgument(captureTime),
|
||||
new SizeArgument(size),
|
||||
new OutputArgument(output)
|
||||
);
|
||||
|
||||
if (!RunProcess(container, output))
|
||||
{
|
||||
throw new OperationCanceledException("Could not take snapshot!");
|
||||
}
|
||||
|
||||
output.Refresh();
|
||||
|
||||
Bitmap result;
|
||||
using (var bmp = (Bitmap) Image.FromFile(output.FullName))
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
bmp.Save(ms, ImageFormat.Png);
|
||||
result = new Bitmap(ms);
|
||||
}
|
||||
|
||||
if (output.Exists && !persistSnapshotOnFileSystem)
|
||||
{
|
||||
output.Delete();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a video do a different format.
|
||||
/// </summary>
|
||||
/// <param name="source">Input video source.</param>
|
||||
/// <param name="output">Output information.</param>
|
||||
/// <param name="type">Target conversion video type.</param>
|
||||
/// <param name="speed">Conversion target speed/quality (faster speed = lower quality).</param>
|
||||
/// <param name="size">Video size.</param>
|
||||
/// <param name="audioQuality">Conversion target audio quality.</param>
|
||||
/// <param name="multithreaded">Is encoding multithreaded.</param>
|
||||
/// <returns>Output video information.</returns>
|
||||
public VideoInfo Convert(
|
||||
VideoInfo source,
|
||||
FileInfo output,
|
||||
VideoType type = VideoType.Mp4,
|
||||
Speed speed = Speed.SuperFast,
|
||||
VideoSize size = VideoSize.Original,
|
||||
AudioQuality audioQuality = AudioQuality.Normal,
|
||||
bool multithreaded = false)
|
||||
{
|
||||
FFMpegHelper.ConversionExceptionCheck(source.ToFileInfo(), output);
|
||||
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.ForType(type));
|
||||
FFMpegHelper.ConversionSizeExceptionCheck(source);
|
||||
|
||||
_totalTime = source.Duration;
|
||||
|
||||
var scale = VideoSize.Original == size ? 1 : (double) source.Height / (int) size;
|
||||
|
||||
var outputSize = new Size(
|
||||
(int) (source.Width / scale),
|
||||
(int) (source.Height / scale)
|
||||
);
|
||||
|
||||
if (outputSize.Width % 2 != 0)
|
||||
{
|
||||
outputSize.Width += 1;
|
||||
}
|
||||
|
||||
var container = new ArgumentContainer();
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case VideoType.Mp4:
|
||||
container.Add(
|
||||
new InputArgument(source),
|
||||
new ThreadsArgument(multithreaded),
|
||||
new ScaleArgument(outputSize),
|
||||
new VideoCodecArgument(VideoCodec.LibX264, 2400),
|
||||
new SpeedArgument(speed),
|
||||
new AudioCodecArgument(AudioCodec.Aac, audioQuality),
|
||||
new OutputArgument(output)
|
||||
);
|
||||
break;
|
||||
case VideoType.Ogv:
|
||||
container.Add(
|
||||
new InputArgument(source),
|
||||
new ThreadsArgument(multithreaded),
|
||||
new ScaleArgument(outputSize),
|
||||
new VideoCodecArgument(VideoCodec.LibTheora, 2400),
|
||||
new SpeedArgument(speed),
|
||||
new AudioCodecArgument(AudioCodec.LibVorbis, audioQuality),
|
||||
new OutputArgument(output)
|
||||
);
|
||||
break;
|
||||
case VideoType.Ts:
|
||||
container.Add(
|
||||
new InputArgument(source),
|
||||
new CopyArgument(),
|
||||
new BitStreamFilterArgument(Channel.Video, Filter.H264_Mp4ToAnnexB),
|
||||
new ForceFormatArgument(VideoCodec.MpegTs),
|
||||
new OutputArgument(output)
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
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>
|
||||
/// Adds a poster image to an audio file.
|
||||
/// </summary>
|
||||
/// <param name="image">Source image file.</param>
|
||||
/// <param name="audio">Source audio file.</param>
|
||||
/// <param name="output">Output video file.</param>
|
||||
/// <returns></returns>
|
||||
public VideoInfo PosterWithAudio(FileInfo image, FileInfo audio, FileInfo output)
|
||||
{
|
||||
FFMpegHelper.InputsExistExceptionCheck(image, audio);
|
||||
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp4);
|
||||
FFMpegHelper.ConversionSizeExceptionCheck(Image.FromFile(image.FullName));
|
||||
|
||||
var container = new ArgumentContainer(
|
||||
new LoopArgument(1),
|
||||
new InputArgument(image.FullName, audio.FullName),
|
||||
new VideoCodecArgument(VideoCodec.LibX264, 2400),
|
||||
new AudioCodecArgument(AudioCodec.Aac, AudioQuality.Normal),
|
||||
new ShortestArgument(true),
|
||||
new OutputArgument(output)
|
||||
);
|
||||
|
||||
if (!RunProcess(container, output))
|
||||
{
|
||||
throw new FFMpegException(FFMpegExceptionType.Operation,
|
||||
"An error occured while adding the audio file to the image.");
|
||||
}
|
||||
|
||||
return new VideoInfo(output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Joins a list of video files.
|
||||
/// </summary>
|
||||
/// <param name="output">Output video file.</param>
|
||||
/// <param name="videos">List of vides that need to be joined together.</param>
|
||||
/// <returns>Output video information.</returns>
|
||||
public VideoInfo Join(FileInfo output, params VideoInfo[] videos)
|
||||
{
|
||||
FFMpegHelper.OutputExistsExceptionCheck(output);
|
||||
FFMpegHelper.InputsExistExceptionCheck(videos.Select(video => video.ToFileInfo()).ToArray());
|
||||
|
||||
var temporaryVideoParts = videos.Select(video =>
|
||||
{
|
||||
FFMpegHelper.ConversionSizeExceptionCheck(video);
|
||||
var destinationPath = video.FullName.Replace(video.Extension, FileExtension.Ts);
|
||||
Convert(
|
||||
video,
|
||||
new FileInfo(destinationPath),
|
||||
VideoType.Ts
|
||||
);
|
||||
return destinationPath;
|
||||
}).ToList();
|
||||
|
||||
var container = new ArgumentContainer(
|
||||
new ConcatArgument(temporaryVideoParts),
|
||||
new CopyArgument(),
|
||||
new BitStreamFilterArgument(Channel.Audio, Filter.Aac_AdtstoAsc),
|
||||
new OutputArgument(output)
|
||||
);
|
||||
|
||||
try
|
||||
{
|
||||
if (!RunProcess(container, output))
|
||||
{
|
||||
throw new FFMpegException(FFMpegExceptionType.Operation,
|
||||
"Could not join the provided video files.");
|
||||
}
|
||||
|
||||
return new VideoInfo(output);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Cleanup(temporaryVideoParts);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an image sequence to a video.
|
||||
/// </summary>
|
||||
/// <param name="output">Output video file.</param>
|
||||
/// <param name="frameRate">FPS</param>
|
||||
/// <param name="images">Image sequence collection</param>
|
||||
/// <returns>Output video information.</returns>
|
||||
public VideoInfo JoinImageSequence(FileInfo output, double frameRate = 30, params ImageInfo[] images)
|
||||
{
|
||||
var temporaryImageFiles = images.Select((image, index) =>
|
||||
{
|
||||
FFMpegHelper.ConversionSizeExceptionCheck(Image.FromFile(image.FullName));
|
||||
var destinationPath =
|
||||
image.FullName.Replace(image.Name, $"{index.ToString().PadLeft(9, '0')}{image.Extension}");
|
||||
File.Copy(image.FullName, destinationPath);
|
||||
|
||||
return destinationPath;
|
||||
}).ToList();
|
||||
|
||||
var firstImage = images.First();
|
||||
|
||||
var container = new ArgumentContainer(
|
||||
new FrameRateArgument(frameRate),
|
||||
new SizeArgument(firstImage.Width, firstImage.Height),
|
||||
new StartNumberArgument(0),
|
||||
new InputArgument($"{firstImage.Directory}{Path.DirectorySeparatorChar}%09d.png"),
|
||||
new FrameOutputCountArgument(images.Length),
|
||||
new VideoCodecArgument(VideoCodec.LibX264),
|
||||
new OutputArgument(output)
|
||||
);
|
||||
|
||||
try
|
||||
{
|
||||
if (!RunProcess(container, output))
|
||||
{
|
||||
throw new FFMpegException(FFMpegExceptionType.Operation,
|
||||
"Could not join the provided image sequence.");
|
||||
}
|
||||
|
||||
return new VideoInfo(output);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Cleanup(temporaryImageFiles);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records M3U8 streams to the specified output.
|
||||
/// </summary>
|
||||
/// <param name="uri">URI to pointing towards stream.</param>
|
||||
/// <param name="output">Output file</param>
|
||||
/// <returns>Success state.</returns>
|
||||
public VideoInfo SaveM3U8Stream(Uri uri, FileInfo output)
|
||||
{
|
||||
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp4);
|
||||
|
||||
if (uri.Scheme == "http" || uri.Scheme == "https")
|
||||
{
|
||||
var container = new ArgumentContainer(
|
||||
new InputArgument(uri),
|
||||
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.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Strips a video file of audio.
|
||||
/// </summary>
|
||||
/// <param name="source">Source video file.</param>
|
||||
/// <param name="output">Output video file.</param>
|
||||
/// <returns></returns>
|
||||
public VideoInfo Mute(VideoInfo source, FileInfo output)
|
||||
{
|
||||
FFMpegHelper.ConversionExceptionCheck(source.ToFileInfo(), output);
|
||||
FFMpegHelper.ConversionSizeExceptionCheck(source);
|
||||
FFMpegHelper.ExtensionExceptionCheck(output, source.Extension);
|
||||
|
||||
var container = new ArgumentContainer(
|
||||
new InputArgument(source),
|
||||
new CopyArgument(),
|
||||
new DisableChannelArgument(Channel.Audio),
|
||||
new OutputArgument(output)
|
||||
);
|
||||
|
||||
if (!RunProcess(container, output))
|
||||
{
|
||||
throw new FFMpegException(FFMpegExceptionType.Operation, "Could not mute the requested video.");
|
||||
}
|
||||
|
||||
return new VideoInfo(output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves audio from a specific video file to disk.
|
||||
/// </summary>
|
||||
/// <param name="source">Source video file.</param>
|
||||
/// <param name="output">Output audio file.</param>
|
||||
/// <returns>Success state.</returns>
|
||||
public FileInfo ExtractAudio(VideoInfo source, FileInfo output)
|
||||
{
|
||||
FFMpegHelper.ConversionExceptionCheck(source.ToFileInfo(), output);
|
||||
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp3);
|
||||
|
||||
var container = new ArgumentContainer(
|
||||
new InputArgument(source),
|
||||
new DisableChannelArgument(Channel.Video),
|
||||
new OutputArgument(output)
|
||||
);
|
||||
|
||||
if (!RunProcess(container, output))
|
||||
{
|
||||
throw new FFMpegException(FFMpegExceptionType.Operation,
|
||||
"Could not extract the audio from the requested video.");
|
||||
}
|
||||
|
||||
output.Refresh();
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds audio to a video file.
|
||||
/// </summary>
|
||||
/// <param name="source">Source video file.</param>
|
||||
/// <param name="audio">Source audio file.</param>
|
||||
/// <param name="output">Output video file.</param>
|
||||
/// <param name="stopAtShortest">Indicates if the encoding should stop at the shortest input file.</param>
|
||||
/// <returns>Success state</returns>
|
||||
public VideoInfo ReplaceAudio(VideoInfo source, FileInfo audio, FileInfo output, bool stopAtShortest = false)
|
||||
{
|
||||
FFMpegHelper.ConversionExceptionCheck(source.ToFileInfo(), output);
|
||||
FFMpegHelper.InputsExistExceptionCheck(audio);
|
||||
FFMpegHelper.ConversionSizeExceptionCheck(source);
|
||||
FFMpegHelper.ExtensionExceptionCheck(output, source.Extension);
|
||||
|
||||
var container = new ArgumentContainer(
|
||||
new InputArgument(source.FullName, audio.FullName),
|
||||
new CopyArgument(),
|
||||
new AudioCodecArgument(AudioCodec.Aac, AudioQuality.Hd),
|
||||
new ShortestArgument(stopAtShortest),
|
||||
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)
|
||||
{
|
||||
var output = ((OutputArgument) arguments[typeof(OutputArgument)]).GetAsFileInfo();
|
||||
var sources = ((InputArgument) arguments[typeof(InputArgument)]).GetAsVideoInfo();
|
||||
|
||||
// Sum duration of all sources
|
||||
_totalTime = TimeSpan.Zero;
|
||||
foreach (var source in sources)
|
||||
_totalTime += source.Duration;
|
||||
|
||||
if (!RunProcess(arguments, output))
|
||||
{
|
||||
throw new FFMpegException(FFMpegExceptionType.Operation, "Could not replace the video audio.");
|
||||
}
|
||||
|
||||
_totalTime = TimeSpan.MinValue;
|
||||
|
||||
return new VideoInfo(output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the associated process is still alive/running.
|
||||
/// </summary>
|
||||
public bool IsWorking => _instance.Started;
|
||||
|
||||
/// <summary>
|
||||
/// Stops any current job that FFMpeg is running.
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
if (IsWorking)
|
||||
{
|
||||
_instance.SendInput("q").Wait();
|
||||
}
|
||||
}
|
||||
|
||||
#region Private Members & Methods
|
||||
|
||||
private readonly string _ffmpegPath;
|
||||
private TimeSpan _totalTime;
|
||||
|
||||
private volatile StringBuilder _errorOutput = new StringBuilder();
|
||||
|
||||
private bool RunProcess(ArgumentContainer container, FileInfo output)
|
||||
{
|
||||
_instance?.Dispose();
|
||||
var arguments = ArgumentBuilder.BuildArguments(container);
|
||||
|
||||
_instance = new Instance(_ffmpegPath, arguments);
|
||||
_instance.DataReceived += OutputData;
|
||||
var exitCode = _instance.BlockUntilFinished();
|
||||
|
||||
if (!File.Exists(output.FullName) || new FileInfo(output.FullName).Length == 0)
|
||||
{
|
||||
throw new FFMpegException(FFMpegExceptionType.Process, _errorOutput);
|
||||
}
|
||||
|
||||
return exitCode == 0;
|
||||
}
|
||||
|
||||
private void Cleanup(IEnumerable<string> pathList)
|
||||
{
|
||||
foreach (var path in pathList)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Regex ProgressRegex = new Regex(@"\w\w:\w\w:\w\w", RegexOptions.Compiled);
|
||||
private Instance _instance;
|
||||
|
||||
private void OutputData(object sender, (DataType Type, string Data) msg)
|
||||
{
|
||||
var (type, data) = msg;
|
||||
|
||||
if (data == null) return;
|
||||
if (type == DataType.Error)
|
||||
{
|
||||
_errorOutput.AppendLine(data);
|
||||
return;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
Trace.WriteLine(data);
|
||||
#endif
|
||||
|
||||
if (OnProgress == null) return;
|
||||
if (!data.Contains("frame")) return;
|
||||
|
||||
var match = ProgressRegex.Match(data);
|
||||
if (!match.Success) return;
|
||||
|
||||
var processed = TimeSpan.Parse(match.Value, CultureInfo.InvariantCulture);
|
||||
var percentage = Math.Round(processed.TotalSeconds / _totalTime.TotalSeconds * 100, 2);
|
||||
OnProgress(percentage);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
using FFMpegCore.FFMPEG.Exceptions;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Instances;
|
||||
|
||||
namespace FFMpegCore.FFMPEG
|
||||
{
|
||||
public class FFMpegOptions
|
||||
{
|
||||
private static readonly string ConfigFile = Path.Combine(".", "ffmpeg.config.json");
|
||||
private static readonly string DefaultRoot = Path.Combine(".", "FFMPEG", "bin");
|
||||
|
||||
public static FFMpegOptions Options { get; private set; } = new FFMpegOptions();
|
||||
|
||||
public static void Configure(FFMpegOptions options)
|
||||
{
|
||||
Options = options;
|
||||
}
|
||||
|
||||
static FFMpegOptions()
|
||||
{
|
||||
if (File.Exists(ConfigFile))
|
||||
{
|
||||
Options = JsonConvert.DeserializeObject<FFMpegOptions>(File.ReadAllText(ConfigFile));
|
||||
}
|
||||
}
|
||||
|
||||
public string RootDirectory { get; set; } = DefaultRoot;
|
||||
|
||||
public string FFmpegBinary => FFBinary("FFMpeg");
|
||||
|
||||
public string FFProbeBinary => FFBinary("FFProbe");
|
||||
|
||||
private static string FFBinary(string name)
|
||||
{
|
||||
var ffName = name.ToLowerInvariant();
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
ffName += ".exe";
|
||||
|
||||
var target = Environment.Is64BitProcess ? "x64" : "x86";
|
||||
if (Directory.Exists(Path.Combine(Options.RootDirectory, target)))
|
||||
{
|
||||
ffName = Path.Combine(target, ffName);
|
||||
}
|
||||
|
||||
return Path.Combine(Options.RootDirectory, ffName);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FFMpegCore.FFMPEG
|
||||
{
|
||||
internal class Stream
|
||||
{
|
||||
[JsonProperty("index")]
|
||||
internal int Index { get; set; }
|
||||
|
||||
[JsonProperty("codec_name")]
|
||||
internal string CodecName { get; set; }
|
||||
|
||||
[JsonProperty("bit_rate")]
|
||||
internal string BitRate { get; set; }
|
||||
|
||||
[JsonProperty("profile")]
|
||||
internal string Profile { get; set; }
|
||||
|
||||
[JsonProperty("codec_type")]
|
||||
internal string CodecType { get; set; }
|
||||
|
||||
[JsonProperty("width")]
|
||||
internal int Width { get; set; }
|
||||
|
||||
[JsonProperty("height")]
|
||||
internal int Height { get; set; }
|
||||
|
||||
[JsonProperty("duration")]
|
||||
internal string Duration { get; set; }
|
||||
|
||||
[JsonProperty("r_frame_rate")]
|
||||
internal string FrameRate { get; set; }
|
||||
|
||||
[JsonProperty("tags")]
|
||||
internal Tags Tags { get; set; }
|
||||
}
|
||||
|
||||
internal class Tags
|
||||
{
|
||||
[JsonProperty("DURATION")]
|
||||
internal string Duration { get; set; }
|
||||
}
|
||||
|
||||
internal class FFMpegStreamMetadata
|
||||
{
|
||||
[JsonProperty("streams")]
|
||||
internal List<Stream> Streams { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,135 +0,0 @@
|
|||
using FFMpegCore.FFMPEG.Exceptions;
|
||||
using FFMpegCore.Helpers;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using Instances;
|
||||
|
||||
namespace FFMpegCore.FFMPEG
|
||||
{
|
||||
public sealed class FFProbe
|
||||
{
|
||||
static readonly double BITS_TO_MB = 1024 * 1024 * 8;
|
||||
private readonly string _ffprobePath;
|
||||
|
||||
public FFProbe(): base()
|
||||
{
|
||||
FFProbeHelper.RootExceptionCheck(FFMpegOptions.Options.RootDirectory);
|
||||
_ffprobePath = FFMpegOptions.Options.FFProbeBinary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Probes the targeted video file and retrieves all available details.
|
||||
/// </summary>
|
||||
/// <param name="source">Source video file.</param>
|
||||
/// <returns>A video info object containing all details necessary.</returns>
|
||||
public VideoInfo ParseVideoInfo(string source)
|
||||
{
|
||||
return ParseVideoInfo(new VideoInfo(source));
|
||||
}
|
||||
/// <summary>
|
||||
/// Probes the targeted video file asynchronously and retrieves all available details.
|
||||
/// </summary>
|
||||
/// <param name="source">Source video file.</param>
|
||||
/// <returns>A task for the video info object containing all details necessary.</returns>
|
||||
public Task<VideoInfo> ParseVideoInfoAsync(string source)
|
||||
{
|
||||
return ParseVideoInfoAsync(new VideoInfo(source));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Probes the targeted video file and retrieves all available details.
|
||||
/// </summary>
|
||||
/// <param name="info">Source video file.</param>
|
||||
/// <returns>A video info object containing all details necessary.</returns>
|
||||
public VideoInfo ParseVideoInfo(VideoInfo info)
|
||||
{
|
||||
var instance = new Instance(_ffprobePath, BuildFFProbeArguments(info));
|
||||
instance.BlockUntilFinished();
|
||||
var output = string.Join("", instance.OutputData);
|
||||
return ParseVideoInfoInternal(info, output);
|
||||
}
|
||||
/// <summary>
|
||||
/// Probes the targeted video file asynchronously and retrieves all available details.
|
||||
/// </summary>
|
||||
/// <param name="info">Source video file.</param>
|
||||
/// <returns>A video info object containing all details necessary.</returns>
|
||||
public async Task<VideoInfo> ParseVideoInfoAsync(VideoInfo info)
|
||||
{
|
||||
var instance = new Instance(_ffprobePath, BuildFFProbeArguments(info));
|
||||
await instance.FinishedRunning();
|
||||
var output = string.Join("", instance.OutputData);
|
||||
return ParseVideoInfoInternal(info, output);
|
||||
}
|
||||
|
||||
private static string BuildFFProbeArguments(VideoInfo info) =>
|
||||
$"-v quiet -print_format json -show_streams \"{info.FullName}\"";
|
||||
|
||||
private VideoInfo ParseVideoInfoInternal(VideoInfo info, string probeOutput)
|
||||
{
|
||||
var metadata = JsonConvert.DeserializeObject<FFMpegStreamMetadata>(probeOutput);
|
||||
|
||||
if (metadata.Streams == null || metadata.Streams.Count == 0)
|
||||
{
|
||||
throw new FFMpegException(FFMpegExceptionType.File, $"No video or audio streams could be detected. Source: ${info.FullName}");
|
||||
}
|
||||
|
||||
var video = metadata.Streams.Find(s => s.CodecType == "video");
|
||||
var audio = metadata.Streams.Find(s => s.CodecType == "audio");
|
||||
|
||||
double videoSize = 0d;
|
||||
double audioSize = 0d;
|
||||
|
||||
string sDuration = (video ?? audio).Duration;
|
||||
TimeSpan duration = TimeSpan.Zero;
|
||||
if (sDuration != null)
|
||||
{
|
||||
duration = TimeSpan.FromSeconds(double.TryParse(sDuration, NumberStyles.Any, CultureInfo.InvariantCulture, out var output) ? output : 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
sDuration = (video ?? audio).Tags.Duration;
|
||||
if (sDuration != null)
|
||||
TimeSpan.TryParse(sDuration.Remove(sDuration.LastIndexOf('.') + 8), CultureInfo.InvariantCulture, out duration); // TimeSpan fractions only allow up to 7 digits
|
||||
}
|
||||
info.Duration = duration;
|
||||
|
||||
if (video != null)
|
||||
{
|
||||
var bitRate = Convert.ToDouble(video.BitRate, CultureInfo.InvariantCulture);
|
||||
var fr = video.FrameRate.Split('/');
|
||||
var commonDenominator = FFProbeHelper.Gcd(video.Width, video.Height);
|
||||
|
||||
videoSize = bitRate * duration.TotalSeconds / BITS_TO_MB;
|
||||
|
||||
info.VideoFormat = video.CodecName;
|
||||
info.Width = video.Width;
|
||||
info.Height = video.Height;
|
||||
info.FrameRate = Math.Round(
|
||||
Convert.ToDouble(fr[0], CultureInfo.InvariantCulture) /
|
||||
Convert.ToDouble(fr[1], CultureInfo.InvariantCulture),
|
||||
3);
|
||||
info.Ratio = video.Width / commonDenominator + ":" + video.Height / commonDenominator;
|
||||
} else
|
||||
{
|
||||
info.VideoFormat = "none";
|
||||
}
|
||||
|
||||
if (audio != null)
|
||||
{
|
||||
var bitRate = Convert.ToDouble(audio.BitRate, CultureInfo.InvariantCulture);
|
||||
info.AudioFormat = audio.CodecName;
|
||||
audioSize = bitRate * duration.TotalSeconds / BITS_TO_MB;
|
||||
} else
|
||||
{
|
||||
info.AudioFormat = "none";
|
||||
|
||||
}
|
||||
|
||||
info.Size = Math.Round(videoSize + audioSize, 2);
|
||||
|
||||
return info;
|
||||
}
|
||||
}
|
||||
}
|
20
FFMpegCore/FFMpeg/Arguments/AudioBitrateArgument.cs
Normal file
20
FFMpegCore/FFMpeg/Arguments/AudioBitrateArgument.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents parameter of audio codec and it's quality
|
||||
/// </summary>
|
||||
public class AudioBitrateArgument : IArgument
|
||||
{
|
||||
public readonly int Bitrate;
|
||||
public AudioBitrateArgument(AudioQuality value) : this((int)value) { }
|
||||
public AudioBitrateArgument(int bitrate)
|
||||
{
|
||||
Bitrate = bitrate;
|
||||
}
|
||||
|
||||
|
||||
public string Text => $"-b:a {Bitrate}k";
|
||||
}
|
||||
}
|
28
FFMpegCore/FFMpeg/Arguments/AudioCodecArgument.cs
Normal file
28
FFMpegCore/FFMpeg/Arguments/AudioCodecArgument.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using FFMpegCore.Enums;
|
||||
using FFMpegCore.Exceptions;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents parameter of audio codec and it's quality
|
||||
/// </summary>
|
||||
public class AudioCodecArgument : IArgument
|
||||
{
|
||||
public readonly string AudioCodec;
|
||||
|
||||
public AudioCodecArgument(Codec audioCodec)
|
||||
{
|
||||
if (audioCodec.Type != CodecType.Audio)
|
||||
throw new FFMpegException(FFMpegExceptionType.Operation, $"Codec \"{audioCodec.Name}\" is not an audio codec");
|
||||
|
||||
AudioCodec = audioCodec.Name;
|
||||
}
|
||||
|
||||
public AudioCodecArgument(string audioCodec)
|
||||
{
|
||||
AudioCodec = audioCodec;
|
||||
}
|
||||
|
||||
public string Text => $"-c:a {AudioCodec.ToString().ToLowerInvariant()}";
|
||||
}
|
||||
}
|
16
FFMpegCore/FFMpeg/Arguments/AudioSamplingRateArgument.cs
Normal file
16
FFMpegCore/FFMpeg/Arguments/AudioSamplingRateArgument.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Audio sampling rate argument. Defaults to 48000 (Hz)
|
||||
/// </summary>
|
||||
public class AudioSamplingRateArgument : IArgument
|
||||
{
|
||||
public readonly int SamplingRate;
|
||||
public AudioSamplingRateArgument(int samplingRate = 48000)
|
||||
{
|
||||
SamplingRate = samplingRate;
|
||||
}
|
||||
|
||||
public string Text => $"-ar {SamplingRate}";
|
||||
}
|
||||
}
|
26
FFMpegCore/FFMpeg/Arguments/BitStreamFilterArgument.cs
Normal file
26
FFMpegCore/FFMpeg/Arguments/BitStreamFilterArgument.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents parameter of bitstream filter
|
||||
/// </summary>
|
||||
public class BitStreamFilterArgument : IArgument
|
||||
{
|
||||
public readonly Channel Channel;
|
||||
public readonly Filter Filter;
|
||||
|
||||
public BitStreamFilterArgument(Channel channel, Filter filter)
|
||||
{
|
||||
Channel = channel;
|
||||
Filter = filter;
|
||||
}
|
||||
|
||||
public string Text => Channel switch
|
||||
{
|
||||
Channel.Audio => $"-bsf:a {Filter.ToString().ToLowerInvariant()}",
|
||||
Channel.Video => $"-bsf:v {Filter.ToString().ToLowerInvariant()}",
|
||||
_ => string.Empty
|
||||
};
|
||||
}
|
||||
}
|
26
FFMpegCore/FFMpeg/Arguments/ConcatArgument.cs
Normal file
26
FFMpegCore/FFMpeg/Arguments/ConcatArgument.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Represents parameter of concat argument
|
||||
/// Used for creating video from multiple images or videos
|
||||
/// </summary>
|
||||
public class ConcatArgument : IInputArgument
|
||||
{
|
||||
public readonly IEnumerable<string> Values;
|
||||
public ConcatArgument(IEnumerable<string> values)
|
||||
{
|
||||
Values = values;
|
||||
}
|
||||
|
||||
public void Pre() { }
|
||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
public void Post() { }
|
||||
|
||||
public string Text => $"-i \"concat:{string.Join(@"|", Values)}\"";
|
||||
}
|
||||
}
|
24
FFMpegCore/FFMpeg/Arguments/ConstantRateFactorArgument.cs
Normal file
24
FFMpegCore/FFMpeg/Arguments/ConstantRateFactorArgument.cs
Normal file
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Constant Rate Factor (CRF) argument
|
||||
/// </summary>
|
||||
public class ConstantRateFactorArgument : IArgument
|
||||
{
|
||||
public readonly int Crf;
|
||||
|
||||
public ConstantRateFactorArgument(int crf)
|
||||
{
|
||||
if (crf < 0 || crf > 63)
|
||||
{
|
||||
throw new ArgumentException("Argument is outside range (0 - 63)", nameof(crf));
|
||||
}
|
||||
|
||||
Crf = crf;
|
||||
}
|
||||
|
||||
public string Text => $"-crf {Crf}";
|
||||
}
|
||||
}
|
24
FFMpegCore/FFMpeg/Arguments/CopyArgument.cs
Normal file
24
FFMpegCore/FFMpeg/Arguments/CopyArgument.cs
Normal file
|
@ -0,0 +1,24 @@
|
|||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents parameter of copy parameter
|
||||
/// Defines if channel (audio, video or both) should be copied to output file
|
||||
/// </summary>
|
||||
public class CopyArgument : IArgument
|
||||
{
|
||||
public readonly Channel Channel;
|
||||
public CopyArgument(Channel channel = Channel.Both)
|
||||
{
|
||||
Channel = channel;
|
||||
}
|
||||
|
||||
public string Text => Channel switch
|
||||
{
|
||||
Channel.Audio => "-c:a copy",
|
||||
Channel.Video => "-c:v copy",
|
||||
_ => "-c copy"
|
||||
};
|
||||
}
|
||||
}
|
14
FFMpegCore/FFMpeg/Arguments/CustomArgument.cs
Normal file
14
FFMpegCore/FFMpeg/Arguments/CustomArgument.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class CustomArgument : IArgument
|
||||
{
|
||||
public readonly string Argument;
|
||||
|
||||
public CustomArgument(string argument)
|
||||
{
|
||||
Argument = argument;
|
||||
}
|
||||
|
||||
public string Text => Argument ?? string.Empty;
|
||||
}
|
||||
}
|
29
FFMpegCore/FFMpeg/Arguments/DemuxConcatArgument.cs
Normal file
29
FFMpegCore/FFMpeg/Arguments/DemuxConcatArgument.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents parameter of concat argument
|
||||
/// Used for creating video from multiple images or videos
|
||||
/// </summary>
|
||||
public class DemuxConcatArgument : IInputArgument
|
||||
{
|
||||
public readonly IEnumerable<string> Values;
|
||||
public DemuxConcatArgument(IEnumerable<string> values)
|
||||
{
|
||||
Values = values.Select(value => $"file '{value}'");
|
||||
}
|
||||
private readonly string _tempFileName = Path.Combine(FFMpegOptions.Options.TempDirectory, Guid.NewGuid() + ".txt");
|
||||
|
||||
public void Pre() => File.WriteAllLines(_tempFileName, Values);
|
||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
public void Post() => File.Delete(_tempFileName);
|
||||
|
||||
public string Text => $"-f concat -safe 0 -i \"{_tempFileName}\"";
|
||||
}
|
||||
}
|
27
FFMpegCore/FFMpeg/Arguments/DisableChannelArgument.cs
Normal file
27
FFMpegCore/FFMpeg/Arguments/DisableChannelArgument.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
using FFMpegCore.Enums;
|
||||
using FFMpegCore.Exceptions;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents cpu speed parameter
|
||||
/// </summary>
|
||||
public class DisableChannelArgument : IArgument
|
||||
{
|
||||
public readonly Channel Channel;
|
||||
|
||||
public DisableChannelArgument(Channel channel)
|
||||
{
|
||||
if (channel == Channel.Both)
|
||||
throw new FFMpegException(FFMpegExceptionType.Conversion, "Cannot disable both channels");
|
||||
Channel = channel;
|
||||
}
|
||||
|
||||
public string Text => Channel switch
|
||||
{
|
||||
Channel.Video => "-vn",
|
||||
Channel.Audio => "-an",
|
||||
_ => string.Empty
|
||||
};
|
||||
}
|
||||
}
|
61
FFMpegCore/FFMpeg/Arguments/DrawTextArgument.cs
Normal file
61
FFMpegCore/FFMpeg/Arguments/DrawTextArgument.cs
Normal file
|
@ -0,0 +1,61 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Drawtext video filter argument
|
||||
/// </summary>
|
||||
public class DrawTextArgument : IArgument
|
||||
{
|
||||
public readonly DrawTextOptions Options;
|
||||
|
||||
public DrawTextArgument(DrawTextOptions options)
|
||||
{
|
||||
Options = options;
|
||||
}
|
||||
|
||||
public string Text => $"-vf drawtext=\"{Options.TextInternal}\"";
|
||||
}
|
||||
|
||||
public class DrawTextOptions
|
||||
{
|
||||
public readonly string Text;
|
||||
public readonly string Font;
|
||||
public readonly List<(string key, string value)> Parameters;
|
||||
|
||||
public static DrawTextOptions Create(string text, string font)
|
||||
{
|
||||
return new DrawTextOptions(text, font, new List<(string, string)>());
|
||||
}
|
||||
public static DrawTextOptions Create(string text, string font, params (string key, string value)[] parameters)
|
||||
{
|
||||
return new DrawTextOptions(text, font, parameters);
|
||||
}
|
||||
|
||||
internal string TextInternal => string.Join(":", new[] {("text", Text), ("fontfile", Font)}.Concat(Parameters).Select(FormatArgumentPair));
|
||||
|
||||
private static string FormatArgumentPair((string key, string value) pair)
|
||||
{
|
||||
return $"{pair.key}={EncloseIfContainsSpace(pair.value)}";
|
||||
}
|
||||
|
||||
private static string EncloseIfContainsSpace(string input)
|
||||
{
|
||||
return input.Contains(" ") ? $"'{input}'" : input;
|
||||
}
|
||||
|
||||
private DrawTextOptions(string text, string font, IEnumerable<(string, string)> parameters)
|
||||
{
|
||||
Text = text;
|
||||
Font = font;
|
||||
Parameters = parameters.ToList();
|
||||
}
|
||||
|
||||
public DrawTextOptions WithParameter(string key, string value)
|
||||
{
|
||||
Parameters.Add((key, value));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
18
FFMpegCore/FFMpeg/Arguments/DurationArgument.cs
Normal file
18
FFMpegCore/FFMpeg/Arguments/DurationArgument.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents duration parameter
|
||||
/// </summary>
|
||||
public class DurationArgument : IArgument
|
||||
{
|
||||
public readonly TimeSpan? Duration;
|
||||
public DurationArgument(TimeSpan? duration)
|
||||
{
|
||||
Duration = duration;
|
||||
}
|
||||
|
||||
public string Text => !Duration.HasValue ? string.Empty : $"-t {Duration.Value}";
|
||||
}
|
||||
}
|
10
FFMpegCore/FFMpeg/Arguments/FaststartArgument.cs
Normal file
10
FFMpegCore/FFMpeg/Arguments/FaststartArgument.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Faststart argument - for moving moov atom to the start of file
|
||||
/// </summary>
|
||||
public class FaststartArgument : IArgument
|
||||
{
|
||||
public string Text => "-movflags faststart";
|
||||
}
|
||||
}
|
23
FFMpegCore/FFMpeg/Arguments/ForceFormatArgument.cs
Normal file
23
FFMpegCore/FFMpeg/Arguments/ForceFormatArgument.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents force format parameter
|
||||
/// </summary>
|
||||
public class ForceFormatArgument : IArgument
|
||||
{
|
||||
private readonly string _format;
|
||||
public ForceFormatArgument(string format)
|
||||
{
|
||||
_format = format;
|
||||
}
|
||||
|
||||
public ForceFormatArgument(ContainerFormat format)
|
||||
{
|
||||
_format = format.Name;
|
||||
}
|
||||
|
||||
public string Text => $"-f {_format}";
|
||||
}
|
||||
}
|
17
FFMpegCore/FFMpeg/Arguments/ForcePixelFormat.cs
Normal file
17
FFMpegCore/FFMpeg/Arguments/ForcePixelFormat.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class ForcePixelFormat : IArgument
|
||||
{
|
||||
public string PixelFormat { get; }
|
||||
public string Text => $"-pix_fmt {PixelFormat}";
|
||||
|
||||
public ForcePixelFormat(string format)
|
||||
{
|
||||
PixelFormat = format;
|
||||
}
|
||||
|
||||
public ForcePixelFormat(PixelFormat format) : this(format.Name) { }
|
||||
}
|
||||
}
|
16
FFMpegCore/FFMpeg/Arguments/FrameOutputCountArgument.cs
Normal file
16
FFMpegCore/FFMpeg/Arguments/FrameOutputCountArgument.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents frame output count parameter
|
||||
/// </summary>
|
||||
public class FrameOutputCountArgument : IArgument
|
||||
{
|
||||
public readonly int Frames;
|
||||
public FrameOutputCountArgument(int frames)
|
||||
{
|
||||
Frames = frames;
|
||||
}
|
||||
|
||||
public string Text => $"-vframes {Frames}";
|
||||
}
|
||||
}
|
17
FFMpegCore/FFMpeg/Arguments/FrameRateArgument.cs
Normal file
17
FFMpegCore/FFMpeg/Arguments/FrameRateArgument.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents frame rate parameter
|
||||
/// </summary>
|
||||
public class FrameRateArgument : IArgument
|
||||
{
|
||||
public readonly double Framerate;
|
||||
|
||||
public FrameRateArgument(double framerate)
|
||||
{
|
||||
Framerate = framerate;
|
||||
}
|
||||
|
||||
public string Text => $"-r {Framerate.ToString(System.Globalization.CultureInfo.InvariantCulture)}";
|
||||
}
|
||||
}
|
18
FFMpegCore/FFMpeg/Arguments/HardwareAccelerationArgument.cs
Normal file
18
FFMpegCore/FFMpeg/Arguments/HardwareAccelerationArgument.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class HardwareAccelerationArgument : IArgument
|
||||
{
|
||||
public HardwareAccelerationDevice HardwareAccelerationDevice { get; }
|
||||
|
||||
public HardwareAccelerationArgument(HardwareAccelerationDevice hardwareAccelerationDevice)
|
||||
{
|
||||
HardwareAccelerationDevice = hardwareAccelerationDevice;
|
||||
}
|
||||
|
||||
public string Text => HardwareAccelerationDevice != HardwareAccelerationDevice.Auto
|
||||
? $"-hwaccel {HardwareAccelerationDevice.ToString().ToLower()}"
|
||||
: "-hwaccel";
|
||||
}
|
||||
}
|
10
FFMpegCore/FFMpeg/Arguments/IArgument.cs
Normal file
10
FFMpegCore/FFMpeg/Arguments/IArgument.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public interface IArgument
|
||||
{
|
||||
/// <summary>
|
||||
/// The textual representation of the argument
|
||||
/// </summary>
|
||||
string Text { get; }
|
||||
}
|
||||
}
|
6
FFMpegCore/FFMpeg/Arguments/IInputArgument.cs
Normal file
6
FFMpegCore/FFMpeg/Arguments/IInputArgument.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public interface IInputArgument : IInputOutputArgument
|
||||
{
|
||||
}
|
||||
}
|
12
FFMpegCore/FFMpeg/Arguments/IInputOutputArgument.cs
Normal file
12
FFMpegCore/FFMpeg/Arguments/IInputOutputArgument.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public interface IInputOutputArgument : IArgument
|
||||
{
|
||||
void Pre();
|
||||
Task During(CancellationToken cancellationToken = default);
|
||||
void Post();
|
||||
}
|
||||
}
|
6
FFMpegCore/FFMpeg/Arguments/IOutputArgument.cs
Normal file
6
FFMpegCore/FFMpeg/Arguments/IOutputArgument.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public interface IOutputArgument : IInputOutputArgument
|
||||
{
|
||||
}
|
||||
}
|
34
FFMpegCore/FFMpeg/Arguments/InputArgument.cs
Normal file
34
FFMpegCore/FFMpeg/Arguments/InputArgument.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents input parameter
|
||||
/// </summary>
|
||||
public class InputArgument : IInputArgument
|
||||
{
|
||||
public readonly bool VerifyExists;
|
||||
public readonly string FilePath;
|
||||
|
||||
public InputArgument(bool verifyExists, string filePaths)
|
||||
{
|
||||
VerifyExists = verifyExists;
|
||||
FilePath = filePaths;
|
||||
}
|
||||
|
||||
public InputArgument(string path, bool verifyExists) : this(verifyExists, path) { }
|
||||
|
||||
public void Pre()
|
||||
{
|
||||
if (VerifyExists && !File.Exists(FilePath))
|
||||
throw new FileNotFoundException("Input file not found", FilePath);
|
||||
}
|
||||
|
||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
public void Post() { }
|
||||
|
||||
public string Text => $"-i \"{FilePath}\"";
|
||||
}
|
||||
}
|
30
FFMpegCore/FFMpeg/Arguments/InputPipeArgument.cs
Normal file
30
FFMpegCore/FFMpeg/Arguments/InputPipeArgument.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
using System.IO.Pipes;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FFMpegCore.Pipes;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents input parameter for a named pipe
|
||||
/// </summary>
|
||||
public class InputPipeArgument : PipeArgument, IInputArgument
|
||||
{
|
||||
public readonly IPipeSource Writer;
|
||||
|
||||
public InputPipeArgument(IPipeSource writer) : base(PipeDirection.Out)
|
||||
{
|
||||
Writer = writer;
|
||||
}
|
||||
|
||||
public override string Text => $"-y {Writer.GetFormat()} -i \"{PipePath}\"";
|
||||
|
||||
protected override async Task ProcessDataAsync(CancellationToken token)
|
||||
{
|
||||
await Pipe.WaitForConnectionAsync(token).ConfigureAwait(false);
|
||||
if (!Pipe.IsConnected)
|
||||
throw new TaskCanceledException();
|
||||
await Writer.CopyAsync(Pipe, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
16
FFMpegCore/FFMpeg/Arguments/LoopArgument.cs
Normal file
16
FFMpegCore/FFMpeg/Arguments/LoopArgument.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents loop parameter
|
||||
/// </summary>
|
||||
public class LoopArgument : IArgument
|
||||
{
|
||||
public readonly int Times;
|
||||
public LoopArgument(int times)
|
||||
{
|
||||
Times = times;
|
||||
}
|
||||
|
||||
public string Text => $"-loop {Times}";
|
||||
}
|
||||
}
|
39
FFMpegCore/FFMpeg/Arguments/OutputArgument.cs
Normal file
39
FFMpegCore/FFMpeg/Arguments/OutputArgument.cs
Normal file
|
@ -0,0 +1,39 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FFMpegCore.Exceptions;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents output parameter
|
||||
/// </summary>
|
||||
public class OutputArgument : IOutputArgument
|
||||
{
|
||||
public readonly string Path;
|
||||
public readonly bool Overwrite;
|
||||
|
||||
public OutputArgument(string path, bool overwrite = true)
|
||||
{
|
||||
Path = path;
|
||||
Overwrite = overwrite;
|
||||
}
|
||||
|
||||
public void Pre()
|
||||
{
|
||||
if (!Overwrite && File.Exists(Path))
|
||||
throw new FFMpegException(FFMpegExceptionType.File, "Output file already exists and overwrite is disabled");
|
||||
}
|
||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
public void Post()
|
||||
{
|
||||
}
|
||||
|
||||
public OutputArgument(FileInfo value) : this(value.FullName) { }
|
||||
|
||||
public OutputArgument(Uri value) : this(value.AbsolutePath) { }
|
||||
|
||||
public string Text => $"\"{Path}\"{(Overwrite ? " -y" : string.Empty)}";
|
||||
}
|
||||
}
|
27
FFMpegCore/FFMpeg/Arguments/OutputPipeArgument.cs
Normal file
27
FFMpegCore/FFMpeg/Arguments/OutputPipeArgument.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
using System.IO.Pipes;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FFMpegCore.Pipes;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class OutputPipeArgument : PipeArgument, IOutputArgument
|
||||
{
|
||||
public readonly IPipeSink Reader;
|
||||
|
||||
public OutputPipeArgument(IPipeSink reader) : base(PipeDirection.In)
|
||||
{
|
||||
Reader = reader;
|
||||
}
|
||||
|
||||
public override string Text => $"\"{PipePath}\" -y";
|
||||
|
||||
protected override async Task ProcessDataAsync(CancellationToken token)
|
||||
{
|
||||
await Pipe.WaitForConnectionAsync(token).ConfigureAwait(false);
|
||||
if (!Pipe.IsConnected)
|
||||
throw new TaskCanceledException();
|
||||
await Reader.CopyAsync(Pipe, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
11
FFMpegCore/FFMpeg/Arguments/OverwriteArgument.cs
Normal file
11
FFMpegCore/FFMpeg/Arguments/OverwriteArgument.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents overwrite parameter
|
||||
/// If output file should be overwritten if exists
|
||||
/// </summary>
|
||||
public class OverwriteArgument : IArgument
|
||||
{
|
||||
public string Text => "-y";
|
||||
}
|
||||
}
|
52
FFMpegCore/FFMpeg/Arguments/PipeArgument.cs
Normal file
52
FFMpegCore/FFMpeg/Arguments/PipeArgument.cs
Normal file
|
@ -0,0 +1,52 @@
|
|||
using System;
|
||||
using System.IO.Pipes;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FFMpegCore.Pipes;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public abstract class PipeArgument
|
||||
{
|
||||
private string PipeName { get; }
|
||||
public string PipePath => PipeHelpers.GetPipePath(PipeName);
|
||||
|
||||
protected NamedPipeServerStream Pipe { get; private set; } = null!;
|
||||
private readonly PipeDirection _direction;
|
||||
|
||||
protected PipeArgument(PipeDirection direction)
|
||||
{
|
||||
PipeName = PipeHelpers.GetUnqiuePipeName();
|
||||
_direction = direction;
|
||||
}
|
||||
|
||||
public void Pre()
|
||||
{
|
||||
if (Pipe != null)
|
||||
throw new InvalidOperationException("Pipe already has been opened");
|
||||
|
||||
Pipe = new NamedPipeServerStream(PipeName, _direction, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
|
||||
}
|
||||
|
||||
public void Post()
|
||||
{
|
||||
Pipe?.Dispose();
|
||||
Pipe = null!;
|
||||
}
|
||||
|
||||
public async Task During(CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ProcessDataAsync(cancellationToken);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
}
|
||||
Pipe.Disconnect();
|
||||
}
|
||||
|
||||
protected abstract Task ProcessDataAsync(CancellationToken token);
|
||||
public abstract string Text { get; }
|
||||
}
|
||||
}
|
10
FFMpegCore/FFMpeg/Arguments/RemoveMetadataArgument.cs
Normal file
10
FFMpegCore/FFMpeg/Arguments/RemoveMetadataArgument.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Remove metadata argument
|
||||
/// </summary>
|
||||
public class RemoveMetadataArgument : IArgument
|
||||
{
|
||||
public string Text => "-map_metadata -1";
|
||||
}
|
||||
}
|
26
FFMpegCore/FFMpeg/Arguments/ScaleArgument.cs
Normal file
26
FFMpegCore/FFMpeg/Arguments/ScaleArgument.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using System.Drawing;
|
||||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents scale parameter
|
||||
/// </summary>
|
||||
public class ScaleArgument : IArgument
|
||||
{
|
||||
public readonly Size? Size;
|
||||
public ScaleArgument(Size? size)
|
||||
{
|
||||
Size = size;
|
||||
}
|
||||
|
||||
public ScaleArgument(int width, int height) : this(new Size(width, height)) { }
|
||||
|
||||
public ScaleArgument(VideoSize videosize)
|
||||
{
|
||||
Size = videosize == VideoSize.Original ? new Size(-1, -1) : new Size(-1, (int)videosize);
|
||||
}
|
||||
|
||||
public virtual string Text => Size.HasValue ? $"-vf scale={Size.Value.Width}:{Size.Value.Height}" : string.Empty;
|
||||
}
|
||||
}
|
18
FFMpegCore/FFMpeg/Arguments/SeekArgument.cs
Normal file
18
FFMpegCore/FFMpeg/Arguments/SeekArgument.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents seek parameter
|
||||
/// </summary>
|
||||
public class SeekArgument : IArgument
|
||||
{
|
||||
public readonly TimeSpan? SeekTo;
|
||||
public SeekArgument(TimeSpan? seekTo)
|
||||
{
|
||||
SeekTo = seekTo;
|
||||
}
|
||||
|
||||
public string Text => !SeekTo.HasValue ? string.Empty : $"-ss {SeekTo.Value}";
|
||||
}
|
||||
}
|
17
FFMpegCore/FFMpeg/Arguments/ShortestArgument.cs
Normal file
17
FFMpegCore/FFMpeg/Arguments/ShortestArgument.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents shortest parameter
|
||||
/// </summary>
|
||||
public class ShortestArgument : IArgument
|
||||
{
|
||||
public readonly bool Shortest;
|
||||
|
||||
public ShortestArgument(bool shortest)
|
||||
{
|
||||
Shortest = shortest;
|
||||
}
|
||||
|
||||
public string Text => Shortest ? "-shortest" : string.Empty;
|
||||
}
|
||||
}
|
19
FFMpegCore/FFMpeg/Arguments/SizeArgument.cs
Normal file
19
FFMpegCore/FFMpeg/Arguments/SizeArgument.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System.Drawing;
|
||||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents size parameter
|
||||
/// </summary>
|
||||
public class SizeArgument : ScaleArgument
|
||||
{
|
||||
public SizeArgument(Size? value) : base(value) { }
|
||||
|
||||
public SizeArgument(VideoSize videosize) : base(videosize) { }
|
||||
|
||||
public SizeArgument(int width, int height) : base(width, height) { }
|
||||
|
||||
public override string Text => Size.HasValue ? $"-s {Size.Value.Width}x{Size.Value.Height}" : string.Empty;
|
||||
}
|
||||
}
|
19
FFMpegCore/FFMpeg/Arguments/SpeedPresetArgument.cs
Normal file
19
FFMpegCore/FFMpeg/Arguments/SpeedPresetArgument.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents speed parameter
|
||||
/// </summary>
|
||||
public class SpeedPresetArgument : IArgument
|
||||
{
|
||||
public readonly Speed Speed;
|
||||
|
||||
public SpeedPresetArgument(Speed speed)
|
||||
{
|
||||
Speed = speed;
|
||||
}
|
||||
|
||||
public string Text => $"-preset {Speed.ToString().ToLowerInvariant()}";
|
||||
}
|
||||
}
|
17
FFMpegCore/FFMpeg/Arguments/StartNumberArgument.cs
Normal file
17
FFMpegCore/FFMpeg/Arguments/StartNumberArgument.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents start number parameter
|
||||
/// </summary>
|
||||
public class StartNumberArgument : IArgument
|
||||
{
|
||||
public readonly int StartNumber;
|
||||
|
||||
public StartNumberArgument(int startNumber)
|
||||
{
|
||||
StartNumber = startNumber;
|
||||
}
|
||||
|
||||
public string Text => $"-start_number {StartNumber}";
|
||||
}
|
||||
}
|
21
FFMpegCore/FFMpeg/Arguments/ThreadsArgument.cs
Normal file
21
FFMpegCore/FFMpeg/Arguments/ThreadsArgument.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents threads parameter
|
||||
/// Number of threads used for video encoding
|
||||
/// </summary>
|
||||
public class ThreadsArgument : IArgument
|
||||
{
|
||||
public readonly int Threads;
|
||||
public ThreadsArgument(int threads)
|
||||
{
|
||||
Threads = threads;
|
||||
}
|
||||
|
||||
public ThreadsArgument(bool isMultiThreaded) : this(isMultiThreaded ? Environment.ProcessorCount : 1) { }
|
||||
|
||||
public string Text => $"-threads {Threads}";
|
||||
}
|
||||
}
|
22
FFMpegCore/FFMpeg/Arguments/TransposeArgument.cs
Normal file
22
FFMpegCore/FFMpeg/Arguments/TransposeArgument.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Transpose argument.
|
||||
/// 0 = 90CounterCLockwise and Vertical Flip (default)
|
||||
/// 1 = 90Clockwise
|
||||
/// 2 = 90CounterClockwise
|
||||
/// 3 = 90Clockwise and Vertical Flip
|
||||
/// </summary>
|
||||
public class TransposeArgument : IArgument
|
||||
{
|
||||
public readonly Transposition Transposition;
|
||||
public TransposeArgument(Transposition transposition)
|
||||
{
|
||||
Transposition = transposition;
|
||||
}
|
||||
|
||||
public string Text => $"-vf \"transpose={(int)Transposition}\"";
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue