@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/old.js ADDED
@@ -0,0 +1,513 @@
1
+ const axios = require('axios');
2
+ const fs = require('fs');
3
+ const { alldown } = require("nayan-videos-downloader");
4
+ // Import btch-downloader as a fallback method
5
+ const { igdl, ttdl, fbdown, twitter, youtube } = require('btch-downloader');
6
+ const ytdl = require('ytdl-core'); // Imported for YouTube downloads
7
+
8
+ const pathToFfmpeg = require('ffmpeg-ffprobe-static');
9
+ const ffmpeg = require('fluent-ffmpeg');
10
+ ffmpeg.setFfmpegPath(pathToFfmpeg.ffmpegPath);
11
+
12
+ // Liste des URL des plateformes de vidéos
13
+ const videoPlatforms = [
14
+ "https://open.spotify.com",
15
+ "https://www.facebook.com",
16
+ "https://facebook.com",
17
+ "https://www.tiktok.com",
18
+ "https://tiktok.com",
19
+ "https://www.x.com",
20
+ "https://x.com",
21
+ "https://www.twitter.com",
22
+ "https://twitter.com",
23
+ "https://www.instagram.com",
24
+ "https://instagram.com",
25
+ "https://www.youtube.com",
26
+ "https://youtube.com",
27
+ "https://youtu.be",
28
+ "https://www.pinterest.com",
29
+ "https://pinterest.com",
30
+ "https://drive.google.com",
31
+ "https://www.google.com/drive",
32
+ "https://www.capcut.com",
33
+ "https://capcut.com",
34
+ "https://www.likee.video",
35
+ "https://likee.video",
36
+ "https://www.threads.net",
37
+ "https://threads.net"
38
+ ];
39
+
40
+ // Blacklist links
41
+ const linkCant = [{
42
+ link: "https://vm.tiktok.com",
43
+ reason: "Use real link like 'https://www.tiktok.com'. (just click on your link and copy the link in the browser)"
44
+ }];
45
+
46
+ // Fonction pour vérifier si un lien correspond à une vidéo
47
+ const isVideoLink = (link) => {
48
+ return videoPlatforms.some(platform => link.startsWith(platform));
49
+ };
50
+
51
+ // Fonction pour vérifier si un lien est blacklist
52
+ const blacklistLink = (link) => {
53
+ return linkCant.find(item => link.includes(item.link));
54
+ };
55
+
56
+ const defaultConfig = {
57
+ autocrop: false,
58
+ limitSizeMB: null,
59
+ rotation: null, // Added rotation parameter
60
+ };
61
+
62
+ // Fonction pour obtenir le type de plateforme à partir de l'URL
63
+ function getPlatformType(url) {
64
+ if (url.includes("instagram.com")) return "instagram";
65
+ if (url.includes("tiktok.com")) return "tiktok";
66
+ if (url.includes("facebook.com")) return "facebook";
67
+ if (url.includes("twitter.com") || url.includes("x.com")) return "twitter";
68
+ if (url.includes("youtube.com") || url.includes("youtu.be")) return "youtube";
69
+ return "unknown";
70
+ }
71
+
72
+ // Nouvelle fonction pour essayer la méthode btch-downloader comme fallback
73
+ async function tryFallbackDownload(url) {
74
+ const platform = getPlatformType(url);
75
+ let data;
76
+
77
+ try {
78
+ switch (platform) {
79
+ case "instagram":
80
+ data = await igdl(url);
81
+ break;
82
+ case "tiktok":
83
+ data = await ttdl(url);
84
+ break;
85
+ case "facebook":
86
+ data = await fbdown(url);
87
+ break;
88
+ case "twitter":
89
+ data = await twitter(url);
90
+ break;
91
+ case "youtube":
92
+ data = await youtube(url);
93
+ break;
94
+ default:
95
+ throw new Error("Platform not supported by fallback downloader");
96
+ }
97
+ // Extract the video URL from the response
98
+ let videoUrl = null;
99
+
100
+ if (data) {
101
+ // Handle different response formats for different platforms
102
+ if (platform === "instagram" && data && data.length > 0) {
103
+ videoUrl = data[0].url;
104
+ } else if (platform === "tiktok" && data && data.video && data.video[0]) {
105
+ videoUrl = data.video[0];
106
+ } else if (platform === "twitter" && data && data.url && data.url.length > 0 && data.url[1] && data.url[1].sd) {
107
+ videoUrl = data.url[1].sd;
108
+ } else if (platform === "youtube" && data.link && data.link.length > 0) {
109
+ // Get the highest quality video URL
110
+ data.link.sort((a, b) => (b.size || 0) - (a.size || 0));
111
+ videoUrl = data.link[0].url;
112
+ }
113
+ }
114
+
115
+ return videoUrl;
116
+ } catch (error) {
117
+ console.log(`Fallback download failed for ${platform}: ${error.message}`);
118
+ return null;
119
+ }
120
+ }
121
+
122
+ const MediaDownloader = async (url, options = {}) => {
123
+ const config = { ...defaultConfig, ...options };
124
+
125
+ if (!url || !url.includes("http")) {
126
+ throw new Error("Please specify a video URL...");
127
+ }
128
+
129
+ url = extractUrlFromString(url);
130
+
131
+ const blacklisted = blacklistLink(url);
132
+ if (blacklisted) {
133
+ throw new Error(`URL not supported. ${blacklisted.reason}`);
134
+ }
135
+
136
+ if (!isVideoLink(url)) {
137
+ const videofile = await downloadDirectVideo(url, config);
138
+
139
+ if (videofile) {
140
+ return videofile;
141
+ } else {
142
+ throw new Error("URL not supported. Please provide a video URL from a valid platform.");
143
+ }
144
+ }
145
+
146
+ await deleteTempVideos();
147
+
148
+ if (url.includes('youtube') || url.includes('youtu.be')) {
149
+ if (!options.cookie) {
150
+ throw new Error("YouTube download requires a cookie. Please provide a valid cookie.");
151
+ }
152
+ const videofile = await downloadYoutubeVideo(url, config, options.cookie);
153
+ if (videofile) {
154
+ return videofile;
155
+ } else {
156
+ throw new Error("URL not supported. Please provide a video URL from a valid platform.");
157
+ }
158
+ }
159
+
160
+ else if (url.includes("http")) {
161
+ try {
162
+ // Try the primary method first
163
+ const videofile = await downloadSmartVideo(url, config);
164
+ return videofile;
165
+ } catch (error) {
166
+ console.log(`Primary download method failed: ${error.message}`);
167
+ console.log(`Trying fallback method for ${url}...`);
168
+
169
+ // Try the fallback method if the primary fails
170
+ const fallbackUrl = await tryFallbackDownload(url);
171
+ if (fallbackUrl) {
172
+ return await downloadDirectVideo(fallbackUrl, config);
173
+ } else {
174
+ throw new Error(`Failed to download video from ${url} with both methods.`);
175
+ }
176
+ }
177
+ } else {
178
+ throw new Error("Please specify a video URL from Instagram, YouTube, or TikTok...");
179
+ }
180
+ };
181
+
182
+ async function downloadSmartVideo(url, config) {
183
+ try {
184
+ let data = await alldown(url);
185
+
186
+ if (!data || !data.data) {
187
+ throw new Error("Can't download this link.");
188
+ }
189
+ if (data.data.low) {
190
+ url = data.data.low;
191
+ } else if (data.data.high) {
192
+ url = data.data.high;
193
+ } else {
194
+ throw new Error("Can't download this link.");
195
+ }
196
+
197
+ const response = await axios({
198
+ url: url,
199
+ method: 'GET',
200
+ responseType: 'stream'
201
+ });
202
+
203
+ let fileName = 'temp_video.mp4';
204
+ let count = 1;
205
+ while (fs.existsSync(fileName)) {
206
+ fileName = `temp_video_${count}.mp4`;
207
+ count++;
208
+ }
209
+
210
+ const videoWriter = fs.createWriteStream(fileName);
211
+ response.data.pipe(videoWriter);
212
+
213
+ return new Promise((resolve, reject) => {
214
+ videoWriter.on('finish', async () => {
215
+ try {
216
+ let processedFile = fileName;
217
+
218
+ // Apply rotation if specified
219
+ if (config.rotation) {
220
+ processedFile = await rotateVideo(processedFile, config.rotation);
221
+ }
222
+
223
+ // Apply autocrop if specified
224
+ if (config.autocrop) {
225
+ processedFile = await autoCrop(processedFile);
226
+ }
227
+
228
+ // Check and compress if size limit is specified
229
+ processedFile = await checkAndCompressVideo(processedFile, config.limitSizeMB);
230
+
231
+ resolve(processedFile);
232
+ } catch (error) {
233
+ reject(error);
234
+ }
235
+ });
236
+ videoWriter.on('error', (error) => reject(error));
237
+ });
238
+
239
+ } catch (error) {
240
+ throw new Error(`An error occurred while downloading video: ${error.message}`);
241
+ }
242
+ }
243
+
244
+ async function downloadDirectVideo(url, config) {
245
+ try {
246
+ const response = await axios({
247
+ url: url,
248
+ method: 'GET',
249
+ responseType: 'stream'
250
+ });
251
+
252
+ let fileName = 'temp_video.mp4';
253
+ let count = 1;
254
+ while (fs.existsSync(fileName)) {
255
+ fileName = `temp_video_${count}.mp4`;
256
+ count++;
257
+ }
258
+
259
+ const videoWriter = fs.createWriteStream(fileName);
260
+ response.data.pipe(videoWriter);
261
+
262
+ return new Promise((resolve, reject) => {
263
+ videoWriter.on('finish', async () => {
264
+ try {
265
+ let processedFile = fileName;
266
+
267
+ // Apply rotation if specified
268
+ if (config.rotation) {
269
+ processedFile = await rotateVideo(processedFile, config.rotation);
270
+ }
271
+
272
+ // Apply autocrop if specified
273
+ if (config.autocrop) {
274
+ processedFile = await autoCrop(processedFile);
275
+ }
276
+
277
+ // Check and compress if size limit is specified
278
+ processedFile = await checkAndCompressVideo(processedFile, config.limitSizeMB);
279
+
280
+ resolve(processedFile);
281
+ } catch (error) {
282
+ reject(error);
283
+ }
284
+ });
285
+ videoWriter.on('error', (error) => reject(error));
286
+ });
287
+ } catch (error) {
288
+ throw new Error(`An error occurred while downloading video: ${error.message}`);
289
+ }
290
+ }
291
+
292
+ async function downloadYoutubeVideo(url, config, cookie) {
293
+ try {
294
+ const info = await ytdl.getInfo(url, {
295
+ requestOptions: {
296
+ headers: {
297
+ cookie: cookie,
298
+ },
299
+ },
300
+ });
301
+
302
+ const durationSeconds = parseInt(info.videoDetails.lengthSeconds, 10);
303
+
304
+ if (durationSeconds > 60) {
305
+ throw new Error('❌ The video is longer than 1 minute. Aborting.');
306
+ }
307
+
308
+ const formats = info.formats.filter(format => {
309
+ return format.contentLength && parseInt(format.contentLength) <= 10 * 1024 * 1024 && // ≤ 10 MB
310
+ format.hasAudio && format.hasVideo;
311
+ });
312
+
313
+ if (formats.length === 0) {
314
+ throw new Error('❌ No format found under 10 MB.');
315
+ }
316
+
317
+ const bestFormat = formats.sort((a, b) => b.height - a.height)[0];
318
+
319
+ let fileName = 'temp_video.mp4';
320
+ let count = 1;
321
+ while (fs.existsSync(fileName)) {
322
+ fileName = `temp_video_${count}.mp4`;
323
+ count++;
324
+ }
325
+
326
+ const videoStream = ytdl(url, {
327
+ format: bestFormat,
328
+ requestOptions: {
329
+ headers: {
330
+ cookie: cookie,
331
+ },
332
+ },
333
+ });
334
+
335
+ const videoWriter = fs.createWriteStream(fileName);
336
+ videoStream.pipe(videoWriter);
337
+
338
+ return new Promise((resolve, reject) => {
339
+ videoWriter.on('finish', async () => {
340
+ try {
341
+ let processedFile = fileName;
342
+
343
+ // Apply rotation if specified
344
+ if (config.rotation) {
345
+ processedFile = await rotateVideo(processedFile, config.rotation);
346
+ }
347
+
348
+ // Apply autocrop if specified
349
+ if (config.autocrop) {
350
+ processedFile = await autoCrop(processedFile);
351
+ }
352
+
353
+ // Check and compress if size limit is specified
354
+ processedFile = await checkAndCompressVideo(processedFile, config.limitSizeMB);
355
+
356
+ resolve(processedFile);
357
+ } catch (error) {
358
+ reject(error);
359
+ }
360
+ });
361
+ videoWriter.on('error', (error) => reject(error));
362
+ });
363
+
364
+ } catch (error) {
365
+ throw new Error(`An error occurred while downloading the YouTube video: ${error.message}`);
366
+ }
367
+ }
368
+
369
+ // New function to handle video rotation
370
+ async function rotateVideo(fileName, rotation) {
371
+ const outputPath = fileName.split('.')[0] + "_rotated.mp4";
372
+
373
+ // Determine rotation angle
374
+ let angle;
375
+ switch (rotation.toLowerCase()) {
376
+ case "left":
377
+ angle = "90";
378
+ break;
379
+ case "right":
380
+ angle = "270";
381
+ break;
382
+ case "180":
383
+ case "flip":
384
+ angle = "180";
385
+ break;
386
+ default:
387
+ throw new Error("Invalid rotation value. Use 'left', 'right', '180', or 'flip'");
388
+ }
389
+
390
+ return new Promise((resolve, reject) => {
391
+ ffmpeg(fileName)
392
+ .videoFilters(`transpose=${angle === "90" ? 2 : angle === "270" ? 1 : 0}${angle === "180" ? ",hflip,vflip" : ""}`)
393
+ .output(outputPath)
394
+ .on('end', () => {
395
+ // Delete the original file since we now have the rotated version
396
+ fs.unlinkSync(fileName);
397
+ resolve(outputPath);
398
+ })
399
+ .on('error', (err) => {
400
+ reject(new Error(`Error during video rotation: ${err.message}`));
401
+ })
402
+ .run();
403
+ });
404
+ }
405
+
406
+
407
+ function extractUrlFromString(text) {
408
+ const urlRegex = /(https?:\/\/[^\s]+)/;
409
+ const match = text.match(urlRegex);
410
+ if (match) {
411
+ return match[0];
412
+ } else {
413
+ return null;
414
+ }
415
+ }
416
+
417
+ async function deleteTempVideos() {
418
+ try {
419
+ const files = fs.readdirSync("./");
420
+ const tempVideoFiles = files.filter(file => file.startsWith('temp_video'));
421
+
422
+ for (const file of tempVideoFiles) {
423
+ fs.unlinkSync("./" + file);
424
+ }
425
+ } catch (error) {
426
+ throw new Error(`Error deleting temp_video files: ${error.message}`);
427
+ }
428
+ }
429
+
430
+ async function autoCrop(fileName) {
431
+ const inputPath = fileName;
432
+ const outputPath = fileName.split('.')[0] + "_cropped.mp4";
433
+
434
+ return new Promise((resolve, reject) => {
435
+ ffmpeg(inputPath)
436
+ .videoFilters('cropdetect')
437
+ .output(outputPath)
438
+ .on('end', function (stdout, stderr) {
439
+ const crop = parseCrop(stderr);
440
+ if (!crop) {
441
+ reject(new Error('Error: Unable to detect crop values.'));
442
+ return;
443
+ }
444
+
445
+ ffmpeg(inputPath)
446
+ .videoFilters(`crop=${crop.width}:${crop.height}:${crop.x}:${crop.y}`)
447
+ .on('end', () => {
448
+ // Delete the original file since we now have the cropped version
449
+ fs.unlinkSync(inputPath);
450
+ resolve(outputPath);
451
+ })
452
+ .on('error', (err) => {
453
+ reject(new Error('Error during cropping: ' + err.message));
454
+ }
455
+ ).save(outputPath);
456
+ })
457
+ .on('error', (err) => {
458
+ reject(new Error('Error during crop detection: ' + err.message));
459
+ })
460
+ .run();
461
+ });
462
+
463
+ function parseCrop(stderr) {
464
+ const cropRegex = /crop=([0-9]+):([0-9]+):([0-9]+):([0-9]+)/;
465
+ const match = stderr.match(cropRegex);
466
+ if (match) {
467
+ return {
468
+ width: match[1],
469
+ height: match[2],
470
+ x: match[3],
471
+ y: match[4],
472
+ };
473
+ } else {
474
+ return null;
475
+ }
476
+ }
477
+ }
478
+
479
+ async function checkAndCompressVideo(filePath, limitSizeMB) {
480
+ if (!limitSizeMB) return filePath;
481
+
482
+ const stats = fs.statSync(filePath);
483
+ const fileSizeInMB = stats.size / (1024 * 1024);
484
+
485
+ if (fileSizeInMB <= limitSizeMB) {
486
+ return filePath;
487
+ }
488
+
489
+ const compressedFilePath = filePath.split('.')[0] + "_compressed.mp4";
490
+
491
+ return new Promise((resolve, reject) => {
492
+ ffmpeg(filePath)
493
+ .outputOptions([
494
+ '-vf', 'scale=640:-2',
495
+ '-b:v', '500k',
496
+ '-b:a', '128k',
497
+ '-movflags', 'faststart'
498
+ ])
499
+ .output(compressedFilePath)
500
+ .on('end', () => {
501
+ fs.unlinkSync(filePath);
502
+ resolve(compressedFilePath);
503
+ })
504
+ .on('error', (err) => {
505
+ reject(new Error('Error during compression: ' + err.message));
506
+ })
507
+ .run();
508
+ });
509
+ }
510
+
511
+ MediaDownloader.isVideoLink = isVideoLink;
512
+
513
+ module.exports = MediaDownloader;
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "media-downloader-ez",
3
+ "version": "2.0.0",
4
+ "description": "download videos from URLs and autocrop (Instagram, YouTube, TikTok, X, etc.) to send directly to Discord or other!",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/Walkoud/media-downloader-ez.git"
12
+ },
13
+ "keywords": [
14
+ "discord",
15
+ "media",
16
+ "download",
17
+ "downloader",
18
+ "instagram",
19
+ "tiktok",
20
+ "youtube",
21
+ "video"
22
+ ],
23
+ "author": "Walkoud",
24
+ "license": "ISC",
25
+ "bugs": {
26
+ "url": "https://github.com/Walkoud/media-downloader-ez/issues"
27
+ },
28
+ "homepage": "https://github.com/Walkoud/media-downloader-ez#readme",
29
+ "dependencies": {
30
+ "@sasmeee/igdl": "^1.0.0",
31
+ "@tobyg74/tiktok-api-dl": "^1.0.13",
32
+ "axios": "^1.6.2",
33
+ "@distube/ytdl-core": "^4.13.5",
34
+ "twitter-downloader": "^1.1.7",
35
+ "ffmpeg-ffprobe-static": "^6.1.1-rc.5",
36
+ "fluent-ffmpeg": "^2.1.3"
37
+ }
38
+ }
package/README.md ADDED
@@ -0,0 +1,133 @@
1
+ <div align="center">
2
+ <a href="https://nodei.co/npm/@media-downloaders/v2" title="npm"><img src="https://nodei.co/npm/@media-downloaders/v2.png?downloads=true&downloadRank=true&stars=true"></img></a>
3
+ </div>
4
+
5
+ # ⚙️ @media-downloaders/v2 ⚙️
6
+ A simple npm package to download videos from various platforms, including Instagram, YouTube, TikTok, X/Twitter, and more.
7
+
8
+ ## Features
9
+ - **AutoCompress**: Automatically compress videos with a size limit.
10
+ - **AutoCrop**: Automatically crop videos to remove black bars.
11
+ - **Rotate**: Rotate the video left or right.
12
+
13
+ ## Supported Platforms
14
+ - Facebook
15
+ - TikTok
16
+ - Twitter (X)
17
+ - Instagram
18
+ - YouTube
19
+ - Pinterest
20
+ - Google Drive
21
+ - CapCut
22
+ - Likee
23
+ - Threads
24
+ - Mediafire
25
+
26
+ **Note:** Some Instagram posts/reels/stories can fail with the default extractor. This package includes an alternate Instagram method called "option3" (uses `instagramcustom.js`) that you can force when needed.
27
+
28
+ ---
29
+
30
+ ## Basic Example
31
+
32
+ ```js
33
+ const MediaDownloader = require('@media-downloaders/v2');
34
+
35
+ let url = "http://";
36
+
37
+ MediaDownloader(url, {
38
+ autocrop: true, // Automatically crop black bars (useful for TikTok, Instagram videos)
39
+ limitSizeMB: "10", // Maximum size limit in MB
40
+ rotation: null, // Rotate video: "right", "left", or null
41
+ useInstaOption3: false // Set to true to force the custom Instagram downloader
42
+ });
43
+ ```
44
+
45
+ ---
46
+
47
+ ## Example for Discord.js
48
+
49
+ ```js
50
+ const MediaDownloader = require('@media-downloaders/v2');
51
+ const Discord = require('discord.js-v11-stable');
52
+ const client = new Discord.Client({
53
+ disableEveryone: true
54
+ });
55
+
56
+ client.on('message', async (message) => {
57
+ try {
58
+ if (message.content.startsWith('!download') && message.content.includes('http')) {
59
+ let attachment = await MediaDownloader(message.content, {
60
+ autocrop: true,
61
+ limitSizeMB: "10"
62
+ });
63
+ message.channel.send({ content: `Downloaded by: \`${message.author.username}\``, files: [attachment] });
64
+ }
65
+ } catch (error) {
66
+ console.error('Error downloading video:', error);
67
+ message.reply('An error occurred while downloading the video.').then((m) => { m.delete(); });
68
+ }
69
+ });
70
+
71
+ client.login("your token").catch((err) => {
72
+ console.log('INCORRECT TOKEN LOGIN!');
73
+ });
74
+ ```
75
+
76
+ > **Note:** Keep your Discord bot token private and secure.
77
+
78
+ ---
79
+
80
+ ## YouTube Download and Get Cookie
81
+
82
+ To download YouTube videos requiring authentication, follow these steps:
83
+
84
+ 1. Open **youtube.com** in your browser.
85
+ 2. Press `CTRL + SHIFT + I` (or right-click → Inspect).
86
+ 3. Open the **Console** tab.
87
+ 4. In the console, **type**:
88
+ ```
89
+ allow pasting
90
+ ```
91
+ and press Enter. *(This allows pasting commands into the console.)*
92
+ 5. Then, type:
93
+ ```
94
+ copy(document.cookie)
95
+ ```
96
+ 6. Your cookie will now be copied to your clipboard. Paste it into your script where required.
97
+
98
+ ### Example with YouTube Cookie
99
+
100
+ ```js
101
+ const MediaDownloader = require('@media-downloaders/v2');
102
+
103
+ let url = "http://";
104
+ let cookie = "your_cookie_here";
105
+
106
+ MediaDownloader(url, {
107
+ YTBcookie: cookie, // YouTube cookie
108
+ YTBmaxduration: 80, // Maximum duration in seconds
109
+ autocrop: true, // Automatically crop black bars
110
+ limitSizeMB: "10", // Maximum size limit in MB
111
+ rotation: null // Rotate video: "right", "left", or null
112
+ });
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Only Safe Links Example
118
+
119
+ ```js
120
+ if (MediaDownloader.isVideoLink(url)) {
121
+ let attachment = await MediaDownloader(url, {
122
+ autocrop: true,
123
+ limitSizeMB: "10",
124
+ rotation: "left" // or "right" or null
125
+ });
126
+ }
127
+ ```
128
+
129
+ ---
130
+
131
+ ## Credits
132
+
133
+ This package is built upon the main **media-downloaders** project. Special thanks to **Walkoud**, the main contributor of the original project.