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