heywatch 0.0.1

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.
@@ -0,0 +1,269 @@
1
+ module HeyWatch
2
+ # You can connect to the Hey!Watch service permanently
3
+ #
4
+ # Base::establish_connection! :login => "login", :password => "password"
5
+ #
6
+ # You can access all the resources and manipulate the objects like in ActiveRecord
7
+ #
8
+ # EncodedVideo.find(:all, :conditions => {:title => /bad video/i}).each do |v|
9
+ # v.destroy
10
+ # end
11
+ #
12
+ # Format.create :name => "my new format", :video_codec => "mpeg4", ...
13
+ #
14
+ # f = Format.new
15
+ # f.container = "avi"
16
+ # f.audio_codec = "mp3"
17
+ # ...
18
+ # f.save
19
+ #
20
+ # v = Video.find(15).update_attributes :title => "My edited title"
21
+ # puts v.title
22
+ #
23
+ # Format.find_by_name("iPod 4:3")
24
+ #
25
+ # Format.find_all_by_name /ipod/i
26
+ class Base
27
+ attr_reader :attributes, :errors
28
+
29
+ class << self
30
+ def session=(session) #:nodoc:
31
+ @session = session
32
+ end
33
+
34
+ def session #:nodoc:
35
+ (@session || Base.session) rescue nil
36
+ end
37
+
38
+ # Establish the connection for all your session
39
+ def establish_connection!(options={})
40
+ Base.session = Browser::login(options[:login], options[:password])
41
+ end
42
+
43
+ def disconnect!
44
+ Base.session = nil
45
+ end
46
+
47
+ def method_missing(m, *args) #:nodoc:
48
+ if m.to_s =~ /find_by_(.*)/
49
+ find(:first, :conditions => {$1.to_sym => args.first}) rescue nil
50
+ elsif m.to_s =~ /find_all_by_(.*)/
51
+ find :all, {:conditions => {$1.to_sym => args.first}}.merge(args[1]||{})
52
+ else
53
+ raise NoMethodError, "undefined method `#{m.to_s}' for #{self}"
54
+ end
55
+ end
56
+
57
+ def path #:nodoc:
58
+ return @path if @path
59
+ "/"+self.to_s.split("::").last.
60
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
61
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
62
+ tr("-", "_").
63
+ downcase
64
+ end
65
+
66
+ # if the path of the resource is not standard, you can set it manually.
67
+ #
68
+ # class Log < Base
69
+ # self.path = "/user_logs"
70
+ # end
71
+ def path=(path) #:nodoc:
72
+ @path = path
73
+ end
74
+
75
+ # Find objects
76
+ #
77
+ # Arguments:
78
+ #
79
+ # * <tt>ID</tt> ID of the object
80
+ # * <tt>:first</tt> retrieve the first result
81
+ # * <tt>:all</tt> retrieve all the results
82
+ #
83
+ # Options:
84
+ #
85
+ # * <tt>:conditions</tt> {:fieldname => "keyword"} or {:fieldname => /[0-9]+/}
86
+ # * <tt>:order</tt> to sort the result
87
+ # * <tt>:limit</tt> limit the number of result to return
88
+ # * <tt>:include</tt> fetch the object to include
89
+ #
90
+ # Format.find(:all, :conditions => {:name => /mobile/i})
91
+ # Format.find(:all, :conditions => {:width => '<320'})
92
+ # Job.find(:all, :include => "encoded_video", :order => "created_at DESC", :limit => 3)
93
+ # Video.find :first
94
+ # Download.find(5)
95
+ def find(*args)
96
+ scope, options = args
97
+ options ||= {}
98
+ case scope
99
+ when :all then find_every(options)
100
+ when :first then find_every(options).first
101
+ else find_single(scope, options)
102
+ end
103
+ end
104
+
105
+ private
106
+
107
+ def find_every(options) #:nodoc:
108
+ collection = []
109
+ res = HeyWatch::response(Browser::get(path, session).body)
110
+ return collection if res.empty?
111
+
112
+ [res[res.keys.first]].flatten.each {|object| collection << new(object)}
113
+ collection.find_with_options(options)
114
+ end
115
+
116
+ def find_single(arg, options) #:nodoc:
117
+ new(HeyWatch::response(Browser::get(path+"/"+arg.to_s, session).body)).include_heywatch_object(options[:include])
118
+ end
119
+
120
+ public
121
+
122
+ # Create the object
123
+ #
124
+ # Download.create :url => "http://host.com/video.avi"
125
+ def create(attributes={})
126
+ new(HeyWatch::response(Browser::post(path, attributes, session).body))
127
+ end
128
+
129
+ # Update the object passing its ID
130
+ #
131
+ # EncodedVideo.update 15, :title => "my title"
132
+ def update(id, attributes={})
133
+ Browser::put(path+"/"+id.to_s, attributes, session)
134
+ end
135
+
136
+ # Destroy the object passing its ID
137
+ #
138
+ # Format.destroy 12
139
+ def destroy(id)
140
+ Browser::delete(path+"/"+id.to_s, session)
141
+ end
142
+
143
+ # Destroy all the objects
144
+ #
145
+ # Video.destroy_all
146
+ def destroy_all
147
+ find(:all).each do |object|
148
+ object.destroy
149
+ end
150
+ end
151
+
152
+ # Count request
153
+ #
154
+ # Accept :conditions like in Base#find
155
+ def count(field=nil, options={})
156
+ find(:all, options).size
157
+ end
158
+ end
159
+
160
+ # Instanciate a new object
161
+ #
162
+ # Format.new :name => "test format", :sample_rate => 24000
163
+ def initialize(attributes={})
164
+ @attributes = attributes.underscore_keys!
165
+ @attributes.type_cast!
166
+ end
167
+
168
+ def include_heywatch_object(objects=nil) #:nodoc:
169
+ return self if objects.nil?
170
+ objects = objects.to_s.split(",") if objects.is_a?(String) or objects.is_a?(Symbol)
171
+ objects.each do |ob|
172
+ begin
173
+ self.instance_variable_set "@#{ob}", (self.send(ob) rescue nil)
174
+ self.instance_eval "attr_reader #{ob.to_sym}"
175
+ rescue
176
+ end
177
+ end
178
+ self
179
+ end
180
+
181
+ # Save the object.
182
+ #
183
+ # If the object doesn't exist, it will be created, otherwise updated.
184
+ # If an error occurred, @errors will be filled. This method doesn't raise.
185
+ def save
186
+ begin
187
+ save!
188
+ true
189
+ rescue => e
190
+ @errors = e.to_s
191
+ false
192
+ end
193
+ end
194
+
195
+ # Save the current object
196
+ #
197
+ # Raise if an error occurred. Return self.
198
+ def save!
199
+ if new_record?
200
+ self.class.create(@attributes)
201
+ else
202
+ update_attributes(@attributes)
203
+ end
204
+ self
205
+ end
206
+
207
+ def id #:nodoc:
208
+ @attributes["id"].to_i
209
+ end
210
+
211
+ # Update the object with the given attributes
212
+ #
213
+ # Video.find(10).update_attributes :title => "test title"
214
+ def update_attributes(attributes={})
215
+ if self.class.update(id, attributes)
216
+ reload
217
+ true
218
+ end
219
+ end
220
+
221
+ # Destroy the object
222
+ #
223
+ # Video.find(56).destroy
224
+ def destroy
225
+ self.class.destroy(id)
226
+ end
227
+
228
+ # Reload the current object
229
+ #
230
+ # j = Job.find(5400)
231
+ # j.reload
232
+ def reload
233
+ unless new_record?
234
+ @attributes = self.class.find(self.id).attributes
235
+ end
236
+ self
237
+ end
238
+
239
+ def new_record? #:nodoc:
240
+ @attributes["id"].nil?
241
+ end
242
+
243
+ def method_missing(m, *args) #:nodoc:
244
+ method_name = m.to_s
245
+ case method_name[-1..-1]
246
+ when '='
247
+ @attributes[method_name[0..-2]] = *args.first
248
+ when '?'
249
+ @attributes[method_name[0..-2]]
250
+ else
251
+ if instance_variables.include?("@#{m.to_s}")
252
+ eval("@#{m.to_s}")
253
+ else
254
+ if object_id = @attributes[m.to_s+"_id"] # belongs_to
255
+ klass = HeyWatch::const_get(m.to_s.camelize)
256
+ klass.session = self.class.session
257
+ klass.find(object_id)
258
+ else
259
+ @attributes[m.to_s] rescue nil
260
+ end
261
+ end
262
+ end
263
+ end
264
+ end
265
+
266
+ Resources.each do |k|
267
+ HeyWatch.module_eval(%{class #{k.to_s.camelize} < Base; end})
268
+ end
269
+ end
@@ -0,0 +1,98 @@
1
+ require "net/http"
2
+
3
+ module HeyWatch
4
+ # This class is used to request the Hey!Watch service.
5
+ #
6
+ # Browser::get '/video', session
7
+ # Browser::post '/download', {:url => 'http://host.com/video.avi'}, session
8
+ # Browser::put '/encoded_video/54000', {:title => 'new title'}, session
9
+ # Browser::delete '/encoded_video/54000', session
10
+ class Browser
11
+ class << self
12
+ # Raise when code response != 2xx
13
+ def raise_if_response_error(res)
14
+ code = res.response.code.to_i
15
+ message = res.response.message
16
+ return if code.to_s =~ /^2/
17
+
18
+ raise RequestError, HeyWatch::response(res.body).content if code == 400
19
+ raise NotAuthorized, message if code == 401
20
+ raise ResourceNotFound, message if code == 404
21
+ raise ServerError, message if code == 500
22
+ end
23
+
24
+ def header(session=nil) #:nodoc:
25
+ h = {}
26
+ h.merge!({"Cookie" => "_session_id=#{session};"}) if session
27
+ h.merge!({"User-Agent" => "Hey!Watch ruby API - #{VERSION::STRING}"})
28
+ end
29
+
30
+ # Login to Hey!Watch service. Return the session ID.
31
+ #
32
+ # You should not use it directly, use Auth#create instead
33
+ #
34
+ # Browser::login 'login', 'password'
35
+ def login(login, password) #:nodoc:
36
+ res = Browser::post("/auth/login", :login => login, :password => password)
37
+ return res["Set-cookie"].match(/_session_id=(.*);/i)[1].to_s
38
+ end
39
+
40
+ # GET on path
41
+ def get(path, session=nil)
42
+ path += ".#{OutFormat}" unless path.include? "."
43
+ res = Net::HTTP.start(Host) {|http| http.get(path, header(session))}
44
+ raise_if_response_error(res)
45
+ res
46
+ end
47
+
48
+ # POST on path and pass the query(Hash)
49
+ def post(path, query={}, session=nil)
50
+ res = Net::HTTP.start(Host) {|http| http.post(path, query.merge(:format => OutFormat).to_a.map{|x| x.join("=")}.join("&"), self.header(session))}
51
+ raise_if_response_error(res)
52
+ res
53
+ end
54
+
55
+ # PUT on path and pass the query(Hash)
56
+ def put(path, query={}, session=nil)
57
+ req = Net::HTTP::Put.new(path, header(session))
58
+ req.form_data = query.merge(:format => OutFormat)
59
+ res = Net::HTTP.new(Host).start {|http| http.request(req) }
60
+ raise_if_response_error(res)
61
+ true
62
+ end
63
+
64
+ # DELETE on path
65
+ def delete(path, session=nil)
66
+ res = Net::HTTP.start(Host) {|http| http.delete(path+"."+OutFormat, header(session))}
67
+ raise_if_response_error(res)
68
+ true
69
+ end
70
+
71
+ def post_multipart(path, attributes={}, session=nil) #:nodoc:
72
+ file = attributes.delete(:file)
73
+ params = [file_to_multipart("data", File.basename(file),"application/octet-stream", File.read(file))]
74
+ attributes.merge("format" => OutFormat).each_pair{|k,v| params << text_to_multipart(k.to_s, v.to_s)}
75
+
76
+ boundary = '349832898984244898448024464570528145'
77
+ query = params.collect {|p| '--' + boundary + "\r\n" + p}.join('') + "--" + boundary + "--\r\n"
78
+ res = Net::HTTP.start(Host) {|http| http.post(path, query, header(session).merge("Content-Type" => "multipart/form-data; boundary=" + boundary))}
79
+ raise_if_response_error(res)
80
+ res
81
+ end
82
+
83
+ def text_to_multipart(key,value) #:nodoc:
84
+ return "Content-Disposition: form-data; name=\"#{CGI::escape(key.to_s)}\"\r\n" +
85
+ "\r\n" +
86
+ "#{value}\r\n"
87
+ end
88
+
89
+ def file_to_multipart(key,filename,mime_type,content) #:nodoc:
90
+ return "Content-Disposition: form-data; name=\"#{CGI::escape(key.to_s)}\"; filename=\"#{filename}\"\r\n" +
91
+ "Content-Transfer-Encoding: binary\r\n" +
92
+ "Content-Type: #{mime_type}\r\n" +
93
+ "\r\n" +
94
+ "#{content}\r\n"
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,39 @@
1
+ module HeyWatch
2
+ class VideoNotFound < RuntimeError; end
3
+ class MultipleVideoFound < RuntimeError; end
4
+
5
+ class Discover < Base
6
+ # Discover video from the given URL
7
+ #
8
+ # If you pass a block, you will follow the process until you get the raw video.
9
+ # Return the new video if pass a block.
10
+ #
11
+ # If more than 1 video is found, raise an exception.
12
+ #
13
+ # Discover.create(:url => 'http://host.com/', :download => true) {|percent, total_size, received| ...}
14
+ def self.create(attributes={}, &block)
15
+ discover = super
16
+ return discover unless attributes[:download] and block_given?
17
+
18
+ while (discover.status rescue "working") == "working"
19
+ discover.reload rescue nil
20
+ end
21
+
22
+ case discover.status
23
+ when "ok"
24
+ if discover.results.result.is_a?(String)
25
+ while discover.download_id.nil?
26
+ sleep 1
27
+ discover.reload
28
+ end
29
+ video = Download.progress(discover.download, &block)
30
+ return video
31
+ else
32
+ raise MultipleVideoFound, "You have to choose the video you want to transfer"
33
+ end
34
+ when "error"
35
+ raise VideoNotFound, "No video found in this website"
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,30 @@
1
+ module HeyWatch
2
+ class DownloadFailed < RuntimeError; end
3
+
4
+ class Download < Base
5
+ # Download file from the given URL
6
+ #
7
+ # If you pass a block, you get the progress. Return the new video.
8
+ #
9
+ # Download.create(:url => 'http://host.com/file.avi') {|percent, total_size, received| ...}
10
+ def self.create(attributes={}, &block)
11
+ download = super
12
+ return download unless block_given?
13
+ self.progress(download, &block)
14
+ end
15
+
16
+ def self.progress(download, &block)
17
+ while download.status == "downloading"
18
+ download.reload
19
+ yield download.progress.percent, download.length, download.progress.current_length
20
+ sleep 1
21
+ end
22
+ case download.status
23
+ when "finished"
24
+ return download.video
25
+ when "error"
26
+ raise DownloadFailed, download.error_msg
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,49 @@
1
+ module HeyWatch
2
+ class EncodedVideo < Base
3
+ def url
4
+ res = Browser::get("#{self.class.path}/#{self.id}.bin", self.class.session)
5
+ HeyWatch::sanitize_url(res.get_fields("Location").first)
6
+ end
7
+
8
+ # Generate a thumbnail of the encoded video
9
+ #
10
+ # options:
11
+ # * <tt>start:</tt> offset in second
12
+ # * <tt>width</tt>
13
+ # * <tt>height</tt>
14
+ #
15
+ # All these options are optionals. Return binary data.
16
+ #
17
+ # EncodedVideo.find(:first).thumbnail :start => 15, :width => 320, :height => 240
18
+ def thumbnail(options={})
19
+ param = options.to_a.map{|v| v.join("=")}.join("&")
20
+ res = Browser::get("#{self.class.path}/#{self.id}.jpg#{'?'+param unless param.empty?}", self.class.session)
21
+ case res
22
+ when Net::HTTPRedirection
23
+ Net::HTTP.get_response(URI(HeyWatch::sanitize_url(res["location"]))).body
24
+ else
25
+ res.body
26
+ end
27
+ end
28
+
29
+ # Download the encoded video
30
+ #
31
+ # The path is . by default. You can pass a block to have progression. Return the full path of the video.
32
+ #
33
+ # EncodedVideo.find(:first).download('/tmp') {|progress| puts progress.to_s + '%'}
34
+ def download(path=".")
35
+ uri = URI(url)
36
+ size = self.specs["size"] * 1024
37
+ path = File.join(path, self.filename)
38
+ file = File.open(path, "wb")
39
+ Net::HTTP.start(uri.host) do |http|
40
+ http.get(uri.path) do |data|
41
+ file.write data
42
+ yield((File.size(path) / size.to_f) * 100) if block_given?
43
+ end
44
+ end
45
+ file.close
46
+ return path
47
+ end
48
+ end
49
+ end