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
|
2024-01-03 12:57:14 +13:00
|
|
|
|
Copyright (c) 2007-2024 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;
|
2022-09-03 22:50:09 +12:00
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
2015-08-04 21:39:27 +12:00
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
|
|
|
|
|
|
namespace ShareX.MediaLib
|
|
|
|
|
{
|
|
|
|
|
public class FFmpegCLIManager : ExternalCLIManager
|
|
|
|
|
{
|
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;
|
2023-04-23 20:17:58 +12:00
|
|
|
|
public const int av1_min = 0;
|
|
|
|
|
public const int av1_max = 63;
|
2019-12-02 05:50:41 +13:00
|
|
|
|
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)
|
|
|
|
|
{
|
2021-12-13 06:02:44 +13:00
|
|
|
|
OutputBox.Show(Output.ToString(), Resources.FFmpegError, true);
|
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);
|
|
|
|
|
|
2022-10-15 12:10:59 +13:00
|
|
|
|
if (!IsEncoding && data.Contains("Press [q] to stop", StringComparison.OrdinalIgnoreCase))
|
2019-11-24 04:06:13 +13:00
|
|
|
|
{
|
|
|
|
|
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
|
|
|
|
|
2021-06-10 10:14:01 +12:00
|
|
|
|
if (match.Success && TimeSpan.TryParse(match.Groups[1].Value, out TimeSpan duration))
|
2019-11-23 21:23:07 +13:00
|
|
|
|
{
|
2021-06-10 10:14:01 +12:00
|
|
|
|
VideoDuration = duration;
|
2019-11-23 21:23:07 +13:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
|
|
2021-06-10 10:14:01 +12:00
|
|
|
|
if (match.Success && TimeSpan.TryParse(match.Groups[1].Value, out TimeSpan time))
|
2019-11-23 21:23:07 +13:00
|
|
|
|
{
|
2021-06-10 10:14:01 +12:00
|
|
|
|
EncodeTime = time;
|
|
|
|
|
EncodePercentage = ((float)EncodeTime.Ticks / VideoDuration.Ticks) * 100;
|
2019-11-23 21:23:07 +13:00
|
|
|
|
|
2021-06-10 10:14:01 +12:00
|
|
|
|
OnEncodeProgressChanged(EncodePercentage);
|
2019-11-23 21:23:07 +13:00
|
|
|
|
}
|
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;
|
|
|
|
|
|
2023-11-28 14:44:42 +13:00
|
|
|
|
Run($"-i \"{videoPath}\"");
|
2020-04-10 04:22:55 +12:00
|
|
|
|
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();
|
|
|
|
|
|
2023-11-28 14:44:42 +13:00
|
|
|
|
Run("-list_devices true -f dshow -i dummy");
|
2019-11-24 01:26:56 +13:00
|
|
|
|
|
|
|
|
|
string output = Output.ToString();
|
|
|
|
|
string[] lines = output.Lines();
|
2022-01-30 14:31:07 +13:00
|
|
|
|
bool isAudio = false;
|
|
|
|
|
Regex regex = new Regex(@"\[dshow @ \w+\] +""(.+)""", RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
2019-11-24 01:26:56 +13:00
|
|
|
|
|
|
|
|
|
foreach (string line in lines)
|
|
|
|
|
{
|
2022-01-30 14:31:07 +13:00
|
|
|
|
if (line.Contains("] DirectShow video devices"))
|
2019-11-24 01:26:56 +13:00
|
|
|
|
{
|
2022-01-30 14:31:07 +13:00
|
|
|
|
isAudio = false;
|
2019-11-24 01:26:56 +13:00
|
|
|
|
continue;
|
|
|
|
|
}
|
2022-01-30 14:31:07 +13:00
|
|
|
|
else if (line.Contains("] DirectShow audio devices"))
|
2019-11-24 01:26:56 +13:00
|
|
|
|
{
|
2022-01-30 14:31:07 +13:00
|
|
|
|
isAudio = true;
|
2019-11-24 01:26:56 +13:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Match match = regex.Match(line);
|
|
|
|
|
|
|
|
|
|
if (match.Success)
|
|
|
|
|
{
|
2022-01-30 14:31:07 +13:00
|
|
|
|
if (line.EndsWith("\" (video)"))
|
|
|
|
|
{
|
|
|
|
|
isAudio = false;
|
|
|
|
|
}
|
|
|
|
|
else if (line.EndsWith("\" (audio)"))
|
|
|
|
|
{
|
|
|
|
|
isAudio = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string deviceName = match.Groups[1].Value;
|
2019-11-24 01:26:56 +13:00
|
|
|
|
|
2022-01-30 14:31:07 +13:00
|
|
|
|
if (isAudio)
|
2019-11-24 01:26:56 +13:00
|
|
|
|
{
|
2022-01-30 14:31:07 +13:00
|
|
|
|
devices.AudioDevices.Add(deviceName);
|
2019-11-24 01:26:56 +13:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2022-01-30 14:31:07 +13:00
|
|
|
|
devices.VideoDevices.Add(deviceName);
|
2019-11-24 01:26:56 +13:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return devices;
|
|
|
|
|
}
|
2022-09-03 22:50:09 +12:00
|
|
|
|
|
|
|
|
|
public void ConcatenateVideos(string[] inputFiles, string outputFile, bool autoDeleteInputFiles = false)
|
|
|
|
|
{
|
|
|
|
|
string listFile = outputFile + ".txt";
|
|
|
|
|
string contents = string.Join(Environment.NewLine, inputFiles.Select(inputFile => $"file '{inputFile}'"));
|
|
|
|
|
File.WriteAllText(listFile, contents);
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2023-11-28 14:44:42 +13:00
|
|
|
|
bool result = Run($"-f concat -safe 0 -i \"{listFile}\" -c copy \"{outputFile}\"");
|
2022-09-03 22:50:09 +12:00
|
|
|
|
|
|
|
|
|
if (result && autoDeleteInputFiles)
|
|
|
|
|
{
|
|
|
|
|
foreach (string inputFile in inputFiles)
|
|
|
|
|
{
|
2022-09-04 01:52:42 +12:00
|
|
|
|
if (File.Exists(inputFile))
|
2022-09-03 22:50:09 +12:00
|
|
|
|
{
|
|
|
|
|
File.Delete(inputFile);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
if (File.Exists(listFile))
|
|
|
|
|
{
|
|
|
|
|
File.Delete(listFile);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-08-04 21:39:27 +12:00
|
|
|
|
}
|
|
|
|
|
}
|