@eluvio/elv-client-js 4.2.25 → 4.2.29
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.
- package/package.json +5 -1
- package/src/EthClient.js +2 -1
- package/src/client/ABRPublishing.js +33 -19
- package/src/client/ContentAccess.js +70 -1
- package/src/client/Files.js +425 -219
- package/src/client/LiveStream.js +47 -21
- package/utilities/MediaIngest.js +424 -0
- package/utilities/MigrateEncryptionConkForNewUser.js +62 -0
- package/utilities/ObjectAddFiles.js +29 -9
- package/utilities/README_INGEST.md +332 -0
- package/utilities/lib/abr_profiles/abr_profile_4k_all.json +181 -0
- package/utilities/lib/concerns/Client.js +71 -10
- package/utilities/lib/concerns/CloudAccess.js +2 -2
- package/utilities/lib/concerns/CloudFile.js +16 -8
- package/utilities/lib/concerns/LocalFile.js +2 -1
- package/utilities/lib/data/network_specs.json +13 -0
- package/utilities/lib/models/CredentialSet.js +26 -7
- package/utilities/tests/unit/lib/concerns/Client.test.js +96 -0
package/src/client/LiveStream.js
CHANGED
|
@@ -322,15 +322,16 @@ exports.StreamCreate = async function({
|
|
|
322
322
|
video_type: "live",
|
|
323
323
|
slug: slugify(name)
|
|
324
324
|
}
|
|
325
|
-
}
|
|
326
|
-
"live_recording_config": liveRecordingConfig
|
|
325
|
+
}
|
|
327
326
|
};
|
|
328
327
|
|
|
329
|
-
const
|
|
328
|
+
const currentLiveRecordingConfig = await this.ContentObjectMetadata({
|
|
330
329
|
libraryId,
|
|
331
330
|
objectId,
|
|
332
|
-
metadataSubtree: "live_recording_config
|
|
333
|
-
});
|
|
331
|
+
metadataSubtree: "live_recording_config"
|
|
332
|
+
}) || {};
|
|
333
|
+
|
|
334
|
+
const oldProfile = currentLiveRecordingConfig.name;
|
|
334
335
|
|
|
335
336
|
if(liveRecordingConfig?.name && liveRecordingConfig.name !== oldProfile) {
|
|
336
337
|
metadata.public.asset_metadata.profile_last_updated = new Date().toISOString();
|
|
@@ -343,6 +344,15 @@ exports.StreamCreate = async function({
|
|
|
343
344
|
metadata
|
|
344
345
|
});
|
|
345
346
|
|
|
347
|
+
// Full overwrite avoids the server-side merge mixing streams from the old and new profile
|
|
348
|
+
await this.ReplaceMetadata({
|
|
349
|
+
libraryId,
|
|
350
|
+
objectId,
|
|
351
|
+
writeToken,
|
|
352
|
+
metadataSubtree: "live_recording_config",
|
|
353
|
+
metadata: R.mergeDeepRight(currentLiveRecordingConfig, liveRecordingConfig)
|
|
354
|
+
});
|
|
355
|
+
|
|
346
356
|
try {
|
|
347
357
|
await this.CreateLinks({
|
|
348
358
|
libraryId,
|
|
@@ -1080,9 +1090,9 @@ exports.StreamStatus = async function({name, showParams=false, writeToken}) {
|
|
|
1080
1090
|
status.recordingStatus = lroStatus.custom.status;
|
|
1081
1091
|
}
|
|
1082
1092
|
} catch(error) {
|
|
1083
|
-
console.log("LRO Status (failed): ", error.response
|
|
1093
|
+
console.log("LRO Status (failed): ", error.response?.statusCode);
|
|
1084
1094
|
status.state = "stopped";
|
|
1085
|
-
status.error = error.response;
|
|
1095
|
+
status.error = error.response || error.message;
|
|
1086
1096
|
return status;
|
|
1087
1097
|
}
|
|
1088
1098
|
|
|
@@ -3522,6 +3532,13 @@ exports.OutputsList = async function({libraryId, objectId, includeState=true}) {
|
|
|
3522
3532
|
method: "live/outputs",
|
|
3523
3533
|
constant: true
|
|
3524
3534
|
});
|
|
3535
|
+
} catch(error) {
|
|
3536
|
+
if(error.status === 404) {
|
|
3537
|
+
// Stream object may have been deleted
|
|
3538
|
+
return {};
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3541
|
+
throw error;
|
|
3525
3542
|
} finally {
|
|
3526
3543
|
restore();
|
|
3527
3544
|
}
|
|
@@ -3534,17 +3551,22 @@ exports.OutputsList = async function({libraryId, objectId, includeState=true}) {
|
|
|
3534
3551
|
const streamId = value.input?.stream;
|
|
3535
3552
|
if(!streamId) { return; }
|
|
3536
3553
|
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3554
|
+
try {
|
|
3555
|
+
const [streamLibraryId, streamStatus] = await Promise.all([
|
|
3556
|
+
this.ContentObjectLibraryId({objectId: streamId}),
|
|
3557
|
+
this.StreamStatus({name: streamId})
|
|
3558
|
+
]);
|
|
3559
|
+
|
|
3560
|
+
value.input.name = await this.ContentObjectMetadata({
|
|
3561
|
+
libraryId: streamLibraryId,
|
|
3562
|
+
objectId: streamId,
|
|
3563
|
+
metadataSubtree: "/public/name",
|
|
3564
|
+
});
|
|
3565
|
+
value.input.status = streamStatus?.state;
|
|
3566
|
+
} catch(error) {
|
|
3567
|
+
// Stream object may have been deleted
|
|
3568
|
+
value.input.status = "unavailable";
|
|
3569
|
+
}
|
|
3548
3570
|
}
|
|
3549
3571
|
);
|
|
3550
3572
|
|
|
@@ -3559,9 +3581,13 @@ exports.OutputsList = async function({libraryId, objectId, includeState=true}) {
|
|
|
3559
3581
|
|
|
3560
3582
|
const nodeUrls = {};
|
|
3561
3583
|
await this.utils.LimitedMap(10, nodeIds, async nodeId => {
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3584
|
+
try {
|
|
3585
|
+
const nodes = await this.SpaceNodes({matchNodeId: nodeId});
|
|
3586
|
+
const fabricUrl = nodes?.[0]?.services?.fabric_api?.urls?.[0];
|
|
3587
|
+
if(fabricUrl) { nodeUrls[nodeId] = fabricUrl; }
|
|
3588
|
+
} catch(error) {
|
|
3589
|
+
this.Log(`Failed to resolve node ${nodeId}: ${error.message}`, true);
|
|
3590
|
+
}
|
|
3565
3591
|
});
|
|
3566
3592
|
|
|
3567
3593
|
// Rewrite fabric-generated srt_pull URLs to the egress host, and group outputs by node
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
// Create or update a playable media object from specified file(s)
|
|
2
|
+
const R = require("ramda");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
|
|
5
|
+
const {ModOpt, NewOpt} = require("./lib/options");
|
|
6
|
+
const Utility = require("./lib/Utility");
|
|
7
|
+
|
|
8
|
+
const ABR = require("@eluvio/elv-abr-profile");
|
|
9
|
+
|
|
10
|
+
const Client = require("./lib/concerns/Client");
|
|
11
|
+
const CloudFile = require("./lib/concerns/CloudFile");
|
|
12
|
+
const Finalize = require("./lib/concerns/Finalize");
|
|
13
|
+
const LocalFile = require("./lib/concerns/LocalFile");
|
|
14
|
+
const LRO = require("./lib/concerns/LRO");
|
|
15
|
+
const ArgLibraryId = require("./lib/concerns/ArgLibraryId");
|
|
16
|
+
const ArgTenant = require("./lib/concerns/ArgTenant");
|
|
17
|
+
const {seconds} = require("./lib/helpers");
|
|
18
|
+
|
|
19
|
+
const AbrProfile = require("./lib/abr_profiles/abr_profile_4k_all.json");
|
|
20
|
+
const STATUS_FILE = "./ingest-status.json";
|
|
21
|
+
|
|
22
|
+
// Status object - used for reporting and resume.
|
|
23
|
+
const status = {
|
|
24
|
+
state: "",
|
|
25
|
+
object_id: "",
|
|
26
|
+
write_token: "",
|
|
27
|
+
master_created: false
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const SetStatus = statusUpdate => {
|
|
31
|
+
Object.assign(status, statusUpdate);
|
|
32
|
+
fs.writeFileSync(STATUS_FILE, `${JSON.stringify(status, null, 2)}\n`);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
class MediaIngest extends Utility {
|
|
36
|
+
blueprint() {
|
|
37
|
+
return {
|
|
38
|
+
concerns: [Client, CloudFile, Finalize, LocalFile, ArgLibraryId, ArgTenant, LRO],
|
|
39
|
+
options: [
|
|
40
|
+
ModOpt("libraryId", {demand: false, forX: "new media object"}),
|
|
41
|
+
NewOpt("objectId", {
|
|
42
|
+
demand: false,
|
|
43
|
+
descTemplate: "Existing media object to ingest into",
|
|
44
|
+
type: "string"
|
|
45
|
+
}),
|
|
46
|
+
NewOpt("title", {
|
|
47
|
+
demand: false,
|
|
48
|
+
descTemplate: "Title for new media object (required unless --object-id is given)",
|
|
49
|
+
type: "string"
|
|
50
|
+
}),
|
|
51
|
+
NewOpt("drm", {
|
|
52
|
+
default: false,
|
|
53
|
+
descTemplate: "Use DRM for playback",
|
|
54
|
+
type: "boolean"
|
|
55
|
+
}),
|
|
56
|
+
NewOpt("hdrInfoFile", {
|
|
57
|
+
descTemplate: "Path to HDR info JSON file to enable HDR10 ingest. File format: { \"master_display\": \"G(...)B(...)R(...)WP(...)L(...)\", \"max_cll\": \"...,...\" }",
|
|
58
|
+
type: "string"
|
|
59
|
+
}),
|
|
60
|
+
NewOpt("finalize", {
|
|
61
|
+
default: true,
|
|
62
|
+
descTemplate: "Finalize the content object write token after ingest. Use --no-finalize to leave the draft open.",
|
|
63
|
+
type: "boolean"
|
|
64
|
+
}),
|
|
65
|
+
NewOpt("offeringKey", {
|
|
66
|
+
default: "default",
|
|
67
|
+
descTemplate: "Offering key (default: 'default')",
|
|
68
|
+
type: "string"
|
|
69
|
+
}),
|
|
70
|
+
NewOpt("variantKey", {
|
|
71
|
+
default: "default",
|
|
72
|
+
descTemplate: "Master variant key (default: 'default')",
|
|
73
|
+
type: "string"
|
|
74
|
+
}),
|
|
75
|
+
ModOpt("files", {forX: "for new media object"})
|
|
76
|
+
]
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async body() {
|
|
81
|
+
const logger = this.logger;
|
|
82
|
+
|
|
83
|
+
const s3Reference = Boolean(this.args.s3Reference);
|
|
84
|
+
const useS3 = Boolean(this.args.s3Copy || s3Reference);
|
|
85
|
+
const access = useS3
|
|
86
|
+
? this.concerns.CloudFile.credentialSet()
|
|
87
|
+
: undefined;
|
|
88
|
+
|
|
89
|
+
let fileHandles = [];
|
|
90
|
+
const fileInfo = useS3
|
|
91
|
+
? this.concerns.CloudFile.fileInfo()
|
|
92
|
+
: this.concerns.LocalFile.fileInfo(fileHandles);
|
|
93
|
+
const fileConcern = useS3
|
|
94
|
+
? this.concerns.CloudFile
|
|
95
|
+
: this.concerns.LocalFile;
|
|
96
|
+
|
|
97
|
+
// delay getting elvClient until this point so script exits faster
|
|
98
|
+
// if there is a validation error above
|
|
99
|
+
const client = await this.concerns.Client.get();
|
|
100
|
+
|
|
101
|
+
// get type from Tenant
|
|
102
|
+
const tenantInfo = await this.concerns.ArgTenant.tenantInfo();
|
|
103
|
+
|
|
104
|
+
const type = tenantInfo.typeTitle;
|
|
105
|
+
|
|
106
|
+
if(R.isNil(type)) throw Error("Library does not specify content type for media ingests");
|
|
107
|
+
|
|
108
|
+
let {drm, finalize, hdrInfoFile, libraryId, objectId, offeringKey, title, variantKey} = this.args;
|
|
109
|
+
const noFinalize = !finalize;
|
|
110
|
+
const copy = !s3Reference;
|
|
111
|
+
const encrypt = !s3Reference;
|
|
112
|
+
|
|
113
|
+
let id;
|
|
114
|
+
let nodeUrl;
|
|
115
|
+
let writeToken;
|
|
116
|
+
if(status.write_token) {
|
|
117
|
+
id = status.object_id;
|
|
118
|
+
writeToken = status.write_token;
|
|
119
|
+
if(!libraryId) {
|
|
120
|
+
libraryId = await client.ContentObjectLibraryId({objectId: id});
|
|
121
|
+
}
|
|
122
|
+
// Re-pin client to the node holding the draft
|
|
123
|
+
nodeUrl = await client.WriteTokenNodeUrlNetwork({writeToken});
|
|
124
|
+
await client.SetNodes({fabricURIs: [nodeUrl]});
|
|
125
|
+
logger.log(`Resuming from write token ${writeToken} on ${nodeUrl}`);
|
|
126
|
+
} else {
|
|
127
|
+
if(!libraryId && !objectId) {
|
|
128
|
+
throw Error("One of library-id or object-id must be specified");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if(!objectId && !title) {
|
|
132
|
+
throw Error("--title is required when creating a new media object");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if(objectId) {
|
|
136
|
+
if(!libraryId) {
|
|
137
|
+
libraryId = await client.ContentObjectLibraryId({objectId});
|
|
138
|
+
}
|
|
139
|
+
const editResp = await client.EditContentObject({
|
|
140
|
+
libraryId,
|
|
141
|
+
objectId,
|
|
142
|
+
options: type ? { type } : {}
|
|
143
|
+
});
|
|
144
|
+
id = objectId;
|
|
145
|
+
writeToken = editResp.writeToken;
|
|
146
|
+
nodeUrl = editResp.nodeUrl;
|
|
147
|
+
} else {
|
|
148
|
+
const createResp = await client.CreateContentObject({
|
|
149
|
+
libraryId,
|
|
150
|
+
options: type ? { type } : {}
|
|
151
|
+
});
|
|
152
|
+
id = createResp.id;
|
|
153
|
+
writeToken = createResp.writeToken;
|
|
154
|
+
nodeUrl = createResp.nodeUrl;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
await client.authClient.GenerateAuthorizationToken({
|
|
158
|
+
libraryId,
|
|
159
|
+
objectId: id,
|
|
160
|
+
update: true
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
status.object_id = id;
|
|
164
|
+
status.write_token = writeToken;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
SetStatus({state: "object_created"});
|
|
168
|
+
|
|
169
|
+
if(!nodeUrl) {
|
|
170
|
+
nodeUrl = client.WriteTokenNodeUrlLocal({writeToken});
|
|
171
|
+
}
|
|
172
|
+
if(!nodeUrl) {
|
|
173
|
+
nodeUrl = await client.WriteTokenNodeUrlNetwork({writeToken});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
logger.data("object_id", id);
|
|
177
|
+
|
|
178
|
+
if(!status.master_created) {
|
|
179
|
+
logger.log("Uploading files...");
|
|
180
|
+
|
|
181
|
+
const createMasterResponse = await client.CreateProductionMaster({
|
|
182
|
+
libraryId,
|
|
183
|
+
type,
|
|
184
|
+
name: title,
|
|
185
|
+
fileInfo,
|
|
186
|
+
encrypt,
|
|
187
|
+
access,
|
|
188
|
+
copy,
|
|
189
|
+
callback: fileConcern.callback,
|
|
190
|
+
writeToken
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Close file handles (if any)
|
|
194
|
+
this.concerns.LocalFile.closeFileHandles(fileHandles);
|
|
195
|
+
|
|
196
|
+
logger.errorsAndWarnings(createMasterResponse);
|
|
197
|
+
|
|
198
|
+
logger.logList(
|
|
199
|
+
"",
|
|
200
|
+
"Production master default variant created:",
|
|
201
|
+
` Object ID: ${id}`,
|
|
202
|
+
` Write token: ${writeToken}`,
|
|
203
|
+
""
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
if(!R.isNil(createMasterResponse.errors) && !R.isEmpty(createMasterResponse.errors)) throw Error(`Error(s) encountered while inspecting uploaded files: ${createMasterResponse.errors.join("\n")}`);
|
|
207
|
+
|
|
208
|
+
status.master_created = true;
|
|
209
|
+
} else {
|
|
210
|
+
this.concerns.LocalFile.closeFileHandles(fileHandles);
|
|
211
|
+
logger.log(`Skipping upload + master creation (resume), write token ${writeToken}`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
SetStatus({state: "master_created", master_created: true});
|
|
215
|
+
|
|
216
|
+
// get production master metadata
|
|
217
|
+
const masterMetadata = (await client.ContentObjectMetadata({
|
|
218
|
+
libraryId,
|
|
219
|
+
objectId: id,
|
|
220
|
+
writeToken,
|
|
221
|
+
metadataSubtree: "/production_master"
|
|
222
|
+
}));
|
|
223
|
+
|
|
224
|
+
const sources = R.prop("sources", masterMetadata);
|
|
225
|
+
const variant = R.path(["variants", variantKey], masterMetadata);
|
|
226
|
+
if(R.isNil(variant)) throw Error(`Variant '${variantKey}' not found in production master metadata`);
|
|
227
|
+
|
|
228
|
+
// add info on source files and variant to data if --json selected
|
|
229
|
+
if(this.args.json) {
|
|
230
|
+
logger.data("media_files", sources);
|
|
231
|
+
logger.data("variant_key", variantKey);
|
|
232
|
+
logger.data("variant", variant);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// HDR: inject HDR info into first video stream's sources metadata
|
|
236
|
+
let hdrInfo;
|
|
237
|
+
if(hdrInfoFile) {
|
|
238
|
+
hdrInfo = JSON.parse(fs.readFileSync(hdrInfoFile, "utf8"));
|
|
239
|
+
const fileName = Object.keys(sources)[0];
|
|
240
|
+
const streams = sources[fileName].streams;
|
|
241
|
+
const videoStreamIdx = streams.findIndex(s => s.type === "StreamVideo");
|
|
242
|
+
if(videoStreamIdx < 0) throw Error(`No video stream found in /production_master/sources/${fileName}`);
|
|
243
|
+
|
|
244
|
+
streams[videoStreamIdx].hdr = hdrInfo;
|
|
245
|
+
logger.log(`Adding HDR info to /production_master/sources/${fileName}/streams/${videoStreamIdx}/hdr`);
|
|
246
|
+
|
|
247
|
+
await client.ReplaceMetadata({
|
|
248
|
+
libraryId,
|
|
249
|
+
objectId: id,
|
|
250
|
+
writeToken,
|
|
251
|
+
metadataSubtree: "/production_master/sources",
|
|
252
|
+
metadata: sources
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// generate ABR profile
|
|
257
|
+
const genProfileRetVal = ABR.ABRProfileForVariant(sources, variant, AbrProfile);
|
|
258
|
+
if(!genProfileRetVal.ok) throw Error(`Error(s) encountered while generating ABR profile: ${genProfileRetVal.errors.join("\n")}`);
|
|
259
|
+
|
|
260
|
+
// filter DRM/clear as needed
|
|
261
|
+
const filterProfileRetVal = drm ?
|
|
262
|
+
ABR.ProfileExcludeClear(genProfileRetVal.result) :
|
|
263
|
+
ABR.ProfileExcludeDRM(genProfileRetVal.result);
|
|
264
|
+
if(!filterProfileRetVal.ok) throw Error(`Error(s) encountered while setting playout formats: ${filterProfileRetVal.errors.join("\n")}`);
|
|
265
|
+
|
|
266
|
+
// HDR: add HEVC/10-bit segment specs to the profile
|
|
267
|
+
if(hdrInfo) {
|
|
268
|
+
filterProfileRetVal.result.segment_specs = filterProfileRetVal.result.segment_specs || {};
|
|
269
|
+
filterProfileRetVal.result.segment_specs.video = {
|
|
270
|
+
...(filterProfileRetVal.result.segment_specs.video || {}),
|
|
271
|
+
bit_depth: 10,
|
|
272
|
+
encoding: "h265"
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// set up mezzanine offering
|
|
277
|
+
logger.log("Setting up media file conversion...");
|
|
278
|
+
const createMezResponse = await client.CreateABRMezzanine({
|
|
279
|
+
name: title,
|
|
280
|
+
libraryId,
|
|
281
|
+
writeToken,
|
|
282
|
+
type,
|
|
283
|
+
masterWriteToken: writeToken,
|
|
284
|
+
variant: variantKey,
|
|
285
|
+
offeringKey,
|
|
286
|
+
abrProfile: filterProfileRetVal.result
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
logger.errorsAndWarnings(createMezResponse);
|
|
290
|
+
const createMezErrors = createMezResponse.errors;
|
|
291
|
+
if(!R.isNil(createMezErrors) && !R.isEmpty(createMezErrors)) throw Error(`Error(s) encountered while setting up media file conversion: ${createMezErrors.join("\n")}`);
|
|
292
|
+
|
|
293
|
+
SetStatus({state: "mez_created", mez_created: true});
|
|
294
|
+
|
|
295
|
+
logger.log("Starting conversion to streaming format...");
|
|
296
|
+
|
|
297
|
+
const startJobsResponse = await client.StartABRMezzanineJobs({
|
|
298
|
+
libraryId,
|
|
299
|
+
objectId: id,
|
|
300
|
+
offeringKey,
|
|
301
|
+
writeToken
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
logger.errorsAndWarnings(startJobsResponse);
|
|
305
|
+
const startJobsErrors = createMezResponse.errors;
|
|
306
|
+
if(!R.isNil(startJobsErrors) && !R.isEmpty(startJobsErrors)) throw Error(`Error(s) encountered while starting file conversion: ${startJobsErrors.join("\n")}`);
|
|
307
|
+
|
|
308
|
+
writeToken = startJobsResponse.writeToken || writeToken;
|
|
309
|
+
nodeUrl = startJobsResponse.nodeUrl || nodeUrl;
|
|
310
|
+
|
|
311
|
+
const lroWriteToken = R.path(["lro_draft", "write_token"], startJobsResponse) || writeToken;
|
|
312
|
+
const lroNode = R.path(["lro_draft", "node"], startJobsResponse) || nodeUrl;
|
|
313
|
+
|
|
314
|
+
logger.data("library_id", libraryId);
|
|
315
|
+
logger.data("object_id", id);
|
|
316
|
+
logger.data("offering_key", offeringKey);
|
|
317
|
+
logger.data("write_token", lroWriteToken);
|
|
318
|
+
logger.data("write_node", lroNode);
|
|
319
|
+
logger.data("node_endpoint", lroNode);
|
|
320
|
+
|
|
321
|
+
logger.logList(
|
|
322
|
+
"",
|
|
323
|
+
`Library ID: ${libraryId}`,
|
|
324
|
+
`Object ID: ${id}`,
|
|
325
|
+
`Variant: ${variantKey}`,
|
|
326
|
+
`Offering: ${offeringKey}`,
|
|
327
|
+
`Write Token: ${lroWriteToken}`,
|
|
328
|
+
`Write Node: ${lroNode}`,
|
|
329
|
+
""
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
SetStatus({state: "mez_started", mez_started: true, mez_job: startJobsResponse});
|
|
333
|
+
|
|
334
|
+
logger.log("Progress:");
|
|
335
|
+
|
|
336
|
+
const lro = this.concerns.LRO;
|
|
337
|
+
let done = false;
|
|
338
|
+
let lastStatus;
|
|
339
|
+
while(!done) {
|
|
340
|
+
const statusMap = await lro.status({libraryId, objectId: id, writeToken}); // TODO: check how offering key is used, if at all
|
|
341
|
+
const statusSummary = lro.statusSummary(statusMap);
|
|
342
|
+
lastStatus = statusSummary.run_state;
|
|
343
|
+
if(lastStatus !== LRO.STATE_RUNNING) done = true;
|
|
344
|
+
logger.log(`run_state: ${lastStatus}`);
|
|
345
|
+
const eta = statusSummary.estimated_time_left_h_m_s;
|
|
346
|
+
if(eta) logger.log(`estimated time left: ${eta}`);
|
|
347
|
+
await seconds(15);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const finalizeAbrResponse = await client.FinalizeABRMezzanine({
|
|
351
|
+
libraryId,
|
|
352
|
+
objectId: id,
|
|
353
|
+
writeToken,
|
|
354
|
+
offeringKey
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
logger.errorsAndWarnings(finalizeAbrResponse);
|
|
358
|
+
const finalizeErrors = finalizeAbrResponse.errors;
|
|
359
|
+
if(!R.isNil(finalizeErrors) && !R.isEmpty(finalizeErrors)) throw Error(`Error(s) encountered while finalizing object: ${finalizeErrors.join("\n")}`);
|
|
360
|
+
|
|
361
|
+
if(s3Reference) {
|
|
362
|
+
logger.log("Retaining S3 source file reference.");
|
|
363
|
+
} else {
|
|
364
|
+
logger.log("Deleting source files...");
|
|
365
|
+
await client.DeleteFiles({
|
|
366
|
+
libraryId,
|
|
367
|
+
objectId: id,
|
|
368
|
+
writeToken,
|
|
369
|
+
filePaths: fileInfo.map(f => f.path)
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
nodeUrl = client.WriteTokenNodeUrlLocal({writeToken}) || nodeUrl;
|
|
373
|
+
|
|
374
|
+
let latestHash = "not finalized";
|
|
375
|
+
|
|
376
|
+
if(noFinalize) {
|
|
377
|
+
logger.log("Skipping content object finalization (--no-finalize).");
|
|
378
|
+
} else {
|
|
379
|
+
logger.log("Finalizing content object...");
|
|
380
|
+
const sourceFileName = fileInfo[0].path.split("/").pop();
|
|
381
|
+
const finalizeObjResponse = await client.FinalizeContentObject({
|
|
382
|
+
libraryId,
|
|
383
|
+
objectId: id,
|
|
384
|
+
writeToken,
|
|
385
|
+
commitMessage: `Ingest ${sourceFileName}${hdrInfoFile ? " [HDR]" : ""}`
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
latestHash = finalizeObjResponse.hash;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
logger.data("version_hash", latestHash);
|
|
392
|
+
logger.data("write_token", writeToken);
|
|
393
|
+
logger.data("write_node", nodeUrl);
|
|
394
|
+
logger.data("node_endpoint", nodeUrl);
|
|
395
|
+
|
|
396
|
+
logger.logList(
|
|
397
|
+
"",
|
|
398
|
+
noFinalize ? "Playable media object draft prepared:" : "Playable media object created:",
|
|
399
|
+
` Object ID: ${id}`,
|
|
400
|
+
` Version Hash: ${latestHash}`,
|
|
401
|
+
` Write Token: ${writeToken}`,
|
|
402
|
+
` Node Endpoint: ${nodeUrl}`,
|
|
403
|
+
""
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
if(!noFinalize) {
|
|
407
|
+
await this.concerns.Finalize.waitForPublish({
|
|
408
|
+
latestHash,
|
|
409
|
+
libraryId,
|
|
410
|
+
objectId: id
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
header() {
|
|
416
|
+
return "Create playable media object via media ingest";
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if(require.main === module) {
|
|
421
|
+
Utility.cmdLineInvoke(MediaIngest);
|
|
422
|
+
} else {
|
|
423
|
+
module.exports = MediaIngest;
|
|
424
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const {NewOpt} = require("./lib/options");
|
|
2
|
+
const Utility = require("./lib/Utility");
|
|
3
|
+
|
|
4
|
+
const Client = require("./lib/concerns/Client");
|
|
5
|
+
const Edit = require("./lib/concerns/Edit");
|
|
6
|
+
|
|
7
|
+
class MigrateEncryptionConkForNewUser extends Utility {
|
|
8
|
+
blueprint() {
|
|
9
|
+
return {
|
|
10
|
+
concerns: [Client, Edit],
|
|
11
|
+
options: [
|
|
12
|
+
NewOpt("objectId",
|
|
13
|
+
{
|
|
14
|
+
descTemplate: "object Id",
|
|
15
|
+
type: "string",
|
|
16
|
+
demand: true
|
|
17
|
+
}),
|
|
18
|
+
NewOpt("publicKey",
|
|
19
|
+
{
|
|
20
|
+
descTemplate: "new user public key with/without encoded form",
|
|
21
|
+
type: "string",
|
|
22
|
+
demand: true
|
|
23
|
+
})
|
|
24
|
+
]
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async body() {
|
|
29
|
+
const logger = this.logger;
|
|
30
|
+
const {objectId, publicKey} = this.args;
|
|
31
|
+
|
|
32
|
+
const client = await this.concerns.Client.get();
|
|
33
|
+
const libraryId = await client.ContentObjectLibraryId({objectId});
|
|
34
|
+
|
|
35
|
+
const res = await client.EditContentObject({libraryId, objectId});
|
|
36
|
+
|
|
37
|
+
await client.MigrateEncryptionConkForUserProvided({
|
|
38
|
+
libraryId,
|
|
39
|
+
objectId,
|
|
40
|
+
writeToken: res.writeToken,
|
|
41
|
+
newEncodedUserPublicKey: publicKey
|
|
42
|
+
});
|
|
43
|
+
logger.log("Migration of encryption conk is complete...");
|
|
44
|
+
|
|
45
|
+
const versionHash = await this.concerns.Edit.finalize({
|
|
46
|
+
libraryId,
|
|
47
|
+
objectId,
|
|
48
|
+
writeToken:res.writeToken
|
|
49
|
+
});
|
|
50
|
+
logger.log("version hash", versionHash);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
header() {
|
|
54
|
+
return `Add encryption conk for user provided '${this.args.objectId}':`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if(require.main === module) {
|
|
59
|
+
Utility.cmdLineInvoke(MigrateEncryptionConkForNewUser);
|
|
60
|
+
} else {
|
|
61
|
+
module.exports = MigrateEncryptionConkForNewUser;
|
|
62
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Create new production master from specified file(s)
|
|
2
2
|
|
|
3
|
-
const {ModOpt, StdOpt} = require("./lib/options");
|
|
3
|
+
const {ModOpt, StdOpt, NewOpt} = require("./lib/options");
|
|
4
4
|
const Utility = require("./lib/Utility");
|
|
5
5
|
|
|
6
6
|
const ArgNoWait = require("./lib/concerns/ArgNoWait");
|
|
@@ -16,14 +16,19 @@ class ObjectAddFiles extends Utility {
|
|
|
16
16
|
concerns: [Logger, ExistObj, Edit, ArgNoWait, LocalFile, CloudFile],
|
|
17
17
|
options: [
|
|
18
18
|
ModOpt("files", {X: "to add"}),
|
|
19
|
-
StdOpt("encrypt", {X: "uploaded files"})
|
|
19
|
+
StdOpt("encrypt", {X: "uploaded files"}),
|
|
20
|
+
NewOpt("resume", {
|
|
21
|
+
descTemplate: "If specified, resume jobs for the given write token (--resume 'write_token')",
|
|
22
|
+
group: "Main",
|
|
23
|
+
type: "string"
|
|
24
|
+
})
|
|
20
25
|
]
|
|
21
26
|
};
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
async body() {
|
|
25
30
|
const logger = this.logger;
|
|
26
|
-
const {encrypt, noWait} = this.args;
|
|
31
|
+
const {encrypt, noWait, resume: resumeArg} = this.args;
|
|
27
32
|
|
|
28
33
|
let access;
|
|
29
34
|
if(this.args.s3Reference || this.args.s3Copy) access = this.concerns.CloudFile.credentialSet();
|
|
@@ -35,10 +40,22 @@ class ObjectAddFiles extends Utility {
|
|
|
35
40
|
|
|
36
41
|
const {libraryId, objectId} = await this.concerns.ExistObj.argsProc();
|
|
37
42
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
let writeToken;
|
|
44
|
+
let resume=false;
|
|
45
|
+
if(resumeArg) {
|
|
46
|
+
writeToken = resumeArg;
|
|
47
|
+
resume=true;
|
|
48
|
+
} else {
|
|
49
|
+
// create new write token
|
|
50
|
+
writeToken = await this.concerns.Edit.getWriteToken({
|
|
51
|
+
libraryId,
|
|
52
|
+
objectId
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
logger.data("WRITE_TOKEN", writeToken);
|
|
56
|
+
// eslint-disable-next-line no-console
|
|
57
|
+
console.log("Using write token:", writeToken);
|
|
58
|
+
|
|
42
59
|
|
|
43
60
|
if(access) {
|
|
44
61
|
await this.concerns.CloudFile.add({
|
|
@@ -47,7 +64,8 @@ class ObjectAddFiles extends Utility {
|
|
|
47
64
|
writeToken,
|
|
48
65
|
access,
|
|
49
66
|
fileInfo,
|
|
50
|
-
encrypt
|
|
67
|
+
encrypt,
|
|
68
|
+
resume,
|
|
51
69
|
});
|
|
52
70
|
} else {
|
|
53
71
|
await this.concerns.LocalFile.add({
|
|
@@ -55,12 +73,14 @@ class ObjectAddFiles extends Utility {
|
|
|
55
73
|
objectId,
|
|
56
74
|
writeToken,
|
|
57
75
|
fileInfo,
|
|
58
|
-
encrypt
|
|
76
|
+
encrypt,
|
|
77
|
+
resume,
|
|
59
78
|
});
|
|
60
79
|
// Close file handles
|
|
61
80
|
this.concerns.LocalFile.closeFileHandles(fileHandles);
|
|
62
81
|
}
|
|
63
82
|
|
|
83
|
+
// finalize the write token
|
|
64
84
|
const hash = await this.concerns.Edit.finalize({
|
|
65
85
|
libraryId,
|
|
66
86
|
noWait,
|