electric_eye 0.0.3 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 681ad7ca61fc217562ed5aadb036c3d234518e5a
4
- data.tar.gz: 83c70fdbc5eada7fa3fe7ec1492e60fbb2f89625
3
+ metadata.gz: b2affb0dc95af7bf5563442e3d984bad0011a6e2
4
+ data.tar.gz: 71cb8b06d07557218df843b86121cd3e3685a124
5
5
  SHA512:
6
- metadata.gz: 6c9d1e40e8de47dd9b5773d2cee132912a9c71f432c1c15849622d854a2958c0e00528d51371b15788d53ef037b96455783d5d1b5536b4cf18d961f3babada4b
7
- data.tar.gz: dec64f404a955f65b6487cc5fcc7c99fc2d44954b3129770e0486474d9ad37dfdec2f43043343c796dcbb6590622cff00dd5cf0598a1980f81ef0e533a6e1724
6
+ metadata.gz: b6eef4c40c21ddfcb1f66428e60a9cba2388718d65bbd25d09fb579aebe64500266d795c26cb5dd196fa186372838215d754d9d737f219e631c45d20ea312851
7
+ data.tar.gz: 258b468cef8ce9839000b1be4b38dab26f4f8345230b76cac884b714ba41ffd3f12c02233b8ef8c1e371e1c34018cb81bc969679f7aa4c91bcbf88a0f47b6bd9
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- electric_eye (0.0.2)
4
+ electric_eye (0.0.4)
5
5
  construct
6
6
  methadone (~> 1.9.0)
7
7
  open4
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 add Reception <url>
68
+ : electric_eye -a Reception <url>
66
69
 
67
70
  Now start the daemon to start the recording process
68
71
 
69
- : electric_eye start
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 stop
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
- - [ ] Add more testing
157
+ - [X] Add more testing
148
158
 
149
- - [ ] Add post recording motion detection (using [rmotion](https://github.com/rikiji/rmotion))
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
- @config.cameras.push({name: camera, url: url})
34
- save
35
- info "Camera added"
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
@@ -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
- debug "Recording #{camera[:name]} to #{path(camera)}..."
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 --qt-minimal-view --no-audio -R #{camera[:url]} --sout file/ts:#{path(camera)}"
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]}.mjpeg"
88
+ "#{dir}/#{Time.now.strftime('%Y%m%d-%H%M')}-#{camera[:name]}"
66
89
  end
67
90
 
68
91
  def initialize(configEye)
@@ -1,3 +1,3 @@
1
1
  module ElectricEye
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.5"
3
3
  end
data/lib/electric_eye.rb CHANGED
@@ -2,6 +2,7 @@ require "electric_eye/version"
2
2
  require "electric_eye/settings"
3
3
  require "electric_eye/config_eye"
4
4
  require "electric_eye/record"
5
+ require "electric_eye/motion"
5
6
 
6
7
  module ElectricEye
7
8
 
data/man/electric_eye.1 CHANGED
@@ -79,7 +79,7 @@ Stop recordings
79
79
  $ electric_eye \-k
80
80
  .
81
81
  .SH "AUTHOR"
82
- Michael Pope (map7777@gmail\.com)
82
+ Michael Pope \fImap7777@gmail\.com\fR
83
83
  .
84
84
  .SH "SEE ALSO"
85
- VLC (https://www\.videolan\.org/vlc/)
85
+ VLC \fIhttps://www\.videolan\.org/vlc/\fR
@@ -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 (map7777@gmail.com)</p>
123
+ <p>Michael Pope <a href="&#x6d;&#x61;&#x69;&#x6c;&#x74;&#x6f;&#58;&#x6d;&#x61;&#112;&#x37;&#x37;&#x37;&#55;&#x40;&#x67;&#x6d;&#97;&#105;&#x6c;&#x2e;&#x63;&#x6f;&#109;" data-bare-link="true">&#x6d;&#x61;&#112;&#55;&#55;&#x37;&#x37;&#64;&#103;&#109;&#x61;&#x69;&#x6c;&#x2e;&#x63;&#111;&#x6d;</a></p>
124
124
 
125
125
  <h2 id="SEE-ALSO">SEE ALSO</h2>
126
126
 
127
- <p>VLC (https://www.videolan.org/vlc/)</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'>
@@ -51,9 +51,9 @@ Stop recordings<br>
51
51
 
52
52
  ## AUTHOR
53
53
 
54
- Michael Pope (map7777@gmail.com)
54
+ Michael Pope <map7777@gmail.com>
55
55
 
56
56
  ## SEE ALSO
57
57
 
58
- VLC (https://www.videolan.org/vlc/)
58
+ [VLC](https://www.videolan.org/vlc/)
59
59
 
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
- 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)
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
- it "calls save" do
99
- expect(@configEye).to receive(:save).once
100
- @configEye.add_camera("Reception", "http://user:pass@my.camera.org/live2.sdp")
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
+