@media-downloaders/v2 2.0.1
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/.old/index old.js +439 -0
- package/.old/index old2.js +528 -0
- package/.old/index.js +277 -0
- package/.old/old.js +513 -0
- package/.old/package.json +38 -0
- package/README.md +133 -0
- package/index.js +641 -0
- package/instagramcakkatrok.js +25 -0
- package/instagramcustom.js +27 -0
- package/package.json +40 -0
- package/tiktokcustom.js +114 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Last update :
|
|
4
|
+
* Added rotation option for videos : left or right
|
|
5
|
+
*
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const axios = require('axios');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const { alldown } = require("nayan-videos-downloader");
|
|
11
|
+
// Import btch-downloader as a fallback method
|
|
12
|
+
const { igdl, ttdl, fbdown, twitter, youtube } = require('btch-downloader');
|
|
13
|
+
|
|
14
|
+
const pathToFfmpeg = require('ffmpeg-ffprobe-static');
|
|
15
|
+
const ffmpeg = require('fluent-ffmpeg');
|
|
16
|
+
ffmpeg.setFfmpegPath(pathToFfmpeg.ffmpegPath);
|
|
17
|
+
|
|
18
|
+
// Liste des URL des plateformes de vidéos
|
|
19
|
+
const videoPlatforms = [
|
|
20
|
+
"https://open.spotify.com",
|
|
21
|
+
"https://www.facebook.com",
|
|
22
|
+
"https://facebook.com",
|
|
23
|
+
"https://www.tiktok.com",
|
|
24
|
+
"https://tiktok.com",
|
|
25
|
+
"https://www.x.com",
|
|
26
|
+
"https://x.com",
|
|
27
|
+
"https://www.twitter.com",
|
|
28
|
+
"https://twitter.com",
|
|
29
|
+
"https://www.instagram.com",
|
|
30
|
+
"https://instagram.com",
|
|
31
|
+
"https://www.youtube.com",
|
|
32
|
+
"https://youtube.com",
|
|
33
|
+
"https://youtu.be",
|
|
34
|
+
"https://www.pinterest.com",
|
|
35
|
+
"https://pinterest.com",
|
|
36
|
+
"https://drive.google.com",
|
|
37
|
+
"https://www.google.com/drive",
|
|
38
|
+
"https://www.capcut.com",
|
|
39
|
+
"https://capcut.com",
|
|
40
|
+
"https://www.likee.video",
|
|
41
|
+
"https://likee.video",
|
|
42
|
+
"https://www.threads.net",
|
|
43
|
+
"https://threads.net"
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
// Blacklist links
|
|
47
|
+
const linkCant = [{ link: "https://vm.tiktok.com", reason: "Use real link like 'https://www.tiktok.com'. (just click on your link and copy the link in the browser)" }];
|
|
48
|
+
|
|
49
|
+
// Fonction pour vérifier si un lien correspond à une vidéo
|
|
50
|
+
const isVideoLink = (link) => {
|
|
51
|
+
return videoPlatforms.some(platform => link.startsWith(platform));
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
function blacklistLink(link) {
|
|
55
|
+
const found = linkCant.find(item => link.includes(item.link));
|
|
56
|
+
return found;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const defaultConfig = {
|
|
60
|
+
autocrop: false,
|
|
61
|
+
limitSizeMB: null,
|
|
62
|
+
rotation: null, // Added rotation parameter
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Fonction pour obtenir le type de plateforme à partir de l'URL
|
|
66
|
+
function getPlatformType(url) {
|
|
67
|
+
if (url.includes("instagram.com")) return "instagram";
|
|
68
|
+
if (url.includes("tiktok.com")) return "tiktok";
|
|
69
|
+
if (url.includes("facebook.com")) return "facebook";
|
|
70
|
+
if (url.includes("twitter.com") || url.includes("x.com")) return "twitter";
|
|
71
|
+
if (url.includes("youtube.com") || url.includes("youtu.be")) return "youtube";
|
|
72
|
+
return "unknown";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Nouvelle fonction pour essayer la méthode btch-downloader comme fallback
|
|
76
|
+
async function tryFallbackDownload(url) {
|
|
77
|
+
const platform = getPlatformType(url);
|
|
78
|
+
let data;
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
switch (platform) {
|
|
82
|
+
case "instagram":
|
|
83
|
+
data = await igdl(url);
|
|
84
|
+
break;
|
|
85
|
+
case "tiktok":
|
|
86
|
+
data = await ttdl(url);
|
|
87
|
+
break;
|
|
88
|
+
case "facebook":
|
|
89
|
+
data = await fbdown(url);
|
|
90
|
+
break;
|
|
91
|
+
case "twitter":
|
|
92
|
+
data = await twitter(url);
|
|
93
|
+
break;
|
|
94
|
+
case "youtube":
|
|
95
|
+
data = await youtube(url);
|
|
96
|
+
break;
|
|
97
|
+
default:
|
|
98
|
+
throw new Error("Platform not supported by fallback downloader");
|
|
99
|
+
}
|
|
100
|
+
//console.log(data)
|
|
101
|
+
|
|
102
|
+
// Extract the video URL from the response
|
|
103
|
+
let videoUrl = null;
|
|
104
|
+
|
|
105
|
+
if (data) {
|
|
106
|
+
// Handle different response formats for different platforms
|
|
107
|
+
if (platform === "instagram" && data && data.length > 0) {
|
|
108
|
+
videoUrl = data[0].url;
|
|
109
|
+
} else if (platform === "tiktok" && data && data.video && data.video[0]) {
|
|
110
|
+
videoUrl = data.video[0];
|
|
111
|
+
} else if (platform === "twitter" && data && data.url && data.url.length > 0 && data.url[1] && data.url[1].sd) {
|
|
112
|
+
videoUrl = data.url[1].sd
|
|
113
|
+
} else if (platform === "youtube" && data.link && data.link.length > 0) {
|
|
114
|
+
// Get the highest quality video URL
|
|
115
|
+
data.link.sort((a, b) => (b.size || 0) - (a.size || 0));
|
|
116
|
+
videoUrl = data.link[0].url;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
return videoUrl;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.log(`Fallback download failed for ${platform}: ${error.message}`);
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const MediaDownloader = async (url, options = {}) => {
|
|
129
|
+
const config = { ...defaultConfig, ...options };
|
|
130
|
+
|
|
131
|
+
if (!url || !url.includes("http")) {
|
|
132
|
+
throw new Error("Please specify a video URL...");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
url = extractUrlFromString(url);
|
|
136
|
+
|
|
137
|
+
if (blacklistLink(url)) {
|
|
138
|
+
let obj = blacklistLink(url);
|
|
139
|
+
if (obj.reason) {
|
|
140
|
+
throw new Error("URL not supported. " + obj.reason);
|
|
141
|
+
} else {
|
|
142
|
+
throw new Error("URL blacklisted and not supported");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
else if (!isVideoLink(url)) {
|
|
147
|
+
const videofile = await downloadDirectVideo(url, config);
|
|
148
|
+
|
|
149
|
+
if (videofile) {
|
|
150
|
+
return videofile;
|
|
151
|
+
} else {
|
|
152
|
+
throw new Error("URL not supported. Please provide a video URL from a valid platform.");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
await deleteTempVideos();
|
|
157
|
+
|
|
158
|
+
if (url.includes("http")) {
|
|
159
|
+
try {
|
|
160
|
+
// Try the primary method first
|
|
161
|
+
const videofile = await downloadSmartVideo(url, config);
|
|
162
|
+
return videofile;
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.log(`Primary download method failed: ${error.message}`);
|
|
165
|
+
console.log(`Trying fallback method for ${url}...`);
|
|
166
|
+
|
|
167
|
+
// Try the fallback method if the primary fails
|
|
168
|
+
const fallbackUrl = await tryFallbackDownload(url);
|
|
169
|
+
if (fallbackUrl) {
|
|
170
|
+
// console.log(`Fallback URL found: ${fallbackUrl}`);
|
|
171
|
+
return await downloadDirectVideo(fallbackUrl, config);
|
|
172
|
+
} else {
|
|
173
|
+
throw new Error(`Failed to download video from ${url} with both methods.`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
throw new Error("Please specify a video URL from Instagram, YouTube, or TikTok...");
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
async function downloadSmartVideo(url, config) {
|
|
182
|
+
try {
|
|
183
|
+
let data = await alldown(url);
|
|
184
|
+
|
|
185
|
+
if (!data || !data.data) {
|
|
186
|
+
throw new Error("Can't download this link.");
|
|
187
|
+
}
|
|
188
|
+
if (data.data.low) {
|
|
189
|
+
url = data.data.low;
|
|
190
|
+
} else if (data.data.high) {
|
|
191
|
+
url = data.data.high;
|
|
192
|
+
} else {
|
|
193
|
+
throw new Error("Can't download this link.");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const response = await axios({
|
|
197
|
+
url: url,
|
|
198
|
+
method: 'GET',
|
|
199
|
+
responseType: 'stream'
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
let fileName = 'temp_video.mp4';
|
|
203
|
+
let count = 1;
|
|
204
|
+
while (fs.existsSync(fileName)) {
|
|
205
|
+
fileName = `temp_video_${count}.mp4`;
|
|
206
|
+
count++;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const videoWriter = fs.createWriteStream(fileName);
|
|
210
|
+
response.data.pipe(videoWriter);
|
|
211
|
+
|
|
212
|
+
return new Promise((resolve, reject) => {
|
|
213
|
+
videoWriter.on('finish', async () => {
|
|
214
|
+
try {
|
|
215
|
+
let processedFile = fileName;
|
|
216
|
+
|
|
217
|
+
// Apply rotation if specified
|
|
218
|
+
if (config.rotation) {
|
|
219
|
+
processedFile = await rotateVideo(processedFile, config.rotation);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Apply autocrop if specified
|
|
223
|
+
if (config.autocrop) {
|
|
224
|
+
processedFile = await autoCrop(processedFile);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Check and compress if size limit is specified
|
|
228
|
+
processedFile = await checkAndCompressVideo(processedFile, config.limitSizeMB);
|
|
229
|
+
|
|
230
|
+
resolve(processedFile);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
reject(error);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
videoWriter.on('error', (error) => reject(error));
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
} catch (error) {
|
|
239
|
+
throw new Error(`An error occurred while downloading video: ${error.message}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function downloadDirectVideo(url, config) {
|
|
244
|
+
try {
|
|
245
|
+
const response = await axios({
|
|
246
|
+
url: url,
|
|
247
|
+
method: 'GET',
|
|
248
|
+
responseType: 'stream'
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
let fileName = 'temp_video.mp4';
|
|
252
|
+
let count = 1;
|
|
253
|
+
while (fs.existsSync(fileName)) {
|
|
254
|
+
fileName = `temp_video_${count}.mp4`;
|
|
255
|
+
count++;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const videoWriter = fs.createWriteStream(fileName);
|
|
259
|
+
response.data.pipe(videoWriter);
|
|
260
|
+
|
|
261
|
+
return new Promise((resolve, reject) => {
|
|
262
|
+
videoWriter.on('finish', async () => {
|
|
263
|
+
try {
|
|
264
|
+
let processedFile = fileName;
|
|
265
|
+
|
|
266
|
+
// Apply rotation if specified
|
|
267
|
+
if (config.rotation) {
|
|
268
|
+
processedFile = await rotateVideo(processedFile, config.rotation);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Apply autocrop if specified
|
|
272
|
+
if (config.autocrop) {
|
|
273
|
+
processedFile = await autoCrop(processedFile);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Check and compress if size limit is specified
|
|
277
|
+
processedFile = await checkAndCompressVideo(processedFile, config.limitSizeMB);
|
|
278
|
+
|
|
279
|
+
resolve(processedFile);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
reject(error);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
videoWriter.on('error', (error) => reject(error));
|
|
285
|
+
});
|
|
286
|
+
} catch (error) {
|
|
287
|
+
throw new Error(`An error occurred while downloading video: ${error.message}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// New function to handle video rotation
|
|
292
|
+
async function rotateVideo(fileName, rotation) {
|
|
293
|
+
const outputPath = fileName.split('.')[0] + "_rotated.mp4";
|
|
294
|
+
|
|
295
|
+
// Determine rotation angle
|
|
296
|
+
let angle;
|
|
297
|
+
switch (rotation.toLowerCase()) {
|
|
298
|
+
case "left":
|
|
299
|
+
angle = "90";
|
|
300
|
+
break;
|
|
301
|
+
case "right":
|
|
302
|
+
angle = "270";
|
|
303
|
+
break;
|
|
304
|
+
case "180":
|
|
305
|
+
case "flip":
|
|
306
|
+
angle = "180";
|
|
307
|
+
break;
|
|
308
|
+
default:
|
|
309
|
+
throw new Error("Invalid rotation value. Use 'left', 'right', '180', or 'flip'");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return new Promise((resolve, reject) => {
|
|
313
|
+
ffmpeg(fileName)
|
|
314
|
+
.videoFilters(`transpose=${angle === "90" ? 2 : angle === "270" ? 1 : 0}${angle === "180" ? ",hflip,vflip" : ""}`)
|
|
315
|
+
.output(outputPath)
|
|
316
|
+
.on('end', () => {
|
|
317
|
+
// Delete the original file since we now have the rotated version
|
|
318
|
+
fs.unlinkSync(fileName);
|
|
319
|
+
resolve(outputPath);
|
|
320
|
+
})
|
|
321
|
+
.on('error', (err) => {
|
|
322
|
+
reject(new Error(`Error during video rotation: ${err.message}`));
|
|
323
|
+
})
|
|
324
|
+
.run();
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function extractUrlFromString(text) {
|
|
329
|
+
const urlRegex = /(https?:\/\/[^\s]+)/;
|
|
330
|
+
const match = text.match(urlRegex);
|
|
331
|
+
if (match) {
|
|
332
|
+
return match[0];
|
|
333
|
+
} else {
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async function deleteTempVideos() {
|
|
339
|
+
try {
|
|
340
|
+
const files = fs.readdirSync("./");
|
|
341
|
+
const tempVideoFiles = files.filter(file => file.startsWith('temp_video'));
|
|
342
|
+
|
|
343
|
+
for (const file of tempVideoFiles) {
|
|
344
|
+
fs.unlinkSync("./" + file);
|
|
345
|
+
}
|
|
346
|
+
} catch (error) {
|
|
347
|
+
throw new Error(`Error deleting temp_video files: ${error.message}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async function autoCrop(fileName) {
|
|
352
|
+
const inputPath = fileName;
|
|
353
|
+
const outputPath = fileName.split('.')[0] + "_cropped.mp4";
|
|
354
|
+
|
|
355
|
+
return new Promise((resolve, reject) => {
|
|
356
|
+
ffmpeg(inputPath)
|
|
357
|
+
.videoFilters('cropdetect')
|
|
358
|
+
.output(outputPath)
|
|
359
|
+
.on('end', function (stdout, stderr) {
|
|
360
|
+
const crop = parseCrop(stderr);
|
|
361
|
+
if (!crop) {
|
|
362
|
+
reject(new Error('Erreur: Impossible de détecter les valeurs de crop.'));
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
ffmpeg(inputPath)
|
|
367
|
+
.videoFilters(`crop=${crop.width}:${crop.height}:${crop.x}:${crop.y}`)
|
|
368
|
+
.on('end', () => {
|
|
369
|
+
// Delete the original file since we now have the cropped version
|
|
370
|
+
fs.unlinkSync(inputPath);
|
|
371
|
+
resolve(outputPath);
|
|
372
|
+
})
|
|
373
|
+
.on('error', (err) => {
|
|
374
|
+
reject(new Error('Erreur lors du rognage: ' + err.message));
|
|
375
|
+
})
|
|
376
|
+
.save(outputPath);
|
|
377
|
+
})
|
|
378
|
+
.on('error', (err) => {
|
|
379
|
+
reject(new Error('Erreur lors de la détection du crop: ' + err.message));
|
|
380
|
+
})
|
|
381
|
+
.run();
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
function parseCrop(stderr) {
|
|
385
|
+
const cropRegex = /crop=([0-9]+):([0-9]+):([0-9]+):([0-9]+)/;
|
|
386
|
+
const match = stderr.match(cropRegex);
|
|
387
|
+
if (match) {
|
|
388
|
+
return {
|
|
389
|
+
width: match[1],
|
|
390
|
+
height: match[2],
|
|
391
|
+
x: match[3],
|
|
392
|
+
y: match[4],
|
|
393
|
+
};
|
|
394
|
+
} else {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
async function checkAndCompressVideo(filePath, limitSizeMB) {
|
|
401
|
+
if (!limitSizeMB) return filePath;
|
|
402
|
+
|
|
403
|
+
const stats = fs.statSync(filePath);
|
|
404
|
+
const fileSizeInMB = stats.size / (1024 * 1024);
|
|
405
|
+
|
|
406
|
+
if (fileSizeInMB <= limitSizeMB) {
|
|
407
|
+
return filePath;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const compressedFilePath = filePath.split('.')[0] + "_compressed.mp4";
|
|
411
|
+
|
|
412
|
+
return new Promise((resolve, reject) => {
|
|
413
|
+
ffmpeg(filePath)
|
|
414
|
+
.outputOptions([
|
|
415
|
+
'-vf', 'scale=640:-2',
|
|
416
|
+
'-b:v', '500k',
|
|
417
|
+
'-b:a', '128k',
|
|
418
|
+
'-movflags', 'faststart'
|
|
419
|
+
])
|
|
420
|
+
.output(compressedFilePath)
|
|
421
|
+
.on('end', () => {
|
|
422
|
+
fs.unlinkSync(filePath);
|
|
423
|
+
resolve(compressedFilePath);
|
|
424
|
+
})
|
|
425
|
+
.on('error', (err) => {
|
|
426
|
+
reject(new Error('Erreur lors de la compression: ' + err.message));
|
|
427
|
+
})
|
|
428
|
+
.run();
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
MediaDownloader.isVideoLink = isVideoLink;
|
|
438
|
+
|
|
439
|
+
module.exports = MediaDownloader;
|