screen-recorder 1.1.0 → 1.2.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.
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