screen-recorder 1.1.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE.md +2 -0
- data/.github/workflows/tests.yml +97 -0
- data/.rspec +1 -1
- data/.rubocop.yml +83 -32
- data/{CHANGES.md → CHANGELOG.md} +37 -1
- data/Gemfile +3 -3
- data/README.md +281 -219
- data/Rakefile +10 -10
- data/bin/console +14 -14
- data/lib/screen-recorder.rb +34 -6
- data/lib/screen-recorder/common.rb +113 -56
- data/lib/screen-recorder/desktop.rb +5 -5
- data/lib/screen-recorder/errors.rb +10 -2
- data/lib/screen-recorder/options.rb +77 -59
- data/lib/screen-recorder/titles.rb +8 -41
- data/lib/screen-recorder/type_checker.rb +2 -0
- data/lib/screen-recorder/version.rb +1 -1
- data/lib/screen-recorder/window.rb +45 -9
- data/screen-recorder.gemspec +16 -12
- metadata +57 -23
- data/.travis.yml +0 -35
data/Rakefile
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
-
require 'bundler/gem_tasks'
|
2
|
-
require 'rspec/core/rake_task'
|
3
|
-
require 'rubocop/rake_task'
|
4
|
-
|
5
|
-
RuboCop::RakeTask.new do |task|
|
6
|
-
task.requires << 'rubocop-performance'
|
7
|
-
end
|
8
|
-
|
9
|
-
RSpec::Core::RakeTask.new(:spec)
|
10
|
-
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'rubocop/rake_task'
|
4
|
+
|
5
|
+
RuboCop::RakeTask.new do |task|
|
6
|
+
task.requires << 'rubocop-performance'
|
7
|
+
end
|
8
|
+
|
9
|
+
RSpec::Core::RakeTask.new(:spec)
|
10
|
+
|
11
11
|
task default: %w[spec rubocop]
|
data/bin/console
CHANGED
@@ -1,14 +1,14 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'bundler/setup'
|
4
|
-
require 'screen-recorder'
|
5
|
-
|
6
|
-
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
-
# with your gem easier. You can also use a different console, if you like.
|
8
|
-
|
9
|
-
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
-
# require "pry"
|
11
|
-
# Pry.start
|
12
|
-
|
13
|
-
require 'irb'
|
14
|
-
IRB.start(__FILE__)
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'screen-recorder'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require 'irb'
|
14
|
+
IRB.start(__FILE__)
|
data/lib/screen-recorder.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
-
require '
|
2
|
-
require 'os'
|
1
|
+
require 'childprocess'
|
3
2
|
require 'logger'
|
3
|
+
require 'os'
|
4
|
+
require 'streamio-ffmpeg'
|
4
5
|
|
5
6
|
# @since 1.0.0.beta11
|
6
7
|
module ScreenRecorder
|
@@ -11,14 +12,41 @@ module ScreenRecorder
|
|
11
12
|
# ScreenRecorder.ffmpeg_binary = 'C:\ffmpeg.exe'
|
12
13
|
#
|
13
14
|
def self.ffmpeg_binary=(bin)
|
15
|
+
ScreenRecorder.logger.debug 'Setting ffmpeg path...'
|
14
16
|
FFMPEG.ffmpeg_binary = bin
|
17
|
+
ScreenRecorder.logger.debug "ffmpeg path set: #{bin}"
|
18
|
+
ScreenRecorder.ffmpeg_binary
|
15
19
|
end
|
16
20
|
|
17
21
|
#
|
18
|
-
# Returns path to ffmpeg binary
|
22
|
+
# Returns path to ffmpeg binary or raises DependencyNotFound
|
19
23
|
#
|
20
24
|
def self.ffmpeg_binary
|
21
25
|
FFMPEG.ffmpeg_binary
|
26
|
+
rescue Errno::ENOENT # Raised when binary is not set in project or found in ENV
|
27
|
+
raise Errors::DependencyNotFound
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Uses user given ffprobe binary
|
32
|
+
#
|
33
|
+
# @example
|
34
|
+
# ScreenRecorder.ffprobe_binary= = 'C:\ffprobe.exe'
|
35
|
+
#
|
36
|
+
def self.ffprobe_binary=(bin)
|
37
|
+
ScreenRecorder.logger.debug 'Setting ffprobe path...'
|
38
|
+
FFMPEG.ffprobe_binary = bin
|
39
|
+
ScreenRecorder.logger.debug "ffprobe path set: #{bin}"
|
40
|
+
ScreenRecorder.ffmpeg_binary
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# Returns path to ffprobe binary or raises DependencyNotFound
|
45
|
+
#
|
46
|
+
def self.ffprobe_binary
|
47
|
+
FFMPEG.ffprobe_binary
|
48
|
+
rescue Errno::ENOENT # Raised when binary is not set in project or found in ENV
|
49
|
+
raise Errors::DependencyNotFound
|
22
50
|
end
|
23
51
|
|
24
52
|
#
|
@@ -34,9 +62,9 @@ module ScreenRecorder
|
|
34
62
|
def self.logger
|
35
63
|
return @logger if @logger
|
36
64
|
|
37
|
-
logger
|
38
|
-
logger.level
|
39
|
-
logger.progname
|
65
|
+
logger = Logger.new($stdout)
|
66
|
+
logger.level = Logger::ERROR
|
67
|
+
logger.progname = 'ScreenRecorder'
|
40
68
|
logger.formatter = proc do |severity, time, progname, msg|
|
41
69
|
"#{time.strftime('%F %T')} #{progname} - #{severity} - #{msg}\n"
|
42
70
|
end
|
@@ -1,10 +1,16 @@
|
|
1
1
|
# @since 1.0.0-beta11
|
2
2
|
module ScreenRecorder
|
3
3
|
# @since 1.0.0-beta11
|
4
|
+
#
|
5
|
+
# @api private
|
4
6
|
class Common
|
7
|
+
PROCESS_TIMEOUT = 5 # Seconds to wait for ffmpeg to quit
|
8
|
+
|
5
9
|
attr_reader :options, :video
|
6
10
|
|
7
11
|
def initialize(input:, output:, advanced: {})
|
12
|
+
raise Errors::DependencyNotFound unless ffmpeg_exists?
|
13
|
+
|
8
14
|
@options = Options.new(input: input, output: output, advanced: advanced)
|
9
15
|
@video = nil
|
10
16
|
@process = nil
|
@@ -14,11 +20,9 @@ module ScreenRecorder
|
|
14
20
|
# Starts the recording
|
15
21
|
#
|
16
22
|
def start
|
17
|
-
|
18
|
-
|
19
|
-
@process
|
20
|
-
elapsed = Time.now - start_time
|
21
|
-
ScreenRecorder.logger.debug "Process started in #{elapsed}s"
|
23
|
+
ScreenRecorder.logger.debug 'Starting recorder...'
|
24
|
+
@video = nil # New file
|
25
|
+
@process = start_ffmpeg
|
22
26
|
ScreenRecorder.logger.info 'Recording...'
|
23
27
|
@process
|
24
28
|
end
|
@@ -27,11 +31,27 @@ module ScreenRecorder
|
|
27
31
|
# Stops the recording
|
28
32
|
#
|
29
33
|
def stop
|
30
|
-
ScreenRecorder.logger.debug 'Stopping ffmpeg
|
31
|
-
|
32
|
-
|
34
|
+
ScreenRecorder.logger.debug 'Stopping ffmpeg...'
|
35
|
+
exit_code = stop_ffmpeg
|
36
|
+
return if exit_code == 1 # recording failed
|
37
|
+
|
38
|
+
ScreenRecorder.logger.debug 'Stopped ffmpeg.'
|
33
39
|
ScreenRecorder.logger.info 'Recording complete.'
|
34
|
-
@video =
|
40
|
+
@video = prepare_video unless exit_code == 1
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# Takes a screenshot in the current context (input) - desktop or current window
|
45
|
+
#
|
46
|
+
def screenshot(filename)
|
47
|
+
process = execute_command(screenshot_cmd(filename))
|
48
|
+
exit_code = wait_for_process_exit(process) # 0 (success) or 1 (fail)
|
49
|
+
if exit_code&.zero?
|
50
|
+
ScreenRecorder.logger.info "Screenshot: #{filename}"
|
51
|
+
return filename
|
52
|
+
end
|
53
|
+
ScreenRecorder.logger.error 'Failed to take a screenshot.'
|
54
|
+
nil
|
35
55
|
end
|
36
56
|
|
37
57
|
#
|
@@ -40,7 +60,7 @@ module ScreenRecorder
|
|
40
60
|
# needed.
|
41
61
|
#
|
42
62
|
def discard
|
43
|
-
|
63
|
+
File.delete options.output
|
44
64
|
end
|
45
65
|
|
46
66
|
alias delete discard
|
@@ -52,75 +72,81 @@ module ScreenRecorder
|
|
52
72
|
# the given options.
|
53
73
|
#
|
54
74
|
def start_ffmpeg
|
55
|
-
|
75
|
+
process = execute_command(ffmpeg_command, options.log)
|
76
|
+
sleep(1.5) # Takes ~1.5s to initialize ffmpeg
|
77
|
+
# Check if it exited unexpectedly
|
78
|
+
raise FFMPEG::Error, "Failed to start ffmpeg. Reason: #{lines_from_log(:last, 2)}" if process.exited?
|
56
79
|
|
57
|
-
ScreenRecorder.logger.debug "Command: #{command}"
|
58
|
-
process = IO.popen(command, 'r+')
|
59
|
-
sleep(1.5) # Takes ~1.5s on average to initialize
|
60
80
|
process
|
61
81
|
end
|
62
82
|
|
63
83
|
#
|
64
84
|
# Sends 'q' to the ffmpeg binary to gracefully stop the process.
|
65
|
-
# Forcefully terminates it if it takes more than
|
85
|
+
# Forcefully terminates it if it takes more than 5s.
|
66
86
|
#
|
67
87
|
def stop_ffmpeg
|
68
|
-
@process.puts 'q' # Gracefully exit ffmpeg
|
69
|
-
|
70
|
-
@
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
88
|
+
@process.io.stdin.puts 'q' # Gracefully exit ffmpeg
|
89
|
+
@process.io.stdin.close
|
90
|
+
@log_file.close
|
91
|
+
wait_for_process_exit(@process)
|
92
|
+
end
|
93
|
+
|
94
|
+
#
|
95
|
+
# Runs ffprobe on the output video file and returns
|
96
|
+
# a FFMPEG::Movie object.
|
97
|
+
#
|
98
|
+
def prepare_video
|
99
|
+
max_attempts = 3
|
100
|
+
attempts_made = 0
|
101
|
+
delay = 1.0
|
102
|
+
|
103
|
+
begin # Fixes #79
|
104
|
+
ScreenRecorder.logger.info 'Running ffprobe to prepare video (output) file.'
|
105
|
+
FFMPEG::Movie.new(options.output)
|
106
|
+
rescue Errno::EAGAIN, Errno::EACCES
|
107
|
+
attempts_made += 1
|
108
|
+
ScreenRecorder.logger.error "Failed to run ffprobe. Retrying... (#{attempts_made}/#{max_attempts})"
|
109
|
+
sleep(delay)
|
110
|
+
retry if attempts_made < max_attempts
|
111
|
+
raise
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def ffmpeg_bin
|
116
|
+
"#{ScreenRecorder.ffmpeg_binary} -y"
|
80
117
|
end
|
81
118
|
|
82
119
|
#
|
83
120
|
# Generates the command line arguments based on the given
|
84
121
|
# options.
|
85
122
|
#
|
86
|
-
def
|
87
|
-
|
88
|
-
cmd << @options.parsed
|
123
|
+
def ffmpeg_command
|
124
|
+
"#{ffmpeg_bin} #{@options.parsed}"
|
89
125
|
end
|
90
126
|
|
91
127
|
#
|
92
|
-
#
|
93
|
-
# after 'q' is sent to the ffmpeg process.
|
128
|
+
# Parameters to capture a single frame
|
94
129
|
#
|
95
|
-
def
|
96
|
-
|
97
|
-
|
98
|
-
sleep(0.1) until @process.eof?
|
99
|
-
end
|
100
|
-
ScreenRecorder.logger.debug "IO#eof? #{@process.eof?}"
|
101
|
-
Time.now - start
|
130
|
+
def screenshot_cmd(filename)
|
131
|
+
# -f overwrites existing file
|
132
|
+
"#{ffmpeg_bin} -f #{options.capture_device} -i #{options.input} -framerate 1 -frames:v 1 #{filename}"
|
102
133
|
end
|
103
134
|
|
104
135
|
#
|
105
136
|
# Returns true if ffmpeg binary is found.
|
106
137
|
#
|
107
138
|
def ffmpeg_exists?
|
108
|
-
return
|
109
|
-
|
110
|
-
return !`where ffmpeg`.empty? if OS.windows?
|
139
|
+
return true if FFMPEG.ffmpeg_binary
|
111
140
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
return false if ScreenRecorder.ffmpeg_binary == 'ffmpeg'
|
116
|
-
|
117
|
-
true
|
141
|
+
false
|
142
|
+
rescue Errno::ENOENT # Raised when binary is not set in project or found in ENV
|
143
|
+
false
|
118
144
|
end
|
119
145
|
|
120
146
|
#
|
121
147
|
# Returns lines from the log file
|
122
148
|
#
|
123
|
-
def
|
149
|
+
def lines_from_log(position = :last, count = 2)
|
124
150
|
f = File.open(options.log)
|
125
151
|
lines = f.readlines
|
126
152
|
lines = lines.last(count) if position == :last
|
@@ -131,17 +157,48 @@ module ScreenRecorder
|
|
131
157
|
end
|
132
158
|
|
133
159
|
#
|
134
|
-
#
|
160
|
+
# Executes the given command and outputs to the
|
161
|
+
# optional logfile
|
135
162
|
#
|
136
|
-
def
|
163
|
+
def execute_command(cmd, logfile = nil)
|
164
|
+
ScreenRecorder.logger.debug "Executing command: #{cmd}"
|
165
|
+
process = new_process(cmd)
|
166
|
+
process.duplex = true
|
167
|
+
if logfile
|
168
|
+
@log_file = File.new(logfile, 'w+')
|
169
|
+
process.io.stdout = process.io.stderr = @log_file
|
170
|
+
@log_file.sync = true
|
171
|
+
end
|
172
|
+
process.start
|
173
|
+
process
|
174
|
+
end
|
175
|
+
|
176
|
+
#
|
177
|
+
# Calls Childprocess.new with OS specific arguments
|
178
|
+
# to start the given process.
|
179
|
+
#
|
180
|
+
def new_process(process)
|
181
|
+
ChildProcess.posix_spawn = true if RUBY_PLATFORM == 'java' # Support JRuby.
|
137
182
|
if OS.windows?
|
138
|
-
|
139
|
-
|
140
|
-
|
183
|
+
ChildProcess.new('cmd.exe', '/c', process)
|
184
|
+
else
|
185
|
+
ChildProcess.new('sh', '-c', process)
|
141
186
|
end
|
187
|
+
end
|
142
188
|
|
143
|
-
|
144
|
-
|
189
|
+
#
|
190
|
+
# Waits for given process to exit.
|
191
|
+
# Forcefully kills the process if it does not exit within 5 seconds.
|
192
|
+
# Returns exit code.
|
193
|
+
#
|
194
|
+
def wait_for_process_exit(process)
|
195
|
+
process.poll_for_exit(PROCESS_TIMEOUT)
|
196
|
+
process.exit_code # 0
|
197
|
+
rescue ChildProcess::TimeoutError
|
198
|
+
ScreenRecorder.logger.error 'ffmpeg failed to stop. Force killing it...'
|
199
|
+
process.stop # Tries increasingly harsher methods to kill the process.
|
200
|
+
ScreenRecorder.logger.error 'Forcefully killed ffmpeg. Recording failed!'
|
201
|
+
1
|
145
202
|
end
|
146
203
|
end
|
147
204
|
end
|
@@ -2,14 +2,14 @@
|
|
2
2
|
module ScreenRecorder
|
3
3
|
# @since 1.0.0-beta11
|
4
4
|
class Desktop < Common
|
5
|
-
DEFAULT_INPUT_WIN
|
5
|
+
DEFAULT_INPUT_WIN = 'desktop'.freeze
|
6
6
|
DEFAULT_INPUT_LINUX = ':0'.freeze
|
7
|
-
DEFAULT_INPUT_MAC
|
7
|
+
DEFAULT_INPUT_MAC = '1'.freeze
|
8
8
|
|
9
9
|
#
|
10
|
-
# Desktop recording
|
10
|
+
# Desktop recording mode.
|
11
11
|
#
|
12
|
-
def initialize(input: input_by_os,
|
12
|
+
def initialize(output:, input: input_by_os, advanced: {})
|
13
13
|
super(input: determine_input(input), output: output, advanced: advanced)
|
14
14
|
end
|
15
15
|
|
@@ -25,7 +25,7 @@ module ScreenRecorder
|
|
25
25
|
|
26
26
|
return DEFAULT_INPUT_MAC if OS.mac?
|
27
27
|
|
28
|
-
raise
|
28
|
+
raise 'Your OS is not supported. Feel free to create an Issue on GitHub.'
|
29
29
|
end
|
30
30
|
|
31
31
|
#
|
@@ -2,9 +2,17 @@ module ScreenRecorder
|
|
2
2
|
# @since 1.0.0-beta5
|
3
3
|
module Errors
|
4
4
|
# @since 1.0.0-beta3
|
5
|
-
class ApplicationNotFound < StandardError
|
5
|
+
class ApplicationNotFound < StandardError
|
6
|
+
def message
|
7
|
+
'expected application was not found by ffmpeg.'
|
8
|
+
end
|
9
|
+
end
|
6
10
|
|
7
11
|
# @since 1.0.0-beta5
|
8
|
-
class DependencyNotFound < StandardError
|
12
|
+
class DependencyNotFound < StandardError
|
13
|
+
def message
|
14
|
+
'ffmpeg/ffprobe binary path not set or not found in ENV.'
|
15
|
+
end
|
16
|
+
end
|
9
17
|
end
|
10
18
|
end
|
@@ -1,26 +1,34 @@
|
|
1
1
|
# @since 1.0.0-beta11
|
2
|
+
#
|
3
|
+
# @api private
|
2
4
|
module ScreenRecorder
|
3
5
|
# @since 1.0.0-beta11
|
4
6
|
class Options
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
7
|
+
attr_reader :all
|
8
|
+
|
9
|
+
DEFAULT_LOG_FILE = 'ffmpeg.log'.freeze
|
10
|
+
DEFAULT_FPS = 15.0
|
11
|
+
DEFAULT_MAC_INPUT_PIX_FMT = 'uyvy422'.freeze # For avfoundation
|
12
|
+
DEFAULT_PIX_FMT = 'yuv420p'.freeze
|
13
|
+
YUV420P_SCALING = '"scale=trunc(iw/2)*2:trunc(ih/2)*2"'.freeze
|
9
14
|
|
10
15
|
def initialize(options)
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
advanced[:
|
15
|
-
advanced[:log]
|
16
|
-
|
16
|
+
# @todo Consider using OpenStruct
|
17
|
+
@all = verify_options options
|
18
|
+
advanced[:input] = default_advanced_input.merge(advanced_input)
|
19
|
+
advanced[:output] = default_advanced_output.merge(advanced_output)
|
20
|
+
advanced[:log] ||= DEFAULT_LOG_FILE
|
21
|
+
|
22
|
+
# Fix for using yuv420p pixel format for output
|
23
|
+
# @see https://www.reck.dk/ffmpeg-libx264-height-not-divisible-by-2/
|
24
|
+
advanced_output[:vf] = YUV420P_SCALING if advanced_output[:pix_fmt] == 'yuv420p'
|
17
25
|
end
|
18
26
|
|
19
27
|
#
|
20
28
|
# Returns given input file or input
|
21
29
|
#
|
22
30
|
def input
|
23
|
-
@
|
31
|
+
@all[:input]
|
24
32
|
end
|
25
33
|
|
26
34
|
#
|
@@ -34,21 +42,22 @@ module ScreenRecorder
|
|
34
42
|
# Returns given output filepath
|
35
43
|
#
|
36
44
|
def output
|
37
|
-
@
|
45
|
+
@all[:output]
|
38
46
|
end
|
39
47
|
|
40
48
|
#
|
41
49
|
# Returns given values that are optional
|
42
50
|
#
|
43
51
|
def advanced
|
44
|
-
@
|
52
|
+
@all[:advanced] ||= {}
|
45
53
|
end
|
46
54
|
|
47
55
|
#
|
48
56
|
# Returns given framerate
|
49
57
|
#
|
50
58
|
def framerate
|
51
|
-
advanced
|
59
|
+
ScreenRecorder.logger.warn '#framerate will not be available in the next release. Use #advanced instead.'
|
60
|
+
advanced[:output][:framerate]
|
52
61
|
end
|
53
62
|
|
54
63
|
#
|
@@ -58,28 +67,17 @@ module ScreenRecorder
|
|
58
67
|
advanced[:log]
|
59
68
|
end
|
60
69
|
|
61
|
-
#
|
62
|
-
# Returns all given options
|
63
|
-
#
|
64
|
-
def all
|
65
|
-
@options
|
66
|
-
end
|
67
|
-
|
68
70
|
#
|
69
71
|
# Returns a String with all options parsed and
|
70
72
|
# ready for the ffmpeg process to use
|
71
73
|
#
|
72
74
|
def parsed
|
73
75
|
vals = "-f #{capture_device} "
|
74
|
-
vals <<
|
75
|
-
vals <<
|
76
|
-
vals <<
|
77
|
-
vals <<
|
78
|
-
# Fix for using yuv420p
|
79
|
-
# @see https://www.reck.dk/ffmpeg-libx264-height-not-divisible-by-2/
|
80
|
-
vals << '-vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" ' if advanced[:pix_fmt] == 'yuv420p'
|
76
|
+
vals << parse_advanced(advanced_input)
|
77
|
+
vals << "-i #{input} " unless advanced_input[:i] # Input provided by user
|
78
|
+
vals << parse_advanced(advanced)
|
79
|
+
vals << parse_advanced(advanced_output)
|
81
80
|
vals << output
|
82
|
-
vals << ffmpeg_log_to(log)
|
83
81
|
end
|
84
82
|
|
85
83
|
private
|
@@ -90,13 +88,36 @@ module ScreenRecorder
|
|
90
88
|
# options are not present in the given Hash.
|
91
89
|
#
|
92
90
|
def verify_options(options)
|
91
|
+
TypeChecker.check options, Hash
|
92
|
+
TypeChecker.check options[:advanced], Hash if options[:advanced]
|
93
93
|
missing_options = required_options.select { |req| options[req].nil? }
|
94
|
-
err
|
94
|
+
err = "Required options are missing: #{missing_options}"
|
95
95
|
raise(ArgumentError, err) unless missing_options.empty?
|
96
96
|
|
97
97
|
options
|
98
98
|
end
|
99
99
|
|
100
|
+
def advanced_input
|
101
|
+
advanced[:input] ||= {}
|
102
|
+
end
|
103
|
+
|
104
|
+
def advanced_output
|
105
|
+
advanced[:output] ||= {}
|
106
|
+
end
|
107
|
+
|
108
|
+
def default_advanced_input
|
109
|
+
{
|
110
|
+
pix_fmt: OS.mac? ? DEFAULT_MAC_INPUT_PIX_FMT : nil
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
def default_advanced_output
|
115
|
+
{
|
116
|
+
pix_fmt: DEFAULT_PIX_FMT,
|
117
|
+
framerate: advanced[:framerate] || DEFAULT_FPS
|
118
|
+
}
|
119
|
+
end
|
120
|
+
|
100
121
|
#
|
101
122
|
# Returns Array of required options as Symbols
|
102
123
|
#
|
@@ -105,47 +126,44 @@ module ScreenRecorder
|
|
105
126
|
end
|
106
127
|
|
107
128
|
#
|
108
|
-
# Returns
|
129
|
+
# Returns given Hash parsed and ready for ffmpeg to receive.
|
109
130
|
#
|
110
|
-
def
|
131
|
+
def parse_advanced(opts)
|
132
|
+
# @todo Replace arr with opts.each_with_object([])
|
111
133
|
arr = []
|
112
|
-
|
113
|
-
#
|
114
|
-
|
115
|
-
advanced.reject { |k, _| %i[log pix_fmt].include? k }
|
134
|
+
rejects = %i[input output log]
|
135
|
+
# Do not parse input/output and log as they're placed separately in #parsed
|
136
|
+
opts.reject { |k, _| rejects.include? k }
|
116
137
|
.each do |k, v|
|
117
|
-
arr.push "-#{k} #{v}"
|
138
|
+
arr.push "-#{k} #{v}" unless v.nil? # Ignore blank params
|
118
139
|
end
|
119
|
-
arr.join(' ')
|
140
|
+
"#{arr.join(' ')} "
|
120
141
|
end
|
121
142
|
|
122
143
|
#
|
123
|
-
# Returns
|
124
|
-
# from options or the default file.
|
144
|
+
# Returns input capture device based on user given value or the current OS.
|
125
145
|
#
|
126
|
-
def
|
127
|
-
|
128
|
-
|
146
|
+
def determine_capture_device
|
147
|
+
# User given capture device or format from advanced configs Hash
|
148
|
+
# @see https://www.ffmpeg.org/ffmpeg.html#Main-options
|
149
|
+
return advanced_input[:f] if advanced_input[:f]
|
150
|
+
|
151
|
+
return advanced_input[:fmt] if advanced_input[:fmt]
|
152
|
+
|
153
|
+
default_capture_device
|
129
154
|
end
|
130
155
|
|
131
156
|
#
|
132
|
-
# Returns input capture device
|
157
|
+
# Returns input capture device for current OS.
|
133
158
|
#
|
134
|
-
def
|
135
|
-
|
136
|
-
|
137
|
-
return
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
elsif OS.linux?
|
143
|
-
'x11grab'
|
144
|
-
elsif OS.mac?
|
145
|
-
'avfoundation'
|
146
|
-
else
|
147
|
-
raise NotImplementedError, 'Your OS is not supported.'
|
148
|
-
end
|
159
|
+
def default_capture_device
|
160
|
+
return 'gdigrab' if OS.windows?
|
161
|
+
|
162
|
+
return 'x11grab' if OS.linux?
|
163
|
+
|
164
|
+
return 'avfoundation' if OS.mac?
|
165
|
+
|
166
|
+
raise 'Your OS is not supported. Feel free to create an Issue on GitHub.'
|
149
167
|
end
|
150
168
|
end
|
151
169
|
end
|