2015-08-04 21:39:27 +12:00
|
|
|
|
#region License Information (GPL v3)
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
ShareX - A program that allows you to take screenshots and share any file type
|
2020-02-05 20:19:48 +13:00
|
|
|
|
Copyright (c) 2007-2020 ShareX Team
|
2015-08-04 21:39:27 +12:00
|
|
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
|
|
|
modify it under the terms of the GNU General Public License
|
|
|
|
|
as published by the Free Software Foundation; either version 2
|
|
|
|
|
of the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
|
along with this program; if not, write to the Free Software
|
|
|
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
|
|
|
|
|
|
Optionally you can also view the license at <http://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#endregion License Information (GPL v3)
|
|
|
|
|
|
|
|
|
|
using ShareX.HelpersLib;
|
2020-02-15 10:21:30 +13:00
|
|
|
|
using ShareX.MediaLib.Properties;
|
2015-08-04 21:39:27 +12:00
|
|
|
|
using System;
|
|
|
|
|
using System.Diagnostics;
|
2015-08-06 22:46:24 +12:00
|
|
|
|
using System.Drawing;
|
2015-08-06 23:44:03 +12:00
|
|
|
|
using System.Globalization;
|
2015-08-04 21:39:27 +12:00
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
|
|
|
|
|
|
namespace ShareX.MediaLib
|
|
|
|
|
{
|
|
|
|
|
public class FFmpegCLIManager : ExternalCLIManager
|
|
|
|
|
{
|
2019-11-24 04:06:13 +13:00
|
|
|
|
public const string SourceNone = "None";
|
|
|
|
|
public const string SourceGDIGrab = "GDI grab";
|
|
|
|
|
public const string SourceVideoDevice = "screen-capture-recorder";
|
|
|
|
|
public const string SourceAudioDevice = "virtual-audio-capturer";
|
2019-12-02 05:50:41 +13:00
|
|
|
|
|
|
|
|
|
public const int x264_min = 0;
|
|
|
|
|
public const int x264_max = 51;
|
|
|
|
|
public const int x265_min = 0;
|
|
|
|
|
public const int x265_max = 51;
|
|
|
|
|
public const int vp8_min = 4;
|
|
|
|
|
public const int vp8_max = 63;
|
|
|
|
|
public const int vp9_min = 0;
|
|
|
|
|
public const int vp9_max = 63;
|
|
|
|
|
public const int xvid_min = 1;
|
|
|
|
|
public const int xvid_max = 31;
|
|
|
|
|
public const int mp3_min = 0;
|
|
|
|
|
public const int mp3_max = 9;
|
2019-11-24 04:06:13 +13:00
|
|
|
|
|
|
|
|
|
public delegate void EncodeStartedEventHandler();
|
|
|
|
|
public event EncodeStartedEventHandler EncodeStarted;
|
|
|
|
|
|
2019-11-23 21:23:07 +13:00
|
|
|
|
public delegate void EncodeProgressChangedEventHandler(float percentage);
|
|
|
|
|
public event EncodeProgressChangedEventHandler EncodeProgressChanged;
|
|
|
|
|
|
2015-08-04 21:39:27 +12:00
|
|
|
|
public string FFmpegPath { get; private set; }
|
|
|
|
|
public StringBuilder Output { get; private set; }
|
2019-11-24 04:06:13 +13:00
|
|
|
|
public bool IsEncoding { get; set; }
|
2019-11-17 14:03:54 +13:00
|
|
|
|
public bool ShowError { get; set; }
|
2019-11-26 00:10:47 +13:00
|
|
|
|
public bool StopRequested { get; set; }
|
2019-11-23 03:14:24 +13:00
|
|
|
|
public bool TrackEncodeProgress { get; set; }
|
|
|
|
|
public TimeSpan VideoDuration { get; set; }
|
|
|
|
|
public TimeSpan EncodeTime { get; set; }
|
|
|
|
|
public float EncodePercentage { get; set; }
|
|
|
|
|
|
2019-11-24 04:06:13 +13:00
|
|
|
|
private int closeTryCount = 0;
|
|
|
|
|
|
2015-08-04 21:39:27 +12:00
|
|
|
|
public FFmpegCLIManager(string ffmpegPath)
|
|
|
|
|
{
|
|
|
|
|
FFmpegPath = ffmpegPath;
|
|
|
|
|
Output = new StringBuilder();
|
|
|
|
|
OutputDataReceived += FFmpeg_DataReceived;
|
|
|
|
|
ErrorDataReceived += FFmpeg_DataReceived;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-23 21:23:07 +13:00
|
|
|
|
public bool Run(string args)
|
|
|
|
|
{
|
|
|
|
|
return Run(FFmpegPath, args);
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-24 04:06:13 +13:00
|
|
|
|
protected bool Run(string path, string args)
|
2019-11-23 21:23:07 +13:00
|
|
|
|
{
|
2019-11-26 00:10:47 +13:00
|
|
|
|
StopRequested = false;
|
2019-11-23 21:23:07 +13:00
|
|
|
|
int errorCode = Open(path, args);
|
2019-11-24 04:06:13 +13:00
|
|
|
|
IsEncoding = false;
|
2019-11-23 21:23:07 +13:00
|
|
|
|
bool result = errorCode == 0;
|
|
|
|
|
if (!result && ShowError)
|
|
|
|
|
{
|
2020-02-15 10:21:30 +13:00
|
|
|
|
using (OutputBox outputBox = new OutputBox(Output.ToString(), Resources.FFmpegError))
|
2019-11-24 04:06:13 +13:00
|
|
|
|
{
|
|
|
|
|
outputBox.ShowDialog();
|
|
|
|
|
}
|
2019-11-23 21:23:07 +13:00
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-24 04:06:13 +13:00
|
|
|
|
public override void Close()
|
|
|
|
|
{
|
2019-11-26 00:10:47 +13:00
|
|
|
|
StopRequested = true;
|
|
|
|
|
|
2019-11-24 04:06:13 +13:00
|
|
|
|
if (IsProcessRunning && process != null)
|
|
|
|
|
{
|
|
|
|
|
if (closeTryCount >= 2)
|
|
|
|
|
{
|
|
|
|
|
process.Kill();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
WriteInput("q");
|
|
|
|
|
|
|
|
|
|
closeTryCount++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-04 21:39:27 +12:00
|
|
|
|
private void FFmpeg_DataReceived(object sender, DataReceivedEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
lock (this)
|
|
|
|
|
{
|
2019-11-23 03:14:24 +13:00
|
|
|
|
string data = e.Data;
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(data))
|
2015-08-04 21:39:27 +12:00
|
|
|
|
{
|
2019-11-23 03:14:24 +13:00
|
|
|
|
Output.AppendLine(data);
|
|
|
|
|
|
2019-11-24 04:06:13 +13:00
|
|
|
|
if (!IsEncoding && data.Contains("Press [q] to stop", StringComparison.InvariantCultureIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
IsEncoding = true;
|
|
|
|
|
|
|
|
|
|
OnEncodeStarted();
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-23 03:14:24 +13:00
|
|
|
|
if (TrackEncodeProgress)
|
|
|
|
|
{
|
2019-11-23 21:23:07 +13:00
|
|
|
|
UpdateEncodeProgress(data);
|
2019-11-23 03:14:24 +13:00
|
|
|
|
}
|
2015-08-04 21:39:27 +12:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-23 21:23:07 +13:00
|
|
|
|
private void UpdateEncodeProgress(string data)
|
2019-11-23 03:14:24 +13:00
|
|
|
|
{
|
2019-11-23 21:23:07 +13:00
|
|
|
|
if (VideoDuration.Ticks == 0)
|
|
|
|
|
{
|
|
|
|
|
// Duration: 00:00:15.32, start: 0.000000, bitrate: 1095 kb/s
|
|
|
|
|
Match match = Regex.Match(data, @"Duration:\s*(\d+:\d+:\d+\.\d+),\s*start:", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
2019-11-23 03:14:24 +13:00
|
|
|
|
|
2019-11-23 21:23:07 +13:00
|
|
|
|
if (match.Success)
|
|
|
|
|
{
|
|
|
|
|
TimeSpan duration;
|
2019-11-17 14:03:54 +13:00
|
|
|
|
|
2019-11-23 21:23:07 +13:00
|
|
|
|
if (TimeSpan.TryParse(match.Groups[1].Value, out duration))
|
|
|
|
|
{
|
|
|
|
|
VideoDuration = duration;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
2019-11-17 14:03:54 +13:00
|
|
|
|
{
|
2019-11-23 21:23:07 +13:00
|
|
|
|
//frame= 942 fps=187 q=35.0 size= 3072kB time=00:00:38.10 bitrate= 660.5kbits/s speed=7.55x
|
|
|
|
|
Match match = Regex.Match(data, @"time=\s*(\d+:\d+:\d+\.\d+)\s*bitrate=", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
|
|
|
|
|
|
|
|
|
if (match.Success)
|
|
|
|
|
{
|
|
|
|
|
TimeSpan time;
|
|
|
|
|
|
|
|
|
|
if (TimeSpan.TryParse(match.Groups[1].Value, out time))
|
|
|
|
|
{
|
|
|
|
|
EncodeTime = time;
|
|
|
|
|
EncodePercentage = ((float)EncodeTime.Ticks / VideoDuration.Ticks) * 100;
|
|
|
|
|
|
|
|
|
|
OnEncodeProgressChanged(EncodePercentage);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-11-17 14:03:54 +13:00
|
|
|
|
}
|
2019-11-23 21:23:07 +13:00
|
|
|
|
}
|
|
|
|
|
|
2019-11-24 04:06:13 +13:00
|
|
|
|
protected void OnEncodeStarted()
|
|
|
|
|
{
|
|
|
|
|
EncodeStarted?.Invoke();
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-23 21:23:07 +13:00
|
|
|
|
protected void OnEncodeProgressChanged(float percentage)
|
|
|
|
|
{
|
|
|
|
|
EncodeProgressChanged?.Invoke(percentage);
|
2019-11-17 14:03:54 +13:00
|
|
|
|
}
|
|
|
|
|
|
2015-08-04 21:39:27 +12:00
|
|
|
|
public VideoInfo GetVideoInfo(string videoPath)
|
|
|
|
|
{
|
2015-08-06 22:46:24 +12:00
|
|
|
|
VideoInfo videoInfo = new VideoInfo();
|
|
|
|
|
videoInfo.FilePath = videoPath;
|
|
|
|
|
|
2020-04-10 04:22:55 +12:00
|
|
|
|
Run($"-i \"{videoPath}\" -hide_banner");
|
|
|
|
|
string output = Output.ToString();
|
2015-08-04 21:39:27 +12:00
|
|
|
|
|
2020-04-10 04:22:55 +12:00
|
|
|
|
Match matchInput = Regex.Match(output, @"Duration: (?<Duration>\d{2}:\d{2}:\d{2}\.\d{2}),.+?start: (?<Start>\d+\.\d+),.+?bitrate: (?<Bitrate>\d+) kb/s",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
2015-08-06 22:46:24 +12:00
|
|
|
|
|
2020-04-10 04:22:55 +12:00
|
|
|
|
if (matchInput.Success)
|
|
|
|
|
{
|
|
|
|
|
videoInfo.Duration = TimeSpan.Parse(matchInput.Groups["Duration"].Value);
|
|
|
|
|
//videoInfo.Start = TimeSpan.Parse(match.Groups["Start"].Value);
|
|
|
|
|
videoInfo.Bitrate = int.Parse(matchInput.Groups["Bitrate"].Value);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2015-08-06 22:46:24 +12:00
|
|
|
|
|
2020-04-10 04:22:55 +12:00
|
|
|
|
Match matchVideoStream = Regex.Match(output, @"Stream #\d+:\d+(?:\(.+?\))?: Video: (?<Codec>.+?) \(.+?,.+?, (?<Width>\d+)x(?<Height>\d+).+?, (?<FPS>\d+(?:\.\d+)?) fps",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
2015-08-05 22:18:16 +12:00
|
|
|
|
|
2020-04-10 04:22:55 +12:00
|
|
|
|
if (matchVideoStream.Success)
|
|
|
|
|
{
|
|
|
|
|
videoInfo.VideoCodec = matchVideoStream.Groups["Codec"].Value;
|
|
|
|
|
videoInfo.VideoResolution = new Size(int.Parse(matchVideoStream.Groups["Width"].Value), int.Parse(matchVideoStream.Groups["Height"].Value));
|
|
|
|
|
videoInfo.VideoFPS = double.Parse(matchVideoStream.Groups["FPS"].Value, CultureInfo.InvariantCulture);
|
|
|
|
|
}
|
2015-08-06 22:46:24 +12:00
|
|
|
|
|
2020-04-10 04:22:55 +12:00
|
|
|
|
Match matchAudioStream = Regex.Match(output, @"Stream #\d+:\d+(?:\(.+?\))?: Audio: (?<Codec>.+?)(?: \(|,)",
|
|
|
|
|
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
2015-08-06 22:46:24 +12:00
|
|
|
|
|
2020-04-10 04:22:55 +12:00
|
|
|
|
if (matchAudioStream.Success)
|
|
|
|
|
{
|
|
|
|
|
videoInfo.AudioCodec = matchAudioStream.Groups["Codec"].Value;
|
2015-08-04 21:39:27 +12:00
|
|
|
|
}
|
|
|
|
|
|
2015-08-06 22:46:24 +12:00
|
|
|
|
return videoInfo;
|
2015-08-04 21:39:27 +12:00
|
|
|
|
}
|
2019-11-24 01:26:56 +13:00
|
|
|
|
|
|
|
|
|
public DirectShowDevices GetDirectShowDevices()
|
|
|
|
|
{
|
|
|
|
|
DirectShowDevices devices = new DirectShowDevices();
|
|
|
|
|
|
|
|
|
|
Run("-list_devices true -f dshow -i dummy");
|
|
|
|
|
|
|
|
|
|
string output = Output.ToString();
|
|
|
|
|
string[] lines = output.Lines();
|
|
|
|
|
bool isVideo = true;
|
|
|
|
|
Regex regex = new Regex(@"\[dshow @ \w+\] ""(.+)""", RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
|
|
|
|
|
|
|
|
|
foreach (string line in lines)
|
|
|
|
|
{
|
|
|
|
|
if (line.Contains("] DirectShow video devices", StringComparison.InvariantCulture))
|
|
|
|
|
{
|
|
|
|
|
isVideo = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (line.Contains("] DirectShow audio devices", StringComparison.InvariantCulture))
|
|
|
|
|
{
|
|
|
|
|
isVideo = false;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Match match = regex.Match(line);
|
|
|
|
|
|
|
|
|
|
if (match.Success)
|
|
|
|
|
{
|
|
|
|
|
string value = match.Groups[1].Value;
|
|
|
|
|
|
|
|
|
|
if (isVideo)
|
|
|
|
|
{
|
|
|
|
|
devices.VideoDevices.Add(value);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
devices.AudioDevices.Add(value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return devices;
|
|
|
|
|
}
|
2015-08-04 21:39:27 +12:00
|
|
|
|
}
|
|
|
|
|
}
|