FFMpegCore: port lib to .net standard

This commit is contained in:
Vlad Jerca 2019-02-08 12:19:40 +02:00
parent b52fbdf53d
commit c215191bf5
65 changed files with 4353 additions and 0 deletions

17
.gitattributes vendored Normal file
View file

@ -0,0 +1,17 @@
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain

343
.gitignore vendored Normal file
View file

@ -0,0 +1,343 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# ASP.NET Core default setup: bower directory is configured as wwwroot/lib/ and bower restore is true
**/wwwroot/lib/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
!FFMpegCore/FFMPEG/bin/**/*

BIN
.nuget/nuget.exe Normal file

Binary file not shown.

View file

@ -0,0 +1,223 @@
using FFMpegCore.FFMPEG.Arguments;
using FFMpegCore.FFMPEG.Enums;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.Tests
{
[TestClass]
public class ArgumentBuilderTests : BaseTest
{
List<string> concatFiles = new List<string>
{ "1.mp4", "2.mp4", "3.mp4", "4.mp4"};
FFArgumentBuilder builder;
public ArgumentBuilderTests() : base()
{
builder = new FFArgumentBuilder();
}
private string GetArgumentsString(params Argument[] args)
{
var container = new ArgumentsContainer();
container.Add(new OutputArgument("output.mp4"));
container.Add(new InputArgument("input.mp4"));
foreach (var a in args)
{
container.Add(a);
}
return builder.BuildArguments(container);
}
[TestMethod]
public void Builder_BuildString_IO_1()
{
var str = GetArgumentsString();
Assert.IsTrue(str == "-i \"input.mp4\" \"output.mp4\"");
}
[TestMethod]
public void Builder_BuildString_Scale()
{
var str = GetArgumentsString(new ScaleArgument(VideoSize.Hd));
Assert.IsTrue(str == "-i \"input.mp4\" -vf scale=-1:720 \"output.mp4\"");
}
[TestMethod]
public void Builder_BuildString_AudioCodec()
{
var str = GetArgumentsString(new AudioCodecArgument(AudioCodec.Aac, AudioQuality.Normal));
Assert.IsTrue(str == "-i \"input.mp4\" -codec:a aac -b:a 128k -strict experimental \"output.mp4\"");
}
[TestMethod]
public void Builder_BuildString_BitStream()
{
var str = GetArgumentsString(new BitStreamFilterArgument(Channel.Audio, Filter.H264_Mp4ToAnnexB));
Assert.IsTrue(str == "-i \"input.mp4\" -bsf:a h264_mp4toannexb \"output.mp4\"");
}
[TestMethod]
public void Builder_BuildString_Concat()
{
var container = new ArgumentsContainer();
container.Add(new OutputArgument("output.mp4"));
container.Add(new ConcatArgument(concatFiles));
var str = builder.BuildArguments(container);
Assert.IsTrue(str == "-i \"concat:1.mp4|2.mp4|3.mp4|4.mp4\" \"output.mp4\"");
}
[TestMethod]
public void Builder_BuildString_Copy_Audio()
{
var str = GetArgumentsString(new CopyArgument(Channel.Audio));
Assert.IsTrue(str == "-i \"input.mp4\" -c:a copy \"output.mp4\"");
}
[TestMethod]
public void Builder_BuildString_Copy_Video()
{
var str = GetArgumentsString(new CopyArgument(Channel.Video));
Assert.IsTrue(str == "-i \"input.mp4\" -c:v copy \"output.mp4\"");
}
[TestMethod]
public void Builder_BuildString_Copy_Both()
{
var str = GetArgumentsString(new CopyArgument(Channel.Both));
Assert.IsTrue(str == "-i \"input.mp4\" -c copy \"output.mp4\"");
}
[TestMethod]
public void Builder_BuildString_CpuSpeed()
{
var str = GetArgumentsString(new CpuSpeedArgument(10));
Assert.IsTrue(str == "-i \"input.mp4\" -quality good -cpu-used 10 -deadline realtime \"output.mp4\"");
}
[TestMethod]
public void Builder_BuildString_ForceFormat()
{
var str = GetArgumentsString(new ForceFormatArgument(VideoCodec.LibX264));
Assert.IsTrue(str == "-i \"input.mp4\" -f libx264 \"output.mp4\"");
}
[TestMethod]
public void Builder_BuildString_FrameOutputCount()
{
var str = GetArgumentsString(new FrameOutputCountArgument(50));
Assert.IsTrue(str == "-i \"input.mp4\" -vframes 50 \"output.mp4\"");
}
[TestMethod]
public void Builder_BuildString_FrameRate()
{
var str = GetArgumentsString(new FrameRateArgument(50));
Assert.IsTrue(str == "-i \"input.mp4\" -r 50 \"output.mp4\"");
}
[TestMethod]
public void Builder_BuildString_Loop()
{
var str = GetArgumentsString(new LoopArgument(50));
Assert.IsTrue(str == "-i \"input.mp4\" -loop 50 \"output.mp4\"");
}
[TestMethod]
public void Builder_BuildString_Seek()
{
var str = GetArgumentsString(new SeekArgument(TimeSpan.FromSeconds(10)));
Assert.IsTrue(str == "-i \"input.mp4\" -ss 00:00:10 \"output.mp4\"");
}
[TestMethod]
public void Builder_BuildString_Shortest()
{
var str = GetArgumentsString(new ShortestArgument(true));
Assert.IsTrue(str == "-i \"input.mp4\" -shortest \"output.mp4\"");
}
[TestMethod]
public void Builder_BuildString_Size()
{
var str = GetArgumentsString(new SizeArgument(1920, 1080));
Assert.IsTrue(str == "-i \"input.mp4\" -s 1920x1080 \"output.mp4\"");
}
[TestMethod]
public void Builder_BuildString_Speed()
{
var str = GetArgumentsString(new SpeedArgument(Speed.Fast));
Assert.IsTrue(str == "-i \"input.mp4\" -preset fast \"output.mp4\"");
}
[TestMethod]
public void Builder_BuildString_StartNumber()
{
var str = GetArgumentsString(new StartNumberArgument(50));
Assert.IsTrue(str == "-i \"input.mp4\" -start_number 50 \"output.mp4\"");
}
[TestMethod]
public void Builder_BuildString_Threads_1()
{
var str = GetArgumentsString(new ThreadsArgument(50));
Assert.IsTrue(str == "-i \"input.mp4\" -threads 50 \"output.mp4\"");
}
[TestMethod]
public void Builder_BuildString_Threads_2()
{
var str = GetArgumentsString(new ThreadsArgument(true));
Assert.IsTrue(str == $"-i \"input.mp4\" -threads {Environment.ProcessorCount} \"output.mp4\"");
}
[TestMethod]
public void Builder_BuildString_Codec()
{
var str = GetArgumentsString(new VideoCodecArgument(VideoCodec.LibX264));
Assert.IsTrue(str == "-i \"input.mp4\" -codec:v libx264 -pix_fmt yuv420p \"output.mp4\"");
}
[TestMethod]
public void Builder_BuildString_Codec_Override()
{
var str = GetArgumentsString(new VideoCodecArgument(VideoCodec.LibX264), new OverrideArgument());
Assert.IsTrue(str == "-i \"input.mp4\" -codec:v libx264 -pix_fmt yuv420p \"output.mp4\" -y");
}
}
}

View file

@ -0,0 +1,84 @@
using FFMpegCore.Enums;
using FFMpegCore.Tests.Resources;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.IO;
namespace FFMpegCore.Tests
{
[TestClass]
public class AudioTest : BaseTest
{
[TestMethod]
public void Audio_Remove()
{
var output = Input.OutputLocation(VideoType.Mp4);
try
{
Encoder.Mute(VideoInfo.FromFileInfo(Input), output);
Assert.IsTrue(File.Exists(output.FullName));
}
finally
{
if (File.Exists(output.FullName))
output.Delete();
}
}
[TestMethod]
public void Audio_Save()
{
var output = Input.OutputLocation(AudioType.Mp3);
try
{
Encoder.ExtractAudio(VideoInfo.FromFileInfo(Input), output);
Assert.IsTrue(File.Exists(output.FullName));
}
finally
{
if (File.Exists(output.FullName))
output.Delete();
}
}
[TestMethod]
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));
}
finally
{
if (File.Exists(output.FullName))
output.Delete();
}
}
[TestMethod]
public void Image_AddAudio()
{
var output = Input.OutputLocation(VideoType.Mp4);
try
{
var result = Encoder.PosterWithAudio(new FileInfo(VideoLibrary.LocalCover.FullName), VideoLibrary.LocalAudio, output);
Assert.IsTrue(result.Duration.TotalSeconds > 0);
Assert.IsTrue(result.Exists);
}
finally
{
if (File.Exists(output.FullName))
output.Delete();
}
}
}
}

