screen-recorder 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/appveyor.yml ADDED
@@ -0,0 +1,23 @@
1
+ build: off
2
+ cache:
3
+ - vendor/bundle
4
+ environment:
5
+ matrix:
6
+ - RUBY_VERSION: 24
7
+ RAKE_TASK: spec
8
+ - RUBY_VERSION: 24
9
+ RAKE_TASK: rubocop
10
+ - RUBY_VERSION: 26
11
+ RAKE_TASK: spec
12
+ install:
13
+ - choco install ffmpeg
14
+ - set PATH=C:\Ruby%RUBY_VERSION%\bin;%PATH%
15
+ - bundle config --local path vendor/bundle
16
+ - bundle install
17
+ before_test:
18
+ - ffmpeg -version
19
+ - ruby -v
20
+ - gem -v
21
+ - bundle -v
22
+ test_script:
23
+ - bundle exec rake %RAKE_TASK%
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,54 +1,55 @@
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'
1
+ require 'childprocess'
2
+ require 'logger'
3
+ require 'os'
4
+ require 'streamio-ffmpeg'
5
+
6
+ # @since 1.0.0.beta11
7
+ module ScreenRecorder
8
+ #
9
+ # Uses user given FFMPEG binary
10
+ #
11
+ # @example
12
+ # ScreenRecorder.ffmpeg_binary = 'C:\ffmpeg.exe'
13
+ #
14
+ def self.ffmpeg_binary=(bin)
15
+ FFMPEG.ffmpeg_binary = bin
16
+ end
17
+
18
+ #
19
+ # Returns path to ffmpeg binary
20
+ #
21
+ def self.ffmpeg_binary
22
+ FFMPEG.ffmpeg_binary
23
+ end
24
+
25
+ #
26
+ # Set external logger if you want.
27
+ #
28
+ def self.logger=(log)
29
+ @logger = log
30
+ end
31
+
32
+ #
33
+ # ScreenRecorder.logger
34
+ #
35
+ def self.logger
36
+ return @logger if @logger
37
+
38
+ logger = Logger.new(STDOUT)
39
+ logger.level = Logger::ERROR
40
+ logger.progname = 'ScreenRecorder'
41
+ logger.formatter = proc do |severity, time, progname, msg|
42
+ "#{time.strftime('%F %T')} #{progname} - #{severity} - #{msg}\n"
43
+ end
44
+ logger.debug 'Logger initialized.'
45
+ @logger = logger
46
+ end
47
+ end
48
+
49
+ require 'screen-recorder/type_checker'
50
+ require 'screen-recorder/errors'
51
+ require 'screen-recorder/options'
52
+ require 'screen-recorder/titles'
53
+ require 'screen-recorder/common'
54
+ require 'screen-recorder/desktop'
54
55
  require 'screen-recorder/window'
