Added FFmpeg animated GIF support

This commit is contained in:
Jaex 2015-06-03 14:32:34 +03:00
parent 7f1daa2480
commit cd877f5ee2
7 changed files with 114 additions and 44 deletions

View file

@ -35,7 +35,7 @@ public abstract class ExternalCLIManager : IDisposable
public event DataReceivedEventHandler OutputDataReceived; public event DataReceivedEventHandler OutputDataReceived;
public event DataReceivedEventHandler ErrorDataReceived; public event DataReceivedEventHandler ErrorDataReceived;
private Process process = new Process(); private Process process;
public virtual int Open(string path, string args = null) public virtual int Open(string path, string args = null)
{ {
@ -43,26 +43,29 @@ public virtual int Open(string path, string args = null)
if (File.Exists(path)) if (File.Exists(path))
{ {
ProcessStartInfo psi = new ProcessStartInfo(path); using (process = new Process())
psi.UseShellExecute = false; {
psi.CreateNoWindow = true; ProcessStartInfo psi = new ProcessStartInfo(path);
psi.RedirectStandardInput = true; psi.UseShellExecute = false;
psi.RedirectStandardOutput = true; psi.CreateNoWindow = true;
psi.RedirectStandardError = true; psi.RedirectStandardInput = true;
psi.Arguments = args; psi.RedirectStandardOutput = true;
psi.WorkingDirectory = Path.GetDirectoryName(path); psi.RedirectStandardError = true;
psi.StandardOutputEncoding = Encoding.UTF8; psi.Arguments = args;
psi.StandardErrorEncoding = Encoding.UTF8; psi.WorkingDirectory = Path.GetDirectoryName(path);
psi.StandardOutputEncoding = Encoding.UTF8;
psi.StandardErrorEncoding = Encoding.UTF8;
process.EnableRaisingEvents = true; process.EnableRaisingEvents = true;
if (psi.RedirectStandardOutput) process.OutputDataReceived += cli_OutputDataReceived; if (psi.RedirectStandardOutput) process.OutputDataReceived += cli_OutputDataReceived;
if (psi.RedirectStandardError) process.ErrorDataReceived += cli_ErrorDataReceived; if (psi.RedirectStandardError) process.ErrorDataReceived += cli_ErrorDataReceived;
process.StartInfo = psi; process.StartInfo = psi;
process.Start(); process.Start();
if (psi.RedirectStandardOutput) process.BeginOutputReadLine(); if (psi.RedirectStandardOutput) process.BeginOutputReadLine();
if (psi.RedirectStandardError) process.BeginErrorReadLine(); if (psi.RedirectStandardError) process.BeginErrorReadLine();
process.WaitForExit(); process.WaitForExit();
return process.ExitCode; return process.ExitCode;
}
} }
return -1; return -1;

View file

@ -68,6 +68,8 @@ public enum FFmpegVideoCodec
libx264, libx264,
[Description("VP8 (WebM)")] [Description("VP8 (WebM)")]
libvpx, libvpx,
[Description("Animated GIF")]
gif,
[Description("Xvid")] [Description("Xvid")]
libxvid, libxvid,
[Description("x265")] [Description("x265")]

View file

@ -65,7 +65,38 @@ private void FFmpegHelper_DataReceived(object sender, DataReceivedEventArgs e)
public bool Record() public bool Record()
{ {
int errorCode = Open(Options.FFmpeg.CLIPath, Options.GetFFmpegCommands()); return Run(Options.FFmpeg.CLIPath, Options.GetFFmpegCommands());
}
public bool EncodeGIF(string input, string output)
{
bool result;
string palettePath = Path.Combine(Path.GetDirectoryName(Options.FFmpeg.CLIPath), "palette.png");
try
{
result = Run(Options.FFmpeg.CLIPath, string.Format("-i \"{0}\" -vf palettegen -y \"{1}\"", input, palettePath));
if (result)
{
result = Run(Options.FFmpeg.CLIPath, string.Format("-i \"{0}\" -i \"{1}\" -lavfi paletteuse -y \"{2}\"", input, palettePath, output));
}
}
finally
{
if (File.Exists(palettePath))
{
File.Delete(palettePath);
}
}
return result;
}
private bool Run(string path, string args = null)
{
int errorCode = Open(path, args);
bool result = errorCode == 0; bool result = errorCode == 0;
if (Options.FFmpeg.ShowError && !result) if (Options.FFmpeg.ShowError && !result)
{ {

View file

@ -72,7 +72,7 @@ public bool IsAudioSourceSelected
{ {
get get
{ {
return !string.IsNullOrEmpty(AudioSource) && !AudioSource.Equals(FFmpegHelper.SourceNone, StringComparison.InvariantCultureIgnoreCase); return !string.IsNullOrEmpty(AudioSource) && !AudioSource.Equals(FFmpegHelper.SourceNone, StringComparison.InvariantCultureIgnoreCase) && VideoCodec != FFmpegVideoCodec.gif;
} }
} }

View file

@ -202,6 +202,8 @@ public void SaveAsGIF(string path, GIFQuality quality)
{ {
if (imgCache != null && imgCache is HardDiskCache && !IsRecording) if (imgCache != null && imgCache is HardDiskCache && !IsRecording)
{ {
Helpers.CreateDirectoryIfNotExist(path);
HardDiskCache hdCache = imgCache as HardDiskCache; HardDiskCache hdCache = imgCache as HardDiskCache;
using (AnimatedGifCreator gifEncoder = new AnimatedGifCreator(path, delay)) using (AnimatedGifCreator gifEncoder = new AnimatedGifCreator(path, delay))
@ -223,6 +225,12 @@ public void SaveAsGIF(string path, GIFQuality quality)
} }
} }
public bool FFmpegEncodeAsGIF(string path)
{
Helpers.CreateDirectoryIfNotExist(path);
return ffmpegCli.EncodeGIF(Options.OutputPath, path);
}
public void EncodeUsingCommandLine(VideoEncoder encoder, string sourceFilePath, string targetFilePath) public void EncodeUsingCommandLine(VideoEncoder encoder, string sourceFilePath, string targetFilePath)
{ {
if (!string.IsNullOrEmpty(sourceFilePath) && File.Exists(sourceFilePath)) if (!string.IsNullOrEmpty(sourceFilePath) && File.Exists(sourceFilePath))

View file

@ -67,7 +67,7 @@ public string GetFFmpegCommands()
if (FFmpeg.UseCustomCommands && !string.IsNullOrEmpty(FFmpeg.CustomCommands)) if (FFmpeg.UseCustomCommands && !string.IsNullOrEmpty(FFmpeg.CustomCommands))
{ {
commands = FFmpeg.CustomCommands. commands = FFmpeg.CustomCommands.
Replace("$fps$", ScreenRecordFPS.ToString(), StringComparison.InvariantCultureIgnoreCase). Replace("$fps$", FFmpeg.VideoCodec == FFmpegVideoCodec.gif ? GIFFPS.ToString() : ScreenRecordFPS.ToString(), StringComparison.InvariantCultureIgnoreCase).
Replace("$area_x$", CaptureArea.X.ToString(), StringComparison.InvariantCultureIgnoreCase). Replace("$area_x$", CaptureArea.X.ToString(), StringComparison.InvariantCultureIgnoreCase).
Replace("$area_y$", CaptureArea.Y.ToString(), StringComparison.InvariantCultureIgnoreCase). Replace("$area_y$", CaptureArea.Y.ToString(), StringComparison.InvariantCultureIgnoreCase).
Replace("$area_width$", CaptureArea.Width.ToString(), StringComparison.InvariantCultureIgnoreCase). Replace("$area_width$", CaptureArea.Width.ToString(), StringComparison.InvariantCultureIgnoreCase).
@ -95,7 +95,16 @@ public string GetFFmpegArgs(bool isCustom = false)
args.Append("-y "); // -y for overwrite file args.Append("-y "); // -y for overwrite file
args.Append("-rtbufsize 100M "); // default real time buffer size was 3041280 (3M) args.Append("-rtbufsize 100M "); // default real time buffer size was 3041280 (3M)
string fps = isCustom ? "$fps$" : ScreenRecordFPS.ToString(); string fps;
if (isCustom)
{
fps = "$fps$";
}
else
{
fps = FFmpeg.VideoCodec == FFmpegVideoCodec.gif ? GIFFPS.ToString() : ScreenRecordFPS.ToString();
}
if (FFmpeg.IsVideoSourceSelected) if (FFmpeg.IsVideoSourceSelected)
{ {
@ -138,17 +147,34 @@ public string GetFFmpegArgs(bool isCustom = false)
if (FFmpeg.IsVideoSourceSelected) if (FFmpeg.IsVideoSourceSelected)
{ {
args.AppendFormat("-c:v {0} ", FFmpeg.VideoCodec); string videoCodec;
switch (FFmpeg.VideoCodec)
{
default:
videoCodec = FFmpeg.VideoCodec.ToString();
break;
case FFmpegVideoCodec.gif:
videoCodec = FFmpegVideoCodec.libx264.ToString();
break;
}
args.AppendFormat("-c:v {0} ", videoCodec);
args.AppendFormat("-r {0} ", fps); // output FPS args.AppendFormat("-r {0} ", fps); // output FPS
switch (FFmpeg.VideoCodec) switch (FFmpeg.VideoCodec)
{ {
case FFmpegVideoCodec.libx264: // https://trac.ffmpeg.org/wiki/Encode/H.264 case FFmpegVideoCodec.libx264: // https://trac.ffmpeg.org/wiki/Encode/H.264
case FFmpegVideoCodec.libx265: // https://trac.ffmpeg.org/wiki/Encode/H.265 case FFmpegVideoCodec.libx265: // https://trac.ffmpeg.org/wiki/Encode/H.265
case FFmpegVideoCodec.gif:
args.AppendFormat("-crf {0} ", FFmpeg.x264_CRF); args.AppendFormat("-crf {0} ", FFmpeg.x264_CRF);
args.AppendFormat("-preset {0} ", FFmpeg.x264_Preset); args.AppendFormat("-preset {0} ", FFmpeg.x264_Preset);
args.AppendFormat("-tune {0} ", "zerolatency"); args.AppendFormat("-tune {0} ", "zerolatency");
args.Append("-pix_fmt yuv420p "); // -pix_fmt yuv420p required otherwise can't stream in Chrome
if (FFmpeg.VideoCodec != FFmpegVideoCodec.gif)
{
args.AppendFormat("-pix_fmt {0} ", "yuv420p"); // -pix_fmt yuv420p required otherwise can't stream in Chrome
}
break; break;
case FFmpegVideoCodec.libvpx: // https://trac.ffmpeg.org/wiki/Encode/VP8 case FFmpegVideoCodec.libvpx: // https://trac.ffmpeg.org/wiki/Encode/VP8
args.AppendFormat("-deadline {0} ", "realtime"); args.AppendFormat("-deadline {0} ", "realtime");

View file

@ -177,6 +177,13 @@ public void StartRecording(ScreenRecordOutput outputType, TaskSettings taskSetti
{ {
if (outputType == ScreenRecordOutput.FFmpeg) if (outputType == ScreenRecordOutput.FFmpeg)
{ {
if (taskSettings.CaptureSettings.FFmpegOptions.VideoCodec == FFmpegVideoCodec.gif)
{
taskSettings.CaptureSettings.FFmpegOptions.Extension = "mp4";
taskSettings.CaptureSettings.FFmpegOptions.x264_CRF = 0;
taskSettings.CaptureSettings.FFmpegOptions.x264_Preset = FFmpegPreset.ultrafast;
}
path = Path.Combine(taskSettings.CaptureFolder, TaskHelpers.GetFilename(taskSettings, taskSettings.CaptureSettings.FFmpegOptions.Extension)); path = Path.Combine(taskSettings.CaptureFolder, TaskHelpers.GetFilename(taskSettings, taskSettings.CaptureSettings.FFmpegOptions.Extension));
} }
else else
@ -266,30 +273,22 @@ public void StartRecording(ScreenRecordOutput outputType, TaskSettings taskSetti
TrayIcon.Text = "ShareX - " + Resources.ScreenRecordForm_StartRecording_Encoding___; TrayIcon.Text = "ShareX - " + Resources.ScreenRecordForm_StartRecording_Encoding___;
TrayIcon.Icon = Resources.camcorder_pencil.ToIcon(); TrayIcon.Icon = Resources.camcorder_pencil.ToIcon();
string sourceFilePath = path;
if (outputType == ScreenRecordOutput.GIF) if (outputType == ScreenRecordOutput.GIF)
{ {
if (taskSettings.CaptureSettings.RunScreencastCLI) path = Path.Combine(taskSettings.CaptureFolder, TaskHelpers.GetFilename(taskSettings, "gif"));
{ screenRecorder.EncodingProgressChanged += progress => TrayIcon.Text = string.Format("ShareX - {0} ({1}%)", Resources.ScreenRecordForm_StartRecording_Encoding___, progress);
sourceFilePath = Path.ChangeExtension(Program.ScreenRecorderCacheFilePath, "gif"); screenRecorder.SaveAsGIF(path, taskSettings.ImageSettings.ImageGIFQuality);
} }
else else if (outputType == ScreenRecordOutput.FFmpeg && taskSettings.CaptureSettings.FFmpegOptions.VideoCodec == FFmpegVideoCodec.gif)
{ {
sourceFilePath = path = Path.Combine(taskSettings.CaptureFolder, TaskHelpers.GetFilename(taskSettings, "gif")); path = Path.Combine(taskSettings.CaptureFolder, TaskHelpers.GetFilename(taskSettings, "gif"));
} screenRecorder.FFmpegEncodeAsGIF(path);
Helpers.CreateDirectoryIfNotExist(sourceFilePath);
screenRecorder.EncodingProgressChanged += progress =>
{
TrayIcon.Text = string.Format("ShareX - {0} ({1}%)", Resources.ScreenRecordForm_StartRecording_Encoding___, progress);
};
screenRecorder.SaveAsGIF(sourceFilePath, taskSettings.ImageSettings.ImageGIFQuality);
} }
if (taskSettings.CaptureSettings.RunScreencastCLI) if (taskSettings.CaptureSettings.RunScreencastCLI)
{ {
VideoEncoder encoder = Program.Settings.VideoEncoders[taskSettings.CaptureSettings.VideoEncoderSelected]; VideoEncoder encoder = Program.Settings.VideoEncoders[taskSettings.CaptureSettings.VideoEncoderSelected];
string sourceFilePath = path;
path = Path.Combine(taskSettings.CaptureFolder, TaskHelpers.GetFilename(taskSettings, encoder.OutputExtension)); path = Path.Combine(taskSettings.CaptureFolder, TaskHelpers.GetFilename(taskSettings, encoder.OutputExtension));
screenRecorder.EncodeUsingCommandLine(encoder, sourceFilePath, path); screenRecorder.EncodeUsingCommandLine(encoder, sourceFilePath, path);
} }
@ -299,7 +298,8 @@ public void StartRecording(ScreenRecordOutput outputType, TaskSettings taskSetti
{ {
if (screenRecorder != null) if (screenRecorder != null)
{ {
if ((outputType == ScreenRecordOutput.GIF || taskSettings.CaptureSettings.RunScreencastCLI) && if ((outputType == ScreenRecordOutput.GIF || taskSettings.CaptureSettings.RunScreencastCLI ||
(outputType == ScreenRecordOutput.FFmpeg && taskSettings.CaptureSettings.FFmpegOptions.VideoCodec == FFmpegVideoCodec.gif)) &&
!string.IsNullOrEmpty(screenRecorder.CachePath) && File.Exists(screenRecorder.CachePath)) !string.IsNullOrEmpty(screenRecorder.CachePath) && File.Exists(screenRecorder.CachePath))
{ {
File.Delete(screenRecorder.CachePath); File.Delete(screenRecorder.CachePath);