View file

@ -0,0 +1,19 @@
using System.Configuration;
using System.IO;
using FFMpegCore.FFMPEG;
using FFMpegCore.Tests.Resources;
namespace FFMpegCore.Tests
{
public class BaseTest
{
protected FFMpeg Encoder;
protected FileInfo Input;
public BaseTest()
{
Encoder = new FFMpeg();
Input = VideoLibrary.LocalVideo;
}
}
}

View file

@ -0,0 +1,56 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<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" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FFMpegCore\FFMpegCore.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Resources\audio.mp3">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Resources\cover.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Resources\images\a.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Resources\images\b.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Resources\images\c.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Resources\images\d.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Resources\images\e.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Resources\images\f.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Resources\input.mp4">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Resources\mute.mp4">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,49 @@
using System;
using System.IO;
using FFMpegCore.Enums;
namespace FFMpegCore.Tests.Resources
{
public enum AudioType
{
Mp3
}
public enum ImageType
{
Png
}
public static class VideoLibrary
{
public static readonly FileInfo LocalVideo = new FileInfo(".\\Resources\\input.mp4");
public static readonly FileInfo LocalVideoNoAudio = new FileInfo(".\\Resources\\mute.mp4");
public static readonly FileInfo LocalAudio = new FileInfo(".\\Resources\\audio.mp3");
public static readonly FileInfo LocalCover = new FileInfo(".\\Resources\\cover.png");
public static readonly FileInfo ImageDirectory = new FileInfo(".\\Resources\\images");
public static readonly FileInfo ImageJoinOutput = new FileInfo(".\\Resources\\images\\output.mp4");
public static FileInfo OutputLocation(this FileInfo file, VideoType type)
{
return OutputLocation(file, type, "_converted");
}
public static FileInfo OutputLocation(this FileInfo file, AudioType type)
{
return OutputLocation(file, type, "_audio");
}
public static FileInfo OutputLocation(this FileInfo file, ImageType type)
{
return OutputLocation(file, type, "_screenshot");
}
public static FileInfo OutputLocation(this FileInfo file, Enum type, string keyword)
{
string originalLocation = file.Directory.FullName,
outputFile = file.Name.Replace(file.Extension, keyword + "." + type.ToString().ToLower());
return new FileInfo($"{originalLocation}\\{outputFile}");
}
}
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,282 @@
using FFMpegCore.Enums;
using FFMpegCore.FFMPEG;
using FFMpegCore.FFMPEG.Arguments;
using FFMpegCore.FFMPEG.Enums;
using FFMpegCore.Tests.Resources;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
namespace FFMpegCore.Tests
{
[TestClass]
public class VideoTest : BaseTest
{
public bool Convert(VideoType type, bool multithreaded = false, VideoSize size = VideoSize.Original)
{
var output = Input.OutputLocation(type);
try
{
var input = VideoInfo.FromFileInfo(Input);
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);
if (size == VideoSize.Original)
{
Assert.AreEqual(outputVideo.Width, input.Width);
Assert.AreEqual(outputVideo.Height, input.Height);
} else
{
Assert.AreNotEqual(outputVideo.Width, input.Width);
Assert.AreNotEqual(outputVideo.Height, input.Height);
Assert.AreEqual(outputVideo.Height, (int)size);
}
return File.Exists(output.FullName) &&
outputVideo.Duration == input.Duration &&
(
(
size == VideoSize.Original &&
outputVideo.Width == input.Width &&
outputVideo.Height == input.Height
) ||
(
size != VideoSize.Original &&
outputVideo.Width != input.Width &&
outputVideo.Height != input.Height &&
outputVideo.Height == (int)size
)
);
}
finally
{
if (File.Exists(output.FullName))
File.Delete(output.FullName);
}
}
public void Convert(VideoType type, ArgumentsContainer container)
{
var output = Input.OutputLocation(type);
try
{
var input = VideoInfo.FromFileInfo(Input);
container.Add(new InputArgument(input));
container.Add(new OutputArgument(output));
var scaling = container.Find<ScaleArgument>();
Encoder.Convert(container);
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);
} else
{
if (scaling.Value.Width != -1)
{
Assert.AreEqual(outputVideo.Width, scaling.Value.Width);
}
if (scaling.Value.Height != -1)
{
Assert.AreEqual(outputVideo.Height, scaling.Value.Height);
}
Assert.AreNotEqual(outputVideo.Width, input.Width);
Assert.AreNotEqual(outputVideo.Height, input.Height);
}
}
finally
{
if (File.Exists(output.FullName))
File.Delete(output.FullName);
}
}
[TestMethod]
public void Video_ToMP4()
{
Convert(VideoType.Mp4);
}
[TestMethod]
public void Video_ToMP4_Args()
{
var container = new ArgumentsContainer();
container.Add(new VideoCodecArgument(VideoCodec.LibX264));
Convert(VideoType.Mp4, container);
}
[TestMethod]
public void Video_ToTS()
{
Convert(VideoType.Ts);
}
[TestMethod]
public void Video_ToTS_Args()
{
var container = new ArgumentsContainer();
container.Add(new CopyArgument());
container.Add(new BitStreamFilterArgument(Channel.Video, Filter.H264_Mp4ToAnnexB));
container.Add(new ForceFormatArgument(VideoCodec.MpegTs));
Convert(VideoType.Ts, container);
}
[TestMethod]
public void Video_ToOGV_Resize()
{
Convert(VideoType.Ogv, true, VideoSize.Ed);
}
[TestMethod]
public void Video_ToOGV_Resize_Args()
{
var container = new ArgumentsContainer();
container.Add(new ScaleArgument(VideoSize.Ed));
container.Add(new VideoCodecArgument(VideoCodec.LibTheora));
Convert(VideoType.Ogv, container);
}
[TestMethod]
public void Video_ToMP4_Resize()
{
Convert(VideoType.Mp4, true, VideoSize.Ed);
}
[TestMethod]
public void Video_ToMP4_Resize_Args()
{
var container = new ArgumentsContainer();
container.Add(new ScaleArgument(VideoSize.Ld));
container.Add(new VideoCodecArgument(VideoCodec.LibX264));
Convert(VideoType.Mp4, container);
}
[TestMethod]
public void Video_ToOGV()
{
Convert(VideoType.Ogv);
}
[TestMethod]
public void Video_ToMP4_MultiThread()
{
Convert(VideoType.Mp4, true);
}
[TestMethod]
public void Video_ToTS_MultiThread()
{
Convert(VideoType.Ts, true);
}
[TestMethod]
public void Video_ToOGV_MultiThread()
{
Convert(VideoType.Ogv, true);
}
[TestMethod]
public void Video_Snapshot()
{
var output = Input.OutputLocation(ImageType.Png);
try
{
var input = VideoInfo.FromFileInfo(Input);
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);
}
}
finally
{
if (File.Exists(output.FullName))
File.Delete(output.FullName);
}
}
[TestMethod]
public void Video_Join()
{
var output = Input.OutputLocation(VideoType.Mp4);
var newInput = Input.OutputLocation(VideoType.Mp4, "duplicate");
try
{
var input = VideoInfo.FromFileInfo(Input);
File.Copy(input.FullName, newInput.FullName);
var input2 = VideoInfo.FromFileInfo(newInput);
var result = Encoder.Join(output, input, input2);
Assert.IsTrue(File.Exists(output.FullName));
Assert.AreEqual(input.Duration.TotalSeconds * 2, result.Duration.TotalSeconds);
Assert.AreEqual(input.Height, result.Height);
Assert.AreEqual(input.Width, result.Width);
}
finally
{
if (File.Exists(output.FullName))
File.Delete(output.FullName);
if (File.Exists(newInput.FullName))
File.Delete(newInput.FullName);
}
}
[TestMethod]
public void Video_Join_Image_Sequence()
{
try
{
var imageSet = new List<ImageInfo>();
Directory.EnumerateFiles(VideoLibrary.ImageDirectory.FullName)
.Where(file => file.ToLower().EndsWith(".png"))
.ToList()
.ForEach(file =>
{
for (int i = 0; i < 15; i++)
{
imageSet.Add(new ImageInfo(file));
}
});
var result = Encoder.JoinImageSequence(VideoLibrary.ImageJoinOutput, images: imageSet.ToArray());
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);
}
finally
{
VideoLibrary.ImageJoinOutput.Refresh();
if (VideoLibrary.ImageJoinOutput.Exists)
{
VideoLibrary.ImageJoinOutput.Delete();
}
}
}
}
}

