electric_eye 0.0.5 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|