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.
- checksums.yaml +4 -4
- data/.gitignore +99 -99
- data/.rspec +3 -3
- data/.rubocop.yml +1 -1
- data/.travis.yml +49 -35
- data/CHANGES.md +89 -78
- data/LICENSE.txt +21 -21
- data/README.md +212 -218
- data/appveyor.yml +23 -0
- data/bin/console +0 -0
- data/bin/setup +8 -8
- data/lib/screen-recorder.rb +54 -53
- data/lib/screen-recorder/common.rb +152 -146
- data/lib/screen-recorder/desktop.rb +40 -40
- data/lib/screen-recorder/errors.rb +9 -9
- data/lib/screen-recorder/options.rb +168 -151
- data/lib/screen-recorder/titles.rb +52 -49
- data/lib/screen-recorder/type_checker.rb +13 -11
- data/lib/screen-recorder/version.rb +2 -2
- data/lib/screen-recorder/window.rb +22 -22
- data/screen-recorder.gemspec +41 -39
- metadata +34 -6
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
|
data/lib/screen-recorder.rb
CHANGED
@@ -1,54 +1,55 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
require '
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
#
|
19
|
-
#
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
#
|
26
|
-
#
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
#
|
33
|
-
#
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
logger
|
39
|
-
logger.
|
40
|
-
logger.
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
end
|
47
|
-
|
48
|
-
|
49
|
-
require 'screen-recorder/
|
50
|
-
require 'screen-recorder/
|
51
|
-
require 'screen-recorder/
|
52
|
-
require 'screen-recorder/
|
53
|
-
require 'screen-recorder/
|
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
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
@process
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
#
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
end
|
90
|
-
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
def
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
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
|