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.
Files changed (2) hide show
  1. package/index.js +164 -131
  2. 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
- data.result.media[0].videos && data.result.media[0].videos[0] &&
535
- data.result.media[0].videos[0].url) {
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
- (obj.includes('.mp4') || obj.includes('video') || obj.includes('media'))) {
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
- data.result.media[0].videos && data.result.media[0].videos[0] &&
595
- data.result.media[0].videos[0].url) {
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
- (obj.includes('.mp4') || obj.includes('video') || obj.includes('media'))) {
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
- data.result.media[0].videos && data.result.media[0].videos[0] &&
668
- data.result.media[0].videos[0].url) {
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
- if (config.rotation) {
890
- processedFile = await rotateVideo(processedFile, config.rotation);
891
- }
892
- if (config.autocrop) {
893
- processedFile = await autoCrop(processedFile);
894
- }
895
- processedFile = await checkAndCompressVideo(processedFile, config.limitSizeMB, platform);
896
- if (config.outputFormat) {
897
- processedFile = await convertVideoFormat(processedFile, String(config.outputFormat).toLowerCase());
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
- data.result.media[0].videos && data.result.media[0].videos[0] &&
1074
- data.result.media[0].videos[0].url) {
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
- (obj.includes('.mp4') || obj.includes('video') || obj.includes('media'))) {
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)}.mp4`);
1134
-
1135
- const videoWriter = fs.createWriteStream(fileName);
1136
- response.data.pipe(videoWriter);
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
- videoWriter.on("finish", async () => {
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 video: ${error.message}`));
1165
+ reject(new Error(`Error processing downloaded file: ${error.message}`));
1145
1166
  }
1146
1167
  });
1147
- videoWriter.on("error", (error) => {
1148
- reject(new Error(`Error saving video: ${error.message}`));
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)}.mp4`);
1171
-
1172
- const videoWriter = fs.createWriteStream(fileName);
1173
- response.data.pipe(videoWriter);
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
- videoWriter.on("finish", async () => {
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 video: ${error.message}`));
1214
+ reject(new Error(`Error processing downloaded file: ${error.message}`));
1182
1215
  }
1183
1216
  });
1184
- videoWriter.on("error", (error) => {
1185
- reject(new Error(`Error saving video: ${error.message}`));
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')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frostpv",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "description": "downloads",
5
5
  "main": "index.js",
6
6
  "scripts": {