airvideo 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/VERSION +1 -1
  2. data/airvideo.gemspec +43 -0
  3. data/lib/airvideo.rb +150 -84
  4. metadata +5 -4
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.7
1
+ 0.0.8
@@ -0,0 +1,43 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{airvideo}
8
+ s.version = "0.0.8"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["JP Hastings-Spital"]
12
+ s.date = %q{2010-07-09}
13
+ s.description = %q{Communicate with an AirVideo server, even through a proxy: Retrieve the streaming URLs for your videos.}
14
+ s.email = %q{jphastings@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ "LICENSE",
21
+ "README.rdoc",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "airvideo.gemspec",
25
+ "lib/airvideo.rb"
26
+ ]
27
+ s.homepage = %q{http://github.com/jphastings/AirVideo}
28
+ s.rdoc_options = ["--charset=UTF-8"]
29
+ s.require_paths = ["lib"]
30
+ s.rubygems_version = %q{1.3.7}
31
+ s.summary = %q{Allows communication with an AirVideo server}
32
+
33
+ if s.respond_to? :specification_version then
34
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
35
+ s.specification_version = 3
36
+
37
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
38
+ else
39
+ end
40
+ else
41
+ end
42
+ end
43
+
@@ -4,6 +4,9 @@ require 'digest/sha1'
4
4
 
5
5
  # == TODO
6
6
  # * Potential bug: can you cd into a file?
7
+ # * Caching for details?
8
+ # - Active-record?
9
+ # - In-memory by default
7
10
 
8
11
  module AirVideo
9
12
  # The AirVideo Client. At this stage it can emulate the iPhone app in all major features.
@@ -63,7 +66,7 @@ module AirVideo
63
66
  when "air.video.DiskRootFolder", "air.video.ITunesRootFolder","air.video.Folder"
64
67
  FolderObject.new(self,hash['name'],hash['itemId'])
65
68
  when "air.video.VideoItem","air.video.ITunesVideoItem"
66
- VideoObject.new(self,hash['name'],hash['itemId'],hash['detail'] || {})
69
+ VideoObject.new(self,hash['name'],hash['itemId'],hash['detail'] || nil)
67
70
  else
68
71
  raise NotImplementedError, "Unknown: #{hash.name}"
69
72
  end
@@ -86,19 +89,41 @@ module AirVideo
86
89
  end
87
90
 
88
91
  # Returns the streaming video URL for the given AirVideo::VideoObject.
89
- def get_url(fileobj,liveconvert = false)
90
- raise NoMethodError, "Please pass a VideoObject" if not fileobj.is_a? VideoObject
92
+ def get_url(videoobj,liveconvert = false)
93
+ raise NoMethodError, "Please pass a VideoObject" if not videoobj.is_a? VideoObject
94
+
91
95
  begin
92
96
  if liveconvert
93
- request("livePlaybackService","initLivePlayback",[conversion_settings(fileobj)])['result']['contentURL']
97
+ request("livePlaybackService","initLivePlayback",[conversion_settings(videoobj)])['result']['contentURL']
94
98
  else
95
- request("playbackService","initPlayback",[fileobj.location[1..-1]])['result']['contentURL']
99
+ request("playbackService","initPlayback",[videoobj.location[1..-1]])['result']['contentURL']
96
100
  end
97
101
  rescue NoMethodError
98
102
  raise RuntimeError, "This video does not exist"
99
103
  end
100
104
  end
101
105
 
106
+ def get_details(items)
107
+ items = [items] if !items.is_a? Array
108
+ items.collect! do |item|
109
+ case item
110
+ when VideoObject
111
+ item.location[1..-1]
112
+ when String
113
+ item
114
+ end
115
+ end.compact!
116
+
117
+ request("browseService","getItemsWithDetail",[items])['result'][0]
118
+ end
119
+
120
+ # Searches the current directory for items matching the given regular expression
121
+ def search(re_string,dir=".")
122
+ # Get the directory we're searching
123
+ dir = File.expand_path((dir.is_a? FolderObject) ? dir.location : dir,@current_dir)
124
+ ls(dir).select {|item| item.name =~ %r{#{re_string}}}
125
+ end
126
+
102
127
  # Returns the path to the current directory
103
128
  def pwd
104
129
  @current_dir
@@ -109,31 +134,25 @@ module AirVideo
109
134
  "<AirVideo Connection: #{@endpoint.host}:#{@endpoint.port}>"
110
135
  end
111
136
 
112
- private
113
- def conversion_settings(fileobj)
114
- video = {}
115
- fileobj.details['streams'].each do |stream|
116
- if stream['streamType'] == 0
117
- video = stream
118
- break
119
- end
120
- end
137
+ #private
138
+ def conversion_settings(videoobj)
139
+ video = videoobj.video_stream
121
140
  scaling = [video['width'] / @max_width, video['height'] / @max_height]
122
141
  if scaling.max > 1.0
123
- video['width'] = video['width'] / scaling.max
124
- video['height'] = video['height'] / scaling.max
142
+ video['width'] = (video['width'] / scaling.max).to_i
143
+ video['height'] = (video['height'] / scaling.max).to_i
125
144
  end
126
145
 
127
146
  # TODO: fill these in correctly
128
147
  AvMap::Hash.new("air.video.ConversionRequest", {
129
- "itemId" => fileobj.location[1..-1],
130
- "audioStream"=>1,
148
+ "itemId" => videoobj.location[1..-1],
149
+ "audioStream"=>1,#videoobj.audio_stream['index'],
131
150
  "allowedBitrates"=> AirVideo::AvMap::BitrateList["512", "768", "1536", "1024", "384", "1280", "256"],
132
151
  "audioBoost"=>0.0,
133
152
  "cropRight"=>0,
134
153
  "cropLeft"=>0,
135
154
  "resolutionWidth"=>video['width'],
136
- "videoStream"=>0,
155
+ "videoStream"=>0,#video['index'],
137
156
  "cropBottom"=>0,
138
157
  "cropTop"=>0,
139
158
  "quality"=>0.699999988079071,
@@ -168,6 +187,7 @@ module AirVideo
168
187
  # Has helper functions like #cd which will move the current directory of the originating AirVideo::Client instance to this folder.
169
188
  class FolderObject
170
189
  attr_reader :name, :location
190
+ Helpers = [:cd, :ls, :search]
171
191
 
172
192
  # Shouldn't be used outside of the AirVideo module
173
193
  def initialize(server,name,location) # :nodoc:
@@ -176,18 +196,20 @@ module AirVideo
176
196
  @location = "/"+location
177
197
  end
178
198
 
179
- # A helper method that will move the current directory of the AirVideo::Client instance to this FolderObject.
199
+ def inspect
200
+ "<Folder: #{(name.nil?) ? "/Unknown/" : name}>"
201
+ end
202
+
180
203
  def cd
181
204
  @server.cd(self)
182
205
  end
183
206
 
184
- # A helper method that will list the contents of this directory.
185
207
  def ls
186
208
  @server.ls(self)
187
209
  end
188
-
189
- def inspect
190
- "<Folder: #{(name.nil?) ? "/Unknown/" : name}>"
210
+
211
+ def search(re_string)
212
+ @server.search(re_string,self)
191
213
  end
192
214
  end
193
215
 
@@ -195,14 +217,59 @@ module AirVideo
195
217
  #
196
218
  # Has helper functions like #url and #live_url which give the video playback URLs of this video, as produced by the originating AirVideo::Client instance's AirVideo::Client.get_url method.
197
219
  class VideoObject
198
- attr_reader :name, :location, :details
220
+ attr_reader :name, :location, :details, :streams
221
+ attr_accessor :audio_stream, :video_stream
199
222
 
200
223
  # Shouldn't be used outside of the AirVideo module
201
- def initialize(server,name,location,detail = {}) # :nodoc:
224
+ def initialize(server,name,location,detail = nil) # :nodoc:
202
225
  @server = server
203
226
  @name = name
204
227
  @location = "/"+location
205
- @details = detail
228
+ @details = detail # nil implies the details haven't been loaded
229
+ # These are the defaults, all videos *should* have these.
230
+ @video_stream = {'index' => 1}
231
+ @audio_stream = {'index' => 0}
232
+ details if !@details.nil?
233
+ end
234
+
235
+ def details
236
+ #@details = @server.get_details(self)
237
+
238
+ if !@details.nil?
239
+ @streams = {'video' => [],'audio' => [],'unknown' => []}
240
+ @details['streams'].each do |stream|
241
+ @streams[case
242
+ when 0
243
+ "video"
244
+ when 1
245
+ "audio"
246
+ else
247
+ "unknown"
248
+ end
249
+ ]
250
+ end
251
+ @audio_stream = @details['streams'][0]
252
+ @video_stream = @details['streams'][0]
253
+ end
254
+ @details
255
+ end
256
+
257
+ # Checks to see if this video has that audio stream index, then changes internal settings so that live conversions will use this stream.
258
+ def audio_stream=(stream_hash_or_index)
259
+ index = stream_hash_or_index['index'] rescue stream_hash_or_index
260
+ get_details if @details.nil?
261
+ raise RuntimeError, "Couldn't retrieve video details" if @details.nil?
262
+ raise RuntimeError, "No such audio stream" if @streams['audio'].collect{|stream| stream['index']}.include? index
263
+ @audio_stream = index
264
+ end
265
+
266
+ # Checks to see if this video has that video stream index, then changes internal settings so that live conversions will use this stream.
267
+ def video_stream=(stream_hash_or_index)
268
+ index = stream_hash_or_index['index'] rescue stream_hash_or_index
269
+ get_details if @details.nil?
270
+ raise RuntimeError, "Couldn't retrieve video details" if @details.nil?
271
+ raise RuntimeError, "No such audio stream" if @streams['video'].collect{|stream| stream['index']}.include? index
272
+ @video_stream = index
206
273
  end
207
274
 
208
275
  # Gives the URL for direct video playback
@@ -237,59 +304,52 @@ module AirVideo
237
304
 
238
305
  private
239
306
  def self.read_identifier(depth = 0)
240
- begin
241
- ident = @input.read(1)
242
- case ident
243
- when "o" # Hash
244
- unknown = @input.read(4).unpack("N")[0]
245
- hash = Hash.new(@input.read(@input.read(4).unpack("N")[0]), {})
246
- unknown = @input.read(4).unpack("N")[0]
247
- num_els = @input.read(4).unpack("N")[0]
248
- #$stderr.puts "#{" "*depth}Hash: #{arr_name} // #{num_els} times"
249
- 1.upto(num_els) do |iter|
250
- hash_item = @input.read(@input.read(4).unpack("N")[0])
251
- #$stderr.puts "#{" "*depth}-#{arr_name}:#{iter} - #{hash_item}"
252
- hash[hash_item] = self.read_identifier(depth + 1)
253
- end
254
- hash
255
- when "s" # String
256
- #$stderr.puts "#{" "*depth}String"
257
- unknown = @input.read(4).unpack("N")[0]
258
- @input.read(@input.read(4).unpack("N")[0])
259
- when "i" # Integer?
260
- #$stderr.puts "#{" "*depth}Integer"
261
- @input.read(4).unpack("N")[0]
262
- when "a","e" # Array
263
- #$stderr.puts "#{" "*depth}Array"
264
- unknown = @input.read(4).unpack("N")[0]
265
- num_els = @input.read(4).unpack("N")[0]
266
- arr = []
267
- 1.upto(num_els) do |iter|
268
- arr.push self.read_identifier(depth + 1)
269
- end
270
- arr
271
- when "n" # nil
272
- #$stderr.puts "#{" "*depth}Nil"
273
- nil
274
- when "f" # Float?
275
- @input.read(8).unpack('G')[0]
276
- when "l" # Big Integer
277
- @input.read(8).unpack("NN").reverse.inject([0,0]){|res,el| [res[0] + (el << (32 * res[1])),res[1] + 1]}[0]
278
- when "r" # Integer?
279
- #$stderr.puts "#{" "*depth}R?"
280
- @input.read(4).unpack("N")[0]
281
- when "x" # Binary Data
282
- #$stderr.puts "#{" "*depth}R?"
283
- unknown = @input.read(4).unpack("N")[0]
284
- BinaryData.new @input.read(@input.read(4).unpack("N")[0])
285
- else
286
- raise NotImplementedError, "I don't know what to do with the '#{ident}' identifier"
307
+ ident = @input.read(1)
308
+ case ident
309
+ when "o" # Hash
310
+ unknown = @input.read(4).unpack("N")[0]
311
+ hash = Hash.new(@input.read(@input.read(4).unpack("N")[0]), {})
312
+ unknown = @input.read(4).unpack("N")[0]
313
+ num_els = @input.read(4).unpack("N")[0]
314
+ #$stderr.puts "#{" "*depth}Hash: #{arr_name} // #{num_els} times"
315
+ 1.upto(num_els) do |iter|
316
+ hash_item = @input.read(@input.read(4).unpack("N")[0])
317
+ #$stderr.puts "#{" "*depth}-#{arr_name}:#{iter} - #{hash_item}"
318
+ hash[hash_item] = self.read_identifier(depth + 1)
287
319
  end
288
- rescue Exception => e
289
- puts e.message
290
- puts "Error : #{@input.tell}"
291
- p e.backtrace
292
- Process.exit
320
+ hash
321
+ when "s" # String
322
+ #$stderr.puts "#{" "*depth}String"
323
+ unknown = @input.read(4).unpack("N")[0]
324
+ @input.read(@input.read(4).unpack("N")[0])
325
+ when "i" # Integer?
326
+ #$stderr.puts "#{" "*depth}Integer"
327
+ @input.read(4).unpack("N")[0]
328
+ when "a","e" # Array
329
+ #$stderr.puts "#{" "*depth}Array"
330
+ unknown = @input.read(4).unpack("N")[0]
331
+ num_els = @input.read(4).unpack("N")[0]
332
+ arr = []
333
+ 1.upto(num_els) do |iter|
334
+ arr.push self.read_identifier(depth + 1)
335
+ end
336
+ arr
337
+ when "n" # nil
338
+ #$stderr.puts "#{" "*depth}Nil"
339
+ nil
340
+ when "f" # Float?
341
+ @input.read(8).unpack('G')[0]
342
+ when "l" # Big Integer
343
+ @input.read(8).unpack("NN").reverse.inject([0,0]){|res,el| [res[0] + (el << (32 * res[1])),res[1] + 1]}[0]
344
+ when "r" # Integer?
345
+ #$stderr.puts "#{" "*depth}R?"
346
+ @input.read(4).unpack("N")[0]
347
+ when "x" # Binary Data
348
+ #$stderr.puts "#{" "*depth}R?"
349
+ unknown = @input.read(4).unpack("N")[0]
350
+ BinaryData.new @input.read(@input.read(4).unpack("N")[0])
351
+ else
352
+ raise NotImplementedError, "I don't know what to do with the '#{ident}' identifier"
293
353
  end
294
354
  end
295
355
 
@@ -327,9 +387,13 @@ module AirVideo
327
387
  f.write @data
328
388
  end
329
389
  end
390
+
391
+ def length
392
+ @data.length
393
+ end
330
394
 
331
395
  def inspect
332
- "<Data: #{data.length} bytes>"
396
+ "<Data: #{@data.length} bytes>"
333
397
  end
334
398
  end
335
399
 
@@ -344,13 +408,13 @@ end
344
408
  class Object
345
409
  # Will convert an object into an AirVideo map, if the object and it's contents are supported
346
410
  def to_avmap(reset_counter = true)
347
- $to_avmap_counter = 0 if reset_counter
411
+ @@to_avmap_counter = 0 if reset_counter
348
412
 
349
413
  case self
350
414
  when Array
351
415
  letter = (self.is_a? AirVideo::AvMap::BitrateList) ? "e" : "a"
352
416
  self.push nil if self.length == 0 # Must have at least one entry in the hash, I think
353
- "#{letter}#{[($to_avmap_counter += 1) - 1].pack("N")}#{[self.length].pack("N")}"+self.collect do |item|
417
+ "#{letter}#{[(@@to_avmap_counter += 1) - 1].pack("N")}#{[self.length].pack("N")}"+self.collect do |item|
354
418
  item.to_avmap(false)
355
419
  end.join
356
420
  when AirVideo::AvMap::Hash
@@ -360,11 +424,13 @@ class Object
360
424
  else
361
425
  1
362
426
  end
363
- "o#{[($to_avmap_counter += 1) - 1].pack("N")}#{[self.name.length].pack("N")}#{self.name}#{[version].pack("N")}#{[self.length].pack("N")}"+self.to_a.collect do |key,val|
427
+ "o#{[(@@to_avmap_counter += 1) - 1].pack("N")}#{[self.name.length].pack("N")}#{self.name}#{[version].pack("N")}#{[self.length].pack("N")}"+self.to_a.collect do |key,val|
364
428
  "#{[key.length].pack("N")}#{key}"+val.to_avmap(false)
365
429
  end.join
430
+ when AirVideo::AvMap::BinaryData
431
+ "x#{[(@@to_avmap_counter += 1) - 1].pack("N")}#{[self.length].pack("N")}#{self.data}"
366
432
  when String
367
- "s#{[($to_avmap_counter += 1) - 1].pack("N")}#{[self.length].pack("N")}#{self}"
433
+ "s#{[(@@to_avmap_counter += 1) - 1].pack("N")}#{[self.length].pack("N")}#{self}"
368
434
  when NilClass
369
435
  "n"
370
436
  when Integer
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: airvideo
3
3
  version: !ruby/object:Gem::Version
4
- hash: 17
4
+ hash: 15
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 7
10
- version: 0.0.7
9
+ - 8
10
+ version: 0.0.8
11
11
  platform: ruby
12
12
  authors:
13
13
  - JP Hastings-Spital
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-05-26 00:00:00 +01:00
18
+ date: 2010-07-09 00:00:00 +01:00
19
19
  default_executable:
20
20
  dependencies: []
21
21
 
@@ -33,6 +33,7 @@ files:
33
33
  - README.rdoc
34
34
  - Rakefile
35
35
  - VERSION
36
+ - airvideo.gemspec
36
37
  - lib/airvideo.rb
37
38
  has_rdoc: true
38
39
  homepage: http://github.com/jphastings/AirVideo