@@ -1,147 +1,153 @@
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
1
+ # @since 1.0.0-beta11
2
+ #
3
+ # @api private
4
+ module ScreenRecorder
5
+ # @since 1.0.0-beta11
6
+ class Common
7
+ PROCESS_TIMEOUT = 5 # Seconds to wait for ffmpeg to quit
8
+
9
+ attr_reader :options, :video
10
+
11
+ def initialize(input:, output:, advanced: {})
12
+ @options = Options.new(input: input, output: output, advanced: advanced)
13
+ @video = nil
14
+ @process = nil
15
+ end
16
+
17
+ #
18
+ # Starts the recording
19
+ #
20
+ def start
21
+ @video = nil # New file
22
+ start_time = Time.now
23
+ @process = start_ffmpeg
24
+ elapsed = Time.now - start_time
25
+ ScreenRecorder.logger.debug "Process started in #{elapsed}s"
26
+ ScreenRecorder.logger.info 'Recording...'
27
+ @process
28
+ end
29
+
30
+ #
31
+ # Stops the recording
32
+ #
33
+ def stop
34
+ ScreenRecorder.logger.debug 'Stopping ffmpeg...'
35
+ stop_ffmpeg
36
+ ScreenRecorder.logger.debug 'Stopped ffmpeg.'
37
+ ScreenRecorder.logger.info 'Recording complete.'
38
+ @video = FFMPEG::Movie.new(options.output)
39
+ end
40
+
41
+ #
42
+ # Discards the recorded file. Useful in automated testing
43
+ # when a test passes and the recorded file is no longer
44
+ # needed.
45
+ #
46
+ def discard
47
+ FileUtils.rm options.output
48
+ end
49
+
50
+ alias delete discard
51
+
52
+ private
53
+
54
+ #
55
+ # Launches the ffmpeg binary using a generated command based on
56
+ # the given options.
57
+ #
58
+ def start_ffmpeg
59
+ raise Errors::DependencyNotFound, 'ffmpeg binary not found.' unless ffmpeg_exists?
60
+
61
+ ScreenRecorder.logger.debug "Command: #{command}"
62
+ process = build_command
63
+ @log_file = File.new(options.log, 'w+')
64
+ process.io.stdout = process.io.stderr = @log_file
65
+ @log_file.sync = true
66
+ process.duplex = true
67
+ process.start
68
+ sleep(1.5) # Takes ~1.5s on average to initialize
69
+ # Stopped because of an error
70
+ raise FFMPEG::Error, "Failed to start ffmpeg. Reason: #{lines_from_log(:last, 2)}" if process.exited?
71
+
72
+ process
73
+ end
74
+
75
+ #
76
+ # Sends 'q' to the ffmpeg binary to gracefully stop the process.
77
+ # Forcefully terminates it if it takes more than 5s.
78
+ #
79
+ def stop_ffmpeg
80
+ @process.io.stdin.puts 'q' # Gracefully exit ffmpeg
81
+ @process.io.stdin.close
82
+ @log_file.close
83
+ @process.poll_for_exit(PROCESS_TIMEOUT)
84
+ @process.exit_code
85
+ rescue ChildProcess::TimeoutError
86
+ ScreenRecorder.logger.error 'FFmpeg failed to stop. Force killing it...'
87
+ @process.stop # Tries increasingly harsher methods to kill the process.
88
+ ScreenRecorder.logger.error "Check '#{@options.log}' for more information."
89
+ end
90
+
91
+ #
92
+ # Generates the command line arguments based on the given
93
+ # options.
94
+ #
95
+ def command
96
+ cmd = "#{ScreenRecorder.ffmpeg_binary} -y "
97
+ cmd << @options.parsed
98
+ end
99
+
100
+ #
101
+ # Waits for IO#eof? to return true
102
+ # after 'q' is sent to the ffmpeg process.
103
+ #
104
+ def wait_for_io_eof(timeout)
105
+ start = Time.now
106
+ Timeout.timeout(timeout) do
107
+ sleep(0.1) until @process.eof?
108
+ end
109
+ ScreenRecorder.logger.debug "IO#eof? #{@process.eof?}"
110
+ Time.now - start
111
+ end
112
+
113
+ #
114
+ # Returns true if ffmpeg binary is found.
115
+ #
116
+ def ffmpeg_exists?
117
+ return !`which ffmpeg`.empty? if OS.linux? # "" if not found
118
+
119
+ return !`where ffmpeg`.empty? if OS.windows?
120
+
121
+ # If the user does not use ScreenRecorder.ffmpeg_binary=() to set the binary path,
122
+ # ScreenRecorder.ffmpeg_binary returns 'ffmpeg' assuming it must be in ENV. However,
123
+ # if the above two checks fail, it is not in the ENV either.
124
+ return false if ScreenRecorder.ffmpeg_binary == 'ffmpeg'
125
+
126
+ true
127
+ end
128
+
129
+ #
130
+ # Returns lines from the log file
131
+ #
132
+ def lines_from_log(position = :last, count = 2)
133
+ f = File.open(options.log)
134
+ lines = f.readlines
135
+ lines = lines.last(count) if position == :last
136
+ lines = lines.first(count) if position == :first
137
+ f.close
138
+
139
+ lines.join(' ')
140
+ end
141
+
142
+ #
143
+ # Returns OS specific arguments for Childprocess.build
144
+ #
145
+ def build_command
146
+ if OS.windows?
147
+ ChildProcess.build('cmd.exe', '/c', command)
148
+ else
149
+ ChildProcess.build('sh', '-c', command)
150
+ end
151
+ end
152
+ end
147
153
  end