screen-recorder 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,41 +1,41 @@
1
- # @since 1.0.0-beta11
2
- module ScreenRecorder
3
- # @since 1.0.0-beta11
4
- class Desktop < Common
5
- DEFAULT_INPUT_WIN = 'desktop'.freeze
6
- DEFAULT_INPUT_LINUX = ':0'.freeze
7
- DEFAULT_INPUT_MAC = '1'.freeze
8
-
9
- #
10
- # Desktop recording specific initializer.
11
- #
12
- def initialize(input: input_by_os, output:, advanced: {})
13
- super(input: determine_input(input), output: output, advanced: advanced)
14
- end
15
-
16
- private
17
-
18
- #
19
- # Returns default input value for current OS
20
- #
21
- def input_by_os
22
- return DEFAULT_INPUT_WIN if OS.windows?
23
-
24
- return DEFAULT_INPUT_LINUX if OS.linux?
25
-
26
- return DEFAULT_INPUT_MAC if OS.mac?
27
-
28
- raise NotImplementedError, 'Your OS is not supported. Feel free to create an Issue on GitHub.'
29
- end
30
-
31
- #
32
- # Returns FFmpeg expected input based on user given value or
33
- # default for the current OS.
34
- #
35
- def determine_input(val)
36
- return val if val
37
-
38
- input_by_os
39
- end
40
- end
1
+ # @since 1.0.0-beta11
2
+ module ScreenRecorder
3
+ # @since 1.0.0-beta11
4
+ class Desktop < Common
5
+ DEFAULT_INPUT_WIN = 'desktop'.freeze
6
+ DEFAULT_INPUT_LINUX = ':0'.freeze
7
+ DEFAULT_INPUT_MAC = '1'.freeze
8
+
9
+ #
10
+ # Desktop recording mode.
11
+ #
12
+ def initialize(input: input_by_os, output:, advanced: {})
13
+ super(input: determine_input(input), output: output, advanced: advanced)
14
+ end
15
+
16
+ private
17
+
18
+ #
19
+ # Returns default input value for current OS
20
+ #
21
+ def input_by_os
22
+ return DEFAULT_INPUT_WIN if OS.windows?
23
+
24
+ return DEFAULT_INPUT_LINUX if OS.linux?
25
+
26
+ return DEFAULT_INPUT_MAC if OS.mac?
27
+
28
+ raise 'Your OS is not supported. Feel free to create an Issue on GitHub.'
29
+ end
30
+
31
+ #
32
+ # Returns FFmpeg expected input based on user given value or
33
+ # default for the current OS.
34
+ #
35
+ def determine_input(val)
36
+ return val if val
37
+
38
+ input_by_os
39
+ end
40
+ end
41
41
  end
