airvideo 0.0.7 → 0.0.8

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.
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