electric_eye 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +66 -0
- data/LICENSE.txt +8 -0
- data/README.md +110 -0
- data/README.rdoc +23 -0
- data/Rakefile +66 -0
- data/bin/electric_eye +57 -0
- data/electric_eye.gemspec +27 -0
- data/features/config.feature +40 -0
- data/features/electric_eye.feature +23 -0
- data/features/record.feature +18 -0
- data/features/step_definitions/config_steps.rb +47 -0
- data/features/step_definitions/electric_eye_steps.rb +10 -0
- data/features/support/env.rb +22 -0
- data/lib/electric_eye/config_eye.rb +75 -0
- data/lib/electric_eye/record.rb +72 -0
- data/lib/electric_eye/settings.rb +5 -0
- data/lib/electric_eye/version.rb +3 -0
- data/lib/electric_eye.rb +9 -0
- data/spec/config_spec.rb +190 -0
- data/spec/electric_eye_spec.rb +77 -0
- data/spec/spec_helper.rb +12 -0
- metadata +162 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a47ea7aadb4898d0e3021215284e48906b11d0af
|
4
|
+
data.tar.gz: 585767b4e074d6443a586ef063123fbef896f19b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 669d9b4aed0148677a03aa7b3a99c346a73ba05571008936e49a9585c88f95281ab55e9f5122880253001948184cc5f000fb0267b031459bec7cbed356f5a3a6
|
7
|
+
data.tar.gz: 6e5e82f135be9c9199baa758be1fce94d56367208abeb54a1dd9117b618328d7d875824155837ff15b8a5d2f84205e0068ffa423069d5be71265c598e68a6e14
|
data/.gitignore
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in electric_eye.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
gem 'construct' # Config manager in YAML format
|
7
|
+
gem 'table_print' # Print a nice table in stdout
|
8
|
+
gem 'timecop' # Freeze time for tests
|
9
|
+
gem 'fakefs', require: "fakefs/safe"
|
10
|
+
gem 'open4' # Handle shell commands
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
electric_eye (0.0.1)
|
5
|
+
methadone (~> 1.9.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
aruba (0.6.2)
|
11
|
+
childprocess (>= 0.3.6)
|
12
|
+
cucumber (>= 1.1.1)
|
13
|
+
rspec-expectations (>= 2.7.0)
|
14
|
+
builder (3.2.2)
|
15
|
+
childprocess (0.5.6)
|
16
|
+
ffi (~> 1.0, >= 1.0.11)
|
17
|
+
construct (0.1.7)
|
18
|
+
cucumber (2.0.0)
|
19
|
+
builder (>= 2.1.2)
|
20
|
+
cucumber-core (~> 1.1.3)
|
21
|
+
diff-lcs (>= 1.1.3)
|
22
|
+
gherkin (~> 2.12)
|
23
|
+
multi_json (>= 1.7.5, < 2.0)
|
24
|
+
multi_test (>= 0.1.2)
|
25
|
+
cucumber-core (1.1.3)
|
26
|
+
gherkin (~> 2.12.0)
|
27
|
+
diff-lcs (1.2.5)
|
28
|
+
fakefs (0.6.7)
|
29
|
+
ffi (1.9.8)
|
30
|
+
gherkin (2.12.2)
|
31
|
+
multi_json (~> 1.3)
|
32
|
+
json (1.8.2)
|
33
|
+
methadone (1.9.0)
|
34
|
+
bundler
|
35
|
+
multi_json (1.11.0)
|
36
|
+
multi_test (0.1.2)
|
37
|
+
open4 (1.3.4)
|
38
|
+
rake (10.4.2)
|
39
|
+
rdoc (4.2.0)
|
40
|
+
json (~> 1.4)
|
41
|
+
rspec (2.99.0)
|
42
|
+
rspec-core (~> 2.99.0)
|
43
|
+
rspec-expectations (~> 2.99.0)
|
44
|
+
rspec-mocks (~> 2.99.0)
|
45
|
+
rspec-core (2.99.2)
|
46
|
+
rspec-expectations (2.99.2)
|
47
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
48
|
+
rspec-mocks (2.99.3)
|
49
|
+
table_print (1.5.3)
|
50
|
+
timecop (0.3.5)
|
51
|
+
|
52
|
+
PLATFORMS
|
53
|
+
ruby
|
54
|
+
|
55
|
+
DEPENDENCIES
|
56
|
+
aruba
|
57
|
+
bundler (~> 1.7)
|
58
|
+
construct
|
59
|
+
electric_eye!
|
60
|
+
fakefs
|
61
|
+
open4
|
62
|
+
rake (~> 10.0)
|
63
|
+
rdoc
|
64
|
+
rspec (~> 2.99)
|
65
|
+
table_print
|
66
|
+
timecop
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
Name: electric_eye
|
2
|
+
Copyright (c) 2015 Michael Pope
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
5
|
+
|
6
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
7
|
+
|
8
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
# ElectricEye
|
2
|
+
|
3
|
+
A network video recorder for multiple IP cameras using VLC.
|
4
|
+
|
5
|
+
## History
|
6
|
+
|
7
|
+
I've been using Zoneminder & motion and these programs are either too large for my requirements (zoneminder) or don't work with the cameras I own (motion). What I did notice is all my cameras work through VLC with high resolution and VLC can record.
|
8
|
+
|
9
|
+
The problem was though VLC doesn't automate the recordings or handle the file structure nicely. This is where I started to think about creating an application which records from VLC and nicely sorts those recordings in directories by date & time.
|
10
|
+
|
11
|
+
## Requirements
|
12
|
+
|
13
|
+
- VLC
|
14
|
+
- ruby
|
15
|
+
- Linux (Tested on Debian 7)
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
|
19
|
+
Add this line to your application's Gemfile:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
gem 'electric_eye'
|
23
|
+
```
|
24
|
+
|
25
|
+
And then execute:
|
26
|
+
|
27
|
+
$ bundle
|
28
|
+
|
29
|
+
Or install it yourself as:
|
30
|
+
|
31
|
+
$ gem install electric_eye
|
32
|
+
|
33
|
+
## Configuration
|
34
|
+
|
35
|
+
Enter your cameras into the JSON config file like so
|
36
|
+
|
37
|
+
```yaml
|
38
|
+
---
|
39
|
+
duration: 60
|
40
|
+
path: "/media/data/recordings/temp"
|
41
|
+
cameras:
|
42
|
+
- :name: Reception
|
43
|
+
:url: rtsp://<user>:<passwd>@<camera's ip>/live2.sdp
|
44
|
+
- :name: Kitchen
|
45
|
+
:url: rtsp://<user>:<passwd>@<camera's ip>/live2.sdp
|
46
|
+
- :name: Workstations
|
47
|
+
:url: rtsp://<user>:<passwd>@<camera's ip>/live2.sdp
|
48
|
+
```
|
49
|
+
|
50
|
+
You should be able to view the URL through vlc before using this program.
|
51
|
+
|
52
|
+
The recordings directory will end up with these directories
|
53
|
+
|
54
|
+
/media/data/recordings/reception/20150527
|
55
|
+
/media/data/recordings/kitchen/20150527
|
56
|
+
|
57
|
+
Notice the date at the end of these paths, there will be one for each day. The contents within will be recordings which are done by default every 10minutes, example;
|
58
|
+
|
59
|
+
20150527-1020-reception.mjpeg
|
60
|
+
20150527-1030-reception.mjpeg
|
61
|
+
20150527-1040-reception.mjpeg
|
62
|
+
|
63
|
+
The default is going to be 10 minute blocks, this can be overridden with the duration variable above in minutes.
|
64
|
+
|
65
|
+
## Usage
|
66
|
+
|
67
|
+
First make sure you add your cameras
|
68
|
+
|
69
|
+
electric_eye add Reception <url>
|
70
|
+
|
71
|
+
Now start the daemon to start the recording process
|
72
|
+
|
73
|
+
electric_eye start
|
74
|
+
|
75
|
+
Stop all recordings
|
76
|
+
|
77
|
+
electric_eye stop
|
78
|
+
|
79
|
+
Usage in development mode
|
80
|
+
|
81
|
+
bundle exec bin/electric_eye -h
|
82
|
+
|
83
|
+
|
84
|
+
## Cleanup
|
85
|
+
|
86
|
+
Cleaning up recordings. Put the following into your /etc/crontab per recording directory.
|
87
|
+
|
88
|
+
00 19 * * * root /usr/bin/find <directory to recordings> -type f -mtime +<days> -exec rm {} \;
|
89
|
+
|
90
|
+
Example for cleaning up reception after 60days at 7pm everynight.
|
91
|
+
|
92
|
+
00 19 * * * root /usr/bin/find /media/recordings/reception -type f -mtime +60 -exec rm {} \;
|
93
|
+
|
94
|
+
## Contributing
|
95
|
+
|
96
|
+
1. Fork it ( https://github.com/map7/electric_eye/fork )
|
97
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
98
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
99
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
100
|
+
5. Create a new Pull Request
|
101
|
+
|
102
|
+
## TODO
|
103
|
+
|
104
|
+
- Adjust directory layout
|
105
|
+
- Turn into a gem
|
106
|
+
- Add testing
|
107
|
+
- Add motion detection (using [rmotion](https://github.com/rikiji/rmotion))
|
108
|
+
- Do inline motion detection (using activevlc)
|
109
|
+
- Allow different recording programs like raspicam
|
110
|
+
|
data/README.rdoc
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
= electric_eye - DESCRIBE YOUR GEM
|
2
|
+
|
3
|
+
Author:: YOUR NAME (YOUR EMAIL)
|
4
|
+
Copyright:: Copyright (c) 2015 YOUR NAME
|
5
|
+
|
6
|
+
|
7
|
+
License:: mit, see LICENSE.txt
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
DESCRIBE YOUR GEM HERE
|
12
|
+
|
13
|
+
== Links
|
14
|
+
|
15
|
+
* {Source on Github}[LINK TO GITHUB]
|
16
|
+
* RDoc[LINK TO RDOC.INFO]
|
17
|
+
|
18
|
+
== Install
|
19
|
+
|
20
|
+
== Examples
|
21
|
+
|
22
|
+
== Contributing
|
23
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
def dump_load_path
|
2
|
+
puts $LOAD_PATH.join("\n")
|
3
|
+
found = nil
|
4
|
+
$LOAD_PATH.each do |path|
|
5
|
+
if File.exists?(File.join(path,"rspec"))
|
6
|
+
puts "Found rspec in #{path}"
|
7
|
+
if File.exists?(File.join(path,"rspec","core"))
|
8
|
+
puts "Found core"
|
9
|
+
if File.exists?(File.join(path,"rspec","core","rake_task"))
|
10
|
+
puts "Found rake_task"
|
11
|
+
found = path
|
12
|
+
else
|
13
|
+
puts "!! no rake_task"
|
14
|
+
end
|
15
|
+
else
|
16
|
+
puts "!!! no core"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
if found.nil?
|
21
|
+
puts "Didn't find rspec/core/rake_task anywhere"
|
22
|
+
else
|
23
|
+
puts "Found in #{path}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
require 'bundler'
|
27
|
+
require 'rake/clean'
|
28
|
+
|
29
|
+
begin
|
30
|
+
require 'rspec/core/rake_task'
|
31
|
+
rescue LoadError
|
32
|
+
dump_load_path
|
33
|
+
raise
|
34
|
+
end
|
35
|
+
|
36
|
+
require 'cucumber'
|
37
|
+
require 'cucumber/rake/task'
|
38
|
+
gem 'rdoc' # we need the installed RDoc gem, not the system one
|
39
|
+
require 'rdoc/task'
|
40
|
+
|
41
|
+
include Rake::DSL
|
42
|
+
|
43
|
+
Bundler::GemHelper.install_tasks
|
44
|
+
|
45
|
+
|
46
|
+
RSpec::Core::RakeTask.new do |t|
|
47
|
+
# Put spec opts in a file named .rspec in root
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
CUKE_RESULTS = 'results.html'
|
52
|
+
CLEAN << CUKE_RESULTS
|
53
|
+
Cucumber::Rake::Task.new(:features) do |t|
|
54
|
+
t.cucumber_opts = "features --format html -o #{CUKE_RESULTS} --format pretty --no-source -x"
|
55
|
+
t.fork = false
|
56
|
+
end
|
57
|
+
|
58
|
+
Rake::RDocTask.new do |rd|
|
59
|
+
|
60
|
+
rd.main = "README.rdoc"
|
61
|
+
|
62
|
+
rd.rdoc_files.include("README.rdoc","lib/**/*.rb","bin/**/*")
|
63
|
+
end
|
64
|
+
|
65
|
+
task :default => [:spec,:features]
|
66
|
+
|
data/bin/electric_eye
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'methadone'
|
5
|
+
require 'construct'
|
6
|
+
require 'electric_eye.rb'
|
7
|
+
include ElectricEye
|
8
|
+
|
9
|
+
class App
|
10
|
+
include Methadone::Main
|
11
|
+
include Methadone::CLILogging
|
12
|
+
|
13
|
+
main do |camera, url| # Add args you want: |like,so|
|
14
|
+
@configEye = ConfigEye.new # Initialize config
|
15
|
+
@record = Record.new(@configEye) # Initialize record and pass in our config
|
16
|
+
|
17
|
+
if options[:a] # Add camera
|
18
|
+
@configEye.add_camera(camera,url)
|
19
|
+
elsif options[:r] # Remove camera
|
20
|
+
@configEye.remove_camera(camera)
|
21
|
+
elsif options[:l] # List cameras
|
22
|
+
@configEye.list_cameras
|
23
|
+
elsif options[:d] # Set duration
|
24
|
+
@configEye.set_duration(options[:duration])
|
25
|
+
elsif options[:p] # Set path
|
26
|
+
@configEye.set_path(options[:path])
|
27
|
+
elsif options[:s] # Start recording
|
28
|
+
@record.start
|
29
|
+
elsif options[:k] # Stop recordings
|
30
|
+
@record.stop
|
31
|
+
end
|
32
|
+
|
33
|
+
exit 0
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
|
38
|
+
description "Network Video Recorder"
|
39
|
+
|
40
|
+
# Accept flags via:
|
41
|
+
on("-a", "--add", "Add a camera")
|
42
|
+
on("-r", "--remove", "Remove a camera")
|
43
|
+
on("-l", "--list", "List cameras")
|
44
|
+
on("-d", "--duration SECONDS", "Set recording duration in seconds")
|
45
|
+
on("-p", "--path DIR", "Set recordings path")
|
46
|
+
on("-s", "--start", "Start recordings")
|
47
|
+
on("-k", "--stop", "Stop recordings")
|
48
|
+
|
49
|
+
# Arguments
|
50
|
+
arg :camera, :optional
|
51
|
+
arg :url, :optional
|
52
|
+
|
53
|
+
version ElectricEye::VERSION
|
54
|
+
use_log_level_option :toggle_debug_on_signal => 'USR1'
|
55
|
+
|
56
|
+
go!
|
57
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'electric_eye/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "electric_eye"
|
8
|
+
spec.version = ElectricEye::VERSION
|
9
|
+
spec.authors = ["Michael Pope"]
|
10
|
+
spec.email = ["michael@dtcorp.com.au"]
|
11
|
+
spec.summary = %q{Network Video Recorder}
|
12
|
+
spec.description = %q{A network video recorder for multiple IP cameras which uses VLC to record and organises the files according to date and camera name.}
|
13
|
+
spec.homepage = "https://github.com/map7/electric_eye"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency('rdoc')
|
24
|
+
spec.add_development_dependency('aruba')
|
25
|
+
spec.add_dependency('methadone', '~> 1.9.0')
|
26
|
+
spec.add_development_dependency('rspec', '~> 2.99')
|
27
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
Feature: Config Electric Eye
|
2
|
+
In order to record cameras
|
3
|
+
I want to easily setup cameras and other variables
|
4
|
+
So we can just start and stop the recordings
|
5
|
+
|
6
|
+
Scenario: Add camera
|
7
|
+
When I successfully run `electric_eye -a Reception rtsp://user:passwd@192.168.0.100/live.sdp`
|
8
|
+
Then the exit status should be 0
|
9
|
+
And we should have a directory called "~/.electric_eye"
|
10
|
+
And we should have a file called "~/.electric_eye/config.yml"
|
11
|
+
And within the file "~/.electric_eye/config.yml" we should have the camera "Reception"
|
12
|
+
And the stdout should contain "Camera added"
|
13
|
+
|
14
|
+
Scenario: Remove camera
|
15
|
+
Given I have a camera called "Reception"
|
16
|
+
When I successfully run `electric_eye -r Reception`
|
17
|
+
Then the exit status should be 0
|
18
|
+
And within the file "~/.electric_eye/config.yml" we should no cameras
|
19
|
+
And the stdout should contain "Camera removed"
|
20
|
+
|
21
|
+
Scenario: List cameras
|
22
|
+
Given I have a camera called "Reception"
|
23
|
+
When I successfully run `electric_eye -l`
|
24
|
+
Then the exit status should be 0
|
25
|
+
And within the file "~/.electric_eye/config.yml" we should have the camera "Reception"
|
26
|
+
And the stdout should contain "Cameras"
|
27
|
+
And the stdout should contain "Reception"
|
28
|
+
|
29
|
+
Scenario: Set duration
|
30
|
+
When I successfully run `electric_eye -d 10`
|
31
|
+
Then the exit status should be 0
|
32
|
+
And within the file "~/.electric_eye/config.yml" we should have the duration "10"
|
33
|
+
And the stdout should contain "Duration set to 10 seconds"
|
34
|
+
|
35
|
+
Scenario: Set path
|
36
|
+
When I successfully run `electric_eye -p '/data/recordings'`
|
37
|
+
Then the exit status should be 0
|
38
|
+
And within the file "~/.electric_eye/config.yml" we should have the path "/data/recordings"
|
39
|
+
And the stdout should contain "Path set to /data/recordings"
|
40
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
Feature: Electric Eye
|
2
|
+
In order to record cameras
|
3
|
+
I want to have an easy to use interface
|
4
|
+
|
5
|
+
Scenario: Basic UI
|
6
|
+
When I get help for "electric_eye"
|
7
|
+
Then the exit status should be 0
|
8
|
+
And the banner should be present
|
9
|
+
And there should be a one line summary of what the app does
|
10
|
+
And the banner should include the version
|
11
|
+
And the banner should document that this app takes options
|
12
|
+
And the banner should document that this app's arguments are:
|
13
|
+
| camera | which is optional |
|
14
|
+
| url | which is optional |
|
15
|
+
And the following options should be documented:
|
16
|
+
| --add |
|
17
|
+
| --remove |
|
18
|
+
| --duration |
|
19
|
+
| --path |
|
20
|
+
| --list |
|
21
|
+
| --start |
|
22
|
+
| --stop |
|
23
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
Feature: Record
|
2
|
+
In order to record cameras
|
3
|
+
I need a way to start recordings per camera
|
4
|
+
|
5
|
+
Scenario: Record cameras
|
6
|
+
Given I have a camera called "Reception"
|
7
|
+
When I successfully run `electric_eye --start`
|
8
|
+
Then the exit status should be 0
|
9
|
+
And it should create a pid file in "/tmp/electric_eye.pid"
|
10
|
+
And the stdout should contain "Cameras recording"
|
11
|
+
|
12
|
+
Scenario: Stop recordings
|
13
|
+
Given I have a camera called "Reception"
|
14
|
+
And I successfully run `electric_eye --start`
|
15
|
+
When I successfully run `electric_eye --stop`
|
16
|
+
Then the exit status should be 0
|
17
|
+
And it should remove the pid file in "/tmp/electric_eye.pid"
|
18
|
+
And the stdout should contain "Stop recordings"
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'construct'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
Given(/^I have a camera called "([^"]*)"$/) do |arg1|
|
5
|
+
@config = Construct.new
|
6
|
+
@config.cameras = [{name: "Reception", url: "http://thecamera.org"}]
|
7
|
+
dir="#{ENV['HOME']}/.electric_eye"
|
8
|
+
FileUtils.rm_r(dir) if Dir.exist?(dir)
|
9
|
+
Dir.mkdir(dir)
|
10
|
+
File.open("#{dir}/config.yml","w"){ |f| f.write @config.to_yaml }
|
11
|
+
end
|
12
|
+
|
13
|
+
Then(/^we should have a directory called "([^"]*)"$/) do |dir|
|
14
|
+
config_dir = File.expand_path(dir) # Expand ~ to ENV["HOME"]
|
15
|
+
expect(Dir.exist?(config_dir)).to equal(true)
|
16
|
+
end
|
17
|
+
|
18
|
+
Then(/^we should have a file called "([^"]*)"$/) do |dir|
|
19
|
+
config_file = File.expand_path(dir) # Expand ~ to ENV["HOME"]
|
20
|
+
expect(File.exist?(config_file)).to equal(true)
|
21
|
+
end
|
22
|
+
|
23
|
+
Then(/^within the file "([^"]*)" we should have the camera "([^"]*)"$/) do |file, camera|
|
24
|
+
config_file = File.expand_path(file) # Expand ~ to ENV["HOME"]
|
25
|
+
@config = Construct.load File.read(config_file)
|
26
|
+
expect(@config.cameras.length).to equal(1)
|
27
|
+
expect(@config.cameras.first[:name] == camera).to equal(true)
|
28
|
+
end
|
29
|
+
|
30
|
+
Then(/^within the file "([^"]*)" we should have the duration "([^"]*)"$/) do |file, duration|
|
31
|
+
config_file = File.expand_path(file) # Expand ~ to ENV["HOME"]
|
32
|
+
@config = Construct.load File.read(config_file)
|
33
|
+
expect(@config.duration == duration.to_i).to equal(true)
|
34
|
+
end
|
35
|
+
|
36
|
+
Then(/^within the file "([^"]*)" we should have the path "([^"]*)"$/) do |file, path|
|
37
|
+
config_file = File.expand_path(file) # Expand ~ to ENV["HOME"]
|
38
|
+
@config = Construct.load File.read(config_file)
|
39
|
+
expect(@config.path == path).to equal(true)
|
40
|
+
end
|
41
|
+
|
42
|
+
Then(/^within the file "([^"]*)" we should no cameras$/) do |file|
|
43
|
+
config_file = File.expand_path(file) # Expand ~ to ENV["HOME"]
|
44
|
+
@config = Construct.load File.read(config_file)
|
45
|
+
expect(@config.cameras.length).to equal(0)
|
46
|
+
end
|
47
|
+
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'construct'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
Then(/^it should create a pid file in "([^"]*)"$/) do |file|
|
5
|
+
expect(File.exist?(file)).to equal(true)
|
6
|
+
end
|
7
|
+
|
8
|
+
Then(/^it should remove the pid file in "([^"]*)"$/) do |file|
|
9
|
+
expect(File.exist?(file)).to equal(false)
|
10
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'aruba/cucumber'
|
2
|
+
require 'methadone/cucumber'
|
3
|
+
|
4
|
+
ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
|
5
|
+
LIB_DIR = File.join(File.expand_path(File.dirname(__FILE__)),'..','..','lib')
|
6
|
+
|
7
|
+
Before do
|
8
|
+
# Using "announce" causes massive warnings on 1.9.2
|
9
|
+
@puts = true
|
10
|
+
@original_rubylib = ENV['RUBYLIB']
|
11
|
+
ENV['RUBYLIB'] = LIB_DIR + File::PATH_SEPARATOR + ENV['RUBYLIB'].to_s
|
12
|
+
|
13
|
+
@original_home = ENV['HOME']
|
14
|
+
ENV['HOME'] = "/tmp/fakehome"
|
15
|
+
FileUtils.rm_rf "/tmp/fakehome"
|
16
|
+
FileUtils.mkdir "/tmp/fakehome"
|
17
|
+
end
|
18
|
+
|
19
|
+
After do
|
20
|
+
ENV['RUBYLIB'] = @original_rubylib
|
21
|
+
ENV['HOME'] = @original_home
|
22
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'methadone'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'table_print'
|
4
|
+
|
5
|
+
module ElectricEye
|
6
|
+
class ConfigEye
|
7
|
+
|
8
|
+
include Methadone::CLILogging
|
9
|
+
|
10
|
+
# Check the directory and if it doesn't exist create it.
|
11
|
+
def self.check_dir
|
12
|
+
FileUtils.mkdir_p(CONFIG_DIR) unless Dir.exist?(CONFIG_DIR)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Check that the config file exists.
|
16
|
+
def self.load
|
17
|
+
# Check if we have a config CONFIG_FILE
|
18
|
+
ConfigEye.check_dir
|
19
|
+
if File.exist?(CONFIG_FILE)
|
20
|
+
Construct.load File.read(CONFIG_FILE)
|
21
|
+
else
|
22
|
+
Construct.new({duration: 600, path: '~/recordings', cameras: []})
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Save the config file
|
27
|
+
def save()
|
28
|
+
File.open(CONFIG_FILE, 'w'){ |f| f.write config.to_yaml } # Store
|
29
|
+
end
|
30
|
+
|
31
|
+
# Add camera
|
32
|
+
def add_camera(camera, url)
|
33
|
+
@config.cameras.push({name: camera, url: url})
|
34
|
+
save
|
35
|
+
info "Camera added"
|
36
|
+
end
|
37
|
+
|
38
|
+
# Remove camera
|
39
|
+
def remove_camera(camera)
|
40
|
+
record = @config.cameras.bsearch{ |c| c[:name] == camera }
|
41
|
+
if record
|
42
|
+
@config.cameras.delete(record)
|
43
|
+
save
|
44
|
+
end
|
45
|
+
info "Camera removed"
|
46
|
+
end
|
47
|
+
|
48
|
+
# List cameras in setup
|
49
|
+
def list_cameras
|
50
|
+
info "Cameras"
|
51
|
+
tp @config.cameras, :name, :url => {width: 120}
|
52
|
+
end
|
53
|
+
|
54
|
+
# Set duration
|
55
|
+
def set_duration(seconds)
|
56
|
+
@config.duration = seconds.to_i
|
57
|
+
save
|
58
|
+
info "Duration set to #{seconds} seconds"
|
59
|
+
end
|
60
|
+
|
61
|
+
# Set path
|
62
|
+
def set_path(dir)
|
63
|
+
@config.path = dir
|
64
|
+
save
|
65
|
+
info "Path set to #{dir}"
|
66
|
+
end
|
67
|
+
|
68
|
+
# Initialise the method.
|
69
|
+
attr_reader :config
|
70
|
+
def initialize
|
71
|
+
@config = ConfigEye.load
|
72
|
+
save
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'methadone'
|
2
|
+
require 'open4'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module ElectricEye
|
6
|
+
class Record
|
7
|
+
|
8
|
+
include Methadone::CLILogging
|
9
|
+
|
10
|
+
def store_pids(pids = [])
|
11
|
+
File.open(PID_FILE, "w") { |file| file.write pids.join(" ") }
|
12
|
+
end
|
13
|
+
|
14
|
+
def start
|
15
|
+
pids = []
|
16
|
+
# Step through each camera
|
17
|
+
@configEye.config.cameras.each do |camera|
|
18
|
+
# Let's first record two 1minute videos of each camera.
|
19
|
+
# This will turn into a never ending loop until we stop the process.
|
20
|
+
pids << fork do
|
21
|
+
stop_recording = false
|
22
|
+
Signal.trap('INT') { stop_recording = true }
|
23
|
+
until stop_recording
|
24
|
+
debug "Recording #{camera[:name]} to #{path(camera)}..."
|
25
|
+
|
26
|
+
# Set a recording going using vlc, hold onto the process till it's finished.
|
27
|
+
cmd="cvlc --qt-minimal-view --no-audio -R #{camera[:url]} --sout file/ts:#{path(camera)}"
|
28
|
+
pid,stdin,stdout,stderr=Open4::popen4(cmd)
|
29
|
+
|
30
|
+
# Wait for a defined duration from the config file.
|
31
|
+
seconds = @configEye.config.duration
|
32
|
+
while(seconds > 0)
|
33
|
+
sleep 1
|
34
|
+
seconds -= 1
|
35
|
+
break if stop_recording
|
36
|
+
end
|
37
|
+
|
38
|
+
Process.kill 9, pid # Stop current recording.
|
39
|
+
Process.wait pid # Wait around so we don't get Zombies
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
store_pids(pids)
|
45
|
+
info "Cameras recording"
|
46
|
+
end
|
47
|
+
|
48
|
+
def stop
|
49
|
+
stop_recordings(get_pids) if File.exist?(PID_FILE)
|
50
|
+
end
|
51
|
+
|
52
|
+
def stop_recordings(pids)
|
53
|
+
info "Stop recordings with PID: #{pids}..."
|
54
|
+
Open4::popen4("kill -INT #{pids}") # Kill all recordings
|
55
|
+
File.delete(PID_FILE) # Remove the pid file.
|
56
|
+
end
|
57
|
+
|
58
|
+
def get_pids
|
59
|
+
File.open(PID_FILE, "r").gets # Get pids
|
60
|
+
end
|
61
|
+
|
62
|
+
def path(camera)
|
63
|
+
dir = "#{@configEye.config.path}/#{camera[:name]}"
|
64
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
65
|
+
"#{dir}/#{Time.now.strftime('%Y%m%d-%H%M')}-#{camera[:name]}.mjpeg"
|
66
|
+
end
|
67
|
+
|
68
|
+
def initialize(configEye)
|
69
|
+
@configEye = configEye
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/electric_eye.rb
ADDED
data/spec/config_spec.rb
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
require ('spec_helper.rb')
|
2
|
+
include ElectricEye
|
3
|
+
|
4
|
+
describe "initialize" do
|
5
|
+
include FakeFS::SpecHelpers
|
6
|
+
|
7
|
+
it "sets config" do
|
8
|
+
configEye = ConfigEye.new
|
9
|
+
|
10
|
+
# Check defaults
|
11
|
+
expect(configEye.config.duration).to equal(600)
|
12
|
+
expect(configEye.config.path).to include("~/recordings")
|
13
|
+
expect(configEye.config.cameras.length).to equal(0)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "check_dir" do
|
18
|
+
include FakeFS::SpecHelpers
|
19
|
+
|
20
|
+
context "when config directory does not exists" do
|
21
|
+
it "makes a directory" do
|
22
|
+
ConfigEye.check_dir
|
23
|
+
expect(File.directory?(CONFIG_DIR)).to equal(true)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "when config directory exists" do
|
28
|
+
it "doesn't make a directory" do
|
29
|
+
FileUtils.mkdir_p(CONFIG_DIR) # Create the directory
|
30
|
+
ConfigEye.check_dir
|
31
|
+
expect(File.directory?(CONFIG_DIR)).to equal(true)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "save" do
|
37
|
+
include FakeFS::SpecHelpers
|
38
|
+
|
39
|
+
context "config is set" do
|
40
|
+
it "writes the config file" do
|
41
|
+
@configEye = ConfigEye.new
|
42
|
+
@configEye.save
|
43
|
+
expect(File.file?(CONFIG_FILE)).to equal(true)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "check config" do
|
49
|
+
include FakeFS::SpecHelpers
|
50
|
+
|
51
|
+
it "checks the directory" do
|
52
|
+
ConfigEye.load
|
53
|
+
expect(File.directory?(CONFIG_DIR)).to equal(true)
|
54
|
+
end
|
55
|
+
|
56
|
+
context "when exists" do
|
57
|
+
before do
|
58
|
+
# Create the file beforehand
|
59
|
+
@configEye = ConfigEye.new
|
60
|
+
@configEye.save
|
61
|
+
end
|
62
|
+
|
63
|
+
it "returns a construct object" do
|
64
|
+
expect(File.directory?(CONFIG_DIR)).to equal(true)
|
65
|
+
@config = ConfigEye.load
|
66
|
+
expect(@config.class).to equal(Construct)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "doesn't exist" do
|
71
|
+
before do
|
72
|
+
@configEye = ConfigEye.load
|
73
|
+
expect(File.file?(CONFIG_FILE)).to equal(false)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "returns a construct object" do
|
77
|
+
expect(@configEye.class).to equal(Construct)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "includes a cameras array" do
|
81
|
+
expect(@configEye.cameras.class).to equal(Array)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "add camera" do
|
87
|
+
include FakeFS::SpecHelpers
|
88
|
+
|
89
|
+
before do
|
90
|
+
@configEye = ConfigEye.new
|
91
|
+
end
|
92
|
+
|
93
|
+
it "adds camera to array" do
|
94
|
+
@configEye.add_camera("Reception", "http://user:pass@my.camera.org/live2.sdp")
|
95
|
+
expect(@configEye.config.cameras.length).to equal(1)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "calls save" do
|
99
|
+
expect(@configEye).to receive(:save).once
|
100
|
+
@configEye.add_camera("Reception", "http://user:pass@my.camera.org/live2.sdp")
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe "remove camera" do
|
105
|
+
include FakeFS::SpecHelpers
|
106
|
+
|
107
|
+
before do
|
108
|
+
@configEye = ConfigEye.new
|
109
|
+
@configEye.add_camera("Reception", "http://user:pass@my.camera.org/live2.sdp")
|
110
|
+
end
|
111
|
+
|
112
|
+
context "when camera exists" do
|
113
|
+
it "removes camera from array" do
|
114
|
+
expect(@configEye.config.cameras.length).to equal(1)
|
115
|
+
@configEye.remove_camera("Reception")
|
116
|
+
expect(@configEye.config.cameras.length).to equal(0)
|
117
|
+
end
|
118
|
+
|
119
|
+
it "calls save" do
|
120
|
+
expect(@configEye).to receive(:save).once
|
121
|
+
@configEye.remove_camera("Reception")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context "when specified camera doesn't exist" do
|
126
|
+
it "keeps the camera array the same size" do
|
127
|
+
expect(@configEye.config.cameras.length).to equal(1)
|
128
|
+
@configEye.remove_camera("Kitchen")
|
129
|
+
expect(@configEye.config.cameras.length).to equal(1)
|
130
|
+
end
|
131
|
+
|
132
|
+
it "calls save" do
|
133
|
+
expect(ConfigEye).to receive(:save).never
|
134
|
+
@configEye.remove_camera("Kitchen")
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe "set_duration" do
|
140
|
+
include FakeFS::SpecHelpers
|
141
|
+
|
142
|
+
before do
|
143
|
+
@configEye = ConfigEye.new
|
144
|
+
end
|
145
|
+
|
146
|
+
context "when no duration has been set" do
|
147
|
+
it "returns the default of 600 seconds" do
|
148
|
+
expect(@configEye.config.duration).to equal(600)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context "when calling with -d 10" do
|
153
|
+
it "returns 10" do
|
154
|
+
@configEye.set_duration(10)
|
155
|
+
expect(@configEye.config.duration).to equal(10)
|
156
|
+
end
|
157
|
+
|
158
|
+
it "calls save" do
|
159
|
+
expect(@configEye).to receive(:save).once
|
160
|
+
@configEye.set_duration(10)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
describe "set_path" do
|
166
|
+
include FakeFS::SpecHelpers
|
167
|
+
|
168
|
+
before do
|
169
|
+
@configEye = ConfigEye.new
|
170
|
+
end
|
171
|
+
|
172
|
+
context "when no path has been set" do
|
173
|
+
it "returns the default of ~/recordings" do
|
174
|
+
expect(@configEye.config.path == "~/recordings").to equal(true)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
context "when calling with -p '/data/recordings'" do
|
179
|
+
it "returns '/data/recordings'" do
|
180
|
+
@configEye.set_path('/data/recordings')
|
181
|
+
expect(@configEye.config.path == '/data/recordings').to equal(true)
|
182
|
+
end
|
183
|
+
|
184
|
+
it "calls save" do
|
185
|
+
expect(@configEye).to receive(:save).once
|
186
|
+
@configEye.set_path('/data/recordings')
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require ('spec_helper.rb')
|
2
|
+
|
3
|
+
describe "record" do
|
4
|
+
include FakeFS::SpecHelpers
|
5
|
+
|
6
|
+
before do
|
7
|
+
@configEye = ConfigEye.new
|
8
|
+
@configEye.add_camera("Reception", "http://user:pass@my.camera.org/live2.sdp")
|
9
|
+
@record = Record.new(@configEye)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "store_pids" do
|
13
|
+
before do
|
14
|
+
@file = "/tmp/electric_eye.pid"
|
15
|
+
ConfigEye.stub(:load).and_return(Construct.new({path: "~/recordings", cameras: [{name: "Reception"}]}))
|
16
|
+
end
|
17
|
+
|
18
|
+
it "creates a file #{@file}" do
|
19
|
+
@record.store_pids
|
20
|
+
expect(File.exist?(@file)).to equal(true)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should store the pids in the file" do
|
24
|
+
pids = [100, 101, 102]
|
25
|
+
@record.store_pids(pids)
|
26
|
+
expect(File.read(@file) == "100 101 102").to equal(true)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "record_path" do
|
31
|
+
before do
|
32
|
+
Timecop.freeze(Time.local(2015,06,30,10,05,0))
|
33
|
+
end
|
34
|
+
|
35
|
+
after do
|
36
|
+
Timecop.return
|
37
|
+
end
|
38
|
+
|
39
|
+
it "returns a full path with todays date" do
|
40
|
+
path = @record.path(@configEye.config.cameras.first)
|
41
|
+
expect(path).to include("~/recordings/Reception/20150630-1005-Reception.mjpeg")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "get_pids" do
|
46
|
+
it "returns pids" do
|
47
|
+
File.open("/tmp/electric_eye.pid", "w") do |file|
|
48
|
+
file.write("1 2 3")
|
49
|
+
end
|
50
|
+
|
51
|
+
expect(@record.get_pids == "1 2 3").to equal(true)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "stop_recordings" do
|
56
|
+
before do
|
57
|
+
File.open("/tmp/electric_eye.pid", "w") do |file|
|
58
|
+
file.write("10000 10001 10002")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
it "calls kill" do
|
63
|
+
open4 = mock(Open4)
|
64
|
+
open4.stub!(:exitstatus).and_return(0)
|
65
|
+
|
66
|
+
Open4.should_receive(:popen4).with('kill -INT 10000 10001 10002').and_return(open4)
|
67
|
+
|
68
|
+
@record.stop_recordings("10000 10001 10002")
|
69
|
+
end
|
70
|
+
|
71
|
+
it "removes the old file" do
|
72
|
+
expect(File.exists?("/tmp/electric_eye.pid")).to equal(true)
|
73
|
+
@record.stop_recordings("10000 10001 10002")
|
74
|
+
expect(File.exists?("/tmp/electric_eye.pid")).to equal(false)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'construct'
|
2
|
+
require 'timecop'
|
3
|
+
require 'fakefs/spec_helpers'
|
4
|
+
require ('electric_eye.rb')
|
5
|
+
|
6
|
+
RSpec.configure do |config|
|
7
|
+
config.before(:each) do
|
8
|
+
# Set the directory
|
9
|
+
stub_const("ElectricEye::CONFIG_DIR", "/tmp/fakehome/.electric_eye")
|
10
|
+
stub_const("ElectricEye::CONFIG_FILE", "/tmp/fakehome/.electric_eye/config.yml")
|
11
|
+
end
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: electric_eye
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Pope
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-06-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
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: rdoc
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: aruba
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: methadone
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.9.0
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.9.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2.99'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2.99'
|
97
|
+
description: A network video recorder for multiple IP cameras which uses VLC to record
|
98
|
+
and organises the files according to date and camera name.
|
99
|
+
email:
|
100
|
+
- michael@dtcorp.com.au
|
101
|
+
executables:
|
102
|
+
- electric_eye
|
103
|
+
extensions: []
|
104
|
+
extra_rdoc_files: []
|
105
|
+
files:
|
106
|
+
- ".gitignore"
|
107
|
+
- Gemfile
|
108
|
+
- Gemfile.lock
|
109
|
+
- LICENSE.txt
|
110
|
+
- README.md
|
111
|
+
- README.rdoc
|
112
|
+
- Rakefile
|
113
|
+
- bin/electric_eye
|
114
|
+
- electric_eye.gemspec
|
115
|
+
- features/config.feature
|
116
|
+
- features/electric_eye.feature
|
117
|
+
- features/record.feature
|
118
|
+
- features/step_definitions/config_steps.rb
|
119
|
+
- features/step_definitions/electric_eye_steps.rb
|
120
|
+
- features/support/env.rb
|
121
|
+
- lib/electric_eye.rb
|
122
|
+
- lib/electric_eye/config_eye.rb
|
123
|
+
- lib/electric_eye/record.rb
|
124
|
+
- lib/electric_eye/settings.rb
|
125
|
+
- lib/electric_eye/version.rb
|
126
|
+
- spec/config_spec.rb
|
127
|
+
- spec/electric_eye_spec.rb
|
128
|
+
- spec/spec_helper.rb
|
129
|
+
homepage: https://github.com/map7/electric_eye
|
130
|
+
licenses:
|
131
|
+
- MIT
|
132
|
+
metadata: {}
|
133
|
+
post_install_message:
|
134
|
+
rdoc_options: []
|
135
|
+
require_paths:
|
136
|
+
- lib
|
137
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - ">="
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
requirements: []
|
148
|
+
rubyforge_project:
|
149
|
+
rubygems_version: 2.2.2
|
150
|
+
signing_key:
|
151
|
+
specification_version: 4
|
152
|
+
summary: Network Video Recorder
|
153
|
+
test_files:
|
154
|
+
- features/config.feature
|
155
|
+
- features/electric_eye.feature
|
156
|
+
- features/record.feature
|
157
|
+
- features/step_definitions/config_steps.rb
|
158
|
+
- features/step_definitions/electric_eye_steps.rb
|
159
|
+
- features/support/env.rb
|
160
|
+
- spec/config_spec.rb
|
161
|
+
- spec/electric_eye_spec.rb
|
162
|
+
- spec/spec_helper.rb
|