2014-05-04 06:55:15 +12:00
#region License Information ( GPL v3 )
/ *
ShareX - A program that allows you to take screenshots and share any file type
2023-01-10 09:31:02 +13:00
Copyright ( c ) 2007 - 2023 ShareX Team
2014-05-04 06:55:15 +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 )
2014-12-11 09:25:20 +13:00
using ShareX.HelpersLib ;
2014-05-04 06:55:15 +12:00
using System ;
using System.Drawing ;
2014-06-08 07:29:26 +12:00
using System.Globalization ;
2014-05-09 23:45:10 +12:00
using System.IO ;
2023-07-14 09:52:22 +12:00
using System.Linq ;
2014-05-04 06:55:15 +12:00
using System.Text ;
2023-07-07 20:37:40 +12:00
using System.Windows.Forms ;
2014-05-04 06:55:15 +12:00
2014-12-11 09:25:20 +13:00
namespace ShareX.ScreenCaptureLib
2014-05-04 06:55:15 +12:00
{
2022-02-07 20:49:09 +13:00
public class ScreenRecordingOptions
2014-05-04 06:55:15 +12:00
{
2018-10-02 07:14:32 +13:00
public bool IsRecording { get ; set ; }
public bool IsLossless { get ; set ; }
public string InputPath { get ; set ; }
2015-01-30 19:50:10 +13:00
public string OutputPath { get ; set ; }
2018-10-02 07:14:32 +13:00
public int FPS { get ; set ; }
2015-01-30 19:50:10 +13:00
public Rectangle CaptureArea { get ; set ; }
public float Duration { get ; set ; }
public bool DrawCursor { get ; set ; }
2016-12-03 05:35:34 +13:00
public FFmpegOptions FFmpeg { get ; set ; } = new FFmpegOptions ( ) ;
2014-05-09 23:45:10 +12:00
2014-06-04 11:18:41 +12:00
public string GetFFmpegCommands ( )
{
2014-06-08 07:29:26 +12:00
string commands ;
2023-07-19 17:02:43 +12:00
if ( IsRecording & & ! string . IsNullOrEmpty ( FFmpeg . VideoSource ) & &
FFmpeg . VideoSource . Equals ( FFmpegCaptureDevice . ScreenCaptureRecorder . Value , StringComparison . OrdinalIgnoreCase ) )
2014-06-04 11:18:41 +12:00
{
2014-06-05 02:08:07 +12:00
// https://github.com/rdp/screen-capture-recorder-to-video-windows-free
string registryPath = "Software\\screen-capture-recorder" ;
RegistryHelpers . CreateRegistry ( registryPath , "start_x" , CaptureArea . X ) ;
RegistryHelpers . CreateRegistry ( registryPath , "start_y" , CaptureArea . Y ) ;
RegistryHelpers . CreateRegistry ( registryPath , "capture_width" , CaptureArea . Width ) ;
RegistryHelpers . CreateRegistry ( registryPath , "capture_height" , CaptureArea . Height ) ;
2015-01-30 19:50:10 +13:00
RegistryHelpers . CreateRegistry ( registryPath , "default_max_fps" , 60 ) ;
2014-06-05 02:08:07 +12:00
RegistryHelpers . CreateRegistry ( registryPath , "capture_mouse_default_1" , DrawCursor ? 1 : 0 ) ;
2014-06-04 11:18:41 +12:00
}
2018-10-02 07:14:32 +13:00
if ( ! IsLossless & & FFmpeg . UseCustomCommands & & ! string . IsNullOrEmpty ( FFmpeg . CustomCommands ) )
2014-06-04 11:18:41 +12:00
{
2014-06-09 14:21:18 +12:00
commands = FFmpeg . CustomCommands .
2022-10-15 12:10:59 +13:00
Replace ( "$fps$" , FPS . ToString ( ) , StringComparison . OrdinalIgnoreCase ) .
Replace ( "$area_x$" , CaptureArea . X . ToString ( ) , StringComparison . OrdinalIgnoreCase ) .
Replace ( "$area_y$" , CaptureArea . Y . ToString ( ) , StringComparison . OrdinalIgnoreCase ) .
Replace ( "$area_width$" , CaptureArea . Width . ToString ( ) , StringComparison . OrdinalIgnoreCase ) .
Replace ( "$area_height$" , CaptureArea . Height . ToString ( ) , StringComparison . OrdinalIgnoreCase ) .
Replace ( "$cursor$" , DrawCursor ? "1" : "0" , StringComparison . OrdinalIgnoreCase ) .
Replace ( "$duration$" , Duration . ToString ( "0.0" , CultureInfo . InvariantCulture ) , StringComparison . OrdinalIgnoreCase ) .
Replace ( "$output$" , Path . ChangeExtension ( OutputPath , FFmpeg . Extension ) , StringComparison . OrdinalIgnoreCase ) ;
2014-06-08 07:29:26 +12:00
}
else
{
commands = GetFFmpegArgs ( ) ;
2014-06-04 11:18:41 +12:00
}
2014-06-04 23:55:25 +12:00
2014-06-08 07:29:26 +12:00
return commands . Trim ( ) ;
2014-06-04 11:18:41 +12:00
}
2014-06-08 07:29:26 +12:00
public string GetFFmpegArgs ( bool isCustom = false )
2014-05-09 23:45:10 +12:00
{
2018-10-02 07:14:32 +13:00
if ( IsRecording & & ! FFmpeg . IsVideoSourceSelected & & ! FFmpeg . IsAudioSourceSelected )
2014-08-29 20:22:51 +12:00
{
return null ;
}
2014-06-04 09:16:33 +12:00
2014-08-29 20:22:51 +12:00
StringBuilder args = new StringBuilder ( ) ;
2021-11-03 14:07:03 +13:00
args . Append ( "-hide_banner " ) ; // All FFmpeg tools will normally show a copyright notice, build options and library versions. This option can be used to suppress printing this information.
2014-05-13 20:44:35 +12:00
2021-12-24 20:58:26 +13:00
string framerate = isCustom ? "$fps$" : FPS . ToString ( ) ;
2014-06-08 07:29:26 +12:00
2018-10-02 07:14:32 +13:00
if ( IsRecording )
2014-05-13 20:44:35 +12:00
{
2018-10-02 07:14:32 +13:00
if ( FFmpeg . IsVideoSourceSelected )
2014-05-13 20:44:35 +12:00
{
2023-07-19 17:02:43 +12:00
if ( FFmpeg . VideoSource . Equals ( FFmpegCaptureDevice . GDIGrab . Value , StringComparison . OrdinalIgnoreCase ) )
2014-08-29 20:22:51 +12:00
{
2021-12-24 20:58:26 +13:00
string x = isCustom ? "$area_x$" : CaptureArea . X . ToString ( ) ;
string y = isCustom ? "$area_y$" : CaptureArea . Y . ToString ( ) ;
string width = isCustom ? "$area_width$" : CaptureArea . Width . ToString ( ) ;
string height = isCustom ? "$area_height$" : CaptureArea . Height . ToString ( ) ;
string cursor = isCustom ? "$cursor$" : DrawCursor ? "1" : "0" ;
2023-07-19 17:02:43 +12:00
// https://ffmpeg.org/ffmpeg-devices.html#gdigrab
2021-12-24 20:58:26 +13:00
AppendInputDevice ( args , "gdigrab" , false ) ;
args . Append ( $"-framerate {framerate} " ) ;
args . Append ( $"-offset_x {x} " ) ;
args . Append ( $"-offset_y {y} " ) ;
args . Append ( $"-video_size {width}x{height} " ) ;
args . Append ( $"-draw_mouse {cursor} " ) ;
args . Append ( "-i desktop " ) ;
2018-10-02 07:14:32 +13:00
if ( FFmpeg . IsAudioSourceSelected )
{
2021-12-24 20:58:26 +13:00
AppendInputDevice ( args , "dshow" , true ) ;
args . Append ( $"-i audio={Helpers.EscapeCLIText(FFmpeg.AudioSource)} " ) ;
2018-10-02 07:14:32 +13:00
}
2014-08-29 20:22:51 +12:00
}
2023-07-19 17:02:43 +12:00
else if ( FFmpeg . VideoSource . Equals ( FFmpegCaptureDevice . DDAGrab . Value , StringComparison . OrdinalIgnoreCase ) )
2023-07-07 20:37:40 +12:00
{
2023-07-14 09:52:22 +12:00
Screen [ ] screens = Screen . AllScreens . OrderBy ( x = > ! x . Primary ) . ToArray ( ) ;
2023-07-07 20:37:40 +12:00
int monitorIndex = 0 ;
2023-07-14 09:52:22 +12:00
Rectangle captureArea = screens [ 0 ] . Bounds ;
2023-07-07 20:37:40 +12:00
int maxIntersectionArea = 0 ;
2023-07-14 09:52:22 +12:00
for ( int i = 0 ; i < screens . Length ; i + + )
2023-07-07 20:37:40 +12:00
{
2023-07-14 09:52:22 +12:00
Screen screen = screens [ i ] ;
2023-07-07 20:37:40 +12:00
Rectangle intersection = Rectangle . Intersect ( screen . Bounds , CaptureArea ) ;
int intersectionArea = intersection . Width * intersection . Height ;
if ( intersectionArea > maxIntersectionArea )
{
maxIntersectionArea = intersectionArea ;
monitorIndex = i ;
captureArea = new Rectangle ( intersection . X - screen . Bounds . X , intersection . Y - screen . Bounds . Y , intersection . Width , intersection . Height ) ;
}
}
2023-07-14 09:52:22 +12:00
if ( FFmpeg . IsEvenSizeRequired )
{
captureArea = CaptureHelpers . EvenRectangleSize ( captureArea ) ;
}
2023-07-07 20:37:40 +12:00
// https://ffmpeg.org/ffmpeg-filters.html#ddagrab
AppendInputDevice ( args , "lavfi" , FFmpeg . IsAudioSourceSelected ) ;
args . Append ( "-i ddagrab=" ) ;
args . Append ( $"output_idx={monitorIndex}:" ) ; // DXGI Output Index to capture.
args . Append ( $"draw_mouse={DrawCursor.ToString().ToLowerInvariant()}:" ) ; // Whether to draw the mouse cursor.
args . Append ( $"framerate={framerate}:" ) ; // Framerate at which the desktop will be captured.
args . Append ( $"offset_x={captureArea.X}:" ) ; // Horizontal offset of the captured video.
args . Append ( $"offset_y={captureArea.Y}:" ) ; // Vertical offset of the captured video.
args . Append ( $"video_size={captureArea.Width}x{captureArea.Height}:" ) ; // Specify the size of the captured video.
args . Append ( "output_fmt=bgra" ) ; // Desired filter output format.
if ( FFmpeg . VideoCodec ! = FFmpegVideoCodec . h264_nvenc & & FFmpeg . VideoCodec ! = FFmpegVideoCodec . hevc_nvenc )
{
args . Append ( ",hwdownload" ) ;
args . Append ( ",format=bgra" ) ;
}
args . Append ( " " ) ;
}
2014-08-29 20:22:51 +12:00
else
{
2023-07-19 17:02:43 +12:00
// https://ffmpeg.org/ffmpeg-devices.html#dshow
2021-12-24 20:58:26 +13:00
AppendInputDevice ( args , "dshow" , FFmpeg . IsAudioSourceSelected ) ;
args . Append ( $"-framerate {framerate} " ) ;
args . Append ( $"-i video={Helpers.EscapeCLIText(FFmpeg.VideoSource)}" ) ;
2018-10-02 07:14:32 +13:00
if ( FFmpeg . IsAudioSourceSelected )
{
2021-12-24 20:58:26 +13:00
args . Append ( $":audio={Helpers.EscapeCLIText(FFmpeg.AudioSource)} " ) ;
2018-10-02 07:14:32 +13:00
}
else
{
args . Append ( " " ) ;
}
2014-08-29 20:22:51 +12:00
}
2014-05-13 20:44:35 +12:00
}
2018-10-02 07:14:32 +13:00
else if ( FFmpeg . IsAudioSourceSelected )
{
2021-12-24 20:58:26 +13:00
AppendInputDevice ( args , "dshow" , true ) ;
args . Append ( $"-i audio={Helpers.EscapeCLIText(FFmpeg.AudioSource)} " ) ;
2018-10-02 07:14:32 +13:00
}
2014-05-13 20:44:35 +12:00
}
2018-10-02 07:14:32 +13:00
else
2014-08-29 20:22:51 +12:00
{
2018-10-02 07:14:32 +13:00
args . Append ( $"-i \" { InputPath } \ " " ) ;
2014-08-29 20:22:51 +12:00
}
2014-05-09 23:45:10 +12:00
2014-05-12 22:04:14 +12:00
if ( ! string . IsNullOrEmpty ( FFmpeg . UserArgs ) )
{
args . Append ( FFmpeg . UserArgs + " " ) ;
}
2014-08-29 20:34:12 +12:00
if ( FFmpeg . IsVideoSourceSelected )
2014-05-09 23:45:10 +12:00
{
2018-10-21 07:59:10 +13:00
if ( IsLossless | | FFmpeg . VideoCodec ! = FFmpegVideoCodec . apng )
2015-06-03 23:32:34 +12:00
{
2018-10-21 07:59:10 +13:00
string videoCodec ;
if ( IsLossless )
{
videoCodec = FFmpegVideoCodec . libx264 . ToString ( ) ;
}
2019-11-29 05:54:07 +13:00
else if ( FFmpeg . VideoCodec = = FFmpegVideoCodec . libvpx_vp9 )
{
videoCodec = "libvpx-vp9" ;
}
2018-10-21 07:59:10 +13:00
else
{
videoCodec = FFmpeg . VideoCodec . ToString ( ) ;
}
2015-06-03 23:32:34 +12:00
2021-12-24 20:58:26 +13:00
args . Append ( $"-c:v {videoCodec} " ) ;
args . Append ( $"-r {framerate} " ) ; // output FPS
2018-10-21 07:59:10 +13:00
}
2014-08-29 20:22:51 +12:00
2018-10-02 07:14:32 +13:00
if ( IsLossless )
2014-08-29 20:22:51 +12:00
{
2021-12-24 20:58:26 +13:00
args . Append ( $"-preset {FFmpegPreset.ultrafast} " ) ;
args . Append ( $"-tune {FFmpegTune.zerolatency} " ) ;
args . Append ( "-qp 0 " ) ;
2018-10-02 07:14:32 +13:00
}
else
{
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
2021-12-24 20:58:26 +13:00
args . Append ( $"-preset {FFmpeg.x264_Preset} " ) ;
if ( IsRecording ) args . Append ( $"-tune {FFmpegTune.zerolatency} " ) ;
2022-02-07 22:36:02 +13:00
if ( FFmpeg . x264_Use_Bitrate )
{
args . Append ( $"-b:v {FFmpeg.x264_Bitrate}k " ) ;
}
else
{
args . Append ( $"-crf {FFmpeg.x264_CRF} " ) ;
}
2021-12-24 20:58:26 +13:00
args . Append ( "-pix_fmt yuv420p " ) ; // -pix_fmt yuv420p required otherwise can't stream in Chrome
args . Append ( "-movflags +faststart " ) ; // This will move some information to the beginning of your file and allow the video to begin playing before it is completely downloaded by the viewer
2018-10-02 07:14:32 +13:00
break ;
case FFmpegVideoCodec . libvpx : // https://trac.ffmpeg.org/wiki/Encode/VP8
2019-11-29 05:54:07 +13:00
case FFmpegVideoCodec . libvpx_vp9 : // https://trac.ffmpeg.org/wiki/Encode/VP9
2021-12-24 20:58:26 +13:00
if ( IsRecording ) args . Append ( "-deadline realtime " ) ;
2022-02-07 21:14:16 +13:00
args . Append ( $"-b:v {FFmpeg.VPx_Bitrate}k " ) ;
2021-12-24 20:58:26 +13:00
args . Append ( "-pix_fmt yuv420p " ) ; // -pix_fmt yuv420p required otherwise causing issues in Chrome related to WebM transparency support
2018-10-02 07:14:32 +13:00
break ;
case FFmpegVideoCodec . libxvid : // https://trac.ffmpeg.org/wiki/Encode/MPEG-4
2022-02-07 21:14:16 +13:00
args . Append ( $"-qscale:v {FFmpeg.XviD_QScale} " ) ;
2018-10-02 07:14:32 +13:00
break ;
case FFmpegVideoCodec . h264_nvenc : // https://trac.ffmpeg.org/wiki/HWAccelIntro#NVENC
case FFmpegVideoCodec . hevc_nvenc :
2022-02-07 21:14:16 +13:00
args . Append ( $"-preset {FFmpeg.NVENC_Preset} " ) ;
args . Append ( $"-b:v {FFmpeg.NVENC_Bitrate}k " ) ;
2021-12-24 20:58:26 +13:00
args . Append ( "-movflags +faststart " ) ; // This will move some information to the beginning of your file and allow the video to begin playing before it is completely downloaded by the viewer
2018-10-02 07:14:32 +13:00
break ;
2018-10-31 12:31:10 +13:00
case FFmpegVideoCodec . h264_amf :
case FFmpegVideoCodec . hevc_amf :
2022-02-07 21:14:16 +13:00
args . Append ( $"-usage {FFmpeg.AMF_Usage} " ) ;
args . Append ( $"-quality {FFmpeg.AMF_Quality} " ) ;
2023-07-22 17:23:20 +12:00
args . Append ( $"-b:v {FFmpeg.AMF_Bitrate}k " ) ;
2021-12-24 20:58:26 +13:00
args . Append ( "-pix_fmt yuv420p " ) ;
2018-10-31 12:31:10 +13:00
break ;
2019-03-15 07:33:18 +13:00
case FFmpegVideoCodec . h264_qsv : // https://trac.ffmpeg.org/wiki/Hardware/QuickSync
case FFmpegVideoCodec . hevc_qsv :
2022-02-07 21:14:16 +13:00
args . Append ( $"-preset {FFmpeg.QSV_Preset} " ) ;
args . Append ( $"-b:v {FFmpeg.QSV_Bitrate}k " ) ;
2019-03-15 07:33:18 +13:00
break ;
2019-11-29 04:43:18 +13:00
case FFmpegVideoCodec . libwebp : // https://www.ffmpeg.org/ffmpeg-codecs.html#libwebp
2021-12-24 20:58:26 +13:00
args . Append ( "-lossless 0 " ) ;
args . Append ( "-preset default " ) ;
args . Append ( "-loop 0 " ) ;
2019-11-29 04:43:18 +13:00
break ;
case FFmpegVideoCodec . apng :
args . Append ( "-f apng " ) ;
2021-12-24 20:58:26 +13:00
args . Append ( "-plays 0 " ) ;
2019-11-29 04:43:18 +13:00
break ;
2018-10-02 07:14:32 +13:00
}
2014-08-29 20:22:51 +12:00
}
2014-05-09 23:45:10 +12:00
}
2014-08-29 20:34:12 +12:00
if ( FFmpeg . IsAudioSourceSelected )
2014-05-13 20:44:35 +12:00
{
2014-05-15 00:08:51 +12:00
switch ( FFmpeg . AudioCodec )
2014-05-14 23:18:18 +12:00
{
2015-05-16 12:21:59 +12:00
case FFmpegAudioCodec . libvoaacenc : // http://trac.ffmpeg.org/wiki/Encode/AAC
2022-02-07 21:14:16 +13:00
args . Append ( $"-c:a aac -ac 2 -b:a {FFmpeg.AAC_Bitrate}k " ) ; // -ac 2 required otherwise failing with 7.1
2014-05-27 22:36:37 +12:00
break ;
2019-10-22 01:51:32 +13:00
case FFmpegAudioCodec . libopus : // https://www.ffmpeg.org/ffmpeg-codecs.html#libopus-1
2022-02-07 21:14:16 +13:00
args . Append ( $"-c:a libopus -b:a {FFmpeg.Opus_Bitrate}k " ) ;
2019-10-22 01:51:32 +13:00
break ;
2014-05-15 00:08:51 +12:00
case FFmpegAudioCodec . libvorbis : // http://trac.ffmpeg.org/wiki/TheoraVorbisEncodingGuide
2022-02-07 21:14:16 +13:00
args . Append ( $"-c:a libvorbis -qscale:a {FFmpeg.Vorbis_QScale} " ) ;
2014-05-15 00:08:51 +12:00
break ;
2015-05-16 12:21:59 +12:00
case FFmpegAudioCodec . libmp3lame : // http://trac.ffmpeg.org/wiki/Encode/MP3
2022-02-07 21:14:16 +13:00
args . Append ( $"-c:a libmp3lame -qscale:a {FFmpeg.MP3_QScale} " ) ;
2014-05-15 00:08:51 +12:00
break ;
2014-05-14 23:18:18 +12:00
}
2014-05-09 23:45:10 +12:00
}
if ( Duration > 0 )
{
2021-12-24 20:58:26 +13:00
string duration = isCustom ? "$duration$" : Duration . ToString ( "0.0" , CultureInfo . InvariantCulture ) ;
args . Append ( $"-t {duration} " ) ; // duration limit
2014-05-09 23:45:10 +12:00
}
2019-11-17 08:42:35 +13:00
args . Append ( "-y " ) ; // overwrite file
2021-12-24 20:58:26 +13:00
string output = isCustom ? "$output$" : Path . ChangeExtension ( OutputPath , IsLossless ? "mp4" : FFmpeg . Extension ) ;
args . Append ( $"\" { output } \ "" ) ;
2018-10-19 09:15:26 +13:00
2021-12-24 20:58:26 +13:00
return args . ToString ( ) ;
}
2018-10-19 09:15:26 +13:00
2021-12-24 20:58:26 +13:00
private void AppendInputDevice ( StringBuilder args , string inputDevice , bool audioSource )
{
args . Append ( $"-f {inputDevice} " ) ;
args . Append ( "-thread_queue_size 1024 " ) ; // This option sets the maximum number of queued packets when reading from the file or device.
args . Append ( "-rtbufsize 256M " ) ; // Default real time buffer size is 3041280 (3M)
2014-05-09 23:45:10 +12:00
2021-12-24 20:58:26 +13:00
if ( audioSource )
{
args . Append ( "-audio_buffer_size 80 " ) ; // Set audio device buffer size in milliseconds (which can directly impact latency, depending on the device).
}
2014-05-09 23:45:10 +12:00
}
2014-05-09 01:28:46 +12:00
}
2014-05-04 06:55:15 +12:00
}