media-dl 2.5.2 → 2.5.3
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/bin/cli.js +146 -139
- package/package.json +1 -1
- package/readme.id.md +103 -0
- package/readme.md +113 -103
package/bin/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ const fs = require('fs');
|
|
|
6
6
|
const os = require('os');
|
|
7
7
|
const { C, printHeader, renderProgressBar, askQuestion, rl } = require('./ui');
|
|
8
8
|
|
|
9
|
-
// ---
|
|
9
|
+
// --- VISUAL CONFIGURATION (ANSI COLORS) ---
|
|
10
10
|
|
|
11
11
|
const TOOLS_DIR = path.join(os.homedir(), '.media-dl');
|
|
12
12
|
|
|
@@ -18,13 +18,13 @@ const isTermux =
|
|
|
18
18
|
let YTDLP_PATH = path.join(TOOLS_DIR, isWindows ? 'yt-dlp.exe' : 'yt-dlp');
|
|
19
19
|
let FFMPEG_PATH = path.join(TOOLS_DIR, isWindows ? 'ffmpeg.exe' : 'ffmpeg');
|
|
20
20
|
|
|
21
|
-
// State
|
|
21
|
+
// Application State
|
|
22
22
|
let safeMode = true;
|
|
23
23
|
|
|
24
24
|
if (!fs.existsSync(TOOLS_DIR)) fs.mkdirSync(TOOLS_DIR, { recursive: true });
|
|
25
25
|
|
|
26
26
|
function checkTools() {
|
|
27
|
-
//
|
|
27
|
+
// Check default local path (internal)
|
|
28
28
|
const LOCAL_YT = path.join(TOOLS_DIR, isWindows ? 'yt-dlp.exe' : 'yt-dlp');
|
|
29
29
|
const LOCAL_FF = path.join(TOOLS_DIR, isWindows ? 'ffmpeg.exe' : 'ffmpeg');
|
|
30
30
|
|
|
@@ -34,11 +34,11 @@ function checkTools() {
|
|
|
34
34
|
let ytExists = isLocalYt;
|
|
35
35
|
let ffExists = isLocalFf;
|
|
36
36
|
|
|
37
|
-
// Reset path
|
|
37
|
+
// Reset path to default before global check
|
|
38
38
|
if (isLocalYt) YTDLP_PATH = LOCAL_YT;
|
|
39
39
|
if (isLocalFf) FFMPEG_PATH = LOCAL_FF;
|
|
40
40
|
|
|
41
|
-
//
|
|
41
|
+
// Check Global yt-dlp only if local is missing
|
|
42
42
|
if (!isLocalYt) {
|
|
43
43
|
try {
|
|
44
44
|
const cmd = isWindows ? 'where yt-dlp' : 'which yt-dlp';
|
|
@@ -51,11 +51,11 @@ function checkTools() {
|
|
|
51
51
|
ytExists = true;
|
|
52
52
|
}
|
|
53
53
|
} catch (e) {
|
|
54
|
-
YTDLP_PATH = LOCAL_YT; //
|
|
54
|
+
YTDLP_PATH = LOCAL_YT; // Revert to local path if global is also missing
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
//
|
|
58
|
+
// Check Global ffmpeg only if local is missing
|
|
59
59
|
if (!isLocalFf) {
|
|
60
60
|
try {
|
|
61
61
|
const cmd = isWindows ? 'where ffmpeg' : 'which ffmpeg';
|
|
@@ -68,7 +68,7 @@ function checkTools() {
|
|
|
68
68
|
ffExists = true;
|
|
69
69
|
}
|
|
70
70
|
} catch (e) {
|
|
71
|
-
FFMPEG_PATH = LOCAL_FF; //
|
|
71
|
+
FFMPEG_PATH = LOCAL_FF; // Revert to local path if global is also missing
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -86,21 +86,21 @@ async function installYtdlp() {
|
|
|
86
86
|
if (!fs.existsSync(TOOLS_DIR)) fs.mkdirSync(TOOLS_DIR, { recursive: true });
|
|
87
87
|
|
|
88
88
|
printHeader('INSTALL / UPDATE YT-DLP');
|
|
89
|
-
console.log(`${C.blue}⏳
|
|
89
|
+
console.log(`${C.blue}⏳ Downloading latest engine...${C.reset}`);
|
|
90
90
|
const url = isWindows
|
|
91
91
|
? 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe'
|
|
92
92
|
: 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp';
|
|
93
93
|
|
|
94
94
|
try {
|
|
95
95
|
if (isTermux) {
|
|
96
|
-
//
|
|
97
|
-
console.log(`${C.dim}
|
|
96
|
+
// On Termux, using python/pip is recommended for stability
|
|
97
|
+
console.log(`${C.dim}Installing yt-dlp via python...${C.reset}`);
|
|
98
98
|
execSync('pkg update && pkg install python ffmpeg -y', {
|
|
99
99
|
stdio: 'inherit',
|
|
100
100
|
});
|
|
101
101
|
execSync('pip install -U "yt-dlp[default]"', { stdio: 'inherit' });
|
|
102
102
|
console.log(
|
|
103
|
-
`\n${C.green}✅ yt-dlp
|
|
103
|
+
`\n${C.green}✅ yt-dlp installed successfully on Termux!${C.reset}`,
|
|
104
104
|
);
|
|
105
105
|
} else if (isWindows) {
|
|
106
106
|
execSync(
|
|
@@ -113,11 +113,11 @@ async function installYtdlp() {
|
|
|
113
113
|
execSync(`chmod a+rx "${YTDLP_PATH}"`);
|
|
114
114
|
}
|
|
115
115
|
if (!isTermux) {
|
|
116
|
-
console.log(`\n${C.green}✅ yt-dlp
|
|
116
|
+
console.log(`\n${C.green}✅ yt-dlp configured successfully!${C.reset}`);
|
|
117
117
|
}
|
|
118
118
|
} catch (e) {
|
|
119
119
|
console.error(
|
|
120
|
-
`\n${C.red}❌
|
|
120
|
+
`\n${C.red}❌ Failed to download. Check your internet connection.${C.reset}`,
|
|
121
121
|
);
|
|
122
122
|
}
|
|
123
123
|
}
|
|
@@ -125,21 +125,21 @@ async function installYtdlp() {
|
|
|
125
125
|
async function installFfmpeg() {
|
|
126
126
|
printHeader('INSTALL FFmpeg');
|
|
127
127
|
console.log(
|
|
128
|
-
`${C.dim}FFmpeg
|
|
128
|
+
`${C.dim}FFmpeg is required for 1080p+ quality and MP3 conversion.${C.reset}\n`,
|
|
129
129
|
);
|
|
130
130
|
|
|
131
131
|
try {
|
|
132
132
|
if (isTermux) {
|
|
133
133
|
console.log(
|
|
134
|
-
`${C.blue}⏳
|
|
134
|
+
`${C.blue}⏳ Termux detected: Installing via pkg...${C.reset}`,
|
|
135
135
|
);
|
|
136
136
|
execSync('pkg update && pkg install ffmpeg -y', { stdio: 'inherit' });
|
|
137
137
|
console.log(
|
|
138
|
-
`\n${C.green}✅ FFmpeg
|
|
138
|
+
`\n${C.green}✅ FFmpeg installed successfully on Termux!${C.reset}`,
|
|
139
139
|
);
|
|
140
140
|
} else if (isMac) {
|
|
141
|
-
// ... (
|
|
142
|
-
console.log(`${C.blue}⏳
|
|
141
|
+
// ... (Your macOS code is correct)
|
|
142
|
+
console.log(`${C.blue}⏳ Downloading FFmpeg for macOS...${C.reset}`);
|
|
143
143
|
const zipPath = path.join(TOOLS_DIR, 'ffmpeg.zip');
|
|
144
144
|
execSync(
|
|
145
145
|
`curl -L -# "https://evermeet.cx/ffmpeg/getrelease/ffmpeg/zip" -o "${zipPath}"`,
|
|
@@ -148,54 +148,54 @@ async function installFfmpeg() {
|
|
|
148
148
|
execSync(`unzip -o "${zipPath}" -d "${TOOLS_DIR}"`, { stdio: 'inherit' });
|
|
149
149
|
execSync(`rm "${zipPath}"`);
|
|
150
150
|
execSync(`chmod a+rx "${FFMPEG_PATH}"`);
|
|
151
|
-
console.log(`\n${C.green}✅ FFmpeg
|
|
151
|
+
console.log(`\n${C.green}✅ FFmpeg active on macOS.${C.reset}`);
|
|
152
152
|
} else if (isWindows) {
|
|
153
153
|
console.log(
|
|
154
|
-
`${C.blue}⏳
|
|
154
|
+
`${C.blue}⏳ Downloading FFmpeg for Windows (Essentials)...${C.reset}`,
|
|
155
155
|
);
|
|
156
156
|
|
|
157
157
|
const zipPath = path.join(TOOLS_DIR, 'ffmpeg.zip');
|
|
158
|
-
//
|
|
158
|
+
// Direct link to build essentials so the file isn't too large
|
|
159
159
|
const url =
|
|
160
160
|
'https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip';
|
|
161
161
|
|
|
162
|
-
// 1. Download
|
|
162
|
+
// 1. Download using PowerShell
|
|
163
163
|
execSync(
|
|
164
164
|
`powershell -Command "Invoke-WebRequest -Uri ${url} -OutFile '${zipPath}'"`,
|
|
165
165
|
{ stdio: 'inherit' },
|
|
166
166
|
);
|
|
167
167
|
|
|
168
|
-
console.log(`${C.yellow}📦
|
|
168
|
+
console.log(`${C.yellow}📦 Extracting FFmpeg...${C.reset}`);
|
|
169
169
|
|
|
170
|
-
// 2.
|
|
171
|
-
//
|
|
170
|
+
// 2. Extract using 'tar' command (Built-in Windows 10+)
|
|
171
|
+
// We only take ffmpeg.exe from the bin folder inside the zip
|
|
172
172
|
execSync(
|
|
173
173
|
`tar -xf "${zipPath}" -C "${TOOLS_DIR}" --strip-components 2 "*/bin/ffmpeg.exe" "*/bin/ffprobe.exe"`,
|
|
174
174
|
{ stdio: 'inherit' },
|
|
175
175
|
);
|
|
176
176
|
|
|
177
|
-
// 3.
|
|
177
|
+
// 3. Clean up zip file
|
|
178
178
|
if (fs.existsSync(zipPath)) fs.unlinkSync(zipPath);
|
|
179
179
|
|
|
180
180
|
console.log(
|
|
181
|
-
`\n${C.green}✅ FFmpeg
|
|
181
|
+
`\n${C.green}✅ FFmpeg installed successfully on Windows!${C.reset}`,
|
|
182
182
|
);
|
|
183
183
|
} else {
|
|
184
|
-
//
|
|
184
|
+
// Assume Linux (Ubuntu/Debian)
|
|
185
185
|
console.log(
|
|
186
|
-
`${C.blue}⏳
|
|
186
|
+
`${C.blue}⏳ Linux detected: Installing via apt...${C.reset}`,
|
|
187
187
|
);
|
|
188
|
-
console.log(`${C.dim}
|
|
188
|
+
console.log(`${C.dim}May require sudo password.${C.reset}`);
|
|
189
189
|
execSync('sudo apt update && sudo apt install ffmpeg -y', {
|
|
190
190
|
stdio: 'inherit',
|
|
191
191
|
});
|
|
192
192
|
console.log(
|
|
193
|
-
`\n${C.green}✅ FFmpeg
|
|
193
|
+
`\n${C.green}✅ FFmpeg installed successfully on Linux!${C.reset}`,
|
|
194
194
|
);
|
|
195
195
|
}
|
|
196
196
|
} catch (e) {
|
|
197
197
|
console.error(
|
|
198
|
-
`${C.red}❌
|
|
198
|
+
`${C.red}❌ Failed to install FFmpeg automatically.${C.reset}`,
|
|
199
199
|
);
|
|
200
200
|
console.log(`${C.dim}Error: ${e.message}${C.reset}`);
|
|
201
201
|
}
|
|
@@ -204,11 +204,10 @@ async function installFfmpeg() {
|
|
|
204
204
|
function runSpawn(command, args) {
|
|
205
205
|
return new Promise((resolve) => {
|
|
206
206
|
const proc = spawn(command, args);
|
|
207
|
-
let lastOutput = '';
|
|
208
207
|
|
|
209
208
|
proc.stdout.on('data', (data) => {
|
|
210
209
|
const output = data.toString();
|
|
211
|
-
// Regex
|
|
210
|
+
// Regex to capture progress from yt-dlp
|
|
212
211
|
const progressMatch = output.match(
|
|
213
212
|
/\[download\]\s+(\d+\.\d+)%\s+of\s+.*\s+at\s+([\d\w\./s]+)\s+ETA\s+([\d:]+)/,
|
|
214
213
|
);
|
|
@@ -217,7 +216,7 @@ function runSpawn(command, args) {
|
|
|
217
216
|
const [_, percent, speed, eta] = progressMatch;
|
|
218
217
|
renderProgressBar(parseFloat(percent), speed, eta);
|
|
219
218
|
} else {
|
|
220
|
-
//
|
|
219
|
+
// If not a bar, print normal (e.g., merging info/ffmpeg)
|
|
221
220
|
if (output.trim() && !output.includes('[download]')) {
|
|
222
221
|
process.stdout.write(`\n${C.dim}${output.trim()}${C.reset}\n`);
|
|
223
222
|
}
|
|
@@ -232,7 +231,7 @@ function runSpawn(command, args) {
|
|
|
232
231
|
});
|
|
233
232
|
|
|
234
233
|
proc.on('close', (code) => {
|
|
235
|
-
process.stdout.write('\n'); //
|
|
234
|
+
process.stdout.write('\n'); // New line after completion
|
|
236
235
|
resolve(code);
|
|
237
236
|
});
|
|
238
237
|
});
|
|
@@ -257,7 +256,7 @@ async function getEstimate(url, format) {
|
|
|
257
256
|
return (bytes / 1024 ** 2).toFixed(2) + ' MB';
|
|
258
257
|
}
|
|
259
258
|
} catch (e) {}
|
|
260
|
-
return '
|
|
259
|
+
return 'Estimate unavailable';
|
|
261
260
|
}
|
|
262
261
|
|
|
263
262
|
// --- DOWNLOAD ENGINE ---
|
|
@@ -265,24 +264,24 @@ async function startDownload(videoURLFromArgs = null) {
|
|
|
265
264
|
let { ytExists, ffExists } = checkTools();
|
|
266
265
|
if (!ytExists) {
|
|
267
266
|
console.log(
|
|
268
|
-
`\n${C.red}❌
|
|
267
|
+
`\n${C.red}❌ yt-dlp engine not found. Please select Update/Install menu.${C.reset}`,
|
|
269
268
|
);
|
|
270
269
|
await backToMenu();
|
|
271
270
|
}
|
|
272
271
|
|
|
273
272
|
if (!ffExists) {
|
|
274
273
|
console.log(
|
|
275
|
-
`${C.yellow}⚠️
|
|
274
|
+
`${C.yellow}⚠️ Warning: FFmpeg not found. Video might not be merged with audio.${C.reset}`,
|
|
276
275
|
);
|
|
277
|
-
const cont = await askQuestion('
|
|
276
|
+
const cont = await askQuestion('Continue anyway? (y/n): ');
|
|
278
277
|
if (cont.toLowerCase() !== 'y') return mainMenu();
|
|
279
278
|
}
|
|
280
279
|
|
|
281
280
|
const videoURL =
|
|
282
|
-
videoURLFromArgs || (await askQuestion('
|
|
281
|
+
videoURLFromArgs || (await askQuestion('Enter Link (Video/Playlist): '));
|
|
283
282
|
if (!videoURL) return mainMenu();
|
|
284
283
|
|
|
285
|
-
console.log(`${C.dim}⏳
|
|
284
|
+
console.log(`${C.dim}⏳ Analyzing link...${C.reset}`);
|
|
286
285
|
let playlistInfo = { isPlaylist: false, title: '', items: [] };
|
|
287
286
|
|
|
288
287
|
try {
|
|
@@ -297,29 +296,29 @@ async function startDownload(videoURLFromArgs = null) {
|
|
|
297
296
|
const lines = rawInfo.trim().split('\n');
|
|
298
297
|
if (lines.length > 1 || videoURL.includes('playlist?list=')) {
|
|
299
298
|
playlistInfo.isPlaylist = true;
|
|
300
|
-
playlistInfo.title = lines[0].split('|')[0] || '
|
|
299
|
+
playlistInfo.title = lines[0].split('|')[0] || 'Playlist Download';
|
|
301
300
|
playlistInfo.items = lines.map((l) => l.split('|')[1]).filter(Boolean);
|
|
302
301
|
}
|
|
303
302
|
} catch (e) {
|
|
304
|
-
console.log(`\n${C.red}❌
|
|
303
|
+
console.log(`\n${C.red}❌ Failed to analyze link.${C.reset}`);
|
|
305
304
|
if (e.message.includes('ETIMEDOUT')) {
|
|
306
305
|
console.log(
|
|
307
|
-
`${C.yellow}⚠️
|
|
306
|
+
`${C.yellow}⚠️ Analysis timed out. Check your internet connection.${C.reset}`,
|
|
308
307
|
);
|
|
309
308
|
} else {
|
|
310
309
|
console.log(
|
|
311
|
-
`${C.yellow}⚠️
|
|
310
|
+
`${C.yellow}⚠️ Ensure link is valid or not private/deleted.${C.reset}`,
|
|
312
311
|
);
|
|
313
312
|
}
|
|
314
313
|
await backToMenu();
|
|
315
314
|
}
|
|
316
315
|
|
|
317
|
-
// ---
|
|
316
|
+
// --- PLAYLIST SELECTION (Logic remains same) ---
|
|
318
317
|
let playlistSelection = null;
|
|
319
318
|
if (playlistInfo.isPlaylist) {
|
|
320
|
-
// ... (
|
|
319
|
+
// ... (Playlist display logic same as before)
|
|
321
320
|
console.log(
|
|
322
|
-
`\n${C.bgBlue}${C.bright} 📂 PLAYLIST
|
|
321
|
+
`\n${C.bgBlue}${C.bright} 📂 PLAYLIST DETECTED: ${playlistInfo.title} ${C.reset}`,
|
|
323
322
|
);
|
|
324
323
|
playlistInfo.items.forEach((item, index) => {
|
|
325
324
|
console.log(
|
|
@@ -327,11 +326,11 @@ async function startDownload(videoURLFromArgs = null) {
|
|
|
327
326
|
);
|
|
328
327
|
});
|
|
329
328
|
console.log(
|
|
330
|
-
`\n${C.dim}
|
|
329
|
+
`\n${C.dim}Example: 1,3,5-10 or leave empty for all.${C.reset}`,
|
|
331
330
|
);
|
|
332
|
-
const selectionInput = await askQuestion('\
|
|
331
|
+
const selectionInput = await askQuestion('\nSelect numbers: ');
|
|
333
332
|
if (selectionInput) {
|
|
334
|
-
// ... (
|
|
333
|
+
// ... (Playlist parsing logic)
|
|
335
334
|
const selected = new Set();
|
|
336
335
|
selectionInput.split(',').forEach((p) => {
|
|
337
336
|
if (p.includes('-')) {
|
|
@@ -347,20 +346,20 @@ async function startDownload(videoURLFromArgs = null) {
|
|
|
347
346
|
}
|
|
348
347
|
}
|
|
349
348
|
|
|
350
|
-
// ---
|
|
351
|
-
console.log(`\n${C.bright} [
|
|
349
|
+
// --- MAIN FORMAT MENU ---
|
|
350
|
+
console.log(`\n${C.bright} [ SELECT FORMAT ]${C.reset}`);
|
|
352
351
|
console.log(` ${C.green}1.${C.reset} Video (MP4)`);
|
|
353
352
|
console.log(` ${C.green}2.${C.reset} Audio Only (MP3)`);
|
|
354
|
-
const mode = await askQuestion('
|
|
353
|
+
const mode = await askQuestion('Choice: ');
|
|
355
354
|
|
|
356
|
-
// ---
|
|
355
|
+
// --- OPTIMAL RESOLUTION LOGIC ---
|
|
357
356
|
let formatArg = '';
|
|
358
357
|
if (mode === '1') {
|
|
359
|
-
console.log(`\n${C.bright} [
|
|
358
|
+
console.log(`\n${C.bright} [ SELECT RESOLUTION ]${C.reset}`);
|
|
360
359
|
console.log(` ${C.cyan}1.${C.reset} Best Quality (Up to 4K)`);
|
|
361
360
|
console.log(` ${C.cyan}2.${C.reset} Tablet Optimal (720p)`);
|
|
362
361
|
console.log(` ${C.cyan}3.${C.reset} Mobile Optimal (480p)`);
|
|
363
|
-
const resChoice = await askQuestion('
|
|
362
|
+
const resChoice = await askQuestion('Select Resolution (1-3): ');
|
|
364
363
|
|
|
365
364
|
if (resChoice === '2') {
|
|
366
365
|
formatArg =
|
|
@@ -373,13 +372,13 @@ async function startDownload(videoURLFromArgs = null) {
|
|
|
373
372
|
'bestvideo[vcodec^=avc1]+bestaudio[acodec^=mp4a]/best[vcodec^=avc1]/best';
|
|
374
373
|
}
|
|
375
374
|
}
|
|
376
|
-
// ---
|
|
375
|
+
// --- DISPLAY ESTIMATE ---
|
|
377
376
|
if (!playlistInfo.isPlaylist && mode === '1') {
|
|
378
|
-
console.log(`${C.dim}⏳
|
|
377
|
+
console.log(`${C.dim}⏳ Calculating file size estimate...${C.reset}`);
|
|
379
378
|
const size = await getEstimate(videoURL, formatArg);
|
|
380
|
-
console.log(`${C.yellow}📊
|
|
379
|
+
console.log(`${C.yellow}📊 Estimated Size: ${C.bright}${size}${C.reset}`);
|
|
381
380
|
|
|
382
|
-
const confirm = await askQuestion('
|
|
381
|
+
const confirm = await askQuestion('Proceed with download? (Y/n): ');
|
|
383
382
|
if (confirm.toLowerCase() === 'n') return mainMenu();
|
|
384
383
|
}
|
|
385
384
|
|
|
@@ -401,7 +400,7 @@ async function startDownload(videoURLFromArgs = null) {
|
|
|
401
400
|
videoURL,
|
|
402
401
|
];
|
|
403
402
|
|
|
404
|
-
//
|
|
403
|
+
// Integrate Safe Mode (cite: cli.js)
|
|
405
404
|
if (safeMode) {
|
|
406
405
|
args.push(
|
|
407
406
|
'--rate-limit',
|
|
@@ -421,7 +420,7 @@ async function startDownload(videoURLFromArgs = null) {
|
|
|
421
420
|
if (mode === '2') {
|
|
422
421
|
if (!ffExists) {
|
|
423
422
|
console.log(
|
|
424
|
-
`${C.red}❌ Error:
|
|
423
|
+
`${C.red}❌ Error: FFmpeg is required to download audio.${C.reset}`,
|
|
425
424
|
);
|
|
426
425
|
return mainMenu();
|
|
427
426
|
}
|
|
@@ -432,11 +431,11 @@ async function startDownload(videoURLFromArgs = null) {
|
|
|
432
431
|
}
|
|
433
432
|
|
|
434
433
|
args.push('--no-mtime');
|
|
435
|
-
console.log(`\n${C.bgBlue}${C.bright} 🚀
|
|
434
|
+
console.log(`\n${C.bgBlue}${C.bright} 🚀 STARTING PROCESS... ${C.reset}\n`);
|
|
436
435
|
|
|
437
436
|
const code = await runSpawn(YTDLP_PATH, args);
|
|
438
437
|
if (code === 0) {
|
|
439
|
-
console.log(`\n${C.green}✨
|
|
438
|
+
console.log(`\n${C.green}✨ DONE! Check folder: ${outputDir}${C.reset}`);
|
|
440
439
|
try {
|
|
441
440
|
execSync(
|
|
442
441
|
isWindows
|
|
@@ -447,42 +446,42 @@ async function startDownload(videoURLFromArgs = null) {
|
|
|
447
446
|
);
|
|
448
447
|
} catch (e) {}
|
|
449
448
|
} else {
|
|
450
|
-
console.log(`\n${C.red}❌
|
|
449
|
+
console.log(`\n${C.red}❌ An error occurred while downloading.${C.reset}`);
|
|
451
450
|
}
|
|
452
451
|
await backToMenu();
|
|
453
452
|
}
|
|
454
453
|
|
|
455
454
|
async function backToMenu() {
|
|
456
455
|
try {
|
|
457
|
-
await askQuestion('
|
|
458
|
-
mainMenu(); //
|
|
456
|
+
await askQuestion('Press Enter to return to Main Menu...');
|
|
457
|
+
mainMenu(); // Return to menu
|
|
459
458
|
} catch (err) {
|
|
460
|
-
//
|
|
459
|
+
// Handle if readline is already closed (Ctrl+C pressed previously)
|
|
461
460
|
if (err.code === 'ERR_USE_AFTER_CLOSE') {
|
|
462
|
-
console.log(`\n${C.dim}Program
|
|
461
|
+
console.log(`\n${C.dim}Program terminated by user.${C.reset}`);
|
|
463
462
|
process.exit(0);
|
|
464
463
|
} else {
|
|
465
|
-
//
|
|
464
|
+
// Throw other errors if necessary
|
|
466
465
|
throw err;
|
|
467
466
|
}
|
|
468
467
|
}
|
|
469
468
|
}
|
|
470
469
|
|
|
471
470
|
async function showSupport() {
|
|
472
|
-
//
|
|
473
|
-
printHeader('
|
|
471
|
+
// Using 2 parameters: Title and Summary
|
|
472
|
+
printHeader('ABOUT APPLICATION', 'Media-DL Manager Pro v2.0.0 - 2026');
|
|
474
473
|
|
|
475
|
-
// ---
|
|
474
|
+
// --- FEATURES SECTION ---
|
|
476
475
|
console.log(` ${C.bright}${C.cyan}OVERVIEW${C.reset}`);
|
|
477
|
-
console.log(`
|
|
478
|
-
console.log(`
|
|
476
|
+
console.log(` Thank you for choosing MEDIA-DL. This script is designed`);
|
|
477
|
+
console.log(` to facilitate local media download management.\n`);
|
|
479
478
|
|
|
480
|
-
console.log(` ${C.bright}${C.cyan}
|
|
479
|
+
console.log(` ${C.bright}${C.cyan}KEY FEATURES${C.reset}`);
|
|
481
480
|
const features = [
|
|
482
481
|
{
|
|
483
482
|
icon: '✦',
|
|
484
483
|
title: 'High Quality',
|
|
485
|
-
desc: '
|
|
484
|
+
desc: 'Supports up to 4K & 320kbps Audio',
|
|
486
485
|
},
|
|
487
486
|
{
|
|
488
487
|
icon: '✦',
|
|
@@ -492,12 +491,12 @@ async function showSupport() {
|
|
|
492
491
|
{
|
|
493
492
|
icon: '✦',
|
|
494
493
|
title: 'Batch Mode',
|
|
495
|
-
desc: '
|
|
494
|
+
desc: 'Supports bulk Playlist downloading',
|
|
496
495
|
},
|
|
497
496
|
{
|
|
498
497
|
icon: '✦',
|
|
499
498
|
title: 'Safe Guard',
|
|
500
|
-
desc: '
|
|
499
|
+
desc: 'Protection mode to prevent account/IP blocks',
|
|
501
500
|
},
|
|
502
501
|
];
|
|
503
502
|
|
|
@@ -511,15 +510,25 @@ async function showSupport() {
|
|
|
511
510
|
|
|
512
511
|
console.log('\n' + '─'.repeat(52));
|
|
513
512
|
|
|
514
|
-
// ---
|
|
515
|
-
console.log(`\n ${C.bright}${C.magenta}
|
|
516
|
-
console.log(`
|
|
517
|
-
console.log(`
|
|
513
|
+
// --- SUPPORT SECTION ---
|
|
514
|
+
console.log(`\n ${C.bright}${C.magenta}SUPPORT & DONATION${C.reset}`);
|
|
515
|
+
console.log(` Your support helps the developer to keep updating`);
|
|
516
|
+
console.log(` the engine and features of this application.\n`);
|
|
518
517
|
|
|
519
|
-
//
|
|
518
|
+
// Display Links with background label to stand out
|
|
520
519
|
const links = [
|
|
521
|
-
{
|
|
522
|
-
|
|
520
|
+
{
|
|
521
|
+
label: ' ☕ BUY COFFEE (Paypal)',
|
|
522
|
+
url: 'https://www.paypal.com/ncp/payment/RSXEBXBQGDYN4',
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
label: ' ☕ BUY COFFEE (Midtrans)',
|
|
526
|
+
url: 'https://app.midtrans.com/coffee',
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
label: ' 🍕 BUY PIZZA (Midtrans)',
|
|
530
|
+
url: 'https://app.midtrans.com/pizza',
|
|
531
|
+
},
|
|
523
532
|
];
|
|
524
533
|
|
|
525
534
|
links.forEach((l) => {
|
|
@@ -537,10 +546,10 @@ async function mainMenu() {
|
|
|
537
546
|
const status = checkTools();
|
|
538
547
|
const { ytExists, ffExists } = status;
|
|
539
548
|
|
|
540
|
-
//
|
|
541
|
-
printHeader('MEDIA-DL PRO 2026', '
|
|
549
|
+
// Using 2 parameters: Title and short summary status
|
|
550
|
+
printHeader('MEDIA-DL PRO 2026', 'Local Media Download Control Center');
|
|
542
551
|
|
|
543
|
-
// ---
|
|
552
|
+
// --- DASHBOARD SECTION (SYSTEM INFO) ---
|
|
544
553
|
const ytLabel = status.isLocalYt
|
|
545
554
|
? `${C.green}Ready (Internal)${C.reset}`
|
|
546
555
|
: status.ytExists
|
|
@@ -563,14 +572,14 @@ async function mainMenu() {
|
|
|
563
572
|
|
|
564
573
|
console.log(` ${C.cyan}━${'━'.repeat(48)}${C.reset}`);
|
|
565
574
|
|
|
566
|
-
// ---
|
|
575
|
+
// --- NAVIGATION SECTION ---
|
|
567
576
|
console.log(` ${C.bright}MAIN SERVICES${C.reset}`);
|
|
568
577
|
console.log(
|
|
569
578
|
` ${C.cyan}1.${C.reset} 📥 Download Media ${C.dim}(Video, Music, Playlist)${C.reset}`,
|
|
570
579
|
);
|
|
571
580
|
console.log(
|
|
572
|
-
` ${C.cyan}2.${C.reset} 🛡️ Toggle Safe Mode ${C.dim}(
|
|
573
|
-
safeMode ? '
|
|
581
|
+
` ${C.cyan}2.${C.reset} 🛡️ Toggle Safe Mode ${C.dim}(Current: ${
|
|
582
|
+
safeMode ? 'Active' : 'Inactive'
|
|
574
583
|
})${C.reset}`,
|
|
575
584
|
);
|
|
576
585
|
|
|
@@ -579,13 +588,13 @@ async function mainMenu() {
|
|
|
579
588
|
` ${C.cyan}3.${C.reset} ⚙️ Maintenance & Update ${C.dim}(Update engine / Cleanup)${C.reset}`,
|
|
580
589
|
);
|
|
581
590
|
console.log(
|
|
582
|
-
` ${C.cyan}4.${C.reset} ❤️
|
|
591
|
+
` ${C.cyan}4.${C.reset} ❤️ About Application ${C.dim}(Support & Features)${C.reset}`,
|
|
583
592
|
);
|
|
584
|
-
console.log(` ${C.cyan}0.${C.reset} 🚪
|
|
593
|
+
console.log(` ${C.cyan}0.${C.reset} 🚪 Exit`);
|
|
585
594
|
|
|
586
595
|
console.log(` ${C.cyan}━${'━'.repeat(48)}${C.reset}`);
|
|
587
596
|
|
|
588
|
-
const choice = await askQuestion('\
|
|
597
|
+
const choice = await askQuestion('\nSelect menu (0-4): ');
|
|
589
598
|
|
|
590
599
|
switch (choice) {
|
|
591
600
|
case '1':
|
|
@@ -593,10 +602,10 @@ async function mainMenu() {
|
|
|
593
602
|
break;
|
|
594
603
|
case '2':
|
|
595
604
|
safeMode = !safeMode;
|
|
596
|
-
//
|
|
605
|
+
// Provide short visual feedback before refreshing menu
|
|
597
606
|
console.log(
|
|
598
|
-
`\n${C.yellow} 🛡️ Safe Mode
|
|
599
|
-
safeMode ? '
|
|
607
|
+
`\n${C.yellow} 🛡️ Safe Mode has been ${
|
|
608
|
+
safeMode ? 'ENABLED' : 'DISABLED'
|
|
600
609
|
}${C.reset}`,
|
|
601
610
|
);
|
|
602
611
|
setTimeout(() => mainMenu(), 800);
|
|
@@ -610,14 +619,14 @@ async function mainMenu() {
|
|
|
610
619
|
case '0':
|
|
611
620
|
console.log(`\n${C.cyan}━${'━'.repeat(48)}${C.reset}`);
|
|
612
621
|
console.log(
|
|
613
|
-
` ${C.bright}${C.white}
|
|
622
|
+
` ${C.bright}${C.white}Thank you for using MEDIA-DL!${C.reset}`,
|
|
614
623
|
);
|
|
615
624
|
console.log(
|
|
616
|
-
` ${C.green}✨
|
|
625
|
+
` ${C.green}✨ Wishing you success, prosperity, and good health! ✨${C.reset}`,
|
|
617
626
|
);
|
|
618
627
|
console.log(`${C.cyan}━${'━'.repeat(48)}${C.reset}\n`);
|
|
619
628
|
|
|
620
|
-
//
|
|
629
|
+
// Give a short delay before actually closing terminal
|
|
621
630
|
setTimeout(() => {
|
|
622
631
|
rl.close();
|
|
623
632
|
process.exit(0);
|
|
@@ -625,7 +634,7 @@ async function mainMenu() {
|
|
|
625
634
|
|
|
626
635
|
break;
|
|
627
636
|
default:
|
|
628
|
-
//
|
|
637
|
+
// If input incorrect, show menu again
|
|
629
638
|
mainMenu();
|
|
630
639
|
break;
|
|
631
640
|
}
|
|
@@ -636,7 +645,7 @@ async function cleanUp() {
|
|
|
636
645
|
fs.rmSync(TOOLS_DIR, { recursive: true, force: true });
|
|
637
646
|
}
|
|
638
647
|
|
|
639
|
-
// RESET PATH
|
|
648
|
+
// RESET PATH to local default so checkTools doesn't "get lost" using old path
|
|
640
649
|
YTDLP_PATH = path.join(TOOLS_DIR, isWindows ? 'yt-dlp.exe' : 'yt-dlp');
|
|
641
650
|
FFMPEG_PATH = path.join(TOOLS_DIR, isWindows ? 'ffmpeg.exe' : 'ffmpeg');
|
|
642
651
|
}
|
|
@@ -646,28 +655,28 @@ async function firstTimeSetup() {
|
|
|
646
655
|
const { ytExists, ffExists } = checkTools();
|
|
647
656
|
printHeader(
|
|
648
657
|
'FIRST-TIME SETUP',
|
|
649
|
-
'
|
|
658
|
+
'Components required to run the application',
|
|
650
659
|
);
|
|
651
660
|
|
|
652
|
-
console.log(`${C.white}Status
|
|
661
|
+
console.log(`${C.white}Installation Status:${C.reset}`);
|
|
653
662
|
console.log(
|
|
654
663
|
` [${ytExists ? C.green + '✓' : C.red + '✗'}${
|
|
655
664
|
C.reset
|
|
656
|
-
}]
|
|
665
|
+
}] yt-dlp Engine (Required)`,
|
|
657
666
|
);
|
|
658
667
|
console.log(
|
|
659
668
|
` [${ffExists ? C.green + '✓' : C.red + '✗'}${
|
|
660
669
|
C.reset
|
|
661
|
-
}] FFmpeg (
|
|
670
|
+
}] FFmpeg (Recommended)`,
|
|
662
671
|
);
|
|
663
672
|
|
|
664
673
|
console.log(
|
|
665
|
-
`\n${C.yellow}
|
|
674
|
+
`\n${C.yellow}Application is not ready. Select option:${C.reset}`,
|
|
666
675
|
);
|
|
667
|
-
console.log(` ${C.cyan}1.${C.reset} Install
|
|
668
|
-
console.log(` ${C.cyan}0.${C.reset}
|
|
676
|
+
console.log(` ${C.cyan}1.${C.reset} Install All Components Automatically`);
|
|
677
|
+
console.log(` ${C.cyan}0.${C.reset} Exit Application`);
|
|
669
678
|
|
|
670
|
-
const choice = await askQuestion('\
|
|
679
|
+
const choice = await askQuestion('\nSelect: ');
|
|
671
680
|
|
|
672
681
|
if (choice === '1') {
|
|
673
682
|
if (!ytExists) await installYtdlp();
|
|
@@ -676,13 +685,13 @@ async function firstTimeSetup() {
|
|
|
676
685
|
const status = checkTools();
|
|
677
686
|
if (status.ytExists) {
|
|
678
687
|
console.log(
|
|
679
|
-
`\n${C.green}✨ Setup
|
|
688
|
+
`\n${C.green}✨ Setup Complete! Opening Main Menu...${C.reset}`,
|
|
680
689
|
);
|
|
681
690
|
await new Promise((r) => setTimeout(r, 1500));
|
|
682
|
-
return mainMenu(); //
|
|
691
|
+
return mainMenu(); // Success, proceed to main menu
|
|
683
692
|
}
|
|
684
693
|
} else if (choice === '0') {
|
|
685
|
-
console.log('
|
|
694
|
+
console.log('Closing application...');
|
|
686
695
|
process.exit(0);
|
|
687
696
|
}
|
|
688
697
|
}
|
|
@@ -693,12 +702,9 @@ async function systemMaintenance() {
|
|
|
693
702
|
|
|
694
703
|
while (inMaintenance) {
|
|
695
704
|
const { ytExists, ffExists } = checkTools();
|
|
696
|
-
printHeader(
|
|
697
|
-
'SYSTEM MAINTENANCE',
|
|
698
|
-
'Update engine atau bersihkan file sistem',
|
|
699
|
-
);
|
|
705
|
+
printHeader('SYSTEM MAINTENANCE', 'Update engine or clean system files');
|
|
700
706
|
|
|
701
|
-
console.log(`${C.white}
|
|
707
|
+
console.log(`${C.white}Installed Versions:${C.reset}`);
|
|
702
708
|
console.log(
|
|
703
709
|
` • yt-dlp : ${ytExists ? C.green + 'Ready' : C.red + 'Not Found'}${
|
|
704
710
|
C.reset
|
|
@@ -710,39 +716,41 @@ async function systemMaintenance() {
|
|
|
710
716
|
}`,
|
|
711
717
|
);
|
|
712
718
|
|
|
713
|
-
console.log(`\n${C.bright}
|
|
719
|
+
console.log(`\n${C.bright}Maintenance Options:${C.reset}`);
|
|
714
720
|
console.log(` ${C.cyan}1.${C.reset} Update / Reinstall Engines`);
|
|
715
|
-
console.log(` ${C.cyan}2.${C.reset} 🗑️
|
|
716
|
-
console.log(` ${C.cyan}3.${C.reset} ⬅️
|
|
721
|
+
console.log(` ${C.cyan}2.${C.reset} 🗑️ Delete All Tools (System Reset)`);
|
|
722
|
+
console.log(` ${C.cyan}3.${C.reset} ⬅️ Return to Main Menu`);
|
|
717
723
|
|
|
718
|
-
const choice = await askQuestion('\
|
|
724
|
+
const choice = await askQuestion('\nSelect action: ');
|
|
719
725
|
|
|
720
726
|
switch (choice) {
|
|
721
727
|
case '1':
|
|
722
728
|
await installYtdlp();
|
|
723
729
|
await installFfmpeg();
|
|
724
|
-
await askQuestion('\nUpdate
|
|
730
|
+
await askQuestion('\nUpdate complete. Press Enter...');
|
|
725
731
|
break;
|
|
726
732
|
|
|
727
733
|
case '2':
|
|
728
734
|
const confirm = await askQuestion(
|
|
729
|
-
`${C.bgRed}${C.white}
|
|
735
|
+
`${C.bgRed}${C.white} CONFIRMATION ${C.reset} Delete all tools? (y/n): `,
|
|
730
736
|
);
|
|
731
737
|
if (confirm.toLowerCase() === 'y') {
|
|
732
|
-
await cleanUp(); //
|
|
733
|
-
console.log(
|
|
738
|
+
await cleanUp(); // Call folder deletion function
|
|
739
|
+
console.log(
|
|
740
|
+
`${C.yellow}Folder .media-dl has been deleted.${C.reset}`,
|
|
741
|
+
);
|
|
734
742
|
|
|
735
|
-
//
|
|
743
|
+
// Re-check status after deletion
|
|
736
744
|
const finalCheck = checkTools();
|
|
737
745
|
if (finalCheck.isLocalYt || finalCheck.isLocalFf) {
|
|
738
746
|
console.log(
|
|
739
|
-
`${C.red}
|
|
747
|
+
`${C.red}Failed to delete some files. Ensure no processes are locking them.${C.reset}`,
|
|
740
748
|
);
|
|
741
749
|
} else {
|
|
742
|
-
console.log(`${C.green}
|
|
750
|
+
console.log(`${C.green}Local reset successful.${C.reset}`);
|
|
743
751
|
}
|
|
744
|
-
await askQuestion('
|
|
745
|
-
return bootstrap(); //
|
|
752
|
+
await askQuestion('Press Enter...');
|
|
753
|
+
return bootstrap(); // Return to initial check
|
|
746
754
|
}
|
|
747
755
|
break;
|
|
748
756
|
|
|
@@ -762,18 +770,17 @@ async function bootstrap() {
|
|
|
762
770
|
if (!status.allReady) {
|
|
763
771
|
await firstTimeSetup();
|
|
764
772
|
} else {
|
|
765
|
-
// process.argv[2]
|
|
773
|
+
// process.argv[2] takes the first argument after the command name
|
|
766
774
|
const urlArgument = process.argv[2];
|
|
767
775
|
|
|
768
776
|
if (urlArgument) {
|
|
769
|
-
//
|
|
777
|
+
// If there is a URL in terminal, run download directly
|
|
770
778
|
await startDownload(urlArgument);
|
|
771
779
|
} else {
|
|
772
|
-
//
|
|
780
|
+
// If none, enter main menu as usual
|
|
773
781
|
mainMenu();
|
|
774
782
|
}
|
|
775
783
|
}
|
|
776
784
|
}
|
|
777
785
|
|
|
778
786
|
bootstrap();
|
|
779
|
-
|
package/package.json
CHANGED
package/readme.id.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# 🚀 Media-DL Pro 2026
|
|
2
|
+
|
|
3
|
+
**The Ultimate Cross-Platform Media Engine Manager**
|
|
4
|
+
|
|
5
|
+
Media-DL Pro adalah *CLI wrapper* canggih berbasis `yt-dlp` yang dirancang untuk kecepatan, keteraturan, dan keamanan. Bukan sekadar pengunduh, ini adalah manajer media lokal yang cerdas dengan sistem instalasi otomatis.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## ✨ Fitur Unggulan Baru
|
|
10
|
+
|
|
11
|
+
### 1. ⚡ Direct Download & Menu Mode
|
|
12
|
+
|
|
13
|
+
Sekarang kamu bisa memilih dua cara penggunaan:
|
|
14
|
+
|
|
15
|
+
* **Interactive Mode**: Cukup ketik `media-dl` untuk masuk ke menu utama yang cantik.
|
|
16
|
+
* **Fast Mode**: Ketik `media-dl <url>` untuk langsung masuk ke proses download tanpa basa-basi.
|
|
17
|
+
|
|
18
|
+
### 2. 📱 Android (Termux) Ready
|
|
19
|
+
|
|
20
|
+
Dukungan penuh untuk pengguna mobile via Termux dengan script instalasi otomatis yang menyesuaikan lingkungan Linux Android.
|
|
21
|
+
|
|
22
|
+
### 3. 🛡️ Safe Mode Guard™ (Updated)
|
|
23
|
+
|
|
24
|
+
Menghindari deteksi bot dengan:
|
|
25
|
+
|
|
26
|
+
* **Rate Limiting**: Dibatasi hingga 5 MB/s.
|
|
27
|
+
* **Smart Sleep**: Jeda acak 3–10 detik.
|
|
28
|
+
* **Modern User-Agent**: Identitas browser terbaru agar tetap aman.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 🎞️ Platform yang Didukung
|
|
33
|
+
|
|
34
|
+
Berkat engine `yt-dlp` yang selalu diperbarui, kamu bisa mengunduh dari:
|
|
35
|
+
|
|
36
|
+
* **YouTube**: Video, Shorts, & Playlist.
|
|
37
|
+
* **Social Media**: TikTok, Instagram Reels, Twitter (X).
|
|
38
|
+
* **VOD Services**: Dan ratusan platform video lainnya.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 📦 Instalasi
|
|
43
|
+
|
|
44
|
+
### Prasyarat
|
|
45
|
+
|
|
46
|
+
* **Node.js**: Versi 14.0.0 atau lebih tinggi.
|
|
47
|
+
|
|
48
|
+
### Cara Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install -g media-dl
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Penggunaan
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Buka menu utama
|
|
59
|
+
media-dl
|
|
60
|
+
|
|
61
|
+
# Download langsung tanpa menu
|
|
62
|
+
media-dl https://www.youtube.com/watch?v=example
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 🛠️ Navigasi Sistem
|
|
69
|
+
|
|
70
|
+
1. **📥 Download Media**: Mendukung pemilihan kualitas (Video/Audio MP3) dan seleksi playlist (misal: `1,3,5-10`).
|
|
71
|
+
2. **🛡️ Toggle Safe Mode**: Aktifkan perlindungan tambahan secara *on-the-fly*.
|
|
72
|
+
3. **⚙️ Maintenance**: Update otomatis `yt-dlp` dan `FFmpeg` langsung dari aplikasi tanpa perlu download manual.
|
|
73
|
+
4. **🗑️ Reset System**: Hapus semua engine untuk instalasi ulang yang bersih.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 💻 Kompatibilitas Sistem
|
|
78
|
+
|
|
79
|
+
| Sistem Operasi | Status | Cara Kerja |
|
|
80
|
+
| --- | --- | --- |
|
|
81
|
+
| **Windows** | ✅ Supported | Auto-download `.exe` ke folder `~/.media-dl` |
|
|
82
|
+
| **macOS** | ✅ Supported | Auto-download via `curl` |
|
|
83
|
+
| **Linux** | ✅ Supported | Integrasi via `apt` (Debian/Ubuntu) |
|
|
84
|
+
| **Termux** | ✅ Supported | Integrasi via `pkg` & `pip` |
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 📂 Struktur Penyimpanan
|
|
89
|
+
|
|
90
|
+
Unduhan kamu akan tersimpan rapi di:
|
|
91
|
+
|
|
92
|
+
* **Video**: `~/Downloads/media-dl/video/`
|
|
93
|
+
* **Audio**: `~/Downloads/media-dl/audio/`
|
|
94
|
+
* **Playlist**: Sub-folder otomatis berdasarkan nama playlist.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## ❤️ Dukungan
|
|
99
|
+
|
|
100
|
+
Aplikasi ini dikembangkan oleh **Ariska Hidayat**. Jika bermanfaat, kamu bisa memberikan dukungan untuk biaya pemeliharaan server/engine:
|
|
101
|
+
|
|
102
|
+
* **☕ Traktir Kopi**: [Midtrans Coffee](https://app.midtrans.com/coffee)
|
|
103
|
+
* **🍕 Beli Pizza**: [Midtrans Pizza](https://app.midtrans.com/pizza)
|
package/readme.md
CHANGED
|
@@ -1,103 +1,113 @@
|
|
|
1
|
-
# 🚀 Media-DL Pro 2026
|
|
2
|
-
|
|
3
|
-
**The Ultimate Cross-Platform Media Engine Manager**
|
|
4
|
-
|
|
5
|
-
Media-DL Pro
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## ✨
|
|
10
|
-
|
|
11
|
-
### 1. ⚡ Direct Download & Menu Mode
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
* **Interactive Mode**:
|
|
16
|
-
* **Fast Mode**:
|
|
17
|
-
|
|
18
|
-
### 2. 📱 Android (Termux) Ready
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
### 3. 🛡️ Safe Mode Guard™ (Updated)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
* **Rate Limiting**:
|
|
27
|
-
* **Smart Sleep**:
|
|
28
|
-
* **Modern User-Agent**:
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## 🎞️
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
* **YouTube**:
|
|
37
|
-
* **Social Media**: TikTok, Instagram Reels, Twitter (X).
|
|
38
|
-
* **VOD Services**:
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
## 📦
|
|
43
|
-
|
|
44
|
-
###
|
|
45
|
-
|
|
46
|
-
* **Node.js**:
|
|
47
|
-
|
|
48
|
-
###
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
npm install -g media-dl
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
2. **🛡️ Toggle Safe Mode
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
1
|
+
# 🚀 Media-DL Pro 2026
|
|
2
|
+
|
|
3
|
+
**The Ultimate Cross-Platform Media Engine Manager**
|
|
4
|
+
|
|
5
|
+
Media-DL Pro is an advanced *CLI wrapper* built on top of `yt-dlp`, designed for speed, structure, and security. It is not just a downloader, but a smart local media manager with an automated installation system.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## ✨ New Key Features
|
|
10
|
+
|
|
11
|
+
### 1. ⚡ Direct Download & Menu Mode
|
|
12
|
+
|
|
13
|
+
You can now choose between two usage styles:
|
|
14
|
+
|
|
15
|
+
* **Interactive Mode**: Simply run `media-dl` to access a clean, user-friendly main menu.
|
|
16
|
+
* **Fast Mode**: Run `media-dl <url>` to start downloading immediately without entering the menu.
|
|
17
|
+
|
|
18
|
+
### 2. 📱 Android (Termux) Ready
|
|
19
|
+
|
|
20
|
+
Full support for mobile users via Termux, with automatic installation scripts tailored for the Android Linux environment.
|
|
21
|
+
|
|
22
|
+
### 3. 🛡️ Safe Mode Guard™ (Updated)
|
|
23
|
+
|
|
24
|
+
Designed to avoid bot detection through:
|
|
25
|
+
|
|
26
|
+
* **Rate Limiting**: Capped at 5 MB/s.
|
|
27
|
+
* **Smart Sleep**: Random delays between 3–10 seconds.
|
|
28
|
+
* **Modern User-Agent**: Uses up-to-date browser identifiers for safer requests.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 🎞️ Supported Platforms
|
|
33
|
+
|
|
34
|
+
Powered by the continuously updated `yt-dlp` engine, Media-DL Pro supports downloads from:
|
|
35
|
+
|
|
36
|
+
* **YouTube**: Videos, Shorts, and Playlists.
|
|
37
|
+
* **Social Media**: TikTok, Instagram Reels, Twitter (X).
|
|
38
|
+
* **VOD Services**: And hundreds of other video platforms.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 📦 Installation
|
|
43
|
+
|
|
44
|
+
### Requirements
|
|
45
|
+
|
|
46
|
+
* **Node.js**: Version 14.0.0 or later.
|
|
47
|
+
|
|
48
|
+
### Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install -g media-dl
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Usage
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Open the main menu
|
|
58
|
+
media-dl
|
|
59
|
+
|
|
60
|
+
# Direct download without menu
|
|
61
|
+
media-dl https://www.youtube.com/watch?v=example
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 🛠️ System Navigation
|
|
67
|
+
|
|
68
|
+
1. **📥 Download Media**
|
|
69
|
+
Supports quality selection (Video / MP3 Audio) and playlist filtering (e.g. `1,3,5-10`).
|
|
70
|
+
|
|
71
|
+
2. **🛡️ Toggle Safe Mode**
|
|
72
|
+
Enable or disable additional protection on the fly.
|
|
73
|
+
|
|
74
|
+
3. **⚙️ Maintenance**
|
|
75
|
+
Automatically update `yt-dlp` and `FFmpeg` directly from the app—no manual downloads required.
|
|
76
|
+
|
|
77
|
+
4. **🗑️ Reset System**
|
|
78
|
+
Remove all engines for a clean reinstallation.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 💻 System Compatibility
|
|
83
|
+
|
|
84
|
+
| Operating System | Status | Method |
|
|
85
|
+
| ---------------- | ----------- | --------------------------------------- |
|
|
86
|
+
| **Windows** | ✅ Supported | Auto-download `.exe` into `~/.media-dl` |
|
|
87
|
+
| **macOS** | ✅ Supported | Auto-download via `curl` |
|
|
88
|
+
| **Linux** | ✅ Supported | Integrated via `apt` (Debian/Ubuntu) |
|
|
89
|
+
| **Termux** | ✅ Supported | Integrated via `pkg` & `pip` |
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 📂 Storage Structure
|
|
94
|
+
|
|
95
|
+
Your downloads are neatly organized under:
|
|
96
|
+
|
|
97
|
+
* **Video**: `~/Downloads/media-dl/video/`
|
|
98
|
+
* **Audio**: `~/Downloads/media-dl/audio/`
|
|
99
|
+
* **Playlists**: Automatically grouped into subfolders by playlist name.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## ❤️ Support
|
|
104
|
+
|
|
105
|
+
This project is developed and maintained by **Ariska Hidayat**.
|
|
106
|
+
If you find it useful, you can support ongoing development and server/engine maintenance via:
|
|
107
|
+
|
|
108
|
+
* **☕ Buy Me a Coffee (Indonesia)**:
|
|
109
|
+
[https://app.midtrans.com/coffee](https://app.midtrans.com/coffee)
|
|
110
|
+
* **🍕 Buy Me a Pizza (Indonesia)**:
|
|
111
|
+
[https://app.midtrans.com/pizza](https://app.midtrans.com/pizza)
|
|
112
|
+
* **🌍 PayPal (International)**:
|
|
113
|
+
[https://www.paypal.com/ncp/payment/RSXEBXBQGDYN4](https://www.paypal.com/ncp/payment/RSXEBXBQGDYN4)
|