frostpv 1.0.11 → 1.0.13
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 +139 -141
- 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, pinterest } = require("btch-downloader");
|
|
7
|
+
const { igdl, ttdl, fbdown, mediafire, capcut, gdrive, pinterest, twitter } = 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
|
|
|
@@ -73,7 +73,7 @@ const isVideoLink = (link) => {
|
|
|
73
73
|
const u = new URL(link);
|
|
74
74
|
const host = (u.hostname || '').toLowerCase();
|
|
75
75
|
if (host.endsWith('pintere21313213st.com')) return true;
|
|
76
|
-
} catch (_) {}
|
|
76
|
+
} catch (_) { }
|
|
77
77
|
return videoPlatforms.some(platform => link.startsWith(platform));
|
|
78
78
|
};
|
|
79
79
|
|
|
@@ -146,14 +146,14 @@ async function safeUnlinkWithRetry(filePath, maxRetries = 3) {
|
|
|
146
146
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
147
147
|
try {
|
|
148
148
|
const resolvedPath = path.resolve(filePath);
|
|
149
|
-
|
|
149
|
+
|
|
150
150
|
if (fs.existsSync(resolvedPath)) {
|
|
151
151
|
// Check if file is in use (Windows)
|
|
152
152
|
if (process.platform === 'win32' && isFileInUse(resolvedPath)) {
|
|
153
153
|
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
|
|
154
154
|
continue;
|
|
155
155
|
}
|
|
156
|
-
|
|
156
|
+
|
|
157
157
|
fs.rmSync(resolvedPath, { force: true });
|
|
158
158
|
return true;
|
|
159
159
|
} else {
|
|
@@ -225,7 +225,7 @@ async function validateVideoUrl(url, platform) {
|
|
|
225
225
|
return status >= 200 && status < 400; // Accept redirects
|
|
226
226
|
}
|
|
227
227
|
});
|
|
228
|
-
|
|
228
|
+
|
|
229
229
|
return true;
|
|
230
230
|
} catch (error) {
|
|
231
231
|
return false;
|
|
@@ -237,18 +237,18 @@ function validateTwitterUrl(url) {
|
|
|
237
237
|
try {
|
|
238
238
|
// Remove query parameters that might cause issues
|
|
239
239
|
const cleanUrl = url.split('?')[0];
|
|
240
|
-
|
|
240
|
+
|
|
241
241
|
// Ensure it's a valid Twitter/X URL format
|
|
242
242
|
if (!cleanUrl.includes('x.com') && !cleanUrl.includes('twitter.com')) {
|
|
243
243
|
throw new Error("Not a valid Twitter/X URL");
|
|
244
244
|
}
|
|
245
|
-
|
|
245
|
+
|
|
246
246
|
// Check if it has the required structure
|
|
247
247
|
const urlParts = cleanUrl.split('/');
|
|
248
248
|
if (urlParts.length < 5) {
|
|
249
249
|
throw new Error("Invalid Twitter/X URL structure");
|
|
250
250
|
}
|
|
251
|
-
|
|
251
|
+
|
|
252
252
|
return cleanUrl;
|
|
253
253
|
} catch (error) {
|
|
254
254
|
throw new Error(`Twitter URL validation failed: ${error.message}`);
|
|
@@ -287,10 +287,10 @@ async function downloadYoutubeAudio(url, outputPath) {
|
|
|
287
287
|
try {
|
|
288
288
|
const ytdlp = new YtDlp({ ffmpegPath });
|
|
289
289
|
const cookiesPath = getYoutubeCookiesPath();
|
|
290
|
-
|
|
290
|
+
|
|
291
291
|
// Garantir que o nome do arquivo tenha a extensão correta
|
|
292
292
|
const baseName = outputPath.replace(/\.[^/.]+$/, ""); // Remove extensão se existir
|
|
293
|
-
|
|
293
|
+
|
|
294
294
|
// Primeiro, tentar com a API de opções
|
|
295
295
|
try {
|
|
296
296
|
const options = {
|
|
@@ -301,11 +301,11 @@ async function downloadYoutubeAudio(url, outputPath) {
|
|
|
301
301
|
},
|
|
302
302
|
output: outputPath
|
|
303
303
|
};
|
|
304
|
-
|
|
304
|
+
|
|
305
305
|
if (cookiesPath) {
|
|
306
306
|
options.cookies = cookiesPath;
|
|
307
307
|
}
|
|
308
|
-
|
|
308
|
+
|
|
309
309
|
// Remover logs detalhados para download de áudio do YouTube
|
|
310
310
|
const result = await ytdlp.downloadAsync(url, options);
|
|
311
311
|
// Verificar se o arquivo foi criado
|
|
@@ -314,7 +314,7 @@ async function downloadYoutubeAudio(url, outputPath) {
|
|
|
314
314
|
resolve(actualFileName);
|
|
315
315
|
return;
|
|
316
316
|
}
|
|
317
|
-
|
|
317
|
+
|
|
318
318
|
// Tentar encontrar o arquivo com extensão diferente
|
|
319
319
|
const files = fs.readdirSync('./');
|
|
320
320
|
const downloadedFile = files.find(file => file.startsWith(baseName.split('/').pop()));
|
|
@@ -325,18 +325,18 @@ async function downloadYoutubeAudio(url, outputPath) {
|
|
|
325
325
|
} catch (apiError) {
|
|
326
326
|
console.log('API method failed, trying direct args method:', apiError.message);
|
|
327
327
|
}
|
|
328
|
-
|
|
328
|
+
|
|
329
329
|
// Fallback: usar argumentos diretos para máxima qualidade de áudio
|
|
330
330
|
const args = [
|
|
331
331
|
url,
|
|
332
332
|
'-f', 'bestaudio[ext=mp3]/bestaudio',
|
|
333
333
|
'-o', outputPath
|
|
334
334
|
];
|
|
335
|
-
|
|
335
|
+
|
|
336
336
|
if (cookiesPath) {
|
|
337
337
|
args.push('--cookies', cookiesPath);
|
|
338
338
|
}
|
|
339
|
-
|
|
339
|
+
|
|
340
340
|
// Remover logs detalhados para download de áudio do YouTube
|
|
341
341
|
const result = await ytdlp.execAsync(args);
|
|
342
342
|
// Verificar se o arquivo foi criado
|
|
@@ -363,25 +363,25 @@ async function downloadYoutubeAudio(url, outputPath) {
|
|
|
363
363
|
// Enhanced function to get platform type from URL
|
|
364
364
|
function getPlatformType(url) {
|
|
365
365
|
const lowerUrl = url.toLowerCase();
|
|
366
|
-
|
|
366
|
+
|
|
367
367
|
if (lowerUrl.includes("instagram.com") || lowerUrl.includes("instagr.am")) return "instagram";
|
|
368
368
|
if (lowerUrl.includes("tiktok.com") || lowerUrl.includes("vm.tiktok.com") || lowerUrl.includes("vt.tiktok.com")) return "tiktok";
|
|
369
369
|
if (lowerUrl.includes("facebook.com") || lowerUrl.includes("fb.watch") || lowerUrl.includes("fb.com")) return "facebook";
|
|
370
370
|
if (lowerUrl.includes("mediafire.com")) return "mediafire";
|
|
371
371
|
if (lowerUrl.includes("x.com") || lowerUrl.includes("twitter.com")) return "twitter";
|
|
372
372
|
if (lowerUrl.includes("youtube.com") || lowerUrl.includes("you112t12u.be") || lowerUrl.includes("m.y11outu314be.com")) return "youtu1354be";
|
|
373
|
-
|
|
373
|
+
|
|
374
374
|
return "unknown";
|
|
375
375
|
}
|
|
376
376
|
|
|
377
377
|
// Enhanced fallback download function with platform-specific methods
|
|
378
378
|
async function tryFallbackDownload(url, maxRetries = 3) {
|
|
379
379
|
const platform = getPlatformType(url);
|
|
380
|
-
|
|
380
|
+
|
|
381
381
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
382
382
|
try {
|
|
383
383
|
let videoUrl = null;
|
|
384
|
-
|
|
384
|
+
|
|
385
385
|
// Platform-specific fallback methods
|
|
386
386
|
switch (platform) {
|
|
387
387
|
case "instagram": {
|
|
@@ -427,7 +427,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
427
427
|
}
|
|
428
428
|
}
|
|
429
429
|
];
|
|
430
|
-
|
|
430
|
+
|
|
431
431
|
for (const method of methods) {
|
|
432
432
|
try {
|
|
433
433
|
videoUrl = await method();
|
|
@@ -438,7 +438,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
438
438
|
}
|
|
439
439
|
break;
|
|
440
440
|
}
|
|
441
|
-
|
|
441
|
+
|
|
442
442
|
case "tiktok": {
|
|
443
443
|
// Try multiple TikTok methods
|
|
444
444
|
const methods = [
|
|
@@ -473,7 +473,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
473
473
|
throw new Error("No valid video in Tiktok.Downloader v2 response");
|
|
474
474
|
}
|
|
475
475
|
];
|
|
476
|
-
|
|
476
|
+
|
|
477
477
|
for (const method of methods) {
|
|
478
478
|
try {
|
|
479
479
|
videoUrl = await method();
|
|
@@ -484,7 +484,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
484
484
|
}
|
|
485
485
|
break;
|
|
486
486
|
}
|
|
487
|
-
|
|
487
|
+
|
|
488
488
|
case "facebook": {
|
|
489
489
|
// Try multiple Facebook methods
|
|
490
490
|
const methods = [
|
|
@@ -508,7 +508,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
508
508
|
throw new Error("No valid video in fbdown response with custom headers");
|
|
509
509
|
}
|
|
510
510
|
];
|
|
511
|
-
|
|
511
|
+
|
|
512
512
|
for (const method of methods) {
|
|
513
513
|
try {
|
|
514
514
|
videoUrl = await method();
|
|
@@ -519,42 +519,42 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
519
519
|
}
|
|
520
520
|
break;
|
|
521
521
|
}
|
|
522
|
-
|
|
522
|
+
|
|
523
523
|
case "twitter": {
|
|
524
524
|
// Try multiple Twitter methods with better response handling
|
|
525
525
|
const methods = [
|
|
526
526
|
async () => {
|
|
527
527
|
const cleanUrl = validateTwitterUrl(url);
|
|
528
528
|
const data = await TwitterDL(cleanUrl, {});
|
|
529
|
-
|
|
529
|
+
|
|
530
530
|
// Check multiple possible response structures
|
|
531
531
|
if (data && data.result) {
|
|
532
532
|
// Structure 1: data.result.media[0].videos[0].url
|
|
533
|
-
if (data.result.media && data.result.media[0] &&
|
|
534
|
-
|
|
535
|
-
|
|
533
|
+
if (data.result.media && data.result.media[0] &&
|
|
534
|
+
data.result.media[0].videos && data.result.media[0].videos[0] &&
|
|
535
|
+
data.result.media[0].videos[0].url) {
|
|
536
536
|
return data.result.media[0].videos[0].url;
|
|
537
537
|
}
|
|
538
|
-
|
|
538
|
+
|
|
539
539
|
// Structure 2: data.result.video[0].url
|
|
540
540
|
if (data.result.video && data.result.video[0] && data.result.video[0].url) {
|
|
541
541
|
return data.result.video[0].url;
|
|
542
542
|
}
|
|
543
|
-
|
|
543
|
+
|
|
544
544
|
// Structure 3: data.result.url (direct URL)
|
|
545
545
|
if (data.result.url) {
|
|
546
546
|
return data.result.url;
|
|
547
547
|
}
|
|
548
|
-
|
|
548
|
+
|
|
549
549
|
// Structure 4: data.result.media[0].url
|
|
550
550
|
if (data.result.media && data.result.media[0] && data.result.media[0].url) {
|
|
551
551
|
return data.result.media[0].url;
|
|
552
552
|
}
|
|
553
|
-
|
|
553
|
+
|
|
554
554
|
// Structure 5: Check for any video URL in the entire response
|
|
555
555
|
const findVideoUrl = (obj) => {
|
|
556
|
-
if (typeof obj === 'string' && obj.includes('http') &&
|
|
557
|
-
|
|
556
|
+
if (typeof obj === 'string' && obj.includes('http') &&
|
|
557
|
+
(obj.includes('.mp4') || obj.includes('video') || obj.includes('media'))) {
|
|
558
558
|
return obj;
|
|
559
559
|
}
|
|
560
560
|
if (typeof obj === 'object' && obj !== null) {
|
|
@@ -565,13 +565,13 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
565
565
|
}
|
|
566
566
|
return null;
|
|
567
567
|
};
|
|
568
|
-
|
|
568
|
+
|
|
569
569
|
const foundUrl = findVideoUrl(data.result);
|
|
570
570
|
if (foundUrl) {
|
|
571
571
|
return foundUrl;
|
|
572
572
|
}
|
|
573
573
|
}
|
|
574
|
-
|
|
574
|
+
|
|
575
575
|
throw new Error("No valid video URL found in TwitterDL response structure");
|
|
576
576
|
},
|
|
577
577
|
async () => {
|
|
@@ -587,30 +587,30 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
587
587
|
'Upgrade-Insecure-Requests': '1'
|
|
588
588
|
}
|
|
589
589
|
});
|
|
590
|
-
|
|
590
|
+
|
|
591
591
|
// Same structure checking as above
|
|
592
592
|
if (data && data.result) {
|
|
593
|
-
if (data.result.media && data.result.media[0] &&
|
|
594
|
-
|
|
595
|
-
|
|
593
|
+
if (data.result.media && data.result.media[0] &&
|
|
594
|
+
data.result.media[0].videos && data.result.media[0].videos[0] &&
|
|
595
|
+
data.result.media[0].videos[0].url) {
|
|
596
596
|
return data.result.media[0].videos[0].url;
|
|
597
597
|
}
|
|
598
|
-
|
|
598
|
+
|
|
599
599
|
if (data.result.video && data.result.video[0] && data.result.video[0].url) {
|
|
600
600
|
return data.result.video[0].url;
|
|
601
601
|
}
|
|
602
|
-
|
|
602
|
+
|
|
603
603
|
if (data.result.url) {
|
|
604
604
|
return data.result.url;
|
|
605
605
|
}
|
|
606
|
-
|
|
606
|
+
|
|
607
607
|
if (data.result.media && data.result.media[0] && data.result.media[0].url) {
|
|
608
608
|
return data.result.media[0].url;
|
|
609
609
|
}
|
|
610
|
-
|
|
610
|
+
|
|
611
611
|
const findVideoUrl = (obj) => {
|
|
612
|
-
if (typeof obj === 'string' && obj.includes('http') &&
|
|
613
|
-
|
|
612
|
+
if (typeof obj === 'string' && obj.includes('http') &&
|
|
613
|
+
(obj.includes('.mp4') || obj.includes('video') || obj.includes('media'))) {
|
|
614
614
|
return obj;
|
|
615
615
|
}
|
|
616
616
|
if (typeof obj === 'object' && obj !== null) {
|
|
@@ -621,13 +621,13 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
621
621
|
}
|
|
622
622
|
return null;
|
|
623
623
|
};
|
|
624
|
-
|
|
624
|
+
|
|
625
625
|
const foundUrl = findVideoUrl(data.result);
|
|
626
626
|
if (foundUrl) {
|
|
627
627
|
return foundUrl;
|
|
628
628
|
}
|
|
629
629
|
}
|
|
630
|
-
|
|
630
|
+
|
|
631
631
|
throw new Error("No valid video URL found in TwitterDL response with custom headers");
|
|
632
632
|
},
|
|
633
633
|
async () => {
|
|
@@ -663,9 +663,9 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
663
663
|
const data = await btchOld[funcName](cleanUrl);
|
|
664
664
|
// Check multiple possible response structures
|
|
665
665
|
if (data && data.result) {
|
|
666
|
-
if (data.result.media && data.result.media[0] &&
|
|
667
|
-
|
|
668
|
-
|
|
666
|
+
if (data.result.media && data.result.media[0] &&
|
|
667
|
+
data.result.media[0].videos && data.result.media[0].videos[0] &&
|
|
668
|
+
data.result.media[0].videos[0].url) {
|
|
669
669
|
return data.result.media[0].videos[0].url;
|
|
670
670
|
}
|
|
671
671
|
if (data.result.video && data.result.video[0] && data.result.video[0].url) {
|
|
@@ -692,7 +692,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
692
692
|
}
|
|
693
693
|
}
|
|
694
694
|
];
|
|
695
|
-
|
|
695
|
+
|
|
696
696
|
for (const method of methods) {
|
|
697
697
|
try {
|
|
698
698
|
videoUrl = await method();
|
|
@@ -703,12 +703,12 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
703
703
|
}
|
|
704
704
|
break;
|
|
705
705
|
}
|
|
706
|
-
|
|
706
|
+
|
|
707
707
|
case "youtube": {
|
|
708
708
|
// YouTube doesn't need fallback - it has its own specific method
|
|
709
709
|
throw new Error("YouTube downloads should use the primary method, not fallback");
|
|
710
710
|
}
|
|
711
|
-
|
|
711
|
+
|
|
712
712
|
case "mediafire": {
|
|
713
713
|
// Try MediaFire method
|
|
714
714
|
try {
|
|
@@ -746,10 +746,10 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
746
746
|
};
|
|
747
747
|
mediaUrl = pickFrom(data);
|
|
748
748
|
if (mediaUrl) return mediaUrl;
|
|
749
|
-
} catch (_) {}
|
|
749
|
+
} catch (_) { }
|
|
750
750
|
break;
|
|
751
751
|
}
|
|
752
|
-
|
|
752
|
+
|
|
753
753
|
default: {
|
|
754
754
|
// Generic fallback for unknown platforms
|
|
755
755
|
try {
|
|
@@ -765,18 +765,18 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
765
765
|
break;
|
|
766
766
|
}
|
|
767
767
|
}
|
|
768
|
-
|
|
768
|
+
|
|
769
769
|
// If we got a video URL, validate it
|
|
770
770
|
if (videoUrl && videoUrl.includes("http")) {
|
|
771
771
|
// Validate the URL is accessible
|
|
772
772
|
try {
|
|
773
|
-
const response = await axios.head(videoUrl, {
|
|
773
|
+
const response = await axios.head(videoUrl, {
|
|
774
774
|
timeout: 10000,
|
|
775
775
|
headers: {
|
|
776
776
|
'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'
|
|
777
777
|
}
|
|
778
778
|
});
|
|
779
|
-
|
|
779
|
+
|
|
780
780
|
if (response.status === 200) {
|
|
781
781
|
return videoUrl;
|
|
782
782
|
} else {
|
|
@@ -788,7 +788,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
788
788
|
} else {
|
|
789
789
|
throw new Error("No valid video URL obtained from any fallback method");
|
|
790
790
|
}
|
|
791
|
-
|
|
791
|
+
|
|
792
792
|
} catch (error) {
|
|
793
793
|
if (attempt === maxRetries) {
|
|
794
794
|
throw new Error(`Enhanced fallback method failed after ${maxRetries} attempts for ${platform}: ${error.message}`);
|
|
@@ -849,21 +849,21 @@ const MediaDownloader = async (url, options = {}) => {
|
|
|
849
849
|
downloadedFilePath = await downloadSmartVideo(url, config);
|
|
850
850
|
} catch (error) {
|
|
851
851
|
const platform = getPlatformType(url);
|
|
852
|
-
|
|
852
|
+
|
|
853
853
|
// YouTube doesn't use fallback method - it has its own specific implementation
|
|
854
854
|
if (platform === "youtube") {
|
|
855
855
|
throw new Error(`YouTube download failed: ${error.message}`);
|
|
856
856
|
}
|
|
857
|
-
|
|
857
|
+
|
|
858
858
|
try {
|
|
859
859
|
const fallbackUrl = await tryFallbackDownload(url);
|
|
860
|
-
|
|
860
|
+
|
|
861
861
|
// Validate fallback URL
|
|
862
862
|
const isValid = await validateVideoUrl(fallbackUrl, platform);
|
|
863
863
|
if (!isValid) {
|
|
864
864
|
throw new Error("Fallback URL validation failed");
|
|
865
865
|
}
|
|
866
|
-
|
|
866
|
+
|
|
867
867
|
downloadedFilePath = await downloadDirectVideo(fallbackUrl, config);
|
|
868
868
|
} catch (fallbackError) {
|
|
869
869
|
throw new Error(`Failed to download video with both methods: ${fallbackError.message}`);
|
|
@@ -986,15 +986,15 @@ async function downloadSmartVideo(url, config) {
|
|
|
986
986
|
throw new Error("No video URLs found in Facebook response");
|
|
987
987
|
}
|
|
988
988
|
videoUrl = data.Normal_video || data.HD;
|
|
989
|
-
|
|
989
|
+
|
|
990
990
|
// Validate the URL is accessible
|
|
991
|
-
const response = await axios.head(videoUrl, {
|
|
991
|
+
const response = await axios.head(videoUrl, {
|
|
992
992
|
timeout: 10000,
|
|
993
993
|
headers: {
|
|
994
994
|
'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'
|
|
995
995
|
}
|
|
996
996
|
}).catch(() => null);
|
|
997
|
-
|
|
997
|
+
|
|
998
998
|
if (!response || response.status !== 200) {
|
|
999
999
|
throw new Error("Facebook video URL is not accessible");
|
|
1000
1000
|
}
|
|
@@ -1063,47 +1063,45 @@ async function downloadSmartVideo(url, config) {
|
|
|
1063
1063
|
}
|
|
1064
1064
|
case "twitter": {
|
|
1065
1065
|
try {
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
data.result.media[0].videos[0].url) {
|
|
1075
|
-
videoUrl = data.result.media[0].videos[0].url;
|
|
1076
|
-
} else if (data.result.video && data.result.video[0] && data.result.video[0].url) {
|
|
1077
|
-
videoUrl = data.result.video[0].url;
|
|
1078
|
-
} else if (data.result.url) {
|
|
1079
|
-
videoUrl = data.result.url;
|
|
1080
|
-
} else if (data.result.media && data.result.media[0] && data.result.media[0].url) {
|
|
1081
|
-
videoUrl = data.result.media[0].url;
|
|
1082
|
-
} else {
|
|
1083
|
-
// Search for any video URL in the response
|
|
1084
|
-
const findVideoUrl = (obj) => {
|
|
1085
|
-
if (typeof obj === 'string' && obj.includes('http') &&
|
|
1086
|
-
(obj.includes('.mp4') || obj.includes('video') || obj.includes('media'))) {
|
|
1087
|
-
return obj;
|
|
1088
|
-
}
|
|
1089
|
-
if (typeof obj === 'object' && obj !== null) {
|
|
1090
|
-
for (const key in obj) {
|
|
1091
|
-
const result = findVideoUrl(obj[key]);
|
|
1092
|
-
if (result) return result;
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
return null;
|
|
1096
|
-
};
|
|
1097
|
-
|
|
1098
|
-
const foundUrl = findVideoUrl(data.result);
|
|
1099
|
-
if (foundUrl) {
|
|
1100
|
-
videoUrl = foundUrl;
|
|
1066
|
+
const data = await twitter(url);
|
|
1067
|
+
if (data && data.url) {
|
|
1068
|
+
// Se data.url for um array, procuramos por qualidade HD
|
|
1069
|
+
if (Array.isArray(data.url)) {
|
|
1070
|
+
// Tentar encontrar HD especificamente
|
|
1071
|
+
const hdVideo = data.url.find(v => v.hd);
|
|
1072
|
+
if (hdVideo) {
|
|
1073
|
+
videoUrl = hdVideo.hd;
|
|
1101
1074
|
} else {
|
|
1102
|
-
|
|
1075
|
+
// Tentar encontrar por label ou qualidade
|
|
1076
|
+
const highQuality = data.url.find(v =>
|
|
1077
|
+
(v.quality && typeof v.quality === 'string' && v.quality.toUpperCase().includes('HD')) ||
|
|
1078
|
+
(v.label && typeof v.label === 'string' && v.label.toUpperCase().includes('HD'))
|
|
1079
|
+
);
|
|
1080
|
+
|
|
1081
|
+
if (highQuality) {
|
|
1082
|
+
videoUrl = highQuality.url;
|
|
1083
|
+
} else {
|
|
1084
|
+
// Fallback: pega o primeiro que tiver url
|
|
1085
|
+
const anyVideo = data.url.find(v => v.url);
|
|
1086
|
+
videoUrl = anyVideo ? anyVideo.url : data.url[0];
|
|
1087
|
+
}
|
|
1103
1088
|
}
|
|
1089
|
+
} else if (typeof data.url === 'string') {
|
|
1090
|
+
videoUrl = data.url;
|
|
1104
1091
|
}
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
if (!videoUrl) {
|
|
1095
|
+
// Fallback para estrutura antiga ou diferente
|
|
1096
|
+
if (Array.isArray(data) && data[0] && data[0].url) {
|
|
1097
|
+
videoUrl = data[0].url;
|
|
1098
|
+
} else if (data && data.HD) {
|
|
1099
|
+
videoUrl = data.HD;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
if (!videoUrl) {
|
|
1104
|
+
throw new Error("No video URL found in btch-downloader twitter response");
|
|
1107
1105
|
}
|
|
1108
1106
|
} catch (error) {
|
|
1109
1107
|
throw new Error(`Twitter download failed: ${error.message}`);
|
|
@@ -1117,21 +1115,21 @@ async function downloadSmartVideo(url, config) {
|
|
|
1117
1115
|
if (!videoUrl || !videoUrl.includes("http")) {
|
|
1118
1116
|
throw new Error("Returned video URL is invalid or unavailable.");
|
|
1119
1117
|
}
|
|
1120
|
-
|
|
1118
|
+
|
|
1121
1119
|
// Download the video with better error handling
|
|
1122
|
-
const response = await axios({
|
|
1123
|
-
url: videoUrl,
|
|
1124
|
-
method: "GET",
|
|
1120
|
+
const response = await axios({
|
|
1121
|
+
url: videoUrl,
|
|
1122
|
+
method: "GET",
|
|
1125
1123
|
responseType: "stream",
|
|
1126
1124
|
timeout: 30000,
|
|
1127
1125
|
headers: {
|
|
1128
1126
|
'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'
|
|
1129
1127
|
}
|
|
1130
1128
|
});
|
|
1131
|
-
|
|
1129
|
+
|
|
1132
1130
|
// Create minimal unique file name in output dir
|
|
1133
|
-
let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0,4)}.mp4`);
|
|
1134
|
-
|
|
1131
|
+
let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0, 4)}.mp4`);
|
|
1132
|
+
|
|
1135
1133
|
const videoWriter = fs.createWriteStream(fileName);
|
|
1136
1134
|
response.data.pipe(videoWriter);
|
|
1137
1135
|
|
|
@@ -1156,19 +1154,19 @@ async function downloadSmartVideo(url, config) {
|
|
|
1156
1154
|
// Function for direct video download
|
|
1157
1155
|
async function downloadDirectVideo(url, config) {
|
|
1158
1156
|
try {
|
|
1159
|
-
const response = await axios({
|
|
1160
|
-
url: url,
|
|
1161
|
-
method: "GET",
|
|
1157
|
+
const response = await axios({
|
|
1158
|
+
url: url,
|
|
1159
|
+
method: "GET",
|
|
1162
1160
|
responseType: "stream",
|
|
1163
1161
|
timeout: 30000,
|
|
1164
1162
|
headers: {
|
|
1165
1163
|
'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'
|
|
1166
1164
|
}
|
|
1167
1165
|
});
|
|
1168
|
-
|
|
1166
|
+
|
|
1169
1167
|
// Create minimal unique file name in output dir
|
|
1170
|
-
let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0,4)}.mp4`);
|
|
1171
|
-
|
|
1168
|
+
let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0, 4)}.mp4`);
|
|
1169
|
+
|
|
1172
1170
|
const videoWriter = fs.createWriteStream(fileName);
|
|
1173
1171
|
response.data.pipe(videoWriter);
|
|
1174
1172
|
|
|
@@ -1216,7 +1214,7 @@ async function downloadGenericFile(url, preferredExt = null) {
|
|
|
1216
1214
|
else ext = 'bin';
|
|
1217
1215
|
}
|
|
1218
1216
|
|
|
1219
|
-
const fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0,4)}.${ext}`);
|
|
1217
|
+
const fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0, 4)}.${ext}`);
|
|
1220
1218
|
const writer = fs.createWriteStream(fileName);
|
|
1221
1219
|
response.data.pipe(writer);
|
|
1222
1220
|
|
|
@@ -1231,7 +1229,7 @@ async function downloadGenericFile(url, preferredExt = null) {
|
|
|
1231
1229
|
|
|
1232
1230
|
// Function to rotate video
|
|
1233
1231
|
async function rotateVideo(fileName, rotation) {
|
|
1234
|
-
const outputPath = path.join(OUTPUT_DIR, `vr_${uuidv4().slice(0,4)}.mp4`);
|
|
1232
|
+
const outputPath = path.join(OUTPUT_DIR, `vr_${uuidv4().slice(0, 4)}.mp4`);
|
|
1235
1233
|
let angle;
|
|
1236
1234
|
switch (rotation.toLowerCase()) {
|
|
1237
1235
|
case "left": angle = "transpose=2"; break;
|
|
@@ -1282,8 +1280,8 @@ async function cleanupTempFiles() {
|
|
|
1282
1280
|
try {
|
|
1283
1281
|
if (!fs.existsSync(TEMP_DIR)) return;
|
|
1284
1282
|
const files = fs.readdirSync(TEMP_DIR);
|
|
1285
|
-
const tempFiles = files.filter(file =>
|
|
1286
|
-
/^temp_video.*\.mp4$/.test(file) ||
|
|
1283
|
+
const tempFiles = files.filter(file =>
|
|
1284
|
+
/^temp_video.*\.mp4$/.test(file) ||
|
|
1287
1285
|
/_audio\.mp3$/.test(file) ||
|
|
1288
1286
|
/_rotated\.mp4$/.test(file) ||
|
|
1289
1287
|
/_cropped\.mp4$/.test(file) ||
|
|
@@ -1315,9 +1313,9 @@ async function cleanupOutputFiles() {
|
|
|
1315
1313
|
if (now - st.mtimeMs > maxAgeMs) {
|
|
1316
1314
|
await safeUnlinkWithRetry(full);
|
|
1317
1315
|
}
|
|
1318
|
-
} catch (_) {}
|
|
1316
|
+
} catch (_) { }
|
|
1319
1317
|
}
|
|
1320
|
-
} catch (_) {}
|
|
1318
|
+
} catch (_) { }
|
|
1321
1319
|
}
|
|
1322
1320
|
|
|
1323
1321
|
// Schedule periodic cleanup (every 10 minutes)
|
|
@@ -1331,7 +1329,7 @@ cleanupOutputFiles();
|
|
|
1331
1329
|
// Function to auto-crop video
|
|
1332
1330
|
async function autoCrop(fileName) {
|
|
1333
1331
|
const inputPath = fileName;
|
|
1334
|
-
const outputPath = path.join(OUTPUT_DIR, `vc_${uuidv4().slice(0,4)}.mp4`);
|
|
1332
|
+
const outputPath = path.join(OUTPUT_DIR, `vc_${uuidv4().slice(0, 4)}.mp4`);
|
|
1335
1333
|
|
|
1336
1334
|
return new Promise((resolve, reject) => {
|
|
1337
1335
|
let cropValues = null;
|
|
@@ -1339,13 +1337,13 @@ async function autoCrop(fileName) {
|
|
|
1339
1337
|
.outputOptions('-vf', 'cropdetect=24:16:0')
|
|
1340
1338
|
.outputFormat('null')
|
|
1341
1339
|
.output('-')
|
|
1342
|
-
.on('stderr', function(stderrLine) {
|
|
1340
|
+
.on('stderr', function (stderrLine) {
|
|
1343
1341
|
const cropMatch = stderrLine.match(/crop=([0-9]+):([0-9]+):([0-9]+):([0-9]+)/);
|
|
1344
1342
|
if (cropMatch) {
|
|
1345
1343
|
cropValues = `crop=${cropMatch[1]}:${cropMatch[2]}:${cropMatch[3]}:${cropMatch[4]}`;
|
|
1346
1344
|
}
|
|
1347
1345
|
})
|
|
1348
|
-
.on('end', function() {
|
|
1346
|
+
.on('end', function () {
|
|
1349
1347
|
if (!cropValues) {
|
|
1350
1348
|
resolve(inputPath);
|
|
1351
1349
|
return;
|
|
@@ -1378,7 +1376,7 @@ async function checkAndCompressVideo(filePath, limitSizeMB, platform = null) {
|
|
|
1378
1376
|
return filePath;
|
|
1379
1377
|
}
|
|
1380
1378
|
|
|
1381
|
-
const outputPath = path.join(OUTPUT_DIR, `vx_${uuidv4().slice(0,4)}.mp4`);
|
|
1379
|
+
const outputPath = path.join(OUTPUT_DIR, `vx_${uuidv4().slice(0, 4)}.mp4`);
|
|
1382
1380
|
|
|
1383
1381
|
return new Promise((resolve, reject) => {
|
|
1384
1382
|
ffmpeg(filePath)
|
|
@@ -1413,17 +1411,17 @@ async function convertVideoFormat(inputPath, targetFormat) {
|
|
|
1413
1411
|
const supported = ["mp4", "mov", "webm", "mkv"];
|
|
1414
1412
|
if (!supported.includes(fmt)) return inputPath;
|
|
1415
1413
|
|
|
1416
|
-
const outputPath = path.join(OUTPUT_DIR, `vf_${uuidv4().slice(0,4)}.${fmt}`);
|
|
1414
|
+
const outputPath = path.join(OUTPUT_DIR, `vf_${uuidv4().slice(0, 4)}.${fmt}`);
|
|
1417
1415
|
|
|
1418
1416
|
const ff = ffmpeg(inputPath);
|
|
1419
1417
|
switch (fmt) {
|
|
1420
1418
|
case "mp4":
|
|
1421
1419
|
case "mov":
|
|
1422
1420
|
case "mkv":
|
|
1423
|
-
ff.outputOptions("-c:v","libx264","-pix_fmt","yuv420p","-c:a","aac","-b:a","192k");
|
|
1421
|
+
ff.outputOptions("-c:v", "libx264", "-pix_fmt", "yuv420p", "-c:a", "aac", "-b:a", "192k");
|
|
1424
1422
|
break;
|
|
1425
1423
|
case "webm":
|
|
1426
|
-
ff.outputOptions("-c:v","libvpx-vp9","-b:v","0","-crf","30","-c:a","libopus","-b:a","128k");
|
|
1424
|
+
ff.outputOptions("-c:v", "libvpx-vp9", "-b:v", "0", "-crf", "30", "-c:a", "libopus", "-b:a", "128k");
|
|
1427
1425
|
break;
|
|
1428
1426
|
default:
|
|
1429
1427
|
return inputPath;
|
|
@@ -1480,14 +1478,14 @@ async function unshortenUrl(url) {
|
|
|
1480
1478
|
return status >= 200 && status < 400; // Accept redirects
|
|
1481
1479
|
}
|
|
1482
1480
|
});
|
|
1483
|
-
|
|
1481
|
+
|
|
1484
1482
|
// Get the final URL after all redirects
|
|
1485
1483
|
const finalUrl = response.request.res.responseUrl || response.config.url;
|
|
1486
1484
|
return finalUrl;
|
|
1487
1485
|
}
|
|
1488
|
-
|
|
1486
|
+
|
|
1489
1487
|
// For other URLs, use the original method
|
|
1490
|
-
const response = await axios.head(url, {
|
|
1488
|
+
const response = await axios.head(url, {
|
|
1491
1489
|
maxRedirects: 10,
|
|
1492
1490
|
timeout: 10000,
|
|
1493
1491
|
headers: {
|
|
@@ -1556,10 +1554,10 @@ const AudioDownloader = async (url, options = {}) => {
|
|
|
1556
1554
|
if (downloadedFilePath) {
|
|
1557
1555
|
// Extrair áudio em mp3
|
|
1558
1556
|
audioFilePath = await extractAudioMp3(downloadedFilePath);
|
|
1559
|
-
|
|
1557
|
+
|
|
1560
1558
|
// Remove o arquivo de vídeo temporário após extrair o áudio
|
|
1561
1559
|
await safeUnlinkWithRetry(downloadedFilePath);
|
|
1562
|
-
|
|
1560
|
+
|
|
1563
1561
|
const result = await uploadToGoFileIfNeeded(audioFilePath);
|
|
1564
1562
|
return result;
|
|
1565
1563
|
} else {
|
|
@@ -1575,7 +1573,7 @@ const AudioDownloader = async (url, options = {}) => {
|
|
|
1575
1573
|
// Função para extrair áudio em mp3 usando ffmpeg
|
|
1576
1574
|
async function extractAudioMp3(videoPath) {
|
|
1577
1575
|
return new Promise((resolve, reject) => {
|
|
1578
|
-
const audioPath = path.join(OUTPUT_DIR, `a_${uuidv4().slice(0,4)}.mp3`);
|
|
1576
|
+
const audioPath = path.join(OUTPUT_DIR, `a_${uuidv4().slice(0, 4)}.mp3`);
|
|
1579
1577
|
ffmpeg(videoPath)
|
|
1580
1578
|
.noVideo()
|
|
1581
1579
|
.audioCodec('libmp3lame')
|