wavesconv 1.5.0 → 1.6.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/cli.js CHANGED
@@ -5,6 +5,7 @@ const path = require('path');
5
5
  const fs = require('fs');
6
6
  const engine = require('./lib/engine');
7
7
  const ui = require('./lib/cli-ui');
8
+ const { runInteractive } = require('./lib/cli-interactive');
8
9
 
9
10
  const VERSION = require('./package.json').version;
10
11
 
@@ -228,9 +229,22 @@ async function run(argv) {
228
229
  }
229
230
 
230
231
  const cmd = positional[0];
231
- if (!cmd) {
232
- ui.printHelp(VERSION, color);
233
- return 1;
232
+
233
+ // wavesconv — tryb interaktywny (wklej linki w konsoli)
234
+ if (!cmd || cmd === 'interactive' || cmd === 'i') {
235
+ if (!process.stdin.isTTY) {
236
+ ui.error(color, 'Tryb interaktywny wymaga terminala. Użyj: wavesconv download <url>');
237
+ return 1;
238
+ }
239
+ return runInteractive(
240
+ {
241
+ download: cmdDownload,
242
+ info: cmdInfo,
243
+ toolsInstall: cmdToolsInstall,
244
+ },
245
+ VERSION,
246
+ color
247
+ );
234
248
  }
235
249
 
236
250
  if (color && cmd !== 'tools') {
@@ -0,0 +1,288 @@
1
+ 'use strict';
2
+
3
+ const readline = require('readline');
4
+ const path = require('path');
5
+ const fs = require('fs');
6
+ const engine = require('./engine');
7
+ const ui = require('./cli-ui');
8
+
9
+ function defaultSession() {
10
+ return {
11
+ output: engine.getDefaultDownloadDir(),
12
+ format: 'mp4',
13
+ audioOnly: false,
14
+ quality: 'best',
15
+ bitrate: '',
16
+ filename: '%(title)s',
17
+ };
18
+ }
19
+
20
+ function sessionToFlags(session) {
21
+ return {
22
+ output: session.output,
23
+ format: session.format,
24
+ audio: session.audioOnly,
25
+ quality: session.quality,
26
+ bitrate: session.bitrate,
27
+ filename: session.filename,
28
+ };
29
+ }
30
+
31
+ function extractYouTubeUrls(text) {
32
+ const found = new Set();
33
+ const re = /https?:\/\/(?:www\.)?(?:youtube\.com\/[^\s]+|youtu\.be\/[^\s]+)/gi;
34
+ let m;
35
+ while ((m = re.exec(text)) !== null) {
36
+ found.add(m[0].replace(/[)\]},.;]+$/, ''));
37
+ }
38
+ const parts = text.trim().split(/\s+/).filter(Boolean);
39
+ for (const p of parts) {
40
+ if (engine.isYouTubeUrl(p)) found.add(p.replace(/[)\]},.;]+$/, ''));
41
+ }
42
+ return [...found];
43
+ }
44
+
45
+ function printSession(color, session) {
46
+ const mode = session.audioOnly ? 'audio' : 'wideo';
47
+ const fmt = session.format;
48
+ ui.info(
49
+ color,
50
+ `Tryb: ${mode} · ${fmt} · ${session.quality} · folder: ${session.output}`
51
+ );
52
+ }
53
+
54
+ function printInteractiveHelp(color) {
55
+ console.log('');
56
+ ui.info(color, 'Wklej link YouTube i naciśnij Enter — pobieranie startuje automatycznie.');
57
+ console.log(ui.c(color, ui.palette.dim, ' Komendy:'));
58
+ const cmds = [
59
+ ['/help', 'Ta pomoc'],
60
+ ['/info <url>', 'Podgląd bez pobierania'],
61
+ ['/audio', 'Tryb audio (mp3 domyślnie)'],
62
+ ['/video', 'Tryb wideo (mp4 domyślnie)'],
63
+ ['/fmt mp3', 'Format: mp4, mp3, webm, mkv…'],
64
+ ['/q 1080p', 'Jakość: best, 1080p, 720p…'],
65
+ ['/folder', 'Zmień folder pobierania'],
66
+ ['/folder C:\\Videos', 'Ustaw folder od razu'],
67
+ ['/status', 'Narzędzia yt-dlp / ffmpeg'],
68
+ ['/install', 'Zainstaluj yt-dlp'],
69
+ ['/quit', 'Wyjście (lub Ctrl+C)'],
70
+ ];
71
+ for (const [cmd, desc] of cmds) {
72
+ console.log(' ' + ui.c(color, ui.palette.cyan, cmd.padEnd(22)) + ui.c(color, ui.palette.dim, desc));
73
+ }
74
+ console.log('');
75
+ }
76
+
77
+ async function runInteractive(handlers, version, color) {
78
+ const session = defaultSession();
79
+ let ytDlpReady = false;
80
+
81
+ ui.printBanner(version, color);
82
+ printSession(color, session);
83
+ printInteractiveHelp(color);
84
+
85
+ const promptText = color
86
+ ? ui.c(true, ui.palette.pink, '🔗 ') + ui.c(true, ui.palette.bold, 'Wklej link') + ui.c(true, ui.palette.dim, ' › ')
87
+ : 'link> ';
88
+
89
+ const rl = readline.createInterface({
90
+ input: process.stdin,
91
+ output: process.stdout,
92
+ terminal: true,
93
+ historySize: 100,
94
+ });
95
+
96
+ const ensureTools = async () => {
97
+ if (ytDlpReady && engine.findYtDlp()) return;
98
+ const spin = new ui.Spinner('Przygotowanie yt-dlp', color);
99
+ spin.start();
100
+ try {
101
+ await engine.ensureYtDlp(msg => spin.draw(msg));
102
+ spin.succeed('Gotowe');
103
+ ytDlpReady = true;
104
+ } catch (e) {
105
+ spin.fail(e.message);
106
+ throw e;
107
+ }
108
+ };
109
+
110
+ const askLine = (question) => new Promise(resolve => {
111
+ rl.question(question, answer => resolve(answer.trim()));
112
+ });
113
+
114
+ const processSlashCommand = async (line) => {
115
+ const parts = line.slice(1).trim().split(/\s+/);
116
+ const cmd = (parts[0] || '').toLowerCase();
117
+ const arg = parts.slice(1).join(' ');
118
+
119
+ switch (cmd) {
120
+ case 'help':
121
+ case 'h':
122
+ case '?':
123
+ printInteractiveHelp(color);
124
+ return;
125
+ case 'quit':
126
+ case 'exit':
127
+ case 'q':
128
+ ui.info(color, 'Do zobaczenia! 🌊');
129
+ rl.close();
130
+ return 'exit';
131
+ case 'audio':
132
+ case 'a':
133
+ session.audioOnly = true;
134
+ if (!['mp3', 'wav', 'flac', 'aac', 'm4a', 'ogg'].includes(session.format)) {
135
+ session.format = 'mp3';
136
+ }
137
+ ui.success(color, `Tryb audio · format: ${session.format}`);
138
+ printSession(color, session);
139
+ return;
140
+ case 'video':
141
+ case 'v':
142
+ session.audioOnly = false;
143
+ if (['mp3', 'wav', 'flac', 'aac', 'm4a', 'ogg'].includes(session.format)) {
144
+ session.format = 'mp4';
145
+ }
146
+ ui.success(color, `Tryb wideo · format: ${session.format}`);
147
+ printSession(color, session);
148
+ return;
149
+ case 'fmt':
150
+ case 'format':
151
+ case 'f':
152
+ if (!arg) {
153
+ ui.warn(color, 'Użyj: /fmt mp3 lub /fmt mp4');
154
+ return;
155
+ }
156
+ session.format = arg.toLowerCase();
157
+ if (['mp3', 'wav', 'flac', 'aac', 'm4a', 'ogg'].includes(session.format)) {
158
+ session.audioOnly = true;
159
+ }
160
+ ui.success(color, `Format: ${session.format}`);
161
+ printSession(color, session);
162
+ return;
163
+ case 'q':
164
+ case 'quality':
165
+ if (!arg) {
166
+ ui.warn(color, 'Użyj: /q 1080p lub /q best');
167
+ return;
168
+ }
169
+ session.quality = arg;
170
+ ui.success(color, `Jakość: ${session.quality}`);
171
+ printSession(color, session);
172
+ return;
173
+ case 'folder':
174
+ case 'o':
175
+ case 'output':
176
+ if (arg) {
177
+ const dir = path.resolve(arg);
178
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
179
+ session.output = dir;
180
+ ui.success(color, 'Folder: ' + dir);
181
+ } else {
182
+ const entered = await askLine(
183
+ color ? ui.c(true, ui.palette.cyan, 'Ścieżka folderu: ') : 'Folder: '
184
+ );
185
+ if (entered) {
186
+ const dir = path.resolve(entered);
187
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
188
+ session.output = dir;
189
+ ui.success(color, 'Folder: ' + dir);
190
+ }
191
+ }
192
+ printSession(color, session);
193
+ return;
194
+ case 'status':
195
+ ui.kv(color, 'yt-dlp', engine.findYtDlp() || 'BRAK', !!engine.findYtDlp());
196
+ ui.kv(color, 'ffmpeg', engine.findFfmpeg() || 'BRAK', !!engine.findFfmpeg());
197
+ return;
198
+ case 'install':
199
+ await handlers.toolsInstall(color);
200
+ ytDlpReady = true;
201
+ return;
202
+ case 'info':
203
+ case 'i': {
204
+ const url = arg || '';
205
+ if (!url || !engine.isYouTubeUrl(url)) {
206
+ ui.warn(color, 'Użyj: /info <url>');
207
+ return;
208
+ }
209
+ await handlers.info(url, {}, color);
210
+ return;
211
+ }
212
+ case 'clear':
213
+ console.clear();
214
+ ui.printBanner(version, color);
215
+ printSession(color, session);
216
+ return;
217
+ default:
218
+ ui.warn(color, `Nieznana komenda: /${cmd} — wpisz /help`);
219
+ }
220
+ };
221
+
222
+ rl.setPrompt(promptText);
223
+
224
+ return new Promise(resolve => {
225
+ rl.on('close', () => resolve(0));
226
+
227
+ const loop = () => {
228
+ rl.prompt();
229
+ };
230
+
231
+ rl.on('line', async line => {
232
+ const trimmed = line.trim();
233
+ rl.pause();
234
+
235
+ try {
236
+ if (!trimmed) {
237
+ rl.resume();
238
+ loop();
239
+ return;
240
+ }
241
+
242
+ if (trimmed.startsWith('/')) {
243
+ const result = await processSlashCommand(trimmed);
244
+ if (result === 'exit') {
245
+ resolve(0);
246
+ return;
247
+ }
248
+ rl.resume();
249
+ loop();
250
+ return;
251
+ }
252
+
253
+ const urls = extractYouTubeUrls(trimmed);
254
+ if (!urls.length) {
255
+ ui.warn(color, 'Nie wykryto linku YouTube. Wklej URL lub wpisz /help');
256
+ rl.resume();
257
+ loop();
258
+ return;
259
+ }
260
+
261
+ await ensureTools();
262
+
263
+ for (let i = 0; i < urls.length; i++) {
264
+ if (urls.length > 1) {
265
+ ui.info(color, `Pobieranie ${i + 1}/${urls.length}`);
266
+ }
267
+ await handlers.download(urls[i], sessionToFlags(session), color);
268
+ }
269
+ } catch (e) {
270
+ ui.error(color, e.message || String(e));
271
+ }
272
+
273
+ rl.resume();
274
+ loop();
275
+ });
276
+
277
+ rl.on('SIGINT', () => {
278
+ console.log('');
279
+ ui.info(color, 'Wyjście (Ctrl+C)');
280
+ rl.close();
281
+ resolve(0);
282
+ });
283
+
284
+ loop();
285
+ });
286
+ }
287
+
288
+ module.exports = { runInteractive, defaultSession };
package/lib/cli-ui.js CHANGED
@@ -66,6 +66,7 @@ function printHelp(version, enabled) {
66
66
  [' convert <plik>', 'Konwertuj plik lokalny'],
67
67
  [' tools install', 'Zainstaluj yt-dlp'],
68
68
  [' tools status', 'Status yt-dlp i ffmpeg'],
69
+ [' (bez argumentów)', 'Tryb interaktywny — wklej linki w konsoli'],
69
70
  ['', ''],
70
71
  ['Opcje download', ''],
71
72
  [' -o, --output <dir>', 'Folder docelowy'],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wavesconv",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "CLI: pobieranie YouTube (yt-dlp) i konwersja mediów (ffmpeg) — WavesConverter",
5
5
  "main": "cli.js",
6
6
  "bin": {