media-dl 1.0.6 → 2.0.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 +160 -47
- package/package.json +1 -1
- package/readme.md +32 -19
package/bin/cli.js
CHANGED
|
@@ -8,9 +8,10 @@ const os = require('os');
|
|
|
8
8
|
|
|
9
9
|
const rl = readline.createInterface({
|
|
10
10
|
input: process.stdin,
|
|
11
|
-
output: process.stdout
|
|
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';
|
|
@@ -21,30 +22,35 @@ const FFMPEG_PATH = path.join(TOOLS_DIR, isWindows ? 'ffmpeg.exe' : 'ffmpeg');
|
|
|
21
22
|
if (!fs.existsSync(TOOLS_DIR)) fs.mkdirSync(TOOLS_DIR, { recursive: true });
|
|
22
23
|
|
|
23
24
|
function askQuestion(query) {
|
|
24
|
-
return new Promise(resolve => rl.question(query, resolve));
|
|
25
|
+
return new Promise((resolve) => rl.question(query, resolve));
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
function checkTools() {
|
|
28
29
|
return {
|
|
29
30
|
ytExists: fs.existsSync(YTDLP_PATH),
|
|
30
|
-
ffExists: fs.existsSync(FFMPEG_PATH)
|
|
31
|
+
ffExists: fs.existsSync(FFMPEG_PATH),
|
|
31
32
|
};
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
async function installYtdlp() {
|
|
35
36
|
console.log('\n⏳ Mengunduh yt-dlp...');
|
|
36
|
-
const url = isWindows
|
|
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
|
try {
|
|
40
41
|
if (isWindows) {
|
|
41
|
-
execSync(
|
|
42
|
+
execSync(
|
|
43
|
+
`powershell -Command "Invoke-WebRequest -Uri ${url} -OutFile '${YTDLP_PATH}'"`,
|
|
44
|
+
{ stdio: 'inherit' }
|
|
45
|
+
);
|
|
42
46
|
} else {
|
|
43
47
|
execSync(`curl -L -# "${url}" -o "${YTDLP_PATH}"`, { stdio: 'inherit' });
|
|
44
48
|
execSync(`chmod a+rx "${YTDLP_PATH}"`);
|
|
45
49
|
}
|
|
46
50
|
console.log('✅ yt-dlp berhasil diinstal.');
|
|
47
|
-
} catch (e) {
|
|
51
|
+
} catch (e) {
|
|
52
|
+
console.error('❌ Gagal mengunduh yt-dlp.');
|
|
53
|
+
}
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
async function installFfmpeg() {
|
|
@@ -52,13 +58,34 @@ async function installFfmpeg() {
|
|
|
52
58
|
try {
|
|
53
59
|
if (isMac) {
|
|
54
60
|
const zipPath = path.join(TOOLS_DIR, 'ffmpeg.zip');
|
|
55
|
-
execSync(
|
|
61
|
+
execSync(
|
|
62
|
+
`curl -L -# "https://evermeet.cx/ffmpeg/getrelease/ffmpeg/zip" -o "${zipPath}"`,
|
|
63
|
+
{ stdio: 'inherit' }
|
|
64
|
+
);
|
|
56
65
|
execSync(`unzip -o "${zipPath}" -d "${TOOLS_DIR}"`, { stdio: 'inherit' });
|
|
57
66
|
execSync(`rm "${zipPath}"`);
|
|
58
67
|
execSync(`chmod a+rx "${FFMPEG_PATH}"`);
|
|
59
68
|
console.log('✅ FFmpeg berhasil disiapkan.');
|
|
60
69
|
}
|
|
61
|
-
} catch (e) {
|
|
70
|
+
} catch (e) {
|
|
71
|
+
console.error('❌ Gagal instal FFmpeg.');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
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(',');
|
|
62
89
|
}
|
|
63
90
|
|
|
64
91
|
async function startDownload() {
|
|
@@ -70,83 +97,169 @@ async function startDownload() {
|
|
|
70
97
|
if (!fs.existsSync(YTDLP_PATH)) return mainMenu();
|
|
71
98
|
}
|
|
72
99
|
|
|
73
|
-
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
|
+
}
|
|
130
|
+
|
|
74
131
|
console.log('\n[PILIH FORMAT]');
|
|
75
|
-
console.log('1.
|
|
76
|
-
console.log('2.
|
|
132
|
+
console.log('1. Video MP4 (Sangat Kompatibel Mac/QuickTime)');
|
|
133
|
+
console.log('2. Audio (MP3) ' + (ffExists ? '✅' : '❌ (Butuh FFmpeg)'));
|
|
77
134
|
const mode = await askQuestion('Pilihan: ');
|
|
78
135
|
|
|
79
136
|
const baseDir = path.join(os.homedir(), 'Downloads', 'media-dl');
|
|
80
|
-
const subFolder =
|
|
81
|
-
|
|
82
|
-
|
|
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
|
+
}
|
|
144
|
+
|
|
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';
|
|
150
|
+
let args = [
|
|
151
|
+
'--ffmpeg-location',
|
|
152
|
+
FFMPEG_PATH,
|
|
153
|
+
'-o',
|
|
154
|
+
`${finalOutputDir}/%(title).100s.%(ext)s`,
|
|
155
|
+
videoURL,
|
|
156
|
+
];
|
|
83
157
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
158
|
+
if (playlistSelection) {
|
|
159
|
+
args.push('--playlist-items', playlistSelection);
|
|
160
|
+
} else if (!playlistInfo.isPlaylist) {
|
|
161
|
+
args.push('--no-playlist');
|
|
162
|
+
}
|
|
88
163
|
|
|
89
|
-
if (mode === '
|
|
90
|
-
if (!ffExists) {
|
|
164
|
+
if (mode === '2') {
|
|
165
|
+
if (!ffExists) {
|
|
166
|
+
console.log('❌ Butuh FFmpeg.');
|
|
167
|
+
return mainMenu();
|
|
168
|
+
}
|
|
91
169
|
args.unshift('-x', '--audio-format', 'mp3');
|
|
92
170
|
} else {
|
|
93
171
|
console.log('\n[PILIH KUALITAS]');
|
|
94
172
|
console.log('1. Terbaik (Auto-Conversion ke MP4)');
|
|
95
|
-
console.log('2. 1080p
|
|
96
|
-
console.log('3. 720p
|
|
173
|
+
console.log('2. 1080p');
|
|
174
|
+
console.log('3. 720p');
|
|
97
175
|
const res = await askQuestion('Pilih: ');
|
|
98
|
-
|
|
176
|
+
|
|
99
177
|
let fCode = qtFormat;
|
|
100
|
-
if (res === '2')
|
|
101
|
-
|
|
102
|
-
|
|
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
|
+
|
|
103
185
|
args.unshift('-f', fCode);
|
|
104
|
-
|
|
105
|
-
// FITUR BARU: Jika FFmpeg ada, paksa konversi ke MP4 agar pasti bisa dibuka QuickTime
|
|
106
|
-
if (ffExists) {
|
|
107
|
-
args.unshift('--recode-video', 'mp4');
|
|
108
|
-
}
|
|
186
|
+
if (ffExists) args.unshift('--recode-video', 'mp4');
|
|
109
187
|
}
|
|
110
188
|
|
|
111
|
-
console.log('\n🚀 Memulai proses
|
|
189
|
+
console.log('\n🚀 Memulai proses unduhan...');
|
|
112
190
|
const download = spawn(YTDLP_PATH, args);
|
|
191
|
+
|
|
113
192
|
download.stdout.on('data', (data) => process.stdout.write(data));
|
|
114
193
|
download.stderr.on('data', (data) => process.stderr.write(data));
|
|
115
194
|
|
|
116
195
|
download.on('close', (code) => {
|
|
117
196
|
if (code === 0) {
|
|
118
|
-
console.log(`\n✅ BERHASIL! File
|
|
119
|
-
execSync(
|
|
120
|
-
|
|
197
|
+
console.log(`\n✅ BERHASIL! File disimpan di: ${finalOutputDir}`);
|
|
198
|
+
execSync(
|
|
199
|
+
isMac ? `open "${finalOutputDir}"` : `explorer "${finalOutputDir}"`
|
|
200
|
+
);
|
|
201
|
+
} else {
|
|
202
|
+
console.log('\n❌ Gagal.');
|
|
203
|
+
}
|
|
121
204
|
mainMenu();
|
|
122
205
|
});
|
|
123
206
|
}
|
|
124
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
|
+
|
|
125
225
|
async function mainMenu() {
|
|
126
226
|
const { ytExists, ffExists } = checkTools();
|
|
127
227
|
console.log('\n====================================');
|
|
128
228
|
console.log(' MEDIA-DL MANAGER 2026 ');
|
|
129
229
|
console.log('====================================');
|
|
130
|
-
console.log(
|
|
230
|
+
console.log(
|
|
231
|
+
` OS : ${process.platform} | yt-dlp: ${
|
|
232
|
+
ytExists ? '✅' : '❌'
|
|
233
|
+
} | ffmpeg: ${ffExists ? '✅' : '❌'}`
|
|
234
|
+
);
|
|
131
235
|
console.log('------------------------------------');
|
|
132
|
-
console.log(' 1. 📥 Download Media');
|
|
236
|
+
console.log(' 1. 📥 Download Media (Single/Playlist)');
|
|
133
237
|
console.log(' 2. ⚙️ Update yt-dlp');
|
|
134
238
|
console.log(' 3. 🔨 Instal FFmpeg (macOS)');
|
|
135
|
-
console.log(' 4.
|
|
136
|
-
console.log(' 5.
|
|
137
|
-
|
|
239
|
+
console.log(' 4. ❤️ Support Developer');
|
|
240
|
+
console.log(' 5. 🗑️ Uninstall & Hapus Data');
|
|
241
|
+
console.log(' 6. 🚪 Keluar');
|
|
242
|
+
|
|
138
243
|
const choice = await askQuestion('\nPilih menu: ');
|
|
139
244
|
if (choice === '1') await startDownload();
|
|
140
|
-
else if (choice === '2') {
|
|
141
|
-
|
|
142
|
-
|
|
245
|
+
else if (choice === '2') {
|
|
246
|
+
await installYtdlp();
|
|
247
|
+
mainMenu();
|
|
248
|
+
} else if (choice === '3') {
|
|
249
|
+
await installFfmpeg();
|
|
250
|
+
mainMenu();
|
|
251
|
+
} else if (choice === '4') await showSupport();
|
|
252
|
+
else if (choice === '5') {
|
|
143
253
|
const confirm = await askQuestion('Hapus semua? (y/n): ');
|
|
144
254
|
if (confirm.toLowerCase() === 'y') {
|
|
145
|
-
|
|
146
|
-
|
|
255
|
+
fs.rmSync(TOOLS_DIR, { recursive: true, force: true });
|
|
256
|
+
console.log('✅ Folder tools dibersihkan.');
|
|
147
257
|
}
|
|
148
258
|
mainMenu();
|
|
149
|
-
} else {
|
|
259
|
+
} else {
|
|
260
|
+
rl.close();
|
|
261
|
+
process.exit(0);
|
|
262
|
+
}
|
|
150
263
|
}
|
|
151
264
|
|
|
152
|
-
mainMenu();
|
|
265
|
+
mainMenu();
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
# Media-DL CLI 🚀
|
|
1
|
+
# Media-DL CLI 2026 🚀
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Media-DL CLI** adalah pengunduh media berbasis terminal generasi terbaru yang dirancang untuk kecepatan, kemudahan, dan kompatibilitas penuh. Ditenagai oleh `yt-dlp`, alat ini memastikan video yang Anda unduh selalu siap diputar di perangkat apa pun tanpa kendala codec.
|
|
4
4
|
|
|
5
|
-
## ✨ Fitur
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
## ✨ Fitur Unggulan
|
|
6
|
+
|
|
7
|
+
* 📥 **YouTube Playlist Power**: Deteksi otomatis playlist. Pilih video tertentu (misal: `1,3,5-10`) atau unduh semua sekaligus.
|
|
8
|
+
* 🍏 **Apple Ecosystem Ready**: Secara otomatis mengonversi dan mengoptimalkan video ke format **H.264 (AVC1)** & **AAC** agar lancar diputar di **QuickTime Player**, iPhone, dan iPad.
|
|
9
|
+
* 🛠️ **Smart Auto-Installer**: Tidak perlu pusing mengunduh `yt-dlp` atau `ffmpeg` secara manual. Skrip akan menyiapkannya untuk Anda (Optimal untuk macOS).
|
|
10
|
+
* 📂 **Organized Storage**: Hasil unduhan tersimpan rapi di folder standar sistem `~/Downloads/media-dl/` yang dipisahkan berdasarkan kategori `video` dan `audio`.
|
|
11
|
+
* 🛡️ **Fail-Safe Filename**: Fitur pemotongan judul otomatis untuk menghindari error *filename too long* pada sistem macOS/Windows.
|
|
10
12
|
|
|
11
13
|
## 📦 Instalasi
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
Cukup pastikan Anda memiliki [Node.js](https://nodejs.org/) terinstal, lalu jalankan:
|
|
14
16
|
|
|
15
17
|
```bash
|
|
16
18
|
npm install -g media-dl
|
|
@@ -19,33 +21,44 @@ npm install -g media-dl
|
|
|
19
21
|
|
|
20
22
|
## 🚀 Cara Penggunaan
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
Jalankan perintah berikut di terminal Anda:
|
|
23
25
|
|
|
24
26
|
```bash
|
|
25
27
|
media-dl
|
|
26
28
|
|
|
27
29
|
```
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
### Panduan Menu:
|
|
32
|
+
|
|
33
|
+
1. **Download Media**: Masukkan link video atau playlist.
|
|
34
|
+
2. **Pilih Format**: Video MP4 (QuickTime Compatible) atau Audio MP3.
|
|
35
|
+
3. **Seleksi Playlist**: Masukkan angka video yang diinginkan (Contoh: `1,3-5`) atau tekan `Enter` untuk semua.
|
|
36
|
+
4. **Auto-Open**: Folder tujuan akan otomatis terbuka setelah proses selesai.
|
|
37
|
+
|
|
38
|
+
## 📁 Lokasi Penyimpanan
|
|
39
|
+
|
|
40
|
+
Skrip ini menggunakan struktur folder yang rapi:
|
|
41
|
+
|
|
42
|
+
* **Tools**: `~/.media-dl/` (Tersembunyi agar sistem tetap bersih).
|
|
43
|
+
* **Video**: `~/Downloads/media-dl/video/`
|
|
44
|
+
* **Audio**: `~/Downloads/media-dl/audio/`
|
|
30
45
|
|
|
31
46
|
## 📋 Persyaratan Sistem
|
|
32
47
|
|
|
33
48
|
* **Node.js**: v14.0.0 atau lebih tinggi.
|
|
34
|
-
* **FFmpeg**: Diperlukan untuk konversi
|
|
49
|
+
* **FFmpeg**: Diperlukan untuk konversi tingkat lanjut. Gunakan **Menu 3** di dalam aplikasi untuk instalasi otomatis (macOS).
|
|
35
50
|
|
|
36
|
-
##
|
|
51
|
+
## ❤️ Dukungan & Donasi
|
|
37
52
|
|
|
38
|
-
Jika alat ini membantu
|
|
53
|
+
Skrip ini dikembangkan secara terbuka. Jika alat ini membantu produktivitas Anda, pertimbangkan untuk traktir pengembang:
|
|
39
54
|
|
|
40
55
|
* **Beli Kopi ☕**: [Donasi via Midtrans](https://app.midtrans.com/payment-links/coffee-developer)
|
|
41
56
|
* **Beli Pizza 🍕**: [Donasi via Midtrans](https://app.midtrans.com/payment-links/pizza-developer)
|
|
42
57
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
## ⭐ Ulasan
|
|
46
|
-
|
|
47
|
-
> "Alat paling praktis untuk urusan download cepat lewat terminal tanpa ribet iklan!" - *Community User*
|
|
58
|
+
---
|
|
48
59
|
|
|
49
60
|
## 📄 Lisensi
|
|
50
61
|
|
|
51
|
-
MIT
|
|
62
|
+
Distribusi di bawah Lisensi MIT. Bebas digunakan dan dikembangkan kembali.
|
|
63
|
+
|
|
64
|
+
---
|