31
FFMpegCore.sln Normal file
View file

@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28307.329
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FFMpegCore", "FFMpegCore\FFMpegCore.csproj", "{19DE2EC2-9955-4712-8096-C22EF6713E4F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFMpegCore.Test", "FFMpegCore.Test\FFMpegCore.Test.csproj", "{F20C8353-72D9-454B-9F16-3624DBAD2328}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{19DE2EC2-9955-4712-8096-C22EF6713E4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{19DE2EC2-9955-4712-8096-C22EF6713E4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{19DE2EC2-9955-4712-8096-C22EF6713E4F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{19DE2EC2-9955-4712-8096-C22EF6713E4F}.Release|Any CPU.Build.0 = Release|Any CPU
{F20C8353-72D9-454B-9F16-3624DBAD2328}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F20C8353-72D9-454B-9F16-3624DBAD2328}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F20C8353-72D9-454B-9F16-3624DBAD2328}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F20C8353-72D9-454B-9F16-3624DBAD2328}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F1B53337-60E7-49CB-A171-D4AEF6B4D5F0}
EndGlobalSection
EndGlobal

View file

@ -0,0 +1,38 @@
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";
}
}

View file

@ -0,0 +1,10 @@
namespace FFMpegCore.Enums
{
public enum VideoType
{
Mp4,
Ogv,
Ts,
WebM
}
}

View file

@ -0,0 +1,31 @@
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)
{
var destination = $"{Environment.TickCount}.png";
poster.Save(destination);
var tempFile = new FileInfo(destination);
try
{
return new FFMpeg().PosterWithAudio(tempFile, audio, output);
}
catch(Exception e)
{
throw;
}
finally
{
tempFile.Delete();
}
}
}
}

View file

@ -0,0 +1,14 @@
using System;
using System.IO;
using FFMpegCore.FFMPEG;
namespace FFMpegCore.Extend
{
public static class UriExtensions
{
public static VideoInfo SaveStream(this Uri uri, FileInfo output)
{
return new FFMpeg().SaveM3U8Stream(uri, output);
}
}
}

View file

@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Arguments
{
/// <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)
{
}
}
}

View file

@ -0,0 +1,155 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Arguments
{
/// <summary>
/// Container of arguments represented parameters of FFMPEG process
/// </summary>
public class ArgumentsContainer : IDictionary<Type, Argument>
{
Dictionary<Type, Argument> _args;
public ArgumentsContainer()
{
_args = new Dictionary<Type, Argument>();
}
public Argument this[Type key] { get => ((IDictionary<Type, Argument>)_args)[key]; set => ((IDictionary<Type, Argument>)_args)[key] = value; }
public ICollection<Type> Keys => ((IDictionary<Type, Argument>)_args).Keys;
public ICollection<Argument> Values => ((IDictionary<Type, Argument>)_args).Values;
public int Count => ((IDictionary<Type, Argument>)_args).Count;
public bool IsReadOnly => ((IDictionary<Type, Argument>)_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)
{
throw new InvalidOperationException("Not supported operation");
}
/// <summary>
/// Clears collection of arguments
/// </summary>
public void Clear()
{
((IDictionary<Type, Argument>)_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 ((IDictionary<Type, Argument>)_args).Contains(item);
}
/// <summary>
/// Adds argument to collection
/// </summary>
/// <param name="value">Argument that should be added to collection</param>
public void Add(Argument value)
{
((IDictionary<Type, Argument>)_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 ((IDictionary<Type, Argument>)_args).ContainsKey(key);
}
public void CopyTo(KeyValuePair<Type, Argument>[] array, int arrayIndex)
{
((IDictionary<Type, Argument>)_args).CopyTo(array, arrayIndex);
}
public IEnumerator<KeyValuePair<Type, Argument>> GetEnumerator()
{
return ((IDictionary<Type, Argument>)_args).GetEnumerator();
}
public bool Remove(Type key)
{
return ((IDictionary<Type, Argument>)_args).Remove(key);
}
public bool Remove(KeyValuePair<Type, Argument> item)
{
return ((IDictionary<Type, Argument>)_args).Remove(item);
}
public bool TryGetValue(Type key, out Argument value)
{
return ((IDictionary<Type, Argument>)_args).TryGetValue(key, out value);
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IDictionary<Type, Argument>)_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;
}
}
}

View file

@ -0,0 +1,205 @@
using FFMpegCore.FFMPEG.Enums;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
namespace FFMpegCore.FFMPEG.Arguments
{
internal static class ArgumentsStringifier
{
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, AudioQuality bitrate)
{
return Audio(codec) + Audio(bitrate);
}
internal static string Audio(AudioCodec codec, int bitrate)
{
return Audio(codec) + Audio(bitrate);
}
internal static string Audio(AudioCodec codec)
{
return $"-codec: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 -strict experimental ";
}
internal static string Video(VideoCodec codec, int bitrate = 0)
{
var video = $"-codec: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 Input(Uri uri)
{
return Input(uri.AbsolutePath);
}
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 Input(VideoInfo input)
{
return $"-i \"{input.FullName}\" ";
}
internal static string Input(FileInfo input)
{
return $"-i \"{input.FullName}\" ";
}
internal static string Output(FileInfo output)
{
return $"\"{output.FullName}\"";
}
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 Scale(Size size)
{
return Scale(size.Width, size.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} ";
}
}
}

View file

@ -0,0 +1,47 @@
using FFMpegCore.FFMPEG.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Arguments
{
/// <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 ArgumentsStringifier.Audio(Value, Bitrate);
}
}
}

View file

@ -0,0 +1,32 @@
using FFMpegCore.FFMPEG.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Arguments
{
/// <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 ArgumentsStringifier.BitStreamFilter(First, Second);
}
}
}

View file

@ -0,0 +1,45 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Arguments
{
/// <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 ArgumentsStringifier.InputConcat(Value);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View file

@ -0,0 +1,34 @@
using FFMpegCore.FFMPEG.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Arguments
{
/// <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 ArgumentsStringifier.Copy(Value);
}
}
}

View file

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Arguments
{
/// <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 ArgumentsStringifier.Speed(Value);
}
}
}

View file

