electric_eye 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a47ea7aadb4898d0e3021215284e48906b11d0af
4
+ data.tar.gz: 585767b4e074d6443a586ef063123fbef896f19b
5
+ SHA512:
6
+ metadata.gz: 669d9b4aed0148677a03aa7b3a99c346a73ba05571008936e49a9585c88f95281ab55e9f5122880253001948184cc5f000fb0267b031459bec7cbed356f5a3a6
7
+ data.tar.gz: 6e5e82f135be9c9199baa758be1fce94d56367208abeb54a1dd9117b618328d7d875824155837ff15b8a5d2f84205e0068ffa423069d5be71265c598e68a6e14
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ .DS_Store
2
+ results.html
3
+ pkg
4
+ html
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in electric_eye.gemspec
4
+ gemspec
5
+
6
+ gem 'construct' # Config manager in YAML format
7
+ gem 'table_print' # Print a nice table in stdout
8
+ gem 'timecop' # Freeze time for tests
9
+ gem 'fakefs', require: "fakefs/safe"
10
+ gem 'open4' # Handle shell commands
data/Gemfile.lock ADDED
@@ -0,0 +1,66 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ electric_eye (0.0.1)
5
+ methadone (~> 1.9.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ aruba (0.6.2)
11
+ childprocess (>= 0.3.6)
12
+ cucumber (>= 1.1.1)
13
+ rspec-expectations (>= 2.7.0)
14
+ builder (3.2.2)
15
+ childprocess (0.5.6)
16
+ ffi (~> 1.0, >= 1.0.11)
17
+ construct (0.1.7)
18
+ cucumber (2.0.0)
19
+ builder (>= 2.1.2)
20
+ cucumber-core (~> 1.1.3)
21
+ diff-lcs (>= 1.1.3)
22
+ gherkin (~> 2.12)
23
+ multi_json (>= 1.7.5, < 2.0)
24
+ multi_test (>= 0.1.2)
25
+ cucumber-core (1.1.3)
26
+ gherkin (~> 2.12.0)
27
+ diff-lcs (1.2.5)
28
+ fakefs (0.6.7)
29
+ ffi (1.9.8)
30
+ gherkin (2.12.2)
31
+ multi_json (~> 1.3)
32
+ json (1.8.2)
33
+ methadone (1.9.0)
34
+ bundler
35
+ multi_json (1.11.0)
36
+ multi_test (0.1.2)
37
+ open4 (1.3.4)
38
+ rake (10.4.2)
39
+ rdoc (4.2.0)
40
+ json (~> 1.4)
41
+ rspec (2.99.0)
42
+ rspec-core (~> 2.99.0)
43
+ rspec-expectations (~> 2.99.0)
44
+ rspec-mocks (~> 2.99.0)
45
+ rspec-core (2.99.2)
46
+ rspec-expectations (2.99.2)
47
+ diff-lcs (>= 1.1.3, < 2.0)
48
+ rspec-mocks (2.99.3)
49
+ table_print (1.5.3)
50
+ timecop (0.3.5)
51
+
52
+ PLATFORMS
53
+ ruby
54
+
55
+ DEPENDENCIES
56
+ aruba
57
+ bundler (~> 1.7)
58
+ construct
59
+ electric_eye!
60
+ fakefs
61
+ open4
62
+ rake (~> 10.0)
63
+ rdoc
64
+ rspec (~> 2.99)
65
+ table_print
66
+ timecop
data/LICENSE.txt ADDED
@@ -0,0 +1,8 @@
1
+ Name: electric_eye
2
+ Copyright (c) 2015 Michael Pope
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5
+
6
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7
+
8
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # ElectricEye
2
+
3
+ A network video recorder for multiple IP cameras using VLC.
4
+
5
+ ## History
6
+
7
+ 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.
8
+
9
+ The problem was though VLC doesn't automate the recordings or handle the file structure nicely. This is where I started to think about creating an application which records from VLC and nicely sorts those recordings in directories by date & time.
10
+
11
+ ## Requirements
12
+
13
+ - VLC
14
+ - ruby
15
+ - Linux (Tested on Debian 7)
16
+
17
+ ## Installation
18
+
19
+ Add this line to your application's Gemfile:
20
+
21
+ ```ruby
22
+ gem 'electric_eye'
23
+ ```
24
+
25
+ And then execute:
26
+
27
+ $ bundle
28
+
29
+ Or install it yourself as:
30
+
31
+ $ gem install electric_eye
32
+
33
+ ## Configuration
34
+
35
+ Enter your cameras into the JSON config file like so
36
+
37
+ ```yaml
38
+ ---
39
+ duration: 60
40
+ path: "/media/data/recordings/temp"
41
+ cameras:
42
+ - :name: Reception
43
+ :url: rtsp://<user>:<passwd>@<camera's ip>/live2.sdp
44
+ - :name: Kitchen
45
+ :url: rtsp://<user>:<passwd>@<camera's ip>/live2.sdp
46
+ - :name: Workstations
47
+ :url: rtsp://<user>:<passwd>@<camera's ip>/live2.sdp
48
+ ```
49
+
50
+ You should be able to view the URL through vlc before using this program.
51
+
52
+ The recordings directory will end up with these directories
53
+
54
+ /media/data/recordings/reception/20150527
55
+ /media/data/recordings/kitchen/20150527
56
+
57
+ Notice the date at the end of these paths, there will be one for each day. The contents within will be recordings which are done by default every 10minutes, example;
58
+
59
+ 20150527-1020-reception.mjpeg
60
+ 20150527-1030-reception.mjpeg
61
+ 20150527-1040-reception.mjpeg
62
+
63
+ The default is going to be 10 minute blocks, this can be overridden with the duration variable above in minutes.
64
+
65
+ ## Usage
66
+
67
+ First make sure you add your cameras
68
+
69
+ electric_eye add Reception <url>
70
+
71
+ Now start the daemon to start the recording process
72
+
73
+ electric_eye start
74
+
75
+ Stop all recordings
76
+
77
+ electric_eye stop
78
+
79
+ Usage in development mode
80
+
81
+ bundle exec bin/electric_eye -h
82
+
83
+
84
+ ## Cleanup
85
+
86
+ Cleaning up recordings. Put the following into your /etc/crontab per recording directory.
87
+
88
+ 00 19 * * * root /usr/bin/find <directory to recordings> -type f -mtime +<days> -exec rm {} \;
89
+
90
+ Example for cleaning up reception after 60days at 7pm everynight.
91
+
92
+ 00 19 * * * root /usr/bin/find /media/recordings/reception -type f -mtime +60 -exec rm {} \;
93
+
94
+ ## Contributing
95
+
96
+ 1. Fork it ( https://github.com/map7/electric_eye/fork )
97
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
98
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
99
+ 4. Push to the branch (`git push origin my-new-feature`)
100
+ 5. Create a new Pull Request
101
+
102
+ ## TODO
103
+
104
+ - Adjust directory layout
105
+ - Turn into a gem
106
+ - Add testing
107
+ - Add motion detection (using [rmotion](https://github.com/rikiji/rmotion))
108
+ - Do inline motion detection (using activevlc)
109
+ - Allow different recording programs like raspicam
110
+
data/README.rdoc ADDED
@@ -0,0 +1,23 @@
1
+ = electric_eye - DESCRIBE YOUR GEM
2
+
3
+ Author:: YOUR NAME (YOUR EMAIL)
4
+ Copyright:: Copyright (c) 2015 YOUR NAME
5
+
6
+
7
+ License:: mit, see LICENSE.txt
8
+
9
+
10
+
11
+ DESCRIBE YOUR GEM HERE
12
+
13
+ == Links
14
+
15
+ * {Source on Github}[LINK TO GITHUB]
16
+ * RDoc[LINK TO RDOC.INFO]
17
+
18
+ == Install
19
+
20
+ == Examples
21
+
22
+ == Contributing
23
+
data/Rakefile ADDED
@@ -0,0 +1,66 @@
1
+ def dump_load_path
2
+ puts $LOAD_PATH.join("\n")
3
+ found = nil
4
+ $LOAD_PATH.each do |path|
5
+ if File.exists?(File.join(path,"rspec"))
6
+ puts "Found rspec in #{path}"
7
+ if File.exists?(File.join(path,"rspec","core"))
8
+ puts "Found core"
9
+ if File.exists?(File.join(path,"rspec","core","rake_task"))
10
+ puts "Found rake_task"
11
+ found = path
12
+ else
13
+ puts "!! no rake_task"
14
+ end
15
+ else
16
+ puts "!!! no core"
17
+ end
18
+ end
19
+ end
20
+ if found.nil?
21
+ puts "Didn't find rspec/core/rake_task anywhere"
22
+ else
23
+ puts "Found in #{path}"
24
+ end
25
+ end
26
+ require 'bundler'
27
+ require 'rake/clean'
28
+
29
+ begin
30
+ require 'rspec/core/rake_task'
31
+ rescue LoadError
32
+ dump_load_path
33
+ raise
34
+ end
35
+
36
+ require 'cucumber'
37
+ require 'cucumber/rake/task'
38
+ gem 'rdoc' # we need the installed RDoc gem, not the system one
39
+ require 'rdoc/task'
40
+
41
+ include Rake::DSL
42
+
43
+ Bundler::GemHelper.install_tasks
44
+
45
+
46
+ RSpec::Core::RakeTask.new do |t|
47
+ # Put spec opts in a file named .rspec in root
48
+ end
49
+
50
+
51
+ CUKE_RESULTS = 'results.html'
52
+ CLEAN << CUKE_RESULTS
53
+ Cucumber::Rake::Task.new(:features) do |t|
54
+ t.cucumber_opts = "features --format html -o #{CUKE_RESULTS} --format pretty --no-source -x"
55
+ t.fork = false
56
+ end
57
+
58
+ Rake::RDocTask.new do |rd|
59
+
60
+ rd.main = "README.rdoc"
61
+
62
+ rd.rdoc_files.include("README.rdoc","lib/**/*.rb","bin/**/*")
63
+ end
64
+
65
+ task :default => [:spec,:features]
66
+
data/bin/electric_eye ADDED
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'methadone'
5
+ require 'construct'
6
+ require 'electric_eye.rb'
7
+ include ElectricEye
8
+
9
+ class App
10
+ include Methadone::Main
11
+ include Methadone::CLILogging
12
+
13
+ main do |camera, url| # Add args you want: |like,so|
14
+ @configEye = ConfigEye.new # Initialize config
15
+ @record = Record.new(@configEye) # Initialize record and pass in our config
16
+
17
+ if options[:a] # Add camera
18
+ @configEye.add_camera(camera,url)
19
+ elsif options[:r] # Remove camera
20
+ @configEye.remove_camera(camera)
21
+ elsif options[:l] # List cameras
22
+ @configEye.list_cameras
23
+ elsif options[:d] # Set duration
24
+ @configEye.set_duration(options[:duration])
25
+ elsif options[:p] # Set path
26
+ @configEye.set_path(options[:path])
27
+ elsif options[:s] # Start recording
28
+ @record.start
29
+ elsif options[:k] # Stop recordings
30
+ @record.stop
31
+ end
32
+
33
+ exit 0
34
+ end
35
+
36
+
37
+
38
+ description "Network Video Recorder"
39
+
40
+ # Accept flags via:
41
+ on("-a", "--add", "Add a camera")
42
+ on("-r", "--remove", "Remove a camera")
43
+ on("-l", "--list", "List cameras")
44
+ on("-d", "--duration SECONDS", "Set recording duration in seconds")
45
+ on("-p", "--path DIR", "Set recordings path")
46
+ on("-s", "--start", "Start recordings")
47
+ on("-k", "--stop", "Stop recordings")
48
+
49
+ # Arguments
50
+ arg :camera, :optional
51
+ arg :url, :optional
52
+
53
+ version ElectricEye::VERSION
54
+ use_log_level_option :toggle_debug_on_signal => 'USR1'
55
+
56
+ go!
57
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'electric_eye/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "electric_eye"
8
+ spec.version = ElectricEye::VERSION
9
+ spec.authors = ["Michael Pope"]
10
+ spec.email = ["michael@dtcorp.com.au"]
11
+ spec.summary = %q{Network Video Recorder}
12
+ spec.description = %q{A network video recorder for multiple IP cameras which uses VLC to record and organises the files according to date and camera name.}
13
+ spec.homepage = "https://github.com/map7/electric_eye"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency('rdoc')
24
+ spec.add_development_dependency('aruba')
25
+ spec.add_dependency('methadone', '~> 1.9.0')
26
+ spec.add_development_dependency('rspec', '~> 2.99')
27
+ end
@@ -0,0 +1,40 @@
1
+ Feature: Config Electric Eye
2
+ In order to record cameras
3
+ I want to easily setup cameras and other variables
4
+ So we can just start and stop the recordings
5
+
6
+ Scenario: Add camera
7
+ When I successfully run `electric_eye -a Reception rtsp://user:passwd@192.168.0.100/live.sdp`
8
+ Then the exit status should be 0
9
+ And we should have a directory called "~/.electric_eye"
10
+ And we should have a file called "~/.electric_eye/config.yml"
11
+ And within the file "~/.electric_eye/config.yml" we should have the camera "Reception"
12
+ And the stdout should contain "Camera added"
13
+
14
+ Scenario: Remove camera
15
+ Given I have a camera called "Reception"
16
+ When I successfully run `electric_eye -r Reception`
17
+ Then the exit status should be 0
18
+ And within the file "~/.electric_eye/config.yml" we should no cameras
19
+ And the stdout should contain "Camera removed"
20
+
21
+ Scenario: List cameras
22
+ Given I have a camera called "Reception"
23
+ When I successfully run `electric_eye -l`
24
+ Then the exit status should be 0
25
+ And within the file "~/.electric_eye/config.yml" we should have the camera "Reception"
26
+ And the stdout should contain "Cameras"
27
+ And the stdout should contain "Reception"
28
+
29
+ Scenario: Set duration
30
+ When I successfully run `electric_eye -d 10`
31
+ Then the exit status should be 0
32
+ And within the file "~/.electric_eye/config.yml" we should have the duration "10"
33
+ And the stdout should contain "Duration set to 10 seconds"
34
+
35
+ Scenario: Set path
36
+ When I successfully run `electric_eye -p '/data/recordings'`
37
+ Then the exit status should be 0
38
+ And within the file "~/.electric_eye/config.yml" we should have the path "/data/recordings"
39
+ And the stdout should contain "Path set to /data/recordings"
40
+
@@ -0,0 +1,23 @@
1
+ Feature: Electric Eye
2
+ In order to record cameras
3
+ I want to have an easy to use interface
4
+
5
+ Scenario: Basic UI
6
+ When I get help for "electric_eye"
7
+ Then the exit status should be 0
8
+ And the banner should be present
9
+ And there should be a one line summary of what the app does
10
+ And the banner should include the version
11
+ And the banner should document that this app takes options
12
+ And the banner should document that this app's arguments are:
13
+ | camera | which is optional |
14
+ | url | which is optional |
15
+ And the following options should be documented:
16
+ | --add |
17
+ | --remove |
18
+ | --duration |
19
+ | --path |
20
+ | --list |
21
+ | --start |
22
+ | --stop |
23
+
@@ -0,0 +1,18 @@
1
+ Feature: Record
2
+ In order to record cameras
3
+ I need a way to start recordings per camera
4
+
5
+ Scenario: Record cameras
6
+ Given I have a camera called "Reception"
7
+ When I successfully run `electric_eye --start`
8
+ Then the exit status should be 0
9
+ And it should create a pid file in "/tmp/electric_eye.pid"
10
+ And the stdout should contain "Cameras recording"
11
+
12
+ Scenario: Stop recordings
13
+ Given I have a camera called "Reception"
14
+ And I successfully run `electric_eye --start`
15
+ When I successfully run `electric_eye --stop`
16
+ Then the exit status should be 0
17
+ And it should remove the pid file in "/tmp/electric_eye.pid"
18
+ And the stdout should contain "Stop recordings"
@@ -0,0 +1,47 @@
1
+ require 'construct'
2
+ require 'fileutils'
3
+
4
+ Given(/^I have a camera called "([^"]*)"$/) do |arg1|
5
+ @config = Construct.new
6
+ @config.cameras = [{name: "Reception", url: "http://thecamera.org"}]
7
+ dir="#{ENV['HOME']}/.electric_eye"
8
+ FileUtils.rm_r(dir) if Dir.exist?(dir)
9
+ Dir.mkdir(dir)
10
+ File.open("#{dir}/config.yml","w"){ |f| f.write @config.to_yaml }
11
+ end
12
+
13
+ Then(/^we should have a directory called "([^"]*)"$/) do |dir|
14
+ config_dir = File.expand_path(dir) # Expand ~ to ENV["HOME"]
15
+ expect(Dir.exist?(config_dir)).to equal(true)
16
+ end
17
+
18
+ Then(/^we should have a file called "([^"]*)"$/) do |dir|
19
+ config_file = File.expand_path(dir) # Expand ~ to ENV["HOME"]
20
+ expect(File.exist?(config_file)).to equal(true)
21
+ end
22
+
23
+ Then(/^within the file "([^"]*)" we should have the camera "([^"]*)"$/) do |file, camera|
24
+ config_file = File.expand_path(file) # Expand ~ to ENV["HOME"]
25
+ @config = Construct.load File.read(config_file)
26
+ expect(@config.cameras.length).to equal(1)
27
+ expect(@config.cameras.first[:name] == camera).to equal(true)
28
+ end
29
+
30
+ Then(/^within the file "([^"]*)" we should have the duration "([^"]*)"$/) do |file, duration|
31
+ config_file = File.expand_path(file) # Expand ~ to ENV["HOME"]
32
+ @config = Construct.load File.read(config_file)
33
+ expect(@config.duration == duration.to_i).to equal(true)
34
+ end
35
+
36
+ Then(/^within the file "([^"]*)" we should have the path "([^"]*)"$/) do |file, path|
37
+ config_file = File.expand_path(file) # Expand ~ to ENV["HOME"]
38
+ @config = Construct.load File.read(config_file)
39
+ expect(@config.path == path).to equal(true)
40
+ end
41
+
42
+ Then(/^within the file "([^"]*)" we should no cameras$/) do |file|
43
+ config_file = File.expand_path(file) # Expand ~ to ENV["HOME"]
44
+ @config = Construct.load File.read(config_file)
45
+ expect(@config.cameras.length).to equal(0)
46
+ end
47
+
@@ -0,0 +1,10 @@
1
+ require 'construct'
2
+ require 'fileutils'
3
+
4
+ Then(/^it should create a pid file in "([^"]*)"$/) do |file|
5
+ expect(File.exist?(file)).to equal(true)
6
+ end
7
+
8
+ Then(/^it should remove the pid file in "([^"]*)"$/) do |file|
9
+ expect(File.exist?(file)).to equal(false)
10
+ end
@@ -0,0 +1,22 @@
1
+ require 'aruba/cucumber'
2
+ require 'methadone/cucumber'
3
+
4
+ ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
5
+ LIB_DIR = File.join(File.expand_path(File.dirname(__FILE__)),'..','..','lib')
6
+
7
+ Before do
8
+ # Using "announce" causes massive warnings on 1.9.2
9
+ @puts = true
10
+ @original_rubylib = ENV['RUBYLIB']
11
+ ENV['RUBYLIB'] = LIB_DIR + File::PATH_SEPARATOR + ENV['RUBYLIB'].to_s
12
+
13
+ @original_home = ENV['HOME']
14
+ ENV['HOME'] = "/tmp/fakehome"
15
+ FileUtils.rm_rf "/tmp/fakehome"
16
+ FileUtils.mkdir "/tmp/fakehome"
17
+ end
18
+
19
+ After do
20
+ ENV['RUBYLIB'] = @original_rubylib
21
+ ENV['HOME'] = @original_home
22
+ end
@@ -0,0 +1,75 @@
1
+ require 'methadone'
2
+ require 'fileutils'
3
+ require 'table_print'
4
+
5
+ module ElectricEye
6
+ class ConfigEye
7
+
8
+ include Methadone::CLILogging
9
+
10
+ # Check the directory and if it doesn't exist create it.
11
+ def self.check_dir
12
+ FileUtils.mkdir_p(CONFIG_DIR) unless Dir.exist?(CONFIG_DIR)
13
+ end
14
+
15
+ # Check that the config file exists.
16
+ def self.load
17
+ # Check if we have a config CONFIG_FILE
18
+ ConfigEye.check_dir
19
+ if File.exist?(CONFIG_FILE)
20
+ Construct.load File.read(CONFIG_FILE)
21
+ else
22
+ Construct.new({duration: 600, path: '~/recordings', cameras: []})
23
+ end
24
+ end
25
+
26
+ # Save the config file
27
+ def save()
28
+ File.open(CONFIG_FILE, 'w'){ |f| f.write config.to_yaml } # Store
29
+ end
30
+
31
+ # Add camera
32
+ def add_camera(camera, url)
33
+ @config.cameras.push({name: camera, url: url})
34
+ save
35
+ info "Camera added"
36
+ end
37
+
38
+ # Remove camera
39
+ def remove_camera(camera)
40
+ record = @config.cameras.bsearch{ |c| c[:name] == camera }
41
+ if record
42
+ @config.cameras.delete(record)
43
+ save
44
+ end
45
+ info "Camera removed"
46
+ end
47
+
48
+ # List cameras in setup
49
+ def list_cameras
50
+ info "Cameras"
51
+ tp @config.cameras, :name, :url => {width: 120}
52
+ end
53
+
54
+ # Set duration
55
+ def set_duration(seconds)
56
+ @config.duration = seconds.to_i
57
+ save
58
+ info "Duration set to #{seconds} seconds"
59
+ end
60
+
61
+ # Set path
62
+ def set_path(dir)
63
+ @config.path = dir
64
+ save
65
+ info "Path set to #{dir}"
66
+ end
67
+
68
+ # Initialise the method.
69
+ attr_reader :config
70
+ def initialize
71
+ @config = ConfigEye.load
72
+ save
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,72 @@
1
+ require 'methadone'
2
+ require 'open4'
3
+ require 'fileutils'
4
+
5
+ module ElectricEye
6
+ class Record
7
+
8
+ include Methadone::CLILogging
9
+
10
+ def store_pids(pids = [])
11
+ File.open(PID_FILE, "w") { |file| file.write pids.join(" ") }
12
+ end
13
+
14
+ def start
15
+ pids = []
16
+ # Step through each camera
17
+ @configEye.config.cameras.each do |camera|
18
+ # Let's first record two 1minute videos of each camera.
19
+ # This will turn into a never ending loop until we stop the process.
20
+ pids << fork do
21
+ stop_recording = false
22
+ Signal.trap('INT') { stop_recording = true }
23
+ until stop_recording
24
+ debug "Recording #{camera[:name]} to #{path(camera)}..."
25
+
26
+ # 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)}"
28
+ pid,stdin,stdout,stderr=Open4::popen4(cmd)
29
+
30
+ # Wait for a defined duration from the config file.
31
+ seconds = @configEye.config.duration
32
+ while(seconds > 0)
33
+ sleep 1
34
+ seconds -= 1
35
+ break if stop_recording
36
+ end
37
+
38
+ Process.kill 9, pid # Stop current recording.
39
+ Process.wait pid # Wait around so we don't get Zombies
40
+ end
41
+ end
42
+ end
43
+
44
+ store_pids(pids)
45
+ info "Cameras recording"
46
+ end
47
+
48
+ def stop
49
+ stop_recordings(get_pids) if File.exist?(PID_FILE)
50
+ end
51
+
52
+ def stop_recordings(pids)
53
+ info "Stop recordings with PID: #{pids}..."
54
+ Open4::popen4("kill -INT #{pids}") # Kill all recordings
55
+ File.delete(PID_FILE) # Remove the pid file.
56
+ end
57
+
58
+ def get_pids
59
+ File.open(PID_FILE, "r").gets # Get pids
60
+ end
61
+
62
+ def path(camera)
63
+ dir = "#{@configEye.config.path}/#{camera[:name]}"
64
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
65
+ "#{dir}/#{Time.now.strftime('%Y%m%d-%H%M')}-#{camera[:name]}.mjpeg"
66
+ end
67
+
68
+ def initialize(configEye)
69
+ @configEye = configEye
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,5 @@
1
+ module ElectricEye
2
+ CONFIG_DIR = "#{ENV['HOME']}/.electric_eye"
3
+ CONFIG_FILE = "#{CONFIG_DIR}/config.yml"
4
+ PID_FILE = "/tmp/electric_eye.pid"
5
+ end
@@ -0,0 +1,3 @@
1
+ module ElectricEye
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,9 @@
1
+ require "electric_eye/version"
2
+ require "electric_eye/settings"
3
+ require "electric_eye/config_eye"
4
+ require "electric_eye/record"
5
+
6
+ module ElectricEye
7
+
8
+
9
+ end
@@ -0,0 +1,190 @@
1
+ require ('spec_helper.rb')
2
+ include ElectricEye
3
+
4
+ describe "initialize" do
5
+ include FakeFS::SpecHelpers
6
+
7
+ it "sets config" do
8
+ configEye = ConfigEye.new
9
+
10
+ # Check defaults
11
+ expect(configEye.config.duration).to equal(600)
12
+ expect(configEye.config.path).to include("~/recordings")
13
+ expect(configEye.config.cameras.length).to equal(0)
14
+ end
15
+ end
16
+
17
+ describe "check_dir" do
18
+ include FakeFS::SpecHelpers
19
+
20
+ context "when config directory does not exists" do
21
+ it "makes a directory" do
22
+ ConfigEye.check_dir
23
+ expect(File.directory?(CONFIG_DIR)).to equal(true)
24
+ end
25
+ end
26
+
27
+ context "when config directory exists" do
28
+ it "doesn't make a directory" do
29
+ FileUtils.mkdir_p(CONFIG_DIR) # Create the directory
30
+ ConfigEye.check_dir
31
+ expect(File.directory?(CONFIG_DIR)).to equal(true)
32
+ end
33
+ end
34
+ end
35
+
36
+ describe "save" do
37
+ include FakeFS::SpecHelpers
38
+
39
+ context "config is set" do
40
+ it "writes the config file" do
41
+ @configEye = ConfigEye.new
42
+ @configEye.save
43
+ expect(File.file?(CONFIG_FILE)).to equal(true)
44
+ end
45
+ end
46
+ end
47
+
48
+ describe "check config" do
49
+ include FakeFS::SpecHelpers
50
+
51
+ it "checks the directory" do
52
+ ConfigEye.load
53
+ expect(File.directory?(CONFIG_DIR)).to equal(true)
54
+ end
55
+
56
+ context "when exists" do
57
+ before do
58
+ # Create the file beforehand
59
+ @configEye = ConfigEye.new
60
+ @configEye.save
61
+ end
62
+
63
+ it "returns a construct object" do
64
+ expect(File.directory?(CONFIG_DIR)).to equal(true)
65
+ @config = ConfigEye.load
66
+ expect(@config.class).to equal(Construct)
67
+ end
68
+ end
69
+
70
+ context "doesn't exist" do
71
+ before do
72
+ @configEye = ConfigEye.load
73
+ expect(File.file?(CONFIG_FILE)).to equal(false)
74
+ end
75
+
76
+ it "returns a construct object" do
77
+ expect(@configEye.class).to equal(Construct)
78
+ end
79
+
80
+ it "includes a cameras array" do
81
+ expect(@configEye.cameras.class).to equal(Array)
82
+ end
83
+ end
84
+ end
85
+
86
+ describe "add camera" do
87
+ include FakeFS::SpecHelpers
88
+
89
+ before do
90
+ @configEye = ConfigEye.new
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)
96
+ end
97
+
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")
101
+ end
102
+ end
103
+
104
+ describe "remove camera" do
105
+ include FakeFS::SpecHelpers
106
+
107
+ before do
108
+ @configEye = ConfigEye.new
109
+ @configEye.add_camera("Reception", "http://user:pass@my.camera.org/live2.sdp")
110
+ end
111
+
112
+ context "when camera exists" do
113
+ it "removes camera from array" do
114
+ expect(@configEye.config.cameras.length).to equal(1)
115
+ @configEye.remove_camera("Reception")
116
+ expect(@configEye.config.cameras.length).to equal(0)
117
+ end
118
+
119
+ it "calls save" do
120
+ expect(@configEye).to receive(:save).once
121
+ @configEye.remove_camera("Reception")
122
+ end
123
+ end
124
+
125
+ context "when specified camera doesn't exist" do
126
+ it "keeps the camera array the same size" do
127
+ expect(@configEye.config.cameras.length).to equal(1)
128
+ @configEye.remove_camera("Kitchen")
129
+ expect(@configEye.config.cameras.length).to equal(1)
130
+ end
131
+
132
+ it "calls save" do
133
+ expect(ConfigEye).to receive(:save).never
134
+ @configEye.remove_camera("Kitchen")
135
+ end
136
+ end
137
+ end
138
+
139
+ describe "set_duration" do
140
+ include FakeFS::SpecHelpers
141
+
142
+ before do
143
+ @configEye = ConfigEye.new
144
+ end
145
+
146
+ context "when no duration has been set" do
147
+ it "returns the default of 600 seconds" do
148
+ expect(@configEye.config.duration).to equal(600)
149
+ end
150
+ end
151
+
152
+ context "when calling with -d 10" do
153
+ it "returns 10" do
154
+ @configEye.set_duration(10)
155
+ expect(@configEye.config.duration).to equal(10)
156
+ end
157
+
158
+ it "calls save" do
159
+ expect(@configEye).to receive(:save).once
160
+ @configEye.set_duration(10)
161
+ end
162
+ end
163
+ end
164
+
165
+ describe "set_path" do
166
+ include FakeFS::SpecHelpers
167
+
168
+ before do
169
+ @configEye = ConfigEye.new
170
+ end
171
+
172
+ context "when no path has been set" do
173
+ it "returns the default of ~/recordings" do
174
+ expect(@configEye.config.path == "~/recordings").to equal(true)
175
+ end
176
+ end
177
+
178
+ context "when calling with -p '/data/recordings'" do
179
+ it "returns '/data/recordings'" do
180
+ @configEye.set_path('/data/recordings')
181
+ expect(@configEye.config.path == '/data/recordings').to equal(true)
182
+ end
183
+
184
+ it "calls save" do
185
+ expect(@configEye).to receive(:save).once
186
+ @configEye.set_path('/data/recordings')
187
+ end
188
+ end
189
+ end
190
+
@@ -0,0 +1,77 @@
1
+ require ('spec_helper.rb')
2
+
3
+ describe "record" do
4
+ include FakeFS::SpecHelpers
5
+
6
+ before do
7
+ @configEye = ConfigEye.new
8
+ @configEye.add_camera("Reception", "http://user:pass@my.camera.org/live2.sdp")
9
+ @record = Record.new(@configEye)
10
+ end
11
+
12
+ describe "store_pids" do
13
+ before do
14
+ @file = "/tmp/electric_eye.pid"
15
+ ConfigEye.stub(:load).and_return(Construct.new({path: "~/recordings", cameras: [{name: "Reception"}]}))
16
+ end
17
+
18
+ it "creates a file #{@file}" do
19
+ @record.store_pids
20
+ expect(File.exist?(@file)).to equal(true)
21
+ end
22
+
23
+ it "should store the pids in the file" do
24
+ pids = [100, 101, 102]
25
+ @record.store_pids(pids)
26
+ expect(File.read(@file) == "100 101 102").to equal(true)
27
+ end
28
+ end
29
+
30
+ describe "record_path" 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 path with todays date" do
40
+ path = @record.path(@configEye.config.cameras.first)
41
+ expect(path).to include("~/recordings/Reception/20150630-1005-Reception.mjpeg")
42
+ end
43
+ end
44
+
45
+ describe "get_pids" do
46
+ it "returns pids" do
47
+ File.open("/tmp/electric_eye.pid", "w") do |file|
48
+ file.write("1 2 3")
49
+ end
50
+
51
+ expect(@record.get_pids == "1 2 3").to equal(true)
52
+ end
53
+ end
54
+
55
+ describe "stop_recordings" do
56
+ before do
57
+ File.open("/tmp/electric_eye.pid", "w") do |file|
58
+ file.write("10000 10001 10002")
59
+ end
60
+ end
61
+
62
+ it "calls kill" do
63
+ open4 = mock(Open4)
64
+ open4.stub!(:exitstatus).and_return(0)
65
+
66
+ Open4.should_receive(:popen4).with('kill -INT 10000 10001 10002').and_return(open4)
67
+
68
+ @record.stop_recordings("10000 10001 10002")
69
+ end
70
+
71
+ it "removes the old file" do
72
+ expect(File.exists?("/tmp/electric_eye.pid")).to equal(true)
73
+ @record.stop_recordings("10000 10001 10002")
74
+ expect(File.exists?("/tmp/electric_eye.pid")).to equal(false)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,12 @@
1
+ require 'construct'
2
+ require 'timecop'
3
+ require 'fakefs/spec_helpers'
4
+ require ('electric_eye.rb')
5
+
6
+ RSpec.configure do |config|
7
+ config.before(:each) do
8
+ # Set the directory
9
+ stub_const("ElectricEye::CONFIG_DIR", "/tmp/fakehome/.electric_eye")
10
+ stub_const("ElectricEye::CONFIG_FILE", "/tmp/fakehome/.electric_eye/config.yml")
11
+ end
12
+ end
metadata ADDED
@@ -0,0 +1,162 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: electric_eye
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Michael Pope
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-06-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rdoc
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: aruba
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: methadone
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.9.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.9.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.99'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.99'
97
+ description: A network video recorder for multiple IP cameras which uses VLC to record
98
+ and organises the files according to date and camera name.
99
+ email:
100
+ - michael@dtcorp.com.au
101
+ executables:
102
+ - electric_eye
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - ".gitignore"
107
+ - Gemfile
108
+ - Gemfile.lock
109
+ - LICENSE.txt
110
+ - README.md
111
+ - README.rdoc
112
+ - Rakefile
113
+ - bin/electric_eye
114
+ - electric_eye.gemspec
115
+ - features/config.feature
116
+ - features/electric_eye.feature
117
+ - features/record.feature
118
+ - features/step_definitions/config_steps.rb
119
+ - features/step_definitions/electric_eye_steps.rb
120
+ - features/support/env.rb
121
+ - lib/electric_eye.rb
122
+ - lib/electric_eye/config_eye.rb
123
+ - lib/electric_eye/record.rb
124
+ - lib/electric_eye/settings.rb
125
+ - lib/electric_eye/version.rb
126
+ - spec/config_spec.rb
127
+ - spec/electric_eye_spec.rb
128
+ - spec/spec_helper.rb
129
+ homepage: https://github.com/map7/electric_eye
130
+ licenses:
131
+ - MIT
132
+ metadata: {}
133
+ post_install_message:
134
+ rdoc_options: []
135
+ require_paths:
136
+ - lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ requirements: []
148
+ rubyforge_project:
149
+ rubygems_version: 2.2.2
150
+ signing_key:
151
+ specification_version: 4
152
+ summary: Network Video Recorder
153
+ test_files:
154
+ - features/config.feature
155
+ - features/electric_eye.feature
156
+ - features/record.feature
157
+ - features/step_definitions/config_steps.rb
158
+ - features/step_definitions/electric_eye_steps.rb
159
+ - features/support/env.rb
160
+ - spec/config_spec.rb
161
+ - spec/electric_eye_spec.rb
162
+ - spec/spec_helper.rb