@eluvio/elv-client-js 3.1.66 → 3.1.67

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.
@@ -0,0 +1,205 @@
1
+ const R = require("ramda");
2
+
3
+ const {ModOpt, NewOpt, StdOpt} = require("./lib/options");
4
+ const Utility = require("./lib/Utility");
5
+
6
+ const ArgLibraryId = require("./lib/concerns/ArgLibraryId");
7
+ const ArgType = require("./lib/concerns/ArgType");
8
+ const FabricObject = require("./lib/concerns/FabricObject");
9
+ const Version = require("./lib/concerns/Version");
10
+
11
+ const STREAM_FIELDS = [
12
+ "aspect_ratio",
13
+ "channel_layout",
14
+ "channels",
15
+ "codec_name",
16
+ "codec_type",
17
+ "height",
18
+ "rate",
19
+ "width"
20
+ ];
21
+
22
+ const itemParser = itemStr => {
23
+ const parsed = itemStr.split("/");
24
+ if(parsed.length !== 2) throw Error(`Failed to parse item '${itemStr} - each item should be a version hash + slash + offering key, e.g. 'hq__1234/default'`);
25
+ return {hash: parsed[0], offering: parsed[1]};
26
+ };
27
+
28
+ const mediaStructStreamFields = R.pick(STREAM_FIELDS);
29
+
30
+ const msStreamsFieldSubset = R.map(mediaStructStreamFields);
31
+
32
+ // return media_struct stream keys appearing in playout.streams.representations (usually only 1)
33
+ const poStreamMsStreamKeys = poStream => R.uniq(R.map(R.prop("media_struct_stream_key"), poStream.representations));
34
+
35
+ // return fields that need to be checked from media_struct.streams, include only streams referred to by playout.streams
36
+ const msStreamFields = (poStreams, msStreams) => {
37
+ const msStreamKeys = R.uniq(R.values(R.map(poStreamMsStreamKeys, poStreams)));
38
+ const usedMsStreams = R.pick(msStreamKeys, msStreams);
39
+ return R.map(msStreamsFieldSubset, usedMsStreams);
40
+ };
41
+
42
+ const withoutDrmContentId = poFormat => {
43
+ const clone = R.clone(poFormat);
44
+ if(clone.drm) clone.drm.content_id = null;
45
+ return clone;
46
+ };
47
+
48
+ const withoutEncryptionSchemes = poStream => {
49
+ const clone = R.clone(poStream);
50
+ clone.encryption_schemes = null;
51
+ return clone;
52
+ };
53
+
54
+
55
+ class ChannelCreate extends Utility {
56
+ blueprint() {
57
+ return {
58
+ concerns: [ArgLibraryId, ArgType, FabricObject, Version],
59
+ options: [
60
+ ModOpt("libraryId", {demand: true}),
61
+ ModOpt("type", {demand: true}),
62
+ StdOpt("name",
63
+ {
64
+ demand: true,
65
+ forX: "channel object"
66
+ }),
67
+ NewOpt("items", {
68
+ demand: true,
69
+ descTemplate: "List of channel items, separated by spaces. Each item should be of form VERSION_HASH/OFFERING_KEY, e.g. hq__12345/default ",
70
+ string: true,
71
+ type: "array"
72
+ }),
73
+ NewOpt("val", {
74
+ descTemplate: "VoD-as-Live (make the channel a simulated live stream)",
75
+ type: "boolean"
76
+ })
77
+ ]
78
+ };
79
+ }
80
+
81
+ async body() {
82
+ const logger = this.logger;
83
+ const {name, items, libraryId, val} = this.args;
84
+ const type = await this.concerns.ArgType.typVersionHash();
85
+
86
+ const itemList = R.map(itemParser, items);
87
+
88
+ // check items
89
+ logger.log("\nChecking items for any parameter mismatches...");
90
+
91
+ // get items
92
+ const itemOfferings = [];
93
+ for(let i = 0; i < itemList.length; i++) {
94
+ const item = itemList[i];
95
+ const versionHash = item.hash;
96
+ const objectId = this.concerns.Version.objectId({versionHash});
97
+ const offering = await this.concerns.FabricObject.metadata({
98
+ libraryId,
99
+ objectId,
100
+ subtree: "/offerings/" + item.offering,
101
+ versionHash
102
+ });
103
+ itemOfferings.push(offering);
104
+ }
105
+
106
+ // make sure streams, playout formats, and ladders are the same
107
+ const firstPoStreams = R.map(withoutEncryptionSchemes, itemOfferings[0].playout.streams);
108
+ const firstPoFormats = R.map(withoutDrmContentId, itemOfferings[0].playout.playout_formats);
109
+ const firstMsStreamFields = msStreamFields(firstPoStreams, itemOfferings[0].media_struct.streams);
110
+
111
+ for(let i = 1; i < itemList.length; i++) {
112
+ const testPoStreams = R.map(withoutEncryptionSchemes, itemOfferings[i].playout.streams);
113
+ //
114
+ // console.log(JSON.stringify(firstPoStreams,null,2));
115
+ // console.log(JSON.stringify(testPoStreams,null,2));
116
+
117
+ if(!R.equals(firstPoStreams, testPoStreams)) throw Error("ERROR: All items must have identical playout.streams.representations");
118
+
119
+ const testPoFormats = R.map(withoutDrmContentId, itemOfferings[i].playout.playout_formats);
120
+ if(!R.equals(firstPoFormats, testPoFormats)) throw Error("ERROR: All items must have identical playout.playout_formats (other than widevine content_id field)");
121
+
122
+ const testMsStreamFields = msStreamFields(testPoStreams, itemOfferings[i].media_struct.streams);
123
+ if(!R.equals(firstMsStreamFields, testMsStreamFields)) throw Error("ERROR: All items must have matching values for included streams in the following media_struct stream fields: " + STREAM_FIELDS.join(", "));
124
+ }
125
+ // passed checks, assemble channel items metadata
126
+ logger.log("Mezzanine item parameter checks passed.");
127
+
128
+ // create new object
129
+ let metadata = {public: {name}};
130
+
131
+ logger.log("\nCreating new channel object...");
132
+
133
+ const {objectId} = await this.concerns.ArgLibraryId.libCreateObject({
134
+ metadata,
135
+ type
136
+ });
137
+
138
+ logger.log("\nAdding channel metadata to new object...");
139
+
140
+ // retrieve newly created object's metadata
141
+ metadata = await this.concerns.FabricObject.metadata({libraryId, objectId});
142
+
143
+ const channelMeta = {
144
+ offerings: {
145
+ default: {
146
+ items: [],
147
+ live_end_tol: 60,
148
+ live_seg_count: 60,
149
+ playout: {
150
+ playout_formats: itemOfferings[0].playout.playout_formats,
151
+ streams: itemOfferings[0].playout.streams
152
+ },
153
+ playout_type: (val ? "ch_val" : "ch_vod")
154
+ }
155
+ }
156
+ };
157
+
158
+ // add items
159
+ for(let i = 0; i < itemList.length; i++) {
160
+ const offering = itemOfferings[i];
161
+ const item = itemList[i];
162
+ const itemMeta = {
163
+ duration_rat: offering.media_struct.duration_rat,
164
+ source: {
165
+ ".": {
166
+ auto_update: {
167
+ tag: "latest"
168
+ }
169
+ },
170
+ "/": `/qfab/${item.hash}/rep/playout/${item.offering}`
171
+ },
172
+ type: "mez_vod"
173
+ };
174
+ channelMeta.offerings.default.items.push(itemMeta);
175
+ }
176
+
177
+ metadata.channel = channelMeta;
178
+ metadata.public.offerings = {"/": "./rep/channel/options.json"};
179
+
180
+
181
+ // Write back metadata
182
+ logger.log("Writing metadata...");
183
+ const versionHash = await this.concerns.Metadata.write({
184
+ libraryId,
185
+ metadata,
186
+ objectId
187
+ });
188
+
189
+ logger.log("");
190
+ logger.log(`New object ID: ${objectId}`);
191
+ logger.data("object_id", objectId);
192
+ logger.log("New version hash: " + versionHash);
193
+ logger.data("version_hash", versionHash);
194
+ }
195
+
196
+ header() {
197
+ return `Create channel '${this.args.name}' in lib ${this.args.libraryId}`;
198
+ }
199
+ }
200
+
201
+ if(require.main === module) {
202
+ Utility.cmdLineInvoke(ChannelCreate);
203
+ } else {
204
+ module.exports = ChannelCreate;
205
+ }
@@ -53,6 +53,8 @@ class ChannelStartVaLOffering extends Utility {
53
53
  // ----------------------------------------------------
54
54
  const {delay, libraryId, objectId, offeringKey} = await this.concerns.ExistObj.argsProc();
55
55
 
56
+ const client = await this.concerns.Client.get();
57
+
56
58
  logger.log("Retrieving existing metadata from object...");
57
59
  const currentMetadata = await this.concerns.ExistObj.metadata();
58
60
 
@@ -83,7 +85,34 @@ class ChannelStartVaLOffering extends Utility {
83
85
  0
84
86
  );
85
87
 
86
- const liveSegCount = this.args.liveWindowSize || currentMetadata.channel.offerings[offeringKey].live_seg_count || 3;
88
+ // get item list from channel offering, get an auth token for each to include in playout URLs
89
+ const mezAuthTokens = [];
90
+ const items = currentMetadata.channel.offerings[offeringKey].items;
91
+
92
+ const RE_URI_HASH = /\/(hq__[a-zA-Z0-9]+)\//;
93
+ logger.log("Generating auth tokens...");
94
+ for(let i = 0; i < items.length; i++) {
95
+ const itemURI = items[i].source["/"];
96
+ const mezVersionHash = RE_URI_HASH.exec(itemURI)[1];
97
+ if(!mezVersionHash) throw Error(`hash not found in item URI: ${itemURI}`);
98
+
99
+ const mezObjectId = this.concerns.Version.objectId({versionHash: mezVersionHash});
100
+ const mezLibId = await this.concerns.FabricObject.libraryId({objectId: mezObjectId});
101
+
102
+ mezAuthTokens.push(
103
+ await client.authClient.AuthorizationToken(
104
+ {
105
+ libraryId: mezLibId,
106
+ objectId: mezObjectId,
107
+ versionHash: mezVersionHash,
108
+ update: false
109
+ }
110
+ )
111
+ );
112
+ }
113
+
114
+
115
+ const liveSegCount = this.args.liveWindowSize || currentMetadata.channel.offerings[offeringKey].live_seg_count || 60;
87
116
  const liveEndTol = this.args.liveEndTolerance || currentMetadata.channel.offerings[offeringKey].live_end_tol || 300;
88
117
 
89
118
  const now = new Date; // ).toISOString();
@@ -114,7 +143,6 @@ class ChannelStartVaLOffering extends Utility {
114
143
  objectId
115
144
  });