@ -0,0 +1,122 @@
using FFMpegCore.Enums;
using FFMpegCore.Helpers;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Arguments
{
/// <summary>
/// Builds parameters string from <see cref="ArgumentsContainer"/> that would be passed to ffmpeg process
/// </summary>
public class FFArgumentBuilder : IArgumentBuilder
{
/// <summary>
/// Builds parameters string from <see cref="ArgumentsContainer"/> that would be passed to ffmpeg process
/// </summary>
/// <param name="container">Container of arguments</param>
/// <returns>Parameters string</returns>
public string BuildArguments(ArgumentsContainer container)
{
if (!container.ContainsInputOutput())
throw new ArgumentException("No input or output parameter found", nameof(container));
CheckContainerException(container);
StringBuilder sb = new StringBuilder();
sb.Append(GetInput(container).GetStringValue().Trim() + " ");
foreach(var a in container)
{
if(!IsInputOrOutput(a.Key))
{
sb.Append(a.Value.GetStringValue().Trim() + " ");
}
}
sb.Append(container[typeof(OutputArgument)].GetStringValue().Trim());
var overrideArg = container.Find<OverrideArgument>();
if (overrideArg != null)
sb.Append(" " + overrideArg.GetStringValue().Trim());
return sb.ToString();
}
/// <summary>
/// Builds parameters string from <see cref="ArgumentsContainer"/> that would be passed to ffmpeg process
/// </summary>
/// <param name="container">Container of arguments</param>
/// <param name="input">Input file</param>
/// <param name="output">Output file</param>
/// <returns>Parameters string</returns>
public string BuildArguments(ArgumentsContainer container, FileInfo input, FileInfo output)
{
CheckContainerException(container);
CheckExtensionOfOutputExtension(container, output);
FFMpegHelper.ConversionExceptionCheck(input, output);
StringBuilder sb = new StringBuilder();
var inputA = new InputArgument(input);
var outputA = new OutputArgument();
sb.Append(inputA.GetStringValue().Trim() + " ");
foreach (var a in container)
{
if (!IsInputOrOutput(a.Key))
{
sb.Append(a.Value.GetStringValue().Trim() + " ");
}
}
sb.Append(outputA.GetStringValue().Trim());
var overrideArg = container.Find<OverrideArgument>();
if (overrideArg != null)
sb.Append(" " + overrideArg.GetStringValue().Trim());
return sb.ToString();
}
private void CheckContainerException(ArgumentsContainer container)
{
//TODO: implement arguments check
}
private void CheckExtensionOfOutputExtension(ArgumentsContainer container, FileInfo output)
{
if(container.ContainsKey(typeof(VideoCodecArgument)))
{
var codec = (VideoCodecArgument)container[typeof(VideoCodecArgument)];
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.ForCodec(codec.Value));
}
}
private Argument GetInput(ArgumentsContainer 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");
}
private bool IsInputOrOutput(Argument arg)
{
return IsInputOrOutput(arg.GetType());
}
private bool IsInputOrOutput(Type arg)
{
return (arg == typeof(InputArgument)) || (arg == typeof(ConcatArgument)) || (arg == typeof(OutputArgument)) || (arg == typeof(OverrideArgument));
}
}
}

View file

@ -0,0 +1,32 @@
using FFMpegCore.FFMPEG.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Arguments
{
/// <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 ArgumentsStringifier.ForceFormat(Value);
}
}
}

View file

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Arguments
{
/// <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 ArgumentsStringifier.FrameOutputCount(Value);
}
}
}

View file

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Arguments
{
/// <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 ArgumentsStringifier.FrameRate(Value);
}
}
}

View file

@ -0,0 +1,17 @@
using FFMpegCore.FFMPEG.Enums;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Arguments
{
public interface IArgumentBuilder
{
string BuildArguments(ArgumentsContainer container);
string BuildArguments(ArgumentsContainer container, FileInfo input, FileInfo output);
}
}

View file

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Arguments
{
/// <summary>
/// Represents input parameter
/// </summary>
public class InputArgument : Argument<string>
{
public InputArgument()
{
}
public InputArgument(string value) : base(value)
{
}
public InputArgument(VideoInfo value) : base(value.FullName)
{
}
public InputArgument(FileInfo value) : base(value.FullName)
{
}
public InputArgument(Uri value) : base(value.AbsolutePath)
{
}
/// <summary>
/// String representation of the argument
/// </summary>
/// <returns>String representation of the argument</returns>
public override string GetStringValue()
{
return ArgumentsStringifier.Input(Value);
}
}
}

View file

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Arguments
{
/// <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 ArgumentsStringifier.Loop(Value);
}
}
}

View file

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Arguments
{
/// <summary>
/// Represents output parameter
/// </summary>
public class OutputArgument : InputArgument
{
public OutputArgument()
{
}
public OutputArgument(string value) : base(value)
{
}
public OutputArgument(VideoInfo value) : base(value)
{
}
public OutputArgument(FileInfo value) : base(value)
{
}
public OutputArgument(Uri value) : base(value)
{
}
/// <summary>
/// String representation of the argument
/// </summary>
/// <returns>String representation of the argument</returns>
public override string GetStringValue()
{
return ArgumentsStringifier.Output(Value);
}
public FileInfo GetAsFileInfo()
{
return new FileInfo(Value);
}
}
}

View file

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Arguments
{
/// <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";
}
}
}

View file

@ -0,0 +1,42 @@
using FFMpegCore.FFMPEG.Enums;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Arguments
{
/// <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 ArgumentsStringifier.Scale(Value);
}
}
}

View file

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Arguments
{
/// <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 ArgumentsStringifier.Seek(Value);
}
}
}

View file

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Arguments
{
/// <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 ArgumentsStringifier.FinalizeAtShortestInput(Value);
}
}
}

View file

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FFMpegCore.FFMPEG.Enums;
namespace FFMpegCore.FFMPEG.Arguments
{
/// <summary>
/// Represents size parameter
/// </summary>
public class SizeArgument : ScaleArgument
{
public SizeArgument()
{
}
public SizeArgument(Size value) : base(value)
{
}
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 ArgumentsStringifier.Size(Value);
}
}
}

View file

@ -0,0 +1,32 @@
using FFMpegCore.FFMPEG.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Arguments
{
/// <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 ArgumentsStringifier.Speed(Value);
}
}
}

View file

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Arguments
{
/// <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 ArgumentsStringifier.StartNumber(Value);
}
}
}

View file

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Arguments
{
/// <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 ArgumentsStringifier.Threads(Value);
}
}
}

View file

@ -0,0 +1,39 @@
using FFMpegCore.FFMPEG.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Arguments
{
/// <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 ArgumentsStringifier.Video(Value, Bitrate);
}
}
}

View file

@ -0,0 +1,10 @@
namespace FFMpegCore.FFMPEG.Enums
{
public enum AudioQuality
{
Ultra = 384,
Hd = 192,
Normal = 128,
Low = 64
}
}

View file

@ -0,0 +1,30 @@
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
}
}

View file

@ -0,0 +1,15 @@
namespace FFMpegCore.FFMPEG.Enums
{
public enum Speed
{
VerySlow,
Slower,
Slow,
Medium,
Fast,
Faster,
VeryFast,
SuperFast,
UltraFast
}
}

View file

@ -0,0 +1,11 @@
namespace FFMpegCore.FFMPEG.Enums
{
public enum VideoSize
{
Hd = 720,
FullHd = 1080,
Ed = 480,
Ld = 360,
Original
}
}

View file

@ -0,0 +1,31 @@
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; }
}
}

View file

@ -0,0 +1,81 @@
using System;
using System.Diagnostics;
using System.Linq;
namespace FFMpegCore.FFMPEG
{
public abstract class FFBase : IDisposable
{
protected string ConfiguredRoot;
protected Process Process;
protected FFBase()
{
ConfiguredRoot = ".\\FFMPEG\\bin";
}
/// <summary>
/// Is 'true' when an exception is thrown during process kill (for paranoia level users).
/// </summary>
public bool IsKillFaulty { get; private set; }
/// <summary>
/// Returns true if the associated process is still alive/running.
/// </summary>
public bool IsWorking
{
get
{
bool processHasExited;
try
{
processHasExited = Process.HasExited;
}
catch
{
processHasExited = true;
}
return !processHasExited && Process.GetProcesses().Any(x => x.Id == Process.Id);
}
}
public void Dispose()
{
Process?.Dispose();
}
protected void CreateProcess(string args, string processPath, bool rStandardInput = false,
bool rStandardOutput = false, bool rStandardError = false)
{
if (IsWorking)
throw new InvalidOperationException(
"The current FFMpeg process is busy with another operation. Create a new object for parallel executions.");
Process = new Process();
IsKillFaulty = false;
Process.StartInfo.FileName = processPath;
Process.StartInfo.Arguments = args;
Process.StartInfo.UseShellExecute = false;
Process.StartInfo.CreateNoWindow = true;
Process.StartInfo.RedirectStandardInput = rStandardInput;
Process.StartInfo.RedirectStandardOutput = rStandardOutput;
Process.StartInfo.RedirectStandardError = rStandardError;
}
public void Kill()
{
try
{
if (IsWorking)
Process.Kill();
}
catch
{
IsKillFaulty = true;
}
}
}
}

543
FFMpegCore/FFMPEG/FFMpeg.cs Normal file
View file