@@ -1,168 +1,168 @@
1
- # @since 1.0.0-beta11
2
- #
3
- # @api private
4
- module ScreenRecorder
5
- # @since 1.0.0-beta11
6
- class Options
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
14
-
15
- def initialize(options)
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'
25
- end
26
-
27
- #
28
- # Returns given input file or input
29
- #
30
- def input
31
- @all[:input]
32
- end
33
-
34
- #
35
- # Returns capture device in use
36
- #
37
- def capture_device
38
- determine_capture_device
39
- end
40
-
41
- #
42
- # Returns given output filepath
43
- #
44
- def output
45
- @all[:output]
46
- end
47
-
48
- #
49
- # Returns given values that are optional
50
- #
51
- def advanced
52
- @all[:advanced] ||= {}
53
- end
54
-
55
- #
56
- # Returns given framerate
57
- #
58
- def framerate
59
- ScreenRecorder.logger.warn '#framerate will not be available in the next release. Use #advanced instead.'
60
- advanced[:output][:framerate]
61
- end
62
-
63
- #
64
- # Returns given log filename
65
- #
66
- def log
67
- advanced[:log]
68
- end
69
-
70
- #
71
- # Returns a String with all options parsed and
72
- # ready for the ffmpeg process to use
73
- #
74
- def parsed
75
- vals = "-f #{capture_device} "
76
- vals << parse_advanced(advanced_input)
77
- vals << "-i #{input} "
78
- vals << parse_advanced(advanced_output)
79
- vals << parse_advanced(advanced)
80
- vals << output
81
- end
82
-
83
- private
84
-
85
- #
86
- # Verifies the required options are provided and returns
87
- # the given options Hash. Raises ArgumentError if all required
88
- # options are not present in the given Hash.
89
- #
90
- def verify_options(options)
91
- TypeChecker.check options, Hash
92
- TypeChecker.check options[:advanced], Hash if options[:advanced]
93
- missing_options = required_options.select { |req| options[req].nil? }
94
- err = "Required options are missing: #{missing_options}"
95
- raise(ArgumentError, err) unless missing_options.empty?
96
-
97
- options
98
- end
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
-
121
- #
122
- # Returns Array of required options as Symbols
123
- #
124
- def required_options
125
- %i[input output]
126
- end
127
-
128
- #
129
- # Returns given Hash parsed and ready for ffmpeg to receive.
130
- #
131
- def parse_advanced(opts)
132
- # @todo Replace arr with opts.each_with_object([])
133
- arr = []
134
- # Do not parse input/output and log as they're placed separately in #parsed
135
- opts.reject { |k, _| %i[input output log].include? k }
136
- .each do |k, v|
137
- arr.push "-#{k} #{v}" unless v.nil? # Ignore blank params
138
- end
139
- arr.join(' ') + ' '
140
- end
141
-
142
- #
143
- # Returns input capture device based on user given value or the current OS.
144
- #
145
- def determine_capture_device
146
- # User given capture device or format
147
- # @see https://www.ffmpeg.org/ffmpeg.html#Main-options
148
- return advanced[:f] if advanced[:f]
149
-
150
- return advanced[:fmt] if advanced[:fmt]
151
-
152
- os_specific_capture_device
153
- end
154
-
155
- #
156
- # Returns input capture device for current OS.
157
- #
158
- def os_specific_capture_device
159
- return 'gdigrab' if OS.windows?
160
-
161
- return 'x11grab' if OS.linux?
162
-
163
- return 'avfoundation' if OS.mac?
164
-
165
- raise NotImplementedError, 'Your OS is not supported.'
166
- end
167
- end
168
- end
1
+ # @since 1.0.0-beta11
2
+ #
3
+ # @api private
4
+ module ScreenRecorder
5
+ # @since 1.0.0-beta11
6
+ class Options
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
14
+
15
+ def initialize(options)
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'
25
+ end
26
+
27
+ #
28
+ # Returns given input file or input
29
+ #
30
+ def input
31
+ @all[:input]
32
+ end
33
+
34
+ #
35
+ # Returns capture device in use
36
+ #
37
+ def capture_device
38
+ determine_capture_device
39
+ end
40
+
41
+ #
42
+ # Returns given output filepath
43
+ #
44
+ def output
45
+ @all[:output]
46
+ end
47
+
48
+ #
49
+ # Returns given values that are optional
50
+ #
51
+ def advanced
52
+ @all[:advanced] ||= {}
53
+ end
54
+
55
+ #
56
+ # Returns given framerate
57
+ #
58
+ def framerate
59
+ ScreenRecorder.logger.warn '#framerate will not be available in the next release. Use #advanced instead.'
60
+ advanced[:output][:framerate]
61
+ end
62
+
63
+ #
64
+ # Returns given log filename
65
+ #
66
+ def log
67
+ advanced[:log]
68
+ end
69
+
70
+ #
71
+ # Returns a String with all options parsed and
72
+ # ready for the ffmpeg process to use
73
+ #
74
+ def parsed
75
+ vals = "-f #{capture_device} "
76
+ vals << parse_advanced(advanced_input)
77
+ vals << "-i #{input} "
78
+ vals << parse_advanced(advanced_output)
79
+ vals << parse_advanced(advanced)
80
+ vals << output
81
+ end
82
+
83
+ private
84
+
85
+ #
86
+ # Verifies the required options are provided and returns
87
+ # the given options Hash. Raises ArgumentError if all required
88
+ # options are not present in the given Hash.
89
+ #
90
+ def verify_options(options)
91
+ TypeChecker.check options, Hash
92
+ TypeChecker.check options[:advanced], Hash if options[:advanced]
93
+ missing_options = required_options.select { |req| options[req].nil? }
94
+ err = "Required options are missing: #{missing_options}"
95
+ raise(ArgumentError, err) unless missing_options.empty?
96
+
97
+ options
98
+ end
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
+
121
+ #
122
+ # Returns Array of required options as Symbols
123
+ #
124
+ def required_options
125
+ %i[input output]
126
+ end
127
+
128
+ #
129
+ # Returns given Hash parsed and ready for ffmpeg to receive.
130
+ #
131
+ def parse_advanced(opts)
132
+ # @todo Replace arr with opts.each_with_object([])
133
+ arr = []
134
+ # Do not parse input/output and log as they're placed separately in #parsed
135
+ opts.reject { |k, _| %i[input output log].include? k }
136
+ .each do |k, v|
137
+ arr.push "-#{k} #{v}" unless v.nil? # Ignore blank params
138
+ end
139
+ arr.join(' ') + ' '
140
+ end
141
+
142
+ #
143
+ # Returns input capture device based on user given value or the current OS.
144
+ #
145
+ def determine_capture_device
146
+ # User given capture device or format
147
+ # @see https://www.ffmpeg.org/ffmpeg.html#Main-options
148
+ return advanced[:f] if advanced[:f]
149
+
150
+ return advanced[:fmt] if advanced[:fmt]
151
+
152
+ os_specific_capture_device
153
+ end
154
+
155
+ #
156
+ # Returns input capture device for current OS.
157
+ #
158
+ def os_specific_capture_device
159
+ return 'gdigrab' if OS.windows?
160
+
161
+ return 'x11grab' if OS.linux?
162
+
163
+ return 'avfoundation' if OS.mac?
164
+
165
+ raise 'Your OS is not supported. Feel free to create an Issue on GitHub.'
166
+ end
167
+ end
168
+ end
@@ -1,53 +1,17 @@
1
- module ScreenRecorder
2
- # @since 1.0.0-beta4
3
- module Titles
4
- # Regex to filter out "Window Title: N/A" from Chrome extensions and "Window Title: ".
5
- # This is done to remove unusable titles and to match the Ffmpeg expected input format
6
- # for capturing specific windows.
7
- # For example, "Window Title: Google - Mozilla Firefox" becomes "Google - Mozilla Firefox".
8
- FILTERED_TITLES = %r{^Window Title:( N/A|\s+)?}.freeze
9
-
10
- #
11
- # Returns a list of available window titles for the given application (process) name.
12
- #
13
- # @return [Array]
14
- def self.fetch(application)
15
- ScreenRecorder.logger.debug "Retrieving available windows for: #{application}"
16
- WindowGrabber.new.available_windows_for application
17
- end
18
-
19
- # @since 1.0.0-beta4
20
- #
21
- # @api private
22
- class WindowGrabber
23
- #
24
- # Returns a list of available window titles for the given application (process) name.
25
- #
26
- def available_windows_for(application)
27
- raise NotImplementedError, 'Only Microsoft Windows (gdigrab) supports window capture.' unless OS.windows?
28
-
29
- titles = `tasklist /v /fi "imagename eq #{application}.exe" /fo list | findstr Window`
30
- .split("\n")
31
- .map { |i| i.gsub(FILTERED_TITLES, '') }
32
- .reject(&:empty?)
33
- raise Errors::ApplicationNotFound, "No open windows found for: #{application}.exe" if titles.empty?
34
-
35
- warn_on_mismatch(titles, application)
36
- titles
37
- end
38
-
39
- private
40
-
41
- #
42
- # Prints a warning if the retrieved list of window titles does no include
43
- # the given application process name, which applications commonly do.
44
- #
45
- def warn_on_mismatch(titles, application)
46
- return if titles.map(&:downcase).join(',').include? application.to_s
47
-
48
- ScreenRecorder.logger.warn "Process name and window title(s) do not match: #{titles}"
49
- ScreenRecorder.logger.warn 'Please manually provide the displayed window title.'
50
- end
51
- end # class WindowGrabber
52
- end # module Windows
1
+ module ScreenRecorder
2
+ # @since 1.0.0-beta4
3
+ module Titles
4
+ #
5
+ # Returns a list of available window titles for the given process (application) name.
6
+ #
7
+ # @return [Array]
8
+ #
9
+ # @example
10
+ # ScreenRecorder::Titles.fetch('chrome')
11
+ # #=> ["New Tab - Google Chrome"]
12
+ def self.fetch(application)
13
+ # @todo Remove Titles.fetch in v2.0
14
+ Window.fetch_title application
15
+ end
16
+ end # module Windows
53
17
  end # module FFMPEG