116
145
 
117
- const client = await this.concerns.Client.get();
118
146
  const url = await client.FabricUrl({
119
147
  libraryId,
120
148
  objectId,
@@ -154,14 +182,15 @@ class ChannelStartVaLOffering extends Utility {
154
182
  });
155
183
 
156
184
  let offUrlObj = new URL(offeringUrl);
157
- const urlBase = offUrlObj.origin + offUrlObj.pathname;
185
+ const urlBase = offUrlObj.origin + offUrlObj.pathname;
158
186
  const authToken = offUrlObj.searchParams.get("authorization");
159
187
  let sid = "";
160
188
  for(const [playoutFormatKey, playoutFormatInfo] of Object.entries(offeringOptions)) {
161
189
  const pfUrlObj = new URL(playoutFormatInfo.uri, urlBase);
162
190
  sid = pfUrlObj.searchParams.get("sid");
163
- const playoutUrl = new URL(playoutFormatInfo.uri, urlBase);
191
+ const playoutUrl = new URL(playoutFormatInfo.uri, urlBase);
164
192
  playoutUrl.searchParams.set("authorization", authToken);
193
+ mezAuthTokens.forEach(t => playoutUrl.searchParams.append("authorization", t));
165
194
  playoutUrl.searchParams.set("sid", sid);
166
195
 
167
196
  logger.log();
@@ -170,37 +199,42 @@ class ChannelStartVaLOffering extends Utility {
170
199
  logger.log(playoutUrl.toString());
171
200
  }
172
201
 
173
- const viewsUrl = await client.FabricUrl({
174
- libraryId,
175
- objectId,
176
- versionHash,
177
- rep: `channel/${offeringKey}/views.json`
178
- });
179
- const viewsUrlObj = new URL(viewsUrl);
180
- viewsUrlObj.searchParams.set("sid", sid);
181
- logger.log();
182
- logger.log(`Sample offering '${offeringKey}' current available views URL (sid must be same as in playout URL):`);
183
- logger.log();
184
- logger.log(viewsUrlObj.toString());
202
+ const multiviewPresent = currentMetadata.channel.offerings[offeringKey].multiview &&
203
+ !R.empty(currentMetadata.channel.offerings[offeringKey].multiview);
204
+ if(multiviewPresent) {
205
+ const viewsUrl = await client.FabricUrl({
206
+ libraryId,
207
+ objectId,
208
+ versionHash,
209
+ rep: `channel/${offeringKey}/views.json`
210
+ });
211
+ const viewsUrlObj = new URL(viewsUrl);
212
+ viewsUrlObj.searchParams.set("sid", sid);
213
+ logger.log();
214
+ logger.log(`Sample offering '${offeringKey}' current available views URL (sid must be same as in playout URL):`);
215
+ logger.log();
216
+ logger.log(viewsUrlObj.toString());
185
217
 
186
218
 
187
- const selectViewUrl = await client.FabricUrl({
188
- libraryId,
189
- objectId,
190
- versionHash,
191
- rep: `channel/${offeringKey}/select_view`
192
- });
193
- const viewSelectUrlObj = new URL(selectViewUrl);
194
- viewSelectUrlObj.searchParams.set("sid", sid);
219
+ const selectViewUrl = await client.FabricUrl({
220
+ libraryId,
221
+ objectId,
222
+ versionHash,
223
+ rep: `channel/${offeringKey}/select_view`
224
+ });
225
+ const viewSelectUrlObj = new URL(selectViewUrl);
226
+ viewSelectUrlObj.searchParams.set("sid", sid);
227
+
228
+ logger.log();
229
+ logger.log("Sample curl command to select view (sid must be same as in playout URL):");
230
+ logger.log();
231
+ logger.log(`curl -X POST '${viewSelectUrlObj.toString()}' -d '{"view":1}'`);
232
+ }
195
233
 
196
- logger.log();
197
- logger.log("Sample curl command to select view (sid must be same as in playout URL):");
198
- logger.log();
199
- logger.log(`curl -X POST '${viewSelectUrlObj.toString()}' -d '{"view":1}'`);
200
234
  }
201
235
 
202
236
  header() {
203
- return `Start playout of 'live' channel object ${this.args.objectId}${this.args.delay ? ` with ${this.args.delay} second(s) delay` : ""}`;
237
+ return `Start playout of simulated live channel object ${this.args.objectId}${this.args.delay ? ` with ${this.args.delay} second(s) delay` : ""}`;
204
238
  }
205
239
  }
