@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
package/index.js
ADDED
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version: 2.1.1
|
|
3
|
+
* Last update: 20/11/2025
|
|
4
|
+
* Last update: Added custom API for Instagram download to avoid igdl issues
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const axios = require('axios');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
// Import btch-downloader as the only method
|
|
10
|
+
const { igdl, ttdl, fbdown, youtube, mediafire, capcut, gdrive, pinterest } = require('btch-downloader');
|
|
11
|
+
const { TwitterDL } = require('twitter-downloader');
|
|
12
|
+
// Custom Instagram downloader (option3)
|
|
13
|
+
const instagramCustom = require('./instagramcustom');
|
|
14
|
+
// Custom Instagram downloader (cakkatrok)
|
|
15
|
+
const instagramCakkatrok = require('./instagramcakkatrok');
|
|
16
|
+
// Custom TikTok downloader (twitterpicker)
|
|
17
|
+
const tiktokCustom = require('./tiktokcustom');
|
|
18
|
+
|
|
19
|
+
const pathToFfmpeg = require('ffmpeg-ffprobe-static');
|
|
20
|
+
const ffmpeg = require('fluent-ffmpeg');
|
|
21
|
+
ffmpeg.setFfmpegPath(pathToFfmpeg.ffmpegPath);
|
|
22
|
+
|
|
23
|
+
// Liste des URL des plateformes de vidéos
|
|
24
|
+
const videoPlatforms = [
|
|
25
|
+
"https://www.instagram.com",
|
|
26
|
+
"https://instagram.com",
|
|
27
|
+
"https://www.tiktok.com",
|
|
28
|
+
"https://tiktok.com",
|
|
29
|
+
"https://www.facebook.com",
|
|
30
|
+
"https://facebook.com",
|
|
31
|
+
"https://www.youtube.com",
|
|
32
|
+
"https://youtube.com",
|
|
33
|
+
"https://youtu.be",
|
|
34
|
+
"https://www.mediafire.com",
|
|
35
|
+
"https://mediafire.com",
|
|
36
|
+
"https://www.capcut.com",
|
|
37
|
+
"https://capcut.com",
|
|
38
|
+
"https://drive.google.com",
|
|
39
|
+
"https://www.google.com/drive",
|
|
40
|
+
"https://www.pinterest.com",
|
|
41
|
+
"https://pinterest.com",
|
|
42
|
+
"https://x.com",
|
|
43
|
+
"https://www.x.com",
|
|
44
|
+
"https://twitter.com",
|
|
45
|
+
"https://www.twitter.com"
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
// Blacklist links
|
|
49
|
+
const linkCant = [{
|
|
50
|
+
link: "https://vm.tiktok.com",
|
|
51
|
+
reason: "Use real link like 'https://www.tiktok.com'. (just click on your link and copy the link in the browser)"
|
|
52
|
+
}];
|
|
53
|
+
|
|
54
|
+
// Fonction pour vérifier si un lien correspond à une vidéo
|
|
55
|
+
const isVideoLink = (link) => {
|
|
56
|
+
return videoPlatforms.some(platform => link.startsWith(platform));
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Fonction pour vérifier si un lien est blacklist
|
|
60
|
+
const blacklistLink = (link) => {
|
|
61
|
+
return linkCant.find(item => link.includes(item.link));
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const defaultConfig = {
|
|
65
|
+
autocrop: false,
|
|
66
|
+
limitSizeMB: null,
|
|
67
|
+
rotation: null, // Added rotation parameter
|
|
68
|
+
YTBmaxduration: 30, // Default duration for YouTube videos
|
|
69
|
+
useInstaOption3: false // when true force using instagramcustom for Instagram links
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Fonction pour obtenir le type de plateforme à partir de l'URL
|
|
73
|
+
function getPlatformType(url) {
|
|
74
|
+
if (url.includes("instagram.com")) return "instagram";
|
|
75
|
+
return "unknown";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Nouvelle fonction pour essayer la méthode btch-downloader comme fallback
|
|
79
|
+
async function tryFallbackDownload(url) {
|
|
80
|
+
try {
|
|
81
|
+
// Try igdl first
|
|
82
|
+
try {
|
|
83
|
+
const data = await igdl(url);
|
|
84
|
+
if (data && Array.isArray(data) && data[0] && data[0].url) return data[0].url;
|
|
85
|
+
} catch (e) {
|
|
86
|
+
// ignore and try other fallbacks
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// If it's a TikTok URL, try twitterpicker-based custom downloader
|
|
90
|
+
if (url.includes('tiktok.com')) {
|
|
91
|
+
try {
|
|
92
|
+
const custom = await tiktokCustom(url);
|
|
93
|
+
if (typeof custom === 'string' && custom.length) return custom;
|
|
94
|
+
if (Array.isArray(custom) && custom[0] && custom[0].url) return custom[0].url;
|
|
95
|
+
if (custom && custom.url) return custom.url;
|
|
96
|
+
} catch (e) {
|
|
97
|
+
// ignore
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// If it's an Instagram URL, try instagramCustom
|
|
102
|
+
if (url.includes('instagram.com')) {
|
|
103
|
+
try {
|
|
104
|
+
const custom = await instagramCustom(url);
|
|
105
|
+
if (typeof custom === 'string') return custom;
|
|
106
|
+
if (Array.isArray(custom) && custom[0] && custom[0].url) return custom[0].url;
|
|
107
|
+
if (custom && custom.url) return custom.url;
|
|
108
|
+
} catch (e) {
|
|
109
|
+
// ignore
|
|
110
|
+
}
|
|
111
|
+
// Then try cakkatrok as fallback
|
|
112
|
+
try {
|
|
113
|
+
const custom = await instagramCakkatrok(url);
|
|
114
|
+
if (typeof custom === 'string') return custom;
|
|
115
|
+
if (Array.isArray(custom) && custom[0] && custom[0].url) return custom[0].url;
|
|
116
|
+
if (custom && custom.url) return custom.url;
|
|
117
|
+
} catch (e) {
|
|
118
|
+
// ignore
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return null;
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.log(`Fallback download failed: ${error.message}`);
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const MediaDownloader = async (url, options = {}) => {
|
|
130
|
+
const config = { ...defaultConfig, ...options };
|
|
131
|
+
|
|
132
|
+
if (!url || !url.includes("http")) {
|
|
133
|
+
throw new Error("Please specify a video URL...");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
url = extractUrlFromString(url);
|
|
137
|
+
|
|
138
|
+
const blacklisted = blacklistLink(url);
|
|
139
|
+
if (blacklisted) {
|
|
140
|
+
throw new Error(`URL not supported. ${blacklisted.reason}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!isVideoLink(url)) {
|
|
144
|
+
const videofile = await downloadDirectVideo(url, config);
|
|
145
|
+
|
|
146
|
+
if (videofile) {
|
|
147
|
+
return getFileName(videofile);
|
|
148
|
+
} else {
|
|
149
|
+
throw new Error("URL not supported. Please provide a video URL from a valid platform.");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
await deleteTempVideos();
|
|
154
|
+
|
|
155
|
+
if (url.includes('youtube') || url.includes('youtu.be')) {
|
|
156
|
+
if (!options.YTBcookie) {
|
|
157
|
+
throw new Error("YouTube download requires a cookie. Please provide a valid cookie.");
|
|
158
|
+
}
|
|
159
|
+
const videofile = await downloadYoutubeVideo(url, config, options.YTBcookie, options.YTBmaxduration);
|
|
160
|
+
if (videofile) {
|
|
161
|
+
return getFileName(videofile);
|
|
162
|
+
} else {
|
|
163
|
+
throw new Error("URL not supported. Please provide a video URL from a valid platform.");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
else if (url.includes("http")) {
|
|
168
|
+
try {
|
|
169
|
+
// Try the primary method first
|
|
170
|
+
const videofile = await downloadSmartVideo(url, config, options);
|
|
171
|
+
return getFileName(videofile);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.log(`Primary download method failed: ${error.message}`);
|
|
174
|
+
console.log(`Trying fallback method for ${url}...`);
|
|
175
|
+
|
|
176
|
+
// Try the fallback method if the primary fails
|
|
177
|
+
const fallbackUrl = await tryFallbackDownload(url);
|
|
178
|
+
if (fallbackUrl) {
|
|
179
|
+
return getFileName(await downloadDirectVideo(fallbackUrl, config));
|
|
180
|
+
} else {
|
|
181
|
+
throw new Error(`Failed to download video from ${url} with both methods.`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
throw new Error("Please specify a video URL from Instagram, YouTube, or TikTok...");
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// Ajout du support Twitter via twitter-downloader
|
|
190
|
+
async function downloadSmartVideo(url, config, options = {}) {
|
|
191
|
+
try {
|
|
192
|
+
let videoUrl = null;
|
|
193
|
+
if (url.includes('instagram.com')) {
|
|
194
|
+
// If caller forces option3, skip igdl and use instagramCustom directly
|
|
195
|
+
if (!config.useInstaOption3 && !options.useInstaOption3) {
|
|
196
|
+
try {
|
|
197
|
+
const data = await igdl(url);
|
|
198
|
+
if (data && Array.isArray(data) && data[0] && data[0].url) {
|
|
199
|
+
videoUrl = data[0].url;
|
|
200
|
+
}
|
|
201
|
+
} catch (e) {
|
|
202
|
+
// igdl failed; we'll attempt instagramCustom below
|
|
203
|
+
videoUrl = null;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (!videoUrl) {
|
|
208
|
+
// Try the custom instagram downloader
|
|
209
|
+
try {
|
|
210
|
+
const custom = await instagramCustom(url);
|
|
211
|
+
if (typeof custom === 'string' && custom.length) {
|
|
212
|
+
videoUrl = custom;
|
|
213
|
+
} else if (Array.isArray(custom) && custom[0] && custom[0].url) {
|
|
214
|
+
videoUrl = custom[0].url;
|
|
215
|
+
} else if (custom && custom.url) {
|
|
216
|
+
videoUrl = custom.url;
|
|
217
|
+
}
|
|
218
|
+
} catch (err) {
|
|
219
|
+
// instagramCustom failed; try cakkatrok below
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!videoUrl) {
|
|
224
|
+
// Try the cakkatrok instagram downloader
|
|
225
|
+
try {
|
|
226
|
+
const custom = await instagramCakkatrok(url);
|
|
227
|
+
if (typeof custom === 'string' && custom.length) {
|
|
228
|
+
videoUrl = custom;
|
|
229
|
+
} else if (Array.isArray(custom) && custom[0] && custom[0].url) {
|
|
230
|
+
videoUrl = custom[0].url;
|
|
231
|
+
} else if (custom && custom.url) {
|
|
232
|
+
videoUrl = custom.url;
|
|
233
|
+
}
|
|
234
|
+
} catch (err) {
|
|
235
|
+
throw new Error("Can't download this link.");
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
} else if (url.includes('tiktok.com')) {
|
|
239
|
+
const data = await ttdl(url);
|
|
240
|
+
if (!data || !data.video || !data.video[0]) {
|
|
241
|
+
throw new Error("Can't download this link.");
|
|
242
|
+
}
|
|
243
|
+
videoUrl = data.video[0];
|
|
244
|
+
} else if (url.includes('facebook.com')) {
|
|
245
|
+
const data = await fbdown(url);
|
|
246
|
+
if (!data || !data.links || !data.links[0] || !data.links[0].url) {
|
|
247
|
+
throw new Error("Can't download this link.");
|
|
248
|
+
}
|
|
249
|
+
videoUrl = data.links[0].url;
|
|
250
|
+
} else if (url.includes('mediafire.com')) {
|
|
251
|
+
const data = await mediafire(url);
|
|
252
|
+
if (!data || !data.url) {
|
|
253
|
+
throw new Error("Can't download this link.");
|
|
254
|
+
}
|
|
255
|
+
videoUrl = data.url;
|
|
256
|
+
} else if (url.includes('capcut.com')) {
|
|
257
|
+
const data = await capcut(url);
|
|
258
|
+
if (!data || !data.url) {
|
|
259
|
+
throw new Error("Can't download this link.");
|
|
260
|
+
}
|
|
261
|
+
videoUrl = data.url;
|
|
262
|
+
} else if (url.includes('drive.google.com') || url.includes('google.com/drive')) {
|
|
263
|
+
const data = await gdrive(url);
|
|
264
|
+
if (!data || !data.url) {
|
|
265
|
+
throw new Error("Can't download this link.");
|
|
266
|
+
}
|
|
267
|
+
videoUrl = data.url;
|
|
268
|
+
} else if (url.includes('pinterest.com')) {
|
|
269
|
+
const data = await pinterest(url);
|
|
270
|
+
if (!data || !data.url) {
|
|
271
|
+
throw new Error("Can't download this link.");
|
|
272
|
+
}
|
|
273
|
+
videoUrl = data.url;
|
|
274
|
+
} else if (url.includes('x.com') || url.includes('twitter.com')) {
|
|
275
|
+
const data = await TwitterDL(url, {});
|
|
276
|
+
if (!data || !data.result || !data.result.media || !data.result.media[0] || !data.result.media[0].videos || !data.result.media[0].videos[0] || !data.result.media[0].videos[0].url) {
|
|
277
|
+
throw new Error("Can't download this link.");
|
|
278
|
+
}
|
|
279
|
+
videoUrl = data.result.media[0].videos[0].url;
|
|
280
|
+
} else {
|
|
281
|
+
throw new Error("Platform not supported.");
|
|
282
|
+
}
|
|
283
|
+
const response = await axios({
|
|
284
|
+
url: videoUrl,
|
|
285
|
+
method: 'GET',
|
|
286
|
+
responseType: 'stream',
|
|
287
|
+
headers: {
|
|
288
|
+
'accept': '*/*',
|
|
289
|
+
'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',
|
|
290
|
+
'referer': url
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
let fileName = 'temp_video.mp4';
|
|
295
|
+
let count = 1;
|
|
296
|
+
while (fs.existsSync(fileName)) {
|
|
297
|
+
fileName = `temp_video_${count}.mp4`;
|
|
298
|
+
count++;
|
|
299
|
+
}
|
|
300
|
+
const videoWriter = fs.createWriteStream(fileName);
|
|
301
|
+
response.data.pipe(videoWriter);
|
|
302
|
+
return new Promise((resolve, reject) => {
|
|
303
|
+
videoWriter.on('finish', async () => {
|
|
304
|
+
try {
|
|
305
|
+
let processedFile = fileName;
|
|
306
|
+
|
|
307
|
+
ensureFileNotEmpty(processedFile);
|
|
308
|
+
if (config.rotation) {
|
|
309
|
+
processedFile = await rotateVideo(processedFile, config.rotation);
|
|
310
|
+
ensureFileNotEmpty(processedFile);
|
|
311
|
+
}
|
|
312
|
+
if (config.autocrop) {
|
|
313
|
+
processedFile = await autoCrop(processedFile);
|
|
314
|
+
ensureFileNotEmpty(processedFile);
|
|
315
|
+
}
|
|
316
|
+
processedFile = await checkAndCompressVideo(processedFile, config.limitSizeMB);
|
|
317
|
+
ensureFileNotEmpty(processedFile);
|
|
318
|
+
resolve(processedFile);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
reject(error);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
videoWriter.on('error', (error) => reject(error));
|
|
324
|
+
});
|
|
325
|
+
} catch (error) {
|
|
326
|
+
throw new Error(`An error occurred while downloading video: ${error.message}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async function downloadDirectVideo(url, config) {
|
|
331
|
+
try {
|
|
332
|
+
const response = await axios({
|
|
333
|
+
url: url,
|
|
334
|
+
method: 'GET',
|
|
335
|
+
responseType: 'stream',
|
|
336
|
+
headers: {
|
|
337
|
+
'accept': '*/*',
|
|
338
|
+
'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',
|
|
339
|
+
'referer': 'https://www.tiktok.com/'
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
let fileName = 'temp_video.mp4';
|
|
344
|
+
let count = 1;
|
|
345
|
+
while (fs.existsSync(fileName)) {
|
|
346
|
+
fileName = `temp_video_${count}.mp4`;
|
|
347
|
+
count++;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const videoWriter = fs.createWriteStream(fileName);
|
|
351
|
+
response.data.pipe(videoWriter);
|
|
352
|
+
|
|
353
|
+
return new Promise((resolve, reject) => {
|
|
354
|
+
videoWriter.on('finish', async () => {
|
|
355
|
+
try {
|
|
356
|
+
let processedFile = fileName;
|
|
357
|
+
|
|
358
|
+
ensureFileNotEmpty(processedFile);
|
|
359
|
+
|
|
360
|
+
// Apply rotation if specified
|
|
361
|
+
if (config.rotation) {
|
|
362
|
+
processedFile = await rotateVideo(processedFile, config.rotation);
|
|
363
|
+
ensureFileNotEmpty(processedFile);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Apply autocrop if specified
|
|
367
|
+
if (config.autocrop) {
|
|
368
|
+
processedFile = await autoCrop(processedFile);
|
|
369
|
+
ensureFileNotEmpty(processedFile);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Check and compress if size limit is specified
|
|
373
|
+
processedFile = await checkAndCompressVideo(processedFile, config.limitSizeMB);
|
|
374
|
+
ensureFileNotEmpty(processedFile);
|
|
375
|
+
|
|
376
|
+
resolve(processedFile);
|
|
377
|
+
} catch (error) {
|
|
378
|
+
reject(error);
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
videoWriter.on('error', (error) => reject(error));
|
|
382
|
+
});
|
|
383
|
+
} catch (error) {
|
|
384
|
+
throw new Error(`An error occurred while downloading video: ${error.message}`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async function downloadYoutubeVideo(url, config, YTBcookie, YTBmaxduration) {
|
|
389
|
+
try {
|
|
390
|
+
|
|
391
|
+
const agent = ytdl.createAgent(YTBcookie);
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
const info = await ytdl.getInfo(url, {
|
|
395
|
+
agent
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
const durationSeconds = parseInt(info.videoDetails.lengthSeconds, 10);
|
|
399
|
+
|
|
400
|
+
if (durationSeconds > YTBmaxduration) {
|
|
401
|
+
throw new Error(`❌ The video is longer than ${YTBmaxduration} seconds. Aborting.`);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
let formats = info.formats.filter(format => {
|
|
405
|
+
return format.contentLength && parseInt(format.contentLength) <= 10 * 1024 * 1024 && // ≤ 10 MB
|
|
406
|
+
format.hasAudio && format.hasVideo;
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
if (formats.length === 0) {
|
|
410
|
+
formats = info.formats.filter(format => {
|
|
411
|
+
return format.hasAudio && format.hasVideo;
|
|
412
|
+
});
|
|
413
|
+
if (formats.length === 0) {
|
|
414
|
+
throw new Error('❌ No format found .');
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
}
|
|
418
|
+
console.log(formats)
|
|
419
|
+
|
|
420
|
+
const bestFormat = formats.sort((a, b) => b.height - a.height)[0];
|
|
421
|
+
|
|
422
|
+
let fileName = 'temp_video.mp4';
|
|
423
|
+
let count = 1;
|
|
424
|
+
while (fs.existsSync(fileName)) {
|
|
425
|
+
fileName = `temp_video_${count}.mp4`;
|
|
426
|
+
count++;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
const videoStream = ytdl(url, {
|
|
433
|
+
format: bestFormat,
|
|
434
|
+
agent
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
const videoWriter = fs.createWriteStream(fileName);
|
|
438
|
+
videoStream.pipe(videoWriter);
|
|
439
|
+
|
|
440
|
+
return new Promise((resolve, reject) => {
|
|
441
|
+
videoWriter.on('finish', async () => {
|
|
442
|
+
try {
|
|
443
|
+
let processedFile = fileName;
|
|
444
|
+
|
|
445
|
+
ensureFileNotEmpty(processedFile);
|
|
446
|
+
if (config.rotation) {
|
|
447
|
+
processedFile = await rotateVideo(processedFile, config.rotation);
|
|
448
|
+
ensureFileNotEmpty(processedFile);
|
|
449
|
+
}
|
|
450
|
+
if (config.autocrop) {
|
|
451
|
+
processedFile = await autoCrop(processedFile);
|
|
452
|
+
ensureFileNotEmpty(processedFile);
|
|
453
|
+
}
|
|
454
|
+
processedFile = await checkAndCompressVideo(processedFile, config.limitSizeMB);
|
|
455
|
+
ensureFileNotEmpty(processedFile);
|
|
456
|
+
resolve(processedFile);
|
|
457
|
+
} catch (error) {
|
|
458
|
+
reject(error);
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
videoWriter.on('error', (error) => reject(error));
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
} catch (error) {
|
|
465
|
+
throw new Error(`An error occurred while downloading the YouTube video: ${error.message}`);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// New function to handle video rotation
|
|
470
|
+
async function rotateVideo(fileName, rotation) {
|
|
471
|
+
const outputPath = fileName.split('.')[0] + "_rotated.mp4";
|
|
472
|
+
|
|
473
|
+
// Determine rotation angle
|
|
474
|
+
let angle;
|
|
475
|
+
switch (rotation.toLowerCase()) {
|
|
476
|
+
case "left":
|
|
477
|
+
angle = "90";
|
|
478
|
+
break;
|
|
479
|
+
case "right":
|
|
480
|
+
angle = "270";
|
|
481
|
+
break;
|
|
482
|
+
case "180":
|
|
483
|
+
case "flip":
|
|
484
|
+
angle = "180";
|
|
485
|
+
break;
|
|
486
|
+
default:
|
|
487
|
+
throw new Error("Invalid rotation value. Use 'left', 'right', '180', or 'flip'");
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return new Promise((resolve, reject) => {
|
|
491
|
+
ffmpeg(fileName)
|
|
492
|
+
.videoFilters(`transpose=${angle === "90" ? 2 : angle === "270" ? 1 : 0}${angle === "180" ? ",hflip,vflip" : ""}`)
|
|
493
|
+
.output(outputPath)
|
|
494
|
+
.on('end', () => {
|
|
495
|
+
// Delete the original file since we now have the rotated version
|
|
496
|
+
fs.unlinkSync(fileName);
|
|
497
|
+
resolve(outputPath);
|
|
498
|
+
})
|
|
499
|
+
.on('error', (err) => {
|
|
500
|
+
reject(new Error(`Error during video rotation: ${err.message}`));
|
|
501
|
+
})
|
|
502
|
+
.run();
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function ensureFileNotEmpty(filePath) {
|
|
507
|
+
try {
|
|
508
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
509
|
+
throw new Error('Invalid file path');
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (!fs.existsSync(filePath)) {
|
|
513
|
+
throw new Error('File does not exist');
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const stats = fs.statSync(filePath);
|
|
517
|
+
if (!stats || typeof stats.size !== 'number' || stats.size <= 0) {
|
|
518
|
+
try {
|
|
519
|
+
fs.unlinkSync(filePath);
|
|
520
|
+
} catch (e) {
|
|
521
|
+
// ignore
|
|
522
|
+
}
|
|
523
|
+
throw new Error('Downloaded file is empty (0 bytes)');
|
|
524
|
+
}
|
|
525
|
+
} catch (err) {
|
|
526
|
+
// Re-throw as normal Error
|
|
527
|
+
throw new Error(err.message);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function extractUrlFromString(text) {
|
|
532
|
+
const urlRegex = /(https?:\/\/[^\s]+)/;
|
|
533
|
+
const match = text.match(urlRegex);
|
|
534
|
+
if (match) {
|
|
535
|
+
return match[0];
|
|
536
|
+
} else {
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
async function deleteTempVideos() {
|
|
542
|
+
try {
|
|
543
|
+
const files = fs.readdirSync("./");
|
|
544
|
+
const tempVideoFiles = files.filter(file => file.startsWith('temp_video'));
|
|
545
|
+
|
|
546
|
+
for (const file of tempVideoFiles) {
|
|
547
|
+
fs.unlinkSync("./" + file);
|
|
548
|
+
}
|
|
549
|
+
} catch (error) {
|
|
550
|
+
throw new Error(`Error deleting temp_video files: ${error.message}`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
async function autoCrop(fileName) {
|
|
555
|
+
const inputPath = fileName;
|
|
556
|
+
const outputPath = fileName.split('.')[0] + "_cropped.mp4";
|
|
557
|
+
|
|
558
|
+
return new Promise((resolve, reject) => {
|
|
559
|
+
ffmpeg(inputPath)
|
|
560
|
+
.videoFilters('cropdetect')
|
|
561
|
+
.output(outputPath)
|
|
562
|
+
.on('end', function (stdout, stderr) {
|
|
563
|
+
const crop = parseCrop(stderr);
|
|
564
|
+
if (!crop) {
|
|
565
|
+
reject(new Error('Error: Unable to detect crop values.'));
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
ffmpeg(inputPath)
|
|
570
|
+
.videoFilters(`crop=${crop.width}:${crop.height}:${crop.x}:${crop.y}`)
|
|
571
|
+
.on('end', () => {
|
|
572
|
+
// Delete the original file since we now have the cropped version
|
|
573
|
+
fs.unlinkSync(inputPath);
|
|
574
|
+
resolve(outputPath);
|
|
575
|
+
})
|
|
576
|
+
.on('error', (err) => {
|
|
577
|
+
reject(new Error('Error during cropping: ' + err.message));
|
|
578
|
+
}
|
|
579
|
+
).save(outputPath);
|
|
580
|
+
})
|
|
581
|
+
.on('error', (err) => {
|
|
582
|
+
reject(new Error('Error during crop detection: ' + err.message));
|
|
583
|
+
})
|
|
584
|
+
.run();
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
function parseCrop(stderr) {
|
|
588
|
+
const cropRegex = /crop=([0-9]+):([0-9]+):([0-9]+):([0-9]+)/;
|
|
589
|
+
const match = stderr.match(cropRegex);
|
|
590
|
+
if (match) {
|
|
591
|
+
return {
|
|
592
|
+
width: match[1],
|
|
593
|
+
height: match[2],
|
|
594
|
+
x: match[3],
|
|
595
|
+
y: match[4],
|
|
596
|
+
};
|
|
597
|
+
} else {
|
|
598
|
+
return null;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
async function checkAndCompressVideo(filePath, limitSizeMB) {
|
|
604
|
+
if (!limitSizeMB) return filePath;
|
|
605
|
+
|
|
606
|
+
const stats = fs.statSync(filePath);
|
|
607
|
+
const fileSizeInMB = stats.size / (1024 * 1024);
|
|
608
|
+
|
|
609
|
+
if (fileSizeInMB <= limitSizeMB) {
|
|
610
|
+
return filePath;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const compressedFilePath = filePath.split('.')[0] + "_compressed.mp4";
|
|
614
|
+
|
|
615
|
+
return new Promise((resolve, reject) => {
|
|
616
|
+
ffmpeg(filePath)
|
|
617
|
+
.outputOptions([
|
|
618
|
+
'-vf', 'scale=640:-2',
|
|
619
|
+
'-b:v', '500k',
|
|
620
|
+
'-b:a', '128k',
|
|
621
|
+
'-movflags', 'faststart'
|
|
622
|
+
])
|
|
623
|
+
.output(compressedFilePath)
|
|
624
|
+
.on('end', () => {
|
|
625
|
+
fs.unlinkSync(filePath);
|
|
626
|
+
resolve(compressedFilePath);
|
|
627
|
+
})
|
|
628
|
+
.on('error', (err) => {
|
|
629
|
+
reject(new Error('Error during compression: ' + err.message));
|
|
630
|
+
})
|
|
631
|
+
.run();
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function getFileName(filePath) {
|
|
636
|
+
return filePath.split('/').pop();
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
MediaDownloader.isVideoLink = isVideoLink;
|
|
640
|
+
|
|
641
|
+
module.exports = MediaDownloader;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const Instagram = require('cakkatrok-instagram-downloader');
|
|
2
|
+
|
|
3
|
+
module.exports = async function instagramCakkatrok(instaUrl) {
|
|
4
|
+
try {
|
|
5
|
+
if (!instaUrl || typeof instaUrl !== 'string') {
|
|
6
|
+
throw new Error('Invalid URL');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const result = await Instagram.media(instaUrl);
|
|
10
|
+
|
|
11
|
+
if (!result || !Array.isArray(result.media) || result.media.length === 0) {
|
|
12
|
+
throw new Error('No content found');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const downloadUrl = result.media[0].url;
|
|
16
|
+
|
|
17
|
+
if (!downloadUrl) {
|
|
18
|
+
throw new Error('Download URL not found in the response');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return downloadUrl;
|
|
22
|
+
} catch (err) {
|
|
23
|
+
throw new Error(`instagramCakkatrok failed: ${err.message}`);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const { igdl } = require('ab-downloader');
|
|
2
|
+
|
|
3
|
+
module.exports = async function instagramCustom(instaUrl) {
|
|
4
|
+
try {
|
|
5
|
+
if (!instaUrl || typeof instaUrl !== 'string') {
|
|
6
|
+
throw new Error('Invalid URL');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const data = await igdl(instaUrl);
|
|
10
|
+
|
|
11
|
+
if (!data || !Array.isArray(data) || data.length === 0) {
|
|
12
|
+
throw new Error('No content found');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Assuming the first element contains the desired URL
|
|
16
|
+
const downloadUrl = data[0].url;
|
|
17
|
+
|
|
18
|
+
if (!downloadUrl) {
|
|
19
|
+
throw new Error('Download URL not found in the response');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return downloadUrl;
|
|
23
|
+
} catch (err) {
|
|
24
|
+
// It's good practice to wrap the original error message
|
|
25
|
+
throw new Error(`instagramCustom failed: ${err.message}`);
|
|
26
|
+
}
|
|
27
|
+
};
|