@elizaos/plugin-video 2.0.0-beta.1
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/README.md +31 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +964 -0
- package/dist/index.js.map +1 -0
- package/package.json +46 -0
- package/tsup.config.ts +22 -0
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @elizaos/plugin-video Video Service
|
|
2
|
+
|
|
3
|
+
The VideoService provides comprehensive video processing capabilities with a focus on efficient handling and transcription:
|
|
4
|
+
|
|
5
|
+
**Key Features:**
|
|
6
|
+
- **Video Download**: Supports both YouTube videos and direct MP4 URLs
|
|
7
|
+
- **Format Handling**:
|
|
8
|
+
- Automatic format detection and conversion
|
|
9
|
+
- MP4 to MP3 conversion for audio processing
|
|
10
|
+
- Support for various video platforms (YouTube, Vimeo)
|
|
11
|
+
- **Transcription Pipeline**:
|
|
12
|
+
1. Attempts to extract manual subtitles (SRT format)
|
|
13
|
+
2. Falls back to automatic captions if available
|
|
14
|
+
3. Uses audio transcription as final fallback
|
|
15
|
+
- **Performance Optimizations**:
|
|
16
|
+
- Queue-based processing for multiple videos
|
|
17
|
+
- Built-in caching system for processed results
|
|
18
|
+
- Efficient temporary file management
|
|
19
|
+
- **Error Handling**:
|
|
20
|
+
- Graceful fallbacks for different transcription methods
|
|
21
|
+
- Comprehensive error reporting
|
|
22
|
+
- Automatic cleanup of temporary files
|
|
23
|
+
|
|
24
|
+
**Usage Example:**
|
|
25
|
+
```typescript
|
|
26
|
+
const videoService = runtime.getService<IVideoService>(ServiceType.VIDEO);
|
|
27
|
+
|
|
28
|
+
// Process a video URL
|
|
29
|
+
const result = await videoService.processVideo(videoUrl, runtime);
|
|
30
|
+
// Returns: Media object with id, url, title, source, description, and transcript
|
|
31
|
+
```
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,964 @@
|
|
|
1
|
+
// src/services/video.ts
|
|
2
|
+
import {
|
|
3
|
+
IVideoService,
|
|
4
|
+
ServiceType,
|
|
5
|
+
stringToUuid,
|
|
6
|
+
elizaLogger as elizaLogger2
|
|
7
|
+
} from "@elizaos/core";
|
|
8
|
+
import ffmpeg from "fluent-ffmpeg";
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import { tmpdir } from "os";
|
|
11
|
+
import path2 from "path";
|
|
12
|
+
|
|
13
|
+
// src/services/binaries.ts
|
|
14
|
+
import { createHash } from "crypto";
|
|
15
|
+
import {
|
|
16
|
+
createWriteStream,
|
|
17
|
+
constants as fsConstants,
|
|
18
|
+
promises as fsp
|
|
19
|
+
} from "fs";
|
|
20
|
+
import path from "path";
|
|
21
|
+
import { Readable } from "stream";
|
|
22
|
+
import { pipeline } from "stream/promises";
|
|
23
|
+
import { elizaLogger, resolveStateDir } from "@elizaos/core";
|
|
24
|
+
import youtubeDl from "youtube-dl-exec";
|
|
25
|
+
function isYtDlpModule(value) {
|
|
26
|
+
return typeof value === "function" && typeof value.create === "function";
|
|
27
|
+
}
|
|
28
|
+
if (!isYtDlpModule(youtubeDl)) {
|
|
29
|
+
throw new TypeError("youtube-dl-exec did not expose the expected runner API");
|
|
30
|
+
}
|
|
31
|
+
var ytDlpModule = youtubeDl;
|
|
32
|
+
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;
|
|
34
|
+
var YT_DLP_META_FILENAME = "yt-dlp.meta.json";
|
|
35
|
+
var EXTRACTOR_BROKEN_PATTERNS = [
|
|
36
|
+
/Unable to extract/i,
|
|
37
|
+
/Sign in to confirm/i,
|
|
38
|
+
/nsig extraction failed/i,
|
|
39
|
+
/n_param signature/i,
|
|
40
|
+
/Failed to parse JSON/i,
|
|
41
|
+
/HTTP Error 403/i,
|
|
42
|
+
/This video is unavailable/i,
|
|
43
|
+
/Got error: HTTP Error 429/i
|
|
44
|
+
];
|
|
45
|
+
var BinaryResolver = class _BinaryResolver {
|
|
46
|
+
static _instance = null;
|
|
47
|
+
static instance() {
|
|
48
|
+
if (!_BinaryResolver._instance)
|
|
49
|
+
_BinaryResolver._instance = new _BinaryResolver();
|
|
50
|
+
return _BinaryResolver._instance;
|
|
51
|
+
}
|
|
52
|
+
/** Test hook: drop the singleton so the next instance() returns fresh state. */
|
|
53
|
+
static resetForTests() {
|
|
54
|
+
_BinaryResolver._instance = null;
|
|
55
|
+
}
|
|
56
|
+
binariesDir;
|
|
57
|
+
releaseUrl;
|
|
58
|
+
fetchImpl;
|
|
59
|
+
now;
|
|
60
|
+
disableAutoUpdate;
|
|
61
|
+
preferSystemPath;
|
|
62
|
+
updateThrottleMs;
|
|
63
|
+
envOverridePath;
|
|
64
|
+
resolvedYtDlpPath = null;
|
|
65
|
+
resolvedYtDlpSource = null;
|
|
66
|
+
cachedRunner = null;
|
|
67
|
+
resolvedFfmpegPath = void 0;
|
|
68
|
+
updateInFlight = null;
|
|
69
|
+
constructor(opts = {}) {
|
|
70
|
+
this.binariesDir = opts.binariesDir ?? defaultBinariesDir();
|
|
71
|
+
this.releaseUrl = opts.releaseUrl ?? YT_DLP_RELEASE_URL;
|
|
72
|
+
this.fetchImpl = opts.fetchImpl ?? ((input, init) => globalThis.fetch(input, init));
|
|
73
|
+
this.now = opts.now ?? Date.now;
|
|
74
|
+
this.disableAutoUpdate = opts.disableAutoUpdate ?? envBool("ELIZA_DISABLE_YTDLP_AUTOUPDATE");
|
|
75
|
+
this.preferSystemPath = opts.preferSystemPath ?? envBool("ELIZA_YT_DLP_PREFER_PATH");
|
|
76
|
+
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;
|
|
78
|
+
}
|
|
79
|
+
get cacheDir() {
|
|
80
|
+
return this.binariesDir;
|
|
81
|
+
}
|
|
82
|
+
get cachedYtDlpPath() {
|
|
83
|
+
return path.join(this.binariesDir, ytDlpFileName());
|
|
84
|
+
}
|
|
85
|
+
get metaPath() {
|
|
86
|
+
return path.join(this.binariesDir, YT_DLP_META_FILENAME);
|
|
87
|
+
}
|
|
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
|
+
async getYtDlpPath() {
|
|
95
|
+
if (this.resolvedYtDlpPath) return this.resolvedYtDlpPath;
|
|
96
|
+
if (this.envOverridePath) {
|
|
97
|
+
if (await isExecutable(this.envOverridePath)) {
|
|
98
|
+
this.resolvedYtDlpPath = this.envOverridePath;
|
|
99
|
+
this.resolvedYtDlpSource = "env";
|
|
100
|
+
elizaLogger.log(
|
|
101
|
+
`[plugin-video] Using yt-dlp from env override: ${this.envOverridePath}`
|
|
102
|
+
);
|
|
103
|
+
return this.envOverridePath;
|
|
104
|
+
}
|
|
105
|
+
elizaLogger.warn(
|
|
106
|
+
`[plugin-video] ELIZA_YT_DLP_PATH=${this.envOverridePath} is not executable; falling through.`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
if (this.preferSystemPath) {
|
|
110
|
+
const sys2 = await lookupOnPath("yt-dlp");
|
|
111
|
+
if (sys2) {
|
|
112
|
+
this.resolvedYtDlpPath = sys2;
|
|
113
|
+
this.resolvedYtDlpSource = "path";
|
|
114
|
+
elizaLogger.log(`[plugin-video] Using yt-dlp from PATH: ${sys2}`);
|
|
115
|
+
return sys2;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const cachePath = this.cachedYtDlpPath;
|
|
119
|
+
if (await isExecutable(cachePath)) {
|
|
120
|
+
this.resolvedYtDlpPath = cachePath;
|
|
121
|
+
this.resolvedYtDlpSource = "cache";
|
|
122
|
+
elizaLogger.log(
|
|
123
|
+
`[plugin-video] Using yt-dlp from managed cache: ${cachePath}`
|
|
124
|
+
);
|
|
125
|
+
return cachePath;
|
|
126
|
+
}
|
|
127
|
+
const sys = await lookupOnPath("yt-dlp");
|
|
128
|
+
if (sys) {
|
|
129
|
+
this.resolvedYtDlpPath = sys;
|
|
130
|
+
this.resolvedYtDlpSource = "path";
|
|
131
|
+
elizaLogger.log(
|
|
132
|
+
`[plugin-video] Using yt-dlp from PATH (cache empty): ${sys}`
|
|
133
|
+
);
|
|
134
|
+
return sys;
|
|
135
|
+
}
|
|
136
|
+
const bundled = await getBundledYtDlpPath();
|
|
137
|
+
if (bundled) {
|
|
138
|
+
this.resolvedYtDlpPath = bundled;
|
|
139
|
+
this.resolvedYtDlpSource = "bundled";
|
|
140
|
+
elizaLogger.log(
|
|
141
|
+
`[plugin-video] Using yt-dlp from youtube-dl-exec bundle: ${bundled}`
|
|
142
|
+
);
|
|
143
|
+
return bundled;
|
|
144
|
+
}
|
|
145
|
+
elizaLogger.log(
|
|
146
|
+
"[plugin-video] No yt-dlp binary found; downloading to managed cache."
|
|
147
|
+
);
|
|
148
|
+
await this.downloadYtDlp();
|
|
149
|
+
this.resolvedYtDlpPath = cachePath;
|
|
150
|
+
this.resolvedYtDlpSource = "cache";
|
|
151
|
+
return cachePath;
|
|
152
|
+
}
|
|
153
|
+
/** Resolution order: ELIZA_FFMPEG_PATH env → system ffmpeg → ffmpeg-static. */
|
|
154
|
+
async getFfmpegPath() {
|
|
155
|
+
if (this.resolvedFfmpegPath !== void 0) return this.resolvedFfmpegPath;
|
|
156
|
+
const envPath = process.env.ELIZA_FFMPEG_PATH;
|
|
157
|
+
if (envPath && await isExecutable(envPath)) {
|
|
158
|
+
this.resolvedFfmpegPath = envPath;
|
|
159
|
+
return envPath;
|
|
160
|
+
}
|
|
161
|
+
const sys = await lookupOnPath("ffmpeg");
|
|
162
|
+
if (sys) {
|
|
163
|
+
this.resolvedFfmpegPath = sys;
|
|
164
|
+
return sys;
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
const mod = await import("ffmpeg-static");
|
|
168
|
+
const staticPath = typeof mod === "string" ? mod : mod && typeof mod === "object" && "default" in mod ? mod.default : null;
|
|
169
|
+
if (typeof staticPath === "string" && staticPath.length > 0 && await isExecutable(staticPath)) {
|
|
170
|
+
this.resolvedFfmpegPath = staticPath;
|
|
171
|
+
return staticPath;
|
|
172
|
+
}
|
|
173
|
+
} catch (err) {
|
|
174
|
+
elizaLogger.warn(
|
|
175
|
+
`[plugin-video] ffmpeg-static not loadable: ${describeError(err)}`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
this.resolvedFfmpegPath = null;
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
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
|
+
async getYtDlpRunner() {
|
|
186
|
+
if (this.cachedRunner) return this.cachedRunner;
|
|
187
|
+
const binPath = await this.getYtDlpPath();
|
|
188
|
+
this.cachedRunner = ytDlpModule.create(binPath);
|
|
189
|
+
return this.cachedRunner;
|
|
190
|
+
}
|
|
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
|
+
async runYtDlp(url, flags) {
|
|
196
|
+
const runner = await this.getYtDlpRunner();
|
|
197
|
+
try {
|
|
198
|
+
return await runner(url, flags);
|
|
199
|
+
} catch (err) {
|
|
200
|
+
if (!this.shouldRetryWithUpdate(err)) {
|
|
201
|
+
throw err;
|
|
202
|
+
}
|
|
203
|
+
const updated = await this.tryUpdate();
|
|
204
|
+
if (!updated) {
|
|
205
|
+
throw err;
|
|
206
|
+
}
|
|
207
|
+
const refreshed = await this.getYtDlpRunner();
|
|
208
|
+
return await refreshed(url, flags);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
shouldRetryWithUpdate(err) {
|
|
212
|
+
if (this.disableAutoUpdate) return false;
|
|
213
|
+
if (this.resolvedYtDlpSource !== "cache" && this.resolvedYtDlpSource !== "bundled") {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
const msg = errorMessage(err);
|
|
217
|
+
return EXTRACTOR_BROKEN_PATTERNS.some((p) => p.test(msg));
|
|
218
|
+
}
|
|
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
|
+
async tryUpdate() {
|
|
224
|
+
if (this.updateInFlight) {
|
|
225
|
+
await this.updateInFlight;
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
const meta = await this.readMeta();
|
|
229
|
+
if (meta) {
|
|
230
|
+
const sinceLast = this.now() - meta.lastUpdateAttemptedAt;
|
|
231
|
+
if (sinceLast < this.updateThrottleMs) {
|
|
232
|
+
elizaLogger.warn(
|
|
233
|
+
`[plugin-video] yt-dlp update throttled (last attempt ${Math.floor(sinceLast / 1e3)}s ago).`
|
|
234
|
+
);
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const job = (async () => {
|
|
239
|
+
await this.touchUpdateAttempt(meta);
|
|
240
|
+
await this.downloadYtDlp();
|
|
241
|
+
this.resetRunnerCache();
|
|
242
|
+
})();
|
|
243
|
+
this.updateInFlight = job;
|
|
244
|
+
try {
|
|
245
|
+
await job;
|
|
246
|
+
return true;
|
|
247
|
+
} catch (err) {
|
|
248
|
+
elizaLogger.error(
|
|
249
|
+
`[plugin-video] yt-dlp update failed: ${describeError(err)}`
|
|
250
|
+
);
|
|
251
|
+
return false;
|
|
252
|
+
} finally {
|
|
253
|
+
this.updateInFlight = null;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
resetRunnerCache() {
|
|
257
|
+
this.cachedRunner = null;
|
|
258
|
+
this.resolvedYtDlpPath = null;
|
|
259
|
+
this.resolvedYtDlpSource = null;
|
|
260
|
+
}
|
|
261
|
+
/** Force a fresh yt-dlp download regardless of throttle. Test/admin hook. */
|
|
262
|
+
async forceUpdateYtDlp() {
|
|
263
|
+
const meta = await this.downloadYtDlp();
|
|
264
|
+
this.resetRunnerCache();
|
|
265
|
+
await this.getYtDlpPath();
|
|
266
|
+
return { version: meta.version, path: this.cachedYtDlpPath };
|
|
267
|
+
}
|
|
268
|
+
async readMeta() {
|
|
269
|
+
try {
|
|
270
|
+
const raw = await fsp.readFile(this.metaPath, "utf8");
|
|
271
|
+
return JSON.parse(raw);
|
|
272
|
+
} catch {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
async writeMeta(meta) {
|
|
277
|
+
await fsp.mkdir(this.binariesDir, { recursive: true });
|
|
278
|
+
await fsp.writeFile(this.metaPath, JSON.stringify(meta, null, 2));
|
|
279
|
+
}
|
|
280
|
+
async touchUpdateAttempt(prev) {
|
|
281
|
+
const next = prev ? { ...prev, lastUpdateAttemptedAt: this.now() } : {
|
|
282
|
+
version: "",
|
|
283
|
+
sha256: "",
|
|
284
|
+
assetName: "",
|
|
285
|
+
downloadedAt: 0,
|
|
286
|
+
lastUpdateAttemptedAt: this.now()
|
|
287
|
+
};
|
|
288
|
+
await this.writeMeta(next);
|
|
289
|
+
}
|
|
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
|
+
async downloadYtDlp() {
|
|
296
|
+
const release = await this.fetchRelease();
|
|
297
|
+
const assetName = ytDlpAssetName();
|
|
298
|
+
const asset = release.assets.find((a) => a.name === assetName);
|
|
299
|
+
if (!asset) {
|
|
300
|
+
throw new Error(
|
|
301
|
+
`yt-dlp release ${release.tag_name} has no asset named '${assetName}'`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
const sumsAsset = release.assets.find((a) => a.name === "SHA2-256SUMS");
|
|
305
|
+
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
|
+
);
|
|
314
|
+
await fsp.mkdir(this.binariesDir, { recursive: true });
|
|
315
|
+
const tmpPath = `${this.cachedYtDlpPath}.tmp.${process.pid}.${Date.now()}`;
|
|
316
|
+
await this.downloadToFile(asset.browser_download_url, tmpPath);
|
|
317
|
+
const actualSha = await sha256OfFile(tmpPath);
|
|
318
|
+
if (actualSha !== expectedSha) {
|
|
319
|
+
await safeUnlink(tmpPath);
|
|
320
|
+
throw new Error(
|
|
321
|
+
`yt-dlp SHA256 mismatch: expected ${expectedSha}, got ${actualSha}`
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
await fsp.chmod(tmpPath, 493);
|
|
325
|
+
await fsp.rename(tmpPath, this.cachedYtDlpPath);
|
|
326
|
+
const meta = {
|
|
327
|
+
version: release.tag_name,
|
|
328
|
+
sha256: expectedSha,
|
|
329
|
+
assetName,
|
|
330
|
+
downloadedAt: this.now(),
|
|
331
|
+
lastUpdateAttemptedAt: this.now()
|
|
332
|
+
};
|
|
333
|
+
await this.writeMeta(meta);
|
|
334
|
+
elizaLogger.log(
|
|
335
|
+
`[plugin-video] yt-dlp ${release.tag_name} installed at ${this.cachedYtDlpPath}`
|
|
336
|
+
);
|
|
337
|
+
return meta;
|
|
338
|
+
}
|
|
339
|
+
async fetchRelease() {
|
|
340
|
+
const res = await this.fetchImpl(this.releaseUrl, {
|
|
341
|
+
headers: { Accept: "application/vnd.github+json" }
|
|
342
|
+
});
|
|
343
|
+
if (!res.ok) {
|
|
344
|
+
throw new Error(
|
|
345
|
+
`yt-dlp release fetch failed: ${res.status} ${res.statusText}`
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
return await res.json();
|
|
349
|
+
}
|
|
350
|
+
async fetchExpectedSha(sumsUrl, assetName) {
|
|
351
|
+
const res = await this.fetchImpl(sumsUrl);
|
|
352
|
+
if (!res.ok) {
|
|
353
|
+
throw new Error(
|
|
354
|
+
`SHA2-256SUMS fetch failed: ${res.status} ${res.statusText}`
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
const text = await res.text();
|
|
358
|
+
for (const line of text.split(/\r?\n/)) {
|
|
359
|
+
const trimmed = line.trim();
|
|
360
|
+
if (!trimmed) continue;
|
|
361
|
+
const parts = trimmed.split(/\s+/);
|
|
362
|
+
if (parts.length >= 2 && parts[parts.length - 1] === assetName) {
|
|
363
|
+
return parts[0].toLowerCase();
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
throw new Error(`SHA2-256SUMS missing entry for ${assetName}`);
|
|
367
|
+
}
|
|
368
|
+
async downloadToFile(url, dest) {
|
|
369
|
+
const res = await this.fetchImpl(url);
|
|
370
|
+
if (!res.ok || !res.body) {
|
|
371
|
+
throw new Error(
|
|
372
|
+
`yt-dlp binary fetch failed: ${res.status} ${res.statusText}`
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
const nodeStream = Readable.fromWeb(
|
|
376
|
+
res.body
|
|
377
|
+
);
|
|
378
|
+
await pipeline(nodeStream, createWriteStream(dest));
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
function defaultBinariesDir() {
|
|
382
|
+
const explicit = process.env.ELIZA_BINARIES_DIR;
|
|
383
|
+
if (explicit) return explicit;
|
|
384
|
+
return path.join(resolveStateDir(), "binaries");
|
|
385
|
+
}
|
|
386
|
+
function ytDlpAssetName() {
|
|
387
|
+
const platform = process.platform;
|
|
388
|
+
const arch = process.arch;
|
|
389
|
+
if (platform === "darwin") return "yt-dlp_macos";
|
|
390
|
+
if (platform === "win32")
|
|
391
|
+
return arch === "ia32" ? "yt-dlp_x86.exe" : "yt-dlp.exe";
|
|
392
|
+
if (platform === "linux") {
|
|
393
|
+
if (arch === "arm64") return "yt-dlp_linux_aarch64";
|
|
394
|
+
if (arch === "arm") return "yt-dlp_linux_armv7l";
|
|
395
|
+
return "yt-dlp_linux";
|
|
396
|
+
}
|
|
397
|
+
throw new Error(`Unsupported platform for yt-dlp: ${platform}/${arch}`);
|
|
398
|
+
}
|
|
399
|
+
function ytDlpFileName() {
|
|
400
|
+
return process.platform === "win32" ? "yt-dlp.exe" : "yt-dlp";
|
|
401
|
+
}
|
|
402
|
+
async function isExecutable(p) {
|
|
403
|
+
try {
|
|
404
|
+
await fsp.access(p, fsConstants.X_OK);
|
|
405
|
+
const st = await fsp.stat(p);
|
|
406
|
+
return st.isFile();
|
|
407
|
+
} catch {
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
async function getBundledYtDlpPath() {
|
|
412
|
+
var _a;
|
|
413
|
+
try {
|
|
414
|
+
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;
|
|
416
|
+
if (typeof fromConstants === "string" && fromConstants.length > 0 && await isExecutable(fromConstants)) {
|
|
417
|
+
return fromConstants;
|
|
418
|
+
}
|
|
419
|
+
} catch {
|
|
420
|
+
}
|
|
421
|
+
try {
|
|
422
|
+
const { createRequire } = await import("module");
|
|
423
|
+
const req = createRequire(import.meta.url);
|
|
424
|
+
const pkgPath = req.resolve("youtube-dl-exec/package.json");
|
|
425
|
+
const candidate = path.join(path.dirname(pkgPath), "bin", ytDlpFileName());
|
|
426
|
+
if (await isExecutable(candidate)) return candidate;
|
|
427
|
+
} catch {
|
|
428
|
+
}
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
async function lookupOnPath(name) {
|
|
432
|
+
const pathEnv = process.env.PATH;
|
|
433
|
+
if (!pathEnv) return null;
|
|
434
|
+
const exts = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").map((e) => e.toLowerCase()) : [""];
|
|
435
|
+
for (const dir of pathEnv.split(path.delimiter)) {
|
|
436
|
+
if (!dir) continue;
|
|
437
|
+
for (const ext of exts) {
|
|
438
|
+
const candidate = path.join(dir, `${name}${ext}`);
|
|
439
|
+
if (await isExecutable(candidate)) {
|
|
440
|
+
return candidate;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
async function sha256OfFile(p) {
|
|
447
|
+
const hash = createHash("sha256");
|
|
448
|
+
const fd = await fsp.open(p, "r");
|
|
449
|
+
try {
|
|
450
|
+
const stream = fd.createReadStream();
|
|
451
|
+
for await (const chunk of stream) hash.update(chunk);
|
|
452
|
+
} finally {
|
|
453
|
+
await fd.close();
|
|
454
|
+
}
|
|
455
|
+
return hash.digest("hex");
|
|
456
|
+
}
|
|
457
|
+
async function safeUnlink(p) {
|
|
458
|
+
try {
|
|
459
|
+
await fsp.unlink(p);
|
|
460
|
+
} catch {
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
function envBool(name) {
|
|
464
|
+
const v = process.env[name];
|
|
465
|
+
if (!v) return false;
|
|
466
|
+
return v === "1" || v.toLowerCase() === "true";
|
|
467
|
+
}
|
|
468
|
+
function errorMessage(err) {
|
|
469
|
+
if (!err) return "";
|
|
470
|
+
if (typeof err === "string") return err;
|
|
471
|
+
const anyErr = err;
|
|
472
|
+
if (typeof anyErr.stderr === "string" && anyErr.stderr.length > 0)
|
|
473
|
+
return anyErr.stderr;
|
|
474
|
+
if (typeof anyErr.message === "string") return anyErr.message;
|
|
475
|
+
try {
|
|
476
|
+
return JSON.stringify(err);
|
|
477
|
+
} catch {
|
|
478
|
+
return String(err);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
function describeError(err) {
|
|
482
|
+
return errorMessage(err) || "(no message)";
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// src/services/video.ts
|
|
486
|
+
var VideoService = class _VideoService extends IVideoService {
|
|
487
|
+
capabilityDescription = "Video download, processing, and conversion capabilities";
|
|
488
|
+
static serviceType = ServiceType.VIDEO;
|
|
489
|
+
cacheKey = "content/video";
|
|
490
|
+
dataDir = "./content_cache";
|
|
491
|
+
binaries;
|
|
492
|
+
ffmpegPathConfigured = false;
|
|
493
|
+
/** Serialize downloads/processing so cache keys and temp files do not race. */
|
|
494
|
+
processingChain = Promise.resolve();
|
|
495
|
+
constructor(runtime, binaries) {
|
|
496
|
+
super(runtime);
|
|
497
|
+
this.binaries = binaries ?? BinaryResolver.instance();
|
|
498
|
+
this.ensureDataDirectoryExists();
|
|
499
|
+
}
|
|
500
|
+
static async start(runtime) {
|
|
501
|
+
const service = new _VideoService(runtime);
|
|
502
|
+
await service.initialize(runtime);
|
|
503
|
+
return service;
|
|
504
|
+
}
|
|
505
|
+
async initialize(_runtime) {
|
|
506
|
+
await this.configureFfmpeg();
|
|
507
|
+
}
|
|
508
|
+
async configureFfmpeg() {
|
|
509
|
+
if (this.ffmpegPathConfigured) return;
|
|
510
|
+
const ffmpegPath = await this.binaries.getFfmpegPath();
|
|
511
|
+
if (ffmpegPath) {
|
|
512
|
+
ffmpeg.setFfmpegPath(ffmpegPath);
|
|
513
|
+
elizaLogger2.log(`[plugin-video] ffmpeg path: ${ffmpegPath}`);
|
|
514
|
+
} 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
|
+
);
|
|
518
|
+
}
|
|
519
|
+
this.ffmpegPathConfigured = true;
|
|
520
|
+
}
|
|
521
|
+
async stop() {
|
|
522
|
+
this.processingChain = Promise.resolve();
|
|
523
|
+
}
|
|
524
|
+
// Required abstract methods from IVideoService
|
|
525
|
+
async getVideoInfo(url) {
|
|
526
|
+
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
|
+
);
|
|
541
|
+
return {
|
|
542
|
+
title: videoInfo.title,
|
|
543
|
+
duration: videoInfo.duration,
|
|
544
|
+
url,
|
|
545
|
+
thumbnail: videoInfo.thumbnail,
|
|
546
|
+
description: videoInfo.description,
|
|
547
|
+
uploader: videoInfo.channel,
|
|
548
|
+
viewCount: videoInfo.view_count,
|
|
549
|
+
uploadDate: videoInfo.upload_date ? new Date(videoInfo.upload_date) : void 0,
|
|
550
|
+
formats
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
async downloadVideo(url, options) {
|
|
554
|
+
const videoId = this.getVideoId(url);
|
|
555
|
+
const outputFile = (options == null ? void 0 : options.outputPath) || path2.join(this.dataDir, `${videoId}.mp4`);
|
|
556
|
+
if (fs.existsSync(outputFile)) {
|
|
557
|
+
return outputFile;
|
|
558
|
+
}
|
|
559
|
+
try {
|
|
560
|
+
const downloadOptions = {
|
|
561
|
+
verbose: true,
|
|
562
|
+
output: outputFile,
|
|
563
|
+
writeInfoJson: true
|
|
564
|
+
};
|
|
565
|
+
if (options == null ? void 0 : options.format) {
|
|
566
|
+
downloadOptions.format = options.format;
|
|
567
|
+
}
|
|
568
|
+
if (options == null ? void 0 : options.quality) {
|
|
569
|
+
downloadOptions.format = options.quality;
|
|
570
|
+
}
|
|
571
|
+
if (options == null ? void 0 : options.audioOnly) {
|
|
572
|
+
downloadOptions.extractAudio = true;
|
|
573
|
+
downloadOptions.audioFormat = "mp3";
|
|
574
|
+
}
|
|
575
|
+
if (options == null ? void 0 : options.videoOnly) {
|
|
576
|
+
downloadOptions.format = "bestvideo[ext=mp4]/best[ext=mp4]/best";
|
|
577
|
+
}
|
|
578
|
+
await this.binaries.runYtDlp(url, downloadOptions);
|
|
579
|
+
return outputFile;
|
|
580
|
+
} catch (error) {
|
|
581
|
+
elizaLogger2.log("Error downloading video:", error);
|
|
582
|
+
throw new Error("Failed to download video");
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
async extractAudio(videoPath, outputPath) {
|
|
586
|
+
const videoId = this.getVideoId(videoPath);
|
|
587
|
+
const audioFile = outputPath || path2.join(this.dataDir, `${videoId}.mp3`);
|
|
588
|
+
if (fs.existsSync(audioFile)) {
|
|
589
|
+
return audioFile;
|
|
590
|
+
}
|
|
591
|
+
await this.configureFfmpeg();
|
|
592
|
+
return new Promise((resolve, reject) => {
|
|
593
|
+
ffmpeg(videoPath).output(audioFile).noVideo().audioCodec("libmp3lame").on("end", () => {
|
|
594
|
+
elizaLogger2.log("Audio extraction complete");
|
|
595
|
+
resolve(audioFile);
|
|
596
|
+
}).on("error", (err) => {
|
|
597
|
+
elizaLogger2.log("Error extracting audio:", err);
|
|
598
|
+
reject(err);
|
|
599
|
+
}).run();
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
async getThumbnail(videoPath, timestamp = 1) {
|
|
603
|
+
const videoId = this.getVideoId(videoPath);
|
|
604
|
+
const thumbnailFile = path2.join(this.dataDir, `${videoId}_thumb.jpg`);
|
|
605
|
+
if (fs.existsSync(thumbnailFile)) {
|
|
606
|
+
return thumbnailFile;
|
|
607
|
+
}
|
|
608
|
+
await this.configureFfmpeg();
|
|
609
|
+
return new Promise((resolve, reject) => {
|
|
610
|
+
ffmpeg(videoPath).screenshots({
|
|
611
|
+
timestamps: [timestamp],
|
|
612
|
+
filename: `${videoId}_thumb.jpg`,
|
|
613
|
+
folder: this.dataDir,
|
|
614
|
+
size: "320x240"
|
|
615
|
+
}).on("end", () => {
|
|
616
|
+
elizaLogger2.log("Thumbnail generation complete");
|
|
617
|
+
resolve(thumbnailFile);
|
|
618
|
+
}).on("error", (err) => {
|
|
619
|
+
elizaLogger2.log("Error generating thumbnail:", err);
|
|
620
|
+
reject(err);
|
|
621
|
+
});
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
async convertVideo(videoPath, outputPath, options) {
|
|
625
|
+
await this.configureFfmpeg();
|
|
626
|
+
return new Promise((resolve, reject) => {
|
|
627
|
+
let command = ffmpeg(videoPath);
|
|
628
|
+
if (options == null ? void 0 : options.startTime) {
|
|
629
|
+
command = command.seekInput(options.startTime);
|
|
630
|
+
}
|
|
631
|
+
command = command.output(outputPath);
|
|
632
|
+
if (options == null ? void 0 : options.endTime) {
|
|
633
|
+
command = command.duration(options.endTime - (options.startTime || 0));
|
|
634
|
+
}
|
|
635
|
+
if (options == null ? void 0 : options.outputFormat) {
|
|
636
|
+
command = command.format(options.outputFormat);
|
|
637
|
+
}
|
|
638
|
+
if (options == null ? void 0 : options.resolution) {
|
|
639
|
+
command = command.size(options.resolution);
|
|
640
|
+
}
|
|
641
|
+
if (options == null ? void 0 : options.bitrate) {
|
|
642
|
+
command = command.videoBitrate(options.bitrate);
|
|
643
|
+
}
|
|
644
|
+
if (options == null ? void 0 : options.framerate) {
|
|
645
|
+
command = command.fps(options.framerate);
|
|
646
|
+
}
|
|
647
|
+
if (options == null ? void 0 : options.videoCodec) {
|
|
648
|
+
command = command.videoCodec(options.videoCodec);
|
|
649
|
+
}
|
|
650
|
+
if (options == null ? void 0 : options.audioCodec) {
|
|
651
|
+
command = command.audioCodec(options.audioCodec);
|
|
652
|
+
}
|
|
653
|
+
command.on("end", () => {
|
|
654
|
+
elizaLogger2.log("Video conversion complete");
|
|
655
|
+
resolve(outputPath);
|
|
656
|
+
}).on("error", (err) => {
|
|
657
|
+
elizaLogger2.log("Error converting video:", err);
|
|
658
|
+
reject(err);
|
|
659
|
+
}).run();
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
async getAvailableFormats(url) {
|
|
663
|
+
var _a;
|
|
664
|
+
try {
|
|
665
|
+
const result = await this.binaries.runYtDlp(url, {
|
|
666
|
+
dumpJson: true,
|
|
667
|
+
verbose: true,
|
|
668
|
+
callHome: false,
|
|
669
|
+
noCheckCertificates: true,
|
|
670
|
+
preferFreeFormats: true,
|
|
671
|
+
youtubeSkipDashManifest: true,
|
|
672
|
+
skipDownload: true
|
|
673
|
+
});
|
|
674
|
+
if (typeof result === "object" && result !== null && "formats" in result) {
|
|
675
|
+
const parsed = result;
|
|
676
|
+
if ((_a = parsed.formats) == null ? void 0 : _a.length) {
|
|
677
|
+
return parsed.formats.map((format) => ({
|
|
678
|
+
formatId: format.format_id ?? "",
|
|
679
|
+
url: format.url,
|
|
680
|
+
extension: format.ext,
|
|
681
|
+
quality: format.quality !== void 0 && format.quality !== "" ? String(format.quality) : "unknown",
|
|
682
|
+
fileSize: format.filesize,
|
|
683
|
+
videoCodec: format.vcodec,
|
|
684
|
+
audioCodec: format.acodec,
|
|
685
|
+
resolution: format.resolution,
|
|
686
|
+
fps: format.fps,
|
|
687
|
+
bitrate: format.tbr
|
|
688
|
+
}));
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return [];
|
|
692
|
+
} catch (error) {
|
|
693
|
+
elizaLogger2.log("Error getting available formats:", error);
|
|
694
|
+
throw new Error("Failed to get available formats");
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
ensureDataDirectoryExists() {
|
|
698
|
+
if (!fs.existsSync(this.dataDir)) {
|
|
699
|
+
fs.mkdirSync(this.dataDir);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
isVideoUrl(url) {
|
|
703
|
+
try {
|
|
704
|
+
const { hostname } = new URL(url);
|
|
705
|
+
return hostname === "youtube.com" || hostname.endsWith(".youtube.com") || hostname === "youtu.be" || hostname === "vimeo.com" || hostname.endsWith(".vimeo.com");
|
|
706
|
+
} catch {
|
|
707
|
+
return false;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
async downloadMedia(url) {
|
|
711
|
+
const videoId = this.getVideoId(url);
|
|
712
|
+
const outputFile = path2.join(this.dataDir, `${videoId}.mp4`);
|
|
713
|
+
if (fs.existsSync(outputFile)) {
|
|
714
|
+
return outputFile;
|
|
715
|
+
}
|
|
716
|
+
try {
|
|
717
|
+
await this.binaries.runYtDlp(url, {
|
|
718
|
+
verbose: true,
|
|
719
|
+
output: outputFile,
|
|
720
|
+
format: "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best",
|
|
721
|
+
writeInfoJson: true
|
|
722
|
+
});
|
|
723
|
+
return outputFile;
|
|
724
|
+
} catch (error) {
|
|
725
|
+
elizaLogger2.log("Error downloading media:", error);
|
|
726
|
+
throw new Error("Failed to download media");
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
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
|
+
);
|
|
737
|
+
return run;
|
|
738
|
+
}
|
|
739
|
+
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);
|
|
746
|
+
const cacheKey = `${this.cacheKey}/${videoUuid}`;
|
|
747
|
+
const cached = await runtime.getCache(cacheKey);
|
|
748
|
+
if (cached) {
|
|
749
|
+
elizaLogger2.log("Returning cached video file");
|
|
750
|
+
return cached;
|
|
751
|
+
}
|
|
752
|
+
elizaLogger2.log("Cache miss, processing video");
|
|
753
|
+
elizaLogger2.log("Fetching video info");
|
|
754
|
+
const videoInfo = await this.fetchVideoInfo(url);
|
|
755
|
+
elizaLogger2.log("Getting transcript");
|
|
756
|
+
const transcript = await this.getTranscript(url, videoInfo, runtime);
|
|
757
|
+
const result = {
|
|
758
|
+
id: videoUuid,
|
|
759
|
+
url,
|
|
760
|
+
title: videoInfo.title,
|
|
761
|
+
source: videoInfo.channel,
|
|
762
|
+
description: videoInfo.description,
|
|
763
|
+
text: transcript
|
|
764
|
+
};
|
|
765
|
+
await runtime.setCache(cacheKey, result);
|
|
766
|
+
return result;
|
|
767
|
+
}
|
|
768
|
+
getVideoId(url) {
|
|
769
|
+
return stringToUuid(url);
|
|
770
|
+
}
|
|
771
|
+
async fetchVideoInfo(url) {
|
|
772
|
+
if (url.endsWith(".mp4") || url.includes(".mp4?")) {
|
|
773
|
+
try {
|
|
774
|
+
const response = await fetch(url);
|
|
775
|
+
if (response.ok) {
|
|
776
|
+
return {
|
|
777
|
+
title: path2.basename(url),
|
|
778
|
+
description: "",
|
|
779
|
+
channel: ""
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
} catch (error) {
|
|
783
|
+
elizaLogger2.log("Error downloading MP4 file:", error);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
try {
|
|
787
|
+
const result = await this.binaries.runYtDlp(url, {
|
|
788
|
+
dumpJson: true,
|
|
789
|
+
verbose: true,
|
|
790
|
+
callHome: false,
|
|
791
|
+
noCheckCertificates: true,
|
|
792
|
+
preferFreeFormats: true,
|
|
793
|
+
youtubeSkipDashManifest: true,
|
|
794
|
+
writeSub: true,
|
|
795
|
+
writeAutoSub: true,
|
|
796
|
+
subLang: "en",
|
|
797
|
+
skipDownload: true
|
|
798
|
+
});
|
|
799
|
+
return result;
|
|
800
|
+
} catch (error) {
|
|
801
|
+
elizaLogger2.log("Error fetching video info:", error);
|
|
802
|
+
throw new Error("Failed to fetch video information");
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
async getTranscript(url, videoInfo, runtime) {
|
|
806
|
+
elizaLogger2.log("Getting transcript");
|
|
807
|
+
try {
|
|
808
|
+
if (videoInfo.subtitles && videoInfo.subtitles.en) {
|
|
809
|
+
elizaLogger2.log("Manual subtitles found");
|
|
810
|
+
const srtContent = await this.downloadSRT(
|
|
811
|
+
videoInfo.subtitles.en[0].url
|
|
812
|
+
);
|
|
813
|
+
return this.parseSRT(srtContent);
|
|
814
|
+
}
|
|
815
|
+
if (videoInfo.automatic_captions && videoInfo.automatic_captions.en) {
|
|
816
|
+
elizaLogger2.log("Automatic captions found");
|
|
817
|
+
const captionUrl = videoInfo.automatic_captions.en[0].url;
|
|
818
|
+
const captionContent = await this.downloadCaption(captionUrl);
|
|
819
|
+
return this.parseCaption(captionContent);
|
|
820
|
+
}
|
|
821
|
+
if (videoInfo.categories && videoInfo.categories.includes("Music")) {
|
|
822
|
+
elizaLogger2.log("Music video detected, no lyrics available");
|
|
823
|
+
return "No lyrics available.";
|
|
824
|
+
}
|
|
825
|
+
elizaLogger2.log(
|
|
826
|
+
"No subtitles or captions found, falling back to audio transcription"
|
|
827
|
+
);
|
|
828
|
+
return this.transcribeAudio(url, runtime);
|
|
829
|
+
} catch (error) {
|
|
830
|
+
elizaLogger2.log("Error in getTranscript:", error);
|
|
831
|
+
throw error;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
async downloadCaption(url) {
|
|
835
|
+
elizaLogger2.log("Downloading caption from:", url);
|
|
836
|
+
const response = await fetch(url);
|
|
837
|
+
if (!response.ok) {
|
|
838
|
+
throw new Error(`Failed to download caption: ${response.statusText}`);
|
|
839
|
+
}
|
|
840
|
+
return await response.text();
|
|
841
|
+
}
|
|
842
|
+
parseCaption(captionContent) {
|
|
843
|
+
elizaLogger2.log("Parsing caption");
|
|
844
|
+
try {
|
|
845
|
+
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", " ");
|
|
848
|
+
} else {
|
|
849
|
+
elizaLogger2.log("Unexpected caption format:", jsonContent);
|
|
850
|
+
return "Error: Unable to parse captions";
|
|
851
|
+
}
|
|
852
|
+
} catch (error) {
|
|
853
|
+
elizaLogger2.log("Error parsing caption:", error);
|
|
854
|
+
return "Error: Unable to parse captions";
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
parseSRT(srtContent) {
|
|
858
|
+
return srtContent.split("\n\n").map((block) => block.split("\n").slice(2).join(" ")).join(" ");
|
|
859
|
+
}
|
|
860
|
+
async downloadSRT(url) {
|
|
861
|
+
elizaLogger2.log("downloadSRT");
|
|
862
|
+
const response = await fetch(url);
|
|
863
|
+
return await response.text();
|
|
864
|
+
}
|
|
865
|
+
async transcribeAudio(url, runtime) {
|
|
866
|
+
elizaLogger2.log("Preparing audio for transcription...");
|
|
867
|
+
const mp4FilePath = path2.join(this.dataDir, `${this.getVideoId(url)}.mp4`);
|
|
868
|
+
const mp3FilePath = path2.join(this.dataDir, `${this.getVideoId(url)}.mp3`);
|
|
869
|
+
if (!fs.existsSync(mp3FilePath)) {
|
|
870
|
+
if (fs.existsSync(mp4FilePath)) {
|
|
871
|
+
elizaLogger2.log("MP4 file found. Converting to MP3...");
|
|
872
|
+
await this.convertMp4ToMp3(mp4FilePath, mp3FilePath);
|
|
873
|
+
} else {
|
|
874
|
+
elizaLogger2.log("Downloading audio...");
|
|
875
|
+
await this.downloadAudio(url, mp3FilePath);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
elizaLogger2.log(`Audio prepared at ${mp3FilePath}`);
|
|
879
|
+
const audioBuffer = fs.readFileSync(mp3FilePath);
|
|
880
|
+
elizaLogger2.log(`Audio file size: ${audioBuffer.length} bytes`);
|
|
881
|
+
elizaLogger2.log("Starting transcription...");
|
|
882
|
+
const startTime = Date.now();
|
|
883
|
+
const transcriptionService = runtime.getService(
|
|
884
|
+
ServiceType.TRANSCRIPTION
|
|
885
|
+
);
|
|
886
|
+
if (!transcriptionService) {
|
|
887
|
+
throw new Error("Transcription service not found");
|
|
888
|
+
}
|
|
889
|
+
const result = await transcriptionService.transcribeAudio(audioBuffer);
|
|
890
|
+
const transcript = result.text;
|
|
891
|
+
const endTime = Date.now();
|
|
892
|
+
elizaLogger2.log(
|
|
893
|
+
`Transcription completed in ${(endTime - startTime) / 1e3} seconds`
|
|
894
|
+
);
|
|
895
|
+
return transcript || "Transcription failed";
|
|
896
|
+
}
|
|
897
|
+
async convertMp4ToMp3(inputPath, outputPath) {
|
|
898
|
+
await this.configureFfmpeg();
|
|
899
|
+
return new Promise((resolve, reject) => {
|
|
900
|
+
ffmpeg(inputPath).output(outputPath).noVideo().audioCodec("libmp3lame").on("end", () => {
|
|
901
|
+
elizaLogger2.log("Conversion to MP3 complete");
|
|
902
|
+
resolve();
|
|
903
|
+
}).on("error", (err) => {
|
|
904
|
+
elizaLogger2.log("Error converting to MP3:", err);
|
|
905
|
+
reject(err);
|
|
906
|
+
}).run();
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
async downloadAudio(url, outputFile) {
|
|
910
|
+
elizaLogger2.log("Downloading audio");
|
|
911
|
+
outputFile = outputFile ?? path2.join(this.dataDir, `${this.getVideoId(url)}.mp3`);
|
|
912
|
+
try {
|
|
913
|
+
if (url.endsWith(".mp4") || url.includes(".mp4?")) {
|
|
914
|
+
elizaLogger2.log(
|
|
915
|
+
"Direct MP4 file detected, downloading and converting to MP3"
|
|
916
|
+
);
|
|
917
|
+
const tempMp4File = path2.join(tmpdir(), `${this.getVideoId(url)}.mp4`);
|
|
918
|
+
const response = await fetch(url);
|
|
919
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
920
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
921
|
+
fs.writeFileSync(tempMp4File, buffer);
|
|
922
|
+
await this.configureFfmpeg();
|
|
923
|
+
await new Promise((resolve, reject) => {
|
|
924
|
+
ffmpeg(tempMp4File).output(outputFile).noVideo().audioCodec("libmp3lame").on("end", () => {
|
|
925
|
+
fs.unlinkSync(tempMp4File);
|
|
926
|
+
resolve();
|
|
927
|
+
}).on("error", (err) => {
|
|
928
|
+
reject(err);
|
|
929
|
+
}).run();
|
|
930
|
+
});
|
|
931
|
+
} else {
|
|
932
|
+
elizaLogger2.log(
|
|
933
|
+
"YouTube video detected, downloading audio with youtube-dl"
|
|
934
|
+
);
|
|
935
|
+
await this.binaries.runYtDlp(url, {
|
|
936
|
+
verbose: true,
|
|
937
|
+
extractAudio: true,
|
|
938
|
+
audioFormat: "mp3",
|
|
939
|
+
output: outputFile,
|
|
940
|
+
writeInfoJson: true
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
return outputFile;
|
|
944
|
+
} catch (error) {
|
|
945
|
+
elizaLogger2.log("Error downloading audio:", error);
|
|
946
|
+
throw new Error("Failed to download audio");
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
|
|
951
|
+
// src/index.ts
|
|
952
|
+
var videoPlugin = {
|
|
953
|
+
name: "video",
|
|
954
|
+
description: "Video processing and transcription capabilities",
|
|
955
|
+
services: [VideoService],
|
|
956
|
+
actions: [],
|
|
957
|
+
providers: [],
|
|
958
|
+
routes: []
|
|
959
|
+
};
|
|
960
|
+
var index_default = videoPlugin;
|
|
961
|
+
export {
|
|
962
|
+
index_default as default
|
|
963
|
+
};
|
|
964
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/services/video.ts","../src/services/binaries.ts","../src/index.ts"],"sourcesContent":["import {\n type IAgentRuntime,\n ITranscriptionService,\n IVideoService,\n type Media,\n type Service,\n ServiceType,\n stringToUuid,\n elizaLogger,\n type VideoDownloadOptions,\n type VideoFormat,\n type VideoInfo,\n type VideoProcessingOptions,\n} from \"@elizaos/core\";\nimport ffmpeg from \"fluent-ffmpeg\";\nimport fs from \"fs\";\nimport { tmpdir } from \"os\";\nimport path from \"path\";\nimport { BinaryResolver } from \"./binaries\";\n\n/** Minimal yt-dlp JSON shape used by this service (fields vary by extractor). */\ninterface YtDlpSubtitleTrack {\n url: string;\n}\n\ninterface YtDlpJson {\n title?: string;\n description?: string;\n channel?: string;\n duration?: number;\n thumbnail?: string;\n view_count?: number;\n upload_date?: string;\n formats?: YtDlpFormatRow[];\n categories?: string[];\n subtitles?: Record<string, YtDlpSubtitleTrack[]>;\n automatic_captions?: Record<string, YtDlpSubtitleTrack[]>;\n}\n\ninterface YtDlpFormatRow {\n format_id?: string;\n url?: string;\n ext?: string;\n quality?: string | number;\n filesize?: number;\n vcodec?: string;\n acodec?: string;\n resolution?: string;\n fps?: number;\n tbr?: number;\n}\n\nexport class VideoService extends IVideoService {\n public readonly capabilityDescription =\n \"Video download, processing, and conversion capabilities\";\n static override readonly serviceType = ServiceType.VIDEO;\n private cacheKey = \"content/video\";\n private dataDir = \"./content_cache\";\n private readonly binaries: BinaryResolver;\n private ffmpegPathConfigured = false;\n\n /** Serialize downloads/processing so cache keys and temp files do not race. */\n private processingChain: Promise<void> = Promise.resolve();\n\n constructor(runtime?: IAgentRuntime, binaries?: BinaryResolver) {\n super(runtime);\n this.binaries = binaries ?? BinaryResolver.instance();\n this.ensureDataDirectoryExists();\n }\n\n static async start(runtime: IAgentRuntime): Promise<Service> {\n const service = new VideoService(runtime);\n await service.initialize(runtime);\n return service;\n }\n\n async initialize(_runtime: IAgentRuntime): Promise<void> {\n await this.configureFfmpeg();\n }\n\n private async configureFfmpeg(): Promise<void> {\n if (this.ffmpegPathConfigured) return;\n const ffmpegPath = await this.binaries.getFfmpegPath();\n if (ffmpegPath) {\n ffmpeg.setFfmpegPath(ffmpegPath);\n elizaLogger.log(`[plugin-video] ffmpeg path: ${ffmpegPath}`);\n } else {\n elizaLogger.warn(\n \"[plugin-video] No ffmpeg binary located via env, PATH, or ffmpeg-static; fluent-ffmpeg will fail at first invocation.\",\n );\n }\n this.ffmpegPathConfigured = true;\n }\n\n async stop(): Promise<void> {\n this.processingChain = Promise.resolve();\n }\n\n // Required abstract methods from IVideoService\n async getVideoInfo(url: string): Promise<VideoInfo> {\n const videoInfo = await this.fetchVideoInfo(url);\n const formats: VideoFormat[] = (videoInfo.formats ?? []).map(\n (f: YtDlpFormatRow) => ({\n formatId: f.format_id ?? \"\",\n url: f.url ?? \"\",\n extension: f.ext ?? \"\",\n quality:\n f.quality !== undefined && f.quality !== \"\"\n ? String(f.quality)\n : \"unknown\",\n fileSize: f.filesize,\n videoCodec: f.vcodec,\n audioCodec: f.acodec,\n resolution: f.resolution,\n fps: f.fps,\n bitrate: f.tbr,\n }),\n );\n return {\n title: videoInfo.title,\n duration: videoInfo.duration,\n url: url,\n thumbnail: videoInfo.thumbnail,\n description: videoInfo.description,\n uploader: videoInfo.channel,\n viewCount: videoInfo.view_count,\n uploadDate: videoInfo.upload_date\n ? new Date(videoInfo.upload_date)\n : undefined,\n formats,\n };\n }\n\n async downloadVideo(\n url: string,\n options?: VideoDownloadOptions,\n ): Promise<string> {\n const videoId = this.getVideoId(url);\n const outputFile =\n options?.outputPath || path.join(this.dataDir, `${videoId}.mp4`);\n\n // if it already exists, return it\n if (fs.existsSync(outputFile)) {\n return outputFile;\n }\n\n try {\n const downloadOptions: Record<string, string | boolean> = {\n verbose: true,\n output: outputFile,\n writeInfoJson: true,\n };\n\n if (options?.format) {\n downloadOptions.format = options.format;\n }\n if (options?.quality) {\n downloadOptions.format = options.quality;\n }\n if (options?.audioOnly) {\n downloadOptions.extractAudio = true;\n downloadOptions.audioFormat = \"mp3\";\n }\n if (options?.videoOnly) {\n downloadOptions.format = \"bestvideo[ext=mp4]/best[ext=mp4]/best\";\n }\n\n await this.binaries.runYtDlp(url, downloadOptions);\n return outputFile;\n } catch (error) {\n elizaLogger.log(\"Error downloading video:\", error);\n throw new Error(\"Failed to download video\");\n }\n }\n\n async extractAudio(videoPath: string, outputPath?: string): Promise<string> {\n const videoId = this.getVideoId(videoPath);\n const audioFile = outputPath || path.join(this.dataDir, `${videoId}.mp3`);\n\n if (fs.existsSync(audioFile)) {\n return audioFile;\n }\n\n await this.configureFfmpeg();\n return new Promise((resolve, reject) => {\n ffmpeg(videoPath)\n .output(audioFile)\n .noVideo()\n .audioCodec(\"libmp3lame\")\n .on(\"end\", () => {\n elizaLogger.log(\"Audio extraction complete\");\n resolve(audioFile);\n })\n .on(\"error\", (err) => {\n elizaLogger.log(\"Error extracting audio:\", err);\n reject(err);\n })\n .run();\n });\n }\n\n async getThumbnail(\n videoPath: string,\n timestamp: number = 1,\n ): Promise<string> {\n const videoId = this.getVideoId(videoPath);\n const thumbnailFile = path.join(this.dataDir, `${videoId}_thumb.jpg`);\n\n if (fs.existsSync(thumbnailFile)) {\n return thumbnailFile;\n }\n\n await this.configureFfmpeg();\n return new Promise((resolve, reject) => {\n ffmpeg(videoPath)\n .screenshots({\n timestamps: [timestamp],\n filename: `${videoId}_thumb.jpg`,\n folder: this.dataDir,\n size: \"320x240\",\n })\n .on(\"end\", () => {\n elizaLogger.log(\"Thumbnail generation complete\");\n resolve(thumbnailFile);\n })\n .on(\"error\", (err) => {\n elizaLogger.log(\"Error generating thumbnail:\", err);\n reject(err);\n });\n });\n }\n\n async convertVideo(\n videoPath: string,\n outputPath: string,\n options?: VideoProcessingOptions,\n ): Promise<string> {\n await this.configureFfmpeg();\n return new Promise((resolve, reject) => {\n let command = ffmpeg(videoPath);\n\n if (options?.startTime) {\n command = command.seekInput(options.startTime);\n }\n\n command = command.output(outputPath);\n\n if (options?.endTime) {\n command = command.duration(options.endTime - (options.startTime || 0));\n }\n\n if (options?.outputFormat) {\n command = command.format(options.outputFormat);\n }\n\n if (options?.resolution) {\n command = command.size(options.resolution);\n }\n\n if (options?.bitrate) {\n command = command.videoBitrate(options.bitrate);\n }\n\n if (options?.framerate) {\n command = command.fps(options.framerate);\n }\n\n if (options?.videoCodec) {\n command = command.videoCodec(options.videoCodec);\n }\n\n if (options?.audioCodec) {\n command = command.audioCodec(options.audioCodec);\n }\n\n command\n .on(\"end\", () => {\n elizaLogger.log(\"Video conversion complete\");\n resolve(outputPath);\n })\n .on(\"error\", (err) => {\n elizaLogger.log(\"Error converting video:\", err);\n reject(err);\n })\n .run();\n });\n }\n\n async getAvailableFormats(url: string): Promise<VideoFormat[]> {\n try {\n const result = await this.binaries.runYtDlp(url, {\n dumpJson: true,\n verbose: true,\n callHome: false,\n noCheckCertificates: true,\n preferFreeFormats: true,\n youtubeSkipDashManifest: true,\n skipDownload: true,\n });\n\n if (\n typeof result === \"object\" &&\n result !== null &&\n \"formats\" in result\n ) {\n const parsed = result as YtDlpJson;\n if (parsed.formats?.length) {\n return parsed.formats.map((format: YtDlpFormatRow) => ({\n formatId: format.format_id ?? \"\",\n url: format.url,\n extension: format.ext,\n quality:\n format.quality !== undefined && format.quality !== \"\"\n ? String(format.quality)\n : \"unknown\",\n fileSize: format.filesize,\n videoCodec: format.vcodec,\n audioCodec: format.acodec,\n resolution: format.resolution,\n fps: format.fps,\n bitrate: format.tbr,\n }));\n }\n }\n\n return [];\n } catch (error) {\n elizaLogger.log(\"Error getting available formats:\", error);\n throw new Error(\"Failed to get available formats\");\n }\n }\n\n private ensureDataDirectoryExists() {\n if (!fs.existsSync(this.dataDir)) {\n fs.mkdirSync(this.dataDir);\n }\n }\n\n public isVideoUrl(url: string): boolean {\n try {\n const { hostname } = new URL(url);\n return (\n hostname === \"youtube.com\" ||\n hostname.endsWith(\".youtube.com\") ||\n hostname === \"youtu.be\" ||\n hostname === \"vimeo.com\" ||\n hostname.endsWith(\".vimeo.com\")\n );\n } catch {\n return false;\n }\n }\n\n public async downloadMedia(url: string): Promise<string> {\n const videoId = this.getVideoId(url);\n const outputFile = path.join(this.dataDir, `${videoId}.mp4`);\n\n // if it already exists, return it\n if (fs.existsSync(outputFile)) {\n return outputFile;\n }\n\n try {\n await this.binaries.runYtDlp(url, {\n verbose: true,\n output: outputFile,\n format: \"bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best\",\n writeInfoJson: true,\n });\n return outputFile;\n } catch (error) {\n elizaLogger.log(\"Error downloading media:\", error);\n throw new Error(\"Failed to download media\");\n }\n }\n\n public async processVideo(\n url: string,\n runtime: IAgentRuntime,\n ): Promise<Media> {\n const run = this.processingChain.then(() =>\n this.processVideoFromUrl(url, runtime),\n );\n this.processingChain = run.then(\n () => undefined,\n () => undefined,\n );\n return run;\n }\n\n private async processVideoFromUrl(\n url: string,\n runtime: IAgentRuntime,\n ): Promise<Media> {\n const videoId =\n url.match(\n /(?:youtu\\.be\\/|youtube\\.com(?:\\/embed\\/|\\/v\\/|\\/watch\\?v=|\\/watch\\?.+&v=))([^\\/&?]+)/, // eslint-disable-line\n )?.[1] || \"\";\n const videoUuid = this.getVideoId(videoId);\n const cacheKey = `${this.cacheKey}/${videoUuid}`;\n\n const cached = await runtime.getCache<Media>(cacheKey);\n\n if (cached) {\n elizaLogger.log(\"Returning cached video file\");\n return cached;\n }\n\n elizaLogger.log(\"Cache miss, processing video\");\n elizaLogger.log(\"Fetching video info\");\n const videoInfo = await this.fetchVideoInfo(url);\n elizaLogger.log(\"Getting transcript\");\n const transcript = await this.getTranscript(url, videoInfo, runtime);\n\n const result: Media = {\n id: videoUuid,\n url: url,\n title: videoInfo.title,\n source: videoInfo.channel,\n description: videoInfo.description,\n text: transcript,\n };\n\n await runtime.setCache(cacheKey, result);\n\n return result;\n }\n\n private getVideoId(url: string): string {\n return stringToUuid(url);\n }\n\n async fetchVideoInfo(url: string): Promise<YtDlpJson> {\n if (url.endsWith(\".mp4\") || url.includes(\".mp4?\")) {\n try {\n const response = await fetch(url);\n if (response.ok) {\n // If the URL is a direct link to an MP4 file, return a simplified video info object\n return {\n title: path.basename(url),\n description: \"\",\n channel: \"\",\n };\n }\n } catch (error) {\n elizaLogger.log(\"Error downloading MP4 file:\", error);\n // Fall back to using youtube-dl if direct download fails\n }\n }\n\n try {\n const result = await this.binaries.runYtDlp(url, {\n dumpJson: true,\n verbose: true,\n callHome: false,\n noCheckCertificates: true,\n preferFreeFormats: true,\n youtubeSkipDashManifest: true,\n writeSub: true,\n writeAutoSub: true,\n subLang: \"en\",\n skipDownload: true,\n });\n return result as YtDlpJson;\n } catch (error) {\n elizaLogger.log(\"Error fetching video info:\", error);\n throw new Error(\"Failed to fetch video information\");\n }\n }\n\n private async getTranscript(\n url: string,\n videoInfo: YtDlpJson,\n runtime: IAgentRuntime,\n ): Promise<string> {\n elizaLogger.log(\"Getting transcript\");\n try {\n // Check for manual subtitles\n if (videoInfo.subtitles && videoInfo.subtitles.en) {\n elizaLogger.log(\"Manual subtitles found\");\n const srtContent = await this.downloadSRT(\n videoInfo.subtitles.en[0].url,\n );\n return this.parseSRT(srtContent);\n }\n\n // Check for automatic captions\n if (videoInfo.automatic_captions && videoInfo.automatic_captions.en) {\n elizaLogger.log(\"Automatic captions found\");\n const captionUrl = videoInfo.automatic_captions.en[0].url;\n const captionContent = await this.downloadCaption(captionUrl);\n return this.parseCaption(captionContent);\n }\n\n // Check if it's a music video\n if (videoInfo.categories && videoInfo.categories.includes(\"Music\")) {\n elizaLogger.log(\"Music video detected, no lyrics available\");\n return \"No lyrics available.\";\n }\n\n // Fall back to audio transcription\n elizaLogger.log(\n \"No subtitles or captions found, falling back to audio transcription\",\n );\n return this.transcribeAudio(url, runtime);\n } catch (error) {\n elizaLogger.log(\"Error in getTranscript:\", error);\n throw error;\n }\n }\n\n private async downloadCaption(url: string): Promise<string> {\n elizaLogger.log(\"Downloading caption from:\", url);\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`Failed to download caption: ${response.statusText}`);\n }\n return await response.text();\n }\n\n private parseCaption(captionContent: string): string {\n elizaLogger.log(\"Parsing caption\");\n try {\n const jsonContent = JSON.parse(captionContent);\n if (jsonContent.events) {\n return jsonContent.events\n .filter((event) => event.segs)\n .map((event) => event.segs.map((seg) => seg.utf8).join(\"\"))\n .join(\"\")\n .replace(\"\\n\", \" \");\n } else {\n elizaLogger.log(\"Unexpected caption format:\", jsonContent);\n return \"Error: Unable to parse captions\";\n }\n } catch (error) {\n elizaLogger.log(\"Error parsing caption:\", error);\n return \"Error: Unable to parse captions\";\n }\n }\n\n private parseSRT(srtContent: string): string {\n // Simple SRT parser (replace with a more robust solution if needed)\n return srtContent\n .split(\"\\n\\n\")\n .map((block) => block.split(\"\\n\").slice(2).join(\" \"))\n .join(\" \");\n }\n\n private async downloadSRT(url: string): Promise<string> {\n elizaLogger.log(\"downloadSRT\");\n const response = await fetch(url);\n return await response.text();\n }\n\n async transcribeAudio(url: string, runtime: IAgentRuntime): Promise<string> {\n elizaLogger.log(\"Preparing audio for transcription...\");\n const mp4FilePath = path.join(this.dataDir, `${this.getVideoId(url)}.mp4`);\n\n const mp3FilePath = path.join(this.dataDir, `${this.getVideoId(url)}.mp3`);\n\n if (!fs.existsSync(mp3FilePath)) {\n if (fs.existsSync(mp4FilePath)) {\n elizaLogger.log(\"MP4 file found. Converting to MP3...\");\n await this.convertMp4ToMp3(mp4FilePath, mp3FilePath);\n } else {\n elizaLogger.log(\"Downloading audio...\");\n await this.downloadAudio(url, mp3FilePath);\n }\n }\n\n elizaLogger.log(`Audio prepared at ${mp3FilePath}`);\n\n const audioBuffer = fs.readFileSync(mp3FilePath);\n elizaLogger.log(`Audio file size: ${audioBuffer.length} bytes`);\n\n elizaLogger.log(\"Starting transcription...\");\n const startTime = Date.now();\n const transcriptionService = runtime.getService<ITranscriptionService>(\n ServiceType.TRANSCRIPTION,\n );\n\n if (!transcriptionService) {\n throw new Error(\"Transcription service not found\");\n }\n\n const result = await transcriptionService.transcribeAudio(audioBuffer);\n const transcript = result.text;\n\n const endTime = Date.now();\n elizaLogger.log(\n `Transcription completed in ${(endTime - startTime) / 1000} seconds`,\n );\n\n // Don't delete the MP3 file as it might be needed for future use\n return transcript || \"Transcription failed\";\n }\n\n private async convertMp4ToMp3(\n inputPath: string,\n outputPath: string,\n ): Promise<void> {\n await this.configureFfmpeg();\n return new Promise((resolve, reject) => {\n ffmpeg(inputPath)\n .output(outputPath)\n .noVideo()\n .audioCodec(\"libmp3lame\")\n .on(\"end\", () => {\n elizaLogger.log(\"Conversion to MP3 complete\");\n resolve();\n })\n .on(\"error\", (err) => {\n elizaLogger.log(\"Error converting to MP3:\", err);\n reject(err);\n })\n .run();\n });\n }\n\n private async downloadAudio(\n url: string,\n outputFile: string,\n ): Promise<string> {\n elizaLogger.log(\"Downloading audio\");\n outputFile =\n outputFile ?? path.join(this.dataDir, `${this.getVideoId(url)}.mp3`);\n\n try {\n if (url.endsWith(\".mp4\") || url.includes(\".mp4?\")) {\n elizaLogger.log(\n \"Direct MP4 file detected, downloading and converting to MP3\",\n );\n const tempMp4File = path.join(tmpdir(), `${this.getVideoId(url)}.mp4`);\n const response = await fetch(url);\n const arrayBuffer = await response.arrayBuffer();\n const buffer = Buffer.from(arrayBuffer);\n fs.writeFileSync(tempMp4File, buffer);\n\n await this.configureFfmpeg();\n await new Promise<void>((resolve, reject) => {\n ffmpeg(tempMp4File)\n .output(outputFile)\n .noVideo()\n .audioCodec(\"libmp3lame\")\n .on(\"end\", () => {\n fs.unlinkSync(tempMp4File);\n resolve();\n })\n .on(\"error\", (err) => {\n reject(err);\n })\n .run();\n });\n } else {\n elizaLogger.log(\n \"YouTube video detected, downloading audio with youtube-dl\",\n );\n await this.binaries.runYtDlp(url, {\n verbose: true,\n extractAudio: true,\n audioFormat: \"mp3\",\n output: outputFile,\n writeInfoJson: true,\n });\n }\n return outputFile;\n } catch (error) {\n elizaLogger.log(\"Error downloading audio:\", error);\n throw new Error(\"Failed to download audio\");\n }\n }\n}\n","import { createHash } from \"node:crypto\";\nimport {\n createWriteStream,\n constants as fsConstants,\n promises as fsp,\n} from \"node:fs\";\nimport path from \"node:path\";\nimport { Readable } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport { elizaLogger, resolveStateDir } from \"@elizaos/core\";\nimport youtubeDl from \"youtube-dl-exec\";\n\nexport type YtDlpRunner = (\n url: string,\n flags?: Record<string, string | boolean | number | undefined>,\n opts?: object,\n) => Promise<unknown>;\n\ninterface YtDlpFactory {\n create: (binaryPath: string) => YtDlpRunner;\n}\n\ntype YtDlpModule = YtDlpRunner & YtDlpFactory;\n\nfunction isYtDlpModule(value: unknown): value is YtDlpModule {\n return (\n typeof value === \"function\" &&\n typeof (value as { create?: unknown }).create === \"function\"\n );\n}\n\nif (!isYtDlpModule(youtubeDl)) {\n throw new TypeError(\"youtube-dl-exec did not expose the expected runner API\");\n}\n\nconst ytDlpModule = youtubeDl;\n\nconst YT_DLP_RELEASE_URL =\n \"https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest\";\nconst YT_DLP_UPDATE_THROTTLE_MS = 60 * 60 * 1000;\nconst YT_DLP_META_FILENAME = \"yt-dlp.meta.json\";\n\ninterface GitHubAsset {\n name: string;\n browser_download_url: string;\n size: number;\n}\n\ninterface GitHubRelease {\n tag_name: string;\n name?: string;\n assets: GitHubAsset[];\n}\n\ninterface YtDlpMeta {\n version: string;\n sha256: string;\n assetName: string;\n downloadedAt: number;\n lastUpdateAttemptedAt: number;\n}\n\ntype YtDlpSource = \"env\" | \"path\" | \"cache\" | \"bundled\";\n\nconst EXTRACTOR_BROKEN_PATTERNS: readonly RegExp[] = [\n /Unable to extract/i,\n /Sign in to confirm/i,\n /nsig extraction failed/i,\n /n_param signature/i,\n /Failed to parse JSON/i,\n /HTTP Error 403/i,\n /This video is unavailable/i,\n /Got error: HTTP Error 429/i,\n];\n\nexport interface BinaryResolverOptions {\n binariesDir?: string;\n releaseUrl?: string;\n fetchImpl?: typeof fetch;\n now?: () => number;\n disableAutoUpdate?: boolean;\n preferSystemPath?: boolean;\n updateThrottleMs?: number;\n envOverridePath?: string | null;\n}\n\nexport class BinaryResolver {\n private static _instance: BinaryResolver | null = null;\n\n static instance(): BinaryResolver {\n if (!BinaryResolver._instance)\n BinaryResolver._instance = new BinaryResolver();\n return BinaryResolver._instance;\n }\n\n /** Test hook: drop the singleton so the next instance() returns fresh state. */\n static resetForTests(): void {\n BinaryResolver._instance = null;\n }\n\n private readonly binariesDir: string;\n private readonly releaseUrl: string;\n private readonly fetchImpl: typeof fetch;\n private readonly now: () => number;\n private readonly disableAutoUpdate: boolean;\n private readonly preferSystemPath: boolean;\n private readonly updateThrottleMs: number;\n private readonly envOverridePath: string | null;\n\n private resolvedYtDlpPath: string | null = null;\n private resolvedYtDlpSource: YtDlpSource | null = null;\n private cachedRunner: YtDlpRunner | null = null;\n private resolvedFfmpegPath: string | null | undefined = undefined;\n private updateInFlight: Promise<void> | null = null;\n\n constructor(opts: BinaryResolverOptions = {}) {\n this.binariesDir = opts.binariesDir ?? defaultBinariesDir();\n this.releaseUrl = opts.releaseUrl ?? YT_DLP_RELEASE_URL;\n this.fetchImpl =\n opts.fetchImpl ??\n ((input: RequestInfo | URL, init?: RequestInit) =>\n globalThis.fetch(input, init));\n this.now = opts.now ?? Date.now;\n this.disableAutoUpdate =\n opts.disableAutoUpdate ?? envBool(\"ELIZA_DISABLE_YTDLP_AUTOUPDATE\");\n this.preferSystemPath =\n opts.preferSystemPath ?? envBool(\"ELIZA_YT_DLP_PREFER_PATH\");\n this.updateThrottleMs = opts.updateThrottleMs ?? YT_DLP_UPDATE_THROTTLE_MS;\n this.envOverridePath =\n opts.envOverridePath !== undefined\n ? opts.envOverridePath\n : (process.env.ELIZA_YT_DLP_PATH ?? null);\n }\n\n get cacheDir(): string {\n return this.binariesDir;\n }\n\n get cachedYtDlpPath(): string {\n return path.join(this.binariesDir, ytDlpFileName());\n }\n\n get metaPath(): string {\n return path.join(this.binariesDir, YT_DLP_META_FILENAME);\n }\n\n /**\n * Resolve the yt-dlp binary path. Order:\n * 1. ELIZA_YT_DLP_PATH env override.\n * 2. If ELIZA_YT_DLP_PREFER_PATH=1: system PATH, then cache.\n * 3. Otherwise: cache (download if missing) → PATH fallback.\n */\n async getYtDlpPath(): Promise<string> {\n if (this.resolvedYtDlpPath) return this.resolvedYtDlpPath;\n\n if (this.envOverridePath) {\n if (await isExecutable(this.envOverridePath)) {\n this.resolvedYtDlpPath = this.envOverridePath;\n this.resolvedYtDlpSource = \"env\";\n elizaLogger.log(\n `[plugin-video] Using yt-dlp from env override: ${this.envOverridePath}`,\n );\n return this.envOverridePath;\n }\n elizaLogger.warn(\n `[plugin-video] ELIZA_YT_DLP_PATH=${this.envOverridePath} is not executable; falling through.`,\n );\n }\n\n if (this.preferSystemPath) {\n const sys = await lookupOnPath(\"yt-dlp\");\n if (sys) {\n this.resolvedYtDlpPath = sys;\n this.resolvedYtDlpSource = \"path\";\n elizaLogger.log(`[plugin-video] Using yt-dlp from PATH: ${sys}`);\n return sys;\n }\n }\n\n const cachePath = this.cachedYtDlpPath;\n if (await isExecutable(cachePath)) {\n this.resolvedYtDlpPath = cachePath;\n this.resolvedYtDlpSource = \"cache\";\n elizaLogger.log(\n `[plugin-video] Using yt-dlp from managed cache: ${cachePath}`,\n );\n return cachePath;\n }\n\n const sys = await lookupOnPath(\"yt-dlp\");\n if (sys) {\n this.resolvedYtDlpPath = sys;\n this.resolvedYtDlpSource = \"path\";\n elizaLogger.log(\n `[plugin-video] Using yt-dlp from PATH (cache empty): ${sys}`,\n );\n return sys;\n }\n\n const bundled = await getBundledYtDlpPath();\n if (bundled) {\n this.resolvedYtDlpPath = bundled;\n this.resolvedYtDlpSource = \"bundled\";\n elizaLogger.log(\n `[plugin-video] Using yt-dlp from youtube-dl-exec bundle: ${bundled}`,\n );\n return bundled;\n }\n\n elizaLogger.log(\n \"[plugin-video] No yt-dlp binary found; downloading to managed cache.\",\n );\n await this.downloadYtDlp();\n this.resolvedYtDlpPath = cachePath;\n this.resolvedYtDlpSource = \"cache\";\n return cachePath;\n }\n\n /** Resolution order: ELIZA_FFMPEG_PATH env → system ffmpeg → ffmpeg-static. */\n async getFfmpegPath(): Promise<string | null> {\n if (this.resolvedFfmpegPath !== undefined) return this.resolvedFfmpegPath;\n\n const envPath = process.env.ELIZA_FFMPEG_PATH;\n if (envPath && (await isExecutable(envPath))) {\n this.resolvedFfmpegPath = envPath;\n return envPath;\n }\n\n const sys = await lookupOnPath(\"ffmpeg\");\n if (sys) {\n this.resolvedFfmpegPath = sys;\n return sys;\n }\n\n try {\n const mod: unknown = await import(\"ffmpeg-static\");\n const staticPath =\n typeof mod === \"string\"\n ? mod\n : mod && typeof mod === \"object\" && \"default\" in mod\n ? (mod.default as string | null | undefined)\n : null;\n if (\n typeof staticPath === \"string\" &&\n staticPath.length > 0 &&\n (await isExecutable(staticPath))\n ) {\n this.resolvedFfmpegPath = staticPath;\n return staticPath;\n }\n } catch (err) {\n elizaLogger.warn(\n `[plugin-video] ffmpeg-static not loadable: ${describeError(err)}`,\n );\n }\n\n this.resolvedFfmpegPath = null;\n return null;\n }\n\n /**\n * Build (or reuse) the yt-dlp runner bound to the resolved binary path.\n * The runner mirrors `youtube-dl-exec`'s default callable signature.\n */\n async getYtDlpRunner(): Promise<YtDlpRunner> {\n if (this.cachedRunner) return this.cachedRunner;\n const binPath = await this.getYtDlpPath();\n this.cachedRunner = ytDlpModule.create(binPath);\n return this.cachedRunner;\n }\n\n /**\n * Run yt-dlp with one auto-update + retry attempt on extractor-failure\n * patterns, when the active binary is the managed cache.\n */\n async runYtDlp(\n url: string,\n flags: Record<string, string | boolean | number | undefined>,\n ): Promise<unknown> {\n const runner = await this.getYtDlpRunner();\n try {\n return await runner(url, flags);\n } catch (err) {\n if (!this.shouldRetryWithUpdate(err)) {\n throw err;\n }\n const updated = await this.tryUpdate();\n if (!updated) {\n throw err;\n }\n const refreshed = await this.getYtDlpRunner();\n return await refreshed(url, flags);\n }\n }\n\n private shouldRetryWithUpdate(err: unknown): boolean {\n if (this.disableAutoUpdate) return false;\n // Auto-update fires only when we own the binary lifecycle (managed cache\n // or the bundled-with-youtube-dl-exec copy). User-managed binaries (env\n // override, system PATH) are out of scope — we don't touch homebrew.\n if (\n this.resolvedYtDlpSource !== \"cache\" &&\n this.resolvedYtDlpSource !== \"bundled\"\n ) {\n return false;\n }\n const msg = errorMessage(err);\n return EXTRACTOR_BROKEN_PATTERNS.some((p) => p.test(msg));\n }\n\n /**\n * Run a yt-dlp update attempt, throttled to once per `updateThrottleMs`.\n * Returns true iff a fresh binary was successfully installed.\n */\n private async tryUpdate(): Promise<boolean> {\n if (this.updateInFlight) {\n await this.updateInFlight;\n return true;\n }\n\n const meta = await this.readMeta();\n // No metadata yet means we have never attempted an update; first failure\n // should always be allowed to try.\n if (meta) {\n const sinceLast = this.now() - meta.lastUpdateAttemptedAt;\n if (sinceLast < this.updateThrottleMs) {\n elizaLogger.warn(\n `[plugin-video] yt-dlp update throttled (last attempt ${Math.floor(sinceLast / 1000)}s ago).`,\n );\n return false;\n }\n }\n\n const job = (async () => {\n await this.touchUpdateAttempt(meta);\n await this.downloadYtDlp();\n this.resetRunnerCache();\n })();\n this.updateInFlight = job;\n try {\n await job;\n return true;\n } catch (err) {\n elizaLogger.error(\n `[plugin-video] yt-dlp update failed: ${describeError(err)}`,\n );\n return false;\n } finally {\n this.updateInFlight = null;\n }\n }\n\n private resetRunnerCache(): void {\n this.cachedRunner = null;\n this.resolvedYtDlpPath = null;\n this.resolvedYtDlpSource = null;\n }\n\n /** Force a fresh yt-dlp download regardless of throttle. Test/admin hook. */\n async forceUpdateYtDlp(): Promise<{ version: string; path: string }> {\n const meta = await this.downloadYtDlp();\n this.resetRunnerCache();\n await this.getYtDlpPath();\n return { version: meta.version, path: this.cachedYtDlpPath };\n }\n\n private async readMeta(): Promise<YtDlpMeta | null> {\n try {\n const raw = await fsp.readFile(this.metaPath, \"utf8\");\n return JSON.parse(raw) as YtDlpMeta;\n } catch {\n return null;\n }\n }\n\n private async writeMeta(meta: YtDlpMeta): Promise<void> {\n await fsp.mkdir(this.binariesDir, { recursive: true });\n await fsp.writeFile(this.metaPath, JSON.stringify(meta, null, 2));\n }\n\n private async touchUpdateAttempt(prev: YtDlpMeta | null): Promise<void> {\n const next: YtDlpMeta = prev\n ? { ...prev, lastUpdateAttemptedAt: this.now() }\n : {\n version: \"\",\n sha256: \"\",\n assetName: \"\",\n downloadedAt: 0,\n lastUpdateAttemptedAt: this.now(),\n };\n await this.writeMeta(next);\n }\n\n /**\n * Download the latest yt-dlp release for this platform, verify the SHA256\n * against the published `SHA2-256SUMS`, and atomically replace the cached\n * binary. Returns the new metadata on success; throws on any failure.\n */\n async downloadYtDlp(): Promise<YtDlpMeta> {\n const release = await this.fetchRelease();\n const assetName = ytDlpAssetName();\n const asset = release.assets.find((a) => a.name === assetName);\n if (!asset) {\n throw new Error(\n `yt-dlp release ${release.tag_name} has no asset named '${assetName}'`,\n );\n }\n const sumsAsset = release.assets.find((a) => a.name === \"SHA2-256SUMS\");\n if (!sumsAsset) {\n throw new Error(\n `yt-dlp release ${release.tag_name} has no SHA2-256SUMS asset`,\n );\n }\n\n const expectedSha = await this.fetchExpectedSha(\n sumsAsset.browser_download_url,\n assetName,\n );\n\n await fsp.mkdir(this.binariesDir, { recursive: true });\n const tmpPath = `${this.cachedYtDlpPath}.tmp.${process.pid}.${Date.now()}`;\n await this.downloadToFile(asset.browser_download_url, tmpPath);\n\n const actualSha = await sha256OfFile(tmpPath);\n if (actualSha !== expectedSha) {\n await safeUnlink(tmpPath);\n throw new Error(\n `yt-dlp SHA256 mismatch: expected ${expectedSha}, got ${actualSha}`,\n );\n }\n\n await fsp.chmod(tmpPath, 0o755);\n await fsp.rename(tmpPath, this.cachedYtDlpPath);\n\n const meta: YtDlpMeta = {\n version: release.tag_name,\n sha256: expectedSha,\n assetName,\n downloadedAt: this.now(),\n lastUpdateAttemptedAt: this.now(),\n };\n await this.writeMeta(meta);\n elizaLogger.log(\n `[plugin-video] yt-dlp ${release.tag_name} installed at ${this.cachedYtDlpPath}`,\n );\n return meta;\n }\n\n private async fetchRelease(): Promise<GitHubRelease> {\n const res = await this.fetchImpl(this.releaseUrl, {\n headers: { Accept: \"application/vnd.github+json\" },\n });\n if (!res.ok) {\n throw new Error(\n `yt-dlp release fetch failed: ${res.status} ${res.statusText}`,\n );\n }\n return (await res.json()) as GitHubRelease;\n }\n\n private async fetchExpectedSha(\n sumsUrl: string,\n assetName: string,\n ): Promise<string> {\n const res = await this.fetchImpl(sumsUrl);\n if (!res.ok) {\n throw new Error(\n `SHA2-256SUMS fetch failed: ${res.status} ${res.statusText}`,\n );\n }\n const text = await res.text();\n for (const line of text.split(/\\r?\\n/)) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n const parts = trimmed.split(/\\s+/);\n if (parts.length >= 2 && parts[parts.length - 1] === assetName) {\n return parts[0].toLowerCase();\n }\n }\n throw new Error(`SHA2-256SUMS missing entry for ${assetName}`);\n }\n\n private async downloadToFile(url: string, dest: string): Promise<void> {\n const res = await this.fetchImpl(url);\n if (!res.ok || !res.body) {\n throw new Error(\n `yt-dlp binary fetch failed: ${res.status} ${res.statusText}`,\n );\n }\n const nodeStream = Readable.fromWeb(\n res.body as Parameters<typeof Readable.fromWeb>[0],\n );\n await pipeline(nodeStream, createWriteStream(dest));\n }\n}\n\nfunction defaultBinariesDir(): string {\n const explicit = process.env.ELIZA_BINARIES_DIR;\n if (explicit) return explicit;\n return path.join(resolveStateDir(), \"binaries\");\n}\n\nexport function ytDlpAssetName(): string {\n const platform = process.platform;\n const arch = process.arch;\n if (platform === \"darwin\") return \"yt-dlp_macos\";\n if (platform === \"win32\")\n return arch === \"ia32\" ? \"yt-dlp_x86.exe\" : \"yt-dlp.exe\";\n if (platform === \"linux\") {\n if (arch === \"arm64\") return \"yt-dlp_linux_aarch64\";\n if (arch === \"arm\") return \"yt-dlp_linux_armv7l\";\n return \"yt-dlp_linux\";\n }\n throw new Error(`Unsupported platform for yt-dlp: ${platform}/${arch}`);\n}\n\nexport function ytDlpFileName(): string {\n return process.platform === \"win32\" ? \"yt-dlp.exe\" : \"yt-dlp\";\n}\n\nasync function isExecutable(p: string): Promise<boolean> {\n try {\n await fsp.access(p, fsConstants.X_OK);\n const st = await fsp.stat(p);\n return st.isFile();\n } catch {\n return false;\n }\n}\n\n/**\n * Look for the yt-dlp binary that `youtube-dl-exec`'s postinstall ships into\n * its own `bin/` directory. Tries the package's internal constants first\n * (which the wrapper uses by default), then falls back to a hardcoded\n * relative path. Returns the path only if the binary is actually executable.\n */\nasync function getBundledYtDlpPath(): Promise<string | null> {\n try {\n const constantsModule = (await import(\n \"youtube-dl-exec/src/constants.js\" as string\n )) as { default?: { YOUTUBE_DL_PATH?: string } } & {\n YOUTUBE_DL_PATH?: string;\n };\n const fromConstants =\n constantsModule.default?.YOUTUBE_DL_PATH ??\n constantsModule.YOUTUBE_DL_PATH;\n if (\n typeof fromConstants === \"string\" &&\n fromConstants.length > 0 &&\n (await isExecutable(fromConstants))\n ) {\n return fromConstants;\n }\n } catch {\n /* package internals unavailable; fall through to relative resolve */\n }\n try {\n const { createRequire } = await import(\"node:module\");\n const req = createRequire(import.meta.url);\n const pkgPath = req.resolve(\"youtube-dl-exec/package.json\");\n const candidate = path.join(path.dirname(pkgPath), \"bin\", ytDlpFileName());\n if (await isExecutable(candidate)) return candidate;\n } catch {\n /* youtube-dl-exec not installed in this tree */\n }\n return null;\n}\n\nasync function lookupOnPath(name: string): Promise<string | null> {\n const pathEnv = process.env.PATH;\n if (!pathEnv) return null;\n const exts =\n process.platform === \"win32\"\n ? (process.env.PATHEXT ?? \".EXE;.CMD;.BAT\")\n .split(\";\")\n .map((e) => e.toLowerCase())\n : [\"\"];\n for (const dir of pathEnv.split(path.delimiter)) {\n if (!dir) continue;\n for (const ext of exts) {\n const candidate = path.join(dir, `${name}${ext}`);\n if (await isExecutable(candidate)) {\n return candidate;\n }\n }\n }\n return null;\n}\n\nasync function sha256OfFile(p: string): Promise<string> {\n const hash = createHash(\"sha256\");\n const fd = await fsp.open(p, \"r\");\n try {\n const stream = fd.createReadStream();\n for await (const chunk of stream) hash.update(chunk as Buffer);\n } finally {\n await fd.close();\n }\n return hash.digest(\"hex\");\n}\n\nasync function safeUnlink(p: string): Promise<void> {\n try {\n await fsp.unlink(p);\n } catch {\n /* swallow */\n }\n}\n\nfunction envBool(name: string): boolean {\n const v = process.env[name];\n if (!v) return false;\n return v === \"1\" || v.toLowerCase() === \"true\";\n}\n\nfunction errorMessage(err: unknown): string {\n if (!err) return \"\";\n if (typeof err === \"string\") return err;\n const anyErr = err as { stderr?: unknown; message?: unknown };\n if (typeof anyErr.stderr === \"string\" && anyErr.stderr.length > 0)\n return anyErr.stderr;\n if (typeof anyErr.message === \"string\") return anyErr.message;\n try {\n return JSON.stringify(err);\n } catch {\n return String(err);\n }\n}\n\nfunction describeError(err: unknown): string {\n return errorMessage(err) || \"(no message)\";\n}\n","import { Plugin } from \"@elizaos/core\";\nimport { VideoService } from \"./services/video\";\n\nconst videoPlugin: Plugin = {\n name: \"video\",\n description: \"Video processing and transcription capabilities\",\n services: [VideoService],\n actions: [],\n providers: [],\n routes: [],\n};\n\nexport default videoPlugin;\n"],"mappings":"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","names":["elizaLogger","path","sys","elizaLogger","path"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@elizaos/plugin-video",
|
|
3
|
+
"version": "2.0.0-beta.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"package.json",
|
|
19
|
+
"tsup.config.ts"
|
|
20
|
+
],
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@elizaos/core": "2.0.0-beta.1",
|
|
23
|
+
"ffmpeg-static": "^5.3.0",
|
|
24
|
+
"fluent-ffmpeg": "2.1.3",
|
|
25
|
+
"youtube-dl-exec": "^3.1.5"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "22.19.17",
|
|
29
|
+
"tsup": "^8.5.1",
|
|
30
|
+
"typescript": "^6.0.3",
|
|
31
|
+
"vitest": "^4.0.17"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsup --format esm --dts",
|
|
35
|
+
"dev": "tsup --format esm --dts --watch",
|
|
36
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
37
|
+
"test": "vitest run --config vitest.config.ts"
|
|
38
|
+
},
|
|
39
|
+
"agentConfig": {
|
|
40
|
+
"pluginType": "elizaos:plugin:1.0.0",
|
|
41
|
+
"pluginParameters": {}
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
}
|
|
46
|
+
}
|
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineConfig } from "tsup";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
entry: ["src/index.ts"],
|
|
5
|
+
outDir: "dist",
|
|
6
|
+
sourcemap: true,
|
|
7
|
+
clean: true,
|
|
8
|
+
format: ["esm"], // Ensure you're targeting CommonJS
|
|
9
|
+
external: [
|
|
10
|
+
"dotenv", // Externalize dotenv to prevent bundling
|
|
11
|
+
"fs", // Externalize fs to use Node.js built-in module
|
|
12
|
+
"path", // Externalize other built-ins if necessary
|
|
13
|
+
"@reflink/reflink",
|
|
14
|
+
"@node-llama-cpp",
|
|
15
|
+
"https",
|
|
16
|
+
"http",
|
|
17
|
+
"agentkeepalive",
|
|
18
|
+
"zod",
|
|
19
|
+
"@elizaos/core",
|
|
20
|
+
// Add other modules you want to externalize
|
|
21
|
+
],
|
|
22
|
+
});
|