wavesconv 1.6.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 +11 -20
- package/cli.js +6 -4
- package/lib/cli-interactive.js +4 -14
- package/lib/engine.js +39 -4
- package/lib/urls.js +67 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
<img src="https://
|
|
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
|
-
|
|
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
|
-
[](https://idunnowhytf.github.io/
|
|
19
|
-
[](https://idunnowhytf.github.io/
|
|
20
|
-
[](https://idunnowhytf.github.io/WavesConverter/#download)
|
|
17
|
+
[](https://idunnowhytf.github.io/WavesConverter/)
|
|
18
|
+
[](https://github.com/idunnowhytf/WavesConverter/releases)
|
|
21
19
|
[](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/
|
|
52
|
-
| 🍎 **macOS Intel** (x64) | [WavesConverter.dmg](https://github.com/idunnowhytf/
|
|
53
|
-
| 🪟 **Windows 10+** (x64) | [WavesConverter-Setup.exe](https://github.com/idunnowhytf/
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
@@ -72,7 +72,7 @@ async function cmdInfo(url, flags, color) {
|
|
|
72
72
|
spin.start();
|
|
73
73
|
let items;
|
|
74
74
|
try {
|
|
75
|
-
items = await engine.fetchInfo(url);
|
|
75
|
+
items = await engine.fetchInfo(url, { cookiesPath: process.env.WAVESCONVERTER_COOKIES || '' });
|
|
76
76
|
spin.succeed(`${items.length} element(ów)`);
|
|
77
77
|
} catch (e) {
|
|
78
78
|
spin.fail(e.message);
|
|
@@ -101,8 +101,8 @@ async function cmdInfo(url, flags, color) {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
async function cmdDownload(url, flags, color) {
|
|
104
|
-
if (!engine.
|
|
105
|
-
throw new Error('
|
|
104
|
+
if (!engine.isSupportedMediaUrl(url)) {
|
|
105
|
+
throw new Error('Nieobsługiwany URL. Użyj linku YouTube lub Instagram (post / Reel / Stories).');
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
const setupSpin = new ui.Spinner('Przygotowanie yt-dlp', color);
|
|
@@ -125,7 +125,7 @@ async function cmdDownload(url, flags, color) {
|
|
|
125
125
|
const metaSpin = new ui.Spinner('Wczytywanie metadanych', color);
|
|
126
126
|
metaSpin.start();
|
|
127
127
|
try {
|
|
128
|
-
const items = await engine.fetchInfo(url);
|
|
128
|
+
const items = await engine.fetchInfo(url, { cookiesPath: process.env.WAVESCONVERTER_COOKIES || '' });
|
|
129
129
|
if (items[0]) title = items[0].title || items[0].id || title;
|
|
130
130
|
metaSpin.succeed(title.slice(0, 52) + (title.length > 52 ? '…' : ''));
|
|
131
131
|
} catch (_) {
|
|
@@ -136,6 +136,8 @@ async function cmdDownload(url, flags, color) {
|
|
|
136
136
|
id: 'cli-' + Date.now(),
|
|
137
137
|
title,
|
|
138
138
|
url,
|
|
139
|
+
platform: engine.getMediaPlatform(url),
|
|
140
|
+
cookiesPath: process.env.WAVESCONVERTER_COOKIES || '',
|
|
139
141
|
audioOnly,
|
|
140
142
|
outputFormat: format,
|
|
141
143
|
quality: flags.quality || 'best',
|
package/lib/cli-interactive.js
CHANGED
|
@@ -29,17 +29,7 @@ function sessionToFlags(session) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
function extractYouTubeUrls(text) {
|
|
32
|
-
|
|
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];
|
|
32
|
+
return engine.extractMediaUrls(text);
|
|
43
33
|
}
|
|
44
34
|
|
|
45
35
|
function printSession(color, session) {
|
|
@@ -202,8 +192,8 @@ async function runInteractive(handlers, version, color) {
|
|
|
202
192
|
case 'info':
|
|
203
193
|
case 'i': {
|
|
204
194
|
const url = arg || '';
|
|
205
|
-
if (!url || !engine.
|
|
206
|
-
ui.warn(color, 'Użyj: /info <url>');
|
|
195
|
+
if (!url || !engine.isSupportedMediaUrl(url)) {
|
|
196
|
+
ui.warn(color, 'Użyj: /info <url> (YouTube lub Instagram)');
|
|
207
197
|
return;
|
|
208
198
|
}
|
|
209
199
|
await handlers.info(url, {}, color);
|
|
@@ -252,7 +242,7 @@ async function runInteractive(handlers, version, color) {
|
|
|
252
242
|
|
|
253
243
|
const urls = extractYouTubeUrls(trimmed);
|
|
254
244
|
if (!urls.length) {
|
|
255
|
-
ui.warn(color, 'Nie wykryto linku YouTube. Wklej URL lub wpisz /help');
|
|
245
|
+
ui.warn(color, 'Nie wykryto linku YouTube/Instagram. Wklej URL lub wpisz /help');
|
|
256
246
|
rl.resume();
|
|
257
247
|
loop();
|
|
258
248
|
return;
|
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
|
|
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'
|
|
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
|
|
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
|
+
};
|