electric_eye 0.0.5 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -0
- data/Gemfile.lock +10 -3
- data/README.org +31 -15
- data/bin/electric_eye +8 -0
- data/electric_eye.gemspec +1 -0
- data/features/step_definitions/config_steps.rb +1 -1
- data/lib/electric_eye/config_eye.rb +9 -1
- data/lib/electric_eye/record.rb +61 -39
- data/lib/electric_eye/version.rb +1 -1
- data/spec/config_spec.rb +26 -0
- data/spec/record_spec.rb +53 -4
- metadata +18 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 33c71e7271287c8eefaed7c21b11e5dbfa8f2e8e
|
4
|
+
data.tar.gz: d95da73de1f27d44012389bc7d6b18516ef0f15a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: da2bfd5d2e5366b3e8a3929613207aa2ae489577baea1d99abc85a2626e6843b2b4ee4d8e85f69ba53a36e2147c1649f1ccde2054fed9ecad4c851d80c86dc86
|
7
|
+
data.tar.gz: 7c9e5f484dfc4e6dd4d179fa2982a2c2dced25645bc2e51a418ffebcd08415bb0c94aff473e4ca92702550fabd47d62f9b6cb1f71120ad795c7c161efcf1aba3
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.2.4
|
data/Gemfile.lock
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
electric_eye (0.0
|
4
|
+
electric_eye (0.1.0)
|
5
5
|
construct
|
6
|
+
filewatcher
|
6
7
|
methadone (~> 1.9.0)
|
7
8
|
open4
|
8
9
|
table_print
|
@@ -30,12 +31,14 @@ GEM
|
|
30
31
|
diff-lcs (1.2.5)
|
31
32
|
fakefs (0.6.7)
|
32
33
|
ffi (1.9.8)
|
34
|
+
filewatcher (0.5.3)
|
35
|
+
trollop (~> 2.0)
|
33
36
|
gem-man (0.3.0)
|
34
37
|
gherkin (2.12.2)
|
35
38
|
multi_json (~> 1.3)
|
36
39
|
hpricot (0.8.6)
|
37
40
|
json (1.8.2)
|
38
|
-
methadone (1.9.
|
41
|
+
methadone (1.9.2)
|
39
42
|
bundler
|
40
43
|
multi_json (1.11.0)
|
41
44
|
multi_test (0.1.2)
|
@@ -57,8 +60,9 @@ GEM
|
|
57
60
|
rspec-expectations (2.99.2)
|
58
61
|
diff-lcs (>= 1.1.3, < 2.0)
|
59
62
|
rspec-mocks (2.99.3)
|
60
|
-
table_print (1.5.
|
63
|
+
table_print (1.5.6)
|
61
64
|
timecop (0.3.5)
|
65
|
+
trollop (2.1.2)
|
62
66
|
|
63
67
|
PLATFORMS
|
64
68
|
ruby
|
@@ -74,3 +78,6 @@ DEPENDENCIES
|
|
74
78
|
ronn
|
75
79
|
rspec (~> 2.99)
|
76
80
|
timecop
|
81
|
+
|
82
|
+
BUNDLED WITH
|
83
|
+
1.11.2
|
data/README.org
CHANGED
@@ -6,19 +6,22 @@ A network video recorder for multiple IP cameras using VLC.
|
|
6
6
|
|
7
7
|
** History
|
8
8
|
|
9
|
-
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.
|
9
|
+
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.
|
10
10
|
|
11
|
-
|
11
|
+
I started with VLC doing the recording up to 0.1.0 where I changed over to using ffmpeg instead.
|
12
12
|
|
13
13
|
** Requirements
|
14
14
|
|
15
|
-
-
|
16
|
-
- xvfb - Running virtual frame buffers (ie: desktops)
|
15
|
+
- ffmpeg - recording & motion detection
|
17
16
|
- ruby
|
18
|
-
- Linux (Tested on Debian 7)
|
17
|
+
- Linux (Tested on Debian 7, Xubuntu 14.04)
|
19
18
|
|
20
19
|
** Installation
|
21
20
|
|
21
|
+
Under linux install vlc xvfb & ruby
|
22
|
+
|
23
|
+
: sudo apt-get install ffmpeg ruby
|
24
|
+
|
22
25
|
Add this line to your application's Gemfile:
|
23
26
|
|
24
27
|
: gem 'electric_eye'
|
@@ -37,7 +40,8 @@ Enter your cameras into the JSON config file like so
|
|
37
40
|
|
38
41
|
: ---
|
39
42
|
: duration: 60
|
40
|
-
: path: "/media/data/recordings
|
43
|
+
: path: "/media/data/recordings"
|
44
|
+
: threshold: 2
|
41
45
|
: cameras:
|
42
46
|
: - :name: Reception
|
43
47
|
: :url: rtsp://<user>:<passwd>@<camera's ip>/live2.sdp
|
@@ -50,14 +54,15 @@ You should be able to view the URL through vlc before using this program.
|
|
50
54
|
|
51
55
|
The recordings directory will end up with these directories
|
52
56
|
|
53
|
-
: /media/data/recordings/reception
|
54
|
-
: /media/data/recordings/kitchen
|
57
|
+
: /media/data/recordings/reception
|
58
|
+
: /media/data/recordings/kitchen
|
55
59
|
|
56
|
-
|
60
|
+
Files will be numbered up to your wrap figure. The wrap figure determines when the recording program should start from zero again. EG: If you select you duration as 3600 seconds and a wrap figure of 168 then you get a rolling recording over a 1 week period which would be divided up into 1hr files.
|
57
61
|
|
58
|
-
:
|
59
|
-
:
|
60
|
-
:
|
62
|
+
: reception000.mjpeg
|
63
|
+
: motion-reception000.mjpeg
|
64
|
+
: reception001.mjpeg
|
65
|
+
: motion-reception001.mjpeg
|
61
66
|
|
62
67
|
The default is going to be 10 minute blocks, this can be overridden with the duration variable above in minutes.
|
63
68
|
|
@@ -83,6 +88,9 @@ Usage in development mode
|
|
83
88
|
|
84
89
|
: bundle exec bin/electric_eye -h
|
85
90
|
|
91
|
+
Debug mode
|
92
|
+
|
93
|
+
: bundle exec bin/electric_eye -s --log-level=debug
|
86
94
|
|
87
95
|
** Start on boot
|
88
96
|
|
@@ -133,6 +141,8 @@ Replace johnsmith with your user where you have setup your camera profiles. NOTE
|
|
133
141
|
|
134
142
|
** Cleanup
|
135
143
|
|
144
|
+
Optional - This was needed for versions prior to 0.1.0, now it is only a precaution as ffmpeg does clean up after itself.
|
145
|
+
|
136
146
|
Cleaning up recordings. Put the following into your /etc/crontab per recording directory.
|
137
147
|
|
138
148
|
: 00 19 * * * root /usr/bin/find <directory to recordings> -type f -mtime +<days> -exec rm {} \;
|
@@ -162,11 +172,17 @@ Example for cleaning up reception after 60days at 7pm everynight.
|
|
162
172
|
|
163
173
|
- [X] Create threshold as a variable
|
164
174
|
|
165
|
-
- [
|
175
|
+
- [X] Swap over to using ffmpeg
|
176
|
+
|
177
|
+
- [X] Do post motion detection (using fmmpeg)
|
178
|
+
|
179
|
+
- [X] Add a feature to clean up old recordings using a "period" setting (ffmpeg handles this)
|
166
180
|
EG: 60 day period which could be set in the config file how many days you want to keep
|
167
181
|
Then just call 'electric_eye --remove-recordings' within crontab
|
168
182
|
This would iterate over all my cameras and remove old recordings to keep a rolling set of days.
|
169
183
|
|
170
|
-
- [ ] Allow
|
184
|
+
- [ ] Allow motion detection to be turned on/off (default: off)
|
171
185
|
|
172
|
-
- [ ]
|
186
|
+
- [ ] Threshold should be per camera or have inside & outside thresholds
|
187
|
+
There is a large difference in movement between indoor office cameras
|
188
|
+
and outdoor cameras. With wind and rain comes a lot of motion!
|
data/bin/electric_eye
CHANGED
@@ -22,12 +22,18 @@ class App
|
|
22
22
|
@configEye.list_cameras
|
23
23
|
elsif options[:d] # Set duration
|
24
24
|
@configEye.set_duration(options[:duration])
|
25
|
+
elsif options[:w] # Set wrap
|
26
|
+
@configEye.set_wrap(options[:wrap])
|
25
27
|
elsif options[:p] # Set path
|
26
28
|
@configEye.set_path(options[:path])
|
27
29
|
elsif options[:s] # Start recording
|
28
30
|
@record.start
|
29
31
|
elsif options[:k] # Stop recordings
|
30
32
|
@record.stop
|
33
|
+
# elsif options[:m] # Perform post motion detection
|
34
|
+
# # NOTE: This happens automatically during recording so this option is only here for admins
|
35
|
+
# # to perform the operation manually.
|
36
|
+
# @record.start_motion_detection(options[:listfile])
|
31
37
|
elsif options[:t]
|
32
38
|
@configEye.set_threshold(options[:threshold])
|
33
39
|
else
|
@@ -46,9 +52,11 @@ class App
|
|
46
52
|
on("-r", "--remove", "Remove a camera")
|
47
53
|
on("-l", "--list", "List cameras")
|
48
54
|
on("-d", "--duration SECONDS", "Set recording duration in seconds (default: 600)")
|
55
|
+
on("-w", "--wrap FILES", "Set how many files to keep before wrapping (default: 168 at 1hr = 1week)")
|
49
56
|
on("-p", "--path DIR", "Set recordings path")
|
50
57
|
on("-s", "--start", "Start recordings")
|
51
58
|
on("-k", "--stop", "Stop recordings")
|
59
|
+
# on("-m", "--motion LISTFILE", "Post motion detection, pass in a list of video and it will process")
|
52
60
|
on("-t", "--threshold LEVEL", "Set threshold for motion detection (default: 2)")
|
53
61
|
|
54
62
|
# Arguments
|
data/electric_eye.gemspec
CHANGED
@@ -21,6 +21,7 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.add_runtime_dependency "construct"
|
22
22
|
spec.add_runtime_dependency "table_print"
|
23
23
|
spec.add_runtime_dependency "open4"
|
24
|
+
spec.add_runtime_dependency "filewatcher"
|
24
25
|
|
25
26
|
spec.add_development_dependency "bundler", "~> 1.7"
|
26
27
|
spec.add_development_dependency "rake", "~> 10.0"
|
@@ -2,7 +2,7 @@ require 'construct'
|
|
2
2
|
require 'fileutils'
|
3
3
|
|
4
4
|
Given(/^I have a camera called "([^"]*)"$/) do |arg1|
|
5
|
-
@config = Construct.new
|
5
|
+
@config = Construct.new({path: "/tmp/temp", duration: 600, wrap: 168})
|
6
6
|
@config.cameras = [{name: "Reception", url: "http://thecamera.org"}]
|
7
7
|
dir="#{ENV['HOME']}/.electric_eye"
|
8
8
|
FileUtils.rm_r(dir) if Dir.exist?(dir)
|
@@ -19,7 +19,8 @@ module ElectricEye
|
|
19
19
|
if File.exist?(CONFIG_FILE)
|
20
20
|
Construct.load File.read(CONFIG_FILE)
|
21
21
|
else
|
22
|
-
|
22
|
+
# Create a new file with defaults
|
23
|
+
Construct.new({duration: 600, wrap: 168, path: '~/recordings', threshold: 2, cameras: []})
|
23
24
|
end
|
24
25
|
end
|
25
26
|
|
@@ -57,6 +58,13 @@ module ElectricEye
|
|
57
58
|
tp @config.cameras, :name, :url => {width: 120}
|
58
59
|
end
|
59
60
|
|
61
|
+
# Set wrap
|
62
|
+
def set_wrap(wrap)
|
63
|
+
@config.wrap = wrap.to_i
|
64
|
+
save
|
65
|
+
info "Wrap set to #{wrap} files"
|
66
|
+
end
|
67
|
+
|
60
68
|
# Set duration
|
61
69
|
def set_duration(seconds)
|
62
70
|
@config.duration = seconds.to_i
|
data/lib/electric_eye/record.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'methadone'
|
2
2
|
require 'open4'
|
3
3
|
require 'fileutils'
|
4
|
+
require 'filewatcher'
|
4
5
|
|
5
6
|
module ElectricEye
|
6
7
|
class Record
|
@@ -15,52 +16,69 @@ module ElectricEye
|
|
15
16
|
@motion = Motion.new # Create a new instance method to our motion library
|
16
17
|
|
17
18
|
pids = []
|
19
|
+
# Start the recording for each camera
|
18
20
|
# Step through each camera
|
19
21
|
@configEye.config.cameras.each do |camera|
|
20
|
-
|
21
|
-
#
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
until stop_recording
|
26
|
-
path = "#{path(camera)}"
|
27
|
-
debug "Recording #{camera[:name]} to #{path}.mjpeg..."
|
28
|
-
|
29
|
-
# Set a recording going using vlc, hold onto the process till it's finished.
|
30
|
-
cmd="cvlc #{camera[:url]} --sout file/ts:#{path}.mjpeg"
|
31
|
-
pid,stdin,stdout,stderr=Open4::popen4(cmd)
|
32
|
-
|
33
|
-
# Wait for a defined duration from the config file.
|
34
|
-
seconds = @configEye.config.duration
|
35
|
-
while(seconds > 0)
|
36
|
-
sleep 1
|
37
|
-
seconds -= 1
|
38
|
-
break if stop_recording
|
39
|
-
end
|
40
|
-
|
41
|
-
Process.kill 9, pid # Stop current recording.
|
42
|
-
Process.wait pid # Wait around so we don't get Zombies
|
43
|
-
|
44
|
-
# Look for any motion
|
45
|
-
Thread.new(path) do |threadPath|
|
46
|
-
@motion.create_log(threadPath) # Create the motion detection log file.
|
47
|
-
|
48
|
-
# Remove the log & recording if there is no motion
|
49
|
-
if @motion.detect("#{threadPath}.log", @configEye.config.threshold)
|
50
|
-
debug "KEEP #{threadPath}.mjpeg (motion)"
|
51
|
-
else
|
52
|
-
remove(threadPath)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
22
|
+
|
23
|
+
# until stop_recording
|
24
|
+
path = "#{path(camera)}"
|
25
|
+
listfile = "#{path}.list"
|
26
|
+
debug "Recording #{camera[:name]} to #{path}.mjpeg..."
|
57
27
|
|
28
|
+
# Set a recording going using vlc, hold onto the process till it's finished.
|
29
|
+
# segment_time = how much time to record in each segment in seconds, ie: 3600 = 1hr
|
30
|
+
# sgement_wrap = how many copies
|
31
|
+
loglevel = "-loglevel panic" if logger.level >= 1
|
32
|
+
cmd="ffmpeg -f mjpeg -i #{camera[:url]} #{loglevel} -acodec copy -vcodec copy -y -f segment -segment_list #{listfile} -segment_time #{@configEye.config.duration} -segment_wrap #{@configEye.config.wrap} #{path}%03d.mjpeg"
|
33
|
+
|
34
|
+
# Run command and add to our pids to make it easy for electric_eye to clean up.
|
35
|
+
info "Starting to record #{camera[:name]}"
|
36
|
+
pids << Process.spawn(cmd)
|
37
|
+
|
38
|
+
# Start the motion detection for this camera
|
39
|
+
puts "before thread: #{path}"
|
40
|
+
|
41
|
+
pids << fork do
|
42
|
+
`echo "path: #{dir(camera)}" >> #{listfile}.log`
|
43
|
+
start_motion_detection(camera)
|
44
|
+
end
|
58
45
|
end
|
59
46
|
|
60
47
|
store_pids(pids)
|
61
48
|
info "Cameras recording"
|
62
49
|
end
|
63
50
|
|
51
|
+
# Start motion detection
|
52
|
+
def start_motion_detection(camera)
|
53
|
+
# Watch the ffmpeg segment list output file which will trigger the block within
|
54
|
+
# where we can look at the last line in the file and perform post motion detection.
|
55
|
+
|
56
|
+
dir =dir(camera)
|
57
|
+
path = path(camera)
|
58
|
+
|
59
|
+
# Watch the directory & read from the list file
|
60
|
+
filewatcher = FileWatcher.new("#{path}*.mjpeg")
|
61
|
+
filewatcher.watch do |f|
|
62
|
+
file = read_listfile("#{path}.list")
|
63
|
+
if file
|
64
|
+
debug "Processing #{file}"
|
65
|
+
loglevel = "-loglevel panic" if logger.level >= 1
|
66
|
+
|
67
|
+
# Run motion detection on the file, make sure that we output to a different file.
|
68
|
+
cmd="ffmpeg -i #{dir}/#{file} #{loglevel} -y -vf \"select=gt(scene\\,0.003),setpts=N/(25*TB)\" #{dir}/motion-#{file}"
|
69
|
+
|
70
|
+
# Run command and add to our pids to make it easy for electric_eye to clean up.
|
71
|
+
Process.spawn(cmd)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Read the last line from the list file.
|
77
|
+
def read_listfile(listfile)
|
78
|
+
lines = File.open(listfile).readlines
|
79
|
+
lines.last.chomp! unless lines.length == 0
|
80
|
+
end
|
81
|
+
|
64
82
|
# Remove a recording
|
65
83
|
def remove(path)
|
66
84
|
debug "REMOVE #{path}.mjpeg (no motion)"
|
@@ -82,10 +100,14 @@ module ElectricEye
|
|
82
100
|
File.open(PID_FILE, "r").gets # Get pids
|
83
101
|
end
|
84
102
|
|
85
|
-
def
|
103
|
+
def dir(camera)
|
86
104
|
dir = "#{@configEye.config.path}/#{camera[:name]}"
|
87
105
|
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
88
|
-
|
106
|
+
dir
|
107
|
+
end
|
108
|
+
|
109
|
+
def path(camera)
|
110
|
+
"#{dir(camera)}/#{camera[:name]}"
|
89
111
|
end
|
90
112
|
|
91
113
|
def initialize(configEye)
|
data/lib/electric_eye/version.rb
CHANGED
data/spec/config_spec.rb
CHANGED
@@ -148,6 +148,32 @@ describe "remove camera" do
|
|
148
148
|
end
|
149
149
|
end
|
150
150
|
|
151
|
+
describe "set_wrap" do
|
152
|
+
include FakeFS::SpecHelpers
|
153
|
+
|
154
|
+
before do
|
155
|
+
@configEye = ConfigEye.new
|
156
|
+
end
|
157
|
+
|
158
|
+
context "when no wrap has been set" do
|
159
|
+
it "returns the default of 168 times" do
|
160
|
+
expect(@configEye.config.wrap).to equal(168)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
context "when calling with -w 24" do
|
165
|
+
it "returns 24" do
|
166
|
+
@configEye.set_wrap(24)
|
167
|
+
expect(@configEye.config.wrap).to equal(24)
|
168
|
+
end
|
169
|
+
|
170
|
+
it "calls save" do
|
171
|
+
expect(@configEye).to receive(:save).once
|
172
|
+
@configEye.set_wrap(24)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
151
177
|
describe "set_duration" do
|
152
178
|
include FakeFS::SpecHelpers
|
153
179
|
|
data/spec/record_spec.rb
CHANGED
@@ -27,6 +27,21 @@ describe "record" do
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
+
describe "#dir" 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 dir" do
|
40
|
+
dir = @record.dir(@configEye.config.cameras.first)
|
41
|
+
expect(dir).to eq("~/recordings/Reception")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
30
45
|
describe "record_path" do
|
31
46
|
before do
|
32
47
|
Timecop.freeze(Time.local(2015,06,30,10,05,0))
|
@@ -36,9 +51,9 @@ describe "record" do
|
|
36
51
|
Timecop.return
|
37
52
|
end
|
38
53
|
|
39
|
-
it "returns a full path
|
54
|
+
it "returns a full path" do
|
40
55
|
path = @record.path(@configEye.config.cameras.first)
|
41
|
-
expect(path).to
|
56
|
+
expect(path).to eq("~/recordings/Reception/Reception")
|
42
57
|
end
|
43
58
|
end
|
44
59
|
|
@@ -60,8 +75,8 @@ describe "record" do
|
|
60
75
|
end
|
61
76
|
|
62
77
|
it "calls kill" do
|
63
|
-
open4 =
|
64
|
-
open4.stub
|
78
|
+
open4 = double(Open4)
|
79
|
+
open4.stub(:exitstatus).and_return(0)
|
65
80
|
|
66
81
|
Open4.should_receive(:popen4).with('kill -INT 10000 10001 10002').and_return(open4)
|
67
82
|
|
@@ -75,6 +90,40 @@ describe "record" do
|
|
75
90
|
end
|
76
91
|
end
|
77
92
|
|
93
|
+
describe "#read_listfile" do
|
94
|
+
before do
|
95
|
+
FakeFS.activate!
|
96
|
+
end
|
97
|
+
|
98
|
+
after do
|
99
|
+
FakeFS.deactivate!
|
100
|
+
end
|
101
|
+
|
102
|
+
context "with a full file" do
|
103
|
+
before do
|
104
|
+
File.open("output.list", "w") do |file|
|
105
|
+
file.puts "file1.mjpeg"
|
106
|
+
file.puts "file2.mjpeg"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
it "returns the last filename" do
|
111
|
+
@record.read_listfile("output.list").should eq("file2.mjpeg")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context "with a empty file" do
|
116
|
+
before do
|
117
|
+
File.open("output.list", "w") do |file|
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
it "returns the last filename" do
|
122
|
+
@record.read_listfile("output.list").should eq(nil)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
78
127
|
describe "#remove" do
|
79
128
|
before do
|
80
129
|
@path = "/tmp/electric_eye"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: electric_eye
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Pope
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-03-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: construct
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: filewatcher
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: bundler
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -202,6 +216,7 @@ extensions: []
|
|
202
216
|
extra_rdoc_files: []
|
203
217
|
files:
|
204
218
|
- ".gitignore"
|
219
|
+
- ".ruby-version"
|
205
220
|
- Gemfile
|
206
221
|
- Gemfile.lock
|
207
222
|
- LICENSE.txt
|
@@ -251,7 +266,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
251
266
|
version: '0'
|
252
267
|
requirements: []
|
253
268
|
rubyforge_project:
|
254
|
-
rubygems_version: 2.
|
269
|
+
rubygems_version: 2.4.5.1
|
255
270
|
signing_key:
|
256
271
|
specification_version: 4
|
257
272
|
summary: Network Video Recorder
|