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 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
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
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
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
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
+ [![Gem Version](https://badge.fury.io/rb/screen-recorder.svg)](https://badge.fury.io/rb/screen-recorder)
4
+ ![https://rubygems.org/gems/screen-recorder](https://ruby-gem-downloads-badge.herokuapp.com/screen-recorder?type=total)
5
+ [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](https://www.rubydoc.info/github/kapoorlakshya/screen-recorder/master)
6
+ [![Build Status](https://travis-ci.org/kapoorlakshya/screen-recorder.svg?branch=master)](https://travis-ci.org/kapoorlakshya/screen-recorder)
7
+ [![Maintainability](https://api.codeclimate.com/v1/badges/b6049dfee7375aed9bc8/maintainability)](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
+ [![Streamio](http://d253c4ja9jigvu.cloudfront.net/assets/small-logo.png)](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
+ ![SauceLabs Logo](https://saucelabs.com/content/images/logo.png)
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
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
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,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
@@ -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,10 @@
1
+ module ScreenRecorder
2
+ # @since 1.0.0-beta5
3
+ module Errors
4
+ # @since 1.0.0-beta3
5
+ class ApplicationNotFound < StandardError; end
6
+
7
+ # @since 1.0.0-beta5
8
+ class DependencyNotFound < StandardError; end
9
+ end
10
+ 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,3 @@
1
+ module ScreenRecorder
2
+ VERSION = '1.0.0'.freeze
3
+ 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: []