dropcam 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ .DS_Store
2
+ *.flv
3
+
4
+ *.gem
5
+ *.rbc
6
+ .bundle
7
+ .config
8
+ .yardoc
9
+ Gemfile.lock
10
+ InstalledFiles
11
+ _yardoc
12
+ coverage
13
+ doc/
14
+ lib/bundler/man
15
+ pkg
16
+ rdoc
17
+ spec/reports
18
+ test/tmp
19
+ test/version_tmp
20
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'awesome_print'
4
+ # Specify your gem's dependencies in dropcam.gemspec
5
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Nolan Brown
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # Dropcam
2
+
3
+ RubyGem to access Dropcam account and Camera including direct live stream access
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'dropcam'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install dropcam
18
+
19
+ ## Usage
20
+
21
+ require 'dropcam'
22
+
23
+ dropcam = Dropcam::Dropcam.new("<USERNAME>","<PASSWORD>")
24
+ camera = dropcam.cameras.first
25
+
26
+ # returns jpg image data of the latest frame captured
27
+ screenshot = camera.current_image
28
+
29
+ # write data to disk
30
+ File.open("#{camera.title}.jpg", 'w') {|f| f.write(screenshot) }
31
+
32
+ # access and modify settings
33
+ # this disables the watermark on your camera stream
34
+ settings = camera.settings
35
+ settings["watermark.enabled"].set(false)
36
+
37
+
38
+ ## Live Stream
39
+
40
+ **Streaming isn't directly integrated currently and it's up to you to find a player. Some of the players available:**
41
+
42
+ - VLC (RTSP/RTMP)
43
+ - RTMPDump (RTMP)
44
+ - openRTSP (RTSP)
45
+
46
+ The easiest way to record the live camera stream is with RTMPDump. Install via homebrew:
47
+
48
+ `$ brew install rtmpdump`
49
+
50
+ To save a live stream:
51
+
52
+ require 'dropcam'
53
+ dropcam = Dropcam::Dropcam.new("<USERNAME>","<PASSWORD>")
54
+ camera = dropcam.cameras.first
55
+
56
+ # record the live stream for 30 seconds
57
+ camera.stream.save_live("#{camera.title}.flv", 30)
58
+
59
+ # to get access information to use a third party application
60
+ # RTMP/Flash Streaming
61
+ camera.stream.rtmp_details
62
+
63
+ # RTSP Streaming
64
+ camera.stream.rtsp_details
65
+
66
+
67
+ Currently stream resolution is limited to 400x240.
68
+
69
+
70
+
71
+ ## NOTES: ##
72
+
73
+ The Dropcam API is unofficial and unreleased. This code can break at anytime as Dropcam changes/updates their service.
74
+
75
+ This gem has only been tested on Mac OS 10.8 running Ruby 1.9.3
76
+
77
+ ## Contributing
78
+
79
+ 1. Fork it
80
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
81
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
82
+ 4. Push to the branch (`git push origin my-new-feature`)
83
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/dropcam.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'dropcam/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "dropcam"
8
+ gem.version = Dropcam::VERSION
9
+ gem.authors = ["Nolan Brown"]
10
+ gem.email = ["nolanbrown@gmail.com"]
11
+ gem.description = %q{Access Dropcam account and cameras}
12
+ gem.summary = gem.description
13
+ gem.homepage = "https://github.com/nolanbrown/dropcam"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+
21
+ end
data/example/basics.rb ADDED
@@ -0,0 +1,15 @@
1
+ #dropcam = Dropcam::Session.new("username","password")
2
+ lib = File.expand_path('../../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require 'dropcam'
6
+ require 'awesome_print'
7
+ dropcam = Dropcam::Dropcam.new(ENV["DROPCAM_USERNAME"],ENV["DROPCAM_PASSWORD"])
8
+ camera = dropcam.cameras.first
9
+ ap camera.uuid
10
+ ap camera.session_token
11
+ ap camera.rtsp_url
12
+ ap camera.rtmpdump_stream_command
13
+
14
+ settings = camera.settings
15
+ #ap settings["watermark.enabled"].set(false)
data/example/camera.rb ADDED
@@ -0,0 +1,16 @@
1
+ lib = File.expand_path('../../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ require 'dropcam'
5
+ require 'awesome_print'
6
+
7
+ dropcam = Dropcam::Dropcam.new(ENV["DROPCAM_USERNAME"],ENV["DROPCAM_PASSWORD"])
8
+
9
+ all_cameras = dropcam.cameras
10
+ all_cameras.each { |aCamera|
11
+ puts "\n------------------------------------\n"
12
+ aCamera.instance_variables.each{|variable|
13
+ print "#{variable} : "; $stdout.flush
14
+ ap aCamera.instance_variable_get(variable)
15
+ }
16
+ }
@@ -0,0 +1,19 @@
1
+ lib = File.expand_path('../../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ require 'dropcam'
5
+ require 'awesome_print'
6
+ dropcam = Dropcam::Dropcam.new(ENV["DROPCAM_USERNAME"],ENV["DROPCAM_PASSWORD"])
7
+ camera = dropcam.cameras.first
8
+ camera.notification_devices.each{|notification|
9
+ # print all variable values
10
+ notification.instance_variables.each{|variable|
11
+ print "#{variable} : "; $stdout.flush
12
+ ap notification.instance_variable_get(variable)
13
+ }
14
+
15
+ # enable or disable notification
16
+ # notification.set_enabled(true)
17
+ puts "\n------------------------------------\n\n"
18
+
19
+ }
@@ -0,0 +1,16 @@
1
+ lib = File.expand_path('../../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ require 'dropcam'
5
+ require 'awesome_print'
6
+ dropcam = Dropcam::Dropcam.new(ENV["DROPCAM_USERNAME"],ENV["DROPCAM_PASSWORD"])
7
+ camera = dropcam.cameras.first
8
+ settings = camera.settings
9
+
10
+ ap settings
11
+
12
+ setting_name = "watermark.enabled"
13
+ setting_value = settings[setting_name].value
14
+ puts "Changing #{setting_name} from #{setting_value} to #{!setting_value}"
15
+ settings[setting_name].set(!setting_value)
16
+ puts "Changing #{setting_name} is now #{settings[setting_name].value}"
data/example/stream.rb ADDED
@@ -0,0 +1,16 @@
1
+ lib = File.expand_path('../../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ require 'dropcam'
5
+
6
+ dropcam = Dropcam::Dropcam.new(ENV["DROPCAM_USERNAME"],ENV["DROPCAM_PASSWORD"])
7
+ camera = dropcam.cameras.first
8
+
9
+ puts "\nRTSP URI"
10
+ puts camera.stream.rtsp_uri.to_s
11
+
12
+ puts "\nRTMPDump command"
13
+ puts camera.stream.rtmpdump
14
+
15
+ puts "\nSave Live Stream as FLV"
16
+ puts camera.stream.save_live("#{camera.title}.flv",30)
@@ -0,0 +1,126 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'json'
4
+
5
+ module Net
6
+ class HTTPResponse
7
+ def success?
8
+ self.code.to_i == 200
9
+ end
10
+ def not_found?
11
+ self.code.to_i == 404
12
+ end
13
+ def error?
14
+ self.code.to_i == 400
15
+ end
16
+ def not_authorized?
17
+ self.code.to_i == 403
18
+ end
19
+ end
20
+ end
21
+
22
+ module Dropcam
23
+ class Base
24
+ attr_accessor :session_token, :cookies
25
+
26
+
27
+ ::NEXUS_API_BASE = "https://nexusapi.dropcam.com/"
28
+
29
+ ::NEXUS_GET_IMAGE_PATH = "get_image" # uuid and width
30
+ ::NEXUS_GET_AVAILABLE_PATH = "get_available" # start_time and uuid
31
+ ::NEXUS_GET_CUEPOINT_PATH = "get_cuepoint" # start_time_uuid
32
+ ::NEXUS_GET_EVENT_CLIP_PATH = "get_event_clip" # start_time_uuid
33
+ ::NEXUS_GET_REVERSE_PAGINATED_CUEPOINTS_PATH = "get_reverse_paginated_cuepoint"
34
+ ::API_BASE = "https://www.dropcam.com"
35
+ ::API_PATH = "/api/v1"
36
+
37
+
38
+ ::CAMERA_HTML_SETTINGS_BASE = "/cameras/settings/" # /uuid
39
+
40
+ ::USERS_LOGIN = "#{API_PATH}/login.login"
41
+ ::CAMERAS_UPDATE = "#{API_PATH}/cameras.update" # uuid, is_public
42
+ ::CAMERAS_GET_BY_PUBLIC_TOKEN = "#{API_PATH}/cameras.get_by_public_token"
43
+ ::CAMERAS_GET = "#{API_PATH}/cameras.get" # uuid
44
+ ::CAMERAS_GET_VISIBLE = "#{API_PATH}/cameras.get_visible"
45
+ ::CAMERAS_GET_PUBLIC = "#{API_PATH}/cameras.get_demo"
46
+
47
+ ::DROPCAMS_GET_PROPERTIES = "#{API_PATH}/dropcams.get_properties" # uuid
48
+ ::DROPCAMS_SET_PROPERTY = "#{API_PATH}/dropcams.set_property" # POST: uuid, key, value
49
+ ::CAMERA_NOTIFICATION_UPDATE = "#{API_PATH}/camera_notifications.update"
50
+ ::CAMERA_ADD_EMAIL_NOTIFICATION = "#{API_PATH}/users.add_email_notification_target"
51
+ ::CAMERA_DELETE_NOTIFICATION = "#{API_PATH}/users.delete_notification_target"
52
+ ::CAMERA_FIND_NOTIFICATIONS = "#{API_PATH}/camera_notifications.find_by_camera"
53
+
54
+ ::SUBSCRIPTIONS_LIST = "#{API_PATH}/subscriptions.list" # camera_uuid
55
+ ::SUBSCRIPTIONS_DELETE = "#{API_PATH}/subscriptions.delete"
56
+ ::SUBSCRIPTIONS_CREATE_PUBLIC = "#{API_PATH}/subscriptions.create_public"
57
+ ::USERS_GET_SESSION_TOKEN = "#{API_PATH}/users.get_session_token"
58
+ ::USERS_GET_CURRENT = "#{API_PATH}/users.get_current"
59
+
60
+ ::CLIP_GET_ALL = "#{API_PATH}/videos.get_owned"
61
+ ::CLIP_CREATE = "#{API_PATH}/videos.request" # POST: start_date (ex. 1357598395), title, length (in seconds), uuid, description
62
+ ::CLIP_DELETE = "#{API_PATH}/videos.delete" # DELETE: id = clip_id
63
+
64
+ ## videos.get # id = clip_id
65
+ ## videos.download # id = clip_id
66
+ ## videos.play # id = clip_idß
67
+ def post(path, parameters, cookies, use_nexus=false)
68
+
69
+ http = _dropcam_http(use_nexus)
70
+
71
+ request = Net::HTTP::Post.new(path)
72
+ request.set_form_data(parameters)
73
+
74
+ request.add_field("Cookie",cookies.join('; ')) if cookies
75
+
76
+ response = http.request(request)
77
+
78
+ return response
79
+ end
80
+
81
+ def get(path, parameters,cookies, use_nexus=false)
82
+ http = _dropcam_http(use_nexus)
83
+
84
+ query_path = "#{path}"
85
+ query_path = "#{path}?#{URI.encode_www_form(parameters)}" if parameters.length > 0
86
+ request = Net::HTTP::Get.new(query_path)
87
+
88
+ request.add_field("Cookie",cookies.join('; ')) if cookies
89
+
90
+ response = http.request(request)
91
+ return response
92
+ end
93
+
94
+ def delete(path, parameters,cookies, use_nexus=false)
95
+ http = _dropcam_http(use_nexus)
96
+
97
+ query_path = "#{path}"
98
+ query_path = "#{path}?#{URI.encode_www_form(parameters)}" if parameters.length > 0
99
+ request = Net::HTTP::Delete.new(query_path)
100
+
101
+ request.add_field("Cookie",cookies.join('; ')) if cookies
102
+
103
+ response = http.request(request)
104
+ return response
105
+ end
106
+
107
+ protected
108
+
109
+ def _dropcam_http(use_nexus)
110
+ base = API_BASE
111
+ base = NEXUS_API_BASE if use_nexus
112
+ uri = URI.parse(base)
113
+ http = Net::HTTP.new(uri.host, uri.port)
114
+ http.use_ssl = true
115
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
116
+ #http.set_debug_output($stdout)
117
+
118
+ return http
119
+ end
120
+
121
+
122
+ end
123
+
124
+
125
+
126
+ end
@@ -0,0 +1,263 @@
1
+ require 'hpricot'
2
+
3
+ require_relative 'base'
4
+ require_relative 'error'
5
+ require_relative 'notification'
6
+ require_relative 'setting'
7
+ require_relative 'cuepoint'
8
+ require_relative 'clip'
9
+ require_relative 'stream'
10
+
11
+ module Dropcam
12
+ class Camera < Base
13
+
14
+ attr_reader :uuid, :notification_devices, :download_host, :download_server_live, :is_streaming, :title, :public_token
15
+ attr_reader :description, :timezone_utc_offset, :timezone, :is_connected, :is_online, :is_public, :hours_of_recording_max
16
+ attr_reader :type, :id, :owner_id
17
+ attr_accessor :settings
18
+
19
+
20
+ def initialize(uuid, properties={})
21
+ @uuid = uuid
22
+ @settings = {}
23
+ self.properties = properties
24
+ end
25
+
26
+ def properties=(properties)
27
+ properties.each{|key, value|
28
+ instance_variable_set("@#{key}", value)
29
+ }
30
+ end
31
+
32
+
33
+ def get_image(width=1200, timestamp=nil)
34
+ params = {"uuid"=>@uuid, "width" => width}
35
+ params["time"] = timestamp if timestamp
36
+
37
+ response = get(::IMAGE_PATH, params, @cookies, true)
38
+ if response.success?
39
+ return response.body
40
+ elsif response.not_authorized?
41
+ raise AuthorizationError
42
+ else
43
+ raise CameraNotFoundError
44
+ end
45
+ end
46
+
47
+ def current_image(width=1200)
48
+ return get_image(width)
49
+ end
50
+
51
+ def clips
52
+ response = get(::CLIP_GET_ALL, {}, @cookies)
53
+ if response.success?
54
+ return response.body ## returns a zip
55
+ clips = []
56
+ all_clips = JSON.parse(response.body)["items"]
57
+ for clip in all_clips
58
+ c = Clip.new(self, clip)
59
+ if c.camera_id == self.id
60
+ clips.push c
61
+ end
62
+ end
63
+ return clips
64
+ elsif response.not_authorized?
65
+ raise AuthorizationError
66
+ else
67
+ raise CameraNotFoundError
68
+ end
69
+ end
70
+
71
+
72
+ def create_clip(length, start_date, title, description)
73
+ params = {"uuid"=>@uuid, "length" => length, "start_date" => start_date, "title" => title, "description" => description}
74
+ response = post(::VIDEOS_REQUEST, params, @cookies)
75
+ if response.success?
76
+ clip_info = JSON.parse(response.body)["items"][0]
77
+ return Clip.new(self, clip_info)
78
+ elsif response.not_authorized?
79
+ raise AuthorizationError
80
+ else
81
+ raise CameraNotFoundError
82
+ end
83
+ end
84
+
85
+
86
+ def get_event_clip_image_archive(cuepoint_id, number_of_frames, width)
87
+ params = {"uuid"=>@uuid, "width" => width, "cuepoint_id" => cuepoint_id, "num_frames" => number_of_frames, "format" => "TAR_JPG"}
88
+ response = get(::NEXUS_GET_EVENT_CLIP_PATH, params, @cookies, true)
89
+ if response.success?
90
+ return response.body ## returns a zip
91
+ elsif response.not_authorized?
92
+ raise AuthorizationError
93
+ else
94
+ raise CameraNotFoundError
95
+ end
96
+ end
97
+
98
+ def get_event_clip_video(cuepoint_id, number_of_frames, width)
99
+ params = {"uuid"=>@uuid, "width" => width, "cuepoint_id" => cuepoint_id, "num_frames" => number_of_frames, "format" => "h264"}
100
+ response = get(::NEXUS_GET_EVENT_CLIP_PATH, params, @cookies, true)
101
+ if response.success?
102
+ return response.body ## returns a zip
103
+ elsif response.not_authorized?
104
+ raise AuthorizationError
105
+ else
106
+ raise CameraNotFoundError
107
+ end
108
+ end
109
+
110
+ def get_all_cuepoints(limit=2500)
111
+ params = {"uuid"=>@uuid, "max_results"=>limit}
112
+ response = get(::NEXUS_GET_REVERSE_PAGINATED_CUEPOINTS_PATH, params, @cookies, true)
113
+ if response.success?
114
+ cuepoints = []
115
+ all_cuepoints = JSON.parse(response.body)
116
+ for cuepoint in all_cuepoints
117
+ cuepoints.push Cuepoint.new(cuepoint)
118
+ end
119
+
120
+ return cuepoints
121
+ elsif response.not_authorized?
122
+ raise AuthorizationError
123
+ else
124
+ raise CameraNotFoundError
125
+ end
126
+ end
127
+
128
+ def get_cuepoint(start_time)
129
+ params = {"uuid"=>@uuid, "start_time" => start_time}
130
+ response = get(::NEXUS_GET_CUEPOINT_PATH, params, @cookies, true)
131
+ if response.success?
132
+ return Cuepoint.new(JSON.parse(response.body)[0])
133
+ elsif response.not_authorized?
134
+ raise AuthorizationError
135
+ else
136
+ raise CameraNotFoundError
137
+ end
138
+ end
139
+
140
+ def update_info
141
+ response = get(::CAMERAS_GET, {"id"=>@uuid}, @cookies)
142
+ if response.success?
143
+ self.properties = JSON.parse(response.body)["items"][0]
144
+ elsif response.not_authorized?
145
+ raise AuthorizationError
146
+ else
147
+ raise CameraNotFoundError
148
+ end
149
+ end
150
+
151
+
152
+
153
+ def public=(is_public)
154
+ response = post(::CAMERAS_UPDATE, {"uuid"=>@uuid, "is_public"=>is_public, "accepted_public_terms_at" => "true"}, @cookies)
155
+ if response.success?
156
+ self.properties = JSON.parse(response.body)["items"][0]
157
+ elsif response.not_authorized?
158
+ raise AuthorizationError
159
+ else
160
+ raise CameraNotFoundError
161
+ end
162
+ end
163
+
164
+ def public?
165
+ @is_public
166
+ end
167
+
168
+ def set_public_token(token)
169
+ response = post(::CAMERAS_UPDATE, {"uuid"=>@uuid, "token"=>token}, @cookies)
170
+ if response.success?
171
+ self.properties = JSON.parse(response.body)["items"][0]
172
+ return true
173
+ elsif response.not_authorized?
174
+ raise AuthorizationError
175
+ else
176
+ raise CameraNotFoundError
177
+ end
178
+ end
179
+
180
+ def settings=(new_settings)
181
+ @settings = {}
182
+ new_settings.each{|key,value|
183
+ @settings[key] = Setting.new(key, value, self)
184
+ }
185
+ @settings
186
+ end
187
+
188
+ def settings(force=false)
189
+ return @settings unless force == true or @settings.length == 0 # key these cached
190
+
191
+ response = get(::DROPCAMS_GET_PROPERTIES, {"uuid"=>@uuid}, @cookies)
192
+ if response.success?
193
+ self.settings = JSON.parse(response.body)["items"][0]
194
+ return @settings
195
+ elsif response.not_authorized?
196
+ raise AuthorizationError
197
+ else
198
+ raise CameraNotFoundError
199
+ end
200
+ end
201
+
202
+ def notification_devices
203
+
204
+ response = get(::CAMERA_FIND_NOTIFICATIONS, { "id" => @uuid }, @cookies)
205
+ if response.success?
206
+ notifications = []
207
+
208
+ all_notifications = JSON.parse(response.body)["items"]
209
+ all_notifications.each{|note|
210
+ notifications.push Notification.new(self, note["target"])
211
+ }
212
+
213
+ notifications
214
+ elsif response.not_authorized?
215
+ raise AuthorizationError
216
+ else
217
+ raise CameraNotFoundError
218
+ end
219
+ end
220
+
221
+ ## API for Notifications doesn't include email notifcations
222
+ ## The code below parses an HTML Partial to get all notifcation values
223
+
224
+ def all_notification_devices
225
+ request_path = ::CAMERA_HTML_SETTINGS_BASE + @uuid
226
+ response = get(request_path, {}, @cookies)
227
+ if response.success?
228
+ raw_html = response.body
229
+ doc = Hpricot.parse(raw_html)
230
+
231
+ notifications = []
232
+ doc.search("//div[@class='notification_target']").each { |notification_target|
233
+ puts notification_target
234
+ data_id = notification_target.get_attribute("data-id")
235
+ puts data_id
236
+ name = notification_target.at("div/span").inner_text
237
+
238
+ input = notification_target.at("div/input")
239
+
240
+ attributes = input.attributes.to_hash
241
+ data_type = attributes["data-type"]
242
+ data_value = attributes["data-value"]
243
+ checked = attributes.has_key?("checked")
244
+
245
+
246
+ notifications.push(note)
247
+ }
248
+ return notifications
249
+ elsif response.not_authorized?
250
+ raise AuthorizationError
251
+ else
252
+ raise CameraNotFoundError
253
+ end
254
+ end
255
+
256
+
257
+
258
+ def stream
259
+ Stream.new self
260
+ end
261
+
262
+ end
263
+ end
@@ -0,0 +1,52 @@
1
+ module Dropcam
2
+ class Clip
3
+ attr_reader :id, :public_link, :description, :title, :is_error, :start_time, :server, :camera_id, :generated_time
4
+ attr_reader :filename, :length_in_seconds
5
+
6
+ attr_accessor :properties
7
+ def initialize(camera, properties = nil)
8
+ @camera = camera
9
+ self.properties = properties
10
+ end
11
+
12
+ def properties=(properties)
13
+ properties.each{|key, value|
14
+ instance_variable_set("@#{key}", value)
15
+ }
16
+ @properties = properties
17
+ end
18
+
19
+ def direct_link
20
+ return "https://#{@server}/#{@filename}"
21
+ end
22
+ def direct_screenshot_link
23
+ return "https://#{@server}/s3#{File.basename(@filename, File.extname(@filename))}.jpg"
24
+ end
25
+
26
+ def set_title(title)
27
+ return false unless @id
28
+ response = post(::CLIP_DELETE, { "id" => @id, "title" => title }, @cookies)
29
+ if response.success?
30
+ return true
31
+ elsif response.not_authorized?
32
+ raise AuthorizationError
33
+ else
34
+ raise CameraNotFoundError
35
+ end
36
+
37
+ end
38
+
39
+ def delete
40
+ return false unless @id
41
+ response = post(::CLIP_DELETE, { "id" => @id }, @cookies)
42
+ if response.success?
43
+ return true
44
+ elsif response.not_authorized?
45
+ raise AuthorizationError
46
+ else
47
+ raise CameraNotFoundError
48
+ end
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,11 @@
1
+ module Dropcam
2
+ class Cuepoint
3
+ attr_reader :id, :note, :type, :time
4
+ def initialize(details)
5
+ @id = details["id"]
6
+ @note = details["note"]
7
+ @type = details["type"]
8
+ @time = details["time"]
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,28 @@
1
+ module Dropcam
2
+ class AuthenticationError < StandardError
3
+ def initialize(msg = "Invalid Credentials")
4
+ super(msg)
5
+ end
6
+ end
7
+ class AuthorizationError < StandardError
8
+ def initialize(msg = "Not Authorized")
9
+ super(msg)
10
+ end
11
+ end
12
+ class CameraNotFoundError < StandardError
13
+ def initialize(msg = "Camera Not Found")
14
+ super(msg)
15
+ end
16
+ end
17
+ class UnkownError < StandardError
18
+ def initialize(msg = "Unkown Error")
19
+ super(msg)
20
+ end
21
+ end
22
+ class RequestError < StandardError
23
+ def initialize(msg = "Request Error")
24
+ super(msg)
25
+ end
26
+ end
27
+
28
+ end
@@ -0,0 +1,66 @@
1
+ module Dropcam
2
+ class Notification < Base
3
+
4
+ attr_accessor :name, :type, :value, :is_enabled, :id
5
+
6
+ def initialize(camera, properties={})
7
+ @camera = camera
8
+ @name = properties["name"]
9
+ @type = properties["type"]
10
+ @value = properties["value"]
11
+ @id = properties["id"]
12
+ @is_enabled = properties["enabled"]
13
+ end
14
+
15
+ def find(name)
16
+ note = @camera.notification_devices.select{|note|
17
+ if note.name == name
18
+ puts "#{note.name} == #{name}"
19
+ return note
20
+ end
21
+ }
22
+ note
23
+ end
24
+
25
+ def create(email)
26
+ # {"status": 400, "items": [], "status_description": "bad-request", "status_detail": "This notification target already exists"}
27
+ response = post(::CAMERA_ADD_EMAIL_NOTIFICATION, {"email"=>email}, @camera.cookies)
28
+ if response.success?
29
+ return Notification.new(@camera, JSON.parse(response.body)["items"][0])
30
+ elsif response.error?
31
+ raise UnkownError, JSON.parse(response.body)["status_detail"]
32
+ elsif response.not_authorized?
33
+ raise AuthorizationError
34
+ else
35
+ raise CameraNotFoundError
36
+ end
37
+ end
38
+
39
+ def set(enable)
40
+ # email or gcm or apn
41
+ params = {"id"=>@camera.uuid, "is_enabled"=>enable, "device_token" => @value}
42
+ puts params
43
+ response = post(::CAMERA_NOTIFICATION_UPDATE, params, @camera.cookies)
44
+ if response.success?
45
+ return true
46
+ elsif response.not_authorized?
47
+ raise AuthorizationError
48
+ else
49
+ raise CameraNotFoundError
50
+ end
51
+ end
52
+
53
+ def delete(notifcation_id=nil)
54
+ notifcation_id = @id unless notifcation_id
55
+ response = post(::CAMERA_DELETE_NOTIFICATION, {"id"=>notifcation_id}, @camera.cookies)
56
+ if response.success?
57
+ return true
58
+ elsif response.not_authorized?
59
+ raise AuthorizationError
60
+ else
61
+ raise CameraNotFoundError
62
+ end
63
+ end
64
+
65
+ end
66
+ end
@@ -0,0 +1,47 @@
1
+ require_relative 'base'
2
+ module Dropcam
3
+ class Session < Base
4
+
5
+ attr_accessor :session_token, :cookies
6
+ def initialize(username, password)
7
+ @username = username
8
+ @password = password
9
+ end
10
+
11
+ def authenticate
12
+
13
+ params = {"username" => @username, "password" => @password}
14
+ response = post(::USERS_LOGIN, params, nil)
15
+ all_cookies = response.get_fields('set-cookie') # only cookies are set on valid credentials
16
+
17
+ ## for some reason, dropcam responds with 200 on invalid credentials
18
+ if response.success? and all_cookies
19
+
20
+ cookies = []
21
+ all_cookies.each { | cookie |
22
+ cookies.push(cookie.split('; ')[0])
23
+ }
24
+
25
+ @cookies = cookies
26
+ @session_token = _session_token # this value is embedded in the cookie but leaving this as is incase the API changes
27
+
28
+ else
29
+ raise AuthenticationError, "Invalid Credentials"
30
+ end
31
+ end
32
+
33
+
34
+
35
+ protected
36
+ def _session_token
37
+ response = get(::USERS_GET_SESSION_TOKEN, {}, @cookies)
38
+ if response.success?
39
+ response_json = JSON.parse(response.body)
40
+ token = response_json["items"][0]
41
+ return token
42
+ end
43
+ return nil
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,38 @@
1
+ module Dropcam
2
+ class Setting < Base
3
+ #
4
+ attr_accessor :name
5
+ def initialize(name, value, camera)
6
+ @camera = camera
7
+ @name = name
8
+ @current_value = value
9
+ end
10
+
11
+ def value
12
+ return false if @current_value == 'false'
13
+ return true if @current_value == 'true'
14
+
15
+ @current_value
16
+ end
17
+
18
+ def to_s
19
+ "<Dropcam::Setting:#{object_id} @name=#{@name} @value=#{@current_value}>"
20
+ end
21
+
22
+ def set(value)
23
+ response = post(::DROPCAMS_SET_PROPERTY, {"uuid"=>@camera.uuid, "key" => @name, "value" => value}, @camera.cookies)
24
+ if response.success?
25
+ @current_value = value
26
+ @camera.settings = JSON.parse(response.body)["items"][0]
27
+ true
28
+ elsif response.error?
29
+ raise UnkownError, JSON.parse(response.body)["status_detail"]
30
+ elsif response.not_authorized?
31
+ raise AuthorizationError
32
+ else
33
+ raise CameraNotFoundError
34
+ end
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,96 @@
1
+ require 'Open3'
2
+ require 'timeout'
3
+ module Dropcam
4
+ class Stream
5
+ DEFAULT_RTSP_PORT = "554"
6
+ BUFFER_SIZE = 1024
7
+
8
+ attr_reader :camera
9
+ def initialize(camera)
10
+ @camera = camera
11
+ end
12
+ def rtsp_details
13
+ {
14
+ :protocol => "rtsp",
15
+ :user => "user",
16
+ :password => @camera.session_token,
17
+ :base => URI.parse(@camera.download_host).scheme,
18
+ :path => @camera.uuid,
19
+ :port => DEFAULT_RTSP_PORT
20
+ }
21
+ end
22
+ def rtsp_uri
23
+ URI.parse "rtsp://user:#{@camera.session_token}@#{URI.parse(@camera.download_host).scheme}:#{DEFAULT_RTSP_PORT}/#{@camera.uuid}"
24
+ end
25
+
26
+ def rtmp_details
27
+ {
28
+ :app => "nexus",
29
+ :host => "stream.dropcam.com",
30
+ :playpath => @camera.uuid,
31
+ :variables => { "S:" => @camera.session_token }
32
+ }
33
+ end
34
+
35
+ def rtmpdump
36
+ stream_command = "rtmpdump --live --app nexus --host stream.dropcam.com --playpath " + @camera.uuid
37
+ stream_command += " --conn S:" + @camera.session_token
38
+ return stream_command
39
+ end
40
+
41
+ def save_live(filename, duration=30)
42
+ raise StandardError, "RTMPDump is not found in your PATH" unless system("which -s rtmpdump")
43
+ # we add 10 seconds because it take about that amount of time to get the stream up and running with rtmpdump
44
+ run_with_timeout("#{self.rtmpdump} --quiet --flv #{filename}", duration+10, 1)
45
+ end
46
+
47
+ private
48
+
49
+ # Runs a specified shell command in a separate thread.
50
+ # If it exceeds the given timeout in seconds, kills it.
51
+ # Returns any output produced by the command (stdout or stderr) as a String.
52
+ # Uses Kernel.select to wait up to the tick length (in seconds) between
53
+ # checks on the command's status
54
+ #
55
+ # If you've got a cleaner way of doing this, I'd be interested to see it.
56
+ # If you think you can do it with Ruby's Timeout module, think again.
57
+
58
+ ## VIA https://gist.github.com/1032297
59
+ def run_with_timeout(command, timeout, tick)
60
+ output = ''
61
+ begin
62
+ # Start task in another thread, which spawns a process
63
+ stdin, stderrout, thread = Open3.popen2e(command)
64
+ # Get the pid of the spawned process
65
+ pid = thread[:pid]
66
+ start = Time.now
67
+
68
+ while (Time.now - start) < timeout and thread.alive?
69
+ # Wait up to `tick` seconds for output/error data
70
+ Kernel.select([stderrout], nil, nil, tick)
71
+ # Try to read the data
72
+ begin
73
+ #output << stderrout.read_nonblock(BUFFER_SIZE)
74
+ rescue IO::WaitReadable
75
+ # A read would block, so loop around for another select
76
+ rescue EOFError
77
+ # Command has completed, not really an error...
78
+ break
79
+ end
80
+ end
81
+ # Give Ruby time to clean up the other thread
82
+ sleep 1
83
+
84
+ if thread.alive?
85
+ # We need to kill the process, because killing the thread leaves
86
+ # the process alive but detached, annoyingly enough.
87
+ Process.kill("TERM", pid)
88
+ end
89
+ ensure
90
+ stdin.close if stdin
91
+ stderrout.close if stderrout
92
+ end
93
+ return output
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,3 @@
1
+ module Dropcam
2
+ VERSION = "0.0.1"
3
+ end
data/lib/dropcam.rb ADDED
@@ -0,0 +1,65 @@
1
+ require "dropcam/version"
2
+ require "dropcam/base"
3
+ require "dropcam/camera"
4
+ require "dropcam/session"
5
+
6
+ module Dropcam
7
+ class Dropcam < Base
8
+ attr_reader :session
9
+ def initialize(username, password)
10
+ @session = Session.new(username, password)
11
+ @session.authenticate
12
+ ##
13
+ end
14
+ def camera(uuid)
15
+ c = Camera.new(uuid)
16
+ c.cookies = @session.cookies
17
+ c.session_token = @session.session_token
18
+ c.properties = c.info
19
+ c
20
+ end
21
+
22
+ def get_public_camera(token)
23
+ response = get(::CAMERAS_GET_BY_PUBLIC_TOKEN, {"token"=>token, "return_deleted"=>true}, @session.cookies)
24
+ if response.success?
25
+ return response.body
26
+ elsif response.not_authorized?
27
+ raise AuthorizationError
28
+ else
29
+ raise CameraNotFoundError
30
+ end
31
+ end
32
+
33
+ def public_cameras
34
+ response = get(::CAMERAS_GET_PUBLIC, {}, @session.cookies)
35
+ cameras = []
36
+ if response.success?
37
+ response_json = JSON.parse(response.body)
38
+ owned = response_json["items"][0]["owned"]
39
+ owned.each{|camera|
40
+ c = Camera.new(camera["uuid"], camera)
41
+ c.cookies = @session.cookies
42
+ c.session_token = @session.session_token
43
+ cameras.push(c)
44
+ }
45
+ end
46
+ return cameras
47
+ end
48
+
49
+ def cameras
50
+ response = get(::CAMERAS_GET_VISIBLE, {"group_cameras" => true}, @session.cookies)
51
+ cameras = []
52
+ if response.success?
53
+ response_json = JSON.parse(response.body)
54
+ owned = response_json["items"][0]["owned"]
55
+ owned.each{|camera|
56
+ c = Camera.new(camera["uuid"], camera)
57
+ c.cookies = @session.cookies
58
+ c.session_token = @session.session_token
59
+ cameras.push(c)
60
+ }
61
+ end
62
+ return cameras
63
+ end
64
+ end
65
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dropcam
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Nolan Brown
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-12 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Access Dropcam account and cameras
15
+ email:
16
+ - nolanbrown@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - Gemfile
23
+ - LICENSE.txt
24
+ - README.md
25
+ - Rakefile
26
+ - dropcam.gemspec
27
+ - example/basics.rb
28
+ - example/camera.rb
29
+ - example/notifications.rb
30
+ - example/settings.rb
31
+ - example/stream.rb
32
+ - lib/dropcam.rb
33
+ - lib/dropcam/base.rb
34
+ - lib/dropcam/camera.rb
35
+ - lib/dropcam/clip.rb
36
+ - lib/dropcam/cuepoint.rb
37
+ - lib/dropcam/error.rb
38
+ - lib/dropcam/notification.rb
39
+ - lib/dropcam/session.rb
40
+ - lib/dropcam/setting.rb
41
+ - lib/dropcam/stream.rb
42
+ - lib/dropcam/version.rb
43
+ homepage: https://github.com/nolanbrown/dropcam
44
+ licenses: []
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubyforge_project:
63
+ rubygems_version: 1.8.24
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: Access Dropcam account and cameras
67
+ test_files: []