electric_eye 0.0.3 → 0.0.5
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/Gemfile.lock +1 -1
- data/README.org +20 -8
- data/bin/electric_eye +6 -1
- data/features/electric_eye.feature +8 -7
- data/lib/electric_eye/config_eye.rb +17 -4
- data/lib/electric_eye/motion.rb +44 -0
- data/lib/electric_eye/record.rb +27 -4
- data/lib/electric_eye/version.rb +1 -1
- data/lib/electric_eye.rb +1 -0
- data/man/electric_eye.1 +2 -2
- data/man/electric_eye.1.html +2 -2
- data/man/electric_eye.1.ronn +2 -2
- data/spec/config_spec.rb +46 -7
- data/spec/fixtures/movement.log +1219 -0
- data/spec/fixtures/no_movement.log +1219 -0
- data/spec/motion_spec.rb +79 -0
- data/spec/{electric_eye_spec.rb → record_spec.rb} +21 -1
- metadata +11 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b2affb0dc95af7bf5563442e3d984bad0011a6e2
|
4
|
+
data.tar.gz: 71cb8b06d07557218df843b86121cd3e3685a124
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b6eef4c40c21ddfcb1f66428e60a9cba2388718d65bbd25d09fb579aebe64500266d795c26cb5dd196fa186372838215d754d9d737f219e631c45d20ea312851
|
7
|
+
data.tar.gz: 258b468cef8ce9839000b1be4b38dab26f4f8345230b76cac884b714ba41ffd3f12c02233b8ef8c1e371e1c34018cb81bc969679f7aa4c91bcbf88a0f47b6bd9
|
data/Gemfile.lock
CHANGED
data/README.org
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
A network video recorder for multiple IP cameras using VLC.
|
4
4
|
|
5
|
+
[[http://mlug-au.org/doku.php/workshops/electric_eye_mpd][MLUG presentation slides on electric_eye]]
|
6
|
+
|
5
7
|
** History
|
6
8
|
|
7
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,7 +12,8 @@ The problem was though VLC doesn't automate the recordings or handle the file st
|
|
10
12
|
|
11
13
|
** Requirements
|
12
14
|
|
13
|
-
- VLC
|
15
|
+
- VLC - recording & motion detection
|
16
|
+
- xvfb - Running virtual frame buffers (ie: desktops)
|
14
17
|
- ruby
|
15
18
|
- Linux (Tested on Debian 7)
|
16
19
|
|
@@ -62,15 +65,19 @@ The default is going to be 10 minute blocks, this can be overridden with the dur
|
|
62
65
|
|
63
66
|
First make sure you add your cameras
|
64
67
|
|
65
|
-
: electric_eye
|
68
|
+
: electric_eye -a Reception <url>
|
66
69
|
|
67
70
|
Now start the daemon to start the recording process
|
68
71
|
|
69
|
-
: electric_eye
|
72
|
+
: electric_eye -s
|
73
|
+
|
74
|
+
Start with debug messages
|
75
|
+
|
76
|
+
: electric_eye -s --log-level=debug
|
70
77
|
|
71
78
|
Stop all recordings
|
72
79
|
|
73
|
-
: electric_eye
|
80
|
+
: electric_eye -k
|
74
81
|
|
75
82
|
Usage in development mode
|
76
83
|
|
@@ -143,18 +150,23 @@ Example for cleaning up reception after 60days at 7pm everynight.
|
|
143
150
|
5. Create a new Pull Request
|
144
151
|
|
145
152
|
** TODO
|
153
|
+
:PROPERTIES:
|
154
|
+
:CREATED: [2015-07-01 Wed 16:37]
|
155
|
+
:END:
|
146
156
|
|
147
|
-
- [
|
157
|
+
- [X] Add more testing
|
148
158
|
|
149
|
-
- [
|
159
|
+
- [X] Add post recording motion detection (use vlc)
|
160
|
+
|
161
|
+
- [X] Make sure we cannot add blank cameras
|
162
|
+
|
163
|
+
- [X] Create threshold as a variable
|
150
164
|
|
151
165
|
- [ ] Add a feature to clean up old recordings using a "period" setting
|
152
166
|
EG: 60 day period which could be set in the config file how many days you want to keep
|
153
167
|
Then just call 'electric_eye --remove-recordings' within crontab
|
154
168
|
This would iterate over all my cameras and remove old recordings to keep a rolling set of days.
|
155
169
|
|
156
|
-
- [ ] Make sure we cannot add blank cameras
|
157
|
-
|
158
170
|
- [ ] Allow different recording programs like raspicam
|
159
171
|
|
160
172
|
- [ ] Do inline motion detection (using activevlc)
|
data/bin/electric_eye
CHANGED
@@ -28,6 +28,10 @@ class App
|
|
28
28
|
@record.start
|
29
29
|
elsif options[:k] # Stop recordings
|
30
30
|
@record.stop
|
31
|
+
elsif options[:t]
|
32
|
+
@configEye.set_threshold(options[:threshold])
|
33
|
+
else
|
34
|
+
puts opts.help
|
31
35
|
end
|
32
36
|
|
33
37
|
exit 0
|
@@ -41,10 +45,11 @@ class App
|
|
41
45
|
on("-a", "--add", "Add a camera")
|
42
46
|
on("-r", "--remove", "Remove a camera")
|
43
47
|
on("-l", "--list", "List cameras")
|
44
|
-
on("-d", "--duration SECONDS", "Set recording duration in seconds")
|
48
|
+
on("-d", "--duration SECONDS", "Set recording duration in seconds (default: 600)")
|
45
49
|
on("-p", "--path DIR", "Set recordings path")
|
46
50
|
on("-s", "--start", "Start recordings")
|
47
51
|
on("-k", "--stop", "Stop recordings")
|
52
|
+
on("-t", "--threshold LEVEL", "Set threshold for motion detection (default: 2)")
|
48
53
|
|
49
54
|
# Arguments
|
50
55
|
arg :camera, :optional
|
@@ -13,11 +13,12 @@ Feature: Electric Eye
|
|
13
13
|
| camera | which is optional |
|
14
14
|
| url | which is optional |
|
15
15
|
And the following options should be documented:
|
16
|
-
| --add
|
17
|
-
| --remove
|
18
|
-
| --duration
|
19
|
-
| --path
|
20
|
-
| --list
|
21
|
-
| --start
|
22
|
-
| --stop
|
16
|
+
| --add |
|
17
|
+
| --remove |
|
18
|
+
| --duration |
|
19
|
+
| --path |
|
20
|
+
| --list |
|
21
|
+
| --start |
|
22
|
+
| --stop |
|
23
|
+
| --threshold |
|
23
24
|
|
@@ -19,7 +19,7 @@ module ElectricEye
|
|
19
19
|
if File.exist?(CONFIG_FILE)
|
20
20
|
Construct.load File.read(CONFIG_FILE)
|
21
21
|
else
|
22
|
-
Construct.new({duration: 600, path: '~/recordings', cameras: []})
|
22
|
+
Construct.new({duration: 600, path: '~/recordings', threshold: 2, cameras: []})
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
@@ -30,9 +30,15 @@ module ElectricEye
|
|
30
30
|
|
31
31
|
# Add camera
|
32
32
|
def add_camera(camera, url)
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
if camera.nil?
|
34
|
+
warn "NO camera given"
|
35
|
+
elsif url.nil?
|
36
|
+
warn "NO url given"
|
37
|
+
else
|
38
|
+
@config.cameras.push({name: camera, url: url})
|
39
|
+
save
|
40
|
+
info "Camera added"
|
41
|
+
end
|
36
42
|
end
|
37
43
|
|
38
44
|
# Remove camera
|
@@ -58,6 +64,13 @@ module ElectricEye
|
|
58
64
|
info "Duration set to #{seconds} seconds"
|
59
65
|
end
|
60
66
|
|
67
|
+
# Set threshold
|
68
|
+
def set_threshold(level)
|
69
|
+
@config.threshold = level.to_i
|
70
|
+
save
|
71
|
+
info "Threshold set to #{level} objects"
|
72
|
+
end
|
73
|
+
|
61
74
|
# Set path
|
62
75
|
def set_path(dir)
|
63
76
|
@config.path = dir
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'methadone'
|
2
|
+
require 'open4'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module ElectricEye
|
6
|
+
class Motion
|
7
|
+
include Methadone::CLILogging
|
8
|
+
|
9
|
+
# Detect if there is motion given the results
|
10
|
+
#
|
11
|
+
# path = the log file which is created by vlc with the movementdetect lines in.
|
12
|
+
# threshold is how many objects are moving at once expressed by vlc.
|
13
|
+
#
|
14
|
+
def detect(path, threshold = 2)
|
15
|
+
results = read_log(path)
|
16
|
+
results.each {|line| return true if movement(line) >= threshold}
|
17
|
+
return false
|
18
|
+
end
|
19
|
+
|
20
|
+
# Create the log file using vlc
|
21
|
+
def create_log(path)
|
22
|
+
# Use Xvfb which opens up a virtual desktop to dump a GUI screen we don't want to see.
|
23
|
+
# -a = select the next available display
|
24
|
+
`xvfb-run -a cvlc --no-loop --play-and-exit --video-filter=motiondetect -vvv #{path}.mjpeg > #{path}.log 2>&1`
|
25
|
+
end
|
26
|
+
|
27
|
+
# Read in the log file and return the motiondetect lines
|
28
|
+
def read_log(path)
|
29
|
+
results = []
|
30
|
+
if File.exists?(path)
|
31
|
+
File.readlines(path).each do |line|
|
32
|
+
results.push line.chomp if line =~ /motiondetect filter/
|
33
|
+
end
|
34
|
+
end
|
35
|
+
results
|
36
|
+
end
|
37
|
+
|
38
|
+
# Get the movement amount from the string
|
39
|
+
def movement(line)
|
40
|
+
line.slice!(/\[.*\]/) # Remove the number in brackets at the start of the string
|
41
|
+
line.slice(/\d+/).to_i # Get the movement
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/electric_eye/record.rb
CHANGED
@@ -12,6 +12,8 @@ module ElectricEye
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def start
|
15
|
+
@motion = Motion.new # Create a new instance method to our motion library
|
16
|
+
|
15
17
|
pids = []
|
16
18
|
# Step through each camera
|
17
19
|
@configEye.config.cameras.each do |camera|
|
@@ -21,10 +23,11 @@ module ElectricEye
|
|
21
23
|
stop_recording = false
|
22
24
|
Signal.trap('INT') { stop_recording = true }
|
23
25
|
until stop_recording
|
24
|
-
|
25
|
-
|
26
|
+
path = "#{path(camera)}"
|
27
|
+
debug "Recording #{camera[:name]} to #{path}.mjpeg..."
|
28
|
+
|
26
29
|
# Set a recording going using vlc, hold onto the process till it's finished.
|
27
|
-
cmd="cvlc
|
30
|
+
cmd="cvlc #{camera[:url]} --sout file/ts:#{path}.mjpeg"
|
28
31
|
pid,stdin,stdout,stderr=Open4::popen4(cmd)
|
29
32
|
|
30
33
|
# Wait for a defined duration from the config file.
|
@@ -37,14 +40,34 @@ module ElectricEye
|
|
37
40
|
|
38
41
|
Process.kill 9, pid # Stop current recording.
|
39
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
|
40
55
|
end
|
41
56
|
end
|
57
|
+
|
42
58
|
end
|
43
59
|
|
44
60
|
store_pids(pids)
|
45
61
|
info "Cameras recording"
|
46
62
|
end
|
47
63
|
|
64
|
+
# Remove a recording
|
65
|
+
def remove(path)
|
66
|
+
debug "REMOVE #{path}.mjpeg (no motion)"
|
67
|
+
File.delete("#{path}.log")
|
68
|
+
File.delete("#{path}.mjpeg")
|
69
|
+
end
|
70
|
+
|
48
71
|
def stop
|
49
72
|
stop_recordings(get_pids) if File.exist?(PID_FILE)
|
50
73
|
end
|
@@ -62,7 +85,7 @@ module ElectricEye
|
|
62
85
|
def path(camera)
|
63
86
|
dir = "#{@configEye.config.path}/#{camera[:name]}"
|
64
87
|
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
65
|
-
"#{dir}/#{Time.now.strftime('%Y%m%d-%H%M')}-#{camera[:name]}
|
88
|
+
"#{dir}/#{Time.now.strftime('%Y%m%d-%H%M')}-#{camera[:name]}"
|
66
89
|
end
|
67
90
|
|
68
91
|
def initialize(configEye)
|
data/lib/electric_eye/version.rb
CHANGED
data/lib/electric_eye.rb
CHANGED
data/man/electric_eye.1
CHANGED
data/man/electric_eye.1.html
CHANGED
@@ -120,11 +120,11 @@ duration of the recordings is set by the user (default is 10minutes).</p>
|
|
120
120
|
|
121
121
|
<h2 id="AUTHOR">AUTHOR</h2>
|
122
122
|
|
123
|
-
<p>Michael Pope
|
123
|
+
<p>Michael Pope <a href="mailto:map7777@gmail.com" data-bare-link="true">map7777@gmail.com</a></p>
|
124
124
|
|
125
125
|
<h2 id="SEE-ALSO">SEE ALSO</h2>
|
126
126
|
|
127
|
-
<p
|
127
|
+
<p><a href="https://www.videolan.org/vlc/">VLC</a></p>
|
128
128
|
|
129
129
|
|
130
130
|
<ol class='man-decor man-foot man foot'>
|
data/man/electric_eye.1.ronn
CHANGED
data/spec/config_spec.rb
CHANGED
@@ -89,15 +89,27 @@ describe "add camera" do
|
|
89
89
|
before do
|
90
90
|
@configEye = ConfigEye.new
|
91
91
|
end
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
92
|
+
|
93
|
+
context "when both name & url provided" do
|
94
|
+
it "adds camera to array" do
|
95
|
+
@configEye.add_camera("Reception", "http://user:pass@my.camera.org/live2.sdp")
|
96
|
+
expect(@configEye.config.cameras.length).to equal(1)
|
97
|
+
end
|
98
|
+
|
99
|
+
it "calls save" do
|
100
|
+
expect(@configEye).to receive(:save).once
|
101
|
+
@configEye.add_camera("Reception", "http://user:pass@my.camera.org/live2.sdp")
|
102
|
+
end
|
96
103
|
end
|
97
104
|
|
98
|
-
|
99
|
-
|
100
|
-
|
105
|
+
context "when only name provided" do
|
106
|
+
it "returns an error" do
|
107
|
+
end
|
108
|
+
|
109
|
+
it "doesn't call save" do
|
110
|
+
expect(@configEye).to receive(:save).never
|
111
|
+
@configEye.add_camera("Reception", nil)
|
112
|
+
end
|
101
113
|
end
|
102
114
|
end
|
103
115
|
|
@@ -188,3 +200,30 @@ describe "set_path" do
|
|
188
200
|
end
|
189
201
|
end
|
190
202
|
|
203
|
+
describe "set_threshold" do
|
204
|
+
include FakeFS::SpecHelpers
|
205
|
+
|
206
|
+
before do
|
207
|
+
@configEye = ConfigEye.new
|
208
|
+
end
|
209
|
+
|
210
|
+
context "when no threshold has been set" do
|
211
|
+
it "returns the default of 2 objects" do
|
212
|
+
expect(@configEye.config.threshold).to equal(2)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
context "when calling with -d 3" do
|
217
|
+
it "returns 3" do
|
218
|
+
@configEye.set_threshold(3)
|
219
|
+
expect(@configEye.config.threshold).to equal(3)
|
220
|
+
end
|
221
|
+
|
222
|
+
it "calls save" do
|
223
|
+
expect(@configEye).to receive(:save).once
|
224
|
+
@configEye.set_threshold(3)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
|