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.
data/Rakefile CHANGED
@@ -1,6 +1,11 @@
1
1
  require 'bundler/gem_tasks'
2
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
3
8
 
4
9
  RSpec::Core::RakeTask.new(:spec)
5
10
 
6
- task default: :spec
11
+ task default: %w[spec rubocop]
data/bin/console CHANGED
File without changes
data/bin/setup CHANGED
@@ -1,8 +1,8 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -1,46 +1,54 @@
1
- require 'streamio-ffmpeg'
2
- require 'os'
3
- require 'logger'
4
-
5
- # @since 1.0.0.beta11
6
- module ScreenRecorder
7
- #
8
- # Uses user given FFMPEG binary
9
- #
10
- # @example
11
- # ScreenRecorder.ffmpeg_binary = 'C:\ffmpeg.exe'
12
- #
13
- def ffmpeg_binary=(bin)
14
- FFMPEG.ffmpeg_binary = bin
15
- end
16
-
17
- #
18
- # Set external logger if you want.
19
- #
20
- def self.logger=(log)
21
- @logger = log
22
- end
23
-
24
- #
25
- # ScreenRecorder.logger
26
- #
27
- def self.logger
28
- return @logger if @logger
29
- logger = Logger.new(STDOUT)
30
- logger.level = Logger::ERROR
31
- logger.progname = 'ScreenRecorder'
32
- logger.formatter = proc do |severity, time, progname, msg|
33
- "#{time.strftime('%F %T')} #{progname} - #{severity} - #{msg}\n"
34
- end
35
- logger.debug 'Logger initialized.'
36
- @logger = logger
37
- end
38
- end
39
-
40
- require 'screen-recorder/type_checker'
41
- require 'screen-recorder/errors'
42
- require 'screen-recorder/options'
43
- require 'screen-recorder/titles'
44
- require 'screen-recorder/common'
45
- require 'screen-recorder/desktop'
1
+ require 'streamio-ffmpeg'
2
+ require 'os'
3
+ require 'logger'
4
+
5
+ # @since 1.0.0.beta11
6
+ module ScreenRecorder
7
+ #
8
+ # Uses user given FFMPEG binary
9
+ #
10
+ # @example
11
+ # ScreenRecorder.ffmpeg_binary = 'C:\ffmpeg.exe'
12
+ #
13
+ def self.ffmpeg_binary=(bin)
14
+ FFMPEG.ffmpeg_binary = bin
15
+ end
16
+
17
+ #
18
+ # Returns path to ffmpeg binary
19
+ #
20
+ def self.ffmpeg_binary
21
+ FFMPEG.ffmpeg_binary
22
+ end
23
+
24
+ #
25
+ # Set external logger if you want.
26
+ #
27
+ def self.logger=(log)
28
+ @logger = log
29
+ end
30
+
31
+ #
32
+ # ScreenRecorder.logger
33
+ #
34
+ def self.logger
35
+ return @logger if @logger
36
+
37
+ logger = Logger.new(STDOUT)
38
+ logger.level = Logger::ERROR
39
+ logger.progname = 'ScreenRecorder'
40
+ logger.formatter = proc do |severity, time, progname, msg|
41
+ "#{time.strftime('%F %T')} #{progname} - #{severity} - #{msg}\n"
42
+ end
43
+ logger.debug 'Logger initialized.'
44
+ @logger = logger
45
+ end
46
+ end
47
+
48
+ require 'screen-recorder/type_checker'
49
+ require 'screen-recorder/errors'
50
+ require 'screen-recorder/options'
51
+ require 'screen-recorder/titles'
52
+ require 'screen-recorder/common'
53
+ require 'screen-recorder/desktop'
46
54
  require 'screen-recorder/window'
