@ziplayer/plugin 0.1.41 → 0.1.50

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.
@@ -0,0 +1,481 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.AttachmentsPlugin = void 0;
7
+ const ziplayer_1 = require("ziplayer");
8
+ const axios_1 = __importDefault(require("axios"));
9
+ const music_metadata_1 = require("music-metadata");
10
+ /**
11
+ * A plugin for handling Discord attachment URLs and local audio files.
12
+ *
13
+ * This plugin provides support for:
14
+ * - Discord attachment URLs (cdn.discordapp.com, media.discordapp.net)
15
+ * - Direct audio file URLs
16
+ * - Local file paths (if accessible)
17
+ * - Various audio formats (mp3, wav, ogg, m4a, flac, etc.)
18
+ * - File size validation
19
+ * - Audio metadata analysis (duration, title, artist, album, etc.)
20
+ * - Stream extraction from URLs
21
+ *
22
+ * @example
23
+ * const attachmentsPlugin = new AttachmentsPlugin({
24
+ * maxFileSize: 25 * 1024 * 1024, // 25MB
25
+ * allowedExtensions: ['mp3', 'wav', 'ogg', 'm4a', 'flac']
26
+ * });
27
+ *
28
+ * // Add to PlayerManager
29
+ * const manager = new PlayerManager({
30
+ * plugins: [attachmentsPlugin]
31
+ * });
32
+ *
33
+ * // Search for attachment content
34
+ * const result = await attachmentsPlugin.search(
35
+ * "https://cdn.discordapp.com/attachments/123/456/audio.mp3",
36
+ * "user123"
37
+ * );
38
+ * const stream = await attachmentsPlugin.getStream(result.tracks[0]);
39
+ *
40
+ * @since 1.0.0
41
+ */
42
+ class AttachmentsPlugin extends ziplayer_1.BasePlugin {
43
+ /**
44
+ * Creates a new AttachmentsPlugin instance.
45
+ *
46
+ * @param opts - Configuration options for the attachments plugin
47
+ * @param opts.maxFileSize - Maximum file size in bytes (default: 25MB)
48
+ * @param opts.allowedExtensions - Allowed audio file extensions (default: common audio formats)
49
+ * @param opts.debug - Whether to enable debug logging (default: false)
50
+ *
51
+ * @example
52
+ * // Basic attachments plugin
53
+ * const attachmentsPlugin = new AttachmentsPlugin();
54
+ *
55
+ * // Custom configuration
56
+ * const customPlugin = new AttachmentsPlugin({
57
+ * maxFileSize: 50 * 1024 * 1024, // 50MB
58
+ * allowedExtensions: ['mp3', 'wav', 'ogg'],
59
+ * debug: true
60
+ * });
61
+ */
62
+ constructor(opts) {
63
+ super();
64
+ this.name = "attachments";
65
+ this.version = "1.0.0";
66
+ this.defaultAllowedExtensions = ["mp3", "wav", "ogg", "m4a", "flac", "aac", "wma", "opus", "webm"];
67
+ this.opts = {
68
+ maxFileSize: opts?.maxFileSize || 25 * 1024 * 1024, // 25MB default
69
+ allowedExtensions: opts?.allowedExtensions || this.defaultAllowedExtensions,
70
+ debug: opts?.debug || false,
71
+ };
72
+ }
73
+ /**
74
+ * Determines if this plugin can handle the given query.
75
+ *
76
+ * @param query - The URL or file path to check
77
+ * @returns `true` if the query is a Discord attachment URL or audio file URL, `false` otherwise
78
+ *
79
+ * @example
80
+ * plugin.canHandle("https://cdn.discordapp.com/attachments/123/456/audio.mp3"); // true
81
+ * plugin.canHandle("https://example.com/song.wav"); // true
82
+ * plugin.canHandle("youtube.com/watch?v=123"); // false
83
+ */
84
+ canHandle(query) {
85
+ if (!query)
86
+ return false;
87
+ const q = query.trim();
88
+ // Check if it's a URL
89
+ if (q.startsWith("http://") || q.startsWith("https://")) {
90
+ try {
91
+ const url = new URL(q);
92
+ // Discord attachment URLs
93
+ if (url.hostname === "cdn.discordapp.com" || url.hostname === "media.discordapp.net") {
94
+ return this.isAudioFile(q);
95
+ }
96
+ // Any other URL - check if it's an audio file
97
+ return this.isAudioFile(q);
98
+ }
99
+ catch {
100
+ return false;
101
+ }
102
+ }
103
+ // Check if it's a local file path (basic check)
104
+ if (q.includes("/") || q.includes("\\")) {
105
+ return this.isAudioFile(q);
106
+ }
107
+ return false;
108
+ }
109
+ /**
110
+ * Validates if a URL is a valid Discord attachment URL or audio file URL.
111
+ *
112
+ * @param url - The URL to validate
113
+ * @returns `true` if the URL is valid and points to an audio file, `false` otherwise
114
+ *
115
+ * @example
116
+ * plugin.validate("https://cdn.discordapp.com/attachments/123/456/audio.mp3"); // true
117
+ * plugin.validate("https://example.com/song.wav"); // true
118
+ * plugin.validate("https://example.com/image.jpg"); // false
119
+ */
120
+ validate(url) {
121
+ return this.canHandle(url) && this.isAudioFile(url);
122
+ }
123
+ /**
124
+ * Creates a track from an attachment URL or file path.
125
+ *
126
+ * This method handles both Discord attachment URLs and direct audio file URLs.
127
+ * It extracts metadata from the URL and creates a track that can be played.
128
+ *
129
+ * @param query - The attachment URL or file path
130
+ * @param requestedBy - The user ID who requested the track
131
+ * @returns A SearchResult containing a single track
132
+ *
133
+ * @example
134
+ * // Discord attachment
135
+ * const result = await plugin.search(
136
+ * "https://cdn.discordapp.com/attachments/123/456/audio.mp3",
137
+ * "user123"
138
+ * );
139
+ *
140
+ * // Direct audio file URL
141
+ * const result2 = await plugin.search(
142
+ * "https://example.com/song.wav",
143
+ * "user123"
144
+ * );
145
+ */
146
+ async search(query, requestedBy) {
147
+ if (!this.canHandle(query)) {
148
+ return { tracks: [] };
149
+ }
150
+ try {
151
+ const filename = this.extractFilename(query);
152
+ const fileExtension = this.getFileExtension(filename);
153
+ let title = filename || `Audio File (${fileExtension})`;
154
+ // Get file size if it's a URL
155
+ let fileSize = 0;
156
+ let duration = 0;
157
+ let audioMetadata = {};
158
+ if (query.startsWith("http://") || query.startsWith("https://")) {
159
+ try {
160
+ const headResponse = await axios_1.default.head(query, { timeout: 5000 });
161
+ const contentLength = headResponse.headers["content-length"];
162
+ if (contentLength) {
163
+ fileSize = parseInt(contentLength, 10);
164
+ // Check file size limit
165
+ if (fileSize > this.opts.maxFileSize) {
166
+ throw new Error(`File too large: ${this.formatBytes(fileSize)} (max: ${this.formatBytes(this.opts.maxFileSize)})`);
167
+ }
168
+ }
169
+ }
170
+ catch (error) {
171
+ this.debug("Could not get file size:", error);
172
+ }
173
+ // Analyze audio metadata to get duration and other info
174
+ try {
175
+ const analysisResult = await this.analyzeAudioMetadata(query);
176
+ duration = analysisResult.duration;
177
+ audioMetadata = analysisResult.metadata || {};
178
+ // Use metadata title if available
179
+ if (audioMetadata.title && audioMetadata.title.trim()) {
180
+ const artist = audioMetadata.artist ? ` - ${audioMetadata.artist}` : "";
181
+ const album = audioMetadata.album ? ` (${audioMetadata.album})` : "";
182
+ const finalTitle = `${audioMetadata.title}${artist}${album}`;
183
+ if (finalTitle.trim()) {
184
+ title = finalTitle;
185
+ }
186
+ }
187
+ }
188
+ catch (error) {
189
+ this.debug("Could not analyze audio metadata:", error);
190
+ }
191
+ }
192
+ const track = {
193
+ id: this.generateTrackId(query),
194
+ title,
195
+ url: query,
196
+ duration,
197
+ requestedBy,
198
+ source: this.name,
199
+ metadata: {
200
+ filename,
201
+ extension: fileExtension,
202
+ fileSize,
203
+ isDiscordAttachment: this.isDiscordAttachment(query),
204
+ ...audioMetadata, // Include all audio metadata
205
+ },
206
+ };
207
+ return { tracks: [track] };
208
+ }
209
+ catch (error) {
210
+ this.debug("Error creating track:", error);
211
+ return { tracks: [] };
212
+ }
213
+ }
214
+ /**
215
+ * Retrieves the audio stream from an attachment URL or file path.
216
+ *
217
+ * This method downloads the audio file from the URL and returns it as a stream.
218
+ * It handles various audio formats and provides proper error handling.
219
+ *
220
+ * @param track - The Track object to get the stream for
221
+ * @returns A StreamInfo object containing the audio stream
222
+ * @throws {Error} If the URL is invalid, file is too large, or download fails
223
+ *
224
+ * @example
225
+ * const track = {
226
+ * id: "attachment-123",
227
+ * title: "audio.mp3",
228
+ * url: "https://cdn.discordapp.com/attachments/123/456/audio.mp3",
229
+ * ...
230
+ * };
231
+ * const streamInfo = await plugin.getStream(track);
232
+ * console.log(streamInfo.type); // "arbitrary"
233
+ * console.log(streamInfo.stream); // Readable stream
234
+ */
235
+ async getStream(track) {
236
+ if (track.source !== this.name) {
237
+ throw new Error("Track is not from AttachmentsPlugin");
238
+ }
239
+ const url = track.url;
240
+ if (!url) {
241
+ throw new Error("No URL provided for track");
242
+ }
243
+ try {
244
+ this.debug("Downloading audio from:", url);
245
+ // Download the file
246
+ const response = await axios_1.default.get(url, {
247
+ responseType: "stream",
248
+ timeout: 30000, // 30 second timeout
249
+ maxContentLength: this.opts.maxFileSize,
250
+ });
251
+ const stream = response.data;
252
+ const contentType = response.headers["content-type"] || "";
253
+ const contentLength = response.headers["content-length"];
254
+ this.debug("Download successful:", {
255
+ contentType,
256
+ contentLength: contentLength ? parseInt(contentLength, 10) : "unknown",
257
+ });
258
+ // Determine stream type based on content type or file extension
259
+ const streamType = this.getStreamType(contentType, track.metadata?.extension);
260
+ return {
261
+ stream,
262
+ type: streamType,
263
+ metadata: {
264
+ ...track.metadata,
265
+ contentType,
266
+ contentLength: contentLength ? parseInt(contentLength, 10) : undefined,
267
+ },
268
+ };
269
+ }
270
+ catch (error) {
271
+ if (error.code === "ECONNABORTED") {
272
+ throw new Error(`Download timeout for ${url}`);
273
+ }
274
+ if (error.response?.status === 404) {
275
+ throw new Error(`File not found: ${url}`);
276
+ }
277
+ if (error.response?.status === 403) {
278
+ throw new Error(`Access denied: ${url}`);
279
+ }
280
+ if (error.message?.includes("maxContentLength")) {
281
+ throw new Error(`File too large: ${url}`);
282
+ }
283
+ throw new Error(`Failed to download audio: ${error.message || error}`);
284
+ }
285
+ }
286
+ /**
287
+ * Provides a fallback by attempting to re-download the file.
288
+ *
289
+ * @param track - The Track object to get a fallback stream for
290
+ * @returns A StreamInfo object containing the fallback audio stream
291
+ * @throws {Error} If fallback download fails
292
+ */
293
+ async getFallback(track) {
294
+ this.debug("Attempting fallback for track:", track.title);
295
+ return await this.getStream(track);
296
+ }
297
+ /**
298
+ * Checks if a file path or URL is an audio file based on extension.
299
+ */
300
+ isAudioFile(path) {
301
+ const extension = this.getFileExtension(path);
302
+ return this.opts.allowedExtensions.includes(extension.toLowerCase());
303
+ }
304
+ /**
305
+ * Extracts the file extension from a path or URL.
306
+ */
307
+ getFileExtension(path) {
308
+ const lastDot = path.lastIndexOf(".");
309
+ if (lastDot === -1)
310
+ return "";
311
+ const extension = path.slice(lastDot + 1);
312
+ // Remove query parameters if present
313
+ const questionMark = extension.indexOf("?");
314
+ return questionMark === -1 ? extension : extension.slice(0, questionMark);
315
+ }
316
+ /**
317
+ * Extracts filename from a URL or path.
318
+ */
319
+ extractFilename(path) {
320
+ try {
321
+ if (path.startsWith("http://") || path.startsWith("https://")) {
322
+ const url = new URL(path);
323
+ const pathname = url.pathname;
324
+ const lastSlash = pathname.lastIndexOf("/");
325
+ return lastSlash === -1 ? pathname : pathname.slice(lastSlash + 1);
326
+ }
327
+ // Local file path
328
+ const lastSlash = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
329
+ return lastSlash === -1 ? path : path.slice(lastSlash + 1);
330
+ }
331
+ catch {
332
+ return "Unknown File";
333
+ }
334
+ }
335
+ /**
336
+ * Checks if a URL is a Discord attachment URL.
337
+ */
338
+ isDiscordAttachment(url) {
339
+ try {
340
+ const urlObj = new URL(url);
341
+ return urlObj.hostname === "cdn.discordapp.com" || urlObj.hostname === "media.discordapp.net";
342
+ }
343
+ catch {
344
+ return false;
345
+ }
346
+ }
347
+ /**
348
+ * Generates a unique track ID for a given URL.
349
+ */
350
+ generateTrackId(url) {
351
+ // Create a hash-like ID from the URL
352
+ const hash = this.simpleHash(url);
353
+ return `attachment-${hash}-${Date.now()}`;
354
+ }
355
+ /**
356
+ * Simple hash function for generating IDs.
357
+ */
358
+ simpleHash(str) {
359
+ let hash = 0;
360
+ for (let i = 0; i < str.length; i++) {
361
+ const char = str.charCodeAt(i);
362
+ hash = (hash << 5) - hash + char;
363
+ hash = hash & hash; // Convert to 32-bit integer
364
+ }
365
+ return Math.abs(hash).toString(36);
366
+ }
367
+ /**
368
+ * Determines the appropriate stream type based on content type and file extension.
369
+ */
370
+ getStreamType(contentType, extension) {
371
+ const type = contentType.toLowerCase();
372
+ const ext = extension?.toLowerCase() || "";
373
+ // Check content type first
374
+ if (type.includes("webm") && type.includes("opus"))
375
+ return "webm/opus";
376
+ if (type.includes("ogg") && type.includes("opus"))
377
+ return "ogg/opus";
378
+ // Fallback to extension
379
+ if (ext === "webm")
380
+ return "webm/opus";
381
+ if (ext === "ogg")
382
+ return "ogg/opus";
383
+ // Default to arbitrary for all other types (mp3, wav, flac, mp4, etc.)
384
+ return "arbitrary";
385
+ }
386
+ /**
387
+ * Formats bytes into a human-readable string.
388
+ */
389
+ formatBytes(bytes) {
390
+ if (bytes === 0)
391
+ return "0 Bytes";
392
+ const k = 1024;
393
+ const sizes = ["Bytes", "KB", "MB", "GB"];
394
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
395
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
396
+ }
397
+ /**
398
+ * Analyzes audio metadata to extract duration and other information.
399
+ *
400
+ * @param url - The URL to analyze
401
+ * @returns Promise containing duration in seconds and metadata
402
+ */
403
+ async analyzeAudioMetadata(url) {
404
+ try {
405
+ this.debug("Analyzing audio metadata for:", url);
406
+ // Download a small portion of the file to analyze metadata
407
+ const response = await axios_1.default.get(url, {
408
+ responseType: "arraybuffer",
409
+ timeout: 10000, // 10 second timeout for metadata analysis
410
+ maxContentLength: 1024 * 1024, // Only download first 1MB for metadata
411
+ headers: {
412
+ Range: "bytes=0-1048575", // Request first 1MB
413
+ },
414
+ });
415
+ const buffer = Buffer.from(response.data);
416
+ const metadata = await (0, music_metadata_1.parseBuffer)(buffer);
417
+ const duration = metadata.format.duration || 0;
418
+ this.debug("Audio metadata analysis result:", {
419
+ duration: `${duration}s`,
420
+ format: metadata.format.container,
421
+ codec: metadata.format.codec,
422
+ bitrate: metadata.format.bitrate,
423
+ });
424
+ return {
425
+ duration: Math.round(duration),
426
+ metadata: {
427
+ format: metadata.format.container,
428
+ codec: metadata.format.codec,
429
+ bitrate: metadata.format.bitrate,
430
+ sampleRate: metadata.format.sampleRate,
431
+ channels: metadata.format.numberOfChannels,
432
+ title: metadata.common.title,
433
+ artist: metadata.common.artist,
434
+ album: metadata.common.album,
435
+ year: metadata.common.year,
436
+ genre: metadata.common.genre,
437
+ },
438
+ };
439
+ }
440
+ catch (error) {
441
+ this.debug("Failed to analyze audio metadata:", error.message);
442
+ // Fallback: try without Range header for some servers that don't support it
443
+ try {
444
+ const response = await axios_1.default.get(url, {
445
+ responseType: "arraybuffer",
446
+ timeout: 10000,
447
+ maxContentLength: 1024 * 1024, // 1MB limit
448
+ });
449
+ const buffer = Buffer.from(response.data);
450
+ const metadata = await (0, music_metadata_1.parseBuffer)(buffer);
451
+ const duration = metadata.format.duration || 0;
452
+ this.debug("Audio metadata analysis (fallback) result:", {
453
+ duration: `${duration}s`,
454
+ format: metadata.format.container,
455
+ });
456
+ return {
457
+ duration: Math.round(duration),
458
+ metadata: {
459
+ format: metadata.format.container,
460
+ codec: metadata.format.codec,
461
+ bitrate: metadata.format.bitrate,
462
+ },
463
+ };
464
+ }
465
+ catch (fallbackError) {
466
+ this.debug("Fallback metadata analysis also failed:", fallbackError.message);
467
+ return { duration: 0 };
468
+ }
469
+ }
470
+ }
471
+ /**
472
+ * Debug logging helper.
473
+ */
474
+ debug(message, ...args) {
475
+ if (this.opts.debug) {
476
+ console.log(`[AttachmentsPlugin] ${message}`, ...args);
477
+ }
478
+ }
479
+ }
480
+ exports.AttachmentsPlugin = AttachmentsPlugin;
481
+ //# sourceMappingURL=AttachmentsPlugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AttachmentsPlugin.js","sourceRoot":"","sources":["../src/AttachmentsPlugin.ts"],"names":[],"mappings":"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"}
@@ -36,6 +36,7 @@ export interface TTSPluginOptions {
36
36
  * - Multiple language support
37
37
  * - Configurable speech rate (normal/slow)
38
38
  * - TTS query parsing with language and speed options
39
+ * - Accurate duration analysis by generating sample audio
39
40
  *
40
41
  * @example
41
42
  * const ttsPlugin = new TTSPlugin({
@@ -131,6 +132,22 @@ export declare class TTSPlugin extends BasePlugin {
131
132
  * const result4 = await plugin.search("say Hello world", "user123");
132
133
  */
133
134
  search(query: string, requestedBy: string): Promise<SearchResult>;
135
+ /**
136
+ * Analyzes TTS audio duration by generating a small sample and measuring its length.
137
+ *
138
+ * @param text - The text to analyze
139
+ * @param lang - The language code
140
+ * @param slow - Whether to use slow speech rate
141
+ * @returns Promise containing duration in seconds
142
+ */
143
+ private analyzeTTSDuration;
144
+ /**
145
+ * Estimates TTS duration based on text length and language.
146
+ *
147
+ * @param text - The text to estimate duration for
148
+ * @returns Estimated duration in seconds
149
+ */
150
+ private estimateDuration;
134
151
  /**
135
152
  * Generates an audio stream for a TTS track.
136
153
  *
@@ -1 +1 @@
1
- {"version":3,"file":"TTSPlugin.d.ts","sourceRoot":"","sources":["../src/TTSPlugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAIlC;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sCAAsC;IACtC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;;;;;;;;;;OAYG;IACH,YAAY,CAAC,EAAE,CACd,IAAI,EAAE,MAAM,EACZ,GAAG,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,KAAK,CAAA;KAAE,KAElD,OAAO,CAAC,QAAQ,GAAG,MAAM,GAAG,GAAG,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,CAAC,GACpE,QAAQ,GACR,MAAM,GACN,GAAG,GACH,MAAM,GACN,UAAU,GACV,WAAW,CAAC;CACf;AAcD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,qBAAa,SAAU,SAAQ,UAAU;IACxC,IAAI,SAAS;IACb,OAAO,SAAW;IAClB,OAAO,CAAC,IAAI,CAA0F;IAEtG;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;gBACS,IAAI,CAAC,EAAE,gBAAgB;IASnC;;;;;;;;;;OAUG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAMjC;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAuBvE;;;;;;;;;;;;;;;;OAgBG;IACG,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,UAAU,CAAC;YAsEpC,UAAU;IAgBxB,OAAO,CAAC,UAAU;IAiDlB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,aAAa;CAYrB"}
1
+ {"version":3,"file":"TTSPlugin.d.ts","sourceRoot":"","sources":["../src/TTSPlugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAKlC;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sCAAsC;IACtC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;;;;;;;;;;OAYG;IACH,YAAY,CAAC,EAAE,CACd,IAAI,EAAE,MAAM,EACZ,GAAG,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,KAAK,CAAA;KAAE,KAElD,OAAO,CAAC,QAAQ,GAAG,MAAM,GAAG,GAAG,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,CAAC,GACpE,QAAQ,GACR,MAAM,GACN,GAAG,GACH,MAAM,GACN,UAAU,GACV,WAAW,CAAC;CACf;AAcD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,qBAAa,SAAU,SAAQ,UAAU;IACxC,IAAI,SAAS;IACb,OAAO,SAAW;IAClB,OAAO,CAAC,IAAI,CAA0F;IAEtG;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;gBACS,IAAI,CAAC,EAAE,gBAAgB;IASnC;;;;;;;;;;OAUG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAMjC;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAqCvE;;;;;;;OAOG;YACW,kBAAkB;IAgChC;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;IAaxB;;;;;;;;;;;;;;;;OAgBG;IACG,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,UAAU,CAAC;YAmEpC,UAAU;IAgBxB,OAAO,CAAC,UAAU;IAiDlB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,aAAa;CAYrB"}
package/dist/TTSPlugin.js CHANGED
@@ -8,6 +8,7 @@ const ziplayer_1 = require("ziplayer");
8
8
  const stream_1 = require("stream");
9
9
  const zitts_1 = require("@zibot/zitts");
10
10
  const axios_1 = __importDefault(require("axios"));
11
+ const music_metadata_1 = require("music-metadata");
11
12
  /**
12
13
  * A plugin for Text-to-Speech (TTS) functionality.
13
14
  *
@@ -17,6 +18,7 @@ const axios_1 = __importDefault(require("axios"));
17
18
  * - Multiple language support
18
19
  * - Configurable speech rate (normal/slow)
19
20
  * - TTS query parsing with language and speed options
21
+ * - Accurate duration analysis by generating sample audio
20
22
  *
21
23
  * @example
22
24
  * const ttsPlugin = new TTSPlugin({
@@ -130,18 +132,80 @@ class TTSPlugin extends ziplayer_1.BasePlugin {
130
132
  const config = { text, lang, slow };
131
133
  const url = this.encodeConfig(config);
132
134
  const title = `TTS (${lang}${slow ? ", slow" : ""}): ${text.slice(0, 64)}${text.length > 64 ? "…" : ""}`;
133
- const estimatedSeconds = Math.max(1, Math.min(60, Math.ceil(text.length / 12)));
135
+ // Analyze actual TTS duration
136
+ let duration;
137
+ try {
138
+ duration = await this.analyzeTTSDuration(text, lang, slow);
139
+ }
140
+ catch (error) {
141
+ // Fallback to estimation if analysis fails
142
+ duration = this.estimateDuration(text);
143
+ }
134
144
  const track = {
135
145
  id: `tts-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
136
146
  title,
137
147
  url,
138
- duration: estimatedSeconds,
148
+ duration,
139
149
  requestedBy,
140
150
  source: this.name,
141
- metadata: { tts: config },
151
+ metadata: {
152
+ tts: config,
153
+ analyzedDuration: true,
154
+ textLength: text.length,
155
+ language: lang,
156
+ slowMode: slow,
157
+ },
142
158
  };
143
159
  return { tracks: [track] };
144
160
  }
161
+ /**
162
+ * Analyzes TTS audio duration by generating a small sample and measuring its length.
163
+ *
164
+ * @param text - The text to analyze
165
+ * @param lang - The language code
166
+ * @param slow - Whether to use slow speech rate
167
+ * @returns Promise containing duration in seconds
168
+ */
169
+ async analyzeTTSDuration(text, lang, slow) {
170
+ try {
171
+ // Use a shorter sample text for duration analysis to minimize processing time
172
+ const sampleText = text.length > 50 ? text.slice(0, 50) + "..." : text;
173
+ const urls = (0, zitts_1.getTTSUrls)(sampleText, { lang, slow });
174
+ if (!urls || urls.length === 0) {
175
+ return this.estimateDuration(text);
176
+ }
177
+ // Download the sample audio
178
+ const parts = await Promise.all(urls.map((u) => axios_1.default.get(u, { responseType: "arraybuffer" }).then((r) => Buffer.from(r.data))));
179
+ const merged = Buffer.concat(parts);
180
+ // Parse metadata to get actual duration
181
+ const metadata = await (0, music_metadata_1.parseBuffer)(merged);
182
+ const actualDuration = metadata.format.duration || 0;
183
+ // Calculate ratio and apply to full text
184
+ const sampleRatio = sampleText.length / text.length;
185
+ const estimatedDuration = actualDuration / sampleRatio;
186
+ return Math.round(estimatedDuration);
187
+ }
188
+ catch (error) {
189
+ // Fallback to text-based estimation
190
+ return this.estimateDuration(text);
191
+ }
192
+ }
193
+ /**
194
+ * Estimates TTS duration based on text length and language.
195
+ *
196
+ * @param text - The text to estimate duration for
197
+ * @returns Estimated duration in seconds
198
+ */
199
+ estimateDuration(text) {
200
+ // Base estimation: ~12 characters per second for normal speech
201
+ // Adjust based on text complexity and language
202
+ const baseRate = 12; // characters per second
203
+ const wordCount = text.split(/\s+/).length;
204
+ const charCount = text.length;
205
+ // Use word count for better estimation
206
+ const estimatedSeconds = Math.max(1, Math.min(300, Math.ceil(wordCount / 2.5)));
207
+ return estimatedSeconds;
208
+ }
145
209
  /**
146
210
  * Generates an audio stream for a TTS track.
147
211
  *
@@ -198,10 +262,7 @@ class TTSPlugin extends ziplayer_1.BasePlugin {
198
262
  const urlStr = o.url.toString();
199
263
  try {
200
264
  type =
201
- normType(o.type) ||
202
- (urlStr.endsWith(".webm") ? "webm/opus"
203
- : urlStr.endsWith(".ogg") ? "ogg/opus"
204
- : undefined);
265
+ normType(o.type) || (urlStr.endsWith(".webm") ? "webm/opus" : urlStr.endsWith(".ogg") ? "ogg/opus" : undefined);
205
266
  const res = await axios_1.default.get(urlStr, { responseType: "stream" });
206
267
  stream = res.data;
207
268
  metadata = o.metadata;