@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/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 * 1e3;
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
- var BinaryResolver = class _BinaryResolver {
48
+
49
+ class BinaryResolver {
46
50
  static _instance = null;
47
51
  static instance() {
48
- if (!_BinaryResolver._instance)
49
- _BinaryResolver._instance = new _BinaryResolver();
50
- return _BinaryResolver._instance;
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
- _BinaryResolver._instance = null;
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 = void 0;
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 !== void 0 ? opts.envOverridePath : process.env.ELIZA_YT_DLP_PATH ?? null;
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) return 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 !== void 0) return 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) return 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) return false;
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
- `yt-dlp release ${release.tag_name} has no SHA2-256SUMS asset`
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) continue;
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) return 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") return "yt-dlp_macos";
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") return "yt-dlp_linux_aarch64";
394
- if (arch === "arm") return "yt-dlp_linux_armv7l";
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 = ((_a = constantsModule.default) == null ? void 0 : _a.YOUTUBE_DL_PATH) ?? constantsModule.YOUTUBE_DL_PATH;
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 = createRequire(import.meta.url);
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)) return candidate;
427
- } catch {
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) return null;
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) continue;
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) hash.update(chunk);
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) return false;
415
+ if (!v)
416
+ return false;
466
417
  return v === "1" || v.toLowerCase() === "true";
467
418
  }
468
419
  function errorMessage(err) {
469
- if (!err) return "";
470
- if (typeof err === "string") return err;
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") return anyErr.message;
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
- var VideoService = class _VideoService extends IVideoService {
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 _VideoService(runtime);
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) return;
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
- (f) => ({
529
- formatId: f.format_id ?? "",
530
- url: f.url ?? "",
531
- extension: f.ext ?? "",
532
- quality: f.quality !== void 0 && f.quality !== "" ? String(f.quality) : "unknown",
533
- fileSize: f.filesize,
534
- videoCodec: f.vcodec,
535
- audioCodec: f.acodec,
536
- resolution: f.resolution,
537
- fps: f.fps,
538
- bitrate: f.tbr
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: videoInfo.upload_date ? new Date(videoInfo.upload_date) : void 0,
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 = (options == null ? void 0 : options.outputPath) || path2.join(this.dataDir, `${videoId}.mp4`);
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 == null ? void 0 : options.format) {
529
+ if (options?.format) {
566
530
  downloadOptions.format = options.format;
567
531
  }
568
- if (options == null ? void 0 : options.quality) {
532
+ if (options?.quality) {
569
533
  downloadOptions.format = options.quality;
570
534
  }
571
- if (options == null ? void 0 : options.audioOnly) {
535
+ if (options?.audioOnly) {
572
536
  downloadOptions.extractAudio = true;
573
537
  downloadOptions.audioFormat = "mp3";
574
538
  }
575
- if (options == null ? void 0 : options.videoOnly) {
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 == null ? void 0 : options.startTime) {
592
+ if (options?.startTime) {
629
593
  command = command.seekInput(options.startTime);
630
594
  }
631
595
  command = command.output(outputPath);
632
- if (options == null ? void 0 : options.endTime) {
596
+ if (options?.endTime) {
633
597
  command = command.duration(options.endTime - (options.startTime || 0));
634
598
  }
635
- if (options == null ? void 0 : options.outputFormat) {
599
+ if (options?.outputFormat) {
636
600
  command = command.format(options.outputFormat);
637
601
  }
638
- if (options == null ? void 0 : options.resolution) {
602
+ if (options?.resolution) {
639
603
  command = command.size(options.resolution);
640
604
  }
641
- if (options == null ? void 0 : options.bitrate) {
605
+ if (options?.bitrate) {
642
606
  command = command.videoBitrate(options.bitrate);
643
607
  }
644
- if (options == null ? void 0 : options.framerate) {
608
+ if (options?.framerate) {
645
609
  command = command.fps(options.framerate);
646
610
  }
647
- if (options == null ? void 0 : options.videoCodec) {
611
+ if (options?.videoCodec) {
648
612
  command = command.videoCodec(options.videoCodec);
649
613
  }
650
- if (options == null ? void 0 : options.audioCodec) {
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 ((_a = parsed.formats) == null ? void 0 : _a.length) {
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 !== void 0 && format.quality !== "" ? String(format.quality) : "unknown",
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
- () => this.processVideoFromUrl(url, runtime)
732
- );
733
- this.processingChain = run.then(
734
- () => void 0,
735
- () => void 0
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
- var _a;
741
- const videoId = ((_a = url.match(
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 && videoInfo.subtitles.en) {
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 && videoInfo.automatic_captions.en) {
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 && videoInfo.categories.includes("Music")) {
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("\n", " ");
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("\n\n").map((block) => block.split("\n").slice(2).join(" ")).join(" ");
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 index_default = videoPlugin;
916
+ var src_default = videoPlugin;
961
917
  export {
962
- index_default as default
918
+ src_default as default
963
919
  };
964
- //# sourceMappingURL=index.js.map
920
+
921
+ //# debugId=17A8C3F43B7ECE4364756E2164756E21