screen-recorder 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,10 +1,10 @@
1
- module ScreenRecorder
2
- # @since 1.0.0-beta5
3
- module Errors
4
- # @since 1.0.0-beta3
5
- class ApplicationNotFound < StandardError; end
6
-
7
- # @since 1.0.0-beta5
8
- class DependencyNotFound < StandardError; end
9
- end
1
+ module ScreenRecorder
2
+ # @since 1.0.0-beta5
3
+ module Errors
4
+ # @since 1.0.0-beta3
5
+ class ApplicationNotFound < StandardError; end
6
+
7
+ # @since 1.0.0-beta5
8
+ class DependencyNotFound < StandardError; end
9
+ end
10
10
  end
@@ -1,148 +1,151 @@
1
- # @since 1.0.0-beta11
2
- module ScreenRecorder
3
- # @since 1.0.0-beta11
4
- class Options
5
- DEFAULT_LOG_FILE = 'ffmpeg.log'.freeze
6
- DEFAULT_FPS = 15.0
7
-
8
- def initialize(options)
9
- TypeChecker.check options, Hash
10
- TypeChecker.check options[:advanced], Hash if options[:advanced]
11
- @options = verify_options options
12
-
13
- unless advanced[:framerate]
14
- advanced[:framerate] = DEFAULT_FPS
15
- end
16
-
17
- unless advanced[:log]
18
- advanced[:log] = DEFAULT_LOG_FILE
19
- end
20
- end
21
-
22
- #
23
- # Returns given input file or input
24
- #
25
- def input
26
- @options[:input]
27
- end
28
-
29
- #
30
- # Returns capture device in use
31
- #
32
- def capture_device
33
- determine_capture_device
34
- end
35
-
36
- #
37
- # Returns given output filepath
38
- #
39
- def output
40
- @options[:output]
41
- end
42
-
43
- #
44
- # Returns given values that are optional
45
- #
46
- def advanced
47
- @options[:advanced] ||= {}
48
- end
49
-
50
- #
51
- # Returns given framerate
52
- #
53
- def framerate
54
- advanced[:framerate]
55
- end
56
-
57
- #
58
- # Returns given log filename
59
- #
60
- def log
61
- advanced[:log]
62
- end
63
-
64
- #
65
- # Returns all given options
66
- #
67
- def all
68
- @options
69
- end
70
-
71
- #
72
- # Returns a String with all options parsed and
73
- # ready for the ffmpeg process to use
74
- #
75
- def parsed
76
- vals = "-f #{capture_device} "
77
- vals << advanced_options unless advanced.empty?
78
- vals << "-i #{input} "
79
- vals << output
80
- vals << ffmpeg_log_to(log) # If provided
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
- missing_options = required_options.select { |req| options[req].nil? }
92
- err = "Required options are missing: #{missing_options}"
93
- raise(ArgumentError, err) unless missing_options.empty?
94
-
95
- options
96
- end
97
-
98
- #
99
- # Returns Array of required options as Symbols
100
- #
101
- def required_options
102
- %i[input output]
103
- end
104
-
105
- #
106
- # Returns advanced options parsed and ready for ffmpeg to receive.
107
- #
108
- def advanced_options
109
- arr = []
110
-
111
- # Log file is handled separately at the end of the command
112
- advanced.select { |k, _| k != :log }
113
- .each do |k, v|
114
- arr.push "-#{k} #{v}"
115
- end
116
- arr.join(' ') + ' '
117
- end
118
-
119
- #
120
- # Returns logging command with user given log file
121
- # from options or the default file.
122
- #
123
- def ffmpeg_log_to(file)
124
- file ||= DEFAULT_LOG_FILE
125
- " 2> #{file}"
126
- end
127
-
128
- #
129
- # Returns input capture device based on user given value or the current OS.
130
- #
131
- def determine_capture_device
132
- # User given capture device or format
133
- # @see https://www.ffmpeg.org/ffmpeg.html#Main-options
134
- return advanced[:f] if advanced[:f]
135
- return advanced[:fmt] if advanced[:fmt]
136
-
137
- if OS.windows?
138
- 'gdigrab'
139
- elsif OS.linux?
140
- 'x11grab'
141
- elsif OS.mac?
142
- 'avfoundation'
143
- else
144
- raise NotImplementedError, 'Your OS is not supported.'
145
- end
146
- end
147
- end
148
- end
1
+ # @since 1.0.0-beta11
2
+ module ScreenRecorder
3
+ # @since 1.0.0-beta11
4
+ class Options
5
+ DEFAULT_LOG_FILE = 'ffmpeg.log'.freeze
6
+ DEFAULT_FPS = 15.0
7
+ DEFAULT_INPUT_PIX_FMT = 'uyvy422'.freeze # For macOS / avfoundation
8
+ DEFAULT_OUTPUT_PIX_FMT = 'yuv420p'.freeze
9
+
10
+ def initialize(options)
11
+ TypeChecker.check options, Hash
12
+ TypeChecker.check options[:advanced], Hash if options[:advanced]
13
+ @options = verify_options options
14
+ advanced[:framerate] ||= DEFAULT_FPS
15
+ advanced[:log] ||= DEFAULT_LOG_FILE
16
+ advanced[:pix_fmt] ||= DEFAULT_OUTPUT_PIX_FMT
17
+ end
18
+
19
+ #
20
+ # Returns given input file or input
21
+ #
22
+ def input
23
+ @options[:input]
24
+ end
25
+
26
+ #
27
+ # Returns capture device in use
28
+ #
29
+ def capture_device
30
+ determine_capture_device
31
+ end
32
+
33
+ #
34
+ # Returns given output filepath
35
+ #
36
+ def output
37
+ @options[:output]
38
+ end
39
+
40
+ #
41
+ # Returns given values that are optional
42
+ #
43
+ def advanced
44
+ @options[:advanced] ||= {}
45
+ end
46
+
47
+ #
48
+ # Returns given framerate
49
+ #
50
+ def framerate
51
+ advanced[:framerate]
52
+ end
53
+
54
+ #
55
+ # Returns given log filename
56
+ #
57
+ def log
58
+ advanced[:log]
59
+ end
60
+
61
+ #
62
+ # Returns all given options
63
+ #
64
+ def all
65
+ @options
66
+ end
67
+
68
+ #
69
+ # Returns a String with all options parsed and
70
+ # ready for the ffmpeg process to use
71
+ #
72
+ def parsed
73
+ vals = "-f #{capture_device} "
74
+ vals << "-pix_fmt #{DEFAULT_INPUT_PIX_FMT} " if OS.mac? # Input pixel format
75
+ vals << advanced_options unless advanced.empty?
76
+ vals << "-i #{input} "
77
+ vals << "-pix_fmt #{advanced[:pix_fmt]} " if advanced[:pix_fmt] # Output pixel format
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'
81
+ vals << output
82
+ vals << ffmpeg_log_to(log)
83
+ end
84
+
85
+ private
86
+
87
+ #
88
+ # Verifies the required options are provided and returns
89
+ # the given options Hash. Raises ArgumentError if all required
90
+ # options are not present in the given Hash.
91
+ #
92
+ def verify_options(options)
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
+ #
101
+ # Returns Array of required options as Symbols
102
+ #
103
+ def required_options
104
+ %i[input output]
105
+ end
106
+
107
+ #
108
+ # Returns advanced options parsed and ready for ffmpeg to receive.
109
+ #
110
+ def advanced_options
111
+ arr = []
112
+
113
+ # Log file and output pixel format is handled separately
114
+ # at the end of the command
115
+ advanced.reject { |k, _| %i[log pix_fmt].include? k }
116
+ .each do |k, v|
117
+ arr.push "-#{k} #{v}"
118
+ end
119
+ arr.join(' ') + ' '
120
+ end
121
+
122
+ #
123
+ # Returns logging command with user given log file
124
+ # from options or the default file.
125
+ #
126
+ def ffmpeg_log_to(file)
127
+ file ||= DEFAULT_LOG_FILE
128
+ " 2> #{file}"
129
+ end
130
+
131
+ #
132
+ # Returns input capture device based on user given value or the current OS.
133
+ #
134
+ def determine_capture_device
135
+ # User given capture device or format
136
+ # @see https://www.ffmpeg.org/ffmpeg.html#Main-options
137
+ return advanced[:f] if advanced[:f]
138
+ return advanced[:fmt] if advanced[:fmt]
139
+
140
+ if OS.windows?
141
+ 'gdigrab'
142
+ elsif OS.linux?
143
+ 'x11grab'
144
+ elsif OS.mac?
145
+ 'avfoundation'
146
+ else
147
+ raise NotImplementedError, 'Your OS is not supported.'
148
+ end
149
+ end
150
+ end
151
+ end
@@ -1,82 +1,50 @@
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+)?}
9
-
10
- #
11
- # Returns a list of available window titles for the given application (process) name.
12
- #
13
- def self.fetch(application)
14
- ScreenRecorder.logger.debug "Retrieving available windows for: #{application}"
15
- WindowGrabber.new.available_windows_for application
16
- end
17
-
18
- # @since 1.0.0-beta4
19
- class WindowGrabber
20
- #
21
- # Returns a cleaned up list of available window titles
22
- # for the given application (process) name.
23
- #
24
- def available_windows_for(application)
25
- return windows_os_window(application) if OS.windows?
26
- return linux_os_window(application) if OS.linux?
27
-
28
- raise NotImplementedError, 'Your OS is not supported.'
29
- end
30
-
31
- private
32
-
33
- #
34
- # Returns list of window titles in FFmpeg expected format when using Microsoft Windows
35
- #
36
- def windows_os_window(application)
37
- titles = `tasklist /v /fi "imagename eq #{application}.exe" /fo list | findstr Window`
38
- .split("\n")
39
- .map { |i| i.gsub(FILTERED_TITLES, '') }
40
- .reject(&:empty?)
41
- raise Errors::ApplicationNotFound, "No open windows found for: #{application}.exe" if titles.empty?
42
-
43
- warn_on_mismatch(titles, application)
44
- titles
45
- end
46
-
47
- #
48
- # Returns list of window titles in FFmpeg expected format when using Linux
49
- #
50
- def linux_os_window(application)
51
- ScreenRecorder.logger.warn 'Default capture device on Linux (x11grab) does not support window recording.'
52
- raise DependencyNotFound, 'wmctrl is not installed. Run: sudo apt install wmctrl.' unless wmctrl_installed?
53
-
54
- titles = `wmctrl -l | awk '{$3=""; $2=""; $1=""; print $0}'` # Returns all open windows
55
- .split("\n")
56
- .map(&:strip)
57
- .select { |t| t.match?(/#{application}/i) } # Narrow down to given application
58
- raise Errors::ApplicationNotFound, "No open windows found for: #{application}" if titles.empty?
59
-
60
- titles
61
- end
62
-
63
- #
64
- # Returns true if wmctrl is installed
65
- #
66
- def wmctrl_installed?
67
- !`which wmctrl`.empty? # "" when not found
68
- end
69
-
70
- #
71
- # Prints a warning if the retrieved list of window titles does no include
72
- # the given application process name, which applications commonly do.
73
- #
74
- def warn_on_mismatch(titles, application)
75
- unless titles.map(&:downcase).join(',').include? application.to_s
76
- ScreenRecorder.logger.warn "Process name and window title(s) do not match: #{titles}"
77
- ScreenRecorder.logger.warn "Please manually provide the displayed window title."
78
- end
79
- end
80
- end # class WindowGrabber
81
- end # module Windows
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
+ def self.fetch(application)
14
+ ScreenRecorder.logger.debug "Retrieving available windows for: #{application}"
15
+ WindowGrabber.new.available_windows_for application
16
+ end
17
+
18
+ # @since 1.0.0-beta4
19
+ class WindowGrabber
20
+ #
21
+ # Returns a list of available window titles for the given application (process) name.
22
+ #
23
+ def available_windows_for(application)
24
+ raise NotImplementedError, 'Only Microsoft Windows (gdigrab) supports window capture.' unless OS.windows?
25
+
26
+ titles = `tasklist /v /fi "imagename eq #{application}.exe" /fo list | findstr Window`
27
+ .split("\n")
28
+ .map { |i| i.gsub(FILTERED_TITLES, '') }
29
+ .reject(&:empty?)
30
+ raise Errors::ApplicationNotFound, "No open windows found for: #{application}.exe" if titles.empty?
31
+
32
+ warn_on_mismatch(titles, application)
33
+ titles
34
+ end
35
+
36
+ private
37
+
38
+ #
39
+ # Prints a warning if the retrieved list of window titles does no include
40
+ # the given application process name, which applications commonly do.
41
+ #
42
+ def warn_on_mismatch(titles, application)
43
+ return if titles.map(&:downcase).join(',').include? application.to_s
44
+
45
+ ScreenRecorder.logger.warn "Process name and window title(s) do not match: #{titles}"
46
+ ScreenRecorder.logger.warn 'Please manually provide the displayed window title.'
47
+ end
48
+ end # class WindowGrabber
49
+ end # module Windows
82
50
  end # module FFMPEG