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
|