@eluvio/elv-client-js 4.0.94 → 4.0.96
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/dist/ElvWalletClient-min.js +1 -1
- package/dist/ElvWalletClient-node-min.js +1 -1
- package/dist/src/walletClient/index.js +3 -2
- package/package.json +1 -1
- package/src/walletClient/index.js +3 -3
- package/testScripts/BasicObjectOperations.js +1 -1
- package/testScripts/OfferingDownloadMedia.js +291 -0
|
@@ -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
|
@@ -396,7 +396,7 @@ class ElvWalletClient {
|
|
|
396
396
|
* @methodGroup Login
|
|
397
397
|
*/
|
|
398
398
|
async LogOut() {
|
|
399
|
-
if(this.__authorization.nonce) {
|
|
399
|
+
if(this.__authorization && this.__authorization.nonce) {
|
|
400
400
|
try {
|
|
401
401
|
await this.client.signer.ReleaseCSAT({accessToken: this.AuthToken()});
|
|
402
402
|
} catch(error) {
|
|
@@ -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"),
|
|
@@ -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();
|