media-dl 2.5.2 → 2.7.0
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 +140 -513
- package/package.json +1 -1
- package/readme.id.md +103 -0
- package/readme.md +113 -103
package/bin/cli.js
CHANGED
|
@@ -6,10 +6,8 @@ const fs = require('fs');
|
|
|
6
6
|
const os = require('os');
|
|
7
7
|
const { C, printHeader, renderProgressBar, askQuestion, rl } = require('./ui');
|
|
8
8
|
|
|
9
|
-
// ---
|
|
10
|
-
|
|
9
|
+
// --- CONFIGURATION ---
|
|
11
10
|
const TOOLS_DIR = path.join(os.homedir(), '.media-dl');
|
|
12
|
-
|
|
13
11
|
const isWindows = process.platform === 'win32';
|
|
14
12
|
const isMac = process.platform === 'darwin';
|
|
15
13
|
const isTermux =
|
|
@@ -18,28 +16,21 @@ const isTermux =
|
|
|
18
16
|
let YTDLP_PATH = path.join(TOOLS_DIR, isWindows ? 'yt-dlp.exe' : 'yt-dlp');
|
|
19
17
|
let FFMPEG_PATH = path.join(TOOLS_DIR, isWindows ? 'ffmpeg.exe' : 'ffmpeg');
|
|
20
18
|
|
|
21
|
-
// State Aplikasi
|
|
22
19
|
let safeMode = true;
|
|
23
20
|
|
|
24
21
|
if (!fs.existsSync(TOOLS_DIR)) fs.mkdirSync(TOOLS_DIR, { recursive: true });
|
|
25
22
|
|
|
26
23
|
function checkTools() {
|
|
27
|
-
// Cek jalur default lokal (internal)
|
|
28
24
|
const LOCAL_YT = path.join(TOOLS_DIR, isWindows ? 'yt-dlp.exe' : 'yt-dlp');
|
|
29
25
|
const LOCAL_FF = path.join(TOOLS_DIR, isWindows ? 'ffmpeg.exe' : 'ffmpeg');
|
|
30
26
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
let ytExists = isLocalYt;
|
|
35
|
-
let ffExists = isLocalFf;
|
|
27
|
+
let ytExists = fs.existsSync(LOCAL_YT);
|
|
28
|
+
let ffExists = fs.existsSync(LOCAL_FF);
|
|
36
29
|
|
|
37
|
-
|
|
38
|
-
if (
|
|
39
|
-
if (isLocalFf) FFMPEG_PATH = LOCAL_FF;
|
|
30
|
+
if (ytExists) YTDLP_PATH = LOCAL_YT;
|
|
31
|
+
if (ffExists) FFMPEG_PATH = LOCAL_FF;
|
|
40
32
|
|
|
41
|
-
|
|
42
|
-
if (!isLocalYt) {
|
|
33
|
+
if (!ytExists) {
|
|
43
34
|
try {
|
|
44
35
|
const cmd = isWindows ? 'where yt-dlp' : 'which yt-dlp';
|
|
45
36
|
const pathFound = execSync(cmd, { stdio: ['ignore', 'pipe', 'ignore'] })
|
|
@@ -51,12 +42,11 @@ function checkTools() {
|
|
|
51
42
|
ytExists = true;
|
|
52
43
|
}
|
|
53
44
|
} catch (e) {
|
|
54
|
-
YTDLP_PATH = LOCAL_YT;
|
|
45
|
+
YTDLP_PATH = LOCAL_YT;
|
|
55
46
|
}
|
|
56
47
|
}
|
|
57
48
|
|
|
58
|
-
|
|
59
|
-
if (!isLocalFf) {
|
|
49
|
+
if (!ffExists) {
|
|
60
50
|
try {
|
|
61
51
|
const cmd = isWindows ? 'where ffmpeg' : 'which ffmpeg';
|
|
62
52
|
const globalPath = execSync(cmd, { stdio: ['ignore', 'pipe', 'ignore'] })
|
|
@@ -68,173 +58,110 @@ function checkTools() {
|
|
|
68
58
|
ffExists = true;
|
|
69
59
|
}
|
|
70
60
|
} catch (e) {
|
|
71
|
-
FFMPEG_PATH = LOCAL_FF;
|
|
61
|
+
FFMPEG_PATH = LOCAL_FF;
|
|
72
62
|
}
|
|
73
63
|
}
|
|
74
64
|
|
|
75
65
|
return {
|
|
76
66
|
ytExists,
|
|
77
67
|
ffExists,
|
|
78
|
-
isLocalYt,
|
|
79
|
-
isLocalFf,
|
|
68
|
+
isLocalYt: fs.existsSync(LOCAL_YT),
|
|
69
|
+
isLocalFf: fs.existsSync(LOCAL_FF),
|
|
80
70
|
allReady: ytExists && ffExists,
|
|
81
71
|
};
|
|
82
72
|
}
|
|
83
73
|
|
|
84
74
|
// --- INSTALLERS ---
|
|
85
75
|
async function installYtdlp() {
|
|
86
|
-
if (!fs.existsSync(TOOLS_DIR)) fs.mkdirSync(TOOLS_DIR, { recursive: true });
|
|
87
|
-
|
|
88
76
|
printHeader('INSTALL / UPDATE YT-DLP');
|
|
89
|
-
console.log(`${C.blue}⏳
|
|
77
|
+
console.log(`${C.blue}⏳ Downloading latest engine...${C.reset}`);
|
|
90
78
|
const url = isWindows
|
|
91
79
|
? 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe'
|
|
92
80
|
: 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp';
|
|
93
81
|
|
|
94
82
|
try {
|
|
95
83
|
if (isTermux) {
|
|
96
|
-
// Di Termux, lebih disarankan menggunakan python/pip untuk stabilitas
|
|
97
|
-
console.log(`${C.dim}Menginstall yt-dlp via python...${C.reset}`);
|
|
98
84
|
execSync('pkg update && pkg install python ffmpeg -y', {
|
|
99
85
|
stdio: 'inherit',
|
|
100
86
|
});
|
|
101
87
|
execSync('pip install -U "yt-dlp[default]"', { stdio: 'inherit' });
|
|
102
|
-
console.log(
|
|
103
|
-
`\n${C.green}✅ yt-dlp berhasil diinstal di Termux!${C.reset}`,
|
|
104
|
-
);
|
|
105
88
|
} else if (isWindows) {
|
|
106
89
|
execSync(
|
|
107
90
|
`powershell -Command "Invoke-WebRequest -Uri ${url} -OutFile '${YTDLP_PATH}'"`,
|
|
108
91
|
{ stdio: 'inherit' },
|
|
109
92
|
);
|
|
110
93
|
} else {
|
|
111
|
-
// Linux (Ubuntu) & macOS
|
|
112
94
|
execSync(`curl -L -# "${url}" -o "${YTDLP_PATH}"`, { stdio: 'inherit' });
|
|
113
95
|
execSync(`chmod a+rx "${YTDLP_PATH}"`);
|
|
114
96
|
}
|
|
115
|
-
|
|
116
|
-
console.log(`\n${C.green}✅ yt-dlp berhasil dikonfigurasi!${C.reset}`);
|
|
117
|
-
}
|
|
97
|
+
console.log(`\n${C.green}✅ yt-dlp configured successfully!${C.reset}`);
|
|
118
98
|
} catch (e) {
|
|
119
|
-
console.error(
|
|
120
|
-
`\n${C.red}❌ Gagal mengunduh. Periksa koneksi internet Anda.${C.reset}`,
|
|
121
|
-
);
|
|
99
|
+
console.error(`\n${C.red}❌ Download failed.${C.reset}`);
|
|
122
100
|
}
|
|
123
101
|
}
|
|
124
102
|
|
|
125
103
|
async function installFfmpeg() {
|
|
126
104
|
printHeader('INSTALL FFmpeg');
|
|
127
|
-
console.log(
|
|
128
|
-
`${C.dim}FFmpeg diperlukan untuk kualitas 1080p+ dan konversi MP3.${C.reset}\n`,
|
|
129
|
-
);
|
|
130
|
-
|
|
131
105
|
try {
|
|
132
106
|
if (isTermux) {
|
|
133
|
-
console.log(
|
|
134
|
-
`${C.blue}⏳ Mendeteksi Termux: Menginstall via pkg...${C.reset}`,
|
|
135
|
-
);
|
|
136
107
|
execSync('pkg update && pkg install ffmpeg -y', { stdio: 'inherit' });
|
|
137
|
-
console.log(
|
|
138
|
-
`\n${C.green}✅ FFmpeg berhasil diinstal di Termux!${C.reset}`,
|
|
139
|
-
);
|
|
140
108
|
} else if (isMac) {
|
|
141
|
-
// ... (Kode macOS Anda sudah benar)
|
|
142
|
-
console.log(`${C.blue}⏳ Mengunduh FFmpeg untuk macOS...${C.reset}`);
|
|
143
109
|
const zipPath = path.join(TOOLS_DIR, 'ffmpeg.zip');
|
|
144
110
|
execSync(
|
|
145
111
|
`curl -L -# "https://evermeet.cx/ffmpeg/getrelease/ffmpeg/zip" -o "${zipPath}"`,
|
|
146
112
|
{ stdio: 'inherit' },
|
|
147
113
|
);
|
|
148
114
|
execSync(`unzip -o "${zipPath}" -d "${TOOLS_DIR}"`, { stdio: 'inherit' });
|
|
149
|
-
|
|
115
|
+
if (fs.existsSync(zipPath)) fs.unlinkSync(zipPath);
|
|
150
116
|
execSync(`chmod a+rx "${FFMPEG_PATH}"`);
|
|
151
|
-
console.log(`\n${C.green}✅ FFmpeg aktif di macOS.${C.reset}`);
|
|
152
117
|
} else if (isWindows) {
|
|
153
|
-
console.log(
|
|
154
|
-
`${C.blue}⏳ Mengunduh FFmpeg untuk Windows (Essentials)...${C.reset}`,
|
|
155
|
-
);
|
|
156
|
-
|
|
157
118
|
const zipPath = path.join(TOOLS_DIR, 'ffmpeg.zip');
|
|
158
|
-
// Link direct ke build essentials agar file tidak terlalu besar
|
|
159
119
|
const url =
|
|
160
120
|
'https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip';
|
|
161
|
-
|
|
162
|
-
// 1. Download menggunakan PowerShell
|
|
163
121
|
execSync(
|
|
164
122
|
`powershell -Command "Invoke-WebRequest -Uri ${url} -OutFile '${zipPath}'"`,
|
|
165
123
|
{ stdio: 'inherit' },
|
|
166
124
|
);
|
|
167
|
-
|
|
168
|
-
console.log(`${C.yellow}📦 Mengekstrak FFmpeg...${C.reset}`);
|
|
169
|
-
|
|
170
|
-
// 2. Ekstrak menggunakan perintah 'tar' (Bawaan Windows 10+)
|
|
171
|
-
// Kita hanya mengambil ffmpeg.exe dari dalam folder bin di zip tersebut
|
|
172
125
|
execSync(
|
|
173
126
|
`tar -xf "${zipPath}" -C "${TOOLS_DIR}" --strip-components 2 "*/bin/ffmpeg.exe" "*/bin/ffprobe.exe"`,
|
|
174
127
|
{ stdio: 'inherit' },
|
|
175
128
|
);
|
|
176
|
-
|
|
177
|
-
// 3. Bersihkan file zip
|
|
178
129
|
if (fs.existsSync(zipPath)) fs.unlinkSync(zipPath);
|
|
179
|
-
|
|
180
|
-
console.log(
|
|
181
|
-
`\n${C.green}✅ FFmpeg berhasil diinstal di Windows!${C.reset}`,
|
|
182
|
-
);
|
|
183
130
|
} else {
|
|
184
|
-
// Asumsi Linux (Ubuntu/Debian)
|
|
185
|
-
console.log(
|
|
186
|
-
`${C.blue}⏳ Mendeteksi Linux: Menginstall via apt...${C.reset}`,
|
|
187
|
-
);
|
|
188
|
-
console.log(`${C.dim}Mungkin memerlukan password sudo.${C.reset}`);
|
|
189
131
|
execSync('sudo apt update && sudo apt install ffmpeg -y', {
|
|
190
132
|
stdio: 'inherit',
|
|
191
133
|
});
|
|
192
|
-
console.log(
|
|
193
|
-
`\n${C.green}✅ FFmpeg berhasil diinstal di Linux!${C.reset}`,
|
|
194
|
-
);
|
|
195
134
|
}
|
|
135
|
+
console.log(`\n${C.green}✅ FFmpeg installed successfully!${C.reset}`);
|
|
196
136
|
} catch (e) {
|
|
197
|
-
console.error(
|
|
198
|
-
`${C.red}❌ Gagal menginstal FFmpeg secara otomatis.${C.reset}`,
|
|
199
|
-
);
|
|
200
|
-
console.log(`${C.dim}Error: ${e.message}${C.reset}`);
|
|
137
|
+
console.error(`${C.red}❌ FFmpeg failed.${C.reset}`);
|
|
201
138
|
}
|
|
202
139
|
}
|
|
203
140
|
|
|
204
141
|
function runSpawn(command, args) {
|
|
205
142
|
return new Promise((resolve) => {
|
|
206
143
|
const proc = spawn(command, args);
|
|
207
|
-
let lastOutput = '';
|
|
208
|
-
|
|
209
144
|
proc.stdout.on('data', (data) => {
|
|
210
145
|
const output = data.toString();
|
|
211
|
-
// Regex untuk menangkap progress dari yt-dlp
|
|
212
146
|
const progressMatch = output.match(
|
|
213
147
|
/\[download\]\s+(\d+\.\d+)%\s+of\s+.*\s+at\s+([\d\w\./s]+)\s+ETA\s+([\d:]+)/,
|
|
214
148
|
);
|
|
215
|
-
|
|
216
149
|
if (progressMatch) {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
150
|
+
renderProgressBar(
|
|
151
|
+
parseFloat(progressMatch[1]),
|
|
152
|
+
progressMatch[2],
|
|
153
|
+
progressMatch[3],
|
|
154
|
+
);
|
|
155
|
+
} else if (output.trim() && !output.includes('[download]')) {
|
|
156
|
+
process.stdout.write(`\n${C.dim}${output.trim()}${C.reset}\n`);
|
|
224
157
|
}
|
|
225
158
|
});
|
|
226
|
-
|
|
227
159
|
proc.stderr.on('data', (data) => {
|
|
228
160
|
const err = data.toString();
|
|
229
|
-
if (!err.includes('WARNING'))
|
|
161
|
+
if (!err.includes('WARNING'))
|
|
230
162
|
process.stdout.write(`\n${C.red}⚠️ ${err}${C.reset}`);
|
|
231
|
-
}
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
proc.on('close', (code) => {
|
|
235
|
-
process.stdout.write('\n'); // Baris baru setelah selesai
|
|
236
|
-
resolve(code);
|
|
237
163
|
});
|
|
164
|
+
proc.on('close', resolve);
|
|
238
165
|
});
|
|
239
166
|
}
|
|
240
167
|
|
|
@@ -248,41 +175,25 @@ async function getEstimate(url, format) {
|
|
|
248
175
|
timeout: 10000,
|
|
249
176
|
},
|
|
250
177
|
).trim();
|
|
251
|
-
|
|
252
178
|
if (sizeStr && sizeStr !== 'NA') {
|
|
253
179
|
const bytes = parseInt(sizeStr);
|
|
254
180
|
if (isNaN(bytes)) return 'N/A';
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
181
|
+
return bytes > 1024 ** 3
|
|
182
|
+
? (bytes / 1024 ** 3).toFixed(2) + ' GB'
|
|
183
|
+
: (bytes / 1024 ** 2).toFixed(2) + ' MB';
|
|
258
184
|
}
|
|
259
185
|
} catch (e) {}
|
|
260
|
-
return '
|
|
186
|
+
return 'Estimate unavailable';
|
|
261
187
|
}
|
|
262
188
|
|
|
263
|
-
// --- DOWNLOAD ENGINE ---
|
|
264
189
|
async function startDownload(videoURLFromArgs = null) {
|
|
265
|
-
|
|
266
|
-
if (!ytExists) {
|
|
267
|
-
console.log(
|
|
268
|
-
`\n${C.red}❌ Engine yt-dlp tidak ditemukan. Silakan pilih menu Update/Install.${C.reset}`,
|
|
269
|
-
);
|
|
270
|
-
await backToMenu();
|
|
271
|
-
}
|
|
190
|
+
const { ytExists, ffExists } = checkTools();
|
|
191
|
+
if (!ytExists) return console.log(`\n${C.red}❌ Engine not found.${C.reset}`);
|
|
272
192
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
`${C.yellow}⚠️ Peringatan: FFmpeg tidak ditemukan. Video mungkin tidak tergabung dengan audio.${C.reset}`,
|
|
276
|
-
);
|
|
277
|
-
const cont = await askQuestion('Lanjutkan saja? (y/n): ');
|
|
278
|
-
if (cont.toLowerCase() !== 'y') return mainMenu();
|
|
279
|
-
}
|
|
193
|
+
const videoURL = videoURLFromArgs || (await askQuestion('Enter Link: '));
|
|
194
|
+
if (!videoURL) return;
|
|
280
195
|
|
|
281
|
-
|
|
282
|
-
videoURLFromArgs || (await askQuestion('Masukkan Link (Video/Playlist): '));
|
|
283
|
-
if (!videoURL) return mainMenu();
|
|
284
|
-
|
|
285
|
-
console.log(`${C.dim}⏳ Menganalisa tautan...${C.reset}`);
|
|
196
|
+
console.log(`${C.dim}⏳ Analyzing link...${C.reset}`);
|
|
286
197
|
let playlistInfo = { isPlaylist: false, title: '', items: [] };
|
|
287
198
|
|
|
288
199
|
try {
|
|
@@ -297,43 +208,27 @@ async function startDownload(videoURLFromArgs = null) {
|
|
|
297
208
|
const lines = rawInfo.trim().split('\n');
|
|
298
209
|
if (lines.length > 1 || videoURL.includes('playlist?list=')) {
|
|
299
210
|
playlistInfo.isPlaylist = true;
|
|
300
|
-
playlistInfo.title = lines[0].split('|')[0] || '
|
|
211
|
+
playlistInfo.title = lines[0].split('|')[0] || 'Playlist';
|
|
301
212
|
playlistInfo.items = lines.map((l) => l.split('|')[1]).filter(Boolean);
|
|
302
213
|
}
|
|
303
|
-
} catch (e) {
|
|
304
|
-
console.log(`\n${C.red}❌ Gagal menganalisa tautan.${C.reset}`);
|
|
305
|
-
if (e.message.includes('ETIMEDOUT')) {
|
|
306
|
-
console.log(
|
|
307
|
-
`${C.yellow}⚠️ Waktu analisa habis. Periksa koneksi internet Anda.${C.reset}`,
|
|
308
|
-
);
|
|
309
|
-
} else {
|
|
310
|
-
console.log(
|
|
311
|
-
`${C.yellow}⚠️ Pastikan link valid atau tidak diprivat/dihapus.${C.reset}`,
|
|
312
|
-
);
|
|
313
|
-
}
|
|
314
|
-
await backToMenu();
|
|
315
|
-
}
|
|
214
|
+
} catch (e) {}
|
|
316
215
|
|
|
317
|
-
// --- PEMILIHAN PLAYLIST (Tetap Sama) ---
|
|
318
216
|
let playlistSelection = null;
|
|
319
217
|
if (playlistInfo.isPlaylist) {
|
|
320
|
-
// ... (Logika tampilan playlist sama seperti sebelumnya)
|
|
321
218
|
console.log(
|
|
322
|
-
`\n${C.bgBlue}${C.bright} 📂 PLAYLIST
|
|
219
|
+
`\n${C.bgBlue}${C.bright} 📂 PLAYLIST: ${playlistInfo.title} ${C.reset}`,
|
|
323
220
|
);
|
|
324
|
-
playlistInfo.items.forEach((item,
|
|
221
|
+
playlistInfo.items.forEach((item, i) =>
|
|
325
222
|
console.log(
|
|
326
|
-
`${C.cyan}${(
|
|
327
|
-
)
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
223
|
+
`${C.cyan}${(i + 1).toString().padStart(3, ' ')}.${C.reset} ${item}`,
|
|
224
|
+
),
|
|
225
|
+
);
|
|
226
|
+
const sel = await askQuestion(
|
|
227
|
+
'\nNumbers (ex: 1,3,5-10 or empty for all): ',
|
|
331
228
|
);
|
|
332
|
-
|
|
333
|
-
if (selectionInput) {
|
|
334
|
-
// ... (Logika parsing nomor playlist)
|
|
229
|
+
if (sel) {
|
|
335
230
|
const selected = new Set();
|
|
336
|
-
|
|
231
|
+
sel.split(',').forEach((p) => {
|
|
337
232
|
if (p.includes('-')) {
|
|
338
233
|
const [s, e] = p.split('-').map(Number);
|
|
339
234
|
for (let i = s; i <= e; i++)
|
|
@@ -347,433 +242,165 @@ async function startDownload(videoURLFromArgs = null) {
|
|
|
347
242
|
}
|
|
348
243
|
}
|
|
349
244
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
const mode = await askQuestion('Pilihan: ');
|
|
245
|
+
console.log(
|
|
246
|
+
`\n${C.bright} [ FORMAT ]${C.reset}\n ${C.green}1.${C.reset} Video (MP4)\n ${C.green}2.${C.reset} Audio (MP3)`,
|
|
247
|
+
);
|
|
248
|
+
const mode = await askQuestion('Choice: ');
|
|
355
249
|
|
|
356
|
-
// --- LOGIKA RESOLUSI OPTIMAL ---
|
|
357
250
|
let formatArg = '';
|
|
358
251
|
if (mode === '1') {
|
|
359
|
-
console.log(
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
if (resChoice === '2') {
|
|
366
|
-
formatArg =
|
|
367
|
-
'bestvideo[height<=720][vcodec^=avc1]+bestaudio[acodec^=mp4a]/best[height<=720]';
|
|
368
|
-
} else if (resChoice === '3') {
|
|
252
|
+
console.log(
|
|
253
|
+
`\n${C.bright} [ RESOLUTION ]${C.reset}\n 1. Best\n 2. 720p\n 3. 480p`,
|
|
254
|
+
);
|
|
255
|
+
const res = await askQuestion('Select: ');
|
|
256
|
+
if (res === '2')
|
|
369
257
|
formatArg =
|
|
370
|
-
'bestvideo[height<=
|
|
371
|
-
|
|
258
|
+
'bestvideo[height<=720][vcodec^=avc1]+bestaudio[acodec^=mp4a]/best[height<=720]/best';
|
|
259
|
+
else if (res === '3')
|
|
372
260
|
formatArg =
|
|
373
|
-
'bestvideo[vcodec^=avc1]+bestaudio[acodec^=mp4a]/best[
|
|
261
|
+
'bestvideo[height<=480][vcodec^=avc1]+bestaudio[acodec^=mp4a]/best[height<=480]/best';
|
|
262
|
+
else
|
|
263
|
+
formatArg = 'bestvideo[vcodec^=avc1]+bestaudio[acodec^=mp4a]/best/best';
|
|
264
|
+
|
|
265
|
+
if (!playlistInfo.isPlaylist) {
|
|
266
|
+
const size = await getEstimate(videoURL, formatArg);
|
|
267
|
+
console.log(`${C.yellow}📊 Estimated Size: ${size}${C.reset}`);
|
|
268
|
+
if ((await askQuestion('Proceed? (y/n): ')).toLowerCase() !== 'y') return;
|
|
374
269
|
}
|
|
375
270
|
}
|
|
376
|
-
// --- TAMPILKAN ESTIMASI ---
|
|
377
|
-
if (!playlistInfo.isPlaylist && mode === '1') {
|
|
378
|
-
console.log(`${C.dim}⏳ Menghitung estimasi ukuran file...${C.reset}`);
|
|
379
|
-
const size = await getEstimate(videoURL, formatArg);
|
|
380
|
-
console.log(`${C.yellow}📊 Estimasi Ukuran: ${C.bright}${size}${C.reset}`);
|
|
381
|
-
|
|
382
|
-
const confirm = await askQuestion('Lanjutkan unduhan? (Y/n): ');
|
|
383
|
-
if (confirm.toLowerCase() === 'n') return mainMenu();
|
|
384
|
-
}
|
|
385
271
|
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
let outputDir = path.join(baseDir, subFolder);
|
|
272
|
+
const sub = mode === '2' ? 'audio' : 'video';
|
|
273
|
+
let output = path.join(os.homedir(), 'Downloads', 'media-dl', sub);
|
|
389
274
|
if (playlistInfo.isPlaylist)
|
|
390
|
-
|
|
391
|
-
|
|
275
|
+
output = path.join(
|
|
276
|
+
output,
|
|
392
277
|
playlistInfo.title.replace(/[\\/:"*?<>|]/g, '_'),
|
|
393
278
|
);
|
|
394
|
-
if (!fs.existsSync(
|
|
279
|
+
if (!fs.existsSync(output)) fs.mkdirSync(output, { recursive: true });
|
|
395
280
|
|
|
396
281
|
let args = [
|
|
397
282
|
'--ffmpeg-location',
|
|
398
283
|
FFMPEG_PATH,
|
|
399
284
|
'-o',
|
|
400
|
-
`${
|
|
285
|
+
`${output}/%(title).100s.%(ext)s`,
|
|
286
|
+
'--no-mtime',
|
|
401
287
|
videoURL,
|
|
402
288
|
];
|
|
403
|
-
|
|
404
|
-
// Integrasi Safe Mode (cite: cli.js)
|
|
405
|
-
if (safeMode) {
|
|
289
|
+
if (safeMode)
|
|
406
290
|
args.push(
|
|
407
291
|
'--rate-limit',
|
|
408
292
|
'5M',
|
|
409
293
|
'--sleep-interval',
|
|
410
294
|
'3',
|
|
411
|
-
'--max-sleep-interval',
|
|
412
|
-
'10',
|
|
413
295
|
'--user-agent',
|
|
414
|
-
'Mozilla/5.0
|
|
296
|
+
'Mozilla/5.0...',
|
|
415
297
|
);
|
|
416
|
-
}
|
|
417
|
-
|
|
418
298
|
if (playlistSelection) args.push('--playlist-items', playlistSelection);
|
|
419
|
-
else if (!playlistInfo.isPlaylist) args.push('--no-playlist');
|
|
420
299
|
|
|
421
|
-
if (mode === '2')
|
|
422
|
-
|
|
423
|
-
console.log(
|
|
424
|
-
`${C.red}❌ Error: Anda wajib menginstal FFmpeg untuk mengunduh audio.${C.reset}`,
|
|
425
|
-
);
|
|
426
|
-
return mainMenu();
|
|
427
|
-
}
|
|
428
|
-
args.unshift('-x', '--audio-format', 'mp3');
|
|
429
|
-
} else {
|
|
300
|
+
if (mode === '2') args.unshift('-x', '--audio-format', 'mp3');
|
|
301
|
+
else {
|
|
430
302
|
args.unshift('-f', formatArg);
|
|
431
303
|
if (ffExists) args.unshift('--recode-video', 'mp4');
|
|
432
304
|
}
|
|
433
305
|
|
|
434
|
-
|
|
435
|
-
console.log(`\n${C.bgBlue}${C.bright} 🚀 MEMULAI PROSES... ${C.reset}\n`);
|
|
436
|
-
|
|
306
|
+
console.log(`\n${C.bgBlue}${C.bright} 🚀 STARTING... ${C.reset}\n`);
|
|
437
307
|
const code = await runSpawn(YTDLP_PATH, args);
|
|
438
308
|
if (code === 0) {
|
|
439
|
-
console.log(`\n${C.green}✨
|
|
309
|
+
console.log(`\n${C.green}✨ DONE! Folder: ${output}${C.reset}`);
|
|
440
310
|
try {
|
|
441
311
|
execSync(
|
|
442
312
|
isWindows
|
|
443
|
-
? `explorer "${
|
|
313
|
+
? `explorer "${output}"`
|
|
444
314
|
: isMac
|
|
445
|
-
? `open "${
|
|
446
|
-
: `xdg-open "${
|
|
315
|
+
? `open "${output}"`
|
|
316
|
+
: `xdg-open "${output}"`,
|
|
447
317
|
);
|
|
448
318
|
} catch (e) {}
|
|
449
|
-
} else {
|
|
450
|
-
console.log(`\n${C.red}❌ Terjadi kesalahan saat mengunduh.${C.reset}`);
|
|
451
|
-
}
|
|
452
|
-
await backToMenu();
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
async function backToMenu() {
|
|
456
|
-
try {
|
|
457
|
-
await askQuestion('Tekan Enter untuk kembali ke Menu Utama...');
|
|
458
|
-
mainMenu(); // Kembali ke menu
|
|
459
|
-
} catch (err) {
|
|
460
|
-
// Tangani jika readline sudah tertutup (Ctrl+C ditekan sebelumnya)
|
|
461
|
-
if (err.code === 'ERR_USE_AFTER_CLOSE') {
|
|
462
|
-
console.log(`\n${C.dim}Program dihentikan oleh pengguna.${C.reset}`);
|
|
463
|
-
process.exit(0);
|
|
464
|
-
} else {
|
|
465
|
-
// Jika error lain, lempar kembali agar bisa dideteksi (opsional)
|
|
466
|
-
throw err;
|
|
467
|
-
}
|
|
468
319
|
}
|
|
320
|
+
await askQuestion('\nPress Enter to continue...');
|
|
469
321
|
}
|
|
470
322
|
|
|
471
|
-
async function
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
// --- SEKSI FITUR ---
|
|
476
|
-
console.log(` ${C.bright}${C.cyan}OVERVIEW${C.reset}`);
|
|
477
|
-
console.log(` Terima kasih telah memilih MEDIA-DL. Skrip ini dirancang`);
|
|
478
|
-
console.log(` untuk memudahkan manajemen unduhan media secara lokal.\n`);
|
|
479
|
-
|
|
480
|
-
console.log(` ${C.bright}${C.cyan}FITUR UNGGULAN${C.reset}`);
|
|
481
|
-
const features = [
|
|
482
|
-
{
|
|
483
|
-
icon: '✦',
|
|
484
|
-
title: 'High Quality',
|
|
485
|
-
desc: 'Mendukung hingga 4K & Audio 320kbps',
|
|
486
|
-
},
|
|
487
|
-
{
|
|
488
|
-
icon: '✦',
|
|
489
|
-
title: 'Multi-Source',
|
|
490
|
-
desc: 'YouTube, TikTok, IG Reels, & Shorts',
|
|
491
|
-
},
|
|
492
|
-
{
|
|
493
|
-
icon: '✦',
|
|
494
|
-
title: 'Batch Mode',
|
|
495
|
-
desc: 'Mendukung unduhan Playlist secara massal',
|
|
496
|
-
},
|
|
497
|
-
{
|
|
498
|
-
icon: '✦',
|
|
499
|
-
title: 'Safe Guard',
|
|
500
|
-
desc: 'Mode proteksi agar akun/IP tidak terblokir',
|
|
501
|
-
},
|
|
502
|
-
];
|
|
503
|
-
|
|
504
|
-
features.forEach((f) => {
|
|
323
|
+
async function systemMaintenance() {
|
|
324
|
+
while (true) {
|
|
325
|
+
const { ytExists, ffExists } = checkTools();
|
|
326
|
+
printHeader('MAINTENANCE', 'Update or Reset');
|
|
505
327
|
console.log(
|
|
506
|
-
`
|
|
507
|
-
C.reset
|
|
508
|
-
} ${C.dim}• ${f.desc}${C.reset}`,
|
|
328
|
+
` • Engine: ${ytExists ? C.green + 'Ready' : C.red + 'Missing'}${C.reset}`,
|
|
509
329
|
);
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
console.log('\n' + '─'.repeat(52));
|
|
513
|
-
|
|
514
|
-
// --- SEKSI DUKUNGAN ---
|
|
515
|
-
console.log(`\n ${C.bright}${C.magenta}DUKUNGAN & DONASI${C.reset}`);
|
|
516
|
-
console.log(` Dukungan Anda sangat membantu pengembang untuk terus`);
|
|
517
|
-
console.log(` memperbarui engine dan fitur aplikasi ini.\n`);
|
|
518
|
-
|
|
519
|
-
// Menampilkan Link dengan label background agar menonjol
|
|
520
|
-
const links = [
|
|
521
|
-
{ label: ' ☕ BELI KOPI ', url: 'https://app.midtrans.com/coffee' },
|
|
522
|
-
{ label: ' 🍕 BELI PIZZA', url: 'https://app.midtrans.com/pizza' },
|
|
523
|
-
];
|
|
524
|
-
|
|
525
|
-
links.forEach((l) => {
|
|
526
330
|
console.log(
|
|
527
|
-
`
|
|
331
|
+
` • FFmpeg: ${ffExists ? C.green + 'Ready' : C.red + 'Missing'}${C.reset}\n`,
|
|
528
332
|
);
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
333
|
+
console.log(` 1. Update Engines\n 2. Reset System\n 0. Back`);
|
|
334
|
+
const c = await askQuestion('\nSelect: ');
|
|
335
|
+
if (c === '1') {
|
|
336
|
+
await installYtdlp();
|
|
337
|
+
await installFfmpeg();
|
|
338
|
+
await askQuestion('\nDone. Enter...');
|
|
339
|
+
} else if (c === '2') {
|
|
340
|
+
if ((await askQuestion('Delete all? (y/n): ')).toLowerCase() === 'y') {
|
|
341
|
+
fs.rmSync(TOOLS_DIR, { recursive: true, force: true });
|
|
342
|
+
process.exit(0);
|
|
343
|
+
}
|
|
344
|
+
} else break;
|
|
345
|
+
}
|
|
534
346
|
}
|
|
535
347
|
|
|
536
|
-
async function
|
|
537
|
-
|
|
538
|
-
const { ytExists, ffExists } = status;
|
|
539
|
-
|
|
540
|
-
// Menggunakan 2 parameter: Judul dan Summary status singkat
|
|
541
|
-
printHeader('MEDIA-DL PRO 2026', 'Pusat Kendali Unduhan Media Lokal');
|
|
542
|
-
|
|
543
|
-
// --- SEKSI DASHBOARD (INFO SISTEM) ---
|
|
544
|
-
const ytLabel = status.isLocalYt
|
|
545
|
-
? `${C.green}Ready (Internal)${C.reset}`
|
|
546
|
-
: status.ytExists
|
|
547
|
-
? `${C.cyan}Ready (System)${C.reset}`
|
|
548
|
-
: `${C.red}Not Found${C.reset}`;
|
|
549
|
-
|
|
550
|
-
const ffLabel = status.isLocalFf
|
|
551
|
-
? `${C.green}Ready (Internal)${C.reset}`
|
|
552
|
-
: status.ffExists
|
|
553
|
-
? `${C.cyan}Ready (System)${C.reset}`
|
|
554
|
-
: `${C.yellow}Missing${C.reset}`;
|
|
555
|
-
|
|
556
|
-
const safeBadge = safeMode
|
|
557
|
-
? `${C.bgBlue}${C.white} ON ${C.reset}`
|
|
558
|
-
: `${C.bgRed}${C.white} OFF ${C.reset}`;
|
|
559
|
-
|
|
560
|
-
console.log(` ${C.bright}SYSTEM STATUS${C.reset}`);
|
|
561
|
-
console.log(` 🤖 Engine : [ ${ytLabel} ] | 🎬 FFmpeg : [ ${ffLabel} ]`);
|
|
562
|
-
console.log(` 🛡️ Safe Mode Guard : ${safeBadge}\n`);
|
|
563
|
-
|
|
564
|
-
console.log(` ${C.cyan}━${'━'.repeat(48)}${C.reset}`);
|
|
565
|
-
|
|
566
|
-
// --- SEKSI NAVIGASI ---
|
|
567
|
-
console.log(` ${C.bright}MAIN SERVICES${C.reset}`);
|
|
568
|
-
console.log(
|
|
569
|
-
` ${C.cyan}1.${C.reset} 📥 Download Media ${C.dim}(Video, Music, Playlist)${C.reset}`,
|
|
570
|
-
);
|
|
571
|
-
console.log(
|
|
572
|
-
` ${C.cyan}2.${C.reset} 🛡️ Toggle Safe Mode ${C.dim}(Sekarang: ${
|
|
573
|
-
safeMode ? 'Aktif' : 'Nonaktif'
|
|
574
|
-
})${C.reset}`,
|
|
575
|
-
);
|
|
576
|
-
|
|
577
|
-
console.log(`\n ${C.bright}SYSTEM & INFO${C.reset}`);
|
|
348
|
+
async function showSupport() {
|
|
349
|
+
printHeader('ABOUT', 'Media-DL Pro v2.5.2');
|
|
578
350
|
console.log(
|
|
579
|
-
`
|
|
351
|
+
` ${C.cyan}KEY FEATURES:${C.reset}\n ✦ High Quality 4K & MP3\n ✦ Multi-Source (YT, IG, FB, TikTok)\n ✦ Playlist Batch Mode\n ✦ Safe Guard Mode\n`,
|
|
580
352
|
);
|
|
581
353
|
console.log(
|
|
582
|
-
`
|
|
354
|
+
` ${C.magenta}SUPPORT DEVELOPER:${C.reset}\n ☕ PayPal: https://paypal.me/Ariska138\n`,
|
|
583
355
|
);
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
console.log(` ${C.cyan}━${'━'.repeat(48)}${C.reset}`);
|
|
587
|
-
|
|
588
|
-
const choice = await askQuestion('\nPilih menu (0-4): ');
|
|
589
|
-
|
|
590
|
-
switch (choice) {
|
|
591
|
-
case '1':
|
|
592
|
-
await startDownload();
|
|
593
|
-
break;
|
|
594
|
-
case '2':
|
|
595
|
-
safeMode = !safeMode;
|
|
596
|
-
// Berikan feedback visual singkat sebelum refresh menu
|
|
597
|
-
console.log(
|
|
598
|
-
`\n${C.yellow} 🛡️ Safe Mode telah ${
|
|
599
|
-
safeMode ? 'DIAKTIFKAN' : 'DINONAKTIFKAN'
|
|
600
|
-
}${C.reset}`,
|
|
601
|
-
);
|
|
602
|
-
setTimeout(() => mainMenu(), 800);
|
|
603
|
-
break;
|
|
604
|
-
case '3':
|
|
605
|
-
await systemMaintenance();
|
|
606
|
-
break;
|
|
607
|
-
case '4':
|
|
608
|
-
await showSupport();
|
|
609
|
-
break;
|
|
610
|
-
case '0':
|
|
611
|
-
console.log(`\n${C.cyan}━${'━'.repeat(48)}${C.reset}`);
|
|
612
|
-
console.log(
|
|
613
|
-
` ${C.bright}${C.white}Terima kasih telah menggunakan MEDIA-DL!${C.reset}`,
|
|
614
|
-
);
|
|
615
|
-
console.log(
|
|
616
|
-
` ${C.green}✨ Semoga Anda sukses, jaya, dan sehat selalu! ✨${C.reset}`,
|
|
617
|
-
);
|
|
618
|
-
console.log(`${C.cyan}━${'━'.repeat(48)}${C.reset}\n`);
|
|
619
|
-
|
|
620
|
-
// Memberikan jeda sebentar sebelum benar-benar menutup terminal
|
|
621
|
-
setTimeout(() => {
|
|
622
|
-
rl.close();
|
|
623
|
-
process.exit(0);
|
|
624
|
-
}, 1000);
|
|
625
|
-
|
|
626
|
-
break;
|
|
627
|
-
default:
|
|
628
|
-
// Jika salah input, tampilkan kembali menu
|
|
629
|
-
mainMenu();
|
|
630
|
-
break;
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
async function cleanUp() {
|
|
635
|
-
if (fs.existsSync(TOOLS_DIR)) {
|
|
636
|
-
fs.rmSync(TOOLS_DIR, { recursive: true, force: true });
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
// RESET PATH ke default lokal agar checkTools tidak "tersesat" menggunakan path lama
|
|
640
|
-
YTDLP_PATH = path.join(TOOLS_DIR, isWindows ? 'yt-dlp.exe' : 'yt-dlp');
|
|
641
|
-
FFMPEG_PATH = path.join(TOOLS_DIR, isWindows ? 'ffmpeg.exe' : 'ffmpeg');
|
|
356
|
+
await askQuestion('Press Enter...');
|
|
642
357
|
}
|
|
643
358
|
|
|
644
|
-
async function
|
|
359
|
+
async function mainMenu() {
|
|
645
360
|
while (true) {
|
|
646
|
-
const
|
|
647
|
-
printHeader(
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
C.reset
|
|
656
|
-
}] Engine yt-dlp (Wajib)`,
|
|
657
|
-
);
|
|
361
|
+
const status = checkTools();
|
|
362
|
+
printHeader('MEDIA-DL PRO 2026', 'Stable Control Center');
|
|
363
|
+
const ytL = status.ytExists
|
|
364
|
+
? C.green + 'Ready' + C.reset
|
|
365
|
+
: C.red + 'Missing' + C.reset;
|
|
366
|
+
const ffL = status.ffExists
|
|
367
|
+
? C.green + 'Ready' + C.reset
|
|
368
|
+
: C.yellow + 'Missing' + C.reset;
|
|
369
|
+
console.log(` 🤖 Engine: [ ${ytL} ] | 🎬 FFmpeg: [ ${ffL} ]`);
|
|
658
370
|
console.log(
|
|
659
|
-
`
|
|
660
|
-
C.reset
|
|
661
|
-
}] FFmpeg (Direkomendasikan)`,
|
|
371
|
+
` 🛡️ Safe Mode: ${safeMode ? C.bgBlue + ' ON ' + C.reset : C.bgRed + ' OFF ' + C.reset}\n`,
|
|
662
372
|
);
|
|
663
|
-
|
|
664
373
|
console.log(
|
|
665
|
-
|
|
374
|
+
` 1. 📥 Download Media\n 2. 🛡️ Toggle Safe Mode\n 3. ⚙️ Maintenance\n 4. ❤️ About\n 0. 🚪 Exit`,
|
|
666
375
|
);
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
const status = checkTools();
|
|
677
|
-
if (status.ytExists) {
|
|
678
|
-
console.log(
|
|
679
|
-
`\n${C.green}✨ Setup Selesai! Membuka Menu Utama...${C.reset}`,
|
|
680
|
-
);
|
|
681
|
-
await new Promise((r) => setTimeout(r, 1500));
|
|
682
|
-
return mainMenu(); // Berhasil, lanjut ke menu utama
|
|
683
|
-
}
|
|
684
|
-
} else if (choice === '0') {
|
|
685
|
-
console.log('Menutup aplikasi...');
|
|
686
|
-
process.exit(0);
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
async function systemMaintenance() {
|
|
692
|
-
let inMaintenance = true;
|
|
693
|
-
|
|
694
|
-
while (inMaintenance) {
|
|
695
|
-
const { ytExists, ffExists } = checkTools();
|
|
696
|
-
printHeader(
|
|
697
|
-
'SYSTEM MAINTENANCE',
|
|
698
|
-
'Update engine atau bersihkan file sistem',
|
|
699
|
-
);
|
|
700
|
-
|
|
701
|
-
console.log(`${C.white}Versi Terinstal:${C.reset}`);
|
|
702
|
-
console.log(
|
|
703
|
-
` • yt-dlp : ${ytExists ? C.green + 'Ready' : C.red + 'Not Found'}${
|
|
704
|
-
C.reset
|
|
705
|
-
}`,
|
|
706
|
-
);
|
|
707
|
-
console.log(
|
|
708
|
-
` • FFmpeg : ${ffExists ? C.green + 'Ready' : C.red + 'Not Found'}${
|
|
709
|
-
C.reset
|
|
710
|
-
}`,
|
|
711
|
-
);
|
|
712
|
-
|
|
713
|
-
console.log(`\n${C.bright}Opsi Pemeliharaan:${C.reset}`);
|
|
714
|
-
console.log(` ${C.cyan}1.${C.reset} Update / Reinstall Engines`);
|
|
715
|
-
console.log(` ${C.cyan}2.${C.reset} 🗑️ Hapus Semua Tools (Reset System)`);
|
|
716
|
-
console.log(` ${C.cyan}3.${C.reset} ⬅️ Kembali ke Menu Utama`);
|
|
717
|
-
|
|
718
|
-
const choice = await askQuestion('\nPilih tindakan: ');
|
|
719
|
-
|
|
720
|
-
switch (choice) {
|
|
721
|
-
case '1':
|
|
722
|
-
await installYtdlp();
|
|
723
|
-
await installFfmpeg();
|
|
724
|
-
await askQuestion('\nUpdate selesai. Tekan Enter...');
|
|
725
|
-
break;
|
|
726
|
-
|
|
727
|
-
case '2':
|
|
728
|
-
const confirm = await askQuestion(
|
|
729
|
-
`${C.bgRed}${C.white} KONFIRMASI ${C.reset} Hapus semua tools? (y/n): `,
|
|
730
|
-
);
|
|
731
|
-
if (confirm.toLowerCase() === 'y') {
|
|
732
|
-
await cleanUp(); // Panggil fungsi penghapusan folder
|
|
733
|
-
console.log(`${C.yellow}Folder .media-dl telah dihapus.${C.reset}`);
|
|
734
|
-
|
|
735
|
-
// Cek ulang status setelah hapus
|
|
736
|
-
const finalCheck = checkTools();
|
|
737
|
-
if (finalCheck.isLocalYt || finalCheck.isLocalFf) {
|
|
738
|
-
console.log(
|
|
739
|
-
`${C.red}Gagal menghapus beberapa file. Pastikan tidak ada proses yang mengunci file.${C.reset}`,
|
|
740
|
-
);
|
|
741
|
-
} else {
|
|
742
|
-
console.log(`${C.green}Reset lokal berhasil.${C.reset}`);
|
|
743
|
-
}
|
|
744
|
-
await askQuestion('Tekan Enter...');
|
|
745
|
-
return bootstrap(); // Kembali ke pengecekan awal
|
|
746
|
-
}
|
|
747
|
-
break;
|
|
748
|
-
|
|
749
|
-
case '3':
|
|
750
|
-
inMaintenance = false;
|
|
751
|
-
return mainMenu();
|
|
752
|
-
|
|
753
|
-
default:
|
|
754
|
-
break;
|
|
755
|
-
}
|
|
376
|
+
const choice = await askQuestion('\nChoice: ');
|
|
377
|
+
if (choice === '1') await startDownload();
|
|
378
|
+
else if (choice === '2') {
|
|
379
|
+
safeMode = !safeMode;
|
|
380
|
+
console.log('\nSafe Mode Toggled.');
|
|
381
|
+
await new Promise((r) => setTimeout(r, 600));
|
|
382
|
+
} else if (choice === '3') await systemMaintenance();
|
|
383
|
+
else if (choice === '4') await showSupport();
|
|
384
|
+
else if (choice === '0') process.exit(0);
|
|
756
385
|
}
|
|
757
386
|
}
|
|
758
387
|
|
|
759
|
-
// --- ENTRY POINT ---
|
|
760
388
|
async function bootstrap() {
|
|
761
389
|
const status = checkTools();
|
|
762
|
-
if (!status.
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
await
|
|
771
|
-
} else
|
|
772
|
-
// Jika tidak ada, masuk ke menu utama seperti biasa [cite: 113]
|
|
773
|
-
mainMenu();
|
|
774
|
-
}
|
|
390
|
+
if (!status.ytExists) {
|
|
391
|
+
printHeader('FIRST-TIME SETUP');
|
|
392
|
+
if (
|
|
393
|
+
(
|
|
394
|
+
await askQuestion('Install required components? (y/n): ')
|
|
395
|
+
).toLowerCase() === 'y'
|
|
396
|
+
) {
|
|
397
|
+
await installYtdlp();
|
|
398
|
+
await installFfmpeg();
|
|
399
|
+
} else process.exit(0);
|
|
775
400
|
}
|
|
401
|
+
const urlArg = process.argv[2];
|
|
402
|
+
if (urlArg) await startDownload(urlArg);
|
|
403
|
+
else mainMenu();
|
|
776
404
|
}
|
|
777
405
|
|
|
778
406
|
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)
|