wavesconv 1.5.0 → 1.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/README.md CHANGED
@@ -1,12 +1,10 @@
1
1
  <div align="center">
2
2
 
3
- <img src="https://raw.githubusercontent.com/idunnowhytf/wavesconvsite/main/docs/og.png" alt="WavesConverter" width="100%"/>
3
+ <img src="https://res.cloudinary.com/dyozzp82h/image/upload/v1780341159/Gemini_Generated_Image_ve6asxve6asxve6a-Photoroom_clfoi2.png" alt="WavesConverter" width="100%"/>
4
4
 
5
5
  <br/>
6
6
 
7
- <h1>
8
- <img src="https://img.shields.io/badge/WavesConverter-v1.2.0-7c3aed?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMiAxMyBRNCA3IDYgMTMgUTggMTkgMTAgMTMgUTEyIDcgMTQgMTMgUTE2IDE5IDE4IDEzIFEyMCA3IDIyIDEzIiBzdHJva2U9IiNjMDg0ZmMiIHN0cm9rZS13aWR0aD0iMi41IiBzdHJva2UtbGluZWNhcD0icm91bmQiLz48L3N2Zz4=" alt="WavesConverter v1.2.0"/>
9
- </h1>
7
+
10
8
 
11
9
  **Download anything. Convert everything.**
12
10
 
@@ -15,9 +13,9 @@ converts any media file — all offline, no account needed, completely free.
15
13
 
16
14
  <br/>
17
15
 
