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.
- data/VERSION +1 -1
- data/airvideo.gemspec +43 -0
- data/lib/airvideo.rb +150 -84
- metadata +5 -4
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.8
|
data/airvideo.gemspec
ADDED
@@ -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
|
+
|
data/lib/airvideo.rb
CHANGED
@@ -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(
|
90
|
-
raise NoMethodError, "Please pass a VideoObject" if not
|
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(
|
97
|
+
request("livePlaybackService","initLivePlayback",[conversion_settings(videoobj)])['result']['contentURL']
|
94
98
|
else
|
95
|
-
request("playbackService","initPlayback",[
|
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(
|
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" =>
|
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
|
-
|
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
|
190
|
-
|
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 =
|
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
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
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
|
-
|
289
|
-
|
290
|
-
puts "
|
291
|
-
|
292
|
-
|
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
|
-
|
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}#{[(
|
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#{[(
|
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#{[(
|
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:
|
4
|
+
hash: 15
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
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-
|
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
|