@elizaos/plugin-video 2.0.0-beta.1 → 2.0.3-beta.6
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/LICENSE +21 -0
- package/README.md +64 -27
- package/build.ts +37 -0
- package/dist/index.d.ts +2 -4
- package/dist/index.js +183 -226
- package/dist/index.js.map +12 -1
- package/dist/services/binaries.d.ts +87 -0
- package/dist/services/video.d.ts +68 -0
- package/package.json +26 -9
- package/registry-entry.json +23 -0
- package/tsup.config.ts +0 -22
package/dist/index.js
CHANGED
|
@@ -1,25 +1,28 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
3
|
+
|
|
1
4
|
// src/services/video.ts
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import path2 from "node:path";
|
|
2
8
|
import {
|
|
9
|
+
elizaLogger as elizaLogger2,
|
|
3
10
|
IVideoService,
|
|
4
11
|
ServiceType,
|
|
5
|
-
stringToUuid
|
|
6
|
-
elizaLogger as elizaLogger2
|
|
12
|
+
stringToUuid
|
|
7
13
|
} from "@elizaos/core";
|
|
8
14
|
import ffmpeg from "fluent-ffmpeg";
|
|
9
|
-
import fs from "fs";
|
|
10
|
-
import { tmpdir } from "os";
|
|
11
|
-
import path2 from "path";
|
|
12
15
|
|
|
13
16
|
// src/services/binaries.ts
|
|
14
|
-
import { createHash } from "crypto";
|
|
17
|
+
import { createHash } from "node:crypto";
|
|
15
18
|
import {
|
|
16
19
|
createWriteStream,
|
|
17
20
|
constants as fsConstants,
|
|
18
21
|
promises as fsp
|
|
19
|
-
} from "fs";
|
|
20
|
-
import path from "path";
|
|
21
|
-
import { Readable } from "stream";
|
|
22
|
-
import { pipeline } from "stream/promises";
|
|
22
|
+
} from "node:fs";
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
import { Readable } from "node:stream";
|
|
25
|
+
import { pipeline } from "node:stream/promises";
|
|
23
26
|
import { elizaLogger, resolveStateDir } from "@elizaos/core";
|
|
24
27
|
import youtubeDl from "youtube-dl-exec";
|
|
25
28
|
function isYtDlpModule(value) {
|
|
@@ -30,7 +33,7 @@ if (!isYtDlpModule(youtubeDl)) {
|
|
|
30
33
|
}
|
|
31
34
|
var ytDlpModule = youtubeDl;
|
|
32
35
|
var YT_DLP_RELEASE_URL = "https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest";
|
|
33
|
-
var YT_DLP_UPDATE_THROTTLE_MS = 60 * 60 *
|
|
36
|
+
var YT_DLP_UPDATE_THROTTLE_MS = 60 * 60 * 1000;
|
|
34
37
|
var YT_DLP_META_FILENAME = "yt-dlp.meta.json";
|
|
35
38
|
var EXTRACTOR_BROKEN_PATTERNS = [
|
|
36
39
|
/Unable to extract/i,
|
|
@@ -42,16 +45,16 @@ var EXTRACTOR_BROKEN_PATTERNS = [
|
|
|
42
45
|
/This video is unavailable/i,
|
|
43
46
|
/Got error: HTTP Error 429/i
|
|
44
47
|
];
|
|
45
|
-
|
|
48
|
+
|
|
49
|
+
class BinaryResolver {
|
|
46
50
|
static _instance = null;
|
|
47
51
|
static instance() {
|
|
48
|
-
if (!
|
|
49
|
-
|
|
50
|
-
return
|
|
52
|
+
if (!BinaryResolver._instance)
|
|
53
|
+
BinaryResolver._instance = new BinaryResolver;
|
|
54
|
+
return BinaryResolver._instance;
|
|
51
55
|
}
|
|
52
|
-
/** Test hook: drop the singleton so the next instance() returns fresh state. */
|
|
53
56
|
static resetForTests() {
|
|
54
|
-
|
|
57
|
+
BinaryResolver._instance = null;
|
|
55
58
|
}
|
|
56
59
|
binariesDir;
|
|
57
60
|
releaseUrl;
|
|
@@ -64,7 +67,7 @@ var BinaryResolver = class _BinaryResolver {
|
|
|
64
67
|
resolvedYtDlpPath = null;
|
|
65
68
|
resolvedYtDlpSource = null;
|
|
66
69
|
cachedRunner = null;
|
|
67
|
-
resolvedFfmpegPath =
|
|
70
|
+
resolvedFfmpegPath = undefined;
|
|
68
71
|
updateInFlight = null;
|
|
69
72
|
constructor(opts = {}) {
|
|
70
73
|
this.binariesDir = opts.binariesDir ?? defaultBinariesDir();
|
|
@@ -74,7 +77,7 @@ var BinaryResolver = class _BinaryResolver {
|
|
|
74
77
|
this.disableAutoUpdate = opts.disableAutoUpdate ?? envBool("ELIZA_DISABLE_YTDLP_AUTOUPDATE");
|
|
75
78
|
this.preferSystemPath = opts.preferSystemPath ?? envBool("ELIZA_YT_DLP_PREFER_PATH");
|
|
76
79
|
this.updateThrottleMs = opts.updateThrottleMs ?? YT_DLP_UPDATE_THROTTLE_MS;
|
|
77
|
-
this.envOverridePath = opts.envOverridePath !==
|
|
80
|
+
this.envOverridePath = opts.envOverridePath !== undefined ? opts.envOverridePath : process.env.ELIZA_YT_DLP_PATH ?? null;
|
|
78
81
|
}
|
|
79
82
|
get cacheDir() {
|
|
80
83
|
return this.binariesDir;
|
|
@@ -85,26 +88,17 @@ var BinaryResolver = class _BinaryResolver {
|
|
|
85
88
|
get metaPath() {
|
|
86
89
|
return path.join(this.binariesDir, YT_DLP_META_FILENAME);
|
|
87
90
|
}
|
|
88
|
-
/**
|
|
89
|
-
* Resolve the yt-dlp binary path. Order:
|
|
90
|
-
* 1. ELIZA_YT_DLP_PATH env override.
|
|
91
|
-
* 2. If ELIZA_YT_DLP_PREFER_PATH=1: system PATH, then cache.
|
|
92
|
-
* 3. Otherwise: cache (download if missing) → PATH fallback.
|
|
93
|
-
*/
|
|
94
91
|
async getYtDlpPath() {
|
|
95
|
-
if (this.resolvedYtDlpPath)
|
|
92
|
+
if (this.resolvedYtDlpPath)
|
|
93
|
+
return this.resolvedYtDlpPath;
|
|
96
94
|
if (this.envOverridePath) {
|
|
97
95
|
if (await isExecutable(this.envOverridePath)) {
|
|
98
96
|
this.resolvedYtDlpPath = this.envOverridePath;
|
|
99
97
|
this.resolvedYtDlpSource = "env";
|
|
100
|
-
elizaLogger.log(
|
|
101
|
-
`[plugin-video] Using yt-dlp from env override: ${this.envOverridePath}`
|
|
102
|
-
);
|
|
98
|
+
elizaLogger.log(`[plugin-video] Using yt-dlp from env override: ${this.envOverridePath}`);
|
|
103
99
|
return this.envOverridePath;
|
|
104
100
|
}
|
|
105
|
-
elizaLogger.warn(
|
|
106
|
-
`[plugin-video] ELIZA_YT_DLP_PATH=${this.envOverridePath} is not executable; falling through.`
|
|
107
|
-
);
|
|
101
|
+
elizaLogger.warn(`[plugin-video] ELIZA_YT_DLP_PATH=${this.envOverridePath} is not executable; falling through.`);
|
|
108
102
|
}
|
|
109
103
|
if (this.preferSystemPath) {
|
|
110
104
|
const sys2 = await lookupOnPath("yt-dlp");
|
|
@@ -119,40 +113,32 @@ var BinaryResolver = class _BinaryResolver {
|
|
|
119
113
|
if (await isExecutable(cachePath)) {
|
|
120
114
|
this.resolvedYtDlpPath = cachePath;
|
|
121
115
|
this.resolvedYtDlpSource = "cache";
|
|
122
|
-
elizaLogger.log(
|
|
123
|
-
`[plugin-video] Using yt-dlp from managed cache: ${cachePath}`
|
|
124
|
-
);
|
|
116
|
+
elizaLogger.log(`[plugin-video] Using yt-dlp from managed cache: ${cachePath}`);
|
|
125
117
|
return cachePath;
|
|
126
118
|
}
|
|
127
119
|
const sys = await lookupOnPath("yt-dlp");
|
|
128
120
|
if (sys) {
|
|
129
121
|
this.resolvedYtDlpPath = sys;
|
|
130
122
|
this.resolvedYtDlpSource = "path";
|
|
131
|
-
elizaLogger.log(
|
|
132
|
-
`[plugin-video] Using yt-dlp from PATH (cache empty): ${sys}`
|
|
133
|
-
);
|
|
123
|
+
elizaLogger.log(`[plugin-video] Using yt-dlp from PATH (cache empty): ${sys}`);
|
|
134
124
|
return sys;
|
|
135
125
|
}
|
|
136
126
|
const bundled = await getBundledYtDlpPath();
|
|
137
127
|
if (bundled) {
|
|
138
128
|
this.resolvedYtDlpPath = bundled;
|
|
139
129
|
this.resolvedYtDlpSource = "bundled";
|
|
140
|
-
elizaLogger.log(
|
|
141
|
-
`[plugin-video] Using yt-dlp from youtube-dl-exec bundle: ${bundled}`
|
|
142
|
-
);
|
|
130
|
+
elizaLogger.log(`[plugin-video] Using yt-dlp from youtube-dl-exec bundle: ${bundled}`);
|
|
143
131
|
return bundled;
|
|
144
132
|
}
|
|
145
|
-
elizaLogger.log(
|
|
146
|
-
"[plugin-video] No yt-dlp binary found; downloading to managed cache."
|
|
147
|
-
);
|
|
133
|
+
elizaLogger.log("[plugin-video] No yt-dlp binary found; downloading to managed cache.");
|
|
148
134
|
await this.downloadYtDlp();
|
|
149
135
|
this.resolvedYtDlpPath = cachePath;
|
|
150
136
|
this.resolvedYtDlpSource = "cache";
|
|
151
137
|
return cachePath;
|
|
152
138
|
}
|
|
153
|
-
/** Resolution order: ELIZA_FFMPEG_PATH env → system ffmpeg → ffmpeg-static. */
|
|
154
139
|
async getFfmpegPath() {
|
|
155
|
-
if (this.resolvedFfmpegPath !==
|
|
140
|
+
if (this.resolvedFfmpegPath !== undefined)
|
|
141
|
+
return this.resolvedFfmpegPath;
|
|
156
142
|
const envPath = process.env.ELIZA_FFMPEG_PATH;
|
|
157
143
|
if (envPath && await isExecutable(envPath)) {
|
|
158
144
|
this.resolvedFfmpegPath = envPath;
|
|
@@ -165,33 +151,24 @@ var BinaryResolver = class _BinaryResolver {
|
|
|
165
151
|
}
|
|
166
152
|
try {
|
|
167
153
|
const mod = await import("ffmpeg-static");
|
|
168
|
-
const staticPath = typeof mod === "string" ? mod : mod && typeof mod === "object" && "default" in mod ? mod.default : null;
|
|
154
|
+
const staticPath = typeof mod === "string" ? mod : mod && typeof mod === "object" && ("default" in mod) ? mod.default : null;
|
|
169
155
|
if (typeof staticPath === "string" && staticPath.length > 0 && await isExecutable(staticPath)) {
|
|
170
156
|
this.resolvedFfmpegPath = staticPath;
|
|
171
157
|
return staticPath;
|
|
172
158
|
}
|
|
173
159
|
} catch (err) {
|
|
174
|
-
elizaLogger.warn(
|
|
175
|
-
`[plugin-video] ffmpeg-static not loadable: ${describeError(err)}`
|
|
176
|
-
);
|
|
160
|
+
elizaLogger.warn(`[plugin-video] ffmpeg-static not loadable: ${describeError(err)}`);
|
|
177
161
|
}
|
|
178
162
|
this.resolvedFfmpegPath = null;
|
|
179
163
|
return null;
|
|
180
164
|
}
|
|
181
|
-
/**
|
|
182
|
-
* Build (or reuse) the yt-dlp runner bound to the resolved binary path.
|
|
183
|
-
* The runner mirrors `youtube-dl-exec`'s default callable signature.
|
|
184
|
-
*/
|
|
185
165
|
async getYtDlpRunner() {
|
|
186
|
-
if (this.cachedRunner)
|
|
166
|
+
if (this.cachedRunner)
|
|
167
|
+
return this.cachedRunner;
|
|
187
168
|
const binPath = await this.getYtDlpPath();
|
|
188
169
|
this.cachedRunner = ytDlpModule.create(binPath);
|
|
189
170
|
return this.cachedRunner;
|
|
190
171
|
}
|
|
191
|
-
/**
|
|
192
|
-
* Run yt-dlp with one auto-update + retry attempt on extractor-failure
|
|
193
|
-
* patterns, when the active binary is the managed cache.
|
|
194
|
-
*/
|
|
195
172
|
async runYtDlp(url, flags) {
|
|
196
173
|
const runner = await this.getYtDlpRunner();
|
|
197
174
|
try {
|
|
@@ -209,17 +186,14 @@ var BinaryResolver = class _BinaryResolver {
|
|
|
209
186
|
}
|
|
210
187
|
}
|
|
211
188
|
shouldRetryWithUpdate(err) {
|
|
212
|
-
if (this.disableAutoUpdate)
|
|
189
|
+
if (this.disableAutoUpdate)
|
|
190
|
+
return false;
|
|
213
191
|
if (this.resolvedYtDlpSource !== "cache" && this.resolvedYtDlpSource !== "bundled") {
|
|
214
192
|
return false;
|
|
215
193
|
}
|
|
216
194
|
const msg = errorMessage(err);
|
|
217
195
|
return EXTRACTOR_BROKEN_PATTERNS.some((p) => p.test(msg));
|
|
218
196
|
}
|
|
219
|
-
/**
|
|
220
|
-
* Run a yt-dlp update attempt, throttled to once per `updateThrottleMs`.
|
|
221
|
-
* Returns true iff a fresh binary was successfully installed.
|
|
222
|
-
*/
|
|
223
197
|
async tryUpdate() {
|
|
224
198
|
if (this.updateInFlight) {
|
|
225
199
|
await this.updateInFlight;
|
|
@@ -229,9 +203,7 @@ var BinaryResolver = class _BinaryResolver {
|
|
|
229
203
|
if (meta) {
|
|
230
204
|
const sinceLast = this.now() - meta.lastUpdateAttemptedAt;
|
|
231
205
|
if (sinceLast < this.updateThrottleMs) {
|
|
232
|
-
elizaLogger.warn(
|
|
233
|
-
`[plugin-video] yt-dlp update throttled (last attempt ${Math.floor(sinceLast / 1e3)}s ago).`
|
|
234
|
-
);
|
|
206
|
+
elizaLogger.warn(`[plugin-video] yt-dlp update throttled (last attempt ${Math.floor(sinceLast / 1000)}s ago).`);
|
|
235
207
|
return false;
|
|
236
208
|
}
|
|
237
209
|
}
|
|
@@ -245,9 +217,7 @@ var BinaryResolver = class _BinaryResolver {
|
|
|
245
217
|
await job;
|
|
246
218
|
return true;
|
|
247
219
|
} catch (err) {
|
|
248
|
-
elizaLogger.error(
|
|
249
|
-
`[plugin-video] yt-dlp update failed: ${describeError(err)}`
|
|
250
|
-
);
|
|
220
|
+
elizaLogger.error(`[plugin-video] yt-dlp update failed: ${describeError(err)}`);
|
|
251
221
|
return false;
|
|
252
222
|
} finally {
|
|
253
223
|
this.updateInFlight = null;
|
|
@@ -258,7 +228,6 @@ var BinaryResolver = class _BinaryResolver {
|
|
|
258
228
|
this.resolvedYtDlpPath = null;
|
|
259
229
|
this.resolvedYtDlpSource = null;
|
|
260
230
|
}
|
|
261
|
-
/** Force a fresh yt-dlp download regardless of throttle. Test/admin hook. */
|
|
262
231
|
async forceUpdateYtDlp() {
|
|
263
232
|
const meta = await this.downloadYtDlp();
|
|
264
233
|
this.resetRunnerCache();
|
|
@@ -287,39 +256,25 @@ var BinaryResolver = class _BinaryResolver {
|
|
|
287
256
|
};
|
|
288
257
|
await this.writeMeta(next);
|
|
289
258
|
}
|
|
290
|
-
/**
|
|
291
|
-
* Download the latest yt-dlp release for this platform, verify the SHA256
|
|
292
|
-
* against the published `SHA2-256SUMS`, and atomically replace the cached
|
|
293
|
-
* binary. Returns the new metadata on success; throws on any failure.
|
|
294
|
-
*/
|
|
295
259
|
async downloadYtDlp() {
|
|
296
260
|
const release = await this.fetchRelease();
|
|
297
261
|
const assetName = ytDlpAssetName();
|
|
298
262
|
const asset = release.assets.find((a) => a.name === assetName);
|
|
299
263
|
if (!asset) {
|
|
300
|
-
throw new Error(
|
|
301
|
-
`yt-dlp release ${release.tag_name} has no asset named '${assetName}'`
|
|
302
|
-
);
|
|
264
|
+
throw new Error(`yt-dlp release ${release.tag_name} has no asset named '${assetName}'`);
|
|
303
265
|
}
|
|
304
266
|
const sumsAsset = release.assets.find((a) => a.name === "SHA2-256SUMS");
|
|
305
267
|
if (!sumsAsset) {
|
|
306
|
-
throw new Error(
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
const expectedSha = await this.fetchExpectedSha(
|
|
311
|
-
sumsAsset.browser_download_url,
|
|
312
|
-
assetName
|
|
313
|
-
);
|
|
268
|
+
throw new Error(`yt-dlp release ${release.tag_name} has no SHA2-256SUMS asset`);
|
|
269
|
+
}
|
|
270
|
+
const expectedSha = await this.fetchExpectedSha(sumsAsset.browser_download_url, assetName);
|
|
314
271
|
await fsp.mkdir(this.binariesDir, { recursive: true });
|
|
315
272
|
const tmpPath = `${this.cachedYtDlpPath}.tmp.${process.pid}.${Date.now()}`;
|
|
316
273
|
await this.downloadToFile(asset.browser_download_url, tmpPath);
|
|
317
274
|
const actualSha = await sha256OfFile(tmpPath);
|
|
318
275
|
if (actualSha !== expectedSha) {
|
|
319
276
|
await safeUnlink(tmpPath);
|
|
320
|
-
throw new Error(
|
|
321
|
-
`yt-dlp SHA256 mismatch: expected ${expectedSha}, got ${actualSha}`
|
|
322
|
-
);
|
|
277
|
+
throw new Error(`yt-dlp SHA256 mismatch: expected ${expectedSha}, got ${actualSha}`);
|
|
323
278
|
}
|
|
324
279
|
await fsp.chmod(tmpPath, 493);
|
|
325
280
|
await fsp.rename(tmpPath, this.cachedYtDlpPath);
|
|
@@ -331,9 +286,7 @@ var BinaryResolver = class _BinaryResolver {
|
|
|
331
286
|
lastUpdateAttemptedAt: this.now()
|
|
332
287
|
};
|
|
333
288
|
await this.writeMeta(meta);
|
|
334
|
-
elizaLogger.log(
|
|
335
|
-
`[plugin-video] yt-dlp ${release.tag_name} installed at ${this.cachedYtDlpPath}`
|
|
336
|
-
);
|
|
289
|
+
elizaLogger.log(`[plugin-video] yt-dlp ${release.tag_name} installed at ${this.cachedYtDlpPath}`);
|
|
337
290
|
return meta;
|
|
338
291
|
}
|
|
339
292
|
async fetchRelease() {
|
|
@@ -341,23 +294,20 @@ var BinaryResolver = class _BinaryResolver {
|
|
|
341
294
|
headers: { Accept: "application/vnd.github+json" }
|
|
342
295
|
});
|
|
343
296
|
if (!res.ok) {
|
|
344
|
-
throw new Error(
|
|
345
|
-
`yt-dlp release fetch failed: ${res.status} ${res.statusText}`
|
|
346
|
-
);
|
|
297
|
+
throw new Error(`yt-dlp release fetch failed: ${res.status} ${res.statusText}`);
|
|
347
298
|
}
|
|
348
299
|
return await res.json();
|
|
349
300
|
}
|
|
350
301
|
async fetchExpectedSha(sumsUrl, assetName) {
|
|
351
302
|
const res = await this.fetchImpl(sumsUrl);
|
|
352
303
|
if (!res.ok) {
|
|
353
|
-
throw new Error(
|
|
354
|
-
`SHA2-256SUMS fetch failed: ${res.status} ${res.statusText}`
|
|
355
|
-
);
|
|
304
|
+
throw new Error(`SHA2-256SUMS fetch failed: ${res.status} ${res.statusText}`);
|
|
356
305
|
}
|
|
357
306
|
const text = await res.text();
|
|
358
307
|
for (const line of text.split(/\r?\n/)) {
|
|
359
308
|
const trimmed = line.trim();
|
|
360
|
-
if (!trimmed)
|
|
309
|
+
if (!trimmed)
|
|
310
|
+
continue;
|
|
361
311
|
const parts = trimmed.split(/\s+/);
|
|
362
312
|
if (parts.length >= 2 && parts[parts.length - 1] === assetName) {
|
|
363
313
|
return parts[0].toLowerCase();
|
|
@@ -368,30 +318,30 @@ var BinaryResolver = class _BinaryResolver {
|
|
|
368
318
|
async downloadToFile(url, dest) {
|
|
369
319
|
const res = await this.fetchImpl(url);
|
|
370
320
|
if (!res.ok || !res.body) {
|
|
371
|
-
throw new Error(
|
|
372
|
-
`yt-dlp binary fetch failed: ${res.status} ${res.statusText}`
|
|
373
|
-
);
|
|
321
|
+
throw new Error(`yt-dlp binary fetch failed: ${res.status} ${res.statusText}`);
|
|
374
322
|
}
|
|
375
|
-
const nodeStream = Readable.fromWeb(
|
|
376
|
-
res.body
|
|
377
|
-
);
|
|
323
|
+
const nodeStream = Readable.fromWeb(res.body);
|
|
378
324
|
await pipeline(nodeStream, createWriteStream(dest));
|
|
379
325
|
}
|
|
380
|
-
}
|
|
326
|
+
}
|
|
381
327
|
function defaultBinariesDir() {
|
|
382
328
|
const explicit = process.env.ELIZA_BINARIES_DIR;
|
|
383
|
-
if (explicit)
|
|
329
|
+
if (explicit)
|
|
330
|
+
return explicit;
|
|
384
331
|
return path.join(resolveStateDir(), "binaries");
|
|
385
332
|
}
|
|
386
333
|
function ytDlpAssetName() {
|
|
387
334
|
const platform = process.platform;
|
|
388
335
|
const arch = process.arch;
|
|
389
|
-
if (platform === "darwin")
|
|
336
|
+
if (platform === "darwin")
|
|
337
|
+
return "yt-dlp_macos";
|
|
390
338
|
if (platform === "win32")
|
|
391
339
|
return arch === "ia32" ? "yt-dlp_x86.exe" : "yt-dlp.exe";
|
|
392
340
|
if (platform === "linux") {
|
|
393
|
-
if (arch === "arm64")
|
|
394
|
-
|
|
341
|
+
if (arch === "arm64")
|
|
342
|
+
return "yt-dlp_linux_aarch64";
|
|
343
|
+
if (arch === "arm")
|
|
344
|
+
return "yt-dlp_linux_armv7l";
|
|
395
345
|
return "yt-dlp_linux";
|
|
396
346
|
}
|
|
397
347
|
throw new Error(`Unsupported platform for yt-dlp: ${platform}/${arch}`);
|
|
@@ -409,31 +359,31 @@ async function isExecutable(p) {
|
|
|
409
359
|
}
|
|
410
360
|
}
|
|
411
361
|
async function getBundledYtDlpPath() {
|
|
412
|
-
var _a;
|
|
413
362
|
try {
|
|
414
363
|
const constantsModule = await import("youtube-dl-exec/src/constants.js");
|
|
415
|
-
const fromConstants =
|
|
364
|
+
const fromConstants = constantsModule.default?.YOUTUBE_DL_PATH ?? constantsModule.YOUTUBE_DL_PATH;
|
|
416
365
|
if (typeof fromConstants === "string" && fromConstants.length > 0 && await isExecutable(fromConstants)) {
|
|
417
366
|
return fromConstants;
|
|
418
367
|
}
|
|
419
|
-
} catch {
|
|
420
|
-
}
|
|
368
|
+
} catch {}
|
|
421
369
|
try {
|
|
422
|
-
const { createRequire } = await import("module");
|
|
423
|
-
const req =
|
|
370
|
+
const { createRequire: createRequire2 } = await import("node:module");
|
|
371
|
+
const req = createRequire2(import.meta.url);
|
|
424
372
|
const pkgPath = req.resolve("youtube-dl-exec/package.json");
|
|
425
373
|
const candidate = path.join(path.dirname(pkgPath), "bin", ytDlpFileName());
|
|
426
|
-
if (await isExecutable(candidate))
|
|
427
|
-
|
|
428
|
-
}
|
|
374
|
+
if (await isExecutable(candidate))
|
|
375
|
+
return candidate;
|
|
376
|
+
} catch {}
|
|
429
377
|
return null;
|
|
430
378
|
}
|
|
431
379
|
async function lookupOnPath(name) {
|
|
432
380
|
const pathEnv = process.env.PATH;
|
|
433
|
-
if (!pathEnv)
|
|
381
|
+
if (!pathEnv)
|
|
382
|
+
return null;
|
|
434
383
|
const exts = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").map((e) => e.toLowerCase()) : [""];
|
|
435
384
|
for (const dir of pathEnv.split(path.delimiter)) {
|
|
436
|
-
if (!dir)
|
|
385
|
+
if (!dir)
|
|
386
|
+
continue;
|
|
437
387
|
for (const ext of exts) {
|
|
438
388
|
const candidate = path.join(dir, `${name}${ext}`);
|
|
439
389
|
if (await isExecutable(candidate)) {
|
|
@@ -448,7 +398,8 @@ async function sha256OfFile(p) {
|
|
|
448
398
|
const fd = await fsp.open(p, "r");
|
|
449
399
|
try {
|
|
450
400
|
const stream = fd.createReadStream();
|
|
451
|
-
for await (const chunk of stream)
|
|
401
|
+
for await (const chunk of stream)
|
|
402
|
+
hash.update(chunk);
|
|
452
403
|
} finally {
|
|
453
404
|
await fd.close();
|
|
454
405
|
}
|
|
@@ -457,21 +408,24 @@ async function sha256OfFile(p) {
|
|
|
457
408
|
async function safeUnlink(p) {
|
|
458
409
|
try {
|
|
459
410
|
await fsp.unlink(p);
|
|
460
|
-
} catch {
|
|
461
|
-
}
|
|
411
|
+
} catch {}
|
|
462
412
|
}
|
|
463
413
|
function envBool(name) {
|
|
464
414
|
const v = process.env[name];
|
|
465
|
-
if (!v)
|
|
415
|
+
if (!v)
|
|
416
|
+
return false;
|
|
466
417
|
return v === "1" || v.toLowerCase() === "true";
|
|
467
418
|
}
|
|
468
419
|
function errorMessage(err) {
|
|
469
|
-
if (!err)
|
|
470
|
-
|
|
420
|
+
if (!err)
|
|
421
|
+
return "";
|
|
422
|
+
if (typeof err === "string")
|
|
423
|
+
return err;
|
|
471
424
|
const anyErr = err;
|
|
472
425
|
if (typeof anyErr.stderr === "string" && anyErr.stderr.length > 0)
|
|
473
426
|
return anyErr.stderr;
|
|
474
|
-
if (typeof anyErr.message === "string")
|
|
427
|
+
if (typeof anyErr.message === "string")
|
|
428
|
+
return anyErr.message;
|
|
475
429
|
try {
|
|
476
430
|
return JSON.stringify(err);
|
|
477
431
|
} catch {
|
|
@@ -483,14 +437,28 @@ function describeError(err) {
|
|
|
483
437
|
}
|
|
484
438
|
|
|
485
439
|
// src/services/video.ts
|
|
486
|
-
|
|
440
|
+
function loggableError(error) {
|
|
441
|
+
return error instanceof Error ? error.message : String(error);
|
|
442
|
+
}
|
|
443
|
+
function parseYtDlpUploadDate(value) {
|
|
444
|
+
if (!value)
|
|
445
|
+
return;
|
|
446
|
+
const compact = value.match(/^(\d{4})(\d{2})(\d{2})$/);
|
|
447
|
+
if (compact) {
|
|
448
|
+
const [, year, month, day] = compact;
|
|
449
|
+
return new Date(`${year}-${month}-${day}T00:00:00.000Z`);
|
|
450
|
+
}
|
|
451
|
+
const parsed = new Date(value);
|
|
452
|
+
return Number.isNaN(parsed.getTime()) ? undefined : parsed;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
class VideoService extends IVideoService {
|
|
487
456
|
capabilityDescription = "Video download, processing, and conversion capabilities";
|
|
488
457
|
static serviceType = ServiceType.VIDEO;
|
|
489
458
|
cacheKey = "content/video";
|
|
490
459
|
dataDir = "./content_cache";
|
|
491
460
|
binaries;
|
|
492
461
|
ffmpegPathConfigured = false;
|
|
493
|
-
/** Serialize downloads/processing so cache keys and temp files do not race. */
|
|
494
462
|
processingChain = Promise.resolve();
|
|
495
463
|
constructor(runtime, binaries) {
|
|
496
464
|
super(runtime);
|
|
@@ -498,7 +466,7 @@ var VideoService = class _VideoService extends IVideoService {
|
|
|
498
466
|
this.ensureDataDirectoryExists();
|
|
499
467
|
}
|
|
500
468
|
static async start(runtime) {
|
|
501
|
-
const service = new
|
|
469
|
+
const service = new VideoService(runtime);
|
|
502
470
|
await service.initialize(runtime);
|
|
503
471
|
return service;
|
|
504
472
|
}
|
|
@@ -506,38 +474,34 @@ var VideoService = class _VideoService extends IVideoService {
|
|
|
506
474
|
await this.configureFfmpeg();
|
|
507
475
|
}
|
|
508
476
|
async configureFfmpeg() {
|
|
509
|
-
if (this.ffmpegPathConfigured)
|
|
477
|
+
if (this.ffmpegPathConfigured)
|
|
478
|
+
return;
|
|
510
479
|
const ffmpegPath = await this.binaries.getFfmpegPath();
|
|
511
480
|
if (ffmpegPath) {
|
|
512
481
|
ffmpeg.setFfmpegPath(ffmpegPath);
|
|
513
482
|
elizaLogger2.log(`[plugin-video] ffmpeg path: ${ffmpegPath}`);
|
|
514
483
|
} else {
|
|
515
|
-
elizaLogger2.warn(
|
|
516
|
-
"[plugin-video] No ffmpeg binary located via env, PATH, or ffmpeg-static; fluent-ffmpeg will fail at first invocation."
|
|
517
|
-
);
|
|
484
|
+
elizaLogger2.warn("[plugin-video] No ffmpeg binary located via env, PATH, or ffmpeg-static; fluent-ffmpeg will fail at first invocation.");
|
|
518
485
|
}
|
|
519
486
|
this.ffmpegPathConfigured = true;
|
|
520
487
|
}
|
|
521
488
|
async stop() {
|
|
522
489
|
this.processingChain = Promise.resolve();
|
|
523
490
|
}
|
|
524
|
-
// Required abstract methods from IVideoService
|
|
525
491
|
async getVideoInfo(url) {
|
|
526
492
|
const videoInfo = await this.fetchVideoInfo(url);
|
|
527
|
-
const formats = (videoInfo.formats ?? []).map(
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
})
|
|
540
|
-
);
|
|
493
|
+
const formats = (videoInfo.formats ?? []).map((f) => ({
|
|
494
|
+
formatId: f.format_id ?? "",
|
|
495
|
+
url: f.url ?? "",
|
|
496
|
+
extension: f.ext ?? "",
|
|
497
|
+
quality: f.quality !== undefined && f.quality !== "" ? String(f.quality) : "unknown",
|
|
498
|
+
fileSize: f.filesize,
|
|
499
|
+
videoCodec: f.vcodec,
|
|
500
|
+
audioCodec: f.acodec,
|
|
501
|
+
resolution: f.resolution,
|
|
502
|
+
fps: f.fps,
|
|
503
|
+
bitrate: f.tbr
|
|
504
|
+
}));
|
|
541
505
|
return {
|
|
542
506
|
title: videoInfo.title,
|
|
543
507
|
duration: videoInfo.duration,
|
|
@@ -546,13 +510,13 @@ var VideoService = class _VideoService extends IVideoService {
|
|
|
546
510
|
description: videoInfo.description,
|
|
547
511
|
uploader: videoInfo.channel,
|
|
548
512
|
viewCount: videoInfo.view_count,
|
|
549
|
-
uploadDate:
|
|
513
|
+
uploadDate: parseYtDlpUploadDate(videoInfo.upload_date),
|
|
550
514
|
formats
|
|
551
515
|
};
|
|
552
516
|
}
|
|
553
517
|
async downloadVideo(url, options) {
|
|
554
518
|
const videoId = this.getVideoId(url);
|
|
555
|
-
const outputFile =
|
|
519
|
+
const outputFile = options?.outputPath || path2.join(this.dataDir, `${videoId}.mp4`);
|
|
556
520
|
if (fs.existsSync(outputFile)) {
|
|
557
521
|
return outputFile;
|
|
558
522
|
}
|
|
@@ -562,23 +526,23 @@ var VideoService = class _VideoService extends IVideoService {
|
|
|
562
526
|
output: outputFile,
|
|
563
527
|
writeInfoJson: true
|
|
564
528
|
};
|
|
565
|
-
if (options
|
|
529
|
+
if (options?.format) {
|
|
566
530
|
downloadOptions.format = options.format;
|
|
567
531
|
}
|
|
568
|
-
if (options
|
|
532
|
+
if (options?.quality) {
|
|
569
533
|
downloadOptions.format = options.quality;
|
|
570
534
|
}
|
|
571
|
-
if (options
|
|
535
|
+
if (options?.audioOnly) {
|
|
572
536
|
downloadOptions.extractAudio = true;
|
|
573
537
|
downloadOptions.audioFormat = "mp3";
|
|
574
538
|
}
|
|
575
|
-
if (options
|
|
539
|
+
if (options?.videoOnly) {
|
|
576
540
|
downloadOptions.format = "bestvideo[ext=mp4]/best[ext=mp4]/best";
|
|
577
541
|
}
|
|
578
542
|
await this.binaries.runYtDlp(url, downloadOptions);
|
|
579
543
|
return outputFile;
|
|
580
544
|
} catch (error) {
|
|
581
|
-
elizaLogger2.log("Error downloading video:", error);
|
|
545
|
+
elizaLogger2.log("Error downloading video:", loggableError(error));
|
|
582
546
|
throw new Error("Failed to download video");
|
|
583
547
|
}
|
|
584
548
|
}
|
|
@@ -594,7 +558,7 @@ var VideoService = class _VideoService extends IVideoService {
|
|
|
594
558
|
elizaLogger2.log("Audio extraction complete");
|
|
595
559
|
resolve(audioFile);
|
|
596
560
|
}).on("error", (err) => {
|
|
597
|
-
elizaLogger2.log("Error extracting audio:", err);
|
|
561
|
+
elizaLogger2.log("Error extracting audio:", loggableError(err));
|
|
598
562
|
reject(err);
|
|
599
563
|
}).run();
|
|
600
564
|
});
|
|
@@ -616,7 +580,7 @@ var VideoService = class _VideoService extends IVideoService {
|
|
|
616
580
|
elizaLogger2.log("Thumbnail generation complete");
|
|
617
581
|
resolve(thumbnailFile);
|
|
618
582
|
}).on("error", (err) => {
|
|
619
|
-
elizaLogger2.log("Error generating thumbnail:", err);
|
|
583
|
+
elizaLogger2.log("Error generating thumbnail:", loggableError(err));
|
|
620
584
|
reject(err);
|
|
621
585
|
});
|
|
622
586
|
});
|
|
@@ -625,42 +589,41 @@ var VideoService = class _VideoService extends IVideoService {
|
|
|
625
589
|
await this.configureFfmpeg();
|
|
626
590
|
return new Promise((resolve, reject) => {
|
|
627
591
|
let command = ffmpeg(videoPath);
|
|
628
|
-
if (options
|
|
592
|
+
if (options?.startTime) {
|
|
629
593
|
command = command.seekInput(options.startTime);
|
|
630
594
|
}
|
|
631
595
|
command = command.output(outputPath);
|
|
632
|
-
if (options
|
|
596
|
+
if (options?.endTime) {
|
|
633
597
|
command = command.duration(options.endTime - (options.startTime || 0));
|
|
634
598
|
}
|
|
635
|
-
if (options
|
|
599
|
+
if (options?.outputFormat) {
|
|
636
600
|
command = command.format(options.outputFormat);
|
|
637
601
|
}
|
|
638
|
-
if (options
|
|
602
|
+
if (options?.resolution) {
|
|
639
603
|
command = command.size(options.resolution);
|
|
640
604
|
}
|
|
641
|
-
if (options
|
|
605
|
+
if (options?.bitrate) {
|
|
642
606
|
command = command.videoBitrate(options.bitrate);
|
|
643
607
|
}
|
|
644
|
-
if (options
|
|
608
|
+
if (options?.framerate) {
|
|
645
609
|
command = command.fps(options.framerate);
|
|
646
610
|
}
|
|
647
|
-
if (options
|
|
611
|
+
if (options?.videoCodec) {
|
|
648
612
|
command = command.videoCodec(options.videoCodec);
|
|
649
613
|
}
|
|
650
|
-
if (options
|
|
614
|
+
if (options?.audioCodec) {
|
|
651
615
|
command = command.audioCodec(options.audioCodec);
|
|
652
616
|
}
|
|
653
617
|
command.on("end", () => {
|
|
654
618
|
elizaLogger2.log("Video conversion complete");
|
|
655
619
|
resolve(outputPath);
|
|
656
620
|
}).on("error", (err) => {
|
|
657
|
-
elizaLogger2.log("Error converting video:", err);
|
|
621
|
+
elizaLogger2.log("Error converting video:", loggableError(err));
|
|
658
622
|
reject(err);
|
|
659
623
|
}).run();
|
|
660
624
|
});
|
|
661
625
|
}
|
|
662
626
|
async getAvailableFormats(url) {
|
|
663
|
-
var _a;
|
|
664
627
|
try {
|
|
665
628
|
const result = await this.binaries.runYtDlp(url, {
|
|
666
629
|
dumpJson: true,
|
|
@@ -673,12 +636,12 @@ var VideoService = class _VideoService extends IVideoService {
|
|
|
673
636
|
});
|
|
674
637
|
if (typeof result === "object" && result !== null && "formats" in result) {
|
|
675
638
|
const parsed = result;
|
|
676
|
-
if (
|
|
639
|
+
if (parsed.formats?.length) {
|
|
677
640
|
return parsed.formats.map((format) => ({
|
|
678
641
|
formatId: format.format_id ?? "",
|
|
679
|
-
url: format.url,
|
|
680
|
-
extension: format.ext,
|
|
681
|
-
quality: format.quality !==
|
|
642
|
+
url: format.url ?? "",
|
|
643
|
+
extension: format.ext ?? "",
|
|
644
|
+
quality: format.quality !== undefined && format.quality !== "" ? String(format.quality) : "unknown",
|
|
682
645
|
fileSize: format.filesize,
|
|
683
646
|
videoCodec: format.vcodec,
|
|
684
647
|
audioCodec: format.acodec,
|
|
@@ -690,7 +653,7 @@ var VideoService = class _VideoService extends IVideoService {
|
|
|
690
653
|
}
|
|
691
654
|
return [];
|
|
692
655
|
} catch (error) {
|
|
693
|
-
elizaLogger2.log("Error getting available formats:", error);
|
|
656
|
+
elizaLogger2.log("Error getting available formats:", loggableError(error));
|
|
694
657
|
throw new Error("Failed to get available formats");
|
|
695
658
|
}
|
|
696
659
|
}
|
|
@@ -722,27 +685,22 @@ var VideoService = class _VideoService extends IVideoService {
|
|
|
722
685
|
});
|
|
723
686
|
return outputFile;
|
|
724
687
|
} catch (error) {
|
|
725
|
-
elizaLogger2.log("Error downloading media:", error);
|
|
688
|
+
elizaLogger2.log("Error downloading media:", loggableError(error));
|
|
726
689
|
throw new Error("Failed to download media");
|
|
727
690
|
}
|
|
728
691
|
}
|
|
729
692
|
async processVideo(url, runtime) {
|
|
730
|
-
const run = this.processingChain.then(
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
);
|
|
693
|
+
const run = this.processingChain.then(() => this.processVideoFromUrl(url, runtime));
|
|
694
|
+
this.processingChain = run.then(() => {
|
|
695
|
+
return;
|
|
696
|
+
}, () => {
|
|
697
|
+
return;
|
|
698
|
+
});
|
|
737
699
|
return run;
|
|
738
700
|
}
|
|
739
701
|
async processVideoFromUrl(url, runtime) {
|
|
740
|
-
|
|
741
|
-
const
|
|
742
|
-
/(?:youtu\.be\/|youtube\.com(?:\/embed\/|\/v\/|\/watch\?v=|\/watch\?.+&v=))([^\/&?]+)/
|
|
743
|
-
// eslint-disable-line
|
|
744
|
-
)) == null ? void 0 : _a[1]) || "";
|
|
745
|
-
const videoUuid = this.getVideoId(videoId);
|
|
702
|
+
const extractedVideoId = url.match(/(?:youtu\.be\/|youtube\.com(?:\/embed\/|\/v\/|\/watch\?v=|\/watch\?.+&v=))([^/&?]+)/)?.[1] || url;
|
|
703
|
+
const videoUuid = this.getVideoId(extractedVideoId);
|
|
746
704
|
const cacheKey = `${this.cacheKey}/${videoUuid}`;
|
|
747
705
|
const cached = await runtime.getCache(cacheKey);
|
|
748
706
|
if (cached) {
|
|
@@ -780,7 +738,7 @@ var VideoService = class _VideoService extends IVideoService {
|
|
|
780
738
|
};
|
|
781
739
|
}
|
|
782
740
|
} catch (error) {
|
|
783
|
-
elizaLogger2.log("Error downloading MP4 file:", error);
|
|
741
|
+
elizaLogger2.log("Error downloading MP4 file:", loggableError(error));
|
|
784
742
|
}
|
|
785
743
|
}
|
|
786
744
|
try {
|
|
@@ -798,36 +756,32 @@ var VideoService = class _VideoService extends IVideoService {
|
|
|
798
756
|
});
|
|
799
757
|
return result;
|
|
800
758
|
} catch (error) {
|
|
801
|
-
elizaLogger2.log("Error fetching video info:", error);
|
|
759
|
+
elizaLogger2.log("Error fetching video info:", loggableError(error));
|
|
802
760
|
throw new Error("Failed to fetch video information");
|
|
803
761
|
}
|
|
804
762
|
}
|
|
805
763
|
async getTranscript(url, videoInfo, runtime) {
|
|
806
764
|
elizaLogger2.log("Getting transcript");
|
|
807
765
|
try {
|
|
808
|
-
if (videoInfo.subtitles
|
|
766
|
+
if (videoInfo.subtitles?.en) {
|
|
809
767
|
elizaLogger2.log("Manual subtitles found");
|
|
810
|
-
const srtContent = await this.downloadSRT(
|
|
811
|
-
videoInfo.subtitles.en[0].url
|
|
812
|
-
);
|
|
768
|
+
const srtContent = await this.downloadSRT(videoInfo.subtitles.en[0].url);
|
|
813
769
|
return this.parseSRT(srtContent);
|
|
814
770
|
}
|
|
815
|
-
if (videoInfo.automatic_captions
|
|
771
|
+
if (videoInfo.automatic_captions?.en) {
|
|
816
772
|
elizaLogger2.log("Automatic captions found");
|
|
817
773
|
const captionUrl = videoInfo.automatic_captions.en[0].url;
|
|
818
774
|
const captionContent = await this.downloadCaption(captionUrl);
|
|
819
775
|
return this.parseCaption(captionContent);
|
|
820
776
|
}
|
|
821
|
-
if (videoInfo.categories
|
|
777
|
+
if (videoInfo.categories?.includes("Music")) {
|
|
822
778
|
elizaLogger2.log("Music video detected, no lyrics available");
|
|
823
779
|
return "No lyrics available.";
|
|
824
780
|
}
|
|
825
|
-
elizaLogger2.log(
|
|
826
|
-
"No subtitles or captions found, falling back to audio transcription"
|
|
827
|
-
);
|
|
781
|
+
elizaLogger2.log("No subtitles or captions found, falling back to audio transcription");
|
|
828
782
|
return this.transcribeAudio(url, runtime);
|
|
829
783
|
} catch (error) {
|
|
830
|
-
elizaLogger2.log("Error in getTranscript:", error);
|
|
784
|
+
elizaLogger2.log("Error in getTranscript:", loggableError(error));
|
|
831
785
|
throw error;
|
|
832
786
|
}
|
|
833
787
|
}
|
|
@@ -843,19 +797,23 @@ var VideoService = class _VideoService extends IVideoService {
|
|
|
843
797
|
elizaLogger2.log("Parsing caption");
|
|
844
798
|
try {
|
|
845
799
|
const jsonContent = JSON.parse(captionContent);
|
|
846
|
-
if (jsonContent.events) {
|
|
847
|
-
return jsonContent.events.filter((event) => event.segs).map((event) => event.segs.map((seg) => seg.utf8).join("")).join("").replace(
|
|
800
|
+
if (Array.isArray(jsonContent.events)) {
|
|
801
|
+
return jsonContent.events.filter((event) => Array.isArray(event.segs)).map((event) => event.segs.map((seg) => seg.utf8 ?? "").join("")).join("").replace(`
|
|
802
|
+
`, " ");
|
|
848
803
|
} else {
|
|
849
|
-
elizaLogger2.log("Unexpected caption format:", jsonContent);
|
|
804
|
+
elizaLogger2.log("Unexpected caption format:", JSON.stringify(jsonContent));
|
|
850
805
|
return "Error: Unable to parse captions";
|
|
851
806
|
}
|
|
852
807
|
} catch (error) {
|
|
853
|
-
elizaLogger2.log("Error parsing caption:", error);
|
|
808
|
+
elizaLogger2.log("Error parsing caption:", loggableError(error));
|
|
854
809
|
return "Error: Unable to parse captions";
|
|
855
810
|
}
|
|
856
811
|
}
|
|
857
812
|
parseSRT(srtContent) {
|
|
858
|
-
return srtContent.split(
|
|
813
|
+
return srtContent.split(`
|
|
814
|
+
|
|
815
|
+
`).map((block) => block.split(`
|
|
816
|
+
`).slice(2).join(" ")).join(" ");
|
|
859
817
|
}
|
|
860
818
|
async downloadSRT(url) {
|
|
861
819
|
elizaLogger2.log("downloadSRT");
|
|
@@ -880,18 +838,14 @@ var VideoService = class _VideoService extends IVideoService {
|
|
|
880
838
|
elizaLogger2.log(`Audio file size: ${audioBuffer.length} bytes`);
|
|
881
839
|
elizaLogger2.log("Starting transcription...");
|
|
882
840
|
const startTime = Date.now();
|
|
883
|
-
const transcriptionService = runtime.getService(
|
|
884
|
-
ServiceType.TRANSCRIPTION
|
|
885
|
-
);
|
|
841
|
+
const transcriptionService = runtime.getService(ServiceType.TRANSCRIPTION);
|
|
886
842
|
if (!transcriptionService) {
|
|
887
843
|
throw new Error("Transcription service not found");
|
|
888
844
|
}
|
|
889
845
|
const result = await transcriptionService.transcribeAudio(audioBuffer);
|
|
890
846
|
const transcript = result.text;
|
|
891
847
|
const endTime = Date.now();
|
|
892
|
-
elizaLogger2.log(
|
|
893
|
-
`Transcription completed in ${(endTime - startTime) / 1e3} seconds`
|
|
894
|
-
);
|
|
848
|
+
elizaLogger2.log(`Transcription completed in ${(endTime - startTime) / 1000} seconds`);
|
|
895
849
|
return transcript || "Transcription failed";
|
|
896
850
|
}
|
|
897
851
|
async convertMp4ToMp3(inputPath, outputPath) {
|
|
@@ -901,21 +855,21 @@ var VideoService = class _VideoService extends IVideoService {
|
|
|
901
855
|
elizaLogger2.log("Conversion to MP3 complete");
|
|
902
856
|
resolve();
|
|
903
857
|
}).on("error", (err) => {
|
|
904
|
-
elizaLogger2.log("Error converting to MP3:", err);
|
|
858
|
+
elizaLogger2.log("Error converting to MP3:", loggableError(err));
|
|
905
859
|
reject(err);
|
|
906
860
|
}).run();
|
|
907
861
|
});
|
|
908
862
|
}
|
|
909
863
|
async downloadAudio(url, outputFile) {
|
|
910
864
|
elizaLogger2.log("Downloading audio");
|
|
911
|
-
outputFile = outputFile ?? path2.join(this.dataDir, `${this.getVideoId(url)}.mp3`);
|
|
912
865
|
try {
|
|
913
866
|
if (url.endsWith(".mp4") || url.includes(".mp4?")) {
|
|
914
|
-
elizaLogger2.log(
|
|
915
|
-
"Direct MP4 file detected, downloading and converting to MP3"
|
|
916
|
-
);
|
|
867
|
+
elizaLogger2.log("Direct MP4 file detected, downloading and converting to MP3");
|
|
917
868
|
const tempMp4File = path2.join(tmpdir(), `${this.getVideoId(url)}.mp4`);
|
|
918
869
|
const response = await fetch(url);
|
|
870
|
+
if (!response.ok) {
|
|
871
|
+
throw new Error(`Failed to download MP4: ${response.status} ${response.statusText}`);
|
|
872
|
+
}
|
|
919
873
|
const arrayBuffer = await response.arrayBuffer();
|
|
920
874
|
const buffer = Buffer.from(arrayBuffer);
|
|
921
875
|
fs.writeFileSync(tempMp4File, buffer);
|
|
@@ -929,9 +883,7 @@ var VideoService = class _VideoService extends IVideoService {
|
|
|
929
883
|
}).run();
|
|
930
884
|
});
|
|
931
885
|
} else {
|
|
932
|
-
elizaLogger2.log(
|
|
933
|
-
"YouTube video detected, downloading audio with youtube-dl"
|
|
934
|
-
);
|
|
886
|
+
elizaLogger2.log("YouTube video detected, downloading audio with youtube-dl");
|
|
935
887
|
await this.binaries.runYtDlp(url, {
|
|
936
888
|
verbose: true,
|
|
937
889
|
extractAudio: true,
|
|
@@ -942,11 +894,11 @@ var VideoService = class _VideoService extends IVideoService {
|
|
|
942
894
|
}
|
|
943
895
|
return outputFile;
|
|
944
896
|
} catch (error) {
|
|
945
|
-
elizaLogger2.log("Error downloading audio:", error);
|
|
897
|
+
elizaLogger2.log("Error downloading audio:", loggableError(error));
|
|
946
898
|
throw new Error("Failed to download audio");
|
|
947
899
|
}
|
|
948
900
|
}
|
|
949
|
-
}
|
|
901
|
+
}
|
|
950
902
|
|
|
951
903
|
// src/index.ts
|
|
952
904
|
var videoPlugin = {
|
|
@@ -955,10 +907,15 @@ var videoPlugin = {
|
|
|
955
907
|
services: [VideoService],
|
|
956
908
|
actions: [],
|
|
957
909
|
providers: [],
|
|
958
|
-
routes: []
|
|
910
|
+
routes: [],
|
|
911
|
+
async dispose(runtime) {
|
|
912
|
+
const svc = runtime.getService(VideoService.serviceType);
|
|
913
|
+
await svc?.stop();
|
|
914
|
+
}
|
|
959
915
|
};
|
|
960
|
-
var
|
|
916
|
+
var src_default = videoPlugin;
|
|
961
917
|
export {
|
|
962
|
-
|
|
918
|
+
src_default as default
|
|
963
919
|
};
|
|
964
|
-
|
|
920
|
+
|
|
921
|
+
//# debugId=17A8C3F43B7ECE4364756E2164756E21
|