media-dl 1.0.5 → 1.0.7
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 +125 -64
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -11,6 +11,7 @@ const rl = readline.createInterface({
|
|
|
11
11
|
output: process.stdout,
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
+
// Penentuan folder Tools berdasarkan OS
|
|
14
15
|
const TOOLS_DIR = path.join(os.homedir(), '.media-dl');
|
|
15
16
|
const isWindows = process.platform === 'win32';
|
|
16
17
|
const isMac = process.platform === 'darwin';
|
|
@@ -36,7 +37,6 @@ async function installYtdlp() {
|
|
|
36
37
|
const url = isWindows
|
|
37
38
|
? 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe'
|
|
38
39
|
: 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp';
|
|
39
|
-
|
|
40
40
|
try {
|
|
41
41
|
if (isWindows) {
|
|
42
42
|
execSync(
|
|
@@ -54,7 +54,7 @@ async function installYtdlp() {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
async function installFfmpeg() {
|
|
57
|
-
console.log('\n⏳ Mengunduh FFmpeg (Diperlukan untuk
|
|
57
|
+
console.log('\n⏳ Mengunduh FFmpeg (Diperlukan untuk konversi)...');
|
|
58
58
|
try {
|
|
59
59
|
if (isMac) {
|
|
60
60
|
const zipPath = path.join(TOOLS_DIR, 'ffmpeg.zip');
|
|
@@ -62,25 +62,34 @@ async function installFfmpeg() {
|
|
|
62
62
|
`curl -L -# "https://evermeet.cx/ffmpeg/getrelease/ffmpeg/zip" -o "${zipPath}"`,
|
|
63
63
|
{ stdio: 'inherit' }
|
|
64
64
|
);
|
|
65
|
-
console.log('📦 Mengekstrak FFmpeg...');
|
|
66
65
|
execSync(`unzip -o "${zipPath}" -d "${TOOLS_DIR}"`, { stdio: 'inherit' });
|
|
67
66
|
execSync(`rm "${zipPath}"`);
|
|
68
67
|
execSync(`chmod a+rx "${FFMPEG_PATH}"`);
|
|
69
68
|
console.log('✅ FFmpeg berhasil disiapkan.');
|
|
70
|
-
} else {
|
|
71
|
-
console.log(
|
|
72
|
-
'📝 Untuk Windows, silakan unduh manual dan letakkan ffmpeg.exe di: ' +
|
|
73
|
-
TOOLS_DIR
|
|
74
|
-
);
|
|
75
69
|
}
|
|
76
70
|
} catch (e) {
|
|
77
|
-
console.error('❌ Gagal
|
|
71
|
+
console.error('❌ Gagal instal FFmpeg.');
|
|
78
72
|
}
|
|
79
73
|
}
|
|
80
74
|
|
|
75
|
+
function parseSelection(input, max) {
|
|
76
|
+
if (!input || input.toLowerCase() === 'all') return null;
|
|
77
|
+
const selected = new Set();
|
|
78
|
+
const parts = input.split(',');
|
|
79
|
+
parts.forEach((part) => {
|
|
80
|
+
if (part.includes('-')) {
|
|
81
|
+
const [start, end] = part.split('-').map(Number);
|
|
82
|
+
for (let i = start; i <= end; i++) if (i > 0 && i <= max) selected.add(i);
|
|
83
|
+
} else {
|
|
84
|
+
const num = parseInt(part.trim());
|
|
85
|
+
if (num > 0 && num <= max) selected.add(num);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
return Array.from(selected).join(',');
|
|
89
|
+
}
|
|
90
|
+
|
|
81
91
|
async function startDownload() {
|
|
82
92
|
let { ytExists, ffExists } = checkTools();
|
|
83
|
-
|
|
84
93
|
if (!ytExists) {
|
|
85
94
|
console.log('\n⚠️ yt-dlp belum terpasang.');
|
|
86
95
|
const ans = await askQuestion('Instal sekarang? (y/n): ');
|
|
@@ -88,69 +97,96 @@ async function startDownload() {
|
|
|
88
97
|
if (!fs.existsSync(YTDLP_PATH)) return mainMenu();
|
|
89
98
|
}
|
|
90
99
|
|
|
91
|
-
const videoURL = await askQuestion('\n🔗 Masukkan Link: ');
|
|
100
|
+
const videoURL = await askQuestion('\n🔗 Masukkan Link (Video/Playlist): ');
|
|
101
|
+
|
|
102
|
+
console.log('⏳ Mengecek link...');
|
|
103
|
+
let playlistInfo = { isPlaylist: false, title: '', items: [] };
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const rawInfo = execSync(
|
|
107
|
+
`"${YTDLP_PATH}" --flat-playlist --print "%(playlist_title)s|%(index)s. %(title)s" "${videoURL}"`,
|
|
108
|
+
{ encoding: 'utf-8' }
|
|
109
|
+
);
|
|
110
|
+
const lines = rawInfo.trim().split('\n');
|
|
111
|
+
if (lines.length > 1 || videoURL.includes('playlist?list=')) {
|
|
112
|
+
playlistInfo.isPlaylist = true;
|
|
113
|
+
playlistInfo.title = lines[0].split('|')[0] || 'Unduhan Playlist';
|
|
114
|
+
playlistInfo.items = lines.map((l) => l.split('|')[1]).filter(Boolean);
|
|
115
|
+
}
|
|
116
|
+
} catch (e) {}
|
|
117
|
+
|
|
118
|
+
let playlistSelection = null;
|
|
119
|
+
if (playlistInfo.isPlaylist) {
|
|
120
|
+
console.log(`\n📂 PLAYLIST TERDETEKSI: ${playlistInfo.title}`);
|
|
121
|
+
playlistInfo.items.forEach((item) => console.log(item));
|
|
122
|
+
const selectionInput = await askQuestion(
|
|
123
|
+
'\nPilih nomor video (contoh: 1,3,5-10) atau tekan Enter untuk semua: '
|
|
124
|
+
);
|
|
125
|
+
playlistSelection = parseSelection(
|
|
126
|
+
selectionInput,
|
|
127
|
+
playlistInfo.items.length
|
|
128
|
+
);
|
|
129
|
+
}
|
|
92
130
|
|
|
93
131
|
console.log('\n[PILIH FORMAT]');
|
|
94
|
-
console.log('1.
|
|
95
|
-
console.log('2.
|
|
132
|
+
console.log('1. Video MP4 (Sangat Kompatibel Mac/QuickTime)');
|
|
133
|
+
console.log('2. Audio (MP3) ' + (ffExists ? '✅' : '❌ (Butuh FFmpeg)'));
|
|
96
134
|
const mode = await askQuestion('Pilihan: ');
|
|
97
135
|
|
|
98
|
-
const
|
|
99
|
-
|
|
136
|
+
const baseDir = path.join(os.homedir(), 'Downloads', 'media-dl');
|
|
137
|
+
const subFolder = mode === '2' ? 'audio' : 'video';
|
|
138
|
+
let finalOutputDir = path.join(baseDir, subFolder);
|
|
139
|
+
|
|
140
|
+
if (playlistInfo.isPlaylist) {
|
|
141
|
+
const folderName = playlistInfo.title.replace(/[\\/:"*?<>|]/g, '_');
|
|
142
|
+
finalOutputDir = path.join(finalOutputDir, folderName);
|
|
143
|
+
}
|
|
100
144
|
|
|
101
|
-
|
|
145
|
+
if (!fs.existsSync(finalOutputDir))
|
|
146
|
+
fs.mkdirSync(finalOutputDir, { recursive: true });
|
|
147
|
+
|
|
148
|
+
const qtFormat =
|
|
149
|
+
'bestvideo[vcodec^=avc1]+bestaudio[acodec^=mp4a]/best[vcodec^=avc1]/best';
|
|
102
150
|
let args = [
|
|
103
151
|
'--ffmpeg-location',
|
|
104
152
|
FFMPEG_PATH,
|
|
105
153
|
'-o',
|
|
106
|
-
`${
|
|
154
|
+
`${finalOutputDir}/%(title).100s.%(ext)s`,
|
|
107
155
|
videoURL,
|
|
108
156
|
];
|
|
109
157
|
|
|
110
|
-
if (
|
|
158
|
+
if (playlistSelection) {
|
|
159
|
+
args.push('--playlist-items', playlistSelection);
|
|
160
|
+
} else if (!playlistInfo.isPlaylist) {
|
|
161
|
+
args.push('--no-playlist');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (mode === '2') {
|
|
111
165
|
if (!ffExists) {
|
|
112
|
-
console.log(
|
|
113
|
-
'❌ MP3 butuh FFmpeg. Silakan instal FFmpeg terlebih dahulu.'
|
|
114
|
-
);
|
|
166
|
+
console.log('❌ Butuh FFmpeg.');
|
|
115
167
|
return mainMenu();
|
|
116
168
|
}
|
|
117
169
|
args.unshift('-x', '--audio-format', 'mp3');
|
|
118
170
|
} else {
|
|
119
|
-
console.log('\n[PILIH
|
|
120
|
-
console.log('1. Terbaik (
|
|
121
|
-
console.log('2. 1080p
|
|
171
|
+
console.log('\n[PILIH KUALITAS]');
|
|
172
|
+
console.log('1. Terbaik (Auto-Conversion ke MP4)');
|
|
173
|
+
console.log('2. 1080p');
|
|
122
174
|
console.log('3. 720p');
|
|
123
|
-
|
|
124
|
-
const resChoice = await askQuestion('Pilih resolusi: ');
|
|
125
|
-
|
|
126
|
-
let formatCode = 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best';
|
|
127
|
-
if (resChoice === '2')
|
|
128
|
-
formatCode =
|
|
129
|
-
'bestvideo[height<=1080][ext=mp4]+bestaudio[ext=m4a]/best[height<=1080][ext=mp4]/best';
|
|
130
|
-
if (resChoice === '3')
|
|
131
|
-
formatCode =
|
|
132
|
-
'bestvideo[height<=720][ext=mp4]+bestaudio[ext=m4a]/best[height<=720][ext=mp4]/best';
|
|
133
|
-
if (resChoice === '4')
|
|
134
|
-
formatCode =
|
|
135
|
-
'bestvideo[height<=480][ext=mp4]+bestaudio[ext=m4a]/best[height<=480][ext=mp4]/best';
|
|
136
|
-
|
|
137
|
-
args.unshift('-f', formatCode);
|
|
175
|
+
const res = await askQuestion('Pilih: ');
|
|
138
176
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
'best[
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
];
|
|
150
|
-
}
|
|
177
|
+
let fCode = qtFormat;
|
|
178
|
+
if (res === '2')
|
|
179
|
+
fCode =
|
|
180
|
+
'bestvideo[height<=1080][vcodec^=avc1]+bestaudio[acodec^=mp4a]/best[height<=1080]/best';
|
|
181
|
+
if (res === '3')
|
|
182
|
+
fCode =
|
|
183
|
+
'bestvideo[height<=720][vcodec^=avc1]+bestaudio[acodec^=mp4a]/best[height<=720]/best';
|
|
184
|
+
|
|
185
|
+
args.unshift('-f', fCode);
|
|
186
|
+
if (ffExists) args.unshift('--recode-video', 'mp4');
|
|
151
187
|
}
|
|
152
188
|
|
|
153
|
-
console.log('\n🚀 Memulai proses...');
|
|
189
|
+
console.log('\n🚀 Memulai proses unduhan...');
|
|
154
190
|
const download = spawn(YTDLP_PATH, args);
|
|
155
191
|
|
|
156
192
|
download.stdout.on('data', (data) => process.stdout.write(data));
|
|
@@ -158,29 +194,51 @@ async function startDownload() {
|
|
|
158
194
|
|
|
159
195
|
download.on('close', (code) => {
|
|
160
196
|
if (code === 0) {
|
|
161
|
-
console.log(`\n✅
|
|
162
|
-
execSync(
|
|
197
|
+
console.log(`\n✅ BERHASIL! File disimpan di: ${finalOutputDir}`);
|
|
198
|
+
execSync(
|
|
199
|
+
isMac ? `open "${finalOutputDir}"` : `explorer "${finalOutputDir}"`
|
|
200
|
+
);
|
|
163
201
|
} else {
|
|
164
|
-
console.log('\n❌
|
|
202
|
+
console.log('\n❌ Gagal.');
|
|
165
203
|
}
|
|
166
204
|
mainMenu();
|
|
167
205
|
});
|
|
168
206
|
}
|
|
169
207
|
|
|
208
|
+
async function showSupport() {
|
|
209
|
+
console.log('\n====================================');
|
|
210
|
+
console.log(' ❤️ SUPPORT DEVELOPER ');
|
|
211
|
+
console.log('====================================');
|
|
212
|
+
console.log('Terima kasih telah menggunakan MEDIA-DL!');
|
|
213
|
+
console.log('Dukungan Anda sangat berarti bagi pengembangan skrip ini.');
|
|
214
|
+
console.log(
|
|
215
|
+
'\n* ☕ Beli Kopi: https://app.midtrans.com/payment-links/coffee-developer'
|
|
216
|
+
);
|
|
217
|
+
console.log(
|
|
218
|
+
'* 🍕 Beli Pizza: https://app.midtrans.com/payment-links/pizza-developer'
|
|
219
|
+
);
|
|
220
|
+
console.log('------------------------------------');
|
|
221
|
+
await askQuestion('\nTekan Enter untuk kembali ke Menu Utama...');
|
|
222
|
+
mainMenu();
|
|
223
|
+
}
|
|
224
|
+
|
|
170
225
|
async function mainMenu() {
|
|
171
226
|
const { ytExists, ffExists } = checkTools();
|
|
172
227
|
console.log('\n====================================');
|
|
173
228
|
console.log(' MEDIA-DL MANAGER 2026 ');
|
|
174
229
|
console.log('====================================');
|
|
175
|
-
console.log(
|
|
176
|
-
|
|
177
|
-
|
|
230
|
+
console.log(
|
|
231
|
+
` OS : ${process.platform} | yt-dlp: ${
|
|
232
|
+
ytExists ? '✅' : '❌'
|
|
233
|
+
} | ffmpeg: ${ffExists ? '✅' : '❌'}`
|
|
234
|
+
);
|
|
178
235
|
console.log('------------------------------------');
|
|
179
|
-
console.log(' 1. 📥 Download Media');
|
|
236
|
+
console.log(' 1. 📥 Download Media (Single/Playlist)');
|
|
180
237
|
console.log(' 2. ⚙️ Update yt-dlp');
|
|
181
238
|
console.log(' 3. 🔨 Instal FFmpeg (macOS)');
|
|
182
|
-
console.log(' 4.
|
|
183
|
-
console.log(' 5.
|
|
239
|
+
console.log(' 4. ❤️ Support Developer');
|
|
240
|
+
console.log(' 5. 🗑️ Uninstall & Hapus Data');
|
|
241
|
+
console.log(' 6. 🚪 Keluar');
|
|
184
242
|
|
|
185
243
|
const choice = await askQuestion('\nPilih menu: ');
|
|
186
244
|
if (choice === '1') await startDownload();
|
|
@@ -190,10 +248,13 @@ async function mainMenu() {
|
|
|
190
248
|
} else if (choice === '3') {
|
|
191
249
|
await installFfmpeg();
|
|
192
250
|
mainMenu();
|
|
193
|
-
} else if (choice === '4')
|
|
194
|
-
|
|
195
|
-
|
|
251
|
+
} else if (choice === '4') await showSupport();
|
|
252
|
+
else if (choice === '5') {
|
|
253
|
+
const confirm = await askQuestion('Hapus semua? (y/n): ');
|
|
254
|
+
if (confirm.toLowerCase() === 'y') {
|
|
196
255
|
fs.rmSync(TOOLS_DIR, { recursive: true, force: true });
|
|
256
|
+
console.log('✅ Folder tools dibersihkan.');
|
|
257
|
+
}
|
|
197
258
|
mainMenu();
|
|
198
259
|
} else {
|
|
199
260
|
rl.close();
|