C # /. NET 용 솔리드 FFmpeg 래퍼
나는 C # /. NET 용 견고한 FFmpeg 래퍼를 위해 웹에서 얼마 동안 검색했습니다 . 그러나 나는 아직 유용한 것을 생각해 내지 못했습니다. 다음 세 가지 프로젝트를 찾았지만 모두 초기 알파 단계에서 죽었습니다.
FFmpeg.NET
ffmpeg-sharp
FFLIB.NET
그래서 내 질문은 누군가가 더 성숙한 래퍼 프로젝트를 알고 있는지 여부입니다.
작업 대기열 등이있는 전체 트랜스 코딩 엔진을 찾고 있지 않습니다. 단순한 래퍼이므로 명령 줄 호출을 수행 한 다음 콘솔 출력을 구문 분석 할 필요가 없지만 메서드 호출을 수행하고 진행을 위해 이벤트 리스너를 사용할 수 있습니다.
그리고 초기 단계에 있더라도 진행중인 프로젝트를 자유롭게 언급하십시오.
이것은 내 자신의 래퍼입니다 : https://github.com/AydinAdn/MediaToolkit
MediaToolkit은 다음을 수행 할 수 있습니다.
- 비디오 파일을 다양한 다른 비디오 형식으로 변환합니다.
- 비디오 트랜스 코딩 작업을 수행합니다.
- 옵션 구성 :
Bit rate
,Frame rate
,Resolution / size
,Aspect ratio
,Duration of video
- 옵션 구성 :
- 오디오 트랜스 코딩 작업을 수행합니다.
- 구성 가능한 옵션 :
Audio sample rate
- 구성 가능한 옵션 :
- FILM, PAL 또는 NTSC TV 표준을 사용하여 비디오를 물리적 형식으로 변환
- 매체는 포함한다 :
DVD
,DV
,DV50
,VCD
,SVCD
- 매체는 포함한다 :
진행하면서 업데이트하고 있으며 사용을 환영합니다. 패키지 관리자 콘솔을 사용하여 설치할 수도 있습니다.
PM> Install-Package MediaToolkit
이것을 시도해보십시오. 간단한 래퍼에 사용할 수있는 것을 작성했을 것입니다.
http://jasonjano.wordpress.com/2010/02/09/a-simple-c-wrapper-for-ffmpeg/
여러 래퍼를 시도한 후 FFmpeg auto generated unsafe bindings for C # /. NET and Mono .
FFmpeg 네임 스페이스의 모든 클래스에 대한 저수준 interop 바인딩 집합입니다. 실제 래퍼만큼 편리하지 않을 수도 있지만 IMO는 .Net에서 FFmpeg로 작업하기위한 최상의 솔루션입니다.
장점 :
- 공장
- 신뢰할 수 있음-FFMpeg 자체를 신뢰한다고 가정하고 버그를 도입하는 타사 래퍼 코드가 없습니다.
- 항상 최신 버전의 FFmpeg로 업데이트됩니다.
- 모든 바인딩에 대한 단일 너겟 패키지
- XML 문서가 포함되어 있지만 온라인 문서 FFmpeg 문서 를 계속 사용할 수 있습니다 .
단점 :
- 낮은 수준 : c 구조체에 대한 포인터로 작업하는 방법을 알아야합니다 .
- 작동하려면 초기에 약간의 작업이 필요합니다. 나는 공식적인 예 에서 배우는 것이 좋습니다 .
참고 :이 스레드는 FFmpeg API 사용에 관한 것이지만 일부 사용 사례의 경우 ffmpeg.exe의 명령 줄 인터페이스 를 사용하는 것이 가장 좋습니다 .
ASP.NET/Windows 서비스 (.NET) 응용 프로그램에서 FFmpeg를 사용했습니다. 그러나 콘솔을 구문 분석하지 않고 명령 줄을 사용하게되었습니다. 이것을 사용함으로써-FFmpeg의 업데이트를 제어하고 여러 코어에서 여러 변환을 실행하는 쉬운 방법을 가졌습니다.
MediaHandler Pro라는 ffmpeg 래퍼 라이브러리를 가지고 놀았습니다.
지금까지 유망 해 보입니다.
여기에 또 다른 간단한 것이 있습니다 : http://ivolo.mit.edu/post/Metamorph-Convert-Audio-Video-to-Any-Format-on-Windows-Linux-and-Mac.aspx
나는 똑같은 것을 연구하고 있으며 원래는 변환에 훌륭하게 작동하는 MediaToolKit (다른 답변에서 언급)을 사용했지만 이제는 좀 더 강력한 것이 필요합니다.
성숙하고 여전히 활성화 된 한 가지 옵션은 다음과 같습니다. https://github.com/hudl/HudlFfmpeg 자세한 내용은 여기에서 읽을 수 있습니다 : http://public.hudl.com/bits/archives/2014/08/15/announcing -hudlffmpeg-ac-framework-to-make-ffmpeg-interaction-simple /
많은 경우에 적합하지 않을 수있는 또 다른 옵션은 C # 코드에서 직접 exe를 호출하는 것입니다. http://www.codeproject.com/Articles/774093/Another-FFmpeg-exe-Csharp-Wrapper
이 너겟 패키지를 사용할 수 있습니다.
당신이 성숙한 프로젝트 에 대해 물었다는 것을 알고 있지만, 내 기대를 채우는 프로젝트를 보지 못했기 때문에 내 프로젝트를 만들기로 결정했습니다. 변환을 쉽게 대기열에 넣고 병렬로 실행할 수 있으며 미디어를 다른 형식으로 변환하고 자신의 인수를 ffmpeg로 보내고 ffmpeg + 이벤트 리스너의 출력을 현재 진행 상태로 구문 분석 할 수 있습니다.
Install-Package Xabe.FFmpeg
사용하기 쉬운 크로스 플랫폼 FFmpeg 래퍼를 만들려고합니다.
이에 대한 자세한 정보는 https://xabe.net/product/xabe_ffmpeg/ 에서 찾을 수 있습니다.
자세한 정보 : https://xabe.net/product/xabe_ffmpeg/#documentation
변환은 간단합니다.
IConversionResult result = await Conversion.ToMp4(Resources.MkvWithAudio, output).Start();
진도를 원한다면 :
IConversion conversion = Conversion.ToMp4(Resources.MkvWithAudio, output);
conversion.OnProgress += (duration, length) => { currentProgress = duration; }
await conversion.Start();
여기 요 ...이 코드의 대부분은 2 년 이상 된 것이므로 많은 비동기식 항목이 누락되고 오래된 명명 규칙을 사용합니다. 꽤 오랫동안 생산 환경에서 실행 중 ~ JT
internal static class FFMpegArgUtils
{
public static string GetEncodeVideoFFMpegArgs(string sSourceFile, MP4Info objMp4Info, double nMbps, int iWidth, int iHeight, bool bIncludeAudio, string sOutputFile)
{
//Ensure file contains a video stream, otherwise this command will fail
if (objMp4Info != null && objMp4Info.VideoStreamCount == 0)
{
throw new Exception("FFMpegArgUtils::GetEncodeVideoFFMpegArgs - mp4 does not contain a video stream");
}
int iBitRateInKbps = (int)(nMbps * 1000);
StringBuilder sbArgs = new StringBuilder();
sbArgs.Append(" -y -threads 2 -i \"" + sSourceFile + "\" -strict -2 "); // 0 tells it to choose how many threads to use
if (bIncludeAudio == true)
{
//sbArgs.Append(" -acodec libmp3lame -ab 96k");
sbArgs.Append(" -acodec aac -ar 44100 -ab 96k");
}
else
{
sbArgs.Append(" -an");
}
sbArgs.Append(" -vcodec libx264 -level 41 -r 15 -crf 25 -g 15 -keyint_min 45 -bf 0");
//sbArgs.Append(" -vf pad=" + iWidth + ":" + iHeight + ":" + iVideoOffsetX + ":" + iVideoOffsetY);
sbArgs.Append(String.Format(" -vf \"scale=iw*min({0}/iw\\,{1}/ih):ih*min({0}/iw\\,{1}/ih),pad={0}:{1}:({0}-iw)/2:({1}-ih)/2\"",iWidth, iHeight));
//Output File
sbArgs.Append(" \"" + sOutputFile + "\"");
return sbArgs.ToString();
}
public static string GetEncodeAudioFFMpegArgs(string sSourceFile, string sOutputFile)
{
var args = String.Format(" -y -threads 2 -i \"{0}\" -strict -2 -acodec aac -ar 44100 -ab 96k -vn \"{1}\"", sSourceFile, sOutputFile);
return args;
//return GetEncodeVideoFFMpegArgs(sSourceFile, null, .2, 854, 480, true, sOutputFile);
//StringBuilder sbArgs = new StringBuilder();
//int iWidth = 854;
//int iHeight = 480;
//sbArgs.Append(" -y -i \"" + sSourceFile + "\" -strict -2 "); // 0 tells it to choose how many threads to use
//sbArgs.Append(" -acodec aac -ar 44100 -ab 96k");
//sbArgs.Append(" -vcodec libx264 -level 41 -r 15 -crf 25 -g 15 -keyint_min 45 -bf 0");
//sbArgs.Append(String.Format(" -vf \"scale=iw*min({0}/iw\\,{1}/ih):ih*min({0}/iw\\,{1}/ih),pad={0}:{1}:({0}-iw)/2:({1}-ih)/2\"", iWidth, iHeight));
//sbArgs.Append(" \"" + sOutputFile + "\"");
//return sbArgs.ToString();
}
}
internal class CreateEncodedVideoCommand : ConsoleCommandBase
{
public event ProgressEventHandler OnProgressEvent;
private string _sSourceFile;
private string _sOutputFolder;
private double _nMaxMbps;
public double BitrateInMbps
{
get { return _nMaxMbps; }
}
public int BitrateInKbps
{
get { return (int)Math.Round(_nMaxMbps * 1000); }
}
private int _iOutputWidth;
private int _iOutputHeight;
private bool _bIsConverting = false;
//private TimeSpan _tsDuration;
private double _nPercentageComplete;
private string _sOutputFile;
private string _sOutputFileName;
private bool _bAudioEnabled = true;
private string _sFFMpegPath;
private string _sExePath;
private string _sArgs;
private MP4Info _objSourceInfo;
private string _sOutputExt;
/// <summary>
/// Encodes an MP4 to the specs provided, quality is a value from 0 to 1
/// </summary>
/// <param name="nQuality">A value from 0 to 1</param>
///
public CreateEncodedVideoCommand(string sSourceFile, string sOutputFolder, string sFFMpegPath, double nMaxBitrateInMbps, MP4Info objSourceInfo, int iOutputWidth, int iOutputHeight, string sOutputExt)
{
_sSourceFile = sSourceFile;
_sOutputFolder = sOutputFolder;
_nMaxMbps = nMaxBitrateInMbps;
_objSourceInfo = objSourceInfo;
_iOutputWidth = iOutputWidth;
_iOutputHeight = iOutputHeight;
_sFFMpegPath = sFFMpegPath;
_sOutputExt = sOutputExt;
}
public void SetOutputFileName(string sOutputFileName)
{
_sOutputFileName = sOutputFileName;
}
public override void Execute()
{
try
{
_bIsConverting = false;
string sFileName = _sOutputFileName != null ? _sOutputFileName : Path.GetFileNameWithoutExtension(_sSourceFile) + "_" + _iOutputWidth + "." + _sOutputExt;
_sOutputFile = _sOutputFolder + "\\" + sFileName;
_sExePath = _sFFMpegPath;
_sArgs = FFMpegArgUtils.GetEncodeVideoFFMpegArgs(_sSourceFile, _objSourceInfo,_nMaxMbps, _iOutputWidth, _iOutputHeight, _bAudioEnabled, _sOutputFile);
InternalExecute(_sExePath, _sArgs);
}
catch (Exception objEx)
{
DispatchException(objEx);
}
}
public override string GetCommandInfo()
{
StringBuilder sbInfo = new StringBuilder();
sbInfo.AppendLine("CreateEncodeVideoCommand");
sbInfo.AppendLine("Exe: " + _sExePath);
sbInfo.AppendLine("Args: " + _sArgs);
sbInfo.AppendLine("[ConsoleOutput]");
sbInfo.Append(ConsoleOutput);
sbInfo.AppendLine("[ErrorOutput]");
sbInfo.Append(ErrorOutput);
return base.GetCommandInfo() + "\n" + sbInfo.ToString();
}
protected override void OnInternalCommandComplete(int iExitCode)
{
DispatchCommandComplete( iExitCode == 0 ? CommandResultType.Success : CommandResultType.Fail);
}
override protected void OnOutputRecieved(object sender, ProcessOutputEventArgs objArgs)
{
//FMPEG out always shows as Error
base.OnOutputRecieved(sender, objArgs);
if (_bIsConverting == false && objArgs.Data.StartsWith("Press [q] to stop encoding") == true)
{
_bIsConverting = true;
}
else if (_bIsConverting == true && objArgs.Data.StartsWith("frame=") == true)
{
//Capture Progress
UpdateProgressFromOutputLine(objArgs.Data);
}
else if (_bIsConverting == true && _nPercentageComplete > .8 && objArgs.Data.StartsWith("frame=") == false)
{
UpdateProgress(1);
_bIsConverting = false;
}
}
override protected void OnProcessExit(object sender, ProcessExitedEventArgs args)
{
_bIsConverting = false;
base.OnProcessExit(sender, args);
}
override public void Abort()
{
if (_objCurrentProcessRunner != null)
{
//_objCurrentProcessRunner.SendLineToInputStream("q");
_objCurrentProcessRunner.Dispose();
}
}
#region Helpers
//private void CaptureSourceDetailsFromOutput()
//{
// String sInputStreamInfoStartLine = _colErrorLines.SingleOrDefault(o => o.StartsWith("Input #0"));
// int iStreamInfoStartIndex = _colErrorLines.IndexOf(sInputStreamInfoStartLine);
// if (iStreamInfoStartIndex >= 0)
// {
// string sDurationInfoLine = _colErrorLines[iStreamInfoStartIndex + 1];
// string sDurantionTime = sDurationInfoLine.Substring(12, 11);
// _tsDuration = VideoUtils.GetDurationFromFFMpegDurationString(sDurantionTime);
// }
//}
private void UpdateProgressFromOutputLine(string sOutputLine)
{
int iTimeIndex = sOutputLine.IndexOf("time=");
int iBitrateIndex = sOutputLine.IndexOf(" bitrate=");
string sCurrentTime = sOutputLine.Substring(iTimeIndex + 5, iBitrateIndex - iTimeIndex - 5);
double nCurrentTimeInSeconds = double.Parse(sCurrentTime);
double nPercentageComplete = nCurrentTimeInSeconds / _objSourceInfo.Duration.TotalSeconds;
UpdateProgress(nPercentageComplete);
//Console.WriteLine("Progress: " + _nPercentageComplete);
}
private void UpdateProgress(double nPercentageComplete)
{
_nPercentageComplete = nPercentageComplete;
if (OnProgressEvent != null)
{
OnProgressEvent(this, new ProgressEventArgs( _nPercentageComplete));
}
}
#endregion
//public TimeSpan Duration { get { return _tsDuration; } }
public double Progress { get { return _nPercentageComplete; } }
public string OutputFile { get { return _sOutputFile; } }
public bool AudioEnabled
{
get { return _bAudioEnabled; }
set { _bAudioEnabled = value; }
}
}
public abstract class ConsoleCommandBase : CommandBase, ICommand
{
protected ProcessRunner _objCurrentProcessRunner;
protected List<String> _colOutputLines;
protected List<String> _colErrorLines;
private int _iExitCode;
public ConsoleCommandBase()
{
_colOutputLines = new List<string>();
_colErrorLines = new List<string>();
}
protected void InternalExecute(string sExePath, string sArgs)
{
InternalExecute(sExePath, sArgs, null, null, null);
}
protected void InternalExecute(string sExePath, string sArgs, string sDomain, string sUsername, string sPassword)
{
try
{
if (_objCurrentProcessRunner == null || _bIsRunning == false)
{
StringReader objStringReader = new StringReader(string.Empty);
_objCurrentProcessRunner = new ProcessRunner(sExePath, sArgs);
_objCurrentProcessRunner.SetCredentials(sDomain, sUsername, sPassword);
_objCurrentProcessRunner.OutputReceived += new ProcessOutputEventHandler(OnOutputRecieved);
_objCurrentProcessRunner.ProcessExited += new ProcessExitedEventHandler(OnProcessExit);
_objCurrentProcessRunner.Run();
_bIsRunning = true;
_bIsComplete = false;
}
else
{
DispatchException(new Exception("Processor Already Running"));
}
}
catch (Exception objEx)
{
DispatchException(objEx);
}
}
protected virtual void OnOutputRecieved(object sender, ProcessOutputEventArgs args)
{
try
{
if (args.Error == true)
{
_colErrorLines.Add(args.Data);
//Console.WriteLine("Error: " + args.Data);
}
else
{
_colOutputLines.Add(args.Data);
//Console.WriteLine(args.Data);
}
}
catch (Exception objEx)
{
DispatchException(objEx);
}
}
protected virtual void OnProcessExit(object sender, ProcessExitedEventArgs args)
{
try
{
Console.Write(ConsoleOutput);
_iExitCode = args.ExitCode;
_bIsRunning = false;
_bIsComplete = true;
//Some commands actually fail to succeed
//if(args.ExitCode != 0)
//{
// DispatchException(new Exception("Command Failed: " + this.GetType().Name + "\nConsole: " + ConsoleOutput + "\nConsoleError: " + ErrorOutput));
//}
OnInternalCommandComplete(_iExitCode);
if (_objCurrentProcessRunner != null)
{
_objCurrentProcessRunner.Dispose();
_objCurrentProcessRunner = null;
}
}
catch (Exception objEx)
{
DispatchException(objEx);
}
}
abstract protected void OnInternalCommandComplete(int iExitCode);
protected string JoinLines(List<String> colLines)
{
StringBuilder sbOutput = new StringBuilder();
colLines.ForEach( o => sbOutput.AppendLine(o));
return sbOutput.ToString();
}
#region Properties
public int ExitCode
{
get { return _iExitCode; }
}
#endregion
public override string GetCommandInfo()
{
StringBuilder sbCommandInfo = new StringBuilder();
sbCommandInfo.AppendLine("Command: " + this.GetType().Name);
sbCommandInfo.AppendLine("Console Output");
if (_colOutputLines != null)
{
foreach (string sOutputLine in _colOutputLines)
{
sbCommandInfo.AppendLine("\t" + sOutputLine);
}
}
sbCommandInfo.AppendLine("Error Output");
if (_colErrorLines != null)
{
foreach (string sErrorLine in _colErrorLines)
{
sbCommandInfo.AppendLine("\t" + sErrorLine);
}
}
return sbCommandInfo.ToString();
}
public String ConsoleOutput { get { return JoinLines(_colOutputLines); } }
public String ErrorOutput { get { return JoinLines(_colErrorLines);} }
}
CommandBase : ICommand
{
protected IDedooseContext _context;
protected Boolean _bIsRunning = false;
protected Boolean _bIsComplete = false;
#region Custom Events
public event CommandCompleteEventHandler OnCommandComplete;
event CommandCompleteEventHandler ICommand.OnCommandComplete
{
add { if (OnCommandComplete != null) { lock (OnCommandComplete) { OnCommandComplete += value; } } else { OnCommandComplete = new CommandCompleteEventHandler(value); } }
remove { if (OnCommandComplete != null) { lock (OnCommandComplete) { OnCommandComplete -= value; } } }
}
public event UnhandledExceptionEventHandler OnCommandException;
event UnhandledExceptionEventHandler ICommand.OnCommandException
{
add { if (OnCommandException != null) { lock (OnCommandException) { OnCommandException += value; } } else { OnCommandException = new UnhandledExceptionEventHandler(value); } }
remove { if (OnCommandException != null) { lock (OnCommandException) { OnCommandException -= value; } } }
}
public event ProgressEventHandler OnProgressUpdate;
event ProgressEventHandler ICommand.OnProgressUpdate
{
add { if (OnProgressUpdate != null) { lock (OnProgressUpdate) { OnProgressUpdate += value; } } else { OnProgressUpdate = new ProgressEventHandler(value); } }
remove { if (OnProgressUpdate != null) { lock (OnProgressUpdate) { OnProgressUpdate -= value; } } }
}
#endregion
protected CommandBase()
{
_context = UnityGlobalContainer.Instance.Context;
}
protected void DispatchCommandComplete(CommandResultType enResult)
{
if (enResult == CommandResultType.Fail)
{
StringBuilder sbMessage = new StringBuilder();
sbMessage.AppendLine("Command Commpleted with Failure: " + this.GetType().Name);
sbMessage.Append(GetCommandInfo());
Exception objEx = new Exception(sbMessage.ToString());
DispatchException(objEx);
}
else
{
if (OnCommandComplete != null)
{
OnCommandComplete(this, new CommandCompleteEventArgs(enResult));
}
}
}
protected void DispatchException(Exception objEx)
{
if (OnCommandException != null)
{
OnCommandException(this, new UnhandledExceptionEventArgs(objEx, true));
}
else
{
_context.Logger.LogException(objEx, MethodBase.GetCurrentMethod());
throw objEx;
}
}
protected void DispatchProgressUpdate(double nProgressRatio)
{
if (OnProgressUpdate != null) { OnProgressUpdate(this, new ProgressEventArgs(nProgressRatio)); }
}
public virtual string GetCommandInfo()
{
return "Not Implemented: " + this.GetType().Name;
}
public virtual void Execute() { throw new NotImplementedException(); }
public virtual void Abort() { throw new NotImplementedException(); }
public Boolean IsRunning { get { return _bIsRunning; } }
public Boolean IsComplete { get { return _bIsComplete; } }
public double GetProgressRatio()
{
throw new NotImplementedException();
}
}
public delegate void CommandCompleteEventHandler(object sender, CommandCompleteEventArgs e);
public interface ICommand
{
event CommandCompleteEventHandler OnCommandComplete;
event UnhandledExceptionEventHandler OnCommandException;
event ProgressEventHandler OnProgressUpdate;
double GetProgressRatio();
string GetCommandInfo();
void Execute();
void Abort();
}
// 프로세스 실행기 항목은 Roger Knapp의 ProcessRunner를 찾습니다.
string result = String.Empty;
StreamReader srOutput = null;
var oInfo = new ProcessStartInfo(exePath, parameters)
{
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true
};
var output = string.Empty;
try
{
Process process = System.Diagnostics.Process.Start(oInfo);
output = process.StandardError.ReadToEnd();
process.WaitForExit();
process.Close();
}
catch (Exception)
{
output = string.Empty;
}
return output;
이 래퍼는 메서드가 루프에 빠지지 않게합니다. 이것을 시도해보십시오.
나는 codeplex에서 FFPMEG.net을 포크했습니다.
여전히 활발히 작업 중입니다.
https://github.com/spoiledtechie/FFMpeg.Net
dll을 사용하지 않고 exe를 사용합니다. 그래서 더 안정적인 경향이 있습니다.
C # /. NET 용 자동 생성 FFmpeg 래퍼 및 Mono를 참조하세요 . FFmpeg interop을위한 유일한 진정한 완전한 .NET 래퍼처럼 보이는 멋진 프로젝트입니다.
참고 URL : https://stackoverflow.com/questions/2163036/solid-ffmpeg-wrapper-for-c-net
'Development Tip' 카테고리의 다른 글
다른 컴파일러에 의해 호출되는 다른 캐스트 연산자 (0) | 2020.10.10 |
---|---|
C ++에서 int를 enum으로 캐스트하는 일반적인 방법 (0) | 2020.10.10 |
Ruby on Rails에서 밑줄`_` 대신 대시`-`가있는 경로 (0) | 2020.10.10 |
Regex : InCombiningDiacriticalMarks 란 무엇입니까? (0) | 2020.10.10 |
Affero-GPL과 GPLv3의 차이점 (0) | 2020.10.10 |