frostpv 1.0.11 → 1.0.12
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 +164 -131
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -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}`);
|
|
@@ -886,16 +886,25 @@ const MediaDownloader = async (url, options = {}) => {
|
|
|
886
886
|
// Function to process downloaded file
|
|
887
887
|
async function processDownloadedFile(fileName, config, platform = null) {
|
|
888
888
|
let processedFile = path.resolve(fileName);
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
889
|
+
|
|
890
|
+
// Check if file is video or image based on extension
|
|
891
|
+
const ext = path.extname(processedFile).toLowerCase();
|
|
892
|
+
const isVideo = ['.mp4', '.mov', '.webm', '.mkv'].includes(ext);
|
|
893
|
+
|
|
894
|
+
// Only apply video processing if it is a video
|
|
895
|
+
if (isVideo) {
|
|
896
|
+
if (config.rotation) {
|
|
897
|
+
processedFile = await rotateVideo(processedFile, config.rotation);
|
|
898
|
+
}
|
|
899
|
+
if (config.autocrop) {
|
|
900
|
+
processedFile = await autoCrop(processedFile);
|
|
901
|
+
}
|
|
902
|
+
processedFile = await checkAndCompressVideo(processedFile, config.limitSizeMB, platform);
|
|
903
|
+
if (config.outputFormat) {
|
|
904
|
+
processedFile = await convertVideoFormat(processedFile, String(config.outputFormat).toLowerCase());
|
|
905
|
+
}
|
|
898
906
|
}
|
|
907
|
+
|
|
899
908
|
return processedFile;
|
|
900
909
|
}
|
|
901
910
|
|
|
@@ -986,15 +995,15 @@ async function downloadSmartVideo(url, config) {
|
|
|
986
995
|
throw new Error("No video URLs found in Facebook response");
|
|
987
996
|
}
|
|
988
997
|
videoUrl = data.Normal_video || data.HD;
|
|
989
|
-
|
|
998
|
+
|
|
990
999
|
// Validate the URL is accessible
|
|
991
|
-
const response = await axios.head(videoUrl, {
|
|
1000
|
+
const response = await axios.head(videoUrl, {
|
|
992
1001
|
timeout: 10000,
|
|
993
1002
|
headers: {
|
|
994
1003
|
'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
1004
|
}
|
|
996
1005
|
}).catch(() => null);
|
|
997
|
-
|
|
1006
|
+
|
|
998
1007
|
if (!response || response.status !== 200) {
|
|
999
1008
|
throw new Error("Facebook video URL is not accessible");
|
|
1000
1009
|
}
|
|
@@ -1066,12 +1075,12 @@ async function downloadSmartVideo(url, config) {
|
|
|
1066
1075
|
// Validate and clean the Twitter URL
|
|
1067
1076
|
const cleanUrl = validateTwitterUrl(url);
|
|
1068
1077
|
const data = await TwitterDL(cleanUrl, {});
|
|
1069
|
-
|
|
1078
|
+
|
|
1070
1079
|
if (data && data.result) {
|
|
1071
1080
|
// Try multiple possible response structures
|
|
1072
|
-
if (data.result.media && data.result.media[0] &&
|
|
1073
|
-
|
|
1074
|
-
|
|
1081
|
+
if (data.result.media && data.result.media[0] &&
|
|
1082
|
+
data.result.media[0].videos && data.result.media[0].videos[0] &&
|
|
1083
|
+
data.result.media[0].videos[0].url) {
|
|
1075
1084
|
videoUrl = data.result.media[0].videos[0].url;
|
|
1076
1085
|
} else if (data.result.video && data.result.video[0] && data.result.video[0].url) {
|
|
1077
1086
|
videoUrl = data.result.video[0].url;
|
|
@@ -1082,8 +1091,8 @@ async function downloadSmartVideo(url, config) {
|
|
|
1082
1091
|
} else {
|
|
1083
1092
|
// Search for any video URL in the response
|
|
1084
1093
|
const findVideoUrl = (obj) => {
|
|
1085
|
-
if (typeof obj === 'string' && obj.includes('http') &&
|
|
1086
|
-
|
|
1094
|
+
if (typeof obj === 'string' && obj.includes('http') &&
|
|
1095
|
+
(obj.includes('.mp4') || obj.includes('video') || obj.includes('media'))) {
|
|
1087
1096
|
return obj;
|
|
1088
1097
|
}
|
|
1089
1098
|
if (typeof obj === 'object' && obj !== null) {
|
|
@@ -1094,7 +1103,7 @@ async function downloadSmartVideo(url, config) {
|
|
|
1094
1103
|
}
|
|
1095
1104
|
return null;
|
|
1096
1105
|
};
|
|
1097
|
-
|
|
1106
|
+
|
|
1098
1107
|
const foundUrl = findVideoUrl(data.result);
|
|
1099
1108
|
if (foundUrl) {
|
|
1100
1109
|
videoUrl = foundUrl;
|
|
@@ -1117,35 +1126,47 @@ async function downloadSmartVideo(url, config) {
|
|
|
1117
1126
|
if (!videoUrl || !videoUrl.includes("http")) {
|
|
1118
1127
|
throw new Error("Returned video URL is invalid or unavailable.");
|
|
1119
1128
|
}
|
|
1120
|
-
|
|
1121
|
-
// Download the video with better error handling
|
|
1122
|
-
const response = await axios({
|
|
1123
|
-
url: videoUrl,
|
|
1124
|
-
method: "GET",
|
|
1129
|
+
|
|
1130
|
+
// Download the video/image with better error handling
|
|
1131
|
+
const response = await axios({
|
|
1132
|
+
url: videoUrl,
|
|
1133
|
+
method: "GET",
|
|
1125
1134
|
responseType: "stream",
|
|
1126
1135
|
timeout: 30000,
|
|
1127
1136
|
headers: {
|
|
1128
1137
|
'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
1138
|
}
|
|
1130
1139
|
});
|
|
1131
|
-
|
|
1140
|
+
|
|
1141
|
+
// Detect file type from content-type
|
|
1142
|
+
const contentType = response.headers['content-type'] || 'video/mp4';
|
|
1143
|
+
let ext = 'mp4'; // default
|
|
1144
|
+
|
|
1145
|
+
if (contentType.includes('image/jpeg')) ext = 'jpg';
|
|
1146
|
+
else if (contentType.includes('image/png')) ext = 'png';
|
|
1147
|
+
else if (contentType.includes('image/webp')) ext = 'webp';
|
|
1148
|
+
else if (contentType.includes('image/gif')) ext = 'gif';
|
|
1149
|
+
else if (contentType.includes('video/webm')) ext = 'webm';
|
|
1150
|
+
else if (contentType.includes('video/quicktime')) ext = 'mov';
|
|
1151
|
+
else if (contentType.includes('video/x-matroska')) ext = 'mkv';
|
|
1152
|
+
|
|
1132
1153
|
// Create minimal unique file name in output dir
|
|
1133
|
-
let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0,4)}
|
|
1134
|
-
|
|
1135
|
-
const
|
|
1136
|
-
response.data.pipe(
|
|
1154
|
+
let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0, 4)}.${ext}`);
|
|
1155
|
+
|
|
1156
|
+
const writer = fs.createWriteStream(fileName);
|
|
1157
|
+
response.data.pipe(writer);
|
|
1137
1158
|
|
|
1138
1159
|
return new Promise((resolve, reject) => {
|
|
1139
|
-
|
|
1160
|
+
writer.on("finish", async () => {
|
|
1140
1161
|
try {
|
|
1141
1162
|
const finalFilePath = await processDownloadedFile(fileName, config, platform);
|
|
1142
1163
|
resolve(finalFilePath);
|
|
1143
1164
|
} catch (error) {
|
|
1144
|
-
reject(new Error(`Error processing downloaded
|
|
1165
|
+
reject(new Error(`Error processing downloaded file: ${error.message}`));
|
|
1145
1166
|
}
|
|
1146
1167
|
});
|
|
1147
|
-
|
|
1148
|
-
reject(new Error(`Error saving
|
|
1168
|
+
writer.on("error", (error) => {
|
|
1169
|
+
reject(new Error(`Error saving file: ${error.message}`));
|
|
1149
1170
|
});
|
|
1150
1171
|
});
|
|
1151
1172
|
} catch (error) {
|
|
@@ -1156,33 +1177,45 @@ async function downloadSmartVideo(url, config) {
|
|
|
1156
1177
|
// Function for direct video download
|
|
1157
1178
|
async function downloadDirectVideo(url, config) {
|
|
1158
1179
|
try {
|
|
1159
|
-
const response = await axios({
|
|
1160
|
-
url: url,
|
|
1161
|
-
method: "GET",
|
|
1180
|
+
const response = await axios({
|
|
1181
|
+
url: url,
|
|
1182
|
+
method: "GET",
|
|
1162
1183
|
responseType: "stream",
|
|
1163
1184
|
timeout: 30000,
|
|
1164
1185
|
headers: {
|
|
1165
1186
|
'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
1187
|
}
|
|
1167
1188
|
});
|
|
1168
|
-
|
|
1189
|
+
|
|
1190
|
+
// Detect file type from content-type
|
|
1191
|
+
const contentType = response.headers['content-type'] || 'video/mp4';
|
|
1192
|
+
let ext = 'mp4'; // default
|
|
1193
|
+
|
|
1194
|
+
if (contentType.includes('image/jpeg')) ext = 'jpg';
|
|
1195
|
+
else if (contentType.includes('image/png')) ext = 'png';
|
|
1196
|
+
else if (contentType.includes('image/webp')) ext = 'webp';
|
|
1197
|
+
else if (contentType.includes('image/gif')) ext = 'gif';
|
|
1198
|
+
else if (contentType.includes('video/webm')) ext = 'webm';
|
|
1199
|
+
else if (contentType.includes('video/quicktime')) ext = 'mov';
|
|
1200
|
+
else if (contentType.includes('video/x-matroska')) ext = 'mkv';
|
|
1201
|
+
|
|
1169
1202
|
// Create minimal unique file name in output dir
|
|
1170
|
-
let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0,4)}
|
|
1171
|
-
|
|
1172
|
-
const
|
|
1173
|
-
response.data.pipe(
|
|
1203
|
+
let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0, 4)}.${ext}`);
|
|
1204
|
+
|
|
1205
|
+
const writer = fs.createWriteStream(fileName);
|
|
1206
|
+
response.data.pipe(writer);
|
|
1174
1207
|
|
|
1175
1208
|
return new Promise((resolve, reject) => {
|
|
1176
|
-
|
|
1209
|
+
writer.on("finish", async () => {
|
|
1177
1210
|
try {
|
|
1178
1211
|
const finalFilePath = await processDownloadedFile(fileName, config, "unknown");
|
|
1179
1212
|
resolve(finalFilePath);
|
|
1180
1213
|
} catch (error) {
|
|
1181
|
-
reject(new Error(`Error processing downloaded
|
|
1214
|
+
reject(new Error(`Error processing downloaded file: ${error.message}`));
|
|
1182
1215
|
}
|
|
1183
1216
|
});
|
|
1184
|
-
|
|
1185
|
-
reject(new Error(`Error saving
|
|
1217
|
+
writer.on("error", (error) => {
|
|
1218
|
+
reject(new Error(`Error saving file: ${error.message}`));
|
|
1186
1219
|
});
|
|
1187
1220
|
});
|
|
1188
1221
|
} catch (error) {
|
|
@@ -1216,7 +1249,7 @@ async function downloadGenericFile(url, preferredExt = null) {
|
|
|
1216
1249
|
else ext = 'bin';
|
|
1217
1250
|
}
|
|
1218
1251
|
|
|
1219
|
-
const fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0,4)}.${ext}`);
|
|
1252
|
+
const fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0, 4)}.${ext}`);
|
|
1220
1253
|
const writer = fs.createWriteStream(fileName);
|
|
1221
1254
|
response.data.pipe(writer);
|
|
1222
1255
|
|
|
@@ -1231,7 +1264,7 @@ async function downloadGenericFile(url, preferredExt = null) {
|
|
|
1231
1264
|
|
|
1232
1265
|
// Function to rotate video
|
|
1233
1266
|
async function rotateVideo(fileName, rotation) {
|
|
1234
|
-
const outputPath = path.join(OUTPUT_DIR, `vr_${uuidv4().slice(0,4)}.mp4`);
|
|
1267
|
+
const outputPath = path.join(OUTPUT_DIR, `vr_${uuidv4().slice(0, 4)}.mp4`);
|
|
1235
1268
|
let angle;
|
|
1236
1269
|
switch (rotation.toLowerCase()) {
|
|
1237
1270
|
case "left": angle = "transpose=2"; break;
|
|
@@ -1282,8 +1315,8 @@ async function cleanupTempFiles() {
|
|
|
1282
1315
|
try {
|
|
1283
1316
|
if (!fs.existsSync(TEMP_DIR)) return;
|
|
1284
1317
|
const files = fs.readdirSync(TEMP_DIR);
|
|
1285
|
-
const tempFiles = files.filter(file =>
|
|
1286
|
-
/^temp_video.*\.mp4$/.test(file) ||
|
|
1318
|
+
const tempFiles = files.filter(file =>
|
|
1319
|
+
/^temp_video.*\.mp4$/.test(file) ||
|
|
1287
1320
|
/_audio\.mp3$/.test(file) ||
|
|
1288
1321
|
/_rotated\.mp4$/.test(file) ||
|
|
1289
1322
|
/_cropped\.mp4$/.test(file) ||
|
|
@@ -1315,9 +1348,9 @@ async function cleanupOutputFiles() {
|
|
|
1315
1348
|
if (now - st.mtimeMs > maxAgeMs) {
|
|
1316
1349
|
await safeUnlinkWithRetry(full);
|
|
1317
1350
|
}
|
|
1318
|
-
} catch (_) {}
|
|
1351
|
+
} catch (_) { }
|
|
1319
1352
|
}
|
|
1320
|
-
} catch (_) {}
|
|
1353
|
+
} catch (_) { }
|
|
1321
1354
|
}
|
|
1322
1355
|
|
|
1323
1356
|
// Schedule periodic cleanup (every 10 minutes)
|
|
@@ -1331,7 +1364,7 @@ cleanupOutputFiles();
|
|
|
1331
1364
|
// Function to auto-crop video
|
|
1332
1365
|
async function autoCrop(fileName) {
|
|
1333
1366
|
const inputPath = fileName;
|
|
1334
|
-
const outputPath = path.join(OUTPUT_DIR, `vc_${uuidv4().slice(0,4)}.mp4`);
|
|
1367
|
+
const outputPath = path.join(OUTPUT_DIR, `vc_${uuidv4().slice(0, 4)}.mp4`);
|
|
1335
1368
|
|
|
1336
1369
|
return new Promise((resolve, reject) => {
|
|
1337
1370
|
let cropValues = null;
|
|
@@ -1339,13 +1372,13 @@ async function autoCrop(fileName) {
|
|
|
1339
1372
|
.outputOptions('-vf', 'cropdetect=24:16:0')
|
|
1340
1373
|
.outputFormat('null')
|
|
1341
1374
|
.output('-')
|
|
1342
|
-
.on('stderr', function(stderrLine) {
|
|
1375
|
+
.on('stderr', function (stderrLine) {
|
|
1343
1376
|
const cropMatch = stderrLine.match(/crop=([0-9]+):([0-9]+):([0-9]+):([0-9]+)/);
|
|
1344
1377
|
if (cropMatch) {
|
|
1345
1378
|
cropValues = `crop=${cropMatch[1]}:${cropMatch[2]}:${cropMatch[3]}:${cropMatch[4]}`;
|
|
1346
1379
|
}
|
|
1347
1380
|
})
|
|
1348
|
-
.on('end', function() {
|
|
1381
|
+
.on('end', function () {
|
|
1349
1382
|
if (!cropValues) {
|
|
1350
1383
|
resolve(inputPath);
|
|
1351
1384
|
return;
|
|
@@ -1378,7 +1411,7 @@ async function checkAndCompressVideo(filePath, limitSizeMB, platform = null) {
|
|
|
1378
1411
|
return filePath;
|
|
1379
1412
|
}
|
|
1380
1413
|
|
|
1381
|
-
const outputPath = path.join(OUTPUT_DIR, `vx_${uuidv4().slice(0,4)}.mp4`);
|
|
1414
|
+
const outputPath = path.join(OUTPUT_DIR, `vx_${uuidv4().slice(0, 4)}.mp4`);
|
|
1382
1415
|
|
|
1383
1416
|
return new Promise((resolve, reject) => {
|
|
1384
1417
|
ffmpeg(filePath)
|
|
@@ -1413,17 +1446,17 @@ async function convertVideoFormat(inputPath, targetFormat) {
|
|
|
1413
1446
|
const supported = ["mp4", "mov", "webm", "mkv"];
|
|
1414
1447
|
if (!supported.includes(fmt)) return inputPath;
|
|
1415
1448
|
|
|
1416
|
-
const outputPath = path.join(OUTPUT_DIR, `vf_${uuidv4().slice(0,4)}.${fmt}`);
|
|
1449
|
+
const outputPath = path.join(OUTPUT_DIR, `vf_${uuidv4().slice(0, 4)}.${fmt}`);
|
|
1417
1450
|
|
|
1418
1451
|
const ff = ffmpeg(inputPath);
|
|
1419
1452
|
switch (fmt) {
|
|
1420
1453
|
case "mp4":
|
|
1421
1454
|
case "mov":
|
|
1422
1455
|
case "mkv":
|
|
1423
|
-
ff.outputOptions("-c:v","libx264","-pix_fmt","yuv420p","-c:a","aac","-b:a","192k");
|
|
1456
|
+
ff.outputOptions("-c:v", "libx264", "-pix_fmt", "yuv420p", "-c:a", "aac", "-b:a", "192k");
|
|
1424
1457
|
break;
|
|
1425
1458
|
case "webm":
|
|
1426
|
-
ff.outputOptions("-c:v","libvpx-vp9","-b:v","0","-crf","30","-c:a","libopus","-b:a","128k");
|
|
1459
|
+
ff.outputOptions("-c:v", "libvpx-vp9", "-b:v", "0", "-crf", "30", "-c:a", "libopus", "-b:a", "128k");
|
|
1427
1460
|
break;
|
|
1428
1461
|
default:
|
|
1429
1462
|
return inputPath;
|
|
@@ -1480,14 +1513,14 @@ async function unshortenUrl(url) {
|
|
|
1480
1513
|
return status >= 200 && status < 400; // Accept redirects
|
|
1481
1514
|
}
|
|
1482
1515
|
});
|
|
1483
|
-
|
|
1516
|
+
|
|
1484
1517
|
// Get the final URL after all redirects
|
|
1485
1518
|
const finalUrl = response.request.res.responseUrl || response.config.url;
|
|
1486
1519
|
return finalUrl;
|
|
1487
1520
|
}
|
|
1488
|
-
|
|
1521
|
+
|
|
1489
1522
|
// For other URLs, use the original method
|
|
1490
|
-
const response = await axios.head(url, {
|
|
1523
|
+
const response = await axios.head(url, {
|
|
1491
1524
|
maxRedirects: 10,
|
|
1492
1525
|
timeout: 10000,
|
|
1493
1526
|
headers: {
|
|
@@ -1556,10 +1589,10 @@ const AudioDownloader = async (url, options = {}) => {
|
|
|
1556
1589
|
if (downloadedFilePath) {
|
|
1557
1590
|
// Extrair áudio em mp3
|
|
1558
1591
|
audioFilePath = await extractAudioMp3(downloadedFilePath);
|
|
1559
|
-
|
|
1592
|
+
|
|
1560
1593
|
// Remove o arquivo de vídeo temporário após extrair o áudio
|
|
1561
1594
|
await safeUnlinkWithRetry(downloadedFilePath);
|
|
1562
|
-
|
|
1595
|
+
|
|
1563
1596
|
const result = await uploadToGoFileIfNeeded(audioFilePath);
|
|
1564
1597
|
return result;
|
|
1565
1598
|
} else {
|
|
@@ -1575,7 +1608,7 @@ const AudioDownloader = async (url, options = {}) => {
|
|
|
1575
1608
|
// Função para extrair áudio em mp3 usando ffmpeg
|
|
1576
1609
|
async function extractAudioMp3(videoPath) {
|
|
1577
1610
|
return new Promise((resolve, reject) => {
|
|
1578
|
-
const audioPath = path.join(OUTPUT_DIR, `a_${uuidv4().slice(0,4)}.mp3`);
|
|
1611
|
+
const audioPath = path.join(OUTPUT_DIR, `a_${uuidv4().slice(0, 4)}.mp3`);
|
|
1579
1612
|
ffmpeg(videoPath)
|
|
1580
1613
|
.noVideo()
|
|
1581
1614
|
.audioCodec('libmp3lame')
|