bbc_redux 0.4.0.pre
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/AUTHORS +8 -0
- data/COPYING +212 -0
- data/Gemfile +3 -0
- data/README.md +174 -0
- data/Rakefile +47 -0
- data/bbc_redux.gemspec +61 -0
- data/lib/bbc/redux/asset.rb +119 -0
- data/lib/bbc/redux/channel.rb +45 -0
- data/lib/bbc/redux/channel_category.rb +36 -0
- data/lib/bbc/redux/client.rb +302 -0
- data/lib/bbc/redux/end_points.rb +78 -0
- data/lib/bbc/redux/key.rb +73 -0
- data/lib/bbc/redux/media_url.rb +123 -0
- data/lib/bbc/redux/search_results.rb +82 -0
- data/lib/bbc/redux/serializers.rb +101 -0
- data/lib/bbc/redux/user.rb +107 -0
- data/lib/bbc/redux/version.rb +8 -0
- data/lib/bbc/redux.rb +10 -0
- data/spec/bbc/redux/asset_spec.rb +45 -0
- data/spec/bbc/redux/channel_category_spec.rb +23 -0
- data/spec/bbc/redux/channel_spec.rb +24 -0
- data/spec/bbc/redux/client_spec.rb +279 -0
- data/spec/bbc/redux/key_spec.rb +66 -0
- data/spec/bbc/redux/media_url_spec.rb +151 -0
- data/spec/bbc/redux/search_results_spec.rb +69 -0
- data/spec/bbc/redux/user_spec.rb +37 -0
- data/spec/fixtures/asset.json +24 -0
- data/spec/fixtures/channel_categories.json +28 -0
- data/spec/fixtures/channels.json +51 -0
- data/spec/fixtures/search_results.json +172 -0
- data/spec/fixtures/user.json +22 -0
- data/spec/integration/bbc_redux_spec.rb +78 -0
- data/spec/spec_helper.rb +24 -0
- metadata +227 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'virtus'
|
2
|
+
|
3
|
+
module BBC
|
4
|
+
module Redux
|
5
|
+
|
6
|
+
# Redux API Channel Category Object
|
7
|
+
#
|
8
|
+
# @example Properties of the channel category object
|
9
|
+
#
|
10
|
+
# category = redux_client.channel_categories.first
|
11
|
+
#
|
12
|
+
# category.description #=> String
|
13
|
+
# category.id #=> Integer
|
14
|
+
# category.priority #=> Integer
|
15
|
+
#
|
16
|
+
# @author Matt Haynes <matt.haynes@bbc.co.uk>
|
17
|
+
class ChannelCategory
|
18
|
+
|
19
|
+
include Virtus.value_object
|
20
|
+
|
21
|
+
# @!attribute [r] description
|
22
|
+
# @return [String] category's description, e.g. 'BBC TV'
|
23
|
+
attribute :description, String
|
24
|
+
|
25
|
+
# @!attribute [r] id
|
26
|
+
# @return [Integer] category's id
|
27
|
+
attribute :id, Integer
|
28
|
+
|
29
|
+
# @!attribute [r] priority
|
30
|
+
# @return [String] category's priority, a hint for display in views
|
31
|
+
attribute :priority, Integer
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,302 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'json'
|
3
|
+
require 'typhoeus'
|
4
|
+
|
5
|
+
module BBC
|
6
|
+
module Redux
|
7
|
+
|
8
|
+
# Redux API Client
|
9
|
+
#
|
10
|
+
# @example Initialize client with either username and password or token
|
11
|
+
#
|
12
|
+
# client = BBC::Redux::Client.new({
|
13
|
+
# :username => 'username',
|
14
|
+
# :password => 'password',
|
15
|
+
# })
|
16
|
+
#
|
17
|
+
# client = BBC::Redux::Client.new :token => 'some-token'
|
18
|
+
#
|
19
|
+
# @example Using the client to retrieve data
|
20
|
+
#
|
21
|
+
# client.asset('5966413090093319525') #=> BBC::Redux::Asset
|
22
|
+
# client.channel_categories #=> Array<BBC::Redux::ChannelCategory>
|
23
|
+
# client.channels #=> Array<BBC::Redux::Channel>
|
24
|
+
# client.schedule(Date.today) #=> Array<BBC::Redux::Asset>
|
25
|
+
# client.search(:name => 'Pingu') #=> BBC::Redux::SearchResults
|
26
|
+
# client.user #=> BBC::Redux::User
|
27
|
+
#
|
28
|
+
# @example Call logout once finished to destroy your session
|
29
|
+
#
|
30
|
+
# client.logout
|
31
|
+
#
|
32
|
+
# @author Matt Haynes <matt.haynes@bbc.co.uk>
|
33
|
+
class Client
|
34
|
+
|
35
|
+
# Raised when backend HTTP API returns a 403, indicates you are either
|
36
|
+
# trying to access some content that is unavailable to you, or your token
|
37
|
+
# and session has expired.
|
38
|
+
class ForbiddenException < Exception; end
|
39
|
+
|
40
|
+
# Raised when backend HTTP API returns a 4XX or 5XX status other than
|
41
|
+
# 403, indicates an error within the HTTP API or bug in this library
|
42
|
+
class HttpException < Exception; end
|
43
|
+
|
44
|
+
# Raised when backend HTTP API returns a body that does not parse as json
|
45
|
+
class JsonParseException < Exception; end
|
46
|
+
|
47
|
+
# @!attribute [r] http
|
48
|
+
# @return [Object] http client, by default this is Typhoeus
|
49
|
+
attr_reader :http
|
50
|
+
|
51
|
+
# @!attribute [r] token
|
52
|
+
# @return [String] token for current session
|
53
|
+
attr_reader :token
|
54
|
+
|
55
|
+
# @!attribute [r] host
|
56
|
+
# @return [String] API HTTP host
|
57
|
+
attr_reader :host
|
58
|
+
|
59
|
+
# Client must be initialized with either a username and password
|
60
|
+
# combination or a token
|
61
|
+
#
|
62
|
+
# @param [Hash] options the options to create client with
|
63
|
+
# @option options [String] :username username of a redux account
|
64
|
+
# @option options [String] :password password of a redux account
|
65
|
+
# @option options [String] :token token for an existing redux session
|
66
|
+
# @option options [String] :host (https://i.bbcredux.com) api host
|
67
|
+
# @option options [Object] :http (Typhoeus) The http client, can be
|
68
|
+
# overidden but expects method .post to return an object looking like
|
69
|
+
# Typhoeus::Response (code, headers, body, etc)
|
70
|
+
def initialize(options = {})
|
71
|
+
@host = options[:host] || 'https://i.bbcredux.com'
|
72
|
+
@http = options[:http] || Typhoeus
|
73
|
+
@token = options[:token] || begin
|
74
|
+
username = options[:username]
|
75
|
+
password = options[:password]
|
76
|
+
|
77
|
+
if username && password
|
78
|
+
data = data_for(:login, {
|
79
|
+
:username => username, :password => password
|
80
|
+
})
|
81
|
+
|
82
|
+
@token = data.fetch('token')
|
83
|
+
else
|
84
|
+
raise 'Supply either :token or :username and :password options'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Fetch asset object
|
90
|
+
# @param [String] identifier either disk reference or uuid
|
91
|
+
# @see BBC::Redux::Asset
|
92
|
+
# @return [BBC::Redux::Asset, nil] the asset
|
93
|
+
def asset(identifier)
|
94
|
+
if identifier =~ /^\d{19}$/
|
95
|
+
params = { :reference => identifier }
|
96
|
+
else
|
97
|
+
params = { :uuid => identifier }
|
98
|
+
end
|
99
|
+
|
100
|
+
build :asset, :using => data_for(:asset, params)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Fetch available channels for this session
|
104
|
+
# @see BBC::Redux::Channel
|
105
|
+
# @return [Array<BBC::Redux::Channel>] array of channels
|
106
|
+
def channels
|
107
|
+
build :channels, :using => data_for(:channels)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Fetch available channel categories for this session
|
111
|
+
# @see BBC::Redux::ChannelCategory
|
112
|
+
# @return [Array<BBC::Redux::ChannelCategory>] array of channel categories
|
113
|
+
def channel_categories
|
114
|
+
build :channel_categories, :using => data_for(:channel_categories)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Logout of redux, invalidates your token. After calling this you cannot
|
118
|
+
# make any further requests with this client
|
119
|
+
# @return [nil]
|
120
|
+
def logout
|
121
|
+
data_for(:logout)
|
122
|
+
return nil
|
123
|
+
end
|
124
|
+
|
125
|
+
# Return all programmes for the schedule date, everything from 0600 for
|
126
|
+
# 24 hours afterwards. May make multiple requests to backend to retreive
|
127
|
+
# all the data
|
128
|
+
#
|
129
|
+
# @param [Date,DateTime,Time] date query this schedule date
|
130
|
+
# @param [String,Array<String>,Channel,Array<Channel>,nil] channel
|
131
|
+
# optionally limit schedule query to one or an array of channels
|
132
|
+
# @return [Array<BBC::Redux::Asset>] the list of assets broadcast on date
|
133
|
+
def schedule(date, channels = nil)
|
134
|
+
query = {
|
135
|
+
:date => date,
|
136
|
+
:channels => channels,
|
137
|
+
:offset => 0,
|
138
|
+
:limit => 100,
|
139
|
+
}
|
140
|
+
|
141
|
+
results = search(query)
|
142
|
+
|
143
|
+
assets = [ ]
|
144
|
+
|
145
|
+
while true do
|
146
|
+
|
147
|
+
assets.concat(results.assets)
|
148
|
+
|
149
|
+
if results.has_more?
|
150
|
+
next_query = results.query.merge({
|
151
|
+
:offset => results.query[:offset] + 100
|
152
|
+
})
|
153
|
+
|
154
|
+
results = self.search(next_query)
|
155
|
+
else
|
156
|
+
break
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
assets.sort { |a,b| b.start - a.start }
|
161
|
+
end
|
162
|
+
|
163
|
+
# Perform a search of Redux Archive
|
164
|
+
#
|
165
|
+
# @param [Hash] params your search parameters
|
166
|
+
# @option params [String] :q free text query
|
167
|
+
# @option params [String] :name query on programme name
|
168
|
+
# @option params [String,Array<String>,Channel,Array<Channel>] :channel
|
169
|
+
# query on channel, e.g. 'bbcone'. Can provide an array to search on
|
170
|
+
# multiple channels.
|
171
|
+
# @option params [Integer] :limit number of results to return. Default 10
|
172
|
+
# @option params [Integer] :offset offset of the start of results
|
173
|
+
# @option params [Date,DateTime,Time] :before only return braodcasts
|
174
|
+
# before date
|
175
|
+
# @option params [Date,DateTime,Time] :after only return braodcasts after
|
176
|
+
# date
|
177
|
+
# @option params [Date,DateTime,Time] :date everything from 0600 on given
|
178
|
+
# date for 24hrs
|
179
|
+
# @option params [Integer] :longer constraint on the duration, in seconds
|
180
|
+
# @option params [Integer] :shorter constraint on the duration, in seconds
|
181
|
+
# @option params [String] :programme_crid TV Anytime CRID
|
182
|
+
# @option params [String] :series_crid TV Anytime CRID
|
183
|
+
# @see BBC::Redux::SearchResults
|
184
|
+
# @return [BBC::Redux::SearchResults] search results
|
185
|
+
def search(params = {})
|
186
|
+
|
187
|
+
mapper = lambda do |val|
|
188
|
+
if val.class == Date || val.class == DateTime || val.class == Time
|
189
|
+
val.strftime('%Y-%m-%dT%H:%M:%S')
|
190
|
+
elsif val.class == Channel
|
191
|
+
val.name
|
192
|
+
else
|
193
|
+
val.to_s
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
new_params = params.map do |key, val|
|
198
|
+
if val.class == Array
|
199
|
+
[ key, val.map(&mapper) ]
|
200
|
+
else
|
201
|
+
[ key, mapper.call(val) ]
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
data = data_for(:search_results, Hash[new_params]).merge({
|
206
|
+
'query' => params
|
207
|
+
})
|
208
|
+
|
209
|
+
build :search_results, :using => data
|
210
|
+
end
|
211
|
+
|
212
|
+
# Fetch user object for current session
|
213
|
+
# @see BBC::Redux::User
|
214
|
+
# @return [BBC::Redux::User, nil] the user
|
215
|
+
def user
|
216
|
+
build :user, :using => data_for(:user)
|
217
|
+
end
|
218
|
+
|
219
|
+
private
|
220
|
+
|
221
|
+
# @private
|
222
|
+
def build(type, options)
|
223
|
+
data = options.fetch(:using)
|
224
|
+
|
225
|
+
case type
|
226
|
+
when :asset
|
227
|
+
Serializers::Asset.new(Asset.new).from_hash(data)
|
228
|
+
when :channels
|
229
|
+
Serializers::Channels.new([]).from_hash(data)
|
230
|
+
when :channel_categories
|
231
|
+
Serializers::ChannelCategories.new([]).from_hash(data)
|
232
|
+
when :search_results
|
233
|
+
Serializers::SearchResults.new(SearchResults.new).from_hash(data)
|
234
|
+
when :user
|
235
|
+
Serializers::User.new(User.new).from_hash(data)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# @private
|
240
|
+
def url_for(action)
|
241
|
+
case action
|
242
|
+
when :asset
|
243
|
+
host + '/asset/details'
|
244
|
+
when :channels
|
245
|
+
host + '/asset/channel/available'
|
246
|
+
when :channel_categories
|
247
|
+
host + '/asset/channel/categories'
|
248
|
+
when :login
|
249
|
+
host + '/user/login'
|
250
|
+
when :logout
|
251
|
+
host + '/user/logout'
|
252
|
+
when :search_results
|
253
|
+
host + '/asset/search'
|
254
|
+
when :user
|
255
|
+
host + '/user/details'
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# @private
|
260
|
+
def data_for(action, params = {})
|
261
|
+
url = url_for action
|
262
|
+
|
263
|
+
# Patch typhoeus / ethon's handling of array params, essentially
|
264
|
+
# turn this /?key[0]=1&key[1]=2&key[2]=3 into this
|
265
|
+
# /?key=1&key=2&key=3
|
266
|
+
|
267
|
+
arrays = params.select { |_,v| v.class == Array }
|
268
|
+
|
269
|
+
unless arrays.empty?
|
270
|
+
url += '?' unless url =~ /\?$/
|
271
|
+
|
272
|
+
arrays.each do |key, values|
|
273
|
+
url += values.map { |v| "#{key}=#{v}" }.join('&')
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
params = params.select { |_,v| v.class != Array }
|
278
|
+
|
279
|
+
resp = http.post(url, {
|
280
|
+
:body => params.merge(:token => token),
|
281
|
+
:followlocation => true,
|
282
|
+
})
|
283
|
+
|
284
|
+
case resp.code
|
285
|
+
when 200
|
286
|
+
JSON.parse(resp.body)
|
287
|
+
when 403
|
288
|
+
raise ForbiddenException.new("403 response for #{url}")
|
289
|
+
when 400..599
|
290
|
+
raise HttpException.new("#{resp.code} response for #{url}")
|
291
|
+
else
|
292
|
+
raise "Umm, not sure how to handle #{resp.code} for #{url}"
|
293
|
+
end
|
294
|
+
|
295
|
+
rescue JSON::ParserError => e
|
296
|
+
raise JsonParseException.new("Error parsing #{url}, #{e.message}")
|
297
|
+
end
|
298
|
+
|
299
|
+
end
|
300
|
+
|
301
|
+
end
|
302
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module BBC
|
2
|
+
module Redux
|
3
|
+
|
4
|
+
# @private
|
5
|
+
module EndPoints
|
6
|
+
|
7
|
+
HOST = 'https://i.bbcredux.com'
|
8
|
+
|
9
|
+
# Make dvb subs media file end point
|
10
|
+
# @param id [String] asset identifier
|
11
|
+
# @param key [String] string value of a valid access key
|
12
|
+
# @param fname [String] template for the file name
|
13
|
+
# @return [String] The end point
|
14
|
+
def self.dvbsubs(id, key, fname = nil)
|
15
|
+
HOST + "/asset/media/#{id}/#{key}/dvbsubs/#{(fname || '%s.xml') % id}"
|
16
|
+
end
|
17
|
+
|
18
|
+
# Make FLV media file end point
|
19
|
+
# @param id [String] asset identifier
|
20
|
+
# @param key [String] string value of a valid access key
|
21
|
+
# @param fname [String] template for the file name
|
22
|
+
# @return [String] The end point
|
23
|
+
def self.flv(id, key, fname = nil)
|
24
|
+
HOST + "/asset/media/#{id}/#{key}/Flash_v1.0/#{(fname || '%s.flv') % id}"
|
25
|
+
end
|
26
|
+
|
27
|
+
# Make mp3 media file end point
|
28
|
+
# @param id [String] asset identifier
|
29
|
+
# @param key [String] string value of a valid access key
|
30
|
+
# @param fname [String] template for the file name
|
31
|
+
# @return [String] The end point
|
32
|
+
def self.mp3(id, key, fname = nil)
|
33
|
+
HOST + "/asset/media/#{id}/#{key}/MP3_v1.0/#{(fname || '%s.mp3') % id}"
|
34
|
+
end
|
35
|
+
|
36
|
+
# Make h264_hi media file end point
|
37
|
+
# @param id [String] asset identifier
|
38
|
+
# @param key [String] string value of a valid access key
|
39
|
+
# @param fname [String] template for the file name
|
40
|
+
# @return [String] The end point
|
41
|
+
def self.h264_hi(id, key, fname = nil)
|
42
|
+
HOST + "/asset/media/#{id}/#{key}/h264_mp4_hi_v1.1/" \
|
43
|
+
+ (fname || '%s-h264lg.mp4') % id
|
44
|
+
end
|
45
|
+
|
46
|
+
# Make h264_lo media file end point
|
47
|
+
# @param id [String] asset identifier
|
48
|
+
# @param key [String] string value of a valid access key
|
49
|
+
# @param fname [String] template for the file name
|
50
|
+
# @return [String] The end point
|
51
|
+
def self.h264_lo(id, key, fname = nil)
|
52
|
+
HOST + "/asset/media/#{id}/#{key}/h264_mp4_lo_v1.0/" \
|
53
|
+
+ (fname || '%s-h264sm.mp4') % id
|
54
|
+
end
|
55
|
+
|
56
|
+
# Make ts media file end point
|
57
|
+
# @param id [String] asset identifier
|
58
|
+
# @param key [String] string value of a valid access key
|
59
|
+
# @param fname [String] template for the file name
|
60
|
+
# @return [String] The end point
|
61
|
+
def self.ts(id, key, fname = nil)
|
62
|
+
HOST + "/asset/media/#{id}/#{key}/ts/#{(fname || '%s.mpegts') % id}"
|
63
|
+
end
|
64
|
+
|
65
|
+
# Make stripped ts media file end point
|
66
|
+
# @param id [String] asset identifier
|
67
|
+
# @param key [String] string value of a valid access key
|
68
|
+
# @param fname [String] template for the file name
|
69
|
+
# @return [String] The end point
|
70
|
+
def self.ts_stripped(id, key, fname = nil)
|
71
|
+
HOST + "/asset/media/#{id}/#{key}/strip/" \
|
72
|
+
+ (fname || '%s-stripped.mpegts') % id
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module BBC
|
2
|
+
module Redux
|
3
|
+
|
4
|
+
# Redux API Asset Key Object
|
5
|
+
#
|
6
|
+
# Each asset is served with an associated key that is needed to access
|
7
|
+
# it's associated media. Generally these keys have a lifetime of 24 hours
|
8
|
+
#
|
9
|
+
# @example Properties of the key object
|
10
|
+
#
|
11
|
+
# key = redux_client.asset('5966413090093319525').key
|
12
|
+
#
|
13
|
+
# key.expired? #=> Boolean
|
14
|
+
# key.expires_at #=> DateTime
|
15
|
+
# key.live? #=> Boolean
|
16
|
+
# key.ttl #=> Integer
|
17
|
+
# key.value #=> String
|
18
|
+
#
|
19
|
+
# @author Matt Haynes <matt.haynes@bbc.co.uk>
|
20
|
+
class Key
|
21
|
+
|
22
|
+
# @!attribute [r] expires_at
|
23
|
+
# @return [DateTime] the access key's expiry time
|
24
|
+
attr_reader :expires_at
|
25
|
+
|
26
|
+
# @!attribute [r] value
|
27
|
+
# @return [String] the access key's value
|
28
|
+
attr_reader :value
|
29
|
+
|
30
|
+
# @param value [String] the access keys value
|
31
|
+
def initialize(value)
|
32
|
+
@value = value
|
33
|
+
@expires_at = Time.at( value.split('-')[1].to_i ).to_datetime
|
34
|
+
end
|
35
|
+
|
36
|
+
# @see Key#expired?
|
37
|
+
# @see Key#ttl
|
38
|
+
# @return [Boolean] true if ttl <= 0, false otherwise
|
39
|
+
def expired?
|
40
|
+
ttl <= 0
|
41
|
+
end
|
42
|
+
|
43
|
+
# @see Key#expired?
|
44
|
+
# @see Key#ttl
|
45
|
+
# @return [Boolean] true if ttl > 0, false otherwise
|
46
|
+
def live?
|
47
|
+
ttl > 0
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return the key's value as a string
|
51
|
+
def to_s
|
52
|
+
value
|
53
|
+
end
|
54
|
+
|
55
|
+
# @see Key#expired?
|
56
|
+
# @see Key#expires_at
|
57
|
+
# @see Key#live?
|
58
|
+
# @return [Integer] key's Time To Live in seconds
|
59
|
+
def ttl
|
60
|
+
expires_at.to_time.to_i - Time.now.to_i
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [Boolean] true if other_key is a redux key with the same value
|
64
|
+
def ==(other_key)
|
65
|
+
self.class == other_key.class && self.value == other_key.value
|
66
|
+
end
|
67
|
+
|
68
|
+
alias :eql? :==
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require_relative 'end_points'
|
3
|
+
require_relative 'key'
|
4
|
+
|
5
|
+
module BBC
|
6
|
+
module Redux
|
7
|
+
|
8
|
+
# Redux API Asset URL Object
|
9
|
+
#
|
10
|
+
# Each asset is available as various transcodes using the url function.
|
11
|
+
# The urls are key based and therefore generally have a lifetime of 24 hours #
|
12
|
+
#
|
13
|
+
# @example Properties of the url object
|
14
|
+
#
|
15
|
+
# url = redux_client.asset('5966413090093319525').url(:mp3)
|
16
|
+
#
|
17
|
+
# url.expired? #=> Boolean
|
18
|
+
# url.expires_at #=> DateTime
|
19
|
+
# url.live? #=> Boolean
|
20
|
+
# url.ttl #=> Integer
|
21
|
+
# url.end_point #=> String
|
22
|
+
#
|
23
|
+
# @example generate a URL with a different filename
|
24
|
+
#
|
25
|
+
# url = redux_client.asset('5966413090093319525').mp3_url
|
26
|
+
#
|
27
|
+
# url.end_point('myfile.mp3') #=> String
|
28
|
+
#
|
29
|
+
# @author Matt Haynes <matt.haynes@bbc.co.uk>
|
30
|
+
class MediaUrl
|
31
|
+
|
32
|
+
# Error raised when trying to initiate url with an unknown template type
|
33
|
+
class UnknownTemplateType < Exception; end
|
34
|
+
|
35
|
+
# Known URL templates, these are the only valid options for the
|
36
|
+
# type attribute of MediaUrl.initialize.
|
37
|
+
# @see MediaUrl#initialize
|
38
|
+
TEMPLATES = [
|
39
|
+
:dvbsubs,
|
40
|
+
:flv,
|
41
|
+
:h264_hi,
|
42
|
+
:h264_lo,
|
43
|
+
:mp3,
|
44
|
+
:ts,
|
45
|
+
:ts_stripped
|
46
|
+
].freeze
|
47
|
+
|
48
|
+
# @!attribute [r] expires_at
|
49
|
+
# @return [DateTime] the access url's expiry time
|
50
|
+
|
51
|
+
# @!method expired?
|
52
|
+
# @see MediaUrl#expired?
|
53
|
+
# @see MediaUrl#ttl
|
54
|
+
# @return [Boolean] true if ttl <= 0, false otherwise
|
55
|
+
|
56
|
+
# @!method live?
|
57
|
+
# @see MediaUrl#expired?
|
58
|
+
# @see MediaUrl#ttl
|
59
|
+
# @return [Boolean] true if ttl > 0, false otherwise
|
60
|
+
|
61
|
+
# @!method ttl
|
62
|
+
# @see MediaUrl#expired?
|
63
|
+
# @see MediaUrl#live?
|
64
|
+
# @return [Integer] url's time to live in seconds
|
65
|
+
|
66
|
+
# @private
|
67
|
+
extend Forwardable
|
68
|
+
|
69
|
+
def_delegators :@key, :expired?, :expires_at, :live?, :ttl
|
70
|
+
|
71
|
+
# @!attribute [r] identifier
|
72
|
+
# @return [String] the url's indentifier
|
73
|
+
attr_reader :identifier
|
74
|
+
|
75
|
+
# @!attribute [r] type
|
76
|
+
# @see MediaUrl.TYPES
|
77
|
+
# @return [Symbol] the url's type
|
78
|
+
attr_reader :type
|
79
|
+
|
80
|
+
# @!attribute [r] key
|
81
|
+
# @return [BBC::Redux::Key] the url's key
|
82
|
+
attr_reader :key
|
83
|
+
|
84
|
+
# @param identifier [String] the disk reference or UUID of the asset
|
85
|
+
# @param type [Symbol] the transcode type, must be one of MediaUrl.TYPES
|
86
|
+
# @param key [BBC::Redux:Key] the key
|
87
|
+
# @raise [UnknownTranscodeType] if type parameter is unknown
|
88
|
+
# @see MediaUrl.TYPES
|
89
|
+
def initialize( identifier, type, key )
|
90
|
+
|
91
|
+
unless TEMPLATES.include?(type)
|
92
|
+
raise UnknownTemplateType.new("Unknown template type #{type}")
|
93
|
+
end
|
94
|
+
|
95
|
+
@identifier = identifier
|
96
|
+
@type = type
|
97
|
+
@key = key
|
98
|
+
end
|
99
|
+
|
100
|
+
# Generate the end point to retreive media file
|
101
|
+
#
|
102
|
+
# @param filename [String] an optional filename to specify on the end
|
103
|
+
# point. This can also contain a "%s" template that will be populated
|
104
|
+
# with the MediaUrl#identifier
|
105
|
+
# @return [String] The URL end point
|
106
|
+
def end_point(filename = nil)
|
107
|
+
EndPoints.send(type, identifier, key.value, filename)
|
108
|
+
end
|
109
|
+
|
110
|
+
alias :to_s :end_point
|
111
|
+
|
112
|
+
# @return [Boolean] true if other_url is a redux url with the same type,
|
113
|
+
# identifier and key
|
114
|
+
def ==(other_url)
|
115
|
+
self.class == other_url.class && self.end_point == other_url.end_point
|
116
|
+
end
|
117
|
+
|
118
|
+
alias :eql? :==
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'virtus'
|
2
|
+
require_relative 'asset'
|
3
|
+
|
4
|
+
module BBC
|
5
|
+
module Redux
|
6
|
+
|
7
|
+
# Search results container
|
8
|
+
#
|
9
|
+
# @example Properties of search results
|
10
|
+
#
|
11
|
+
# results = redux_client.search(:name => 'Pingu')
|
12
|
+
#
|
13
|
+
# results.created_at #=> DateTime
|
14
|
+
# results.query #=> Hash
|
15
|
+
# results.query_time #=> Float
|
16
|
+
# results.assets #=> Array<BBC::Redux::Asset>
|
17
|
+
# results.total #=> Integer
|
18
|
+
# results.total_returned #=> Integer
|
19
|
+
# results.has_more? #=> Boolean
|
20
|
+
#
|
21
|
+
# @example Iterating all search results
|
22
|
+
#
|
23
|
+
# results = redux_client.search(:name => 'Pingu', :offset => 0)
|
24
|
+
#
|
25
|
+
# while true do
|
26
|
+
# results.assets.each do |asset|
|
27
|
+
# puts asset.name
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# if results.has_more?
|
31
|
+
# next_query = results.query.merge({
|
32
|
+
# :offset => results.query[:offset] + 10
|
33
|
+
# })
|
34
|
+
#
|
35
|
+
# results = redux_client.search(next_query)
|
36
|
+
# else
|
37
|
+
# break
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
class SearchResults
|
42
|
+
|
43
|
+
include Virtus.value_object
|
44
|
+
|
45
|
+
# @!attribute [r] created_at
|
46
|
+
# @return [DateTime] date and time results were retrieved
|
47
|
+
attribute :created_at, DateTime
|
48
|
+
|
49
|
+
# @!attribute [r] query
|
50
|
+
# @return [Hash] original parameters used in this query
|
51
|
+
attribute :query, Hash
|
52
|
+
|
53
|
+
# @attribute [r] query_time
|
54
|
+
# @return [Float] query time in seconds
|
55
|
+
attribute :query_time, Float
|
56
|
+
|
57
|
+
# @!attribute [r] assets
|
58
|
+
# @return [Array<BBC::Redux::Asset>] assets returned in this query
|
59
|
+
attribute :assets, Array[Asset]
|
60
|
+
|
61
|
+
# @!attribute [r] offset
|
62
|
+
# @return [Integer] offset of this set of results
|
63
|
+
attribute :offset, Integer
|
64
|
+
|
65
|
+
# @!attribute [r] total
|
66
|
+
# @return [Integer] total number of results
|
67
|
+
attribute :total, Integer
|
68
|
+
|
69
|
+
# @!attribute [r] total_returned
|
70
|
+
# @return [Integer] total number of results returned in this query
|
71
|
+
attribute :total_returned, Integer
|
72
|
+
|
73
|
+
# @return [Boolean] true if there are more results available than
|
74
|
+
# returned in this query
|
75
|
+
def has_more?
|
76
|
+
(offset + total_returned) < total
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|