screen-recorder 1.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 +7 -0
- data/.github/ISSUE_TEMPLATE.md +15 -0
- data/.gitignore +108 -0
- data/.rspec +3 -0
- data/.rubocop.yml +50 -0
- data/.travis.yml +32 -0
- data/CHANGES.md +71 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +211 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/screen-recorder.rb +46 -0
- data/lib/screen-recorder/common.rb +128 -0
- data/lib/screen-recorder/desktop.rb +31 -0
- data/lib/screen-recorder/errors.rb +10 -0
- data/lib/screen-recorder/options.rb +148 -0
- data/lib/screen-recorder/titles.rb +82 -0
- data/lib/screen-recorder/type_checker.rb +12 -0
- data/lib/screen-recorder/version.rb +3 -0
- data/lib/screen-recorder/window.rb +23 -0
- data/screen-recorder.gemspec +31 -0
- metadata +178 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 554e522713dae29caa579b2645498635c021cc12a3de77cba2b905215abbd4cd
|
|
4
|
+
data.tar.gz: 37d995de354b476b9b03d97c20961dfae0d4a700de34640b23c84ba29e9cc4ac
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e1d21d41f96ef133eade14aa3477609c30ffe482c4a8530095643a9fc6b8f3357c78d36dae19b38489f9386782ccc227bf79b2d48d54d59a87d51f4be6fdb258
|
|
7
|
+
data.tar.gz: d3b9e5727224acf8415d43ae71f093e9aec72c81ab553f5bfd994c67f81f87b7af68e7ab8bc3c2c2bf8481377507b7bcd094b436bee7ccb4bc5e60ecd2e02ff8
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
### Summary
|
|
2
|
+
A clear and concise description of what the bug is.
|
|
3
|
+
|
|
4
|
+
### Debug Info
|
|
5
|
+
Please provide the following information:
|
|
6
|
+
|
|
7
|
+
* Operating system - Microsoft Windows, Linux, or macOS.
|
|
8
|
+
* `opts` Hash used for the recorder.
|
|
9
|
+
* Recorder log (`ffmpeg.log`) file as a gist or on pastebin.com.
|
|
10
|
+
* Set `log_level: Logger::DEBUG`, run the recorder, and share the
|
|
11
|
+
console output as a gist or on pastebin.com.
|
|
12
|
+
|
|
13
|
+
### Expected Behavior
|
|
14
|
+
|
|
15
|
+
### Actual Behavior
|
data/.gitignore
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Gemfile.lock # Part of best practice
|
|
2
|
+
|
|
3
|
+
# Created by https://www.gitignore.io/api/rubymine+all
|
|
4
|
+
|
|
5
|
+
### RubyMine+all ###
|
|
6
|
+
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
|
7
|
+
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
8
|
+
|
|
9
|
+
/.bundle/
|
|
10
|
+
/.yardoc
|
|
11
|
+
/_yardoc/
|
|
12
|
+
/coverage/
|
|
13
|
+
/doc/
|
|
14
|
+
/pkg/
|
|
15
|
+
/spec/reports/
|
|
16
|
+
/tmp/
|
|
17
|
+
/webdrivers_bin
|
|
18
|
+
*.log
|
|
19
|
+
*.mkv
|
|
20
|
+
*.mp4
|
|
21
|
+
*.avi
|
|
22
|
+
*.gif
|
|
23
|
+
|
|
24
|
+
Gemfile.lock
|
|
25
|
+
|
|
26
|
+
# rspec failure tracking
|
|
27
|
+
.rspec_status
|
|
28
|
+
|
|
29
|
+
# User-specific stuff
|
|
30
|
+
.idea/
|
|
31
|
+
.idea/**/workspace.xml
|
|
32
|
+
.idea/**/tasks.xml
|
|
33
|
+
.idea/**/usage.statistics.xml
|
|
34
|
+
.idea/**/dictionaries
|
|
35
|
+
.idea/**/shelf
|
|
36
|
+
|
|
37
|
+
# Generated files
|
|
38
|
+
.idea/**/contentModel.xml
|
|
39
|
+
|
|
40
|
+
# Sensitive or high-churn files
|
|
41
|
+
.idea/**/dataSources/
|
|
42
|
+
.idea/**/dataSources.ids
|
|
43
|
+
.idea/**/dataSources.local.xml
|
|
44
|
+
.idea/**/sqlDataSources.xml
|
|
45
|
+
.idea/**/dynamic.xml
|
|
46
|
+
.idea/**/uiDesigner.xml
|
|
47
|
+
.idea/**/dbnavigator.xml
|
|
48
|
+
|
|
49
|
+
# Gradle
|
|
50
|
+
.idea/**/gradle.xml
|
|
51
|
+
.idea/**/libraries
|
|
52
|
+
|
|
53
|
+
# Gradle and Maven with auto-import
|
|
54
|
+
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
55
|
+
# since they will be recreated, and may cause churn. Uncomment if using
|
|
56
|
+
# auto-import.
|
|
57
|
+
# .idea/modules.xml
|
|
58
|
+
# .idea/*.iml
|
|
59
|
+
# .idea/modules
|
|
60
|
+
|
|
61
|
+
# CMake
|
|
62
|
+
cmake-build-*/
|
|
63
|
+
|
|
64
|
+
# Mongo Explorer plugin
|
|
65
|
+
.idea/**/mongoSettings.xml
|
|
66
|
+
|
|
67
|
+
# File-based project format
|
|
68
|
+
*.iws
|
|
69
|
+
|
|
70
|
+
# IntelliJ
|
|
71
|
+
out/
|
|
72
|
+
|
|
73
|
+
# mpeltonen/sbt-idea plugin
|
|
74
|
+
.idea_modules/
|
|
75
|
+
|
|
76
|
+
# JIRA plugin
|
|
77
|
+
atlassian-ide-plugin.xml
|
|
78
|
+
|
|
79
|
+
# Cursive Clojure plugin
|
|
80
|
+
.idea/replstate.xml
|
|
81
|
+
|
|
82
|
+
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
83
|
+
com_crashlytics_export_strings.xml
|
|
84
|
+
crashlytics.properties
|
|
85
|
+
crashlytics-build.properties
|
|
86
|
+
fabric.properties
|
|
87
|
+
|
|
88
|
+
# Editor-based Rest Client
|
|
89
|
+
.idea/httpRequests
|
|
90
|
+
|
|
91
|
+
# Android studio 3.1+ serialized cache file
|
|
92
|
+
.idea/caches/build_file_checksums.ser
|
|
93
|
+
|
|
94
|
+
### RubyMine+all Patch ###
|
|
95
|
+
# Ignores the whole .idea folder and all .iml files
|
|
96
|
+
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
|
|
97
|
+
|
|
98
|
+
.idea/
|
|
99
|
+
|
|
100
|
+
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
|
|
101
|
+
|
|
102
|
+
*.iml
|
|
103
|
+
modules.xml
|
|
104
|
+
.idea/misc.xml
|
|
105
|
+
*.ipr
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# End of https://www.gitignore.io/api/rubymine+all
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Style/EndOfLine:
|
|
2
|
+
EnforcedStyle: crlf
|
|
3
|
+
|
|
4
|
+
Metrics/LineLength:
|
|
5
|
+
Max: 120
|
|
6
|
+
|
|
7
|
+
# Cop supports --auto-correct.
|
|
8
|
+
# Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, IgnoredMethods.
|
|
9
|
+
# SupportedStyles: line_count_based, semantic, braces_for_chaining
|
|
10
|
+
# ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object
|
|
11
|
+
# FunctionalMethods: let, let!, subject, watch
|
|
12
|
+
# IgnoredMethods: lambda, proc, it
|
|
13
|
+
Style/BlockDelimiters:
|
|
14
|
+
EnforcedStyle: braces_for_chaining
|
|
15
|
+
|
|
16
|
+
Style/CommentedKeyword:
|
|
17
|
+
Enabled: false
|
|
18
|
+
|
|
19
|
+
Style/DoubleNegation:
|
|
20
|
+
Enabled: false
|
|
21
|
+
|
|
22
|
+
Metrics/MethodLength:
|
|
23
|
+
Max: 18
|
|
24
|
+
|
|
25
|
+
Metrics/PerceivedComplexity:
|
|
26
|
+
Max: 10
|
|
27
|
+
|
|
28
|
+
Metrics/CyclomaticComplexity:
|
|
29
|
+
Max: 8
|
|
30
|
+
|
|
31
|
+
Metrics/AbcSize:
|
|
32
|
+
Max: 20
|
|
33
|
+
|
|
34
|
+
Style/MethodCallWithoutArgsParentheses:
|
|
35
|
+
Enabled: false
|
|
36
|
+
|
|
37
|
+
Style/Documentation:
|
|
38
|
+
Enabled: false
|
|
39
|
+
|
|
40
|
+
# Cop supports --auto-correct.
|
|
41
|
+
# Configuration parameters: EnforcedStyle.
|
|
42
|
+
# SupportedStyles: when_needed, always, never
|
|
43
|
+
Style/FrozenStringLiteralComment:
|
|
44
|
+
Enabled: false
|
|
45
|
+
|
|
46
|
+
TrailingBlankLines:
|
|
47
|
+
Enabled: false
|
|
48
|
+
|
|
49
|
+
Layout/MultilineMethodCallIndentation:
|
|
50
|
+
Enabled: false
|
data/.travis.yml
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
sudo: required
|
|
3
|
+
dist: xenial
|
|
4
|
+
language: ruby
|
|
5
|
+
cache: bundler
|
|
6
|
+
|
|
7
|
+
branches:
|
|
8
|
+
only:
|
|
9
|
+
- /.*/
|
|
10
|
+
|
|
11
|
+
rvm:
|
|
12
|
+
- 2.3.8
|
|
13
|
+
- 2.4.5
|
|
14
|
+
- 2.5.3
|
|
15
|
+
- 2.6.1
|
|
16
|
+
|
|
17
|
+
env:
|
|
18
|
+
- DISPLAY=":0.0"
|
|
19
|
+
|
|
20
|
+
addons:
|
|
21
|
+
firefox: latest
|
|
22
|
+
chrome: stable
|
|
23
|
+
|
|
24
|
+
before_install:
|
|
25
|
+
- gem update --system --no-document
|
|
26
|
+
|
|
27
|
+
before_script:
|
|
28
|
+
- sudo apt-get install -y ffmpeg
|
|
29
|
+
- ffmpeg -hwaccels
|
|
30
|
+
- sudo apt-get install wmctrl
|
|
31
|
+
|
|
32
|
+
script: xvfb-run --server-num=0.0 --server-args="-ac -screen 0 1024x768x24" bundle exec rake spec
|
data/CHANGES.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
### 1.0.0 (2019-03-15)
|
|
2
|
+
* Released first major version.
|
|
3
|
+
* Now uses `ScreenRecorder` as top level module. `FFMPEG` is not directly
|
|
4
|
+
exposed anymore.
|
|
5
|
+
* The recording modes are now available through `ScreenRecorder::Desktop`
|
|
6
|
+
and `ScreenRecorder::Window` classes to make the usage (parameters) simpler.
|
|
7
|
+
* Method parameters are now keywords instead of an `opts` Hash. This means
|
|
8
|
+
at least Ruby 2.0.0 is required.
|
|
9
|
+
* `framerate:` is now to be passed through the `advanced` Hash.
|
|
10
|
+
|
|
11
|
+
### 1.0.0.beta13 (2019-03-15)
|
|
12
|
+
* Gem will now be renamed to `screen-recorder`. Please refer to Issue
|
|
13
|
+
[#45](https://github.com/kapoorlakshya/screen-recorder/issues/45)
|
|
14
|
+
for more information.
|
|
15
|
+
|
|
16
|
+
### 1.0.0.beta12 (2019-03-12)
|
|
17
|
+
* Reverted post install message as `screen_recorder` is already taken.
|
|
18
|
+
|
|
19
|
+
### 1.0.0.beta11 (2019-03-12)
|
|
20
|
+
* Recording FPS (`framerate`) is defaulted to 15.0.
|
|
21
|
+
* Gem will soon be renamed to `screen_recorder`. Please refer to Issue
|
|
22
|
+
[#45](https://github.com/kapoorlakshya/screen-recorder/issues/45)
|
|
23
|
+
for more information.
|
|
24
|
+
|
|
25
|
+
### 1.0.0.beta10 (2019-02-05)
|
|
26
|
+
* Fixed an edge case in Microsoft Windows specific implementation of
|
|
27
|
+
`WindowTitles#fetch` where processes with mismatching names and window
|
|
28
|
+
titles, such as process `"Calculator.exe"` with window title `"CicMarshalWnd"`,
|
|
29
|
+
were omitted ([#35](https://github.com/kapoorlakshya/screen-recorder/issues/35)).
|
|
30
|
+
This fix also prints a warning when this mismatch occurs.
|
|
31
|
+
* Fixed bug in Linux specific `WindowTitles#fetch` implementation where
|
|
32
|
+
the filter by application name logic was removed. This filter is required
|
|
33
|
+
on Linux here because `wmctrl` returns all open window titles unlike
|
|
34
|
+
Microsoft Windows where `taskmgr` allows us get window titles by process
|
|
35
|
+
name.
|
|
36
|
+
* On Linux, you are now required to provide the `input` as `"desktop"`
|
|
37
|
+
or a display number, such as `":0.0"`. Run `echo $DISPLAY` to check your display number.
|
|
38
|
+
* QOL improvements - Type checking of inputs, spec cleanup, added more
|
|
39
|
+
tests, and fixed rubocop warnings.
|
|
40
|
+
|
|
41
|
+
### 1.0.0.beta9 (2019-01-22)
|
|
42
|
+
|
|
43
|
+
* :warning: `FFMPEG::RecordingRegions` is now `FFMPEG::WindowTitles`, so the module name is true to the function it provides.
|
|
44
|
+
* Added support for for a user given path via `FFMPEG#ffmpeg_binary=()`.
|
|
45
|
+
* Removed Bundler version requirement from gemspec to support all versions.
|
|
46
|
+
* Implement `#discard` (alias `#delete`) to discard the video file. Useful when your test passes and you want to get rid of the video file.
|
|
47
|
+
|
|
48
|
+
### 1.0.0.beta8 (2019-01-03)
|
|
49
|
+
|
|
50
|
+
* Fix a bug where the gem was incorrectly configured to be required as `ffmpeg/screenrecorder` instead of `screen-recorder`.
|
|
51
|
+
* `ScreenRecorder#start` now returns the IO process object in case the user has a use case for it.
|
|
52
|
+
* `RecordingRegion#fetch` now logs a warning that `x11grab` for Linux does not supporting window recording.
|
|
53
|
+
* :warning: Parameter `infile` is now `input` to make it more intuitive.
|
|
54
|
+
|
|
55
|
+
### 1.0.0.beta7 (2018-12-23)
|
|
56
|
+
|
|
57
|
+
* Fix bug in RecorderOptions where an incorrect object was referenced to read the user provided options.
|
|
58
|
+
|
|
59
|
+
### 1.0.0.beta6 (2018-12-3)
|
|
60
|
+
|
|
61
|
+
* Stopping the screenrecorder now prints the failure reason given by the ffmpeg binary when `#stop` fails (Issue #7).
|
|
62
|
+
* Log file is now defaulted to `ffmpeg.log` instead of redirecting to nul.
|
|
63
|
+
* `log_level` now defaults to `Logger::ERROR` instead of `Logger::INFO`.
|
|
64
|
+
|
|
65
|
+
### 1.0.0.beta5 (2018-11-26)
|
|
66
|
+
|
|
67
|
+
* `Screenrecorder` class is now `ScreenRecorder`.
|
|
68
|
+
* Add support for Linux.
|
|
69
|
+
* Now an exception raised if the gem fails to find `ffmpeg`.
|
|
70
|
+
* Fix a bug where a file named `nul` was created instead of directing the output to `NUL`.
|
|
71
|
+
* Fix a bug where `RecordingRegions#window_titles` was not returning anything because of missing return keyword.
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018 Lakshya Kapoor
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# ScreenRecorder
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/rb/screen-recorder)
|
|
4
|
+

|
|
5
|
+
[](https://www.rubydoc.info/github/kapoorlakshya/screen-recorder/master)
|
|
6
|
+
[](https://travis-ci.org/kapoorlakshya/screen-recorder)
|
|
7
|
+
[](https://codeclimate.com/github/kapoorlakshya/screen-recorder/maintainability)
|
|
8
|
+
|
|
9
|
+
A Ruby gem to record your computer screen - desktop or specific
|
|
10
|
+
window - using [FFmpeg](https://www.ffmpeg.org/). Primarily
|
|
11
|
+
geared towards recording automated UI test executions for debugging
|
|
12
|
+
and documentation.
|
|
13
|
+
|
|
14
|
+
Demo - [https://kapoorlakshya.github.io/introducing-screen-recorder-ruby-gem](https://kapoorlakshya.github.io/introducing-screen-recorder-ruby-gem).
|
|
15
|
+
|
|
16
|
+
## Compatibility
|
|
17
|
+
|
|
18
|
+
Supports Windows and Linux as of version 1.0.0. macOS support
|
|
19
|
+
is coming very soon.
|
|
20
|
+
|
|
21
|
+
Requires Ruby 2.0.0 (MRI) or higher, and is tested
|
|
22
|
+
with versions 2.3.8, 2.4.5, 2.5.3, and 2.6.1.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
#### 1. Setup FFmpeg
|
|
27
|
+
|
|
28
|
+
Linux and macOS instructions are [here](https://www.ffmpeg.org/download.html).
|
|
29
|
+
|
|
30
|
+
For Microsoft Windows, download the *libx264* enabled binary from [here](https://ffmpeg.zeranoe.com/builds/).
|
|
31
|
+
Once downloaded, add location of the `ffmpeg/bin` folder to `PATH` environment variable
|
|
32
|
+
([instructions](https://windowsloop.com/install-ffmpeg-windows-10/)).
|
|
33
|
+
|
|
34
|
+
Alternatively, you can provide the location using
|
|
35
|
+
`ScreenRecorder.ffmpeg_binary = '/path/to/binary'` in your project.
|
|
36
|
+
|
|
37
|
+
#### 2. Install gem
|
|
38
|
+
|
|
39
|
+
Next, add this line to your application's Gemfile:
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
gem 'screen-recorder'
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
And then execute:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
$ bundle
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Or install it yourself as:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
$ gem install screen-recorder
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
#### 3. Require gem
|
|
58
|
+
|
|
59
|
+
Require this gem in your project and start using the gem:
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
require 'screen-recorder'
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Record Desktop
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
@recorder = ScreenRecorder::Desktop.new(output: 'recording.mp4')
|
|
69
|
+
@recorder.start
|
|
70
|
+
|
|
71
|
+
# Run tests or whatever you want to record
|
|
72
|
+
|
|
73
|
+
@recorder.stop
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Linux users can optionally provide a `$DISPLAY` number as
|
|
77
|
+
`input: ':99.0'`. Default is `:0.0`.
|
|
78
|
+
|
|
79
|
+
## Record Application Window (Microsoft Windows only)
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
require 'watir'
|
|
83
|
+
|
|
84
|
+
browser = Watir::Browser.new :firefox
|
|
85
|
+
@recorder = ScreenRecorder::Window.new(title: 'Mozilla Firefox', output: 'recording.mp4')
|
|
86
|
+
@recorder.start
|
|
87
|
+
|
|
88
|
+
# Run tests or whatever you want to record
|
|
89
|
+
|
|
90
|
+
@recorder.stop
|
|
91
|
+
browser.quit
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
<b>Fetch Title</b>
|
|
95
|
+
|
|
96
|
+
A helper method is available to fetch the title of the active window
|
|
97
|
+
for the given process name.
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
ScreenRecorder::Titles.fetch('firefox') # Name of exe
|
|
101
|
+
#=> ["Mozilla Firefox"]
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
<b>Limitations</b>
|
|
105
|
+
- Only available for Microsoft Windows (*gdigrab*). Linux (*x11grab*) and macOS
|
|
106
|
+
(*avfoundation*) capture devices do not provide this feature. However, there
|
|
107
|
+
is a workaround documented in the [wiki](https://github.com/kapoorlakshya/screen-recorder/wiki/Window-recording-in-Linux-and-Mac).
|
|
108
|
+
- `#fetch` only returns the title from a currently active (visible) window
|
|
109
|
+
for the given process.
|
|
110
|
+
- `#fetch` may return `ArgumentError (invalid byte sequence in UTF-8)`
|
|
111
|
+
for a window title with non `UTF-8` characters. See [wiki](https://github.com/kapoorlakshya/screen-recorder/wiki/Invalid-byte-sequence-in-UTF-8)
|
|
112
|
+
for workaround.
|
|
113
|
+
- Always stop the recording before closing the application. Otherwise,
|
|
114
|
+
ffmpeg will force exit as soon as the window disappears and may produce
|
|
115
|
+
an invalid video file.
|
|
116
|
+
- If you're launching multiple applications or testing an application
|
|
117
|
+
at different window sizes, recording the `desktop` is a better option.
|
|
118
|
+
|
|
119
|
+
## Output
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
@recorder.video
|
|
123
|
+
#=> #<FFMPEG::Movie:0x00000000067e0a08
|
|
124
|
+
@path="recording.mp4",
|
|
125
|
+
@container="mov,mp4,m4a,3gp,3g2,mj2",
|
|
126
|
+
@duration=5.0,
|
|
127
|
+
@time=0.0,
|
|
128
|
+
@creation_time=nil,
|
|
129
|
+
@bitrate=1051,
|
|
130
|
+
@rotation=nil,
|
|
131
|
+
@video_stream="h264 (High 4:4:4 Predictive) (avc1 / 0x31637661), yuv444p, 2560x1440, 1048 kb/s, 15 fps, 15 tbr, 15360 tbn, 30 tbc (default)",
|
|
132
|
+
@audio_stream=nil,
|
|
133
|
+
@video_codec="h264 (High 4:4:4 Predictive) (avc1 / 0x31637661)", @colorspace="yuv444p",
|
|
134
|
+
@video_bitrate=1048,
|
|
135
|
+
@resolution="2560x1440">
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
If your test passes or you do not want the record for any reason,
|
|
139
|
+
simply call `@recorder.discard` or `@recorder.delete` to delete
|
|
140
|
+
the video file.
|
|
141
|
+
|
|
142
|
+
## Advanced Options
|
|
143
|
+
|
|
144
|
+
You can provide additional parameters to FFmpeg using the `advanced`
|
|
145
|
+
parameter. The keys in the Hash are prefixed with `-` and paired with the
|
|
146
|
+
values in the final command.
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
advanced = { framerate: 30,
|
|
150
|
+
log: 'recorder.log',
|
|
151
|
+
loglevel: 'level+debug', # For FFmpeg
|
|
152
|
+
video_size: '640x480',
|
|
153
|
+
show_region: '1' }
|
|
154
|
+
ScreenRecorder::Desktop.new(output: 'recording.mp4',
|
|
155
|
+
advanced: advanced)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
This will be parsed as:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
ffmpeg -y -f gdigrab -framerate 30 -loglevel level+debug -video_size 640x480 -show_region 1 -i desktop recording.mp4 2> recorder.log
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
This feature is yet to be fully tested, so please feel free
|
|
165
|
+
to report any bugs or request a feature.
|
|
166
|
+
|
|
167
|
+
## Logging
|
|
168
|
+
|
|
169
|
+
You can also configure the logging level of the gem:
|
|
170
|
+
|
|
171
|
+
```ruby
|
|
172
|
+
ScreenRecorder.logger.level = Logger::DEBUG
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Use with Cucumber
|
|
176
|
+
|
|
177
|
+
A Cucumber + Watir based example is available
|
|
178
|
+
[here](https://github.com/kapoorlakshya/cucumber-watir-test-recorder-example).
|
|
179
|
+
|
|
180
|
+
## Development
|
|
181
|
+
|
|
182
|
+
After checking out the repo, run `bin/setup` to install dependencies.
|
|
183
|
+
Then, run `bundle exec rake spec` to run the tests. You can also run
|
|
184
|
+
`bin/console` for an interactive prompt that will allow you to experiment.
|
|
185
|
+
|
|
186
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
187
|
+
|
|
188
|
+
## Contributing
|
|
189
|
+
|
|
190
|
+
Bug reports and pull requests are welcome.
|
|
191
|
+
|
|
192
|
+
- Please update the specs for your code changes and run them locally with `bundle exec rake spec`.
|
|
193
|
+
- Follow the Ruby style guide and format your code - https://github.com/rubocop-hq/ruby-style-guide
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
198
|
+
|
|
199
|
+
## Credits
|
|
200
|
+
|
|
201
|
+
[](http://streamio.com)
|
|
202
|
+
|
|
203
|
+
This gem is based on the [streamio-ffmpeg](https://github.com/streamio/streamio-ffmpeg) gem.
|
|
204
|
+
<br />
|
|
205
|
+
<br />
|
|
206
|
+
|
|
207
|
+

|
|
208
|
+
|
|
209
|
+
Thanks to [SauceLabs](https://saucelabs.com) for providing me with a
|
|
210
|
+
free account. If you manage an open source project, you can apply for
|
|
211
|
+
a free account [here](https://saucelabs.com/open-source).
|
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
require 'screen-recorder'
|
|
5
|
+
|
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
+
|
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
+
# require "pry"
|
|
11
|
+
# Pry.start
|
|
12
|
+
|
|
13
|
+
require 'irb'
|
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
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'
|
|
46
|
+
require 'screen-recorder/window'
|
|
@@ -0,0 +1,128 @@
|
|
|
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
|
|
128
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
|
31
|
+
end
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# @since 1.0.0-beta11
|
|
2
|
+
module ScreenRecorder
|
|
3
|
+
# @since 1.0.0-beta11
|
|
4
|
+
class Options
|
|
5
|
+
DEFAULT_LOG_FILE = 'ffmpeg.log'.freeze
|
|
6
|
+
DEFAULT_FPS = 15.0
|
|
7
|
+
|
|
8
|
+
def initialize(options)
|
|
9
|
+
TypeChecker.check options, Hash
|
|
10
|
+
TypeChecker.check options[:advanced], Hash if options[:advanced]
|
|
11
|
+
@options = verify_options options
|
|
12
|
+
|
|
13
|
+
unless advanced[:framerate]
|
|
14
|
+
advanced[:framerate] = DEFAULT_FPS
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
unless advanced[:log]
|
|
18
|
+
advanced[:log] = DEFAULT_LOG_FILE
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
#
|
|
23
|
+
# Returns given input file or input
|
|
24
|
+
#
|
|
25
|
+
def input
|
|
26
|
+
@options[:input]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
#
|
|
30
|
+
# Returns capture device in use
|
|
31
|
+
#
|
|
32
|
+
def capture_device
|
|
33
|
+
determine_capture_device
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
#
|
|
37
|
+
# Returns given output filepath
|
|
38
|
+
#
|
|
39
|
+
def output
|
|
40
|
+
@options[:output]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
#
|
|
44
|
+
# Returns given values that are optional
|
|
45
|
+
#
|
|
46
|
+
def advanced
|
|
47
|
+
@options[:advanced] ||= {}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
#
|
|
51
|
+
# Returns given framerate
|
|
52
|
+
#
|
|
53
|
+
def framerate
|
|
54
|
+
advanced[:framerate]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
#
|
|
58
|
+
# Returns given log filename
|
|
59
|
+
#
|
|
60
|
+
def log
|
|
61
|
+
advanced[:log]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
#
|
|
65
|
+
# Returns all given options
|
|
66
|
+
#
|
|
67
|
+
def all
|
|
68
|
+
@options
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
#
|
|
72
|
+
# Returns a String with all options parsed and
|
|
73
|
+
# ready for the ffmpeg process to use
|
|
74
|
+
#
|
|
75
|
+
def parsed
|
|
76
|
+
vals = "-f #{capture_device} "
|
|
77
|
+
vals << advanced_options unless advanced.empty?
|
|
78
|
+
vals << "-i #{input} "
|
|
79
|
+
vals << output
|
|
80
|
+
vals << ffmpeg_log_to(log) # If provided
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
#
|
|
86
|
+
# Verifies the required options are provided and returns
|
|
87
|
+
# the given options Hash. Raises ArgumentError if all required
|
|
88
|
+
# options are not present in the given Hash.
|
|
89
|
+
#
|
|
90
|
+
def verify_options(options)
|
|
91
|
+
missing_options = required_options.select { |req| options[req].nil? }
|
|
92
|
+
err = "Required options are missing: #{missing_options}"
|
|
93
|
+
raise(ArgumentError, err) unless missing_options.empty?
|
|
94
|
+
|
|
95
|
+
options
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
#
|
|
99
|
+
# Returns Array of required options as Symbols
|
|
100
|
+
#
|
|
101
|
+
def required_options
|
|
102
|
+
%i[input output]
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
#
|
|
106
|
+
# Returns advanced options parsed and ready for ffmpeg to receive.
|
|
107
|
+
#
|
|
108
|
+
def advanced_options
|
|
109
|
+
arr = []
|
|
110
|
+
|
|
111
|
+
# Log file is handled separately at the end of the command
|
|
112
|
+
advanced.select { |k, _| k != :log }
|
|
113
|
+
.each do |k, v|
|
|
114
|
+
arr.push "-#{k} #{v}"
|
|
115
|
+
end
|
|
116
|
+
arr.join(' ') + ' '
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
#
|
|
120
|
+
# Returns logging command with user given log file
|
|
121
|
+
# from options or the default file.
|
|
122
|
+
#
|
|
123
|
+
def ffmpeg_log_to(file)
|
|
124
|
+
file ||= DEFAULT_LOG_FILE
|
|
125
|
+
" 2> #{file}"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
#
|
|
129
|
+
# Returns input capture device based on user given value or the current OS.
|
|
130
|
+
#
|
|
131
|
+
def determine_capture_device
|
|
132
|
+
# User given capture device or format
|
|
133
|
+
# @see https://www.ffmpeg.org/ffmpeg.html#Main-options
|
|
134
|
+
return advanced[:f] if advanced[:f]
|
|
135
|
+
return advanced[:fmt] if advanced[:fmt]
|
|
136
|
+
|
|
137
|
+
if OS.windows?
|
|
138
|
+
'gdigrab'
|
|
139
|
+
elsif OS.linux?
|
|
140
|
+
'x11grab'
|
|
141
|
+
elsif OS.mac?
|
|
142
|
+
'avfoundation'
|
|
143
|
+
else
|
|
144
|
+
raise NotImplementedError, 'Your OS is not supported.'
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
module ScreenRecorder
|
|
2
|
+
# @since 1.0.0-beta4
|
|
3
|
+
module Titles
|
|
4
|
+
# Regex to filter out "Window Title: N/A" from Chrome extensions and "Window Title: ".
|
|
5
|
+
# This is done to remove unusable titles and to match the Ffmpeg expected input format
|
|
6
|
+
# for capturing specific windows.
|
|
7
|
+
# For example, "Window Title: Google - Mozilla Firefox" becomes "Google - Mozilla Firefox".
|
|
8
|
+
FILTERED_TITLES = %r{^Window Title:( N/A|\s+)?}
|
|
9
|
+
|
|
10
|
+
#
|
|
11
|
+
# Returns a list of available window titles for the given application (process) name.
|
|
12
|
+
#
|
|
13
|
+
def self.fetch(application)
|
|
14
|
+
ScreenRecorder.logger.debug "Retrieving available windows for: #{application}"
|
|
15
|
+
WindowGrabber.new.available_windows_for application
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @since 1.0.0-beta4
|
|
19
|
+
class WindowGrabber
|
|
20
|
+
#
|
|
21
|
+
# Returns a cleaned up list of available window titles
|
|
22
|
+
# for the given application (process) name.
|
|
23
|
+
#
|
|
24
|
+
def available_windows_for(application)
|
|
25
|
+
return windows_os_window(application) if OS.windows?
|
|
26
|
+
return linux_os_window(application) if OS.linux?
|
|
27
|
+
|
|
28
|
+
raise NotImplementedError, 'Your OS is not supported.'
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
#
|
|
34
|
+
# Returns list of window titles in FFmpeg expected format when using Microsoft Windows
|
|
35
|
+
#
|
|
36
|
+
def windows_os_window(application)
|
|
37
|
+
titles = `tasklist /v /fi "imagename eq #{application}.exe" /fo list | findstr Window`
|
|
38
|
+
.split("\n")
|
|
39
|
+
.map { |i| i.gsub(FILTERED_TITLES, '') }
|
|
40
|
+
.reject(&:empty?)
|
|
41
|
+
raise Errors::ApplicationNotFound, "No open windows found for: #{application}.exe" if titles.empty?
|
|
42
|
+
|
|
43
|
+
warn_on_mismatch(titles, application)
|
|
44
|
+
titles
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
#
|
|
48
|
+
# Returns list of window titles in FFmpeg expected format when using Linux
|
|
49
|
+
#
|
|
50
|
+
def linux_os_window(application)
|
|
51
|
+
ScreenRecorder.logger.warn 'Default capture device on Linux (x11grab) does not support window recording.'
|
|
52
|
+
raise DependencyNotFound, 'wmctrl is not installed. Run: sudo apt install wmctrl.' unless wmctrl_installed?
|
|
53
|
+
|
|
54
|
+
titles = `wmctrl -l | awk '{$3=""; $2=""; $1=""; print $0}'` # Returns all open windows
|
|
55
|
+
.split("\n")
|
|
56
|
+
.map(&:strip)
|
|
57
|
+
.select { |t| t.match?(/#{application}/i) } # Narrow down to given application
|
|
58
|
+
raise Errors::ApplicationNotFound, "No open windows found for: #{application}" if titles.empty?
|
|
59
|
+
|
|
60
|
+
titles
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
#
|
|
64
|
+
# Returns true if wmctrl is installed
|
|
65
|
+
#
|
|
66
|
+
def wmctrl_installed?
|
|
67
|
+
!`which wmctrl`.empty? # "" when not found
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
#
|
|
71
|
+
# Prints a warning if the retrieved list of window titles does no include
|
|
72
|
+
# the given application process name, which applications commonly do.
|
|
73
|
+
#
|
|
74
|
+
def warn_on_mismatch(titles, application)
|
|
75
|
+
unless titles.map(&:downcase).join(',').include? application.to_s
|
|
76
|
+
ScreenRecorder.logger.warn "Process name and window title(s) do not match: #{titles}"
|
|
77
|
+
ScreenRecorder.logger.warn "Please manually provide the displayed window title."
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end # class WindowGrabber
|
|
81
|
+
end # module Windows
|
|
82
|
+
end # module FFMPEG
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module ScreenRecorder
|
|
2
|
+
# @since 1.0.0.beta10
|
|
3
|
+
module TypeChecker
|
|
4
|
+
#
|
|
5
|
+
# Compares the given object's type (class) to the desired object type.
|
|
6
|
+
# Raises an ArgumentError if the object is not of desired type.
|
|
7
|
+
#
|
|
8
|
+
def self.check(obj, klass)
|
|
9
|
+
raise ArgumentError, "Expected #{klass}, given: #{obj.class}" unless obj.is_a? klass
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# @since 1.0.0-beta11
|
|
2
|
+
module ScreenRecorder
|
|
3
|
+
# @since 1.0.0-beta11
|
|
4
|
+
class Window < Common
|
|
5
|
+
#
|
|
6
|
+
# Window recording specific initializer.
|
|
7
|
+
#
|
|
8
|
+
def initialize(title:, output:, advanced: {})
|
|
9
|
+
raise NotImplementedError, 'Window recording is only supported on Microsoft Windows.' unless OS.windows?
|
|
10
|
+
|
|
11
|
+
super(input: format_input(title), output: output, advanced: advanced)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
#
|
|
17
|
+
# Sets input syntax specific to the FFmpeg window recorder.
|
|
18
|
+
#
|
|
19
|
+
def format_input(title)
|
|
20
|
+
%("title=#{title}")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
|
+
require 'screen-recorder/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'screen-recorder'
|
|
7
|
+
spec.version = ScreenRecorder::VERSION
|
|
8
|
+
spec.required_ruby_version = '>= 2.0.0'
|
|
9
|
+
spec.authors = ['Lakshya Kapoor']
|
|
10
|
+
spec.email = ['kapoorlakshya@gmail.com']
|
|
11
|
+
spec.homepage = 'http://github.com/kapoorlakshya/screen-recorder'
|
|
12
|
+
spec.summary = 'Record your computer screen using FFmpeg via Ruby.'
|
|
13
|
+
spec.description = 'Record your computer screen - desktop or specific window - using FFmpeg (https://www.ffmpeg.org).'
|
|
14
|
+
spec.license = 'MIT'
|
|
15
|
+
|
|
16
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
17
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
spec.require_paths = ['lib']
|
|
21
|
+
|
|
22
|
+
spec.add_development_dependency 'pry-byebug', '~> 3.6'
|
|
23
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
|
24
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
25
|
+
spec.add_development_dependency 'rubocop', '~> 0.59'
|
|
26
|
+
spec.add_development_dependency 'watir', '~> 6.0'
|
|
27
|
+
spec.add_development_dependency 'webdrivers', '~> 3.0'
|
|
28
|
+
|
|
29
|
+
spec.add_runtime_dependency 'os', '~> 0.9.0'
|
|
30
|
+
spec.add_runtime_dependency 'streamio-ffmpeg', '~> 3.0'
|
|
31
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: screen-recorder
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Lakshya Kapoor
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2019-03-15 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: pry-byebug
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '3.6'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '3.6'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '10.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '10.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rspec
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rubocop
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0.59'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0.59'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: watir
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '6.0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '6.0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: webdrivers
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '3.0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '3.0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: os
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - "~>"
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: 0.9.0
|
|
104
|
+
type: :runtime
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - "~>"
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: 0.9.0
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: streamio-ffmpeg
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - "~>"
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '3.0'
|
|
118
|
+
type: :runtime
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - "~>"
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '3.0'
|
|
125
|
+
description: Record your computer screen - desktop or specific window - using FFmpeg
|
|
126
|
+
(https://www.ffmpeg.org).
|
|
127
|
+
email:
|
|
128
|
+
- kapoorlakshya@gmail.com
|
|
129
|
+
executables: []
|
|
130
|
+
extensions: []
|
|
131
|
+
extra_rdoc_files: []
|
|
132
|
+
files:
|
|
133
|
+
- ".github/ISSUE_TEMPLATE.md"
|
|
134
|
+
- ".gitignore"
|
|
135
|
+
- ".rspec"
|
|
136
|
+
- ".rubocop.yml"
|
|
137
|
+
- ".travis.yml"
|
|
138
|
+
- CHANGES.md
|
|
139
|
+
- Gemfile
|
|
140
|
+
- LICENSE.txt
|
|
141
|
+
- README.md
|
|
142
|
+
- Rakefile
|
|
143
|
+
- bin/console
|
|
144
|
+
- bin/setup
|
|
145
|
+
- lib/screen-recorder.rb
|
|
146
|
+
- lib/screen-recorder/common.rb
|
|
147
|
+
- lib/screen-recorder/desktop.rb
|
|
148
|
+
- lib/screen-recorder/errors.rb
|
|
149
|
+
- lib/screen-recorder/options.rb
|
|
150
|
+
- lib/screen-recorder/titles.rb
|
|
151
|
+
- lib/screen-recorder/type_checker.rb
|
|
152
|
+
- lib/screen-recorder/version.rb
|
|
153
|
+
- lib/screen-recorder/window.rb
|
|
154
|
+
- screen-recorder.gemspec
|
|
155
|
+
homepage: http://github.com/kapoorlakshya/screen-recorder
|
|
156
|
+
licenses:
|
|
157
|
+
- MIT
|
|
158
|
+
metadata: {}
|
|
159
|
+
post_install_message:
|
|
160
|
+
rdoc_options: []
|
|
161
|
+
require_paths:
|
|
162
|
+
- lib
|
|
163
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
164
|
+
requirements:
|
|
165
|
+
- - ">="
|
|
166
|
+
- !ruby/object:Gem::Version
|
|
167
|
+
version: 2.0.0
|
|
168
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
169
|
+
requirements:
|
|
170
|
+
- - ">="
|
|
171
|
+
- !ruby/object:Gem::Version
|
|
172
|
+
version: '0'
|
|
173
|
+
requirements: []
|
|
174
|
+
rubygems_version: 3.0.2
|
|
175
|
+
signing_key:
|
|
176
|
+
specification_version: 4
|
|
177
|
+
summary: Record your computer screen using FFmpeg via Ruby.
|
|
178
|
+
test_files: []
|