screen-recorder 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []