headless 2.3.1 → 3.0.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 +5 -5
- data/.github/workflows/test.yml +38 -0
- data/.gitignore +0 -1
- data/CHANGELOG +39 -32
- data/Gemfile +1 -1
- data/Gemfile.lock +96 -0
- data/README.md +39 -20
- data/headless.gemspec +13 -12
- data/lib/headless/cli_util.rb +15 -10
- data/lib/headless/video/video_recorder.rb +26 -37
- data/lib/headless.rb +58 -36
- data/spec/headless_spec.rb +67 -51
- data/spec/integration_spec.rb +8 -8
- data/spec/video_recorder_spec.rb +35 -45
- metadata +26 -16
- data/.travis.yml +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 73b67eca56b8f3e94adeb95432880e74468bf145e91d5c6c72e31d4aa35666a8
|
4
|
+
data.tar.gz: adea55f03a50f1d535397e5c26c0c1f5926a86cff454df8c51b359d6ae080d6d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '083d766fb621d8fe7c9efa94b2489c78d8d3a993e697ee066750bec64a502fd46c300107a0a105e2dcaa0d66ed0ed29a797911d821244409defa83dc3d1b1afc'
|
7
|
+
data.tar.gz: 8a56cbbe08e1bce612eeccdbd999f88510cf0371799c210f0ce26975a601ad47d361622d14eee769c1335d2ca256efae1ac43e41440c0e3edabd6b519835a39d
|
@@ -0,0 +1,38 @@
|
|
1
|
+
name: Test
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
pull_request:
|
6
|
+
|
7
|
+
jobs:
|
8
|
+
test:
|
9
|
+
runs-on: ubuntu-latest
|
10
|
+
strategy:
|
11
|
+
matrix:
|
12
|
+
ruby:
|
13
|
+
- "3.4"
|
14
|
+
- "3.3"
|
15
|
+
- "3.2"
|
16
|
+
- "jruby-10.0.0.1"
|
17
|
+
- "jruby-9.4.12.1"
|
18
|
+
steps:
|
19
|
+
- uses: actions/checkout@v4
|
20
|
+
- name: Set up Ruby
|
21
|
+
uses: ruby/setup-ruby@v1
|
22
|
+
with:
|
23
|
+
ruby-version: ${{ matrix.ruby }}
|
24
|
+
bundler-cache: true
|
25
|
+
- name: Install system dependencies
|
26
|
+
run: |
|
27
|
+
sudo apt-get update
|
28
|
+
sudo apt-get install -y ffmpeg imagemagick
|
29
|
+
- name: Install geckodriver
|
30
|
+
env:
|
31
|
+
GECKODRIVER_VERSION: v0.36.0
|
32
|
+
run: |
|
33
|
+
wget https://github.com/mozilla/geckodriver/releases/download/${GECKODRIVER_VERSION}/geckodriver-${GECKODRIVER_VERSION}-linux64.tar.gz
|
34
|
+
mkdir -p geckodriver
|
35
|
+
tar -xzf geckodriver-${GECKODRIVER_VERSION}-linux64.tar.gz -C geckodriver
|
36
|
+
echo "$GITHUB_WORKSPACE/geckodriver" >> $GITHUB_PATH
|
37
|
+
- name: Run tests
|
38
|
+
run: bundle exec rspec
|
data/.gitignore
CHANGED
data/CHANGELOG
CHANGED
@@ -1,73 +1,80 @@
|
|
1
|
+
## 3.0.0 (2025-05-10)
|
2
|
+
|
3
|
+
- Better late then never!
|
4
|
+
- Support all modern Rubies.
|
5
|
+
- Drop support of Ruby before 3.2.
|
6
|
+
- Drop `avconv` because it's no longer maintained.
|
7
|
+
|
1
8
|
## 2.3.1 (2016-08-25)
|
2
9
|
|
3
|
-
|
4
|
-
|
10
|
+
- Full JRuby support
|
11
|
+
- HOTFIX remove `mkmf` require.
|
5
12
|
|
6
13
|
## 2.3.0 (2016-08-24)
|
7
14
|
|
8
|
-
|
9
|
-
|
10
|
-
|
15
|
+
- Remove dependency on the `which` command (from @skateman)
|
16
|
+
- Add `devices` options to video recorder (from @atzorvas)
|
17
|
+
- By default, do not destroy Headless started by a different process (from @marxarelli)
|
11
18
|
|
12
19
|
## 2.2.3 (2016-03-17)
|
13
20
|
|
14
|
-
|
21
|
+
- Fix race condition when starting Xvfb [#75] (from @NfNitLoop)
|
15
22
|
|
16
23
|
## 2.2.2 (2016-02-08)
|
17
24
|
|
18
|
-
|
25
|
+
- Fix file permissions issue with gem. No actual changes
|
19
26
|
|
20
27
|
## 2.2.0 (2015-07-05)
|
21
28
|
|
22
|
-
|
23
|
-
|
24
|
-
|
29
|
+
- Allow reuse of displays started by other user (from @marxarelli)
|
30
|
+
- Add support for graphicsmagick instead of ImageMagick (from @BlakeMesdag)
|
31
|
+
- Wait for Xvfb to finish when destroying it, to avoid creating zombie processes (from @samnissen)
|
25
32
|
|
26
33
|
## 2.1.0 (2015-05-10)
|
27
34
|
|
28
|
-
|
35
|
+
- Allow path to video recorder binary to be customized (from @briandamaged)
|
29
36
|
|
30
37
|
## 2.0.0 (2015-04-23)
|
31
38
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
39
|
+
- Rewritten Xvfb launch using Process.spawn and avoiding a shell
|
40
|
+
- Do not manually remove X11 lock file when stopping Xvfb; this isn’t conventional. Should eliminate some errors with not being able to find Xvfb
|
41
|
+
- More informative error messages
|
42
|
+
- Detect situation when Xvfb can’t listen to any sockets and raise corresponding error.
|
43
|
+
- If video recorder provider is libav, use avconv binary instead of ffmpeg
|
44
|
+
- Fixes to video recorder launch options (from @gpavlidi, @abotalov, @ynagorny, @WeAreFarmGeek)
|
45
|
+
- Customize launch timeout (from @ShockwaveNN)
|
46
|
+
- Properly working integration tests
|
40
47
|
|
41
48
|
## 1.0.2 (2014-06-03)
|
42
49
|
|
43
|
-
|
44
|
-
|
50
|
+
- pass options correctly to ffmpeg (from @abotalov)
|
51
|
+
- only destroy headless if it was created (from @evandrodp)
|
45
52
|
|
46
53
|
## 1.0.1 (2013-02-20)
|
47
54
|
|
48
|
-
|
55
|
+
- when starting, wait for Xvfb to launch (fixed issue #33)
|
49
56
|
|
50
57
|
## 1.0.0 (2013-01-28)
|
51
58
|
|
52
|
-
|
53
|
-
|
59
|
+
- bugfix release
|
60
|
+
- version number compliant to the [semantic versioning system](http://semver.org)
|
54
61
|
|
55
62
|
## 0.3.1 (2012-03-29)
|
56
63
|
|
57
|
-
|
58
|
-
|
64
|
+
- added autopicking of display number, if the requested one is already taken
|
65
|
+
- fixed plenty of bugs thanks to @recursive, @gshakhn, @masatomo and @mabotelh
|
59
66
|
|
60
67
|
## 0.2.2 (2011-09-01)
|
61
68
|
|
62
|
-
|
69
|
+
- improve detection of ffmpeg process (from https://github.com/alanshields/headless)
|
63
70
|
|
64
71
|
## 0.2.1 (2011-08-26)
|
65
72
|
|
66
|
-
|
67
|
-
|
68
|
-
|
73
|
+
- added ability to capture screenshots (from https://github.com/iafonov/headless)
|
74
|
+
- added ability to capture video (from https://github.com/iafonov/headless)
|
75
|
+
- fixed issue with stray pidfile
|
69
76
|
|
70
77
|
## 0.1.0 (2010-08-15)
|
71
78
|
|
72
|
-
|
73
|
-
|
79
|
+
- introduced options
|
80
|
+
- make it possible to change virtual screen dimensions and pixel depth
|
data/Gemfile
CHANGED
data/Gemfile.lock
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
headless (3.0.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
ast (2.4.3)
|
10
|
+
base64 (0.2.0)
|
11
|
+
diff-lcs (1.6.1)
|
12
|
+
json (2.11.3)
|
13
|
+
json (2.11.3-java)
|
14
|
+
language_server-protocol (3.17.0.4)
|
15
|
+
lint_roller (1.1.0)
|
16
|
+
logger (1.7.0)
|
17
|
+
parallel (1.27.0)
|
18
|
+
parser (3.3.8.0)
|
19
|
+
ast (~> 2.4.1)
|
20
|
+
racc
|
21
|
+
prism (1.4.0)
|
22
|
+
racc (1.8.1)
|
23
|
+
racc (1.8.1-java)
|
24
|
+
rainbow (3.1.1)
|
25
|
+
rake (13.2.1)
|
26
|
+
regexp_parser (2.10.0)
|
27
|
+
rexml (3.4.1)
|
28
|
+
rspec (3.13.0)
|
29
|
+
rspec-core (~> 3.13.0)
|
30
|
+
rspec-expectations (~> 3.13.0)
|
31
|
+
rspec-mocks (~> 3.13.0)
|
32
|
+
rspec-core (3.13.3)
|
33
|
+
rspec-support (~> 3.13.0)
|
34
|
+
rspec-expectations (3.13.4)
|
35
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
36
|
+
rspec-support (~> 3.13.0)
|
37
|
+
rspec-mocks (3.13.4)
|
38
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
39
|
+
rspec-support (~> 3.13.0)
|
40
|
+
rspec-support (3.13.3)
|
41
|
+
rubocop (1.75.5)
|
42
|
+
json (~> 2.3)
|
43
|
+
language_server-protocol (~> 3.17.0.2)
|
44
|
+
lint_roller (~> 1.1.0)
|
45
|
+
parallel (~> 1.10)
|
46
|
+
parser (>= 3.3.0.2)
|
47
|
+
rainbow (>= 2.2.2, < 4.0)
|
48
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
49
|
+
rubocop-ast (>= 1.44.0, < 2.0)
|
50
|
+
ruby-progressbar (~> 1.7)
|
51
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
52
|
+
rubocop-ast (1.44.1)
|
53
|
+
parser (>= 3.3.7.2)
|
54
|
+
prism (~> 1.4)
|
55
|
+
rubocop-performance (1.25.0)
|
56
|
+
lint_roller (~> 1.1)
|
57
|
+
rubocop (>= 1.75.0, < 2.0)
|
58
|
+
rubocop-ast (>= 1.38.0, < 2.0)
|
59
|
+
ruby-progressbar (1.13.0)
|
60
|
+
rubyzip (2.4.1)
|
61
|
+
selenium-webdriver (4.32.0)
|
62
|
+
base64 (~> 0.2)
|
63
|
+
logger (~> 1.4)
|
64
|
+
rexml (~> 3.2, >= 3.2.5)
|
65
|
+
rubyzip (>= 1.2.2, < 3.0)
|
66
|
+
websocket (~> 1.0)
|
67
|
+
standard (1.49.0)
|
68
|
+
language_server-protocol (~> 3.17.0.2)
|
69
|
+
lint_roller (~> 1.0)
|
70
|
+
rubocop (~> 1.75.2)
|
71
|
+
standard-custom (~> 1.0.0)
|
72
|
+
standard-performance (~> 1.8)
|
73
|
+
standard-custom (1.0.2)
|
74
|
+
lint_roller (~> 1.0)
|
75
|
+
rubocop (~> 1.50)
|
76
|
+
standard-performance (1.8.0)
|
77
|
+
lint_roller (~> 1.1)
|
78
|
+
rubocop-performance (~> 1.25.0)
|
79
|
+
unicode-display_width (3.1.4)
|
80
|
+
unicode-emoji (~> 4.0, >= 4.0.4)
|
81
|
+
unicode-emoji (4.0.4)
|
82
|
+
websocket (1.2.11)
|
83
|
+
|
84
|
+
PLATFORMS
|
85
|
+
java
|
86
|
+
ruby
|
87
|
+
|
88
|
+
DEPENDENCIES
|
89
|
+
headless!
|
90
|
+
rake
|
91
|
+
rspec (>= 3.7)
|
92
|
+
selenium-webdriver (>= 4.32)
|
93
|
+
standard
|
94
|
+
|
95
|
+
BUNDLED WITH
|
96
|
+
2.6.8
|
data/README.md
CHANGED
@@ -1,6 +1,11 @@
|
|
1
|
-
# Headless
|
1
|
+
# Headless
|
2
2
|
|
3
|
-
|
3
|
+

|
4
|
+

|
5
|
+
|
6
|
+
<a href="https://www.buymeacoffee.com/leonidshevtsov" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
|
7
|
+
|
8
|
+
Headless is _the_ Ruby interface for Xvfb. It allows you to create a headless display straight from Ruby code, hiding the low-level action.
|
4
9
|
It can also capture images and video from the virtual framebuffer. For example, you can record screenshots and screencasts of your failing integration specs.
|
5
10
|
|
6
11
|
I created it so I can run Selenium tests in Cucumber without any shell scripting. Even more, you can go headless only when you run tests against Selenium.
|
@@ -10,7 +15,7 @@ Documentation is available at [rubydoc.info](http://www.rubydoc.info/gems/headle
|
|
10
15
|
|
11
16
|
[Changelog](https://github.com/leonid-shevtsov/headless/blob/master/CHANGELOG)
|
12
17
|
|
13
|
-
**Note: Headless will NOT hide most applications on
|
18
|
+
**Note: Headless will NOT hide most applications on macOS. [Here is a detailed explanation](https://github.com/leonid-shevtsov/headless/issues/31#issuecomment-8933108)**
|
14
19
|
|
15
20
|
## Installation
|
16
21
|
|
@@ -33,7 +38,7 @@ require 'selenium-webdriver'
|
|
33
38
|
Headless.ly do
|
34
39
|
driver = Selenium::WebDriver.for :firefox
|
35
40
|
driver.navigate.to 'http://google.com'
|
36
|
-
puts driver.title
|
41
|
+
puts driver.title
|
37
42
|
end
|
38
43
|
```
|
39
44
|
|
@@ -80,11 +85,11 @@ Headless.new(display: 100, destroy_at_exit: false).start
|
|
80
85
|
# test_suite_that_could_be_ran_multiple_times.rb
|
81
86
|
Headless.new(display: 100, reuse: true, destroy_at_exit: false).start
|
82
87
|
|
83
|
-
# reap_headless.rb
|
88
|
+
# reap_headless.rb
|
84
89
|
headless = Headless.new(display: 100, reuse: true)
|
85
90
|
headless.destroy
|
86
91
|
|
87
|
-
# kill_headless_without_waiting.rb
|
92
|
+
# kill_headless_without_waiting.rb
|
88
93
|
headless = Headless.new
|
89
94
|
headless.destroy_without_sync
|
90
95
|
```
|
@@ -135,14 +140,13 @@ headless = Headless.new(:video => { :frame_rate => 12, :codec => 'libx264' })
|
|
135
140
|
|
136
141
|
Available options:
|
137
142
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
*
|
143
|
-
|
144
|
-
|
145
|
-
* :extra - array of extra ffmpeg options, default: []
|
143
|
+
- :codec - codec to be used by ffmpeg
|
144
|
+
- :frame_rate - frame rate of video capture
|
145
|
+
- :ffmpeg_path - Explicit path to ffmpeg. Only required when the binary cannot be discovered on the system $PATH.
|
146
|
+
- :pid*file_path - path to ffmpeg pid file, default: "/tmp/.headless_ffmpeg*#{@display}.pid"
|
147
|
+
- :tmp*file_path - path to tmp video file, default: "/tmp/.headless_ffmpeg*#{@display}.mov"
|
148
|
+
- :log_file_path - ffmpeg log file, default: "/dev/null"
|
149
|
+
- :extra - array of extra ffmpeg options, default: []
|
146
150
|
|
147
151
|
## Taking screenshots
|
148
152
|
|
@@ -150,23 +154,39 @@ Call `headless.take_screenshot` to take a screenshot. It needs two arguments:
|
|
150
154
|
|
151
155
|
- file_path - path where the image should be stored
|
152
156
|
- options - options, that can be:
|
153
|
-
|
157
|
+
:using - :imagemagick or :xwd, :imagemagick is default, if :imagemagick is used, image format is determined by file_path extension
|
154
158
|
|
155
159
|
Screenshots can be taken by either using `import` (part of `imagemagick` library) or `xwd` utility.
|
156
160
|
|
157
|
-
`import` captures a screenshot and saves it in the format of the specified file. It is convenient but not too fast as
|
161
|
+
`import` captures a screenshot and saves it in the format of the specified file. It is convenient but not too fast as
|
158
162
|
it has to do the encoding synchronously.
|
159
163
|
|
160
|
-
`xwd` will capture a screenshot very fast and store it in its own format, which can then be converted to one
|
164
|
+
`xwd` will capture a screenshot very fast and store it in its own format, which can then be converted to one
|
161
165
|
of other picture formats using, for example, netpbm utilities - `xwdtopnm <xwd_file> | pnmtopng > capture.png`.
|
162
166
|
|
163
167
|
To install the necessary libraries on ubuntu:
|
164
168
|
|
165
169
|
`import` - run `sudo apt-get install imagemagick`
|
166
170
|
`xwd` - run `sudo apt-get install X11-apps` and if you are going to use netpbm utilities for image conversion - `sudo apt-get install netpbm`
|
167
|
-
|
171
|
+
|
168
172
|
## Troubleshooting
|
169
173
|
|
174
|
+
### `/tmp/.X11-unix` is missing
|
175
|
+
|
176
|
+
Xvfb requires this directory to exist. It cannot be created automatically, because the directory must be owned by the root user. (You will never get this error if running as root - for example, in a Docker container.)
|
177
|
+
|
178
|
+
On macOS, the directory will be created when you run XQuartz.app. But since `/tmp` is cleared on reboot, you will need to open XQuartz.app after a reboot before running Xvfb. (You don't need to leave it running.)
|
179
|
+
|
180
|
+
To create this directory manually, on either macOS or Linux:
|
181
|
+
|
182
|
+
```
|
183
|
+
mkdir /tmp/.X11-unix
|
184
|
+
sudo chmod 1777 /tmp/.X11-unix
|
185
|
+
sudo chown root /tmp/.X11-unix/
|
186
|
+
```
|
187
|
+
|
188
|
+
Note that you may need to run these commands after every reboot, too.
|
189
|
+
|
170
190
|
### Display socket is taken but lock file is missing
|
171
191
|
|
172
192
|
This means that there is an X server that is taking up the chosen display number, but its lock file is missing. This is an exceptional situation. Please stop the server process manually (`pkill Xvfb`) and open an issue.
|
@@ -175,9 +195,8 @@ This means that there is an X server that is taking up the chosen display number
|
|
175
195
|
|
176
196
|
If video is not recording, and there are no visible exceptions, try passing the following option to Headless to figure out the reason: `Headless.new(video: {log_file_path: STDERR})`. In particular, there are some issues with the version of avconv packaged with Ubuntu 12.04 - an outdated release, but still in use on Travis.
|
177
197
|
|
178
|
-
|
179
198
|
##[Contributors](https://github.com/leonid-shevtsov/headless/graphs/contributors)
|
180
199
|
|
181
200
|
---
|
182
201
|
|
183
|
-
© 2011-
|
202
|
+
© 2011-2025 [Leonid Shevtsov](https://leonid.shevtsov.me), released under the MIT license
|
data/headless.gemspec
CHANGED
@@ -1,21 +1,22 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
|
-
s.author =
|
3
|
-
s.email =
|
2
|
+
s.author = "Leonid Shevtsov"
|
3
|
+
s.email = "leonid@shevtsov.me"
|
4
4
|
|
5
|
-
s.name =
|
6
|
-
s.version =
|
7
|
-
s.summary =
|
8
|
-
s.license =
|
5
|
+
s.name = "headless"
|
6
|
+
s.version = "3.0.0"
|
7
|
+
s.summary = "Ruby headless display interface"
|
8
|
+
s.license = "MIT"
|
9
9
|
|
10
10
|
s.description = <<-EOF
|
11
11
|
Headless is a Ruby interface for Xvfb. It allows you to create a headless display straight from Ruby code, hiding some low-level action.
|
12
12
|
EOF
|
13
|
-
s.requirements =
|
14
|
-
s.homepage =
|
13
|
+
s.requirements = "Xvfb"
|
14
|
+
s.homepage = "https://github.com/leonid-shevtsov/headless"
|
15
15
|
|
16
|
-
s.files
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
17
|
|
18
|
-
s.add_development_dependency
|
19
|
-
s.add_development_dependency
|
20
|
-
s.add_development_dependency
|
18
|
+
s.add_development_dependency "rake"
|
19
|
+
s.add_development_dependency "rspec", ">= 3.7"
|
20
|
+
s.add_development_dependency "selenium-webdriver", ">=4.32"
|
21
|
+
s.add_development_dependency "standard"
|
21
22
|
end
|
data/lib/headless/cli_util.rb
CHANGED
@@ -5,21 +5,21 @@ class Headless
|
|
5
5
|
end
|
6
6
|
|
7
7
|
def self.ensure_application_exists!(app, error_message)
|
8
|
-
if !
|
8
|
+
if !application_exists?(app)
|
9
9
|
raise Headless::Exception.new(error_message)
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
13
|
# Credit: http://stackoverflow.com/a/5471032/6678
|
14
14
|
def self.path_to(app)
|
15
|
-
exts = ENV[
|
16
|
-
ENV[
|
15
|
+
exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""]
|
16
|
+
ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
|
17
17
|
exts.each { |ext|
|
18
18
|
exe = File.join(path, "#{app}#{ext}")
|
19
19
|
return exe if File.executable?(exe) && !File.directory?(exe)
|
20
20
|
}
|
21
21
|
end
|
22
|
-
|
22
|
+
nil
|
23
23
|
end
|
24
24
|
|
25
25
|
def self.process_mine?(pid)
|
@@ -35,21 +35,26 @@ class Headless
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def self.read_pid(pid_filename)
|
38
|
-
pid =
|
38
|
+
pid = begin
|
39
|
+
File.read(pid_filename)
|
40
|
+
rescue
|
41
|
+
""
|
42
|
+
end.strip
|
39
43
|
pid.empty? ? nil : pid.to_i
|
40
44
|
end
|
41
45
|
|
42
|
-
def self.fork_process(command, pid_filename, log_filename=
|
46
|
+
def self.fork_process(command, pid_filename, log_filename = File::NULL)
|
43
47
|
pid = Process.spawn(command, err: log_filename)
|
44
|
-
File.open pid_filename,
|
48
|
+
File.open pid_filename, "w" do |f|
|
45
49
|
f.puts pid
|
46
50
|
end
|
47
51
|
end
|
48
52
|
|
49
|
-
def self.kill_process(pid_filename, options={})
|
50
|
-
|
53
|
+
def self.kill_process(pid_filename, options = {})
|
54
|
+
pid = read_pid(pid_filename)
|
55
|
+
if pid
|
51
56
|
begin
|
52
|
-
Process.kill
|
57
|
+
Process.kill "TERM", pid
|
53
58
|
Process.wait pid if options[:wait]
|
54
59
|
rescue Errno::ESRCH
|
55
60
|
# no such process; assume it's already killed
|
@@ -1,16 +1,15 @@
|
|
1
|
-
require
|
1
|
+
require "tempfile"
|
2
2
|
|
3
3
|
class Headless
|
4
4
|
class VideoRecorder
|
5
|
-
attr_accessor :pid_file_path, :tmp_file_path, :log_file_path, :
|
5
|
+
attr_accessor :pid_file_path, :tmp_file_path, :log_file_path, :ffmpeg_path
|
6
6
|
|
7
7
|
# Construct a new Video Recorder instance. Typically done from inside Headless, but can be also created manually,
|
8
8
|
# and even used separately from Headless' Xvfb features.
|
9
9
|
# * display - display number to capture
|
10
10
|
# * dimensions - dimensions of the captured video
|
11
11
|
# * options - available options:
|
12
|
-
# *
|
13
|
-
# * provider_binary_path - override path to ffmpeg / libav binary
|
12
|
+
# * ffmpeg_path - override path to ffmpeg binary
|
14
13
|
# * pid_file_path - override path to PID file, default is placed in /tmp
|
15
14
|
# * tmp_file_path - override path to temp file, default is placed in /tmp
|
16
15
|
# * log_file_path - set log file path, default is /dev/null
|
@@ -24,19 +23,19 @@ class Headless
|
|
24
23
|
|
25
24
|
@pid_file_path = options.fetch(:pid_file_path, "/tmp/.headless_ffmpeg_#{@display}.pid")
|
26
25
|
@tmp_file_path = options.fetch(:tmp_file_path, "/tmp/.headless_ffmpeg_#{@display}.mov")
|
27
|
-
@log_file_path = options.fetch(:log_file_path,
|
26
|
+
@log_file_path = options.fetch(:log_file_path, File::NULL)
|
28
27
|
@codec = options.fetch(:codec, "qtrle")
|
29
28
|
@frame_rate = options.fetch(:frame_rate, 30)
|
30
|
-
@provider = options.fetch(:provider, :libav) # or :ffmpeg
|
31
29
|
|
32
|
-
# If no
|
33
|
-
|
34
|
-
@provider_binary_path = options.fetch(:provider_binary_path, guess_the_provider_binary_path)
|
30
|
+
# If no ffmpeg_path was specified, use the default
|
31
|
+
@ffmpeg_path = options.fetch(:ffmpeg_path, options.fetch(:provider_binary_path, "ffmpeg"))
|
35
32
|
|
36
33
|
@extra = Array(options.fetch(:extra, []))
|
37
34
|
@devices = Array(options.fetch(:devices, []))
|
38
35
|
|
39
|
-
CliUtil.ensure_application_exists!(
|
36
|
+
CliUtil.ensure_application_exists!(ffmpeg_path,
|
37
|
+
"#{ffmpeg_path} not found on your system. " \
|
38
|
+
"Install it or change video recorder provider")
|
40
39
|
end
|
41
40
|
|
42
41
|
def capture_running?
|
@@ -45,7 +44,7 @@ class Headless
|
|
45
44
|
|
46
45
|
def start_capture
|
47
46
|
CliUtil.fork_process(command_line_for_capture,
|
48
|
-
|
47
|
+
@pid_file_path, @log_file_path)
|
49
48
|
at_exit do
|
50
49
|
exit_status = $!.status if $!.is_a?(SystemExit)
|
51
50
|
stop_and_discard
|
@@ -54,9 +53,10 @@ class Headless
|
|
54
53
|
end
|
55
54
|
|
56
55
|
def stop_and_save(path)
|
57
|
-
CliUtil.kill_process(@pid_file_path, :
|
58
|
-
if File.
|
56
|
+
CliUtil.kill_process(@pid_file_path, wait: true)
|
57
|
+
if File.exist? @tmp_file_path
|
59
58
|
begin
|
59
|
+
FileUtils.mkdir_p(File.dirname(path))
|
60
60
|
FileUtils.mv(@tmp_file_path, path)
|
61
61
|
rescue Errno::EINVAL
|
62
62
|
nil
|
@@ -65,7 +65,7 @@ class Headless
|
|
65
65
|
end
|
66
66
|
|
67
67
|
def stop_and_discard
|
68
|
-
CliUtil.kill_process(@pid_file_path, :
|
68
|
+
CliUtil.kill_process(@pid_file_path, wait: true)
|
69
69
|
begin
|
70
70
|
FileUtils.rm(@tmp_file_path)
|
71
71
|
rescue Errno::ENOENT
|
@@ -75,32 +75,21 @@ class Headless
|
|
75
75
|
|
76
76
|
private
|
77
77
|
|
78
|
-
def guess_the_provider_binary_path
|
79
|
-
@provider== :libav ? 'avconv' : 'ffmpeg'
|
80
|
-
end
|
81
|
-
|
82
78
|
def command_line_for_capture
|
83
|
-
|
84
|
-
group_of_pic_size_option = '-g 600'
|
85
|
-
dimensions = @dimensions
|
86
|
-
else
|
87
|
-
group_of_pic_size_option = nil
|
88
|
-
dimensions = @dimensions.match(/^(\d+x\d+)/)[0]
|
89
|
-
end
|
79
|
+
dimensions = @dimensions.match(/^(\d+x\d+)/)[0]
|
90
80
|
|
91
81
|
[
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
].flatten.compact.join(' ')
|
82
|
+
CliUtil.path_to(ffmpeg_path),
|
83
|
+
"-y",
|
84
|
+
"-r #{@frame_rate}",
|
85
|
+
"-s #{dimensions}",
|
86
|
+
"-f x11grab",
|
87
|
+
"-i :#{@display}",
|
88
|
+
@devices,
|
89
|
+
"-vcodec #{@codec}",
|
90
|
+
@extra,
|
91
|
+
@tmp_file_path
|
92
|
+
].flatten.compact.join(" ")
|
104
93
|
end
|
105
94
|
end
|
106
95
|
end
|