@eluvio/elv-client-js 4.0.94 → 4.0.95

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.
@@ -1878,11 +1878,11 @@ var ElvWalletClient = /*#__PURE__*/function () {
1878
1878
  key: "DeployTenant",
1879
1879
  value: function () {
1880
1880
  var _DeployTenant = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee24(_ref23) {
1881
- var tenantId, _ref23$tenantSlug, tenantSlug, tenantHash, tenantLink, deployedTenantHash, body, token;
1881
+ var tenantId, _ref23$tenantSlug, tenantSlug, tenantHash, _ref23$environment, environment, tenantLink, deployedTenantHash, body, token;
1882
1882
  return _regeneratorRuntime.wrap(function _callee24$(_context24) {
1883
1883
  while (1) switch (_context24.prev = _context24.next) {
1884
1884
  case 0:
1885
- tenantId = _ref23.tenantId, _ref23$tenantSlug = _ref23.tenantSlug, tenantSlug = _ref23$tenantSlug === void 0 ? "" : _ref23$tenantSlug, tenantHash = _ref23.tenantHash;
1885
+ tenantId = _ref23.tenantId, _ref23$tenantSlug = _ref23.tenantSlug, tenantSlug = _ref23$tenantSlug === void 0 ? "" : _ref23$tenantSlug, tenantHash = _ref23.tenantHash, _ref23$environment = _ref23.environment, environment = _ref23$environment === void 0 ? "production" : _ref23$environment;
1886
1886
  if (tenantHash) {
1887
1887
  _context24.next = 11;
1888
1888
  break;
@@ -1916,6 +1916,7 @@ var ElvWalletClient = /*#__PURE__*/function () {
1916
1916
  case 11:
1917
1917
  body = {
1918
1918
  content_hash: tenantHash,
1919
+ env: environment,
1919
1920
  ts: Date.now()
1920
1921
  };
1921
1922
  _context24.next = 14;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eluvio/elv-client-js",
3
- "version": "4.0.94",
3
+ "version": "4.0.95",
4
4
  "description": "Javascript client for the Eluvio Content Fabric",
5
5
  "main": "src/index.js",
6
6
  "author": "Kevin Talmadge",
@@ -1388,7 +1388,7 @@ class ElvWalletClient {
1388
1388
  }
1389
1389
  }
1390
1390
 
1391
- async DeployTenant({tenantId, tenantSlug="", tenantHash}) {
1391
+ async DeployTenant({tenantId, tenantSlug="", tenantHash, environment="production"}) {
1392
1392
  if(!tenantHash) {
1393
1393
  const tenantLink = await this.client.ContentObjectMetadata({
1394
1394
  libraryId: this.mainSiteLibraryId,
@@ -1412,7 +1412,7 @@ class ElvWalletClient {
1412
1412
  tenantHash = await this.client.LatestVersionHash({versionHash: deployedTenantHash});
1413
1413
  }
1414
1414
 
1415
- const body = { content_hash: tenantHash, ts: Date.now() };
1415
+ const body = { content_hash: tenantHash, env: environment, ts: Date.now() };
1416
1416
  const token = await this.client.Sign(JSON.stringify(body));
1417
1417
  await this.client.authClient.MakeAuthServiceRequest({
1418
1418
  path: UrlJoin("as", "tnt", "config", tenantId, "metadata"),
@@ -15,7 +15,7 @@ const Tool = async () => {
15
15
  });
16
16
 
17
17
  client.SetSigner({signer});
18
- client.ToggleLogging(true);
18
+ //client.ToggleLogging(true);
19
19
 
20
20
 
21
21
  try {
@@ -0,0 +1,291 @@
1
+ /* eslint-disable no-console */
2
+ const ScriptBase = require("./parentClasses/ScriptBase");
3
+ const utils = require("../src/Utils");
4
+ const Fraction = require("fraction.js");
5
+ const {JSONPath} = require("jsonpath-plus");
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+ const { execSync } = require("child_process");
9
+
10
+ class OfferingDownloadMedia2 extends ScriptBase {
11
+
12
+ async body(){
13
+
14
+ let objectId=this.args.objectId;
15
+ const versionHash=this.args.versionHash;
16
+ const offeringKey=this.args.offeringKey;
17
+ const streamKey=this.args.streamKey;
18
+ const startTime=this.args.startTime;
19
+ let endTime=this.args.endTime;
20
+ const out=this.args.out;
21
+
22
+ if(!objectId && !versionHash) throw new Error("Require object-id or object-hash to be provided");
23
+ if(!objectId) {
24
+ objectId = utils.DecodeVersionHash(versionHash).objectId;
25
+ }
26
+
27
+ if(startTime < 0) {
28
+ throw new Error(`start time provided needs to be greater than 0: start=${startTime}s`);
29
+ }
30
+ if(startTime >= endTime) {
31
+ throw new Error(`end time needs to be greater than start time, values provided: start=${startTime}s, end=${endTime}s`);
32
+ }
33
+
34
+ const client=await this.client();
35
+ const libraryId=await client.ContentObjectLibraryId({objectId, versionHash});
36
+
37
+ // get object metadata
38
+ const metadata = await client.ContentObjectMetadata({ libraryId, objectId });
39
+
40
+ const offeringMetadata=JSONPath({
41
+ json: metadata,
42
+ path: `offerings.${offeringKey}`,
43
+ wrap: false
44
+ });
45
+
46
+ const transcodesMetadata=JSONPath({
47
+ json:metadata,
48
+ path: "transcodes"
49
+ });
50
+
51
+ // Retrieve stream metadata from transcodes or offering metadata
52
+ // Create a map of streamKey => stream metadata
53
+ const streamsMap = Object.fromEntries(
54
+ Object.keys(offeringMetadata.playout.streams).map(streamKey => {
55
+ const info = this.getStreamInfo(offeringMetadata, transcodesMetadata, streamKey);
56
+ if (info === undefined) {
57
+ console.log(`StreamInfo returned undefined for streamKey: ${streamKey}`);
58
+ }
59
+ return [streamKey, info || {}];
60
+ })
61
+ );
62
+
63
+ let streamKeyArray = streamKey.split(",").map(key => key.trim()); // trim() to remove any extra spaces
64
+ const streamsMapKeys = new Set(Object.keys(streamsMap));
65
+
66
+ // Filter the streamKeyArray to only include keys present in streamsMap
67
+ streamKeyArray = streamKeyArray.filter(key => {
68
+ if (!streamsMapKeys.has(key)) {
69
+ console.warn(`Warning: Stream key "${key}" not found in streamsMap.`);
70
+ return false;
71
+ }
72
+ return true;
73
+ });
74
+
75
+ // Retrieve all parts that includes the start and end time for each streamKey
76
+ const partsMap = this.getPartsMap(streamsMap, streamKeyArray, startTime, endTime);
77
+ console.log("PARTS MAP => { parts, minStart, maxEnd } map:", partsMap);
78
+
79
+ // handle directories
80
+ const dirPath=path.resolve(out);
81
+ if(!fs.existsSync(dirPath)) {
82
+ fs.mkdirSync(dirPath, {recursive: true});
83
+ console.log(`Directory created at ${dirPath}`);
84
+ } else {
85
+ console.log(`Directory already exists at ${dirPath}`);
86
+ }
87
+ // create directory for object provided : iq_XXX_start-time_end-time
88
+ const contentObjDirPath = path.join(dirPath, `${objectId}_${this.secondsToHms(startTime, "-")}_${this.secondsToHms(endTime, "-")}`);
89
+ // if(fs.existsSync(contentObjDirPath)) throw new Error(`Directory already exists at ${contentObjDirPath}`);
90
+ // fs.mkdirSync(contentObjDirPath, { recursive: true });
91
+ if(!fs.existsSync(contentObjDirPath)) {
92
+ fs.mkdirSync(contentObjDirPath, { recursive: true });
93
+ console.log(`Directory created at ${contentObjDirPath}`);
94
+ }
95
+
96
+ const trimmedDirectory = path.join(contentObjDirPath, "trimmed");
97
+ if(!fs.existsSync(trimmedDirectory)) {
98
+ fs.mkdirSync(trimmedDirectory, { recursive: true });
99
+ }
100
+
101
+ // Download and concatenate the parts for each streamKey
102
+ // Then, trim the concatenated media
103
+ for(const streamKey of Object.keys(partsMap)) {
104
+ const { parts, minStart, maxEnd } = partsMap[streamKey];
105
+ const partsFile = await this.downloadParts(contentObjDirPath, libraryId, objectId, streamKey, parts);
106
+ console.log(`parts file for ${streamKey}: ${partsFile}\n`);
107
+
108
+ // concatenate the parts
109
+ let mediaFile=path.join(contentObjDirPath, `${streamKey}.mp4`);
110
+ let cmd=`ffmpeg -f concat -safe 0 -i ${partsFile} -c copy ${mediaFile}`;
111
+ console.log("Running", cmd);
112
+ try {
113
+ execSync(cmd);
114
+ console.log("Concatenation complete.");
115
+ } catch(error) {
116
+ console.error("Error running ffmpeg:", error);
117
+ }
118
+
119
+ // trim the parts
120
+ let trimStartTime=null;
121
+ let trimEndTime=null;
122
+ // since new concatenated video starts from 0s
123
+ if(minStart !== null && maxEnd !== null) {
124
+ trimStartTime=startTime - minStart;
125
+ trimEndTime=endTime - minStart;
126
+ }
127
+
128
+ let mediaTrimmedFile=path.join(trimmedDirectory, `${streamKey}_trimmed.mp4`);
129
+ cmd=`ffmpeg -i ${mediaFile} -ss ${this.secondsToHms(trimStartTime, ":")} -t ${this.secondsToHms(trimEndTime - trimStartTime, ":")} ${mediaTrimmedFile}`;
130
+ console.log("Running", cmd);
131
+ try {
132
+ execSync(cmd);
133
+ console.log(`\nTrimmed ${streamKey} file: ${mediaTrimmedFile}`);
134
+ } catch(error) {
135
+ console.error("Error running ffmpeg:", error);
136
+ }
137
+
138
+ console.log("================================================");
139
+ }
140
+
141
+ }
142
+
143
+ getStreamInfo(offering, transcodes, streamKey) {
144
+ const reps = offering.playout.streams[streamKey].representations;
145
+ const rep = reps && Object.values(reps).find(r => r.transcode_id);
146
+ return rep && (transcodes[0] && transcodes[0][rep.transcode_id] && transcodes[0][rep.transcode_id].stream) || offering.media_struct.streams[streamKey];
147
+ }
148
+
149
+ getPartsMap(streamsMap, streamKeyArray, startTime, endTime){
150
+ const partsMap = {};
151
+
152
+ streamKeyArray.forEach(key => {
153
+ let sourcesMetadata = streamsMap[key].sources;
154
+ let durationMetadata = streamsMap[key].duration;
155
+
156
+ const totalDuration = new Fraction(durationMetadata.ts).mul(new Fraction(durationMetadata.time_base));
157
+ if (endTime > totalDuration) {
158
+ console.warn(`Warning: end time exceeds total duration. Setting end time to total duration: ${totalDuration}s`);
159
+ endTime = totalDuration;
160
+ }
161
+
162
+ const sourcesTimeInfo = sourcesMetadata.map(part => {
163
+ const start = new Fraction(part.timeline_start.ts).mul(new Fraction(part.timeline_start.time_base));
164
+ const end = new Fraction(part.timeline_end.ts).mul(new Fraction(part.timeline_end.time_base));
165
+ return {
166
+ start: start.valueOf(),
167
+ end: end.valueOf(),
168
+ source: part.source
169
+ };
170
+ });
171
+
172
+ let parts = [];
173
+ let minStart = null;
174
+ let maxEnd = null;
175
+ sourcesTimeInfo.forEach(item => {
176
+ if(startTime < item.end && endTime > item.start) {
177
+ parts.push(item.source);
178
+ if (minStart === null || item.start < minStart) {
179
+ minStart = item.start;
180
+ }
181
+ if(maxEnd === null || item.end > maxEnd) {
182
+ maxEnd = item.end;
183
+ }
184
+ }
185
+ });
186
+
187
+ partsMap[key] = {
188
+ parts,
189
+ minStart,
190
+ maxEnd
191
+ };
192
+ });
193
+ return partsMap;
194
+ }
195
+
196
+ async downloadParts(outDir, libraryId, objectId, streamKey, parts) {
197
+ const mtPath=path.join(outDir, streamKey);
198
+ if(!fs.existsSync(mtPath)) {
199
+ fs.mkdirSync(mtPath, {recursive: true});
200
+ console.log(`Directory created at ${mtPath}`);
201
+ } else {
202
+ throw new Error(`Directory already exists at ${mtPath}`);
203
+ }
204
+
205
+ const partsFile=path.join(outDir, `parts_${streamKey}.txt`);
206
+ const client=await this.client();
207
+
208
+ console.log("\nDownloading parts...\n");
209
+ for(const [index, partHash] of parts.entries()) {
210
+ let ph=(index + 1).toString().padStart(4, "0") + "." + partHash;
211
+ console.log(`processing ${ph}...`);
212
+
213
+ const buf=await client.DownloadPart({
214
+ libraryId,
215
+ objectId,
216
+ partHash,
217
+ format: "buffer",
218
+ chunked: false,
219
+ });
220
+
221
+ let partFile=path.join(mtPath, ph + ".mp4");
222
+ fs.appendFileSync(partFile, buf, (err) => {
223
+ if(err) {
224
+ console.log(err);
225
+ }
226
+ });
227
+
228
+ fs.appendFileSync(partsFile, `file '${partFile}'\n`, (err) => {
229
+ if(err) {
230
+ console.log(err);
231
+ }
232
+ });
233
+ }
234
+ return partsFile;
235
+ }
236
+
237
+ secondsToHms(seconds, separator) {
238
+ const date=new Date(seconds * 1000);
239
+ const hh=String(date.getUTCHours()).padStart(2, "0");
240
+ const mm=String(date.getUTCMinutes()).padStart(2, "0");
241
+ const ss=String(date.getUTCSeconds()).padStart(2, "0");
242
+ const ms=String(date.getUTCMilliseconds()).padStart(3, "0");
243
+ return `${hh}${separator}${mm}${separator}${ss}.${ms}`;
244
+ }
245
+
246
+ header() {
247
+ return `Downloading parts from startTime: ${this.args.startTime}s to endTime: ${this.args.endTime}s`;
248
+ }
249
+
250
+ options() {
251
+ return super.options()
252
+ .option("objectId", {
253
+ alias: "object-id",
254
+ describe: "Object ID (should start with 'iq__')",
255
+ type: "string"
256
+ })
257
+ .option("versionHash", {
258
+ alias: "version-hash",
259
+ describe: "Object Version Hash (should start with 'hq__')",
260
+ type: "string"
261
+ })
262
+ .option("offeringKey", {
263
+ describe: "offering key",
264
+ type: "string",
265
+ default: "default",
266
+ })
267
+ .option("streamKey", {
268
+ describe: "comma separated list of offerings stream key",
269
+ type: "string",
270
+ default: "video",
271
+ })
272
+ .option("startTime", {
273
+ describe: "start time to retrieve parts in seconds",
274
+ demandOption: true,
275
+ type: "number",
276
+ })
277
+ .option("endTime", {
278
+ describe: "end time to retrieve parts in seconds",
279
+ demandOption: true,
280
+ type: "number",
281
+ })
282
+ .option("out",{
283
+ describe: "output directory",
284
+ type: "string",
285
+ default: "./out",
286
+ });
287
+ }
288
+ }
289
+
290
+ const script = new OfferingDownloadMedia2();
291
+ script.run();