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 ErrorDataReceived;
private Process process = new Process();
private Process process;
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))
{
ProcessStartInfo psi = new ProcessStartInfo(path);
psi.UseShellExecute = false;
psi.CreateNoWindow = true;
psi.RedirectStandardInput = true;
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
psi.Arguments = args;
psi.WorkingDirectory = Path.GetDirectoryName(path);
psi.StandardOutputEncoding = Encoding.UTF8;
psi.StandardErrorEncoding = Encoding.UTF8;
using (process = new Process())
{
ProcessStartInfo psi = new ProcessStartInfo(path);
psi.UseShellExecute = false;
psi.CreateNoWindow = true;
psi.RedirectStandardInput = true;
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
psi.Arguments = args;
psi.WorkingDirectory = Path.GetDirectoryName(path);
psi.StandardOutputEncoding = Encoding.UTF8;
psi.StandardErrorEncoding = Encoding.UTF8;
process.EnableRaisingEvents = true;
if (psi.RedirectStandardOutput) process.OutputDataReceived += cli_OutputDataReceived;
if (psi.RedirectStandardError) process.ErrorDataReceived += cli_ErrorDataReceived;
process.StartInfo = psi;
process.Start();
if (psi.RedirectStandardOutput) process.BeginOutputReadLine();
if (psi.RedirectStandardError) process.BeginErrorReadLine();
process.WaitForExit();
return process.ExitCode;
process.EnableRaisingEvents = true;
if (psi.RedirectStandardOutput) process.OutputDataReceived += cli_OutputDataReceived;
if (psi.RedirectStandardError) process.ErrorDataReceived += cli_ErrorDataReceived;
process.StartInfo = psi;
process.Start();
if (psi.RedirectStandardOutput) process.BeginOutputReadLine();
if (psi.RedirectStandardError) process.BeginErrorReadLine();
process.WaitForExit();
return process.ExitCode;
}
}
return -1;

View file

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

View file

@ -65,7 +65,38 @@ private void FFmpegHelper_DataReceived(object sender, DataReceivedEventArgs e)
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;
if (Options.FFmpeg.ShowError && !result)
{

View file

@ -72,7 +72,7 @@ public bool IsAudioSourceSelected
{
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)
{
Helpers.CreateDirectoryIfNotExist(path);
HardDiskCache hdCache = imgCache as HardDiskCache;
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)
{
if (!string.IsNullOrEmpty(sourceFilePath) && File.Exists(sourceFilePath))

View file

@ -67,7 +67,7 @@ public string GetFFmpegCommands()
if (FFmpeg.UseCustomCommands && !string.IsNullOrEmpty(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_y$", CaptureArea.Y.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("-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)
{
@ -138,17 +147,34 @@ public string GetFFmpegArgs(bool isCustom = false)
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
switch (FFmpeg.VideoCodec)
{
case FFmpegVideoCodec.libx264: // https://trac.ffmpeg.org/wiki/Encode/H.264
case FFmpegVideoCodec.libx265: // https://trac.ffmpeg.org/wiki/Encode/H.265
case FFmpegVideoCodec.gif:
args.AppendFormat("-crf {0} ", FFmpeg.x264_CRF);
args.AppendFormat("-preset {0} ", FFmpeg.x264_Preset);
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;
case FFmpegVideoCodec.libvpx: // https://trac.ffmpeg.org/wiki/Encode/VP8
args.AppendFormat("-deadline {0} ", "realtime");

View file

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