@@ -1,128 +1,147 @@
1
- # @since 1.0.0-beta11
2
- module ScreenRecorder
3
- # @since 1.0.0-beta11
4
- class Common
5
- attr_reader :options, :video
6
-
7
- def initialize(input:, output:, advanced: {})
8
- @options = Options.new(input: input, output: output, advanced: advanced)
9
- @video = nil
10
- @process = nil
11
- end
12
-
13
- #
14
- # Starts the recording
15
- #
16
- def start
17
- @video = nil # New file
18
- start_time = Time.now
19
- @process = start_ffmpeg
20
- elapsed = Time.now - start_time
21
- ScreenRecorder.logger.debug "Process started in #{elapsed}s"
22
- ScreenRecorder.logger.info 'Recording...'
23
- @process
24
- end
25
-
26
- #
27
- # Stops the recording
28
- #
29
- def stop
30
- ScreenRecorder.logger.debug 'Stopping ffmpeg.exe...'
31
- elapsed = kill_ffmpeg
32
- ScreenRecorder.logger.debug "Stopped ffmpeg.exe in #{elapsed}s"
33
- ScreenRecorder.logger.info 'Recording complete.'
34
- @video = FFMPEG::Movie.new(options.output)
35
- end
36
-
37
- #
38
- # Discards the recorded file. Useful in automated testing
39
- # when a test passes and the recorded file is no longer
40
- # needed.
41
- #
42
- def discard
43
- FileUtils.rm options.output
44
- end
45
-
46
- alias_method :delete, :discard
47
-
48
- private
49
-
50
- #
51
- # Launches the ffmpeg binary using a generated command based on
52
- # the given options.
53
- #
54
- def start_ffmpeg
55
- raise Errors::DependencyNotFound, 'ffmpeg binary not found.' unless ffmpeg_exists?
56
-
57
- ScreenRecorder.logger.debug "Command: #{command}"
58
- process = IO.popen(command, 'r+')
59
- sleep(1.5) # Takes ~1.5s on average to initialize
60
- process
61
- end
62
-
63
- #
64
- # Sends 'q' to the ffmpeg binary to gracefully stop the process.
65
- #
66
- def kill_ffmpeg
67
- @process.puts 'q' # Gracefully exit ffmpeg
68
- elapsed = wait_for_io_eof(5)
69
- @process.close_write # Close IO
70
- elapsed
71
- rescue Errno::EPIPE
72
- # Gets last line from log file
73
- err_line = get_lines_from_log(:last, 2)
74
- raise FFMPEG::Error, err_line
75
- end
76
-
77
- #
78
- # Generates the command line arguments based on the given
79
- # options.
80
- #
81
- def command
82
- cmd = "#{FFMPEG.ffmpeg_binary} -y "
83
- cmd << @options.parsed
84
- end
85
-
86
- #
87
- # Waits for IO#eof? to return true
88
- # after 'q' is sent to the ffmpeg process.
89
- #
90
- def wait_for_io_eof(timeout)
91
- start = Time.now
92
- Timeout.timeout(timeout) do
93
- sleep(0.1) until @process.eof?
94
- end
95
- ScreenRecorder.logger.debug "IO#eof? #{@process.eof?}"
96
- Time.now - start
97
- end
98
-
99
- #
100
- # Returns true if ffmpeg binary is found.
101
- #
102
- def ffmpeg_exists?
103
- return !`which ffmpeg`.empty? if OS.linux? # "" if not found
104
-
105
- return !`where ffmpeg`.empty? if OS.windows?
106
-
107
- # If the user does not use FFMPEG#ffmpeg_binary=() to set the binary path,
108
- # FFMPEG#ffmpeg_binary returns 'ffmpeg' assuming it must be in ENV. However,
109
- # if the above two checks fail, it is not in the ENV either.
110
- return false if FFMPEG.ffmpeg_binary == 'ffmpeg'
111
-
112
- true
113
- end
114
-
115
- #
116
- # Returns lines from the log file
117
- #
118
- def get_lines_from_log(position = :last, count = 2)
119
- f = File.open(options.log)
120
- lines = f.readlines
121
- lines = lines.last(count) if position == :last
122
- lines = lines.first(count) if position == :first
123
- f.close
124
-
125
- lines.join(' ')
126
- end
127
- end
1
+ # @since 1.0.0-beta11
2
+ module ScreenRecorder
3
+ # @since 1.0.0-beta11
4
+ class Common
5
+ attr_reader :options, :video
6
+
7
+ def initialize(input:, output:, advanced: {})
8
+ @options = Options.new(input: input, output: output, advanced: advanced)
9
+ @video = nil
10
+ @process = nil
11
+ end
12
+
13
+ #
14
+ # Starts the recording
15
+ #
16
+ def start
17
+ @video = nil # New file
18
+ start_time = Time.now
19
+ @process = start_ffmpeg
20
+ elapsed = Time.now - start_time
21
+ ScreenRecorder.logger.debug "Process started in #{elapsed}s"
22
+ ScreenRecorder.logger.info 'Recording...'
23
+ @process
24
+ end
25
+
26
+ #
27
+ # Stops the recording
28
+ #
29
+ def stop
30
+ ScreenRecorder.logger.debug 'Stopping ffmpeg.exe...'
31
+ elapsed = stop_ffmpeg
32
+ ScreenRecorder.logger.debug "Stopped ffmpeg.exe in #{elapsed}s"
33
+ ScreenRecorder.logger.info 'Recording complete.'
34
+ @video = FFMPEG::Movie.new(options.output)
35
+ end
36
+
37
+ #
38
+ # Discards the recorded file. Useful in automated testing
39
+ # when a test passes and the recorded file is no longer
40
+ # needed.
41
+ #
42
+ def discard
43
+ FileUtils.rm options.output
44
+ end
45
+
46
+ alias delete discard
47
+
48
+ private
49
+
50
+ #
51
+ # Launches the ffmpeg binary using a generated command based on
52
+ # the given options.
53
+ #
54
+ def start_ffmpeg
55
+ raise Errors::DependencyNotFound, 'ffmpeg binary not found.' unless ffmpeg_exists?
56
+
57
+ ScreenRecorder.logger.debug "Command: #{command}"
58
+ process = IO.popen(command, 'r+')
59
+ sleep(1.5) # Takes ~1.5s on average to initialize
60
+ process
61
+ end
62
+
63
+ #
64
+ # Sends 'q' to the ffmpeg binary to gracefully stop the process.
65
+ # Forcefully terminates it if it takes more than 10s.
66
+ #
67
+ def stop_ffmpeg
68
+ @process.puts 'q' # Gracefully exit ffmpeg
69
+ elapsed = wait_for_io_eof(10)
70
+ @process.close_write # Close IO
71
+ elapsed
72
+ rescue Timeout::Error
73
+ ScreenRecorder.logger.error 'FFmpeg failed to stop. Force killing it...'
74
+ force_kill_ffmpeg
75
+ ScreenRecorder.logger.error "Check '#{@options.log}' for more information."
76
+ rescue Errno::EPIPE
77
+ # Gets last line from log file
78
+ err_line = get_lines_from_log(:last, 2)
79
+ raise FFMPEG::Error, err_line
80
+ end
81
+
82
+ #
83
+ # Generates the command line arguments based on the given
84
+ # options.
85
+ #
86
+ def command
87
+ cmd = "#{ScreenRecorder.ffmpeg_binary} -y "
88
+ cmd << @options.parsed
89
+ end
90
+
91
+ #
92
+ # Waits for IO#eof? to return true
93
+ # after 'q' is sent to the ffmpeg process.
94
+ #
95
+ def wait_for_io_eof(timeout)
96
+ start = Time.now
97
+ Timeout.timeout(timeout) do
98
+ sleep(0.1) until @process.eof?
99
+ end
100
+ ScreenRecorder.logger.debug "IO#eof? #{@process.eof?}"
101
+ Time.now - start
102
+ end
103
+
104
+ #
105
+ # Returns true if ffmpeg binary is found.
106
+ #
107
+ def ffmpeg_exists?
108
+ return !`which ffmpeg`.empty? if OS.linux? # "" if not found
109
+
110
+ return !`where ffmpeg`.empty? if OS.windows?
111
+
112
+ # If the user does not use ScreenRecorder.ffmpeg_binary=() to set the binary path,
113
+ # ScreenRecorder.ffmpeg_binary returns 'ffmpeg' assuming it must be in ENV. However,
114
+ # if the above two checks fail, it is not in the ENV either.
115
+ return false if ScreenRecorder.ffmpeg_binary == 'ffmpeg'
116
+
117
+ true
118
+ end
119
+
120
+ #
121
+ # Returns lines from the log file
122
+ #
123
+ def get_lines_from_log(position = :last, count = 2)
124
+ f = File.open(options.log)
125
+ lines = f.readlines
126
+ lines = lines.last(count) if position == :last
127
+ lines = lines.first(count) if position == :first
128
+ f.close
129
+
130
+ lines.join(' ')
131
+ end
132
+
133
+ #
134
+ # Force kills the ffmpeg process.
135
+ #
136
+ def force_kill_ffmpeg
137
+ if OS.windows?
138
+ pid = `powershell (Get-Process -name 'ffmpeg').Id`.strip.to_i
139
+ `taskkill /f /pid #{pid}`
140
+ return
141
+ end
142
+
143
+ `killall -9 ffmpeg` # Linux and macOS
144
+ nil
145
+ end
146
+ end
128
147
  end
@@ -1,31 +1,41 @@
1
- # @since 1.0.0-beta11
2
- module ScreenRecorder
3
- # @since 1.0.0-beta11
4
- class Desktop < Common
5
- DEFAULT_INPUT_LINUX = ':0.0'.freeze
6
- DEFAULT_INPUT_WIN = 'desktop'.freeze
7
-
8
- #
9
- # Desktop recording specific initializer.
10
- #
11
- def initialize(input: 'desktop', output:, advanced: {})
12
- super(input: determine_input(input), output: output, advanced: advanced)
13
- end
14
-
15
- private
16
-
17
- #
18
- # Returns FFmpeg expected input value based on current OS
19
- #
20
- def determine_input(val)
21
- if OS.linux?
22
- return DEFAULT_INPUT_LINUX if val == 'desktop'
23
-
24
- return val # Custom $DISPLAY number in Linux
25
- end
26
- return DEFAULT_INPUT_WIN if OS.windows?
27
-
28
- raise ArgumentError, "Unsupported input type: '#{val}'. Expected: 'desktop'"
29
- end
30
- 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 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
31
41
  end