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 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
+