@ -0,0 +1,543 @@
using FFMpegCore.Enums;
using FFMpegCore.FFMPEG.Arguments;
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;
namespace FFMpegCore.FFMPEG
{
public delegate void ConversionHandler(double percentage);
public class FFMpeg : FFBase
{
IArgumentBuilder argumentBuilder { get; set; }
/// <summary>
/// Intializes the FFMPEG encoder.
/// </summary>
public FFMpeg()
{
_Init();
argumentBuilder = new FFArgumentBuilder();
}
public FFMpeg(IArgumentBuilder builder)
{
_Init();
argumentBuilder = builder;
}
private void _Init()
{
FFMpegHelper.RootExceptionCheck(ConfiguredRoot);
FFProbeHelper.RootExceptionCheck(ConfiguredRoot);
var target = Environment.Is64BitProcess ? "x64" : "x86";
_ffmpegPath = ConfiguredRoot + $"\\{target}\\ffmpeg.exe";
}
/// <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>
/// <returns>Bitmap with the requested snapshot.</returns>
public Bitmap Snapshot(VideoInfo source, FileInfo output, Size? size = null, TimeSpan? captureTime = null)
{
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 thumbArgs = ArgumentsStringifier.Input(source) +
ArgumentsStringifier.Video(VideoCodec.Png) +
ArgumentsStringifier.FrameOutputCount(1) +
ArgumentsStringifier.Seek(captureTime) +
ArgumentsStringifier.Size(size) +
ArgumentsStringifier.Output(output);
if (!RunProcess(thumbArgs, 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)
{
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);
string args = "";
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;
}
switch (type)
{
case VideoType.Mp4:
args = ArgumentsStringifier.Input(source) +
ArgumentsStringifier.Threads(multithreaded) +
ArgumentsStringifier.Scale(outputSize) +
ArgumentsStringifier.Video(VideoCodec.LibX264, 2400) +
ArgumentsStringifier.Speed(speed) +
ArgumentsStringifier.Audio(AudioCodec.Aac, audioQuality) +
ArgumentsStringifier.Output(output);
break;
case VideoType.Ogv:
args = ArgumentsStringifier.Input(source) +
ArgumentsStringifier.Threads(multithreaded) +
ArgumentsStringifier.Scale(outputSize) +
ArgumentsStringifier.Video(VideoCodec.LibTheora, 2400) +
ArgumentsStringifier.Speed(16) +
ArgumentsStringifier.Audio(AudioCodec.LibVorbis, audioQuality) +
ArgumentsStringifier.Output(output);
break;
case VideoType.Ts:
args = ArgumentsStringifier.Input(source) +
ArgumentsStringifier.Copy() +
ArgumentsStringifier.BitStreamFilter(Channel.Video, Filter.H264_Mp4ToAnnexB) +
ArgumentsStringifier.ForceFormat(VideoCodec.MpegTs) +
ArgumentsStringifier.Output(output);
break;
}
if (!RunProcess(args, output))
{
throw new FFMpegException(FFMpegExceptionType.Conversion, $"The video could not be converted to {Enum.GetName(typeof(VideoType), type)}");
}
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 args = ArgumentsStringifier.Loop(1) +
ArgumentsStringifier.Input(image) +
ArgumentsStringifier.Input(audio) +
ArgumentsStringifier.Video(VideoCodec.LibX264, 2400) +
ArgumentsStringifier.Audio(AudioCodec.Aac, AudioQuality.Normal) +
ArgumentsStringifier.FinalizeAtShortestInput(true) +
ArgumentsStringifier.Output(output);
if (!RunProcess(args, 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 args = ArgumentsStringifier.InputConcat(temporaryVideoParts) +
ArgumentsStringifier.Copy() +
ArgumentsStringifier.BitStreamFilter(Channel.Audio, Filter.Aac_AdtstoAsc) +
ArgumentsStringifier.Output(output);
try
{
if (!RunProcess(args, 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 args = ArgumentsStringifier.FrameRate(frameRate) +
ArgumentsStringifier.Size(new Size(firstImage.Width, firstImage.Height)) +
ArgumentsStringifier.StartNumber(0) +
ArgumentsStringifier.Input($"{firstImage.Directory}\\%09d.png") +
ArgumentsStringifier.FrameOutputCount(images.Length) +
ArgumentsStringifier.Video(VideoCodec.LibX264) +
ArgumentsStringifier.Output(output);
try
{
if (!RunProcess(args, 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 args = ArgumentsStringifier.Input(uri) +
ArgumentsStringifier.Output(output);
if (!RunProcess(args, 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 args = ArgumentsStringifier.Input(source) +
ArgumentsStringifier.Copy() +
ArgumentsStringifier.Disable(Channel.Audio) +
ArgumentsStringifier.Output(output);
if (!RunProcess(args, 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 args = ArgumentsStringifier.Input(source) +
ArgumentsStringifier.Disable(Channel.Video) +
ArgumentsStringifier.Output(output);
if (!RunProcess(args, 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 args = ArgumentsStringifier.Input(source) +
ArgumentsStringifier.Input(audio) +
ArgumentsStringifier.Copy(Channel.Video) +
ArgumentsStringifier.Audio(AudioCodec.Aac, AudioQuality.Hd) +
ArgumentsStringifier.FinalizeAtShortestInput(stopAtShortest) +
ArgumentsStringifier.Output(output);
if (!RunProcess(args, output))
{
throw new FFMpegException(FFMpegExceptionType.Operation, "Could not replace the video audio.");
}
return new VideoInfo(output);
}
public VideoInfo Convert(ArgumentsContainer arguments)
{
var args = argumentBuilder.BuildArguments(arguments);
var output = ((OutputArgument)arguments[typeof(OutputArgument)]).GetAsFileInfo();
if (!RunProcess(args, output))
{
throw new FFMpegException(FFMpegExceptionType.Operation, "Could not replace the video audio.");
}
return new VideoInfo(output);
}
public VideoInfo Convert(ArgumentsContainer arguments, FileInfo input, FileInfo output)
{
var args = argumentBuilder.BuildArguments(arguments, input, output);
if (!RunProcess(args, output))
{
throw new FFMpegException(FFMpegExceptionType.Operation, "Could not replace the video audio.");
}
return new VideoInfo(output);
}
/// <summary>
/// Stops any current job that FFMpeg is running.
/// </summary>
public void Stop()
{
if (IsWorking)
{
Process.StandardInput.Write('q');
}
}
#region Private Members & Methods
private string _ffmpegPath;
private TimeSpan _totalTime;
private volatile StringBuilder _errorOutput = new StringBuilder();
private bool RunProcess(string args, FileInfo output)
{
var successState = true;
CreateProcess(args, _ffmpegPath, true, rStandardError: true);
try
{
Process.Start();
Process.ErrorDataReceived += OutputData;
Process.BeginErrorReadLine();
Process.WaitForExit();
}
catch (Exception)
{
successState = false;
}
finally
{
Process.Close();
if (File.Exists(output.FullName))
using (var file = File.Open(output.FullName, FileMode.Open))
{
if (file.Length == 0)
{
throw new FFMpegException(FFMpegExceptionType.Process, _errorOutput);
}
}
else
{
throw new FFMpegException(FFMpegExceptionType.Process, _errorOutput);
}
}
return successState;
}
private void Cleanup(IEnumerable<string> pathList)
{
foreach (var path in pathList)
{
if (File.Exists(path))
{
File.Delete(path);
}
}
}
private void OutputData(object sender, DataReceivedEventArgs e)
{
if (e.Data == null)
return;
_errorOutput.AppendLine(e.Data);
#if DEBUG
Trace.WriteLine(e.Data);
#endif
if (OnProgress == null || !IsWorking) return;
var r = new Regex(@"\w\w:\w\w:\w\w");
var m = r.Match(e.Data);
if (!e.Data.Contains("frame")) return;
if (!m.Success) return;
var t = TimeSpan.Parse(m.Value, CultureInfo.InvariantCulture);
var percentage = Math.Round(t.TotalSeconds / _totalTime.TotalSeconds * 100, 2);
OnProgress(percentage);
}
#endregion
}
}

View file

@ -0,0 +1,138 @@
using FFMpegCore.Helpers;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Globalization;
namespace FFMpegCore.FFMPEG
{
public sealed class FFProbe : FFBase
{
public FFProbe()
{
FFProbeHelper.RootExceptionCheck(ConfiguredRoot);
var target = Environment.Is64BitProcess ? "x64" : "x86";
_ffprobePath = ConfiguredRoot + $"\\{target}\\ffprobe.exe";
}
/// <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 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 jsonOutput =
RunProcess($"-v quiet -print_format json -show_streams \"{info.FullName}\"");
var metadata = JsonConvert.DeserializeObject<Dictionary<string, dynamic>>(jsonOutput);
int videoIndex = metadata["streams"][0]["codec_type"] == "video" ? 0 : 1,
audioIndex = 1 - videoIndex;
var bitRate = Convert.ToDouble(metadata["streams"][videoIndex]["bit_rate"], CultureInfo.InvariantCulture);
try
{
var duration = Convert.ToDouble(metadata["streams"][videoIndex]["duration"], CultureInfo.InvariantCulture);
info.Duration = TimeSpan.FromSeconds(duration);
info.Duration = info.Duration.Subtract(TimeSpan.FromMilliseconds(info.Duration.Milliseconds));
}
catch (Exception)
{
info.Duration = TimeSpan.FromSeconds(0);
}
// Get video size in megabytes
double videoSize = 0,
audioSize = 0;
try
{
info.VideoFormat = metadata["streams"][videoIndex]["codec_name"];
videoSize = bitRate * info.Duration / 8388608;
}
catch (Exception)
{
info.VideoFormat = "none";
}
// Get audio format - wrap for exceptions if the video has no audio
try
{
info.AudioFormat = metadata["streams"][audioIndex]["codec_name"];
audioSize = bitRate * info.Duration / 8388608;
}
catch (Exception)
{
info.AudioFormat = "none";
}
// Get video format
// Get video width
info.Width = metadata["streams"][videoIndex]["width"];
// Get video height
info.Height = metadata["streams"][videoIndex]["height"];
info.Size = Math.Round(videoSize + audioSize, 2);
// Get video aspect ratio
var cd = FFProbeHelper.Gcd(info.Width, info.Height);
info.Ratio = info.Width / cd + ":" + info.Height / cd;
// Get video framerate
var fr = ((string)metadata["streams"][videoIndex]["r_frame_rate"]).Split('/');
info.FrameRate = Math.Round(
Convert.ToDouble(fr[0], CultureInfo.InvariantCulture) /
Convert.ToDouble(fr[1], CultureInfo.InvariantCulture),
3);
return info;
}
#region Private Members & Methods
private readonly string _ffprobePath;
private string RunProcess(string args)
{
CreateProcess(args, _ffprobePath, rStandardOutput: true);
string output;
try
{
Process.Start();
output = Process.StandardOutput.ReadToEnd();
}
catch (Exception)
{
output = "";
}
finally
{
Process.WaitForExit();
Process.Close();
}
return output;
}
#endregion
}
}

View file

@ -0,0 +1,144 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<NeutralLanguage>en</NeutralLanguage>
<RepositoryUrl>https://github.com/vladjerca/FFMpegCore</RepositoryUrl>
<PackageProjectUrl>https://github.com/vladjerca/FFMpegCore</PackageProjectUrl>
<Copyright>Vlad Jerca</Copyright>
<Description>A great way to use FFMpeg encoding when writing video applications, client-side and server-side. It has wrapper methods that allow conversion to all web formats: MP4, OGV, TS and methods of capturing screens from the videos.</Description>
</PropertyGroup>
<ItemGroup>
<Content Include="FFMPEG\bin\presets\ffprobe.xsd">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libvpx-1080p.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libvpx-1080p50_60.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libvpx-360p.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libvpx-720p.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libvpx-720p50_60.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libvpx-ultrafast.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-baseline.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-fast.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-faster.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-faster_firstpass.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-fast_firstpass.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-ipod320.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-ipod640.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-lossless_fast.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-lossless_max.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-lossless_medium.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-lossless_slow.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-lossless_slower.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-lossless_ultrafast.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-main.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-medium.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-medium_firstpass.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-placebo.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-placebo_firstpass.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-slow.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-slower.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-slower_firstpass.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-slow_firstpass.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-superfast.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-superfast_firstpass.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-ultrafast.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-ultrafast_firstpass.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-veryfast.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-veryfast_firstpass.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-veryslow.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\presets\libx264-veryslow_firstpass.ffpreset">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\x64\ffmpeg.exe">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\x64\ffprobe.exe">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\x86\ffmpeg.exe">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFMPEG\bin\x86\ffprobe.exe">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="System.Drawing.Common" Version="4.5.1" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,21 @@
<?xml version="1.0"?>
<package >
<metadata>
<id>$id$</id>
<version>$version$</version>
<title>$title$</title>
<authors>Vlad Jerca</authors>
<owners>Vlad Jerca</owners>
<projectUrl>https://github.com/vladjerca/FFMpegCore</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>$description$</description>
<releaseNotes>
More information available @ https://github.com/vladjerca/FFMpegCore/blob/master/README.md
</releaseNotes>
<contentFiles>
<files include="**/FFMPEG/bin/*.*" buildAction="Content" />
</contentFiles>
<copyright>Copyright 2019</copyright>
<tags>ffmpeg video conversion FFMpegCore mp4 ogv net.core core net</tags>
</metadata>
</package>

View file

@ -0,0 +1,85 @@
using System;
using System.Drawing;
using System.IO;
using FFMpegCore.FFMPEG.Exceptions;
namespace FFMpegCore.Helpers
{
public static class FFMpegHelper
{
public static void ConversionSizeExceptionCheck(Image image)
{
ConversionSizeExceptionCheck(image.Size);
}
public static void ConversionSizeExceptionCheck(VideoInfo info)
{
ConversionSizeExceptionCheck(new Size(info.Width, info.Height));
}
public static void ConversionSizeExceptionCheck(Size size)
{
if (
size.Height % 2 != 0 ||
size.Width % 2 != 0
)
{
throw new ArgumentException("FFMpeg yuv420p encoding requires the width and height to be a multiple of 2!");
}
}
public static void OutputExistsExceptionCheck(FileInfo output)
{
if (File.Exists(output.FullName))
{
throw new FFMpegException(FFMpegExceptionType.File,
$"The output file: {output} already exists!");
}
}
public static void InputExistsExceptionCheck(FileInfo input)
{
if (!File.Exists(input.FullName))
{
throw new FFMpegException(FFMpegExceptionType.File,
$"Input {input.FullName} does not exist!");
}
}
public static void ConversionExceptionCheck(FileInfo originalVideo, FileInfo convertedPath)
{
OutputExistsExceptionCheck(convertedPath);
InputExistsExceptionCheck(originalVideo);
}
public static void InputsExistExceptionCheck(params FileInfo[] paths)
{
foreach (var path in paths)
{
InputExistsExceptionCheck(path);
}
}
public static void ExtensionExceptionCheck(FileInfo output, string expected)
{
if (!expected.Equals(new FileInfo(output.FullName).Extension, StringComparison.OrdinalIgnoreCase))
throw new FFMpegException(FFMpegExceptionType.File,
$"Invalid output file. File extension should be '{expected}' required.");
}
public static void RootExceptionCheck(string root)
{
if (root == null)
throw new FFMpegException(FFMpegExceptionType.Dependency,
"FFMpeg root is not configured in app config. Missing key 'ffmpegRoot'.");
var target = Environment.Is64BitProcess ? "x64" : "x86";
var path = root + $"\\{target}\\ffmpeg.exe";
if (!File.Exists(path))
throw new FFMpegException(FFMpegExceptionType.Dependency,
"FFMpeg cannot be found in the root directory!");
}
}
}

View file

@ -0,0 +1,35 @@
using System;
using System.IO;
using FFMpegCore.FFMPEG.Exceptions;
namespace FFMpegCore.Helpers
{
public class FFProbeHelper
{
public static int Gcd(int first, int second)
{
while (first != 0 && second != 0)
{
if (first > second)
first -= second;
else second -= first;
}
return first == 0 ? second : first;
}
public static void RootExceptionCheck(string root)
{
if (root == null)
throw new FFMpegException(FFMpegExceptionType.Dependency,
"FFProbe root is not configured in app config. Missing key 'ffmpegRoot'.");
var target = Environment.Is64BitProcess ? "x64" : "x86";
var path = root + $"\\{target}\\ffprobe.exe";
if (!File.Exists(path))
throw new FFMpegException(FFMpegExceptionType.Dependency,
$"FFProbe cannot be found in the in {path}...");
}
}
}

183
FFMpegCore/ImageInfo.cs Normal file
View file

@ -0,0 +1,183 @@
using FFMpegCore.Enums;
using FFMpegCore.FFMPEG;
using FFMpegCore.Helpers;
using System;
using System.Drawing;
using System.IO;
using System.Linq;
namespace FFMpegCore
{
public class ImageInfo
{
private FileInfo _file;
/// <summary>
/// Create a image information object from a target path.
/// </summary>
/// <param name="fileInfo">Image file information.</param>
public ImageInfo(FileInfo fileInfo)
{
if (!fileInfo.Extension.ToLower().EndsWith(FileExtension.Png))
{
throw new Exception("Image joining currently suppors only .png file types");
}
fileInfo.Refresh();
this.Size = fileInfo.Length / (1024 * 1024);
using (var image = Image.FromFile(fileInfo.FullName))
{
this.Width = image.Width;
this.Height = image.Height;
var cd = FFProbeHelper.Gcd(this.Width, this.Height);
this.Ratio = $"{this.Width / cd}:{this.Height / cd}";
}
if (!fileInfo.Exists)
throw new ArgumentException($"Input file {fileInfo.FullName} does not exist!");
_file = fileInfo;
}
/// <summary>
/// Create a image information object from a target path.
/// </summary>
/// <param name="path">Path to image.</param>
public ImageInfo(string path) : this(new FileInfo(path))
{
}
/// <summary>
/// Aspect ratio.
/// </summary>
public string Ratio { get; internal set; }
/// <summary>
/// Height of the image file.
/// </summary>
public int Height { get; internal set; }
/// <summary>
/// Width of the image file.
/// </summary>
public int Width { get; internal set; }
/// <summary>
/// Image file size in MegaBytes (MB).
/// </summary>
public double Size { get; internal set; }
/// <summary>
/// Gets the name of the file.
/// </summary>
public string Name => _file.Name;
/// <summary>
/// Gets the full path of the file.
/// </summary>
public string FullName => _file.FullName;
/// <summary>
/// Gets the file extension.
/// </summary>
public string Extension => _file.Extension;
/// <summary>
/// Gets a flag indicating if the file is read-only.
/// </summary>
public bool IsReadOnly => _file.IsReadOnly;
/// <summary>
/// Gets a flag indicating if the file exists (no cache, per call verification).
/// </summary>
public bool Exists => File.Exists(FullName);
/// <summary>
/// Gets the creation date.
/// </summary>
public DateTime CreationTime => _file.CreationTime;
/// <summary>
/// Gets the parent directory information.
/// </summary>
public DirectoryInfo Directory => _file.Directory;
/// <summary>
/// Create a image information object from a file information object.
/// </summary>
/// <param name="fileInfo">Image file information.</param>
/// <returns></returns>
public static ImageInfo FromFileInfo(FileInfo fileInfo)
{
return FromPath(fileInfo.FullName);
}
/// <summary>
/// Create a image information object from a target path.
/// </summary>
/// <param name="path">Path to image.</param>
/// <returns></returns>
public static ImageInfo FromPath(string path)
{
return new ImageInfo(path);
}
/// <summary>
/// Pretty prints the image information.
/// </summary>
/// <returns></returns>
public override string ToString()
{
return "Image Path : " + FullName + Environment.NewLine +
"Image Root : " + Directory.FullName + Environment.NewLine +
"Image Name: " + Name + Environment.NewLine +
"Image Extension : " + Extension + Environment.NewLine +
"Aspect Ratio : " + Ratio + Environment.NewLine +
"Resolution : " + Width + "x" + Height + Environment.NewLine +
"Size : " + Size + " MB";
}
/// <summary>
/// Open a file stream.
/// </summary>
/// <param name="mode">Opens a file in a specified mode.</param>
/// <returns>File stream of the image file.</returns>
public FileStream FileOpen(FileMode mode)
{
return _file.Open(mode);
}
/// <summary>
/// Move file to a specific directory.
/// </summary>
/// <param name="destination"></param>
public void MoveTo(DirectoryInfo destination)
{
var newLocation = $"{destination.FullName}\\{Name}{Extension}";
_file.MoveTo(newLocation);
_file = new FileInfo(newLocation);
}
/// <summary>
/// Delete the file.
/// </summary>
public void Delete()
{
_file.Delete();
}
/// <summary>
/// Converts image info to file info.
/// </summary>
/// <returns>A new FileInfo instance.</returns>
public FileInfo ToFileInfo()
{
return new FileInfo(_file.FullName);
}
}
}

187
FFMpegCore/VideoInfo.cs Normal file
View file

@ -0,0 +1,187 @@
using FFMpegCore.FFMPEG;
using System;
using System.IO;
namespace FFMpegCore
{
public class VideoInfo
{
private FileInfo _file;
/// <summary>
/// Create a video information object from a file information object.
/// </summary>
/// <param name="fileInfo">Video file information.</param>
public VideoInfo(FileInfo fileInfo)
{
fileInfo.Refresh();
if (!fileInfo.Exists)
throw new ArgumentException($"Input file {fileInfo.FullName} does not exist!");
_file = fileInfo;
new FFProbe().ParseVideoInfo(this);
}
/// <summary>
/// Create a video information object from a target path.
/// </summary>
/// <param name="path">Path to video.</param>
public VideoInfo(string path) : this(new FileInfo(path))
{
}
/// <summary>
/// Duration of the video file.
/// </summary>
public TimeSpan Duration { get; internal set; }
/// <summary>
/// Audio format of the video file.
/// </summary>
public string AudioFormat { get; internal set; }
/// <summary>
/// Video format of the video file.
/// </summary>
public string VideoFormat { get; internal set; }
/// <summary>
/// Aspect ratio.
/// </summary>
public string Ratio { get; internal set; }
/// <summary>
/// Video frame rate.
/// </summary>
public double FrameRate { get; internal set; }
/// <summary>
/// Height of the video file.
/// </summary>
public int Height { get; internal set; }
/// <summary>
/// Width of the video file.
/// </summary>
public int Width { get; internal set; }
/// <summary>
/// Video file size in MegaBytes (MB).
/// </summary>
public double Size { get; internal set; }
/// <summary>
/// Gets the name of the file.
/// </summary>
public string Name => _file.Name;
/// <summary>
/// Gets the full path of the file.
/// </summary>
public string FullName => _file.FullName;
/// <summary>
/// Gets the file extension.
/// </summary>
public string Extension => _file.Extension;
/// <summary>
/// Gets a flag indicating if the file is read-only.
/// </summary>
public bool IsReadOnly => _file.IsReadOnly;
/// <summary>
/// Gets a flag indicating if the file exists (no cache, per call verification).
/// </summary>
public bool Exists => File.Exists(FullName);
/// <summary>
/// Gets the creation date.
/// </summary>
public DateTime CreationTime => _file.CreationTime;
/// <summary>
/// Gets the parent directory information.
/// </summary>
public DirectoryInfo Directory => _file.Directory;
/// <summary>
/// Create a video information object from a file information object.
/// </summary>
/// <param name="fileInfo">Video file information.</param>
/// <returns></returns>
public static VideoInfo FromFileInfo(FileInfo fileInfo)
{
return FromPath(fileInfo.FullName);
}
/// <summary>
/// Create a video information object from a target path.
/// </summary>
/// <param name="path">Path to video.</param>
/// <returns></returns>
public static VideoInfo FromPath(string path)
{
return new VideoInfo(path);
}
/// <summary>
/// Pretty prints the video information.
/// </summary>
/// <returns></returns>
public override string ToString()
{
return "Video Path : " + FullName + Environment.NewLine +
"Video Root : " + Directory.FullName + Environment.NewLine +
"Video Name: " + Name + Environment.NewLine +
"Video Extension : " + Extension + Environment.NewLine +
"Video Duration : " + Duration + Environment.NewLine +
"Audio Format : " + AudioFormat + Environment.NewLine +
"Video Format : " + VideoFormat + Environment.NewLine +
"Aspect Ratio : " + Ratio + Environment.NewLine +
"Framerate : " + FrameRate + "fps" + Environment.NewLine +
"Resolution : " + Width + "x" + Height + Environment.NewLine +
"Size : " + Size + " MB";
}
/// <summary>
/// Open a file stream.
/// </summary>
/// <param name="mode">Opens a file in a specified mode.</param>
/// <returns>File stream of the video file.</returns>
public FileStream FileOpen(FileMode mode)
{
return _file.Open(mode);
}
/// <summary>
/// Move file to a specific directory.
/// </summary>
/// <param name="destination"></param>
public void MoveTo(DirectoryInfo destination)
{
var newLocation = $"{destination.FullName}\\{Name}{Extension}";
_file.MoveTo(newLocation);
_file = new FileInfo(newLocation);
}
/// <summary>
/// Delete the file.
/// </summary>
public void Delete()
{
_file.Delete();
}
/// <summary>
/// Converts video info to file info.
/// </summary>
/// <returns>FileInfo</returns>
public FileInfo ToFileInfo()
{
return new FileInfo(_file.FullName);
}
}
}

333
README.md Normal file
View file

@ -0,0 +1,333 @@
![FFMpeg Sharp](https://media.licdn.com/media/gcrc/dms/image/C5612AQFDCKxnyQ3tmw/article-cover_image-shrink_600_2000/0?e=1542844800&v=beta&t=ntfxKUaio7wjO2VFRL4o7gyoIPNKT95SPt94etMFuzw)
# FFMpegCore [![NuGet Badge](https://buildstats.info/nuget/FFMpegCore)](https://www.nuget.org/packages/FFMpegCore/)
## Setup
#### NuGet:
```
Install-Package FFMpegCore
```
A great way to use FFMpeg encoding when writing video applications, client-side and server-side. It has wrapper methods that allow conversion to all web formats: MP4, OGV, TS and methods of capturing screens from the videos.
### FFProbe
FFProbe is used to gather video information
```csharp
static void Main(string[] args)
{
string inputFile = "G:\\input.mp4";
// loaded from configuration
var video = new VideoInfo(inputFile);
string output = video.ToString();
Console.WriteLine(output);
}
```
Sample output:
```csharp
Video Path : G:\input.mp4
Video Root : G:\\
Video Name: input.mp4
Video Extension : .mp4
Video Duration : 00:00:09
Audio Format : none
Video Format : h264
Aspect Ratio : 16:9
Framerate : 30fps
Resolution : 1280x720
Size : 2.88 Mb
```
### FFMpeg
Convert your video files to web ready formats:
```csharp
static void Main(string[] args)
{
string inputFile = "input_path_goes_here";
var encoder = new FFMpeg();
FileInfo outputFile = new FileInfo("output_path_goes_here");
var video = VideoInfo.FromPath(inputFile);
// easily track conversion progress
encoder.OnProgress += (percentage) => Console.WriteLine("Progress {0}%", percentage);
// MP4 conversion
encoder.Convert(
video,
outputFile,
VideoType.Mp4,
Speed.UltraFast,
VideoSize.Original,
AudioQuality.Hd,
true
);
// OGV conversion
encoder.Convert(
video,
outputFile,
VideoType.Ogv,
Speed.UltraFast,
VideoSize.Original,
AudioQuality.Hd,
true
);
// TS conversion
encoder.Convert(
video,
outputFile,
VideoType.Ts
);
}
```
Easily capture screens from your videos:
```csharp
static void Main(string[] args)
{
string inputFile = "input_path_goes_here";
FileInfo output = new FileInfo("output_path_goes_here");
var video = VideoInfo.FromPath(inputFile);
new FFMpeg()
.Snapshot(
video,
output,
new Size(200, 400),
TimeSpan.FromMinutes(1)
);
}
```
Join video parts:
```csharp
static void Main(string[] args)
{
FFMpeg encoder = new FFMpeg();
encoder.Join(
new FileInfo(@"..\joined_video.mp4"),
VideoInfo.FromPath(@"..\part1.mp4"),
VideoInfo.FromPath(@"..\part2.mp4"),
VideoInfo.FromPath(@"..\part3.mp4")
);
}
```
Join image sequences:
```csharp
static void Main(string[] args)
{
FFMpeg encoder = new FFMpeg();
encoder.JoinImageSequence(
new FileInfo(@"..\joined_video.mp4"),
1, // FPS
ImageInfo.FromPath(@"..\1.png"),
ImageInfo.FromPath(@"..\2.png"),
ImageInfo.FromPath(@"..\3.png")
);
}
```
Strip audio track from videos:
```csharp
static void Main(string[] args)
{
string inputFile = "input_path_goes_here",
outputFile = "output_path_goes_here";
new FFMpeg()
.Mute(
VideoInfo.FromPath(inputFile),
new FileInfo(outputFile)
);
}
```
Save audio track from video:
```csharp
static void Main(string[] args)
{
string inputVideoFile = "input_path_goes_here",
outputAudioFile = "output_path_goes_here";
new FFMpeg()
.ExtractAudio(
VideoInfo.FromPath(inputVideoFile),
new FileInfo(outputAudioFile)
);
}
```
Add audio track to video:
```csharp
static void Main(string[] args)
{
string inputVideoFile = "input_path_goes_here",
inputAudioFile = "input_path_goes_here",
outputVideoFile = "output_path_goes_here";
FFMpeg encoder = new FFMpeg();
new FFMpeg()
.ReplaceAudio(
VideoInfo.FromPath(inputVideoFile),
new FileInfo(inputAudioFile),
new FileInfo(outputVideoFile)
);
}
```
Add poster image to audio file (good for youtube videos):
```csharp
static void Main(string[] args)
{
string inputImageFile = "input_path_goes_here",
inputAudioFile = "input_path_goes_here",
outputVideoFile = "output_path_goes_here";
FFMpeg encoder = new FFMpeg();
((Bitmap)Image.FromFile(inputImageFile))
.AddAudio(
new FileInfo(inputAudioFile),
new FileInfo(outputVideoFile)
);
/* OR */
new FFMpeg()
.PosterWithAudio(
inputImageFile,
new FileInfo(inputAudioFile),
new FileInfo(outputVideoFile)
);
}
```
Control over the 'FFmpeg' process doing the job:
```csharp
static void Main(string[] args)
{
string inputVideoFile = "input_path_goes_here",
outputVideoFile = "input_path_goes_here";
FFMpeg encoder = new FFMpeg();
// start the conversion process
Task.Run(() => {
encoder.Convert(new VideoInfo(inputVideoFile), new FileInfo(outputVideoFile));
});
// stop encoding after 2 seconds (only for example purposes)
Thread.Sleep(2000);
encoder.Stop();
}
```
### Enums
Video Size enumeration:
```csharp
public enum VideoSize
{
HD,
FullHD,
ED,
LD,
Original
}
```
Speed enumeration:
```csharp
public enum Speed
{
VerySlow,
Slower,
Slow,
Medium,
Fast,
Faster,
VeryFast,
SuperFast,
UltraFast
}
```
Audio codecs enumeration:
```csharp
public enum AudioCodec
{
Aac,
LibVorbis
}
```
Audio quality presets enumeration:
```csharp
public enum AudioQuality
{
Ultra = 384,
Hd = 192,
Normal = 128,
Low = 64
}
```
Video codecs enumeration:
```csharp
public enum VideoCodec
{
LibX264,
LibVpx,
LibTheora,
Png,
MpegTs
}
```
### ArgumentBuilder
Custom video converting presets could be created with help of `ArgumentsContainer` class:
```csharp
var container = new ArgumentsContainer();
container.Add(new VideoCodecArgument(VideoCodec.LibX264));
container.Add(new ScaleArgument(VideoSize.Hd));
var ffmpeg = new FFMpeg();
var result = ffmpeg.Convert(container, new FileInfo("input.mp4"), new FileInfo("output.mp4"));
```
Other availible arguments could be found in `FFMpegCore.FFMPEG.Arguments` namespace.
If you need to create your custom argument, you just need to create new class, that is inherited from `Argument`, `Argument<T>` or `Argument<T1, T2>`
For example:
```csharp
public class OverrideArgument : Argument
{
public override string GetStringValue()
{
return "-y";
}
}
```
## Contributors
<a href="https://github.com/vladjerca"><img src="https://avatars.githubusercontent.com/u/6339681?v=3" title="vladjerca" width="80" height="80"></a>
<a href="https://github.com/max619"><img src="https://avatars.githubusercontent.com/u/26447324?v=3" title="max619" width="80" height="80"></a>
### License
Copyright © 2018, [Vlad Jerca](https://github.com/vladjerca).
Released under the [MIT license](https://github.com/jonschlinkert/github-contributors/blob/master/LICENSE).

1
pack.ps1 Normal file
View file

@ -0,0 +1 @@
.\.nuget\nuget.exe pack .\FFMpegCore\ -Prop Configuration=Release