frostpv 1.0.6 → 1.0.8
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/index.js +282 -117
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -4,7 +4,7 @@ const path = require("path");
|
|
|
4
4
|
const os = require("os");
|
|
5
5
|
const FormData = require("form-data");
|
|
6
6
|
const crypto = require("crypto");
|
|
7
|
-
const { igdl, ttdl, fbdown, mediafire, capcut, gdrive } = require("btch-downloader");
|
|
7
|
+
const { igdl, ttdl, fbdown, mediafire, capcut, gdrive, pinterest, youtube } = require("btch-downloader");
|
|
8
8
|
const { TwitterDL } = require("twitter-downloader");
|
|
9
9
|
const btch = require("btch-downloader");
|
|
10
10
|
const btchOld = require("btch-downloader-old");
|
|
@@ -21,10 +21,10 @@ ffmpeg.setFfmpegPath(pathToFfmpeg.ffmpegPath);
|
|
|
21
21
|
|
|
22
22
|
// Output directory is the caller's working directory (keeps files next to the running app)
|
|
23
23
|
const OUTPUT_DIR = process.cwd();
|
|
24
|
-
try { fs.mkdirSync(OUTPUT_DIR, { recursive: true }); } catch (_) {}
|
|
24
|
+
try { fs.mkdirSync(OUTPUT_DIR, { recursive: true }); } catch (_) { }
|
|
25
25
|
// Keep an OS temp directory for any future transient needs
|
|
26
26
|
const TEMP_DIR = path.join(os.tmpdir(), "downloader-dl-bot");
|
|
27
|
-
try { fs.mkdirSync(TEMP_DIR, { recursive: true }); } catch (_) {}
|
|
27
|
+
try { fs.mkdirSync(TEMP_DIR, { recursive: true }); } catch (_) { }
|
|
28
28
|
// TTL (minutes) for files in OUTPUT_DIR (audio/video) before they are pruned
|
|
29
29
|
const OUTPUT_RETENTION_MIN = Number(process.env.OUTPUT_RETENTION_MIN || 5);
|
|
30
30
|
|
|
@@ -67,6 +67,16 @@ const linkCant = [{
|
|
|
67
67
|
reason: "Use real link like 'https://www.tiktok.com'. (just click on your link and copy the link in the browser)"
|
|
68
68
|
}];
|
|
69
69
|
|
|
70
|
+
// Function to check if a link corresponds to a video platform
|
|
71
|
+
const isVideoLink = (link) => {
|
|
72
|
+
try {
|
|
73
|
+
const u = new URL(link);
|
|
74
|
+
const host = (u.hostname || '').toLowerCase();
|
|
75
|
+
if (host.endsWith('pintere21313213st.com')) return true;
|
|
76
|
+
} catch (_) { }
|
|
77
|
+
return videoPlatforms.some(platform => link.startsWith(platform));
|
|
78
|
+
};
|
|
79
|
+
|
|
70
80
|
// Function to check if a link is blacklisted
|
|
71
81
|
const blacklistLink = (link) => {
|
|
72
82
|
return linkCant.find(item => link.includes(item.link));
|
|
@@ -136,14 +146,14 @@ async function safeUnlinkWithRetry(filePath, maxRetries = 3) {
|
|
|
136
146
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
137
147
|
try {
|
|
138
148
|
const resolvedPath = path.resolve(filePath);
|
|
139
|
-
|
|
149
|
+
|
|
140
150
|
if (fs.existsSync(resolvedPath)) {
|
|
141
151
|
// Check if file is in use (Windows)
|
|
142
152
|
if (process.platform === 'win32' && isFileInUse(resolvedPath)) {
|
|
143
153
|
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
|
|
144
154
|
continue;
|
|
145
155
|
}
|
|
146
|
-
|
|
156
|
+
|
|
147
157
|
fs.rmSync(resolvedPath, { force: true });
|
|
148
158
|
return true;
|
|
149
159
|
} else {
|
|
@@ -215,7 +225,7 @@ async function validateVideoUrl(url, platform) {
|
|
|
215
225
|
return status >= 200 && status < 400; // Accept redirects
|
|
216
226
|
}
|
|
217
227
|
});
|
|
218
|
-
|
|
228
|
+
|
|
219
229
|
return true;
|
|
220
230
|
} catch (error) {
|
|
221
231
|
return false;
|
|
@@ -227,18 +237,18 @@ function validateTwitterUrl(url) {
|
|
|
227
237
|
try {
|
|
228
238
|
// Remove query parameters that might cause issues
|
|
229
239
|
const cleanUrl = url.split('?')[0];
|
|
230
|
-
|
|
240
|
+
|
|
231
241
|
// Ensure it's a valid Twitter/X URL format
|
|
232
242
|
if (!cleanUrl.includes('x.com') && !cleanUrl.includes('twitter.com')) {
|
|
233
243
|
throw new Error("Not a valid Twitter/X URL");
|
|
234
244
|
}
|
|
235
|
-
|
|
245
|
+
|
|
236
246
|
// Check if it has the required structure
|
|
237
247
|
const urlParts = cleanUrl.split('/');
|
|
238
248
|
if (urlParts.length < 5) {
|
|
239
249
|
throw new Error("Invalid Twitter/X URL structure");
|
|
240
250
|
}
|
|
241
|
-
|
|
251
|
+
|
|
242
252
|
return cleanUrl;
|
|
243
253
|
} catch (error) {
|
|
244
254
|
throw new Error(`Twitter URL validation failed: ${error.message}`);
|
|
@@ -277,10 +287,10 @@ async function downloadYoutubeAudio(url, outputPath) {
|
|
|
277
287
|
try {
|
|
278
288
|
const ytdlp = new YtDlp({ ffmpegPath });
|
|
279
289
|
const cookiesPath = getYoutubeCookiesPath();
|
|
280
|
-
|
|
290
|
+
|
|
281
291
|
// Garantir que o nome do arquivo tenha a extensão correta
|
|
282
292
|
const baseName = outputPath.replace(/\.[^/.]+$/, ""); // Remove extensão se existir
|
|
283
|
-
|
|
293
|
+
|
|
284
294
|
// Primeiro, tentar com a API de opções
|
|
285
295
|
try {
|
|
286
296
|
const options = {
|
|
@@ -291,11 +301,11 @@ async function downloadYoutubeAudio(url, outputPath) {
|
|
|
291
301
|
},
|
|
292
302
|
output: outputPath
|
|
293
303
|
};
|
|
294
|
-
|
|
304
|
+
|
|
295
305
|
if (cookiesPath) {
|
|
296
306
|
options.cookies = cookiesPath;
|
|
297
307
|
}
|
|
298
|
-
|
|
308
|
+
|
|
299
309
|
// Remover logs detalhados para download de áudio do YouTube
|
|
300
310
|
const result = await ytdlp.downloadAsync(url, options);
|
|
301
311
|
// Verificar se o arquivo foi criado
|
|
@@ -304,7 +314,7 @@ async function downloadYoutubeAudio(url, outputPath) {
|
|
|
304
314
|
resolve(actualFileName);
|
|
305
315
|
return;
|
|
306
316
|
}
|
|
307
|
-
|
|
317
|
+
|
|
308
318
|
// Tentar encontrar o arquivo com extensão diferente
|
|
309
319
|
const files = fs.readdirSync('./');
|
|
310
320
|
const downloadedFile = files.find(file => file.startsWith(baseName.split('/').pop()));
|
|
@@ -315,18 +325,18 @@ async function downloadYoutubeAudio(url, outputPath) {
|
|
|
315
325
|
} catch (apiError) {
|
|
316
326
|
console.log('API method failed, trying direct args method:', apiError.message);
|
|
317
327
|
}
|
|
318
|
-
|
|
328
|
+
|
|
319
329
|
// Fallback: usar argumentos diretos para máxima qualidade de áudio
|
|
320
330
|
const args = [
|
|
321
331
|
url,
|
|
322
332
|
'-f', 'bestaudio[ext=mp3]/bestaudio',
|
|
323
333
|
'-o', outputPath
|
|
324
334
|
];
|
|
325
|
-
|
|
335
|
+
|
|
326
336
|
if (cookiesPath) {
|
|
327
337
|
args.push('--cookies', cookiesPath);
|
|
328
338
|
}
|
|
329
|
-
|
|
339
|
+
|
|
330
340
|
// Remover logs detalhados para download de áudio do YouTube
|
|
331
341
|
const result = await ytdlp.execAsync(args);
|
|
332
342
|
// Verificar se o arquivo foi criado
|
|
@@ -353,25 +363,53 @@ async function downloadYoutubeAudio(url, outputPath) {
|
|
|
353
363
|
// Enhanced function to get platform type from URL
|
|
354
364
|
function getPlatformType(url) {
|
|
355
365
|
const lowerUrl = url.toLowerCase();
|
|
356
|
-
|
|
366
|
+
|
|
357
367
|
if (lowerUrl.includes("instagram.com") || lowerUrl.includes("instagr.am")) return "instagram";
|
|
358
368
|
if (lowerUrl.includes("tiktok.com") || lowerUrl.includes("vm.tiktok.com") || lowerUrl.includes("vt.tiktok.com")) return "tiktok";
|
|
359
369
|
if (lowerUrl.includes("facebook.com") || lowerUrl.includes("fb.watch") || lowerUrl.includes("fb.com")) return "facebook";
|
|
360
370
|
if (lowerUrl.includes("mediafire.com")) return "mediafire";
|
|
361
371
|
if (lowerUrl.includes("x.com") || lowerUrl.includes("twitter.com")) return "twitter";
|
|
362
|
-
if (lowerUrl.includes("youtube.com") || lowerUrl.includes("
|
|
363
|
-
|
|
372
|
+
if (lowerUrl.includes("youtube.com") || lowerUrl.includes("youtu.be") || lowerUrl.includes("m.youtube.com")) return "youtube";
|
|
373
|
+
|
|
364
374
|
return "unknown";
|
|
365
375
|
}
|
|
366
376
|
|
|
377
|
+
// Function to get direct video URL from Twitter using yt-dlp
|
|
378
|
+
async function downloadTwitterWithYtDlp(url) {
|
|
379
|
+
return new Promise(async (resolve, reject) => {
|
|
380
|
+
try {
|
|
381
|
+
const ytdlp = new YtDlp({ ffmpegPath });
|
|
382
|
+
// Use -g to get URL, -f "best[ext=mp4]/best" to get best quality single file if possible
|
|
383
|
+
const args = [
|
|
384
|
+
url,
|
|
385
|
+
'-g',
|
|
386
|
+
'-f', 'best[ext=mp4]/best'
|
|
387
|
+
];
|
|
388
|
+
|
|
389
|
+
const result = await ytdlp.execAsync(args);
|
|
390
|
+
// yt-dlp might return multiple lines if it finds separate audio/video streams
|
|
391
|
+
// We take the first one, but for Twitter it is usually a single mp4 file
|
|
392
|
+
const videoUrl = result.trim().split('\n')[0];
|
|
393
|
+
|
|
394
|
+
if (videoUrl && videoUrl.startsWith('http')) {
|
|
395
|
+
resolve(videoUrl);
|
|
396
|
+
} else {
|
|
397
|
+
reject(new Error('yt-dlp did not return a valid URL'));
|
|
398
|
+
}
|
|
399
|
+
} catch (error) {
|
|
400
|
+
reject(error);
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
367
405
|
// Enhanced fallback download function with platform-specific methods
|
|
368
406
|
async function tryFallbackDownload(url, maxRetries = 3) {
|
|
369
407
|
const platform = getPlatformType(url);
|
|
370
|
-
|
|
408
|
+
|
|
371
409
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
372
410
|
try {
|
|
373
411
|
let videoUrl = null;
|
|
374
|
-
|
|
412
|
+
|
|
375
413
|
// Platform-specific fallback methods
|
|
376
414
|
switch (platform) {
|
|
377
415
|
case "instagram": {
|
|
@@ -417,7 +455,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
417
455
|
}
|
|
418
456
|
}
|
|
419
457
|
];
|
|
420
|
-
|
|
458
|
+
|
|
421
459
|
for (const method of methods) {
|
|
422
460
|
try {
|
|
423
461
|
videoUrl = await method();
|
|
@@ -428,7 +466,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
428
466
|
}
|
|
429
467
|
break;
|
|
430
468
|
}
|
|
431
|
-
|
|
469
|
+
|
|
432
470
|
case "tiktok": {
|
|
433
471
|
// Try multiple TikTok methods
|
|
434
472
|
const methods = [
|
|
@@ -463,7 +501,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
463
501
|
throw new Error("No valid video in Tiktok.Downloader v2 response");
|
|
464
502
|
}
|
|
465
503
|
];
|
|
466
|
-
|
|
504
|
+
|
|
467
505
|
for (const method of methods) {
|
|
468
506
|
try {
|
|
469
507
|
videoUrl = await method();
|
|
@@ -474,7 +512,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
474
512
|
}
|
|
475
513
|
break;
|
|
476
514
|
}
|
|
477
|
-
|
|
515
|
+
|
|
478
516
|
case "facebook": {
|
|
479
517
|
// Try multiple Facebook methods
|
|
480
518
|
const methods = [
|
|
@@ -498,7 +536,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
498
536
|
throw new Error("No valid video in fbdown response with custom headers");
|
|
499
537
|
}
|
|
500
538
|
];
|
|
501
|
-
|
|
539
|
+
|
|
502
540
|
for (const method of methods) {
|
|
503
541
|
try {
|
|
504
542
|
videoUrl = await method();
|
|
@@ -509,42 +547,46 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
509
547
|
}
|
|
510
548
|
break;
|
|
511
549
|
}
|
|
512
|
-
|
|
550
|
+
|
|
513
551
|
case "twitter": {
|
|
514
|
-
// Try
|
|
552
|
+
// Try using yt-dlp first (Most reliable), then fallback to existing methods
|
|
515
553
|
const methods = [
|
|
554
|
+
async () => {
|
|
555
|
+
// Priority 1: yt-dlp
|
|
556
|
+
return await downloadTwitterWithYtDlp(url);
|
|
557
|
+
},
|
|
516
558
|
async () => {
|
|
517
559
|
const cleanUrl = validateTwitterUrl(url);
|
|
518
560
|
const data = await TwitterDL(cleanUrl, {});
|
|
519
|
-
|
|
561
|
+
|
|
520
562
|
// Check multiple possible response structures
|
|
521
563
|
if (data && data.result) {
|
|
522
564
|
// Structure 1: data.result.media[0].videos[0].url
|
|
523
|
-
if (data.result.media && data.result.media[0] &&
|
|
524
|
-
|
|
525
|
-
|
|
565
|
+
if (data.result.media && data.result.media[0] &&
|
|
566
|
+
data.result.media[0].videos && data.result.media[0].videos[0] &&
|
|
567
|
+
data.result.media[0].videos[0].url) {
|
|
526
568
|
return data.result.media[0].videos[0].url;
|
|
527
569
|
}
|
|
528
|
-
|
|
570
|
+
|
|
529
571
|
// Structure 2: data.result.video[0].url
|
|
530
572
|
if (data.result.video && data.result.video[0] && data.result.video[0].url) {
|
|
531
573
|
return data.result.video[0].url;
|
|
532
574
|
}
|
|
533
|
-
|
|
575
|
+
|
|
534
576
|
// Structure 3: data.result.url (direct URL)
|
|
535
577
|
if (data.result.url) {
|
|
536
578
|
return data.result.url;
|
|
537
579
|
}
|
|
538
|
-
|
|
580
|
+
|
|
539
581
|
// Structure 4: data.result.media[0].url
|
|
540
582
|
if (data.result.media && data.result.media[0] && data.result.media[0].url) {
|
|
541
583
|
return data.result.media[0].url;
|
|
542
584
|
}
|
|
543
|
-
|
|
585
|
+
|
|
544
586
|
// Structure 5: Check for any video URL in the entire response
|
|
545
587
|
const findVideoUrl = (obj) => {
|
|
546
|
-
if (typeof obj === 'string' && obj.includes('http') &&
|
|
547
|
-
|
|
588
|
+
if (typeof obj === 'string' && obj.includes('http') &&
|
|
589
|
+
(obj.includes('.mp4') || obj.includes('video') || obj.includes('media'))) {
|
|
548
590
|
return obj;
|
|
549
591
|
}
|
|
550
592
|
if (typeof obj === 'object' && obj !== null) {
|
|
@@ -555,13 +597,13 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
555
597
|
}
|
|
556
598
|
return null;
|
|
557
599
|
};
|
|
558
|
-
|
|
600
|
+
|
|
559
601
|
const foundUrl = findVideoUrl(data.result);
|
|
560
602
|
if (foundUrl) {
|
|
561
603
|
return foundUrl;
|
|
562
604
|
}
|
|
563
605
|
}
|
|
564
|
-
|
|
606
|
+
|
|
565
607
|
throw new Error("No valid video URL found in TwitterDL response structure");
|
|
566
608
|
},
|
|
567
609
|
async () => {
|
|
@@ -577,30 +619,30 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
577
619
|
'Upgrade-Insecure-Requests': '1'
|
|
578
620
|
}
|
|
579
621
|
});
|
|
580
|
-
|
|
622
|
+
|
|
581
623
|
// Same structure checking as above
|
|
582
624
|
if (data && data.result) {
|
|
583
|
-
if (data.result.media && data.result.media[0] &&
|
|
584
|
-
|
|
585
|
-
|
|
625
|
+
if (data.result.media && data.result.media[0] &&
|
|
626
|
+
data.result.media[0].videos && data.result.media[0].videos[0] &&
|
|
627
|
+
data.result.media[0].videos[0].url) {
|
|
586
628
|
return data.result.media[0].videos[0].url;
|
|
587
629
|
}
|
|
588
|
-
|
|
630
|
+
|
|
589
631
|
if (data.result.video && data.result.video[0] && data.result.video[0].url) {
|
|
590
632
|
return data.result.video[0].url;
|
|
591
633
|
}
|
|
592
|
-
|
|
634
|
+
|
|
593
635
|
if (data.result.url) {
|
|
594
636
|
return data.result.url;
|
|
595
637
|
}
|
|
596
|
-
|
|
638
|
+
|
|
597
639
|
if (data.result.media && data.result.media[0] && data.result.media[0].url) {
|
|
598
640
|
return data.result.media[0].url;
|
|
599
641
|
}
|
|
600
|
-
|
|
642
|
+
|
|
601
643
|
const findVideoUrl = (obj) => {
|
|
602
|
-
if (typeof obj === 'string' && obj.includes('http') &&
|
|
603
|
-
|
|
644
|
+
if (typeof obj === 'string' && obj.includes('http') &&
|
|
645
|
+
(obj.includes('.mp4') || obj.includes('video') || obj.includes('media'))) {
|
|
604
646
|
return obj;
|
|
605
647
|
}
|
|
606
648
|
if (typeof obj === 'object' && obj !== null) {
|
|
@@ -611,13 +653,13 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
611
653
|
}
|
|
612
654
|
return null;
|
|
613
655
|
};
|
|
614
|
-
|
|
656
|
+
|
|
615
657
|
const foundUrl = findVideoUrl(data.result);
|
|
616
658
|
if (foundUrl) {
|
|
617
659
|
return foundUrl;
|
|
618
660
|
}
|
|
619
661
|
}
|
|
620
|
-
|
|
662
|
+
|
|
621
663
|
throw new Error("No valid video URL found in TwitterDL response with custom headers");
|
|
622
664
|
},
|
|
623
665
|
async () => {
|
|
@@ -653,9 +695,9 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
653
695
|
const data = await btchOld[funcName](cleanUrl);
|
|
654
696
|
// Check multiple possible response structures
|
|
655
697
|
if (data && data.result) {
|
|
656
|
-
if (data.result.media && data.result.media[0] &&
|
|
657
|
-
|
|
658
|
-
|
|
698
|
+
if (data.result.media && data.result.media[0] &&
|
|
699
|
+
data.result.media[0].videos && data.result.media[0].videos[0] &&
|
|
700
|
+
data.result.media[0].videos[0].url) {
|
|
659
701
|
return data.result.media[0].videos[0].url;
|
|
660
702
|
}
|
|
661
703
|
if (data.result.video && data.result.video[0] && data.result.video[0].url) {
|
|
@@ -682,7 +724,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
682
724
|
}
|
|
683
725
|
}
|
|
684
726
|
];
|
|
685
|
-
|
|
727
|
+
|
|
686
728
|
for (const method of methods) {
|
|
687
729
|
try {
|
|
688
730
|
videoUrl = await method();
|
|
@@ -693,12 +735,12 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
693
735
|
}
|
|
694
736
|
break;
|
|
695
737
|
}
|
|
696
|
-
|
|
738
|
+
|
|
697
739
|
case "youtube": {
|
|
698
740
|
// YouTube doesn't need fallback - it has its own specific method
|
|
699
741
|
throw new Error("YouTube downloads should use the primary method, not fallback");
|
|
700
742
|
}
|
|
701
|
-
|
|
743
|
+
|
|
702
744
|
case "mediafire": {
|
|
703
745
|
// Try MediaFire method
|
|
704
746
|
try {
|
|
@@ -713,7 +755,33 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
713
755
|
}
|
|
714
756
|
break;
|
|
715
757
|
}
|
|
716
|
-
|
|
758
|
+
case "pinterest": {
|
|
759
|
+
try {
|
|
760
|
+
const data = await pinterest(url);
|
|
761
|
+
let mediaUrl = null;
|
|
762
|
+
const pickFrom = (obj) => {
|
|
763
|
+
if (!obj) return null;
|
|
764
|
+
if (typeof obj === 'string' && obj.startsWith('http')) return obj;
|
|
765
|
+
if (Array.isArray(obj)) {
|
|
766
|
+
for (const it of obj) {
|
|
767
|
+
const got = pickFrom(it);
|
|
768
|
+
if (got) return got;
|
|
769
|
+
}
|
|
770
|
+
} else if (typeof obj === 'object') {
|
|
771
|
+
const candidates = ['video', 'videoUrl', 'download', 'url', 'image', 'imageUrl', 'src', 'link'];
|
|
772
|
+
for (const k of candidates) {
|
|
773
|
+
const got = pickFrom(obj[k]);
|
|
774
|
+
if (got) return got;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return null;
|
|
778
|
+
};
|
|
779
|
+
mediaUrl = pickFrom(data);
|
|
780
|
+
if (mediaUrl) return mediaUrl;
|
|
781
|
+
} catch (_) { }
|
|
782
|
+
break;
|
|
783
|
+
}
|
|
784
|
+
|
|
717
785
|
default: {
|
|
718
786
|
// Generic fallback for unknown platforms
|
|
719
787
|
try {
|
|
@@ -729,18 +797,18 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
729
797
|
break;
|
|
730
798
|
}
|
|
731
799
|
}
|
|
732
|
-
|
|
800
|
+
|
|
733
801
|
// If we got a video URL, validate it
|
|
734
802
|
if (videoUrl && videoUrl.includes("http")) {
|
|
735
803
|
// Validate the URL is accessible
|
|
736
804
|
try {
|
|
737
|
-
const response = await axios.head(videoUrl, {
|
|
805
|
+
const response = await axios.head(videoUrl, {
|
|
738
806
|
timeout: 10000,
|
|
739
807
|
headers: {
|
|
740
808
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
|
741
809
|
}
|
|
742
810
|
});
|
|
743
|
-
|
|
811
|
+
|
|
744
812
|
if (response.status === 200) {
|
|
745
813
|
return videoUrl;
|
|
746
814
|
} else {
|
|
@@ -752,7 +820,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
752
820
|
} else {
|
|
753
821
|
throw new Error("No valid video URL obtained from any fallback method");
|
|
754
822
|
}
|
|
755
|
-
|
|
823
|
+
|
|
756
824
|
} catch (error) {
|
|
757
825
|
if (attempt === maxRetries) {
|
|
758
826
|
throw new Error(`Enhanced fallback method failed after ${maxRetries} attempts for ${platform}: ${error.message}`);
|
|
@@ -785,7 +853,7 @@ const MediaDownloader = async (url, options = {}) => {
|
|
|
785
853
|
if (url.includes("threads.com")) {
|
|
786
854
|
throw new Error("Threads links are not supported for download. Download videos from Instagram, X, TikTok, and Facebook");
|
|
787
855
|
}
|
|
788
|
-
|
|
856
|
+
// Pinterest now supported
|
|
789
857
|
|
|
790
858
|
const blacklisted = blacklistLink(url);
|
|
791
859
|
if (blacklisted) {
|
|
@@ -799,9 +867,8 @@ const MediaDownloader = async (url, options = {}) => {
|
|
|
799
867
|
|
|
800
868
|
// Verificar se é YouTube e lançar erro customizado
|
|
801
869
|
const platform = getPlatformType(url);
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
}
|
|
870
|
+
// YouTube is now supported
|
|
871
|
+
|
|
805
872
|
|
|
806
873
|
await cleanupTempFiles(); // Clean up previous temp files
|
|
807
874
|
|
|
@@ -813,21 +880,21 @@ const MediaDownloader = async (url, options = {}) => {
|
|
|
813
880
|
downloadedFilePath = await downloadSmartVideo(url, config);
|
|
814
881
|
} catch (error) {
|
|
815
882
|
const platform = getPlatformType(url);
|
|
816
|
-
|
|
817
|
-
// YouTube doesn't use fallback method - it has its own specific implementation
|
|
883
|
+
|
|
884
|
+
// YouTube doesn't use fallback method - it has its own specific implementation in downloadSmartVideo
|
|
818
885
|
if (platform === "youtube") {
|
|
819
886
|
throw new Error(`YouTube download failed: ${error.message}`);
|
|
820
887
|
}
|
|
821
|
-
|
|
888
|
+
|
|
822
889
|
try {
|
|
823
890
|
const fallbackUrl = await tryFallbackDownload(url);
|
|
824
|
-
|
|
891
|
+
|
|
825
892
|
// Validate fallback URL
|
|
826
893
|
const isValid = await validateVideoUrl(fallbackUrl, platform);
|
|
827
894
|
if (!isValid) {
|
|
828
895
|
throw new Error("Fallback URL validation failed");
|
|
829
896
|
}
|
|
830
|
-
|
|
897
|
+
|
|
831
898
|
downloadedFilePath = await downloadDirectVideo(fallbackUrl, config);
|
|
832
899
|
} catch (fallbackError) {
|
|
833
900
|
throw new Error(`Failed to download video with both methods: ${fallbackError.message}`);
|
|
@@ -950,15 +1017,15 @@ async function downloadSmartVideo(url, config) {
|
|
|
950
1017
|
throw new Error("No video URLs found in Facebook response");
|
|
951
1018
|
}
|
|
952
1019
|
videoUrl = data.Normal_video || data.HD;
|
|
953
|
-
|
|
1020
|
+
|
|
954
1021
|
// Validate the URL is accessible
|
|
955
|
-
const response = await axios.head(videoUrl, {
|
|
1022
|
+
const response = await axios.head(videoUrl, {
|
|
956
1023
|
timeout: 10000,
|
|
957
1024
|
headers: {
|
|
958
1025
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
|
959
1026
|
}
|
|
960
1027
|
}).catch(() => null);
|
|
961
|
-
|
|
1028
|
+
|
|
962
1029
|
if (!response || response.status !== 200) {
|
|
963
1030
|
throw new Error("Facebook video URL is not accessible");
|
|
964
1031
|
}
|
|
@@ -979,17 +1046,63 @@ async function downloadSmartVideo(url, config) {
|
|
|
979
1046
|
}
|
|
980
1047
|
break;
|
|
981
1048
|
}
|
|
1049
|
+
case "pinterest": {
|
|
1050
|
+
try {
|
|
1051
|
+
// Supports both pin links and search queries
|
|
1052
|
+
const data = await pinterest(url);
|
|
1053
|
+
// data may be array or object depending on query or pin
|
|
1054
|
+
// Try to find best url (image or video)
|
|
1055
|
+
let directUrl = null;
|
|
1056
|
+
const pickFrom = (obj) => {
|
|
1057
|
+
if (!obj) return null;
|
|
1058
|
+
if (typeof obj === 'string' && obj.startsWith('http')) return obj;
|
|
1059
|
+
if (Array.isArray(obj)) {
|
|
1060
|
+
for (const it of obj) {
|
|
1061
|
+
const got = pickFrom(it);
|
|
1062
|
+
if (got) return got;
|
|
1063
|
+
}
|
|
1064
|
+
} else if (typeof obj === 'object') {
|
|
1065
|
+
const candidates = ['video', 'videoUrl', 'download', 'url', 'image', 'imageUrl', 'src', 'link'];
|
|
1066
|
+
for (const k of candidates) {
|
|
1067
|
+
const got = pickFrom(obj[k]);
|
|
1068
|
+
if (got) return got;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
return null;
|
|
1072
|
+
};
|
|
1073
|
+
directUrl = pickFrom(data);
|
|
1074
|
+
if (!directUrl) throw new Error("No media URL found in Pinterest response");
|
|
1075
|
+
|
|
1076
|
+
// HEAD validate
|
|
1077
|
+
const head = await axios.head(directUrl, { timeout: 10000, maxRedirects: 5 }).catch(() => null);
|
|
1078
|
+
if (!head || (head.status < 200 || head.status >= 400)) {
|
|
1079
|
+
throw new Error("Pinterest media URL not accessible");
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// If it's an image, download with generic file helper; if video, continue normal flow
|
|
1083
|
+
const ct = (head.headers && head.headers['content-type']) || '';
|
|
1084
|
+
if (ct.startsWith('image/')) {
|
|
1085
|
+
const filePath = await downloadGenericFile(directUrl);
|
|
1086
|
+
return filePath;
|
|
1087
|
+
} else {
|
|
1088
|
+
videoUrl = directUrl;
|
|
1089
|
+
}
|
|
1090
|
+
} catch (error) {
|
|
1091
|
+
throw new Error(`Pinterest download failed: ${error.message}`);
|
|
1092
|
+
}
|
|
1093
|
+
break;
|
|
1094
|
+
}
|
|
982
1095
|
case "twitter": {
|
|
983
1096
|
try {
|
|
984
1097
|
// Validate and clean the Twitter URL
|
|
985
1098
|
const cleanUrl = validateTwitterUrl(url);
|
|
986
1099
|
const data = await TwitterDL(cleanUrl, {});
|
|
987
|
-
|
|
1100
|
+
|
|
988
1101
|
if (data && data.result) {
|
|
989
1102
|
// Try multiple possible response structures
|
|
990
|
-
if (data.result.media && data.result.media[0] &&
|
|
991
|
-
|
|
992
|
-
|
|
1103
|
+
if (data.result.media && data.result.media[0] &&
|
|
1104
|
+
data.result.media[0].videos && data.result.media[0].videos[0] &&
|
|
1105
|
+
data.result.media[0].videos[0].url) {
|
|
993
1106
|
videoUrl = data.result.media[0].videos[0].url;
|
|
994
1107
|
} else if (data.result.video && data.result.video[0] && data.result.video[0].url) {
|
|
995
1108
|
videoUrl = data.result.video[0].url;
|
|
@@ -1000,8 +1113,8 @@ async function downloadSmartVideo(url, config) {
|
|
|
1000
1113
|
} else {
|
|
1001
1114
|
// Search for any video URL in the response
|
|
1002
1115
|
const findVideoUrl = (obj) => {
|
|
1003
|
-
if (typeof obj === 'string' && obj.includes('http') &&
|
|
1004
|
-
|
|
1116
|
+
if (typeof obj === 'string' && obj.includes('http') &&
|
|
1117
|
+
(obj.includes('.mp4') || obj.includes('video') || obj.includes('media'))) {
|
|
1005
1118
|
return obj;
|
|
1006
1119
|
}
|
|
1007
1120
|
if (typeof obj === 'object' && obj !== null) {
|
|
@@ -1012,7 +1125,7 @@ async function downloadSmartVideo(url, config) {
|
|
|
1012
1125
|
}
|
|
1013
1126
|
return null;
|
|
1014
1127
|
};
|
|
1015
|
-
|
|
1128
|
+
|
|
1016
1129
|
const foundUrl = findVideoUrl(data.result);
|
|
1017
1130
|
if (foundUrl) {
|
|
1018
1131
|
videoUrl = foundUrl;
|
|
@@ -1028,6 +1141,56 @@ async function downloadSmartVideo(url, config) {
|
|
|
1028
1141
|
}
|
|
1029
1142
|
break;
|
|
1030
1143
|
}
|
|
1144
|
+
case "youtube": {
|
|
1145
|
+
try {
|
|
1146
|
+
const data = await youtube(url);
|
|
1147
|
+
// Try to find the video URL in the response
|
|
1148
|
+
// Common patterns: data.url, data.mp4, data.link, or direct string
|
|
1149
|
+
|
|
1150
|
+
if (!data) throw new Error("No data returned from youtube downloader");
|
|
1151
|
+
|
|
1152
|
+
// Recursive function to find a valid video URL in the object
|
|
1153
|
+
const findVideoUrl = (obj) => {
|
|
1154
|
+
if (!obj) return null;
|
|
1155
|
+
if (typeof obj === 'string' && obj.startsWith('http')) return obj;
|
|
1156
|
+
|
|
1157
|
+
if (Array.isArray(obj)) {
|
|
1158
|
+
for (const item of obj) {
|
|
1159
|
+
const found = findVideoUrl(item);
|
|
1160
|
+
if (found) return found;
|
|
1161
|
+
}
|
|
1162
|
+
} else if (typeof obj === 'object') {
|
|
1163
|
+
// Prioritize certain keys
|
|
1164
|
+
const priorities = ['url', 'link', 'download', 'mp4', 'video', 'src'];
|
|
1165
|
+
for (const key of priorities) {
|
|
1166
|
+
if (obj[key]) {
|
|
1167
|
+
const found = findVideoUrl(obj[key]);
|
|
1168
|
+
if (found) return found;
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
// If not found in priorities, check all keys
|
|
1172
|
+
for (const key of Object.keys(obj)) {
|
|
1173
|
+
if (!priorities.includes(key)) {
|
|
1174
|
+
const found = findVideoUrl(obj[key]);
|
|
1175
|
+
if (found) return found;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
return null;
|
|
1180
|
+
};
|
|
1181
|
+
|
|
1182
|
+
videoUrl = findVideoUrl(data);
|
|
1183
|
+
|
|
1184
|
+
if (!videoUrl) {
|
|
1185
|
+
console.log('YouTube data dump:', JSON.stringify(data, null, 2)); // Debug log since we can't test
|
|
1186
|
+
throw new Error("Could not extract video URL from YouTube response");
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
} catch (error) {
|
|
1190
|
+
throw new Error(`YouTube download failed: ${error.message}`);
|
|
1191
|
+
}
|
|
1192
|
+
break;
|
|
1193
|
+
}
|
|
1031
1194
|
default:
|
|
1032
1195
|
throw new Error("Platform not supported or invalid link.");
|
|
1033
1196
|
}
|
|
@@ -1035,21 +1198,21 @@ async function downloadSmartVideo(url, config) {
|
|
|
1035
1198
|
if (!videoUrl || !videoUrl.includes("http")) {
|
|
1036
1199
|
throw new Error("Returned video URL is invalid or unavailable.");
|
|
1037
1200
|
}
|
|
1038
|
-
|
|
1201
|
+
|
|
1039
1202
|
// Download the video with better error handling
|
|
1040
|
-
const response = await axios({
|
|
1041
|
-
url: videoUrl,
|
|
1042
|
-
method: "GET",
|
|
1203
|
+
const response = await axios({
|
|
1204
|
+
url: videoUrl,
|
|
1205
|
+
method: "GET",
|
|
1043
1206
|
responseType: "stream",
|
|
1044
1207
|
timeout: 30000,
|
|
1045
1208
|
headers: {
|
|
1046
1209
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
|
1047
1210
|
}
|
|
1048
1211
|
});
|
|
1049
|
-
|
|
1212
|
+
|
|
1050
1213
|
// Create minimal unique file name in output dir
|
|
1051
|
-
let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0,4)}.mp4`);
|
|
1052
|
-
|
|
1214
|
+
let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0, 4)}.mp4`);
|
|
1215
|
+
|
|
1053
1216
|
const videoWriter = fs.createWriteStream(fileName);
|
|
1054
1217
|
response.data.pipe(videoWriter);
|
|
1055
1218
|
|
|
@@ -1074,19 +1237,19 @@ async function downloadSmartVideo(url, config) {
|
|
|
1074
1237
|
// Function for direct video download
|
|
1075
1238
|
async function downloadDirectVideo(url, config) {
|
|
1076
1239
|
try {
|
|
1077
|
-
const response = await axios({
|
|
1078
|
-
url: url,
|
|
1079
|
-
method: "GET",
|
|
1240
|
+
const response = await axios({
|
|
1241
|
+
url: url,
|
|
1242
|
+
method: "GET",
|
|
1080
1243
|
responseType: "stream",
|
|
1081
1244
|
timeout: 30000,
|
|
1082
1245
|
headers: {
|
|
1083
1246
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
|
1084
1247
|
}
|
|
1085
1248
|
});
|
|
1086
|
-
|
|
1249
|
+
|
|
1087
1250
|
// Create minimal unique file name in output dir
|
|
1088
|
-
let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0,4)}.mp4`);
|
|
1089
|
-
|
|
1251
|
+
let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0, 4)}.mp4`);
|
|
1252
|
+
|
|
1090
1253
|
const videoWriter = fs.createWriteStream(fileName);
|
|
1091
1254
|
response.data.pipe(videoWriter);
|
|
1092
1255
|
|
|
@@ -1134,7 +1297,7 @@ async function downloadGenericFile(url, preferredExt = null) {
|
|
|
1134
1297
|
else ext = 'bin';
|
|
1135
1298
|
}
|
|
1136
1299
|
|
|
1137
|
-
const fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0,4)}.${ext}`);
|
|
1300
|
+
const fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0, 4)}.${ext}`);
|
|
1138
1301
|
const writer = fs.createWriteStream(fileName);
|
|
1139
1302
|
response.data.pipe(writer);
|
|
1140
1303
|
|
|
@@ -1149,7 +1312,7 @@ async function downloadGenericFile(url, preferredExt = null) {
|
|
|
1149
1312
|
|
|
1150
1313
|
// Function to rotate video
|
|
1151
1314
|
async function rotateVideo(fileName, rotation) {
|
|
1152
|
-
const outputPath = path.join(OUTPUT_DIR, `vr_${uuidv4().slice(0,4)}.mp4`);
|
|
1315
|
+
const outputPath = path.join(OUTPUT_DIR, `vr_${uuidv4().slice(0, 4)}.mp4`);
|
|
1153
1316
|
let angle;
|
|
1154
1317
|
switch (rotation.toLowerCase()) {
|
|
1155
1318
|
case "left": angle = "transpose=2"; break;
|
|
@@ -1200,8 +1363,8 @@ async function cleanupTempFiles() {
|
|
|
1200
1363
|
try {
|
|
1201
1364
|
if (!fs.existsSync(TEMP_DIR)) return;
|
|
1202
1365
|
const files = fs.readdirSync(TEMP_DIR);
|
|
1203
|
-
const tempFiles = files.filter(file =>
|
|
1204
|
-
/^temp_video.*\.mp4$/.test(file) ||
|
|
1366
|
+
const tempFiles = files.filter(file =>
|
|
1367
|
+
/^temp_video.*\.mp4$/.test(file) ||
|
|
1205
1368
|
/_audio\.mp3$/.test(file) ||
|
|
1206
1369
|
/_rotated\.mp4$/.test(file) ||
|
|
1207
1370
|
/_cropped\.mp4$/.test(file) ||
|
|
@@ -1233,9 +1396,9 @@ async function cleanupOutputFiles() {
|
|
|
1233
1396
|
if (now - st.mtimeMs > maxAgeMs) {
|
|
1234
1397
|
await safeUnlinkWithRetry(full);
|
|
1235
1398
|
}
|
|
1236
|
-
} catch (_) {}
|
|
1399
|
+
} catch (_) { }
|
|
1237
1400
|
}
|
|
1238
|
-
} catch (_) {}
|
|
1401
|
+
} catch (_) { }
|
|
1239
1402
|
}
|
|
1240
1403
|
|
|
1241
1404
|
// Schedule periodic cleanup (every 10 minutes)
|
|
@@ -1249,7 +1412,7 @@ cleanupOutputFiles();
|
|
|
1249
1412
|
// Function to auto-crop video
|
|
1250
1413
|
async function autoCrop(fileName) {
|
|
1251
1414
|
const inputPath = fileName;
|
|
1252
|
-
const outputPath = path.join(OUTPUT_DIR, `vc_${uuidv4().slice(0,4)}.mp4`);
|
|
1415
|
+
const outputPath = path.join(OUTPUT_DIR, `vc_${uuidv4().slice(0, 4)}.mp4`);
|
|
1253
1416
|
|
|
1254
1417
|
return new Promise((resolve, reject) => {
|
|
1255
1418
|
let cropValues = null;
|
|
@@ -1257,13 +1420,13 @@ async function autoCrop(fileName) {
|
|
|
1257
1420
|
.outputOptions('-vf', 'cropdetect=24:16:0')
|
|
1258
1421
|
.outputFormat('null')
|
|
1259
1422
|
.output('-')
|
|
1260
|
-
.on('stderr', function(stderrLine) {
|
|
1423
|
+
.on('stderr', function (stderrLine) {
|
|
1261
1424
|
const cropMatch = stderrLine.match(/crop=([0-9]+):([0-9]+):([0-9]+):([0-9]+)/);
|
|
1262
1425
|
if (cropMatch) {
|
|
1263
1426
|
cropValues = `crop=${cropMatch[1]}:${cropMatch[2]}:${cropMatch[3]}:${cropMatch[4]}`;
|
|
1264
1427
|
}
|
|
1265
1428
|
})
|
|
1266
|
-
.on('end', function() {
|
|
1429
|
+
.on('end', function () {
|
|
1267
1430
|
if (!cropValues) {
|
|
1268
1431
|
resolve(inputPath);
|
|
1269
1432
|
return;
|
|
@@ -1296,7 +1459,7 @@ async function checkAndCompressVideo(filePath, limitSizeMB, platform = null) {
|
|
|
1296
1459
|
return filePath;
|
|
1297
1460
|
}
|
|
1298
1461
|
|
|
1299
|
-
const outputPath = path.join(OUTPUT_DIR, `vx_${uuidv4().slice(0,4)}.mp4`);
|
|
1462
|
+
const outputPath = path.join(OUTPUT_DIR, `vx_${uuidv4().slice(0, 4)}.mp4`);
|
|
1300
1463
|
|
|
1301
1464
|
return new Promise((resolve, reject) => {
|
|
1302
1465
|
ffmpeg(filePath)
|
|
@@ -1331,17 +1494,17 @@ async function convertVideoFormat(inputPath, targetFormat) {
|
|
|
1331
1494
|
const supported = ["mp4", "mov", "webm", "mkv"];
|
|
1332
1495
|
if (!supported.includes(fmt)) return inputPath;
|
|
1333
1496
|
|
|
1334
|
-
const outputPath = path.join(OUTPUT_DIR, `vf_${uuidv4().slice(0,4)}.${fmt}`);
|
|
1497
|
+
const outputPath = path.join(OUTPUT_DIR, `vf_${uuidv4().slice(0, 4)}.${fmt}`);
|
|
1335
1498
|
|
|
1336
1499
|
const ff = ffmpeg(inputPath);
|
|
1337
1500
|
switch (fmt) {
|
|
1338
1501
|
case "mp4":
|
|
1339
1502
|
case "mov":
|
|
1340
1503
|
case "mkv":
|
|
1341
|
-
ff.outputOptions("-c:v","libx264","-pix_fmt","yuv420p","-c:a","aac","-b:a","192k");
|
|
1504
|
+
ff.outputOptions("-c:v", "libx264", "-pix_fmt", "yuv420p", "-c:a", "aac", "-b:a", "192k");
|
|
1342
1505
|
break;
|
|
1343
1506
|
case "webm":
|
|
1344
|
-
ff.outputOptions("-c:v","libvpx-vp9","-b:v","0","-crf","30","-c:a","libopus","-b:a","128k");
|
|
1507
|
+
ff.outputOptions("-c:v", "libvpx-vp9", "-b:v", "0", "-crf", "30", "-c:a", "libopus", "-b:a", "128k");
|
|
1345
1508
|
break;
|
|
1346
1509
|
default:
|
|
1347
1510
|
return inputPath;
|
|
@@ -1378,8 +1541,10 @@ function getFileName(url) {
|
|
|
1378
1541
|
// Function to unshorten URLs
|
|
1379
1542
|
async function unshortenUrl(url) {
|
|
1380
1543
|
try {
|
|
1544
|
+
// Special handling for Facebook and Pinterest short-links
|
|
1381
1545
|
if (
|
|
1382
|
-
url.includes('facebook.com') || url.includes('fb.watch') || url.includes('fb.com')
|
|
1546
|
+
url.includes('facebook.com') || url.includes('fb.watch') || url.includes('fb.com') ||
|
|
1547
|
+
url.includes('pi1231231n.it') || url.includes('pint123123erest.com')
|
|
1383
1548
|
) {
|
|
1384
1549
|
const response = await axios.get(url, {
|
|
1385
1550
|
maxRedirects: 10,
|
|
@@ -1396,14 +1561,14 @@ async function unshortenUrl(url) {
|
|
|
1396
1561
|
return status >= 200 && status < 400; // Accept redirects
|
|
1397
1562
|
}
|
|
1398
1563
|
});
|
|
1399
|
-
|
|
1564
|
+
|
|
1400
1565
|
// Get the final URL after all redirects
|
|
1401
1566
|
const finalUrl = response.request.res.responseUrl || response.config.url;
|
|
1402
1567
|
return finalUrl;
|
|
1403
1568
|
}
|
|
1404
|
-
|
|
1569
|
+
|
|
1405
1570
|
// For other URLs, use the original method
|
|
1406
|
-
const response = await axios.head(url, {
|
|
1571
|
+
const response = await axios.head(url, {
|
|
1407
1572
|
maxRedirects: 10,
|
|
1408
1573
|
timeout: 10000,
|
|
1409
1574
|
headers: {
|
|
@@ -1434,7 +1599,7 @@ const AudioDownloader = async (url, options = {}) => {
|
|
|
1434
1599
|
|
|
1435
1600
|
// Verificar se o link está na lista de plataformas suportadas
|
|
1436
1601
|
if (!isVideoLink(url)) {
|
|
1437
|
-
throw new Error("This URL is not from a supported platform. Supported platforms: Instagram, X(Twitter), TikTok, Facebook");
|
|
1602
|
+
throw new Error("This URL is not from a supported platform. Supported platforms: Instagram, X(Twitter), TikTok, Facebook, and YouTube");
|
|
1438
1603
|
}
|
|
1439
1604
|
|
|
1440
1605
|
await cleanupTempFiles(); // Clean up previous temp files
|
|
@@ -1472,10 +1637,10 @@ const AudioDownloader = async (url, options = {}) => {
|
|
|
1472
1637
|
if (downloadedFilePath) {
|
|
1473
1638
|
// Extrair áudio em mp3
|
|
1474
1639
|
audioFilePath = await extractAudioMp3(downloadedFilePath);
|
|
1475
|
-
|
|
1640
|
+
|
|
1476
1641
|
// Remove o arquivo de vídeo temporário após extrair o áudio
|
|
1477
1642
|
await safeUnlinkWithRetry(downloadedFilePath);
|
|
1478
|
-
|
|
1643
|
+
|
|
1479
1644
|
const result = await uploadToGoFileIfNeeded(audioFilePath);
|
|
1480
1645
|
return result;
|
|
1481
1646
|
} else {
|
|
@@ -1491,7 +1656,7 @@ const AudioDownloader = async (url, options = {}) => {
|
|
|
1491
1656
|
// Função para extrair áudio em mp3 usando ffmpeg
|
|
1492
1657
|
async function extractAudioMp3(videoPath) {
|
|
1493
1658
|
return new Promise((resolve, reject) => {
|
|
1494
|
-
const audioPath = path.join(OUTPUT_DIR, `a_${uuidv4().slice(0,4)}.mp3`);
|
|
1659
|
+
const audioPath = path.join(OUTPUT_DIR, `a_${uuidv4().slice(0, 4)}.mp3`);
|
|
1495
1660
|
ffmpeg(videoPath)
|
|
1496
1661
|
.noVideo()
|
|
1497
1662
|
.audioCodec('libmp3lame')
|