206
240
 
@@ -53,6 +53,8 @@ class LibraryListObjects extends Utility {
53
53
  const logger = this.logger;
54
54
  const filter = this.args.filter && this.concerns.JSON.parseStringOrFile({strOrPath: this.args.filter});
55
55
 
56
+ if(!this.args.fields) this.args.fields = [];
57
+
56
58
  const select = ["/public/name", ...this.args.fields];
57
59
 
58
60
  const objectList = await this.concerns.ArgLibraryId.libObjectList(
@@ -0,0 +1,69 @@
1
+ // go through offerings and set all audio streams to use same encryption keys
2
+
3
+ const R = require("ramda");
4
+
5
+ const Utility = require("./lib/Utility");
6
+
7
+ const ExistObj = require("./lib/concerns/ExistObj");
8
+ const Metadata = require("./lib/concerns/Metadata");
9
+
10
+ class MezUnifyAudioDrmKeys extends Utility {
11
+ blueprint() {
12
+ return {
13
+ concerns: [
14
+ ExistObj, Metadata
15
+ ]
16
+ };
17
+ }
18
+
19
+ async body() {
20
+ const logger = this.logger;
21
+
22
+ // operations that need to wait on network access
23
+ // ----------------------------------------------------
24
+ const {libraryId, objectId} = await this.concerns.ExistObj.argsProc();
25
+
26
+ logger.log("Retrieving existing metadata from object...");
27
+ const metadata = await this.concerns.ExistObj.metadata();
28
+
29
+ if(!metadata.offerings || R.isEmpty(metadata.offerings)) throw Error("no offerings found in metadata");
30
+
31
+ // loop through offerings
32
+ for(const [offeringKey, offering] of Object.entries(metadata.offerings)) {
33
+ logger.log(` Checking offering ${offeringKey}...`);
34
+ // loop through playout streams, saving first audio stream's keys
35
+ let keyIds;
36
+ for(const [streamKey, stream] of Object.entries(offering.playout.streams)) {
37
+ if(stream.representations && Object.entries(stream.representations)[0][1].type === "RepAudio") {
38
+ if(keyIds) {
39
+ logger.log(` Setting keys for stream '${streamKey}'...`);
40
+ stream.encryption_schemes = R.clone(keyIds);
41
+ } else {
42
+ if(!stream.encryption_schemes || R.isEmpty(stream.encryption_schemes)) throw Error(`Audio stream ${streamKey} has no encryption scheme info`);
43
+ logger.log(` Using keys from stream '${streamKey}'...`);
44
+ keyIds = stream.encryption_schemes;
45
+ }
46
+ }
47
+ }
48
+ }
49
+
50
+ // Write back metadata
51
+ const newHash = await this.concerns.Metadata.write({
52
+ libraryId,
53
+ metadata,
54
+ objectId
55
+ });
56
+ this.logger.data("version_hash", newHash);
57
+ this.logger.log("New version hash: " + newHash);
58
+ }
59
+
60
+ header() {
61
+ return `Make all audio streams use same DRM keys in object ${this.args.objectId}`;
62
+ }
63
+ }
64
+
65
+ if(require.main === module) {
66
+ Utility.cmdLineInvoke(MezUnifyAudioDrmKeys);
67
+ } else {
68
+ module.exports = MezUnifyAudioDrmKeys;
69
+ }