frostpv 1.0.10 → 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 +65 -161
- package/package.json +3 -3
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
|
|
7
|
+
const { igdl, ttdl, fbdown, mediafire, capcut, gdrive, pinterest } = 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");
|
|
@@ -58,13 +58,7 @@ const videoPlatforms = [
|
|
|
58
58
|
"https://twitter.com",
|
|
59
59
|
"https://www.twitter.com",
|
|
60
60
|
"https://vm.tiktok.com/",
|
|
61
|
-
"https://vt.tiktok.com/"
|
|
62
|
-
"https://www.youtube.com",
|
|
63
|
-
"https://youtube.com",
|
|
64
|
-
"https://youtu.be",
|
|
65
|
-
"https://m.youtube.com",
|
|
66
|
-
"https://www.youtube.com/watch?"
|
|
67
|
-
|
|
61
|
+
"https://vt.tiktok.com/"
|
|
68
62
|
];
|
|
69
63
|
|
|
70
64
|
// Blacklist links
|
|
@@ -375,39 +369,11 @@ function getPlatformType(url) {
|
|
|
375
369
|
if (lowerUrl.includes("facebook.com") || lowerUrl.includes("fb.watch") || lowerUrl.includes("fb.com")) return "facebook";
|
|
376
370
|
if (lowerUrl.includes("mediafire.com")) return "mediafire";
|
|
377
371
|
if (lowerUrl.includes("x.com") || lowerUrl.includes("twitter.com")) return "twitter";
|
|
378
|
-
if (lowerUrl.includes("youtube.com") || lowerUrl.includes("
|
|
372
|
+
if (lowerUrl.includes("youtube.com") || lowerUrl.includes("you112t12u.be") || lowerUrl.includes("m.y11outu314be.com")) return "youtu1354be";
|
|
379
373
|
|
|
380
374
|
return "unknown";
|
|
381
375
|
}
|
|
382
376
|
|
|
383
|
-
// Function to get direct video URL from Twitter using yt-dlp
|
|
384
|
-
async function downloadTwitterWithYtDlp(url) {
|
|
385
|
-
return new Promise(async (resolve, reject) => {
|
|
386
|
-
try {
|
|
387
|
-
const ytdlp = new YtDlp({ ffmpegPath });
|
|
388
|
-
// Use -g to get URL, -f "best[ext=mp4]/best" to get best quality single file if possible
|
|
389
|
-
const args = [
|
|
390
|
-
url,
|
|
391
|
-
'-g',
|
|
392
|
-
'-f', 'best[ext=mp4]/best'
|
|
393
|
-
];
|
|
394
|
-
|
|
395
|
-
const result = await ytdlp.execAsync(args);
|
|
396
|
-
// yt-dlp might return multiple lines if it finds separate audio/video streams
|
|
397
|
-
// We take the first one, but for Twitter it is usually a single mp4 file
|
|
398
|
-
const videoUrl = result.trim().split('\n')[0];
|
|
399
|
-
|
|
400
|
-
if (videoUrl && videoUrl.startsWith('http')) {
|
|
401
|
-
resolve(videoUrl);
|
|
402
|
-
} else {
|
|
403
|
-
reject(new Error('yt-dlp did not return a valid URL'));
|
|
404
|
-
}
|
|
405
|
-
} catch (error) {
|
|
406
|
-
reject(error);
|
|
407
|
-
}
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
|
|
411
377
|
// Enhanced fallback download function with platform-specific methods
|
|
412
378
|
async function tryFallbackDownload(url, maxRetries = 3) {
|
|
413
379
|
const platform = getPlatformType(url);
|
|
@@ -555,12 +521,8 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
555
521
|
}
|
|
556
522
|
|
|
557
523
|
case "twitter": {
|
|
558
|
-
// Try
|
|
524
|
+
// Try multiple Twitter methods with better response handling
|
|
559
525
|
const methods = [
|
|
560
|
-
async () => {
|
|
561
|
-
// Priority 1: yt-dlp
|
|
562
|
-
return await downloadTwitterWithYtDlp(url);
|
|
563
|
-
},
|
|
564
526
|
async () => {
|
|
565
527
|
const cleanUrl = validateTwitterUrl(url);
|
|
566
528
|
const data = await TwitterDL(cleanUrl, {});
|
|
@@ -873,8 +835,9 @@ const MediaDownloader = async (url, options = {}) => {
|
|
|
873
835
|
|
|
874
836
|
// Verificar se é YouTube e lançar erro customizado
|
|
875
837
|
const platform = getPlatformType(url);
|
|
876
|
-
|
|
877
|
-
|
|
838
|
+
if (platform === "youtube") {
|
|
839
|
+
throw new Error("This URL is not from a supported platform. Supported platforms: Instagram, X(Twitter), TikTok, Facebook");
|
|
840
|
+
}
|
|
878
841
|
|
|
879
842
|
await cleanupTempFiles(); // Clean up previous temp files
|
|
880
843
|
|
|
@@ -887,7 +850,7 @@ const MediaDownloader = async (url, options = {}) => {
|
|
|
887
850
|
} catch (error) {
|
|
888
851
|
const platform = getPlatformType(url);
|
|
889
852
|
|
|
890
|
-
// YouTube doesn't use fallback method - it has its own specific implementation
|
|
853
|
+
// YouTube doesn't use fallback method - it has its own specific implementation
|
|
891
854
|
if (platform === "youtube") {
|
|
892
855
|
throw new Error(`YouTube download failed: ${error.message}`);
|
|
893
856
|
}
|
|
@@ -923,16 +886,25 @@ const MediaDownloader = async (url, options = {}) => {
|
|
|
923
886
|
// Function to process downloaded file
|
|
924
887
|
async function processDownloadedFile(fileName, config, platform = null) {
|
|
925
888
|
let processedFile = path.resolve(fileName);
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
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
|
+
}
|
|
935
906
|
}
|
|
907
|
+
|
|
936
908
|
return processedFile;
|
|
937
909
|
}
|
|
938
910
|
|
|
@@ -1147,98 +1119,6 @@ async function downloadSmartVideo(url, config) {
|
|
|
1147
1119
|
}
|
|
1148
1120
|
break;
|
|
1149
1121
|
}
|
|
1150
|
-
case "youtube": {
|
|
1151
|
-
try {
|
|
1152
|
-
const data = await youtube(url);
|
|
1153
|
-
// Try to find the video URL in the response
|
|
1154
|
-
|
|
1155
|
-
if (!data) throw new Error("No data returned from youtube downloader");
|
|
1156
|
-
|
|
1157
|
-
// Helper to check if a value looks like a video URL
|
|
1158
|
-
const isVideoUrl = (val) => typeof val === 'string' && val.startsWith('http') && !val.includes('.mp3');
|
|
1159
|
-
|
|
1160
|
-
let bestUrl = null;
|
|
1161
|
-
let bestScore = -1;
|
|
1162
|
-
|
|
1163
|
-
// Function to score keys based on quality
|
|
1164
|
-
const getScore = (key) => {
|
|
1165
|
-
const k = key.toLowerCase();
|
|
1166
|
-
if (k.includes('1080')) return 100;
|
|
1167
|
-
if (k.includes('720')) return 80;
|
|
1168
|
-
if (k.includes('480')) return 60;
|
|
1169
|
-
if (k.includes('hd')) return 90;
|
|
1170
|
-
if (k.includes('sd')) return 50;
|
|
1171
|
-
if (k.includes('360')) return 40;
|
|
1172
|
-
if (k.includes('mp4')) return 30;
|
|
1173
|
-
if (k.includes('video')) return 20;
|
|
1174
|
-
return 10;
|
|
1175
|
-
};
|
|
1176
|
-
|
|
1177
|
-
// Recursive function to gather all candidates
|
|
1178
|
-
const gatherCandidates = (obj) => {
|
|
1179
|
-
if (!obj) return;
|
|
1180
|
-
|
|
1181
|
-
if (typeof obj === 'object') {
|
|
1182
|
-
for (const key in obj) {
|
|
1183
|
-
const val = obj[key];
|
|
1184
|
-
if (isVideoUrl(val)) {
|
|
1185
|
-
const score = getScore(key);
|
|
1186
|
-
if (score > bestScore) {
|
|
1187
|
-
bestScore = score;
|
|
1188
|
-
bestUrl = val;
|
|
1189
|
-
}
|
|
1190
|
-
} else if (typeof val === 'object') {
|
|
1191
|
-
// Check if this object represents a format (e.g. { quality: '720p', url: '...' })
|
|
1192
|
-
if (val.url && isVideoUrl(val.url)) {
|
|
1193
|
-
let score = getScore(key); // Score from key name
|
|
1194
|
-
if (val.quality || val.resolution) {
|
|
1195
|
-
score = Math.max(score, getScore(String(val.quality || val.resolution)));
|
|
1196
|
-
}
|
|
1197
|
-
if (score > bestScore) {
|
|
1198
|
-
bestScore = score;
|
|
1199
|
-
bestUrl = val.url;
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
// Recurse
|
|
1203
|
-
gatherCandidates(val);
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
};
|
|
1208
|
-
|
|
1209
|
-
gatherCandidates(data);
|
|
1210
|
-
|
|
1211
|
-
// If no scored candidate found, try direct simple extraction with priority keys
|
|
1212
|
-
if (!bestUrl) {
|
|
1213
|
-
const priorities = ['mp4', 'url', 'link', 'download', 'video'];
|
|
1214
|
-
const findSimple = (obj) => {
|
|
1215
|
-
if (!obj) return null;
|
|
1216
|
-
for (const key of priorities) {
|
|
1217
|
-
if (obj[key] && isVideoUrl(obj[key])) return obj[key];
|
|
1218
|
-
}
|
|
1219
|
-
for (const key in obj) {
|
|
1220
|
-
if (typeof obj[key] === 'object') {
|
|
1221
|
-
const found = findSimple(obj[key]);
|
|
1222
|
-
if (found) return found;
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
return null;
|
|
1226
|
-
};
|
|
1227
|
-
bestUrl = findSimple(data);
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
videoUrl = bestUrl;
|
|
1231
|
-
|
|
1232
|
-
if (!videoUrl) {
|
|
1233
|
-
console.log('YouTube data dump:', JSON.stringify(data, null, 2)); // Debug log since we can't test
|
|
1234
|
-
throw new Error("Could not extract video URL from YouTube response");
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
} catch (error) {
|
|
1238
|
-
throw new Error(`YouTube download failed: ${error.message}`);
|
|
1239
|
-
}
|
|
1240
|
-
break;
|
|
1241
|
-
}
|
|
1242
1122
|
default:
|
|
1243
1123
|
throw new Error("Platform not supported or invalid link.");
|
|
1244
1124
|
}
|
|
@@ -1247,7 +1127,7 @@ async function downloadSmartVideo(url, config) {
|
|
|
1247
1127
|
throw new Error("Returned video URL is invalid or unavailable.");
|
|
1248
1128
|
}
|
|
1249
1129
|
|
|
1250
|
-
// Download the video with better error handling
|
|
1130
|
+
// Download the video/image with better error handling
|
|
1251
1131
|
const response = await axios({
|
|
1252
1132
|
url: videoUrl,
|
|
1253
1133
|
method: "GET",
|
|
@@ -1258,23 +1138,35 @@ async function downloadSmartVideo(url, config) {
|
|
|
1258
1138
|
}
|
|
1259
1139
|
});
|
|
1260
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
|
+
|
|
1261
1153
|
// Create minimal unique file name in output dir
|
|
1262
|
-
let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0, 4)}
|
|
1154
|
+
let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0, 4)}.${ext}`);
|
|
1263
1155
|
|
|
1264
|
-
const
|
|
1265
|
-
response.data.pipe(
|
|
1156
|
+
const writer = fs.createWriteStream(fileName);
|
|
1157
|
+
response.data.pipe(writer);
|
|
1266
1158
|
|
|
1267
1159
|
return new Promise((resolve, reject) => {
|
|
1268
|
-
|
|
1160
|
+
writer.on("finish", async () => {
|
|
1269
1161
|
try {
|
|
1270
1162
|
const finalFilePath = await processDownloadedFile(fileName, config, platform);
|
|
1271
1163
|
resolve(finalFilePath);
|
|
1272
1164
|
} catch (error) {
|
|
1273
|
-
reject(new Error(`Error processing downloaded
|
|
1165
|
+
reject(new Error(`Error processing downloaded file: ${error.message}`));
|
|
1274
1166
|
}
|
|
1275
1167
|
});
|
|
1276
|
-
|
|
1277
|
-
reject(new Error(`Error saving
|
|
1168
|
+
writer.on("error", (error) => {
|
|
1169
|
+
reject(new Error(`Error saving file: ${error.message}`));
|
|
1278
1170
|
});
|
|
1279
1171
|
});
|
|
1280
1172
|
} catch (error) {
|
|
@@ -1295,23 +1187,35 @@ async function downloadDirectVideo(url, config) {
|
|
|
1295
1187
|
}
|
|
1296
1188
|
});
|
|
1297
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
|
+
|
|
1298
1202
|
// Create minimal unique file name in output dir
|
|
1299
|
-
let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0, 4)}
|
|
1203
|
+
let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0, 4)}.${ext}`);
|
|
1300
1204
|
|
|
1301
|
-
const
|
|
1302
|
-
response.data.pipe(
|
|
1205
|
+
const writer = fs.createWriteStream(fileName);
|
|
1206
|
+
response.data.pipe(writer);
|
|
1303
1207
|
|
|
1304
1208
|
return new Promise((resolve, reject) => {
|
|
1305
|
-
|
|
1209
|
+
writer.on("finish", async () => {
|
|
1306
1210
|
try {
|
|
1307
1211
|
const finalFilePath = await processDownloadedFile(fileName, config, "unknown");
|
|
1308
1212
|
resolve(finalFilePath);
|
|
1309
1213
|
} catch (error) {
|
|
1310
|
-
reject(new Error(`Error processing downloaded
|
|
1214
|
+
reject(new Error(`Error processing downloaded file: ${error.message}`));
|
|
1311
1215
|
}
|
|
1312
1216
|
});
|
|
1313
|
-
|
|
1314
|
-
reject(new Error(`Error saving
|
|
1217
|
+
writer.on("error", (error) => {
|
|
1218
|
+
reject(new Error(`Error saving file: ${error.message}`));
|
|
1315
1219
|
});
|
|
1316
1220
|
});
|
|
1317
1221
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frostpv",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.12",
|
|
4
4
|
"description": "downloads",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"license": "ISC",
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"axios": "1.12.0",
|
|
16
|
-
"btch-downloader": "6.0.
|
|
16
|
+
"btch-downloader": "6.0.25",
|
|
17
17
|
"btch-downloader-old": "npm:btch-downloader@4.0.15",
|
|
18
18
|
"express": "^5.1.0",
|
|
19
19
|
"ffmpeg-ffprobe-static": "^6.1.1-rc.5",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"path": "^0.12.7",
|
|
23
23
|
"twitter-downloader": "^1.1.8",
|
|
24
24
|
"uuid": "^11.1.0",
|
|
25
|
-
"@tobyg74/tiktok-api-dl": "^1.3.
|
|
25
|
+
"@tobyg74/tiktok-api-dl": "^1.3.7",
|
|
26
26
|
"ytdlp-nodejs": "2.3.4"
|
|
27
27
|
}
|
|
28
28
|
}
|