spaceship 0.7.0 → 0.9.0
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.
- checksums.yaml +4 -4
- data/README.md +2 -0
- data/lib/spaceship/base.rb +3 -1
- data/lib/spaceship/client.rb +7 -3
- data/lib/spaceship/du/du_client.rb +100 -0
- data/lib/spaceship/du/upload_file.rb +45 -0
- data/lib/spaceship/du/utilities.rb +75 -0
- data/lib/spaceship/helper/net_http_generic_request.rb +5 -3
- data/lib/spaceship/portal/app.rb +0 -1
- data/lib/spaceship/portal/app_service.rb +3 -3
- data/lib/spaceship/portal/portal_client.rb +1 -2
- data/lib/spaceship/portal/provisioning_profile.rb +1 -2
- data/lib/spaceship/tunes/app_details.rb +6 -0
- data/lib/spaceship/tunes/app_image.rb +53 -0
- data/lib/spaceship/tunes/app_screenshot.rb +6 -17
- data/lib/spaceship/tunes/app_submission.rb +2 -14
- data/lib/spaceship/tunes/app_trailer.rb +70 -0
- data/lib/spaceship/tunes/app_version.rb +293 -30
- data/lib/spaceship/tunes/app_version_ref.rb +19 -0
- data/lib/spaceship/tunes/application.rb +12 -2
- data/lib/spaceship/tunes/build.rb +4 -1
- data/lib/spaceship/tunes/build_train.rb +0 -1
- data/lib/spaceship/tunes/device_type.rb +14 -0
- data/lib/spaceship/tunes/processing_build.rb +0 -1
- data/lib/spaceship/tunes/tester.rb +0 -2
- data/lib/spaceship/tunes/transit_app_file.rb +27 -0
- data/lib/spaceship/tunes/tunes.rb +11 -0
- data/lib/spaceship/tunes/tunes_client.rb +214 -13
- data/lib/spaceship/tunes/user_detail.rb +17 -0
- data/lib/spaceship/version.rb +1 -1
- metadata +25 -2
@@ -11,9 +11,6 @@ module Spaceship
|
|
11
11
|
# @return (AppVersion) The version to use for this submission
|
12
12
|
attr_accessor :version
|
13
13
|
|
14
|
-
# @return (String) The stage of this submission (start, complete)
|
15
|
-
attr_accessor :stage
|
16
|
-
|
17
14
|
# @return (Boolean) Submitted for Review
|
18
15
|
attr_accessor :submitted_for_review
|
19
16
|
|
@@ -116,11 +113,9 @@ module Spaceship
|
|
116
113
|
|
117
114
|
# @param application (Spaceship::Tunes::Application) The app this submission is for
|
118
115
|
def create(application, version)
|
119
|
-
|
120
|
-
attrs = client.send_app_submission(application.apple_id, version.raw_data, stage)
|
116
|
+
attrs = client.prepare_app_submissions(application.apple_id, application.edit_version.version_id)
|
121
117
|
attrs.merge!(application: application)
|
122
118
|
attrs.merge!(version: version)
|
123
|
-
attrs.merge!(stage: stage)
|
124
119
|
|
125
120
|
return self.factory(attrs)
|
126
121
|
end
|
@@ -128,20 +123,13 @@ module Spaceship
|
|
128
123
|
|
129
124
|
# Save and complete the app submission
|
130
125
|
def complete!
|
131
|
-
|
132
|
-
client.send_app_submission(application.apple_id, raw_data, @stage)
|
126
|
+
client.send_app_submission(application.apple_id, raw_data)
|
133
127
|
@submitted_for_review = true
|
134
128
|
end
|
135
129
|
|
136
|
-
# @return (String) An URL to this specific resource. You can enter this URL into your browser
|
137
|
-
def url
|
138
|
-
"https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa/ra/apps/#{self.application.apple_id}/version/submit/#{self.stage}"
|
139
|
-
end
|
140
|
-
|
141
130
|
def setup
|
142
131
|
@submitted_for_review = false
|
143
132
|
end
|
144
|
-
|
145
133
|
end
|
146
134
|
end
|
147
135
|
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Spaceship
|
2
|
+
module Tunes
|
3
|
+
# Represents a preview video hosted on iTunes Connect. Used for icons, screenshots, etc
|
4
|
+
class AppTrailer < TunesBase
|
5
|
+
attr_accessor :video_asset_token
|
6
|
+
|
7
|
+
attr_accessor :picture_asset_token
|
8
|
+
|
9
|
+
attr_accessor :descriptionXML
|
10
|
+
|
11
|
+
attr_accessor :preview_frame_time_code
|
12
|
+
|
13
|
+
attr_accessor :video_url
|
14
|
+
|
15
|
+
attr_accessor :preview_image_url
|
16
|
+
|
17
|
+
attr_accessor :full_sized_preview_image_url
|
18
|
+
|
19
|
+
attr_accessor :device_type
|
20
|
+
|
21
|
+
attr_accessor :language
|
22
|
+
|
23
|
+
attr_mapping(
|
24
|
+
'videoAssetToken' => :video_asset_token,
|
25
|
+
'pictureAssetToken' => :picture_asset_token,
|
26
|
+
'descriptionXML' => :descriptionXML,
|
27
|
+
'previewFrameTimeCode' => :preview_frame_time_code,
|
28
|
+
'isPortrait' => :is_portrait,
|
29
|
+
'videoUrl' => :video_url,
|
30
|
+
'previewImageUrl' => :preview_image_url,
|
31
|
+
'fullSizedPreviewImageUrl' => :full_sized_preview_image_url,
|
32
|
+
'contentType' => :content_type,
|
33
|
+
'videoStatus' => :video_status
|
34
|
+
)
|
35
|
+
|
36
|
+
class << self
|
37
|
+
def factory(attrs)
|
38
|
+
self.new(attrs)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def reset!(attrs = {})
|
43
|
+
update_raw_data!
|
44
|
+
({
|
45
|
+
video_asset_token: nil,
|
46
|
+
picture_asset_token: nil,
|
47
|
+
descriptionXML: nil,
|
48
|
+
preview_frame_time_code: nil,
|
49
|
+
is_portrait: nil,
|
50
|
+
video_url: nil,
|
51
|
+
preview_image_url: nil,
|
52
|
+
full_sized_preview_image_url: nil,
|
53
|
+
content_type: nil,
|
54
|
+
video_status: nil,
|
55
|
+
device_type: nil,
|
56
|
+
language: nil
|
57
|
+
}.merge(attrs)
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def update_raw_data!(hash)
|
64
|
+
hash.each do |k, v|
|
65
|
+
self.send("#{k}=", v)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -13,6 +13,9 @@ module Spaceship
|
|
13
13
|
# @return (String) The copyright information of this app
|
14
14
|
attr_accessor :copyright
|
15
15
|
|
16
|
+
# @return (String) The appType number of this version
|
17
|
+
attr_accessor :app_type
|
18
|
+
|
16
19
|
# @return (Spaceship::Tunes::AppStatus) What's the current status of this app
|
17
20
|
# e.g. Waiting for Review, Ready for Sale, ...
|
18
21
|
attr_reader :app_status
|
@@ -41,21 +44,21 @@ module Spaceship
|
|
41
44
|
# @return (Bool) Does the binary contain a watch binary?
|
42
45
|
attr_accessor :supports_apple_watch
|
43
46
|
|
44
|
-
# @return (
|
45
|
-
attr_accessor :
|
46
|
-
|
47
|
-
# @return (String) Name of the original file
|
48
|
-
attr_accessor :app_icon_original_name
|
49
|
-
|
50
|
-
# @return (String) URL to the full resolution 1024x1024 app icon
|
51
|
-
attr_accessor :watch_app_icon_url
|
47
|
+
# @return (Spaceship::Tunes::AppImage) the structure containing information about the large app icon (1024x1024)
|
48
|
+
attr_accessor :large_app_icon
|
52
49
|
|
53
|
-
# @return (
|
54
|
-
attr_accessor :
|
50
|
+
# @return (Spaceship::Tunes::AppImage) the structure containing information about the large watch icon (1024x1024)
|
51
|
+
attr_accessor :watch_app_icon
|
55
52
|
|
56
53
|
# @return (Integer) a unqiue ID for this version generated by iTunes Connect
|
57
54
|
attr_accessor :version_id
|
58
55
|
|
56
|
+
####
|
57
|
+
# GeoJson
|
58
|
+
####
|
59
|
+
# @return (Spaceship::Tunes::TransitAppFile) the structure containing information about the geo json. Can be nil
|
60
|
+
attr_accessor :transit_app_file
|
61
|
+
|
59
62
|
####
|
60
63
|
# App Review Information
|
61
64
|
####
|
@@ -84,7 +87,7 @@ module Spaceship
|
|
84
87
|
# Localized values
|
85
88
|
####
|
86
89
|
|
87
|
-
# @return (Array) Raw access the all available languages. You shouldn't use it
|
90
|
+
# @return (Array) Raw access the all available languages. You shouldn't use it probably
|
88
91
|
attr_accessor :languages
|
89
92
|
|
90
93
|
# @return (Hash) A hash representing the keywords in all languages
|
@@ -105,7 +108,11 @@ module Spaceship
|
|
105
108
|
# @return (Hash) Represents the screenshots of this app version (read-only)
|
106
109
|
attr_reader :screenshots
|
107
110
|
|
111
|
+
# @return (Hash) Represents the trailers of this app version (read-only)
|
112
|
+
attr_reader :trailers
|
113
|
+
|
108
114
|
attr_mapping({
|
115
|
+
'appType' => :app_type,
|
109
116
|
'canBetaTest' => :can_beta_test,
|
110
117
|
'canPrepareForUpload' => :can_prepare_for_upload,
|
111
118
|
'canRejectVersion' => :can_reject_version,
|
@@ -119,8 +126,9 @@ module Spaceship
|
|
119
126
|
'supportsAppleWatch' => :supports_apple_watch,
|
120
127
|
'versionId' => :version_id,
|
121
128
|
'version.value' => :version,
|
122
|
-
|
123
|
-
|
129
|
+
|
130
|
+
# GeoJson
|
131
|
+
# 'transitAppFile.value' => :transit_app_file
|
124
132
|
|
125
133
|
# App Review Information
|
126
134
|
'appReviewInfo.firstName.value' => :review_first_name,
|
@@ -195,6 +203,27 @@ module Spaceship
|
|
195
203
|
# languages
|
196
204
|
# end
|
197
205
|
|
206
|
+
# Returns an array of all builds that can be sent to review
|
207
|
+
def candidate_builds
|
208
|
+
res = client.candidate_builds(self.application.apple_id, self.version_id)
|
209
|
+
builds = []
|
210
|
+
res.each do |attrs|
|
211
|
+
next unless attrs["type"] == "BUILD" # I don't know if it can be something else.
|
212
|
+
builds << Tunes::Build.factory(attrs)
|
213
|
+
end
|
214
|
+
return builds
|
215
|
+
end
|
216
|
+
|
217
|
+
# Select a build to be submitted for Review.
|
218
|
+
# You have to pass a build you got from - candidate_builds
|
219
|
+
# Don't forget to call save! after calling this method
|
220
|
+
def select_build(build)
|
221
|
+
raw_data.set(['preReleaseBuildVersionString', 'value'], build.build_version)
|
222
|
+
raw_data.set(['preReleaseBuildTrainVersionString'], build.train_version)
|
223
|
+
raw_data.set(['preReleaseBuildUploadDate'], build.upload_date)
|
224
|
+
true
|
225
|
+
end
|
226
|
+
|
198
227
|
# Push all changes that were made back to iTunes Connect
|
199
228
|
def save!
|
200
229
|
client.update_app_version!(application.apple_id, is_live?, raw_data)
|
@@ -212,13 +241,157 @@ module Spaceship
|
|
212
241
|
# Properly parse the AppStatus
|
213
242
|
status = raw_data['status']
|
214
243
|
@app_status = Tunes::AppStatus.get_from_string(status)
|
244
|
+
setup_large_app_icon
|
245
|
+
setup_watch_app_icon
|
246
|
+
setup_transit_app_file
|
247
|
+
setup_screenshots
|
248
|
+
setup_trailers
|
249
|
+
end
|
215
250
|
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
@
|
251
|
+
# Uploads or removes the large icon
|
252
|
+
# @param icon_path (String): The path to the icon. Use nil to remove it
|
253
|
+
def upload_large_icon!(icon_path)
|
254
|
+
unless icon_path
|
255
|
+
@large_app_icon.reset!
|
256
|
+
return
|
257
|
+
end
|
258
|
+
upload_image = UploadFile.from_path icon_path
|
259
|
+
image_data = client.upload_large_icon(self, upload_image)
|
260
|
+
|
261
|
+
@large_app_icon.reset!({ asset_token: image_data['token'], original_file_name: upload_image.file_name })
|
262
|
+
end
|
263
|
+
|
264
|
+
# Uploads or removes the watch icon
|
265
|
+
# @param icon_path (String): The path to the icon. Use nil to remove it
|
266
|
+
def upload_watch_icon!(icon_path)
|
267
|
+
unless icon_path
|
268
|
+
@watch_app_icon.reset!
|
269
|
+
return
|
270
|
+
end
|
271
|
+
upload_image = UploadFile.from_path icon_path
|
272
|
+
image_data = client.upload_watch_icon(self, upload_image)
|
273
|
+
|
274
|
+
@watch_app_icon.reset!({ asset_token: image_data["token"], original_file_name: upload_image.file_name })
|
275
|
+
end
|
276
|
+
|
277
|
+
# Uploads or removes the transit app file
|
278
|
+
# @param icon_path (String): The path to the geojson file. Use nil to remove it
|
279
|
+
def upload_geojson!(geojson_path)
|
280
|
+
unless geojson_path
|
281
|
+
raw_data["transitAppFile"]["value"] = nil
|
282
|
+
@transit_app_file = nil
|
283
|
+
return
|
284
|
+
end
|
285
|
+
upload_file = UploadFile.from_path geojson_path
|
286
|
+
geojson_data = client.upload_geojson(self, upload_file)
|
287
|
+
|
288
|
+
@transit_app_file = Tunes::TransitAppFile.factory({}) if @transit_app_file.nil?
|
289
|
+
@transit_app_file .url = nil # response.headers['Location']
|
290
|
+
@transit_app_file.asset_token = geojson_data["token"]
|
291
|
+
@transit_app_file.name = upload_file.file_name
|
292
|
+
@transit_app_file.time_stamp = Time.now.to_i * 1000 # works without but...
|
293
|
+
end
|
294
|
+
|
295
|
+
# Uploads or removes a screenshot
|
296
|
+
# @param icon_path (String): The path to the screenshot. Use nil to remove it
|
297
|
+
# @param sort_order (Fixnum): The sort_order, from 1 to 5
|
298
|
+
# @param language (String): The language for this screenshot
|
299
|
+
# @param device (string): The device for this screenshot
|
300
|
+
def upload_screenshot!(screenshot_path, sort_order, language, device)
|
301
|
+
raise "sort_order must be positive" unless sort_order > 0
|
302
|
+
raise "sort_order must not be > 5" if sort_order > 5
|
303
|
+
# this will also check both language and device parameters
|
304
|
+
device_lang_screenshots = screenshots_data_for_language_and_device(language, device)["value"]
|
305
|
+
existing_sort_orders = device_lang_screenshots.map { |s| s["value"]["sortOrder"] }
|
306
|
+
if screenshot_path # adding / replacing
|
307
|
+
upload_file = UploadFile.from_path screenshot_path
|
308
|
+
screenshot_data = client.upload_screenshot(self, upload_file, device)
|
309
|
+
|
310
|
+
new_screenshot = {
|
311
|
+
"value" => {
|
312
|
+
"assetToken" => screenshot_data["token"],
|
313
|
+
"sortOrder" => sort_order,
|
314
|
+
"url" => nil,
|
315
|
+
"thumbNailUrl" => nil,
|
316
|
+
"originalFileName" => upload_file.file_name
|
317
|
+
}
|
318
|
+
}
|
319
|
+
if existing_sort_orders.include?(sort_order) # replace
|
320
|
+
device_lang_screenshots[existing_sort_orders.index(sort_order)] = new_screenshot
|
321
|
+
else # add
|
322
|
+
device_lang_screenshots << new_screenshot
|
323
|
+
end
|
324
|
+
else # removing
|
325
|
+
raise "cannot remove screenshot with non existing sort_order" unless existing_sort_orders.include?(sort_order)
|
326
|
+
device_lang_screenshots.delete_at(existing_sort_orders.index(sort_order))
|
221
327
|
end
|
328
|
+
setup_screenshots
|
329
|
+
end
|
330
|
+
|
331
|
+
# Uploads, removes a trailer video or change its preview image
|
332
|
+
#
|
333
|
+
# A preview image for the video is required by ITC and is usually automatically extracted by your browser.
|
334
|
+
# This method will either automatically extract it from the video (using `ffmpeg) or allow you
|
335
|
+
# to specify it using +preview_image_path+.
|
336
|
+
# If the preview image is specified, ffmpeg` will ot be used. The image resolution will be checked against
|
337
|
+
# expectations (which might be different from the trailer resolution.
|
338
|
+
#
|
339
|
+
# It is recommended to extract the preview image using the spaceship related tools in order to ensure
|
340
|
+
# the appropriate format and resolution are used.
|
341
|
+
#
|
342
|
+
# Note: if the video is already set, the +trailer_path+ is only used to grab the preview screenshot.
|
343
|
+
# Note: to extract its resolution and a screenshot preview, the `ffmpeg` tool will be used
|
344
|
+
#
|
345
|
+
# @param icon_path (String): The path to the screenshot. Use nil to remove it
|
346
|
+
# @param sort_order (Fixnum): The sort_order, from 1 to 5
|
347
|
+
# @param language (String): The language for this screenshot
|
348
|
+
# @param device (String): The device for this screenshot
|
349
|
+
# @param timestamp (String): The optional timestamp of the screenshot to grab
|
350
|
+
def upload_trailer!(trailer_path, language, device, timestamp = "05.00", preview_image_path = nil)
|
351
|
+
raise "No app trailer supported for iphone35" if device == 'iphone35'
|
352
|
+
|
353
|
+
device_lang_trailer = trailer_data_for_language_and_device(language, device)
|
354
|
+
if trailer_path # adding / replacing trailer / replacing preview
|
355
|
+
raise "Invalid timestamp #{timestamp}" if (timestamp =~ /^[0-9][0-9].[0-9][0-9]$/).nil?
|
356
|
+
|
357
|
+
if preview_image_path
|
358
|
+
check_preview_screenshot_resolution(preview_image_path, device)
|
359
|
+
video_preview_path = preview_image_path
|
360
|
+
else
|
361
|
+
# IDEA: optimization, we could avoid fetching the screenshot if the timestamp hasn't changed
|
362
|
+
video_preview_resolution = video_preview_resolution_for(device, trailer_path)
|
363
|
+
video_preview_path = Utilities.grab_video_preview(trailer_path, timestamp, video_preview_resolution)
|
364
|
+
end
|
365
|
+
video_preview_file = UploadFile.from_path video_preview_path
|
366
|
+
video_preview_data = client.upload_trailer_preview(self, video_preview_file)
|
367
|
+
|
368
|
+
trailer = device_lang_trailer["value"]
|
369
|
+
if trailer.nil? # add trailer
|
370
|
+
upload_file = UploadFile.from_path trailer_path
|
371
|
+
trailer_data = client.upload_trailer(self, upload_file)
|
372
|
+
trailer_data = trailer_data['responses'][0]
|
373
|
+
trailer = {
|
374
|
+
"videoAssetToken" => trailer_data["token"],
|
375
|
+
"descriptionXML" => trailer_data["descriptionDoc"],
|
376
|
+
"contentType" => upload_file.content_type
|
377
|
+
}
|
378
|
+
device_lang_trailer["value"] = trailer
|
379
|
+
end
|
380
|
+
# add / update preview
|
381
|
+
# different format required
|
382
|
+
ts = "00:00:#{timestamp}"
|
383
|
+
ts[8] = ':'
|
384
|
+
|
385
|
+
trailer.merge!({
|
386
|
+
"pictureAssetToken" => video_preview_data["token"],
|
387
|
+
"previewFrameTimeCode" => "#{ts}",
|
388
|
+
"isPortrait" => Utilities.portrait?(video_preview_path)
|
389
|
+
})
|
390
|
+
else # removing trailer
|
391
|
+
raise "cannot remove non existing trailer" if device_lang_trailer["value"].nil?
|
392
|
+
device_lang_trailer["value"] = nil
|
393
|
+
end
|
394
|
+
setup_trailers
|
222
395
|
end
|
223
396
|
|
224
397
|
# Prefill name, keywords, etc...
|
@@ -246,29 +419,119 @@ module Spaceship
|
|
246
419
|
|
247
420
|
private
|
248
421
|
|
422
|
+
def setup_large_app_icon
|
423
|
+
large_app_icon = raw_data["largeAppIcon"]["value"]
|
424
|
+
@large_app_icon = nil
|
425
|
+
@large_app_icon = Tunes::AppImage.factory(large_app_icon) if large_app_icon
|
426
|
+
end
|
427
|
+
|
428
|
+
def setup_watch_app_icon
|
429
|
+
watch_app_icon = raw_data["watchAppIcon"]["value"]
|
430
|
+
@watch_app_icon = nil
|
431
|
+
@watch_app_icon = Tunes::AppImage.factory(watch_app_icon) if watch_app_icon
|
432
|
+
end
|
433
|
+
|
434
|
+
def setup_transit_app_file
|
435
|
+
transit_app_file = raw_data["transitAppFile"]["value"]
|
436
|
+
@transit_app_file = nil
|
437
|
+
@transit_app_file = Tunes::TransitAppFile.factory(transit_app_file) if transit_app_file
|
438
|
+
end
|
439
|
+
|
440
|
+
def screenshots_data_for_language_and_device(language, device)
|
441
|
+
container_data_for_language_and_device("screenshots", language, device)
|
442
|
+
end
|
443
|
+
|
444
|
+
def trailer_data_for_language_and_device(language, device)
|
445
|
+
container_data_for_language_and_device("appTrailers", language, device)
|
446
|
+
end
|
447
|
+
|
448
|
+
def container_data_for_language_and_device(data_field, language, device)
|
449
|
+
raise "#{device} isn't a valid device name" unless DeviceType.exists?(device)
|
450
|
+
|
451
|
+
languages = raw_data_details.select { |d| d["language"] == language }
|
452
|
+
# IDEA: better error for non existing language
|
453
|
+
raise "#{language} isn't an activated language" unless languages.count > 0
|
454
|
+
lang_details = languages[0]
|
455
|
+
devices_details = lang_details[data_field]["value"]
|
456
|
+
raise "Unexpected state: missing device details for #{device}" unless devices_details.key? device
|
457
|
+
devices_details[device]
|
458
|
+
end
|
459
|
+
|
460
|
+
def setup_screenshots
|
461
|
+
@screenshots = {}
|
462
|
+
raw_data_details.each do |row|
|
463
|
+
# Now that's one language right here
|
464
|
+
@screenshots[row['language']] = setup_screenshots_for(row)
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
249
468
|
# generates the nested data structure to represent screenshots
|
250
|
-
def
|
251
|
-
screenshots = row.fetch(
|
469
|
+
def setup_screenshots_for(row)
|
470
|
+
screenshots = row.fetch("screenshots", {}).fetch("value", nil)
|
252
471
|
return [] unless screenshots
|
253
472
|
|
254
473
|
result = []
|
255
474
|
|
256
475
|
screenshots.each do |device_type, value|
|
257
|
-
value[
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
device_type: device_type,
|
265
|
-
language: row['language']
|
266
|
-
})
|
476
|
+
value["value"].each do |screenshot|
|
477
|
+
screenshot_data = screenshot["value"]
|
478
|
+
data = {
|
479
|
+
device_type: device_type,
|
480
|
+
language: row["language"]
|
481
|
+
}.merge(screenshot_data)
|
482
|
+
result << Tunes::AppScreenshot.factory(data)
|
267
483
|
end
|
268
484
|
end
|
269
485
|
|
270
486
|
return result
|
271
487
|
end
|
488
|
+
|
489
|
+
def setup_trailers
|
490
|
+
@trailers = {}
|
491
|
+
raw_data_details.each do |row|
|
492
|
+
# Now that's one language right here
|
493
|
+
@trailers[row["language"]] = setup_trailers_for(row)
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
# generates the nested data structure to represent trailers
|
498
|
+
def setup_trailers_for(row)
|
499
|
+
trailers = row.fetch("appTrailers", {}).fetch("value", nil)
|
500
|
+
return [] unless trailers
|
501
|
+
|
502
|
+
result = []
|
503
|
+
|
504
|
+
trailers.each do |device_type, value|
|
505
|
+
trailer_data = value["value"]
|
506
|
+
next if trailer_data.nil?
|
507
|
+
data = {
|
508
|
+
device_type: device_type,
|
509
|
+
language: row["language"]
|
510
|
+
}.merge(trailer_data)
|
511
|
+
result << Tunes::AppTrailer.factory(data)
|
512
|
+
end
|
513
|
+
|
514
|
+
return result
|
515
|
+
end
|
516
|
+
|
517
|
+
# identify the required resolution for this particular video screenshot
|
518
|
+
def video_preview_resolution_for(device, video_path)
|
519
|
+
is_portrait = Utilities.portrait?(video_path)
|
520
|
+
TunesClient.video_preview_resolution_for(device, is_portrait)
|
521
|
+
end
|
522
|
+
|
523
|
+
# ensure the specified preview screenshot has the expected resolution the specified target +device+
|
524
|
+
def check_preview_screenshot_resolution(preview_screenshot_path, device)
|
525
|
+
is_portrait = Utilities.portrait?(preview_screenshot_path)
|
526
|
+
expected_resolution = TunesClient.video_preview_resolution_for(device, is_portrait)
|
527
|
+
actual_resolution = Utilities.resolution(preview_screenshot_path)
|
528
|
+
orientation = is_portrait ? "portrait" : "landscape"
|
529
|
+
raise "Invalid #{orientation} screenshot resolution for device #{device}. Should be #{expected_resolution}" unless (actual_resolution == expected_resolution)
|
530
|
+
end
|
531
|
+
|
532
|
+
def raw_data_details
|
533
|
+
raw_data["details"]["value"]
|
534
|
+
end
|
272
535
|
end
|
273
536
|
end
|
274
537
|
end
|