heywatch 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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