FFMpegCore: port lib to .net standard
17
.gitattributes
vendored
Normal 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
|
@ -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
223
FFMpegCore.Test/ArgumentBuilderTests.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
84
FFMpegCore.Test/AudioTest.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
FFMpegCore.Test/BaseTest.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
56
FFMpegCore.Test/FFMpegCore.Test.csproj
Normal 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>
|
49
FFMpegCore.Test/Resources/VideoLibrary.cs
Normal 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}");
|
||||
}
|
||||
}
|
||||
}
|
BIN
FFMpegCore.Test/Resources/audio.mp3
Normal file
BIN
FFMpegCore.Test/Resources/cover.png
Normal file
After Width: | Height: | Size: 91 KiB |
BIN
FFMpegCore.Test/Resources/images/a.png
Normal file
After Width: | Height: | Size: 9.3 KiB |
BIN
FFMpegCore.Test/Resources/images/b.png
Normal file
After Width: | Height: | Size: 8.5 KiB |
BIN
FFMpegCore.Test/Resources/images/c.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
FFMpegCore.Test/Resources/images/d.png
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
FFMpegCore.Test/Resources/images/e.png
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
FFMpegCore.Test/Resources/images/f.png
Normal file
After Width: | Height: | Size: 8.3 KiB |
BIN
FFMpegCore.Test/Resources/input.mp4
Normal file
BIN
FFMpegCore.Test/Resources/mute.mp4
Normal file
282
FFMpegCore.Test/VideoTest.cs
Normal 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
|
@ -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
|
38
FFMpegCore/Enums/FileExtension.cs
Normal 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";
|
||||
}
|
||||
}
|
10
FFMpegCore/Enums/VideoType.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace FFMpegCore.Enums
|
||||
{
|
||||
public enum VideoType
|
||||
{
|
||||
Mp4,
|
||||
Ogv,
|
||||
Ts,
|
||||
WebM
|
||||
}
|
||||
}
|
31
FFMpegCore/Extend/BitmapExtensions.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
14
FFMpegCore/Extend/UriExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
88
FFMpegCore/FFMPEG/Arguments/Argument.cs
Normal 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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
155
FFMpegCore/FFMPEG/Arguments/ArgumentsContainer.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
205
FFMpegCore/FFMPEG/Arguments/ArgumentsStringifier.cs
Normal 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} ";
|
||||
}
|
||||
}
|
||||
}
|
47
FFMpegCore/FFMPEG/Arguments/AudioCodecArgument.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
32
FFMpegCore/FFMPEG/Arguments/BitStreamFilterArgument.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
45
FFMpegCore/FFMPEG/Arguments/ConcatArgument.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
34
FFMpegCore/FFMPEG/Arguments/CopyArgument.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
31
FFMpegCore/FFMPEG/Arguments/CpuSpeedArgument.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
122
FFMpegCore/FFMPEG/Arguments/FFArgumentBuilder.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
32
FFMpegCore/FFMPEG/Arguments/ForceFormatArgument.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
31
FFMpegCore/FFMPEG/Arguments/FrameOutputCountArgument.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
31
FFMpegCore/FFMPEG/Arguments/FrameRateArgument.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
17
FFMpegCore/FFMPEG/Arguments/IArgumentBuilder.cs
Normal 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);
|
||||
}
|
||||
}
|
44
FFMpegCore/FFMPEG/Arguments/InputArgument.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
31
FFMpegCore/FFMPEG/Arguments/LoopArgument.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
49
FFMpegCore/FFMPEG/Arguments/OutputArgument.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
28
FFMpegCore/FFMPEG/Arguments/OverrideArgument.cs
Normal 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";
|
||||
}
|
||||
}
|
||||
}
|
42
FFMpegCore/FFMPEG/Arguments/ScaleArgument.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
31
FFMpegCore/FFMPEG/Arguments/SeekArgument.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
31
FFMpegCore/FFMPEG/Arguments/ShortestArgument.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
41
FFMpegCore/FFMPEG/Arguments/SizeArgument.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
32
FFMpegCore/FFMPEG/Arguments/SpeedArgument.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
31
FFMpegCore/FFMPEG/Arguments/StartNumberArgument.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
39
FFMpegCore/FFMPEG/Arguments/ThreadsArgument.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
39
FFMpegCore/FFMPEG/Arguments/VideoCodecArgument.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
10
FFMpegCore/FFMPEG/Enums/AudioQuality.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace FFMpegCore.FFMPEG.Enums
|
||||
{
|
||||
public enum AudioQuality
|
||||
{
|
||||
Ultra = 384,
|
||||
Hd = 192,
|
||||
Normal = 128,
|
||||
Low = 64
|
||||
}
|
||||
}
|
30
FFMpegCore/FFMPEG/Enums/Codec.cs
Normal 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
|
||||
}
|
||||
}
|
15
FFMpegCore/FFMPEG/Enums/Speed.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
namespace FFMpegCore.FFMPEG.Enums
|
||||
{
|
||||
public enum Speed
|
||||
{
|
||||
VerySlow,
|
||||
Slower,
|
||||
Slow,
|
||||
Medium,
|
||||
Fast,
|
||||
Faster,
|
||||
VeryFast,
|
||||
SuperFast,
|
||||
UltraFast
|
||||
}
|
||||
}
|
11
FFMpegCore/FFMPEG/Enums/VideoSize.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace FFMpegCore.FFMPEG.Enums
|
||||
{
|
||||
public enum VideoSize
|
||||
{
|
||||
Hd = 720,
|
||||
FullHd = 1080,
|
||||
Ed = 480,
|
||||
Ld = 360,
|
||||
Original
|
||||
}
|
||||
}
|
31
FFMpegCore/FFMPEG/Exceptions/FFMpegException.cs
Normal 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; }
|
||||
}
|
||||
}
|
81
FFMpegCore/FFMPEG/FFBase.cs
Normal 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
|
@ -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
|
||||
}
|
||||
}
|
138
FFMpegCore/FFMPEG/FFProbe.cs
Normal 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
|
||||
}
|
||||
}
|
144
FFMpegCore/FFMpegCore.csproj
Normal 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>
|
21
FFMpegCore/FFMpegCore.nuspec
Normal 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>
|
85
FFMpegCore/Helpers/FFMpegHelper.cs
Normal 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!");
|
||||
}
|
||||
}
|
||||
}
|
35
FFMpegCore/Helpers/FFProbeHelper.cs
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -0,0 +1 @@
|
|||
.\.nuget\nuget.exe pack .\FFMpegCore\ -Prop Configuration=Release
|