media-dl 2.5.1 → 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 +161 -142
- 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,25 +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
|
-
await
|
|
271
|
-
return mainMenu();
|
|
269
|
+
await backToMenu();
|
|
272
270
|
}
|
|
273
271
|
|
|
274
272
|
if (!ffExists) {
|
|
275
273
|
console.log(
|
|
276
|
-
`${C.yellow}⚠️
|
|
274
|
+
`${C.yellow}⚠️ Warning: FFmpeg not found. Video might not be merged with audio.${C.reset}`,
|
|
277
275
|
);
|
|
278
|
-
const cont = await askQuestion('
|
|
276
|
+
const cont = await askQuestion('Continue anyway? (y/n): ');
|
|
279
277
|
if (cont.toLowerCase() !== 'y') return mainMenu();
|
|
280
278
|
}
|
|
281
279
|
|
|
282
280
|
const videoURL =
|
|
283
|
-
videoURLFromArgs || (await askQuestion('
|
|
281
|
+
videoURLFromArgs || (await askQuestion('Enter Link (Video/Playlist): '));
|
|
284
282
|
if (!videoURL) return mainMenu();
|
|
285
283
|
|
|
286
|
-
console.log(`${C.dim}⏳
|
|
284
|
+
console.log(`${C.dim}⏳ Analyzing link...${C.reset}`);
|
|
287
285
|
let playlistInfo = { isPlaylist: false, title: '', items: [] };
|
|
288
286
|
|
|
289
287
|
try {
|
|
@@ -298,30 +296,29 @@ async function startDownload(videoURLFromArgs = null) {
|
|
|
298
296
|
const lines = rawInfo.trim().split('\n');
|
|
299
297
|
if (lines.length > 1 || videoURL.includes('playlist?list=')) {
|
|
300
298
|
playlistInfo.isPlaylist = true;
|
|
301
|
-
playlistInfo.title = lines[0].split('|')[0] || '
|
|
299
|
+
playlistInfo.title = lines[0].split('|')[0] || 'Playlist Download';
|
|
302
300
|
playlistInfo.items = lines.map((l) => l.split('|')[1]).filter(Boolean);
|
|
303
301
|
}
|
|
304
302
|
} catch (e) {
|
|
305
|
-
console.log(`\n${C.red}❌
|
|
303
|
+
console.log(`\n${C.red}❌ Failed to analyze link.${C.reset}`);
|
|
306
304
|
if (e.message.includes('ETIMEDOUT')) {
|
|
307
305
|
console.log(
|
|
308
|
-
`${C.yellow}⚠️
|
|
306
|
+
`${C.yellow}⚠️ Analysis timed out. Check your internet connection.${C.reset}`,
|
|
309
307
|
);
|
|
310
308
|
} else {
|
|
311
309
|
console.log(
|
|
312
|
-
`${C.yellow}⚠️
|
|
310
|
+
`${C.yellow}⚠️ Ensure link is valid or not private/deleted.${C.reset}`,
|
|
313
311
|
);
|
|
314
312
|
}
|
|
315
|
-
await
|
|
316
|
-
return mainMenu();
|
|
313
|
+
await backToMenu();
|
|
317
314
|
}
|
|
318
315
|
|
|
319
|
-
// ---
|
|
316
|
+
// --- PLAYLIST SELECTION (Logic remains same) ---
|
|
320
317
|
let playlistSelection = null;
|
|
321
318
|
if (playlistInfo.isPlaylist) {
|
|
322
|
-
// ... (
|
|
319
|
+
// ... (Playlist display logic same as before)
|
|
323
320
|
console.log(
|
|
324
|
-
`\n${C.bgBlue}${C.bright} 📂 PLAYLIST
|
|
321
|
+
`\n${C.bgBlue}${C.bright} 📂 PLAYLIST DETECTED: ${playlistInfo.title} ${C.reset}`,
|
|
325
322
|
);
|
|
326
323
|
playlistInfo.items.forEach((item, index) => {
|
|
327
324
|
console.log(
|
|
@@ -329,11 +326,11 @@ async function startDownload(videoURLFromArgs = null) {
|
|
|
329
326
|
);
|
|
330
327
|
});
|
|
331
328
|
console.log(
|
|
332
|
-
`\n${C.dim}
|
|
329
|
+
`\n${C.dim}Example: 1,3,5-10 or leave empty for all.${C.reset}`,
|
|
333
330
|
);
|
|
334
|
-
const selectionInput = await askQuestion('\
|
|
331
|
+
const selectionInput = await askQuestion('\nSelect numbers: ');
|
|
335
332
|
if (selectionInput) {
|
|
336
|
-
// ... (
|
|
333
|
+
// ... (Playlist parsing logic)
|
|
337
334
|
const selected = new Set();
|
|
338
335
|
selectionInput.split(',').forEach((p) => {
|
|
339
336
|
if (p.includes('-')) {
|
|
@@ -349,20 +346,20 @@ async function startDownload(videoURLFromArgs = null) {
|
|
|
349
346
|
}
|
|
350
347
|
}
|
|
351
348
|
|
|
352
|
-
// ---
|
|
353
|
-
console.log(`\n${C.bright} [
|
|
349
|
+
// --- MAIN FORMAT MENU ---
|
|
350
|
+
console.log(`\n${C.bright} [ SELECT FORMAT ]${C.reset}`);
|
|
354
351
|
console.log(` ${C.green}1.${C.reset} Video (MP4)`);
|
|
355
352
|
console.log(` ${C.green}2.${C.reset} Audio Only (MP3)`);
|
|
356
|
-
const mode = await askQuestion('
|
|
353
|
+
const mode = await askQuestion('Choice: ');
|
|
357
354
|
|
|
358
|
-
// ---
|
|
355
|
+
// --- OPTIMAL RESOLUTION LOGIC ---
|
|
359
356
|
let formatArg = '';
|
|
360
357
|
if (mode === '1') {
|
|
361
|
-
console.log(`\n${C.bright} [
|
|
358
|
+
console.log(`\n${C.bright} [ SELECT RESOLUTION ]${C.reset}`);
|
|
362
359
|
console.log(` ${C.cyan}1.${C.reset} Best Quality (Up to 4K)`);
|
|
363
360
|
console.log(` ${C.cyan}2.${C.reset} Tablet Optimal (720p)`);
|
|
364
361
|
console.log(` ${C.cyan}3.${C.reset} Mobile Optimal (480p)`);
|
|
365
|
-
const resChoice = await askQuestion('
|
|
362
|
+
const resChoice = await askQuestion('Select Resolution (1-3): ');
|
|
366
363
|
|
|
367
364
|
if (resChoice === '2') {
|
|
368
365
|
formatArg =
|
|
@@ -375,13 +372,13 @@ async function startDownload(videoURLFromArgs = null) {
|
|
|
375
372
|
'bestvideo[vcodec^=avc1]+bestaudio[acodec^=mp4a]/best[vcodec^=avc1]/best';
|
|
376
373
|
}
|
|
377
374
|
}
|
|
378
|
-
// ---
|
|
375
|
+
// --- DISPLAY ESTIMATE ---
|
|
379
376
|
if (!playlistInfo.isPlaylist && mode === '1') {
|
|
380
|
-
console.log(`${C.dim}⏳
|
|
377
|
+
console.log(`${C.dim}⏳ Calculating file size estimate...${C.reset}`);
|
|
381
378
|
const size = await getEstimate(videoURL, formatArg);
|
|
382
|
-
console.log(`${C.yellow}📊
|
|
379
|
+
console.log(`${C.yellow}📊 Estimated Size: ${C.bright}${size}${C.reset}`);
|
|
383
380
|
|
|
384
|
-
const confirm = await askQuestion('
|
|
381
|
+
const confirm = await askQuestion('Proceed with download? (Y/n): ');
|
|
385
382
|
if (confirm.toLowerCase() === 'n') return mainMenu();
|
|
386
383
|
}
|
|
387
384
|
|
|
@@ -403,7 +400,7 @@ async function startDownload(videoURLFromArgs = null) {
|
|
|
403
400
|
videoURL,
|
|
404
401
|
];
|
|
405
402
|
|
|
406
|
-
//
|
|
403
|
+
// Integrate Safe Mode (cite: cli.js)
|
|
407
404
|
if (safeMode) {
|
|
408
405
|
args.push(
|
|
409
406
|
'--rate-limit',
|
|
@@ -423,7 +420,7 @@ async function startDownload(videoURLFromArgs = null) {
|
|
|
423
420
|
if (mode === '2') {
|
|
424
421
|
if (!ffExists) {
|
|
425
422
|
console.log(
|
|
426
|
-
`${C.red}❌ Error:
|
|
423
|
+
`${C.red}❌ Error: FFmpeg is required to download audio.${C.reset}`,
|
|
427
424
|
);
|
|
428
425
|
return mainMenu();
|
|
429
426
|
}
|
|
@@ -434,11 +431,11 @@ async function startDownload(videoURLFromArgs = null) {
|
|
|
434
431
|
}
|
|
435
432
|
|
|
436
433
|
args.push('--no-mtime');
|
|
437
|
-
console.log(`\n${C.bgBlue}${C.bright} 🚀
|
|
434
|
+
console.log(`\n${C.bgBlue}${C.bright} 🚀 STARTING PROCESS... ${C.reset}\n`);
|
|
438
435
|
|
|
439
436
|
const code = await runSpawn(YTDLP_PATH, args);
|
|
440
437
|
if (code === 0) {
|
|
441
|
-
console.log(`\n${C.green}✨
|
|
438
|
+
console.log(`\n${C.green}✨ DONE! Check folder: ${outputDir}${C.reset}`);
|
|
442
439
|
try {
|
|
443
440
|
execSync(
|
|
444
441
|
isWindows
|
|
@@ -449,27 +446,42 @@ async function startDownload(videoURLFromArgs = null) {
|
|
|
449
446
|
);
|
|
450
447
|
} catch (e) {}
|
|
451
448
|
} else {
|
|
452
|
-
console.log(`\n${C.red}❌
|
|
449
|
+
console.log(`\n${C.red}❌ An error occurred while downloading.${C.reset}`);
|
|
450
|
+
}
|
|
451
|
+
await backToMenu();
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async function backToMenu() {
|
|
455
|
+
try {
|
|
456
|
+
await askQuestion('Press Enter to return to Main Menu...');
|
|
457
|
+
mainMenu(); // Return to menu
|
|
458
|
+
} catch (err) {
|
|
459
|
+
// Handle if readline is already closed (Ctrl+C pressed previously)
|
|
460
|
+
if (err.code === 'ERR_USE_AFTER_CLOSE') {
|
|
461
|
+
console.log(`\n${C.dim}Program terminated by user.${C.reset}`);
|
|
462
|
+
process.exit(0);
|
|
463
|
+
} else {
|
|
464
|
+
// Throw other errors if necessary
|
|
465
|
+
throw err;
|
|
466
|
+
}
|
|
453
467
|
}
|
|
454
|
-
await askQuestion('Tekan Enter untuk kembali ke Menu Utama...');
|
|
455
|
-
mainMenu();
|
|
456
468
|
}
|
|
457
469
|
|
|
458
470
|
async function showSupport() {
|
|
459
|
-
//
|
|
460
|
-
printHeader('
|
|
471
|
+
// Using 2 parameters: Title and Summary
|
|
472
|
+
printHeader('ABOUT APPLICATION', 'Media-DL Manager Pro v2.0.0 - 2026');
|
|
461
473
|
|
|
462
|
-
// ---
|
|
474
|
+
// --- FEATURES SECTION ---
|
|
463
475
|
console.log(` ${C.bright}${C.cyan}OVERVIEW${C.reset}`);
|
|
464
|
-
console.log(`
|
|
465
|
-
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`);
|
|
466
478
|
|
|
467
|
-
console.log(` ${C.bright}${C.cyan}
|
|
479
|
+
console.log(` ${C.bright}${C.cyan}KEY FEATURES${C.reset}`);
|
|
468
480
|
const features = [
|
|
469
481
|
{
|
|
470
482
|
icon: '✦',
|
|
471
483
|
title: 'High Quality',
|
|
472
|
-
desc: '
|
|
484
|
+
desc: 'Supports up to 4K & 320kbps Audio',
|
|
473
485
|
},
|
|
474
486
|
{
|
|
475
487
|
icon: '✦',
|
|
@@ -479,12 +491,12 @@ async function showSupport() {
|
|
|
479
491
|
{
|
|
480
492
|
icon: '✦',
|
|
481
493
|
title: 'Batch Mode',
|
|
482
|
-
desc: '
|
|
494
|
+
desc: 'Supports bulk Playlist downloading',
|
|
483
495
|
},
|
|
484
496
|
{
|
|
485
497
|
icon: '✦',
|
|
486
498
|
title: 'Safe Guard',
|
|
487
|
-
desc: '
|
|
499
|
+
desc: 'Protection mode to prevent account/IP blocks',
|
|
488
500
|
},
|
|
489
501
|
];
|
|
490
502
|
|
|
@@ -498,15 +510,25 @@ async function showSupport() {
|
|
|
498
510
|
|
|
499
511
|
console.log('\n' + '─'.repeat(52));
|
|
500
512
|
|
|
501
|
-
// ---
|
|
502
|
-
console.log(`\n ${C.bright}${C.magenta}
|
|
503
|
-
console.log(`
|
|
504
|
-
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`);
|
|
505
517
|
|
|
506
|
-
//
|
|
518
|
+
// Display Links with background label to stand out
|
|
507
519
|
const links = [
|
|
508
|
-
{
|
|
509
|
-
|
|
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
|
+
},
|
|
510
532
|
];
|
|
511
533
|
|
|
512
534
|
links.forEach((l) => {
|
|
@@ -517,18 +539,17 @@ async function showSupport() {
|
|
|
517
539
|
|
|
518
540
|
console.log(`\n${C.cyan}${'━'.repeat(52)}${C.reset}`);
|
|
519
541
|
|
|
520
|
-
await
|
|
521
|
-
mainMenu();
|
|
542
|
+
await backToMenu();
|
|
522
543
|
}
|
|
523
544
|
|
|
524
545
|
async function mainMenu() {
|
|
525
546
|
const status = checkTools();
|
|
526
547
|
const { ytExists, ffExists } = status;
|
|
527
548
|
|
|
528
|
-
//
|
|
529
|
-
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');
|
|
530
551
|
|
|
531
|
-
// ---
|
|
552
|
+
// --- DASHBOARD SECTION (SYSTEM INFO) ---
|
|
532
553
|
const ytLabel = status.isLocalYt
|
|
533
554
|
? `${C.green}Ready (Internal)${C.reset}`
|
|
534
555
|
: status.ytExists
|
|
@@ -551,14 +572,14 @@ async function mainMenu() {
|
|
|
551
572
|
|
|
552
573
|
console.log(` ${C.cyan}━${'━'.repeat(48)}${C.reset}`);
|
|
553
574
|
|
|
554
|
-
// ---
|
|
575
|
+
// --- NAVIGATION SECTION ---
|
|
555
576
|
console.log(` ${C.bright}MAIN SERVICES${C.reset}`);
|
|
556
577
|
console.log(
|
|
557
578
|
` ${C.cyan}1.${C.reset} 📥 Download Media ${C.dim}(Video, Music, Playlist)${C.reset}`,
|
|
558
579
|
);
|
|
559
580
|
console.log(
|
|
560
|
-
` ${C.cyan}2.${C.reset} 🛡️ Toggle Safe Mode ${C.dim}(
|
|
561
|
-
safeMode ? '
|
|
581
|
+
` ${C.cyan}2.${C.reset} 🛡️ Toggle Safe Mode ${C.dim}(Current: ${
|
|
582
|
+
safeMode ? 'Active' : 'Inactive'
|
|
562
583
|
})${C.reset}`,
|
|
563
584
|
);
|
|
564
585
|
|
|
@@ -567,13 +588,13 @@ async function mainMenu() {
|
|
|
567
588
|
` ${C.cyan}3.${C.reset} ⚙️ Maintenance & Update ${C.dim}(Update engine / Cleanup)${C.reset}`,
|
|
568
589
|
);
|
|
569
590
|
console.log(
|
|
570
|
-
` ${C.cyan}4.${C.reset} ❤️
|
|
591
|
+
` ${C.cyan}4.${C.reset} ❤️ About Application ${C.dim}(Support & Features)${C.reset}`,
|
|
571
592
|
);
|
|
572
|
-
console.log(` ${C.cyan}0.${C.reset} 🚪
|
|
593
|
+
console.log(` ${C.cyan}0.${C.reset} 🚪 Exit`);
|
|
573
594
|
|
|
574
595
|
console.log(` ${C.cyan}━${'━'.repeat(48)}${C.reset}`);
|
|
575
596
|
|
|
576
|
-
const choice = await askQuestion('\
|
|
597
|
+
const choice = await askQuestion('\nSelect menu (0-4): ');
|
|
577
598
|
|
|
578
599
|
switch (choice) {
|
|
579
600
|
case '1':
|
|
@@ -581,10 +602,10 @@ async function mainMenu() {
|
|
|
581
602
|
break;
|
|
582
603
|
case '2':
|
|
583
604
|
safeMode = !safeMode;
|
|
584
|
-
//
|
|
605
|
+
// Provide short visual feedback before refreshing menu
|
|
585
606
|
console.log(
|
|
586
|
-
`\n${C.yellow} 🛡️ Safe Mode
|
|
587
|
-
safeMode ? '
|
|
607
|
+
`\n${C.yellow} 🛡️ Safe Mode has been ${
|
|
608
|
+
safeMode ? 'ENABLED' : 'DISABLED'
|
|
588
609
|
}${C.reset}`,
|
|
589
610
|
);
|
|
590
611
|
setTimeout(() => mainMenu(), 800);
|
|
@@ -598,14 +619,14 @@ async function mainMenu() {
|
|
|
598
619
|
case '0':
|
|
599
620
|
console.log(`\n${C.cyan}━${'━'.repeat(48)}${C.reset}`);
|
|
600
621
|
console.log(
|
|
601
|
-
` ${C.bright}${C.white}
|
|
622
|
+
` ${C.bright}${C.white}Thank you for using MEDIA-DL!${C.reset}`,
|
|
602
623
|
);
|
|
603
624
|
console.log(
|
|
604
|
-
` ${C.green}✨
|
|
625
|
+
` ${C.green}✨ Wishing you success, prosperity, and good health! ✨${C.reset}`,
|
|
605
626
|
);
|
|
606
627
|
console.log(`${C.cyan}━${'━'.repeat(48)}${C.reset}\n`);
|
|
607
628
|
|
|
608
|
-
//
|
|
629
|
+
// Give a short delay before actually closing terminal
|
|
609
630
|
setTimeout(() => {
|
|
610
631
|
rl.close();
|
|
611
632
|
process.exit(0);
|
|
@@ -613,7 +634,7 @@ async function mainMenu() {
|
|
|
613
634
|
|
|
614
635
|
break;
|
|
615
636
|
default:
|
|
616
|
-
//
|
|
637
|
+
// If input incorrect, show menu again
|
|
617
638
|
mainMenu();
|
|
618
639
|
break;
|
|
619
640
|
}
|
|
@@ -624,7 +645,7 @@ async function cleanUp() {
|
|
|
624
645
|
fs.rmSync(TOOLS_DIR, { recursive: true, force: true });
|
|
625
646
|
}
|
|
626
647
|
|
|
627
|
-
// RESET PATH
|
|
648
|
+
// RESET PATH to local default so checkTools doesn't "get lost" using old path
|
|
628
649
|
YTDLP_PATH = path.join(TOOLS_DIR, isWindows ? 'yt-dlp.exe' : 'yt-dlp');
|
|
629
650
|
FFMPEG_PATH = path.join(TOOLS_DIR, isWindows ? 'ffmpeg.exe' : 'ffmpeg');
|
|
630
651
|
}
|
|
@@ -634,28 +655,28 @@ async function firstTimeSetup() {
|
|
|
634
655
|
const { ytExists, ffExists } = checkTools();
|
|
635
656
|
printHeader(
|
|
636
657
|
'FIRST-TIME SETUP',
|
|
637
|
-
'
|
|
658
|
+
'Components required to run the application',
|
|
638
659
|
);
|
|
639
660
|
|
|
640
|
-
console.log(`${C.white}Status
|
|
661
|
+
console.log(`${C.white}Installation Status:${C.reset}`);
|
|
641
662
|
console.log(
|
|
642
663
|
` [${ytExists ? C.green + '✓' : C.red + '✗'}${
|
|
643
664
|
C.reset
|
|
644
|
-
}]
|
|
665
|
+
}] yt-dlp Engine (Required)`,
|
|
645
666
|
);
|
|
646
667
|
console.log(
|
|
647
668
|
` [${ffExists ? C.green + '✓' : C.red + '✗'}${
|
|
648
669
|
C.reset
|
|
649
|
-
}] FFmpeg (
|
|
670
|
+
}] FFmpeg (Recommended)`,
|
|
650
671
|
);
|
|
651
672
|
|
|
652
673
|
console.log(
|
|
653
|
-
`\n${C.yellow}
|
|
674
|
+
`\n${C.yellow}Application is not ready. Select option:${C.reset}`,
|
|
654
675
|
);
|
|
655
|
-
console.log(` ${C.cyan}1.${C.reset} Install
|
|
656
|
-
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`);
|
|
657
678
|
|
|
658
|
-
const choice = await askQuestion('\
|
|
679
|
+
const choice = await askQuestion('\nSelect: ');
|
|
659
680
|
|
|
660
681
|
if (choice === '1') {
|
|
661
682
|
if (!ytExists) await installYtdlp();
|
|
@@ -664,13 +685,13 @@ async function firstTimeSetup() {
|
|
|
664
685
|
const status = checkTools();
|
|
665
686
|
if (status.ytExists) {
|
|
666
687
|
console.log(
|
|
667
|
-
`\n${C.green}✨ Setup
|
|
688
|
+
`\n${C.green}✨ Setup Complete! Opening Main Menu...${C.reset}`,
|
|
668
689
|
);
|
|
669
690
|
await new Promise((r) => setTimeout(r, 1500));
|
|
670
|
-
return mainMenu(); //
|
|
691
|
+
return mainMenu(); // Success, proceed to main menu
|
|
671
692
|
}
|
|
672
693
|
} else if (choice === '0') {
|
|
673
|
-
console.log('
|
|
694
|
+
console.log('Closing application...');
|
|
674
695
|
process.exit(0);
|
|
675
696
|
}
|
|
676
697
|
}
|
|
@@ -681,12 +702,9 @@ async function systemMaintenance() {
|
|
|
681
702
|
|
|
682
703
|
while (inMaintenance) {
|
|
683
704
|
const { ytExists, ffExists } = checkTools();
|
|
684
|
-
printHeader(
|
|
685
|
-
'SYSTEM MAINTENANCE',
|
|
686
|
-
'Update engine atau bersihkan file sistem',
|
|
687
|
-
);
|
|
705
|
+
printHeader('SYSTEM MAINTENANCE', 'Update engine or clean system files');
|
|
688
706
|
|
|
689
|
-
console.log(`${C.white}
|
|
707
|
+
console.log(`${C.white}Installed Versions:${C.reset}`);
|
|
690
708
|
console.log(
|
|
691
709
|
` • yt-dlp : ${ytExists ? C.green + 'Ready' : C.red + 'Not Found'}${
|
|
692
710
|
C.reset
|
|
@@ -698,39 +716,41 @@ async function systemMaintenance() {
|
|
|
698
716
|
}`,
|
|
699
717
|
);
|
|
700
718
|
|
|
701
|
-
console.log(`\n${C.bright}
|
|
719
|
+
console.log(`\n${C.bright}Maintenance Options:${C.reset}`);
|
|
702
720
|
console.log(` ${C.cyan}1.${C.reset} Update / Reinstall Engines`);
|
|
703
|
-
console.log(` ${C.cyan}2.${C.reset} 🗑️
|
|
704
|
-
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`);
|
|
705
723
|
|
|
706
|
-
const choice = await askQuestion('\
|
|
724
|
+
const choice = await askQuestion('\nSelect action: ');
|
|
707
725
|
|
|
708
726
|
switch (choice) {
|
|
709
727
|
case '1':
|
|
710
728
|
await installYtdlp();
|
|
711
729
|
await installFfmpeg();
|
|
712
|
-
await askQuestion('\nUpdate
|
|
730
|
+
await askQuestion('\nUpdate complete. Press Enter...');
|
|
713
731
|
break;
|
|
714
732
|
|
|
715
733
|
case '2':
|
|
716
734
|
const confirm = await askQuestion(
|
|
717
|
-
`${C.bgRed}${C.white}
|
|
735
|
+
`${C.bgRed}${C.white} CONFIRMATION ${C.reset} Delete all tools? (y/n): `,
|
|
718
736
|
);
|
|
719
737
|
if (confirm.toLowerCase() === 'y') {
|
|
720
|
-
await cleanUp(); //
|
|
721
|
-
console.log(
|
|
738
|
+
await cleanUp(); // Call folder deletion function
|
|
739
|
+
console.log(
|
|
740
|
+
`${C.yellow}Folder .media-dl has been deleted.${C.reset}`,
|
|
741
|
+
);
|
|
722
742
|
|
|
723
|
-
//
|
|
743
|
+
// Re-check status after deletion
|
|
724
744
|
const finalCheck = checkTools();
|
|
725
745
|
if (finalCheck.isLocalYt || finalCheck.isLocalFf) {
|
|
726
746
|
console.log(
|
|
727
|
-
`${C.red}
|
|
747
|
+
`${C.red}Failed to delete some files. Ensure no processes are locking them.${C.reset}`,
|
|
728
748
|
);
|
|
729
749
|
} else {
|
|
730
|
-
console.log(`${C.green}
|
|
750
|
+
console.log(`${C.green}Local reset successful.${C.reset}`);
|
|
731
751
|
}
|
|
732
|
-
await askQuestion('
|
|
733
|
-
return bootstrap(); //
|
|
752
|
+
await askQuestion('Press Enter...');
|
|
753
|
+
return bootstrap(); // Return to initial check
|
|
734
754
|
}
|
|
735
755
|
break;
|
|
736
756
|
|
|
@@ -750,18 +770,17 @@ async function bootstrap() {
|
|
|
750
770
|
if (!status.allReady) {
|
|
751
771
|
await firstTimeSetup();
|
|
752
772
|
} else {
|
|
753
|
-
// process.argv[2]
|
|
773
|
+
// process.argv[2] takes the first argument after the command name
|
|
754
774
|
const urlArgument = process.argv[2];
|
|
755
775
|
|
|
756
776
|
if (urlArgument) {
|
|
757
|
-
//
|
|
777
|
+
// If there is a URL in terminal, run download directly
|
|
758
778
|
await startDownload(urlArgument);
|
|
759
779
|
} else {
|
|
760
|
-
//
|
|
780
|
+
// If none, enter main menu as usual
|
|
761
781
|
mainMenu();
|
|
762
782
|
}
|
|
763
783
|
}
|
|
764
784
|
}
|
|
765
785
|
|
|
766
786
|
bootstrap();
|
|
767
|
-
|
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)
|