18
- [![Download](https://img.shields.io/badge/⬇_Download-macOS_&_Windows-7c3aed?style=for-the-badge)](https://idunnowhytf.github.io/wavesconvsite/#download)
19
- [![Website](https://img.shields.io/badge/🌐_Website-wavesconverter-a855f7?style=for-the-badge)](https://idunnowhytf.github.io/wavesconvsite/)
20
- [![Releases](https://img.shields.io/github/v/release/idunnowhytf/wavesconvsite?style=for-the-badge&color=d946ef&label=Latest)](https://github.com/idunnowhytf/wavesconvsite/releases)
16
+ [![Download](https://img.shields.io/badge/⬇_Download-macOS_&_Windows-7c3aed?style=for-the-badge)](https://idunnowhytf.github.io/WavesConverter/#download)
17
+ [![Website](https://img.shields.io/badge/🌐_Website-wavesconverter-a855f7?style=for-the-badge)](https://idunnowhytf.github.io/WavesConverter/)
18
+ [![Releases](https://img.shields.io/github/v/release/idunnowhytf/WavesConverter?style=for-the-badge&color=d946ef&label=Latest)](https://github.com/idunnowhytf/WavesConverter/releases)
21
19
  [![License](https://img.shields.io/badge/License-ISC-6d28d9?style=for-the-badge)](LICENSE)
22
20
 
23
21
  <br/>
@@ -48,9 +46,9 @@ converts any media file — all offline, no account needed, completely free.
48
46
 
49
47
  | Platform | Link |
50
48
  |---|---|
51
- | 🍎 **macOS Apple Silicon** (M1/M2/M3/M4) | [WavesConverter-arm64.dmg](https://github.com/idunnowhytf/wavesconvsite/releases/latest/download/WavesConverter-1.0.0-arm64.dmg) |
52
- | 🍎 **macOS Intel** (x64) | [WavesConverter.dmg](https://github.com/idunnowhytf/wavesconvsite/releases/latest/download/WavesConverter-1.0.0.dmg) |
53
- | 🪟 **Windows 10+** (x64) | [WavesConverter-Setup.exe](https://github.com/idunnowhytf/wavesconvsite/releases/latest/download/WavesConverter.Setup.1.0.0.exe) |
49
+ | 🍎 **macOS Apple Silicon** (M1/M2/M3/M4) | [WavesConverter-arm64.dmg](https://github.com/idunnowhytf/WavesConverter/releases/latest/download/WavesConverter-1.0.0-arm64.dmg) |
50
+ | 🍎 **macOS Intel** (x64) | [WavesConverter.dmg](https://github.com/idunnowhytf/WavesConverter/releases/latest/download/WavesConverter-1.0.0.dmg) |
51
+ | 🪟 **Windows 10+** (x64) | [WavesConverter-Setup.exe](https://github.com/idunnowhytf/WavesConverter/releases/latest/download/WavesConverter.Setup.1.0.0.exe) |
54
52
 
55
53
  > **Windows users:** SmartScreen may show a warning since the app isn't signed with a paid certificate.
56
54
  > Click **"More info" → "Run anyway"** to proceed. The source code is fully open and auditable here.
@@ -144,19 +142,12 @@ wavesconvsite/
144
142
 
145
143
  ## 📋 Changelog
146
144
 
147
- See [**Releases →**](https://github.com/idunnowhytf/wavesconvsite/releases) for full version history.
148
-
149
- | Version | Highlights |
150
- |---|---|
151
- | **v1.2.0** | Batch paste for multiple URLs, ETA & speed display on downloads |
152
- | **v1.1.0** | Download history tab, keyboard shortcuts, native notifications, drag & drop, Windows support |
153
- | **v1.0.0** | Initial release — macOS only |
145
+ See [**Releases →**](https://github.com/idunnowhytf/WavesConverter/releases) for full version history.
154
146
 
155
- ---
156
147
 
157
148
  ## 🤝 Contributing
158
149
 
159
- Found a bug or have a feature idea? [Open an issue](https://github.com/idunnowhytf/wavesconvsite/issues) — all feedback welcome.
150
+ Found a bug or have a feature idea? [Open an issue](https://github.com/idunnowhytf/WavesConverter/issues) — all feedback welcome.
160
151
 
161
152
  ---
162
153
 
@@ -170,7 +161,7 @@ This tool is intended for downloading content you own or have permission to down
170
161
 
171
162
  <div align="center">
172
163
 
173
- **[Website](https://idunnowhytf.github.io/wavesconvsite/) · [Releases](https://github.com/idunnowhytf/wavesconvsite/releases) · [Docs](https://idunnowhytf.github.io/wavesconvsite/docs.html) · [Changelog](https://idunnowhytf.github.io/wavesconvsite/changelog.html)**
164
+ **[Website](https://idunnowhytf.github.io/WavesConverter/) · [Releases](https://github.com/idunnowhytf/WavesConverter/releases) · [Docs](https://idunnowhytf.github.io/WavesConverter/docs.html) · [Changelog](https://idunnowhytf.github.io/WavesConverter/changelog.html)**
174
165
 
175
166
  <br/>
176
167
 
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
 
@@ -71,7 +72,7 @@ async function cmdInfo(url, flags, color) {
71
72
  spin.start();
72
73
  let items;
73
74
  try {
74
- items = await engine.fetchInfo(url);
75
+ items = await engine.fetchInfo(url, { cookiesPath: process.env.WAVESCONVERTER_COOKIES || '' });
75
76
  spin.succeed(`${items.length} element(ów)`);
76
77
  } catch (e) {
77
78
  spin.fail(e.message);
@@ -100,8 +101,8 @@ async function cmdInfo(url, flags, color) {
100
101
  }
101
102
 
102
103
  async function cmdDownload(url, flags, color) {
103
- if (!engine.isYouTubeUrl(url)) {
104
- throw new Error('Nieprawidłowy URL YouTube: ' + url);
104
+ if (!engine.isSupportedMediaUrl(url)) {
105
+ throw new Error('Nieobsługiwany URL. Użyj linku YouTube lub Instagram (post / Reel / Stories).');
105
106
  }
106
107
 
107
108
  const setupSpin = new ui.Spinner('Przygotowanie yt-dlp', color);
@@ -124,7 +125,7 @@ async function cmdDownload(url, flags, color) {
124
125
  const metaSpin = new ui.Spinner('Wczytywanie metadanych', color);
125
126
  metaSpin.start();
126
127
  try {
127
- const items = await engine.fetchInfo(url);
128
+ const items = await engine.fetchInfo(url, { cookiesPath: process.env.WAVESCONVERTER_COOKIES || '' });
128
129
  if (items[0]) title = items[0].title || items[0].id || title;
129
130
  metaSpin.succeed(title.slice(0, 52) + (title.length > 52 ? '…' : ''));
130
131
  } catch (_) {
@@ -135,6 +136,8 @@ async function cmdDownload(url, flags, color) {
135
136
  id: 'cli-' + Date.now(),
136
137
  title,
137
138
  url,
139
+ platform: engine.getMediaPlatform(url),
140
+ cookiesPath: process.env.WAVESCONVERTER_COOKIES || '',
138
141
  audioOnly,
139
142
  outputFormat: format,
140
143
  quality: flags.quality || 'best',
@@ -228,9 +231,22 @@ async function run(argv) {
228
231
  }
229
232
 
230
233
  const cmd = positional[0];
231
- if (!cmd) {
232
- ui.printHelp(VERSION, color);
233
- return 1;
234
+
235
+ // wavesconv — tryb interaktywny (wklej linki w konsoli)
236
+ if (!cmd || cmd === 'interactive' || cmd === 'i') {
237
+ if (!process.stdin.isTTY) {
238
+ ui.error(color, 'Tryb interaktywny wymaga terminala. Użyj: wavesconv download <url>');
239
+ return 1;
240
+ }
241
+ return runInteractive(
242
+ {
243
+ download: cmdDownload,
244
+ info: cmdInfo,
245
+ toolsInstall: cmdToolsInstall,
246
+ },
247
+ VERSION,
248
+ color
249
+ );
234
250
  }
235
251
 
236
252
  if (color && cmd !== 'tools') {
@@ -0,0 +1,278 @@
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
+ return engine.extractMediaUrls(text);
33
+ }
34
+
35
+ function printSession(color, session) {
36
+ const mode = session.audioOnly ? 'audio' : 'wideo';
37
+ const fmt = session.format;
38
+ ui.info(
39
+ color,
40
+ `Tryb: ${mode} · ${fmt} · ${session.quality} · folder: ${session.output}`
41
+ );
42
+ }
43
+
44
+ function printInteractiveHelp(color) {
45
+ console.log('');
46
+ ui.info(color, 'Wklej link YouTube i naciśnij Enter — pobieranie startuje automatycznie.');
47
+ console.log(ui.c(color, ui.palette.dim, ' Komendy:'));
48
+ const cmds = [
49
+ ['/help', 'Ta pomoc'],
50
+ ['/info <url>', 'Podgląd bez pobierania'],
51
+ ['/audio', 'Tryb audio (mp3 domyślnie)'],
52
+ ['/video', 'Tryb wideo (mp4 domyślnie)'],
53
+ ['/fmt mp3', 'Format: mp4, mp3, webm, mkv…'],
54
+ ['/q 1080p', 'Jakość: best, 1080p, 720p…'],
55
+ ['/folder', 'Zmień folder pobierania'],
56
+ ['/folder C:\\Videos', 'Ustaw folder od razu'],
57
+ ['/status', 'Narzędzia yt-dlp / ffmpeg'],
58
+ ['/install', 'Zainstaluj yt-dlp'],
59
+ ['/quit', 'Wyjście (lub Ctrl+C)'],
60
+ ];
61
+ for (const [cmd, desc] of cmds) {
62
+ console.log(' ' + ui.c(color, ui.palette.cyan, cmd.padEnd(22)) + ui.c(color, ui.palette.dim, desc));
63
+ }
64
+ console.log('');
65
+ }
66
+
67
+ async function runInteractive(handlers, version, color) {
68
+ const session = defaultSession();
69
+ let ytDlpReady = false;
70
+
71
+ ui.printBanner(version, color);
72
+ printSession(color, session);
73
+ printInteractiveHelp(color);
74
+
75
+ const promptText = color
76
+ ? ui.c(true, ui.palette.pink, '🔗 ') + ui.c(true, ui.palette.bold, 'Wklej link') + ui.c(true, ui.palette.dim, ' › ')
77
+ : 'link> ';
78
+
79
+ const rl = readline.createInterface({
80
+ input: process.stdin,
81
+ output: process.stdout,
82
+ terminal: true,
83
+ historySize: 100,
84
+ });
85
+
86
+ const ensureTools = async () => {
87
+ if (ytDlpReady && engine.findYtDlp()) return;
88
+ const spin = new ui.Spinner('Przygotowanie yt-dlp', color);
89
+ spin.start();
90
+ try {
91
+ await engine.ensureYtDlp(msg => spin.draw(msg));
92
+ spin.succeed('Gotowe');
93
+ ytDlpReady = true;
94
+ } catch (e) {
95
+ spin.fail(e.message);
96
+ throw e;
97
+ }
98
+ };
99
+
100
+ const askLine = (question) => new Promise(resolve => {
101
+ rl.question(question, answer => resolve(answer.trim()));
102
+ });
103
+
104
+ const processSlashCommand = async (line) => {
105
+ const parts = line.slice(1).trim().split(/\s+/);
106
+ const cmd = (parts[0] || '').toLowerCase();
107
+ const arg = parts.slice(1).join(' ');
108
+
109
+ switch (cmd) {
110
+ case 'help':
111
+ case 'h':
112
+ case '?':
113
+ printInteractiveHelp(color);
114
+ return;
115
+ case 'quit':
116
+ case 'exit':
117
+ case 'q':
118
+ ui.info(color, 'Do zobaczenia! 🌊');
119
+ rl.close();
120
+ return 'exit';
121
+ case 'audio':
122
+ case 'a':
123
+ session.audioOnly = true;
124
+ if (!['mp3', 'wav', 'flac', 'aac', 'm4a', 'ogg'].includes(session.format)) {
125
+ session.format = 'mp3';
126
+ }
127
+ ui.success(color, `Tryb audio · format: ${session.format}`);
128
+ printSession(color, session);
129
+ return;
130
+ case 'video':
131
+ case 'v':
132
+ session.audioOnly = false;
133
+ if (['mp3', 'wav', 'flac', 'aac', 'm4a', 'ogg'].includes(session.format)) {
134
+ session.format = 'mp4';
135
+ }
136
+ ui.success(color, `Tryb wideo · format: ${session.format}`);
137
+ printSession(color, session);
138
+ return;
139
+ case 'fmt':
140
+ case 'format':
141
+ case 'f':
142
+ if (!arg) {
143
+ ui.warn(color, 'Użyj: /fmt mp3 lub /fmt mp4');
144
+ return;
145
+ }
146
+ session.format = arg.toLowerCase();
147
+ if (['mp3', 'wav', 'flac', 'aac', 'm4a', 'ogg'].includes(session.format)) {
148
+ session.audioOnly = true;
149
+ }
150
+ ui.success(color, `Format: ${session.format}`);
151
+ printSession(color, session);
152
+ return;
153
+ case 'q':
154
+ case 'quality':
155
+ if (!arg) {
156
+ ui.warn(color, 'Użyj: /q 1080p lub /q best');
157
+ return;
158
+ }
159
+ session.quality = arg;
160
+ ui.success(color, `Jakość: ${session.quality}`);
161
+ printSession(color, session);
162
+ return;
163
+ case 'folder':
164
+ case 'o':
165
+ case 'output':
166
+ if (arg) {
167
+ const dir = path.resolve(arg);
168
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
169
+ session.output = dir;
170
+ ui.success(color, 'Folder: ' + dir);
171
+ } else {
172
+ const entered = await askLine(
173
+ color ? ui.c(true, ui.palette.cyan, 'Ścieżka folderu: ') : 'Folder: '
174
+ );
175
+ if (entered) {
176
+ const dir = path.resolve(entered);
177
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
178
+ session.output = dir;
179
+ ui.success(color, 'Folder: ' + dir);
180
+ }
181
+ }
182
+ printSession(color, session);
183
+ return;
184
+ case 'status':
185
+ ui.kv(color, 'yt-dlp', engine.findYtDlp() || 'BRAK', !!engine.findYtDlp());
186
+ ui.kv(color, 'ffmpeg', engine.findFfmpeg() || 'BRAK', !!engine.findFfmpeg());
187
+ return;
188
+ case 'install':
189
+ await handlers.toolsInstall(color);
190
+ ytDlpReady = true;
191
+ return;
192
+ case 'info':
193
+ case 'i': {
194
+ const url = arg || '';
195
+ if (!url || !engine.isSupportedMediaUrl(url)) {
196
+ ui.warn(color, 'Użyj: /info <url> (YouTube lub Instagram)');
197
+ return;
198
+ }
199
+ await handlers.info(url, {}, color);
200
+ return;
201
+ }
202
+ case 'clear':
203
+ console.clear();
204
+ ui.printBanner(version, color);
205
+ printSession(color, session);
206
+ return;
207
+ default:
208
+ ui.warn(color, `Nieznana komenda: /${cmd} — wpisz /help`);
209
+ }
210
+ };
211
+
212
+ rl.setPrompt(promptText);
213
+
214
+ return new Promise(resolve => {
215
+ rl.on('close', () => resolve(0));
216
+
217
+ const loop = () => {
218
+ rl.prompt();
219
+ };
220
+
221
+ rl.on('line', async line => {
222
+ const trimmed = line.trim();
223
+ rl.pause();
224
+
225
+ try {
226
+ if (!trimmed) {
227
+ rl.resume();
228
+ loop();
229
+ return;
230
+ }
231
+
232
+ if (trimmed.startsWith('/')) {
233
+ const result = await processSlashCommand(trimmed);
234
+ if (result === 'exit') {
235
+ resolve(0);
236
+ return;
237
+ }
238
+ rl.resume();
239
+ loop();
240
+ return;
241
+ }
242
+
243
+ const urls = extractYouTubeUrls(trimmed);
244
+ if (!urls.length) {
245
+ ui.warn(color, 'Nie wykryto linku YouTube/Instagram. Wklej URL lub wpisz /help');
246
+ rl.resume();
247
+ loop();
248
+ return;
249
+ }
250
+
251
+ await ensureTools();
252
+
253
+ for (let i = 0; i < urls.length; i++) {
254
+ if (urls.length > 1) {
255
+ ui.info(color, `Pobieranie ${i + 1}/${urls.length}`);
256
+ }
257
+ await handlers.download(urls[i], sessionToFlags(session), color);
258
+ }
259
+ } catch (e) {
260
+ ui.error(color, e.message || String(e));
261
+ }
262
+
263
+ rl.resume();
264
+ loop();
265
+ });
266
+
267
+ rl.on('SIGINT', () => {
268
+ console.log('');
269
+ ui.info(color, 'Wyjście (Ctrl+C)');
270
+ rl.close();
271
+ resolve(0);
272
+ });
273
+
274
+ loop();
275
+ });
276
+ }
277
+
278
+ 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/lib/engine.js CHANGED
@@ -3,6 +3,7 @@ const fs = require('fs');
3
3
  const os = require('os');
4
4
  const https = require('https');
5
5
  const { spawn, exec } = require('child_process');
6
+ const urls = require('./urls');
6
7
 
7
8
  const APP_FOLDER = 'waves-converter';
8
9
 
@@ -78,13 +79,25 @@ async function ensureYtDlp(onStatus) {
78
79
  return binFile;
79
80
  }
80
81
 
81
- function fetchInfo(url) {
82
+ function appendCookiesArgs(args, options = {}) {
83
+ const cookiesPath = options.cookiesPath;
84
+ if (cookiesPath && fs.existsSync(cookiesPath)) {
85
+ args.push('--cookies', cookiesPath);
86
+ }
87
+ }
88
+
89
+ function fetchInfo(url, options = {}) {
82
90
  const activeYtDlp = findYtDlp();
83
91
  if (!activeYtDlp) {
84
92
  return Promise.reject(new Error('yt-dlp nie znaleziony. Uruchom: wavesconv tools install'));
85
93
  }
94
+ if (!urls.isSupportedMediaUrl(url)) {
95
+ return Promise.reject(new Error('Nieobsługiwany link. Wklej URL YouTube lub Instagram (post, Reel, Stories).'));
96
+ }
86
97
  return new Promise((resolve, reject) => {
87
- const args = ['--dump-json', '--flat-playlist', '--no-warnings', url];
98
+ const args = ['--dump-json', '--flat-playlist', '--no-warnings'];
99
+ appendCookiesArgs(args, options);
100
+ args.push(url);
88
101
  let out = '';
89
102
  let err = '';
90
103
  const proc = spawn(activeYtDlp, args);
@@ -104,14 +117,20 @@ function fetchInfo(url) {
104
117
 
105
118
  function buildDownloadArgs(job) {
106
119
  const ffmpeg = findFfmpeg();
107
- const { url, outputFormat, quality, bitrate, outputDir, filename, audioOnly } = job;
120
+ const { url, outputFormat, quality, bitrate, outputDir, filename, audioOnly, cookiesPath } = job;
121
+ const platform = job.platform || urls.getMediaPlatform(url);
108
122
  const safeName = (filename || '%(title)s').replace(/[<>:"/\\|?*]/g, '_');
109
123
  const outTpl = path.join(outputDir, safeName + '.%(ext)s');
110
124
  const args = ['--no-warnings', '--newline'];
125
+ appendCookiesArgs(args, { cookiesPath });
111
126
  if (ffmpeg) args.push('--ffmpeg-location', path.dirname(ffmpeg));
112
127
  if (audioOnly) {
113
128
  args.push('-x', '--audio-format', outputFormat || 'mp3');
114
129
  if (bitrate) args.push('--audio-quality', bitrate.replace('k', '') + 'K');
130
+ } else if (platform === 'instagram') {
131
+ args.push('-f', 'best');
132
+ args.push('--merge-output-format', outputFormat || 'mp4');
133
+ if (bitrate) args.push('--postprocessor-args', `ffmpeg:-b:v ${bitrate}`);
115
134
  } else {
116
135
  const h = quality && quality !== 'best' ? quality.replace('p', '') : null;
117
136
  args.push('-f', h ? `bestvideo[height<=${h}]+bestaudio/best[height<=${h}]/best` : 'bestvideo+bestaudio/best');
@@ -282,7 +301,19 @@ function runConvert(job, callbacks = {}) {
282
301
  }
283
302
 
284
303
  function isYouTubeUrl(str) {
285
- return /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.be)\/.+/i.test((str || '').trim());
304
+ return urls.isYouTubeUrl(str);
305
+ }
306
+
307
+ function isSupportedMediaUrl(str) {
308
+ return urls.isSupportedMediaUrl(str);
309
+ }
310
+
311
+ function getMediaPlatform(str) {
312
+ return urls.getMediaPlatform(str);
313
+ }
314
+
315
+ function extractMediaUrls(text) {
316
+ return urls.extractMediaUrls(text);
286
317
  }
287
318
 
288
319
  function parseDeepLink(raw) {
@@ -343,6 +374,10 @@ module.exports = {
343
374
  runDownload,
344
375
  runConvert,
345
376
  isYouTubeUrl,
377
+ isSupportedMediaUrl,
378
+ getMediaPlatform,
379
+ extractMediaUrls,
380
+ getInstagramContentKind: urls.getInstagramContentKind,
346
381
  parseDeepLink,
347
382
  findDeepLinkInArgv,
348
383
  };
package/lib/urls.js ADDED
@@ -0,0 +1,67 @@
1
+ 'use strict';
2
+
3
+ const YOUTUBE_RE = /^(https?:\/\/)?(www\.)?(youtube\.com\/(watch(\?.*)?|shorts\/|playlist\?|embed\/|live\/)|youtu\.be\/|music\.youtube\.com\/)/i;
4
+ const INSTAGRAM_RE = /^(https?:\/\/)?(www\.)?instagram\.com\/(p\/|reel\/|reels\/|tv\/|stories\/)/i;
5
+ const INSTAGRAM_ANY_RE = /^(https?:\/\/)?(www\.)?instagram\.com\//i;
6
+
7
+ function normalizeUrl(str) {
8
+ return (str || '').trim().replace(/[)\]},.;]+$/g, '');
9
+ }
10
+
11
+ function getMediaPlatform(url) {
12
+ const u = normalizeUrl(url);
13
+ if (!u) return null;
14
+ if (YOUTUBE_RE.test(u) || /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.be)\/.+/i.test(u)) {
15
+ return 'youtube';
16
+ }
17
+ if (INSTAGRAM_ANY_RE.test(u) || /^(https?:\/\/)?(www\.)?instagr\.am\//i.test(u)) {
18
+ return 'instagram';
19
+ }
20
+ return null;
21
+ }
22
+
23
+ function isSupportedMediaUrl(url) {
24
+ return !!getMediaPlatform(url);
25
+ }
26
+
27
+ function isYouTubeUrl(url) {
28
+ return getMediaPlatform(url) === 'youtube';
29
+ }
30
+
31
+ function isInstagramUrl(url) {
32
+ return getMediaPlatform(url) === 'instagram';
33
+ }
34
+
35
+ function getInstagramContentKind(url) {
36
+ const u = normalizeUrl(url).toLowerCase();
37
+ if (u.includes('/stories/')) return 'story';
38
+ if (u.includes('/reel/') || u.includes('/reels/')) return 'reel';
39
+ if (u.includes('/p/') || u.includes('/tv/')) return 'post';
40
+ return 'post';
41
+ }
42
+
43
+ function extractMediaUrls(text) {
44
+ const found = new Set();
45
+ const raw = text || '';
46
+ const re = /https?:\/\/[^\s<>"']+/gi;
47
+ let m;
48
+ while ((m = re.exec(raw)) !== null) {
49
+ const cleaned = normalizeUrl(m[0]);
50
+ if (isSupportedMediaUrl(cleaned)) found.add(cleaned);
51
+ }
52
+ raw.split(/\s+/).filter(Boolean).forEach(part => {
53
+ const cleaned = normalizeUrl(part);
54
+ if (isSupportedMediaUrl(cleaned)) found.add(cleaned);
55
+ });
56
+ return [...found];
57
+ }
58
+
59
+ module.exports = {
60
+ normalizeUrl,
61
+ getMediaPlatform,
62
+ isSupportedMediaUrl,
63
+ isYouTubeUrl,
64
+ isInstagramUrl,
65
+ getInstagramContentKind,
66
+ extractMediaUrls,
67
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wavesconv",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "description": "CLI: pobieranie YouTube (yt-dlp) i konwersja mediów (ffmpeg) — WavesConverter",
5
5
  "main": "cli.js",
6
6
  "bin": {