flux-dl 1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 flux-dl
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,454 @@
1
+ # flux-dl
2
+
3
+ Eine leistungsstarke Node.js Library zum Downloaden von Videos von YouTube, Vimeo und Dailymotion - komplett in JavaScript, keine externen Binaries!
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install flux-dl
9
+ ```
10
+
11
+ ## Features
12
+
13
+ ✅ YouTube Videos & Audio downloaden (ohne yt-dlp!)
14
+ ✅ YouTube Suche - Song-Namen eingeben statt URLs!
15
+ ✅ Vimeo Videos downloaden
16
+ ✅ Dailymotion Videos downloaden
17
+ ✅ Automatische Format-Auswahl
18
+ ✅ Progress-Tracking
19
+ ✅ Cookie-Support für authentifizierte Videos
20
+ ✅ Perfekt für Bots (WhatsApp, Discord, Telegram)
21
+ ✅ Komplett in JavaScript - keine externen Binaries
22
+
23
+ ## API Dokumentation
24
+
25
+ ### YouTubeSearch
26
+
27
+ Suche nach Videos ohne URL - perfekt für Bots!
28
+
29
+ #### Constructor
30
+
31
+ ```javascript
32
+ const { YouTubeSearch } = require('flux-dl');
33
+
34
+ const search = new YouTubeSearch();
35
+ ```
36
+
37
+ #### Methoden
38
+
39
+ ##### `search(query, maxResults)`
40
+
41
+ Sucht nach Videos auf YouTube.
42
+
43
+ ```javascript
44
+ const results = await search.search('Rick Astley Never Gonna Give You Up', 5);
45
+
46
+ results.forEach(video => {
47
+ console.log(video.title);
48
+ console.log(video.url);
49
+ console.log(video.author);
50
+ });
51
+ ```
52
+
53
+ **Response:**
54
+ ```javascript
55
+ [
56
+ {
57
+ videoId: "dQw4w9WgXcQ",
58
+ title: "Rick Astley - Never Gonna Give You Up",
59
+ url: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
60
+ thumbnail: "https://...",
61
+ duration: 213,
62
+ author: "Rick Astley",
63
+ views: "1.7B views",
64
+ publishedTime: "15 years ago"
65
+ }
66
+ ]
67
+ ```
68
+
69
+ ##### `searchAndDownload(query, downloader, options)`
70
+
71
+ Sucht und lädt direkt das erste Ergebnis herunter.
72
+
73
+ ```javascript
74
+ const { VideoDownloader, YouTubeSearch } = require('flux-dl');
75
+
76
+ const downloader = new VideoDownloader();
77
+ const search = new YouTubeSearch();
78
+
79
+ const result = await search.searchAndDownload(
80
+ 'Rick Astley Never Gonna Give You Up',
81
+ downloader,
82
+ {
83
+ audioOnly: true,
84
+ outputPath: './downloads'
85
+ }
86
+ );
87
+
88
+ console.log(result.filepath);
89
+ ```
90
+
91
+ ### VideoDownloader
92
+
93
+ Die Hauptklasse für Video-Downloads.
94
+
95
+ #### Constructor
96
+
97
+ ```javascript
98
+ const { VideoDownloader } = require('flux-dl');
99
+
100
+ const downloader = new VideoDownloader({
101
+ userAgent: 'Mozilla/5.0 ...', // Optional
102
+ timeout: 30000, // Optional
103
+ encryptionKey: 'your-key' // Optional
104
+ });
105
+ ```
106
+
107
+ #### Methoden
108
+
109
+ ##### `getVideoInfo(url)`
110
+
111
+ Extrahiert Video-Informationen ohne Download.
112
+
113
+ ```javascript
114
+ const info = await downloader.getVideoInfo('https://youtube.com/watch?v=...');
115
+
116
+ console.log(info.title); // Video-Titel
117
+ console.log(info.author); // Autor/Kanal
118
+ console.log(info.duration); // Dauer in Sekunden
119
+ console.log(info.quality); // Qualität (z.B. "1080p")
120
+ console.log(info.videoUrl); // Download-URL
121
+ console.log(info.allFormats); // Alle verfügbaren Formate
122
+ ```
123
+
124
+ **Response:**
125
+ ```javascript
126
+ {
127
+ title: "Video Titel",
128
+ videoId: "dQw4w9WgXcQ",
129
+ duration: 213,
130
+ thumbnail: "https://...",
131
+ author: "Kanal Name",
132
+ viewCount: 1742252685,
133
+ videoUrl: "https://...",
134
+ quality: "360p",
135
+ format: { /* Format-Details */ },
136
+ allFormats: [ /* Array aller Formate */ ],
137
+ platform: "YouTube"
138
+ }
139
+ ```
140
+
141
+ ##### `download(url, outputPath)`
142
+
143
+ Lädt ein Video herunter.
144
+
145
+ ```javascript
146
+ const result = await downloader.download(
147
+ 'https://youtube.com/watch?v=...',
148
+ './downloads' // Optional, default: './downloads'
149
+ );
150
+
151
+ console.log(result.filepath); // Pfad zur heruntergeladenen Datei
152
+ console.log(result.info); // Video-Informationen
153
+ ```
154
+
155
+ **Response:**
156
+ ```javascript
157
+ {
158
+ filepath: "./downloads/video_titel.mp3",
159
+ info: { /* Video-Info Objekt */ }
160
+ }
161
+ ```
162
+
163
+ ##### `downloadAudio(url, outputPath)`
164
+
165
+ Lädt nur die Audio-Spur herunter.
166
+
167
+ ```javascript
168
+ const result = await downloader.downloadAudio(
169
+ 'https://youtube.com/watch?v=...',
170
+ './downloads'
171
+ );
172
+
173
+ console.log(result.filepath); // Pfad zur Audio-Datei
174
+ console.log(result.format); // Audio-Format Details
175
+ ```
176
+
177
+ ## Beispiele
178
+
179
+ ### YouTube Suche (NEU!)
180
+
181
+ ```javascript
182
+ const { VideoDownloader, YouTubeSearch } = require('flux-dl');
183
+
184
+ async function searchAndDownload() {
185
+ const downloader = new VideoDownloader();
186
+ const search = new YouTubeSearch();
187
+
188
+ // Suche nach Song-Name
189
+ const results = await search.search('Rick Astley', 3);
190
+
191
+ console.log('Gefundene Videos:');
192
+ results.forEach((video, i) => {
193
+ console.log(`${i + 1}. ${video.title} - ${video.author}`);
194
+ });
195
+
196
+ // Direkt suchen und downloaden
197
+ const result = await search.searchAndDownload(
198
+ 'Rick Astley Never Gonna Give You Up',
199
+ downloader,
200
+ { audioOnly: true }
201
+ );
202
+
203
+ console.log('✓ Downloaded:', result.filepath);
204
+ }
205
+
206
+ searchAndDownload();
207
+ ```
208
+
209
+ ### Einfacher Download
210
+
211
+ ```javascript
212
+ const { VideoDownloader } = require('flux-dl');
213
+
214
+ async function downloadVideo() {
215
+ const downloader = new VideoDownloader();
216
+
217
+ try {
218
+ const result = await downloader.download(
219
+ 'https://youtube.com/watch?v=dQw4w9WgXcQ'
220
+ );
221
+ console.log('✓ Video gespeichert:', result.filepath);
222
+ } catch (error) {
223
+ console.error('Fehler:', error.message);
224
+ }
225
+ }
226
+
227
+ downloadVideo();
228
+ ```
229
+
230
+ ### Video-Info abrufen
231
+
232
+ ```javascript
233
+ const { VideoDownloader } = require('flux-dl');
234
+
235
+ async function getInfo() {
236
+ const downloader = new VideoDownloader();
237
+
238
+ const info = await downloader.getVideoInfo(
239
+ 'https://youtube.com/watch?v=dQw4w9WgXcQ'
240
+ );
241
+
242
+ console.log(`Titel: ${info.title}`);
243
+ console.log(`Autor: ${info.author}`);
244
+ console.log(`Dauer: ${info.duration}s`);
245
+ console.log(`Qualität: ${info.quality}`);
246
+ console.log(`Verfügbare Formate: ${info.allFormats.length}`);
247
+ }
248
+
249
+ getInfo();
250
+ ```
251
+
252
+ ### Nur Audio downloaden
253
+
254
+ ```javascript
255
+ const { VideoDownloader } = require('flux-dl');
256
+
257
+ async function downloadAudio() {
258
+ const downloader = new VideoDownloader();
259
+
260
+ const result = await downloader.downloadAudio(
261
+ 'https://youtube.com/watch?v=dQw4w9WgXcQ',
262
+ './music'
263
+ );
264
+
265
+ console.log('✓ Audio gespeichert:', result.filepath);
266
+ }
267
+
268
+ downloadAudio();
269
+ ```
270
+
271
+ ### Mit Progress-Tracking
272
+
273
+ ```javascript
274
+ const { VideoDownloader } = require('flux-dl');
275
+
276
+ async function downloadWithProgress() {
277
+ const downloader = new VideoDownloader();
278
+
279
+ // Info abrufen
280
+ const info = await downloader.getVideoInfo(
281
+ 'https://youtube.com/watch?v=dQw4w9WgXcQ'
282
+ );
283
+
284
+ console.log(`Starte Download: ${info.title}`);
285
+
286
+ // Download mit Progress
287
+ const result = await downloader.download(info.videoUrl);
288
+
289
+ console.log('✓ Fertig!');
290
+ }
291
+
292
+ downloadWithProgress();
293
+ ```
294
+
295
+ ### Mehrere Videos downloaden
296
+
297
+ ```javascript
298
+ const { VideoDownloader } = require('flux-dl');
299
+
300
+ async function downloadMultiple() {
301
+ const downloader = new VideoDownloader();
302
+
303
+ const urls = [
304
+ 'https://youtube.com/watch?v=...',
305
+ 'https://vimeo.com/...',
306
+ 'https://dailymotion.com/video/...'
307
+ ];
308
+
309
+ for (const url of urls) {
310
+ try {
311
+ const result = await downloader.download(url);
312
+ console.log('✓', result.info.title);
313
+ } catch (error) {
314
+ console.error('✗', url, error.message);
315
+ }
316
+ }
317
+ }
318
+
319
+ downloadMultiple();
320
+ ```
321
+
322
+ ### Für WhatsApp/Discord Bots
323
+
324
+ ```javascript
325
+ const { VideoDownloader, YouTubeSearch } = require('flux-dl');
326
+
327
+ // WhatsApp Bot Beispiel
328
+ bot.on('message', async (msg) => {
329
+ if (msg.body.startsWith('!song ')) {
330
+ const songName = msg.body.replace('!song ', '');
331
+
332
+ const downloader = new VideoDownloader();
333
+ const search = new YouTubeSearch();
334
+
335
+ try {
336
+ msg.reply('🔍 Suche...');
337
+
338
+ const result = await search.searchAndDownload(
339
+ songName,
340
+ downloader,
341
+ { audioOnly: true }
342
+ );
343
+
344
+ msg.reply('✓ Fertig!');
345
+ await bot.sendFile(msg.from, result.filepath);
346
+ } catch (error) {
347
+ msg.reply('❌ Fehler: ' + error.message);
348
+ }
349
+ }
350
+ });
351
+ ```
352
+
353
+ ### Spezifisches Format wählen
354
+
355
+ ```javascript
356
+ const { VideoDownloader } = require('flux-dl');
357
+
358
+ async function downloadSpecificFormat() {
359
+ const downloader = new VideoDownloader();
360
+
361
+ // Alle Formate abrufen
362
+ const info = await downloader.getVideoInfo(
363
+ 'https://youtube.com/watch?v=dQw4w9WgXcQ'
364
+ );
365
+
366
+ // Bestes 1080p Format finden
367
+ const format1080p = info.allFormats.find(f =>
368
+ f.qualityLabel === '1080p' && f.mimeType.includes('video')
369
+ );
370
+
371
+ if (format1080p) {
372
+ // Direkt mit Format-URL downloaden
373
+ const fs = require('fs');
374
+ const stream = await downloader.spoofing.downloadStream(
375
+ format1080p.url,
376
+ `https://youtube.com/watch?v=${info.videoId}`,
377
+ (percent) => console.log(`Progress: ${percent}%`)
378
+ );
379
+
380
+ const writer = fs.createWriteStream('./video_1080p.mp4');
381
+ stream.pipe(writer);
382
+
383
+ await new Promise((resolve, reject) => {
384
+ writer.on('finish', resolve);
385
+ writer.on('error', reject);
386
+ });
387
+ }
388
+ }
389
+
390
+ downloadSpecificFormat();
391
+ ```
392
+
393
+ ## Unterstützte Plattformen
394
+
395
+ ```javascript
396
+ const { supportedPlatforms } = require('flux-dl');
397
+
398
+ console.log(supportedPlatforms);
399
+ // ['YouTube', 'Vimeo', 'Dailymotion']
400
+ ```
401
+
402
+ ## Cookie-Support
403
+
404
+ Für Videos die Authentifizierung benötigen, kannst du eine `cookies.txt` Datei im Netscape-Format verwenden:
405
+
406
+ 1. Exportiere Cookies mit Browser-Extension (z.B. "Get cookies.txt")
407
+ 2. Speichere als `cookies.txt` im Projekt-Root
408
+ 3. Die Library lädt sie automatisch
409
+
410
+ ## Error Handling
411
+
412
+ ```javascript
413
+ const { VideoDownloader } = require('flux-dl');
414
+
415
+ async function safeDownload(url) {
416
+ const downloader = new VideoDownloader();
417
+
418
+ try {
419
+ const result = await downloader.download(url);
420
+ return { success: true, filepath: result.filepath };
421
+ } catch (error) {
422
+ if (error.message.includes('403')) {
423
+ return { success: false, error: 'Video nicht verfügbar oder geschützt' };
424
+ } else if (error.message.includes('Platform not supported')) {
425
+ return { success: false, error: 'Plattform nicht unterstützt' };
426
+ } else {
427
+ return { success: false, error: error.message };
428
+ }
429
+ }
430
+ }
431
+ ```
432
+
433
+ ## Technische Details
434
+
435
+ - **Keine externen Binaries**: Komplett in JavaScript implementiert
436
+ - **YouTube InnerTube API**: Nutzt die offizielle (undokumentierte) API
437
+ - **Signatur-Entschlüsselung**: Automatische Entschlüsselung von geschützten URLs
438
+ - **Browser-Emulation**: Simuliert echte Browser-Requests
439
+ - **Multi-Platform**: Unterstützt YouTube, Vimeo und Dailymotion
440
+
441
+ ## Dependencies
442
+
443
+ - `axios` - HTTP-Requests
444
+ - `cheerio` - HTML-Parsing (für Vimeo/Dailymotion)
445
+ - `m3u8-parser` - HLS-Stream-Parsing
446
+
447
+ ## Lizenz
448
+
449
+ MIT
450
+
451
+ ## Support
452
+
453
+ Bei Problemen oder Fragen, erstelle ein Issue auf GitHub.
454
+
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "flux-dl",
3
+ "version": "1.0.0",
4
+ "description": "Leistungsstarke Video-Downloader Library für YouTube, Vimeo und Dailymotion - komplett in JavaScript, keine externen Binaries",
5
+ "main": "src/index.js",
6
+ "scripts": {
7
+ "test": "node examples/test.js",
8
+ "test-encryption": "node examples/encryption-test.js",
9
+ "test-platforms": "node examples/test-platforms.js"
10
+ },
11
+ "keywords": [
12
+ "video",
13
+ "download",
14
+ "youtube",
15
+ "vimeo",
16
+ "dailymotion",
17
+ "streaming",
18
+ "downloader",
19
+ "video-downloader",
20
+ "youtube-downloader",
21
+ "youtube-dl",
22
+ "yt-dlp",
23
+ "no-binary",
24
+ "flux",
25
+ "flux-dl"
26
+ ],
27
+ "author": "Your Name <your.email@example.com>",
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/yourusername/flux-dl.git"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/yourusername/flux-dl/issues"
35
+ },
36
+ "homepage": "https://github.com/yourusername/flux-dl#readme",
37
+ "engines": {
38
+ "node": ">=14.0.0"
39
+ },
40
+ "dependencies": {
41
+ "axios": "^1.6.0",
42
+ "cheerio": "^1.0.0-rc.12",
43
+ "m3u8-parser": "^7.1.0"
44
+ },
45
+ "files": [
46
+ "src/",
47
+ "README.md",
48
+ "LICENSE"
49
+ ]
50
+ }
@@ -0,0 +1,197 @@
1
+ const axios = require('axios');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const platforms = require('./platforms');
5
+ const VideoEncryption = require('./utils/encryption');
6
+ const RequestSpoofing = require('./utils/requestSpoofing');
7
+
8
+ class VideoDownloader {
9
+ constructor(options = {}) {
10
+ this.options = {
11
+ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
12
+ timeout: 30000,
13
+ encryptionKey: options.encryptionKey || 'default-key',
14
+ ...options
15
+ };
16
+
17
+ this.encryption = new VideoEncryption(this.options.encryptionKey);
18
+ this.spoofing = new RequestSpoofing();
19
+ }
20
+
21
+ async getVideoInfo(url) {
22
+ const platform = this.detectPlatform(url);
23
+
24
+ if (!platform) {
25
+ throw new Error('Platform not supported');
26
+ }
27
+
28
+ return await platform.extractInfo(url, this.options);
29
+ }
30
+
31
+ async download(url, outputPath = './downloads') {
32
+ const info = await this.getVideoInfo(url);
33
+
34
+ if (!fs.existsSync(outputPath)) {
35
+ fs.mkdirSync(outputPath, { recursive: true });
36
+ }
37
+
38
+ const filename = this.sanitizeFilename(info.title) + '.mp3'; // Geändert zu .mp3
39
+ const filepath = path.join(outputPath, filename);
40
+
41
+ console.log(`Downloading: ${info.title}`);
42
+ console.log(`Quality: ${info.quality}`);
43
+
44
+ // Nutze Browser Emulation für Download
45
+ const platform = this.detectPlatform(url);
46
+ const referer = info.videoId ? `https://www.youtube.com/watch?v=${info.videoId}` : null;
47
+ const writer = fs.createWriteStream(filepath);
48
+
49
+ // Für YouTube: Nutze browser statt spoofing
50
+ const downloader = platform && platform.browser ? platform.browser : this.spoofing;
51
+
52
+ const stream = await downloader.downloadStream(info.videoUrl, referer, (percent, downloaded, total) => {
53
+ if (total) {
54
+ process.stdout.write(`\rProgress: ${percent}% (${Math.round(downloaded / 1024 / 1024)}MB / ${Math.round(total / 1024 / 1024)}MB)`);
55
+ } else {
56
+ process.stdout.write(`\rProgress: ${percent}%`);
57
+ }
58
+ });
59
+
60
+ stream.pipe(writer);
61
+
62
+ await new Promise((resolve, reject) => {
63
+ writer.on('finish', resolve);
64
+ writer.on('error', reject);
65
+ });
66
+
67
+ console.log('\n');
68
+
69
+ return { filepath, info };
70
+ }
71
+
72
+ async downloadAudio(url, outputPath = './downloads') {
73
+ const info = await this.getVideoInfo(url);
74
+
75
+ // Audio-Format auswählen
76
+ const audioFormat = this.selectAudioFormat(info.allFormats);
77
+
78
+ if (!audioFormat) {
79
+ throw new Error('No audio format available');
80
+ }
81
+
82
+ if (!fs.existsSync(outputPath)) {
83
+ fs.mkdirSync(outputPath, { recursive: true });
84
+ }
85
+
86
+ const ext = audioFormat.mimeType.includes('mp4') ? '.m4a' : '.webm';
87
+ const filename = this.sanitizeFilename(info.title) + ext;
88
+ const filepath = path.join(outputPath, filename);
89
+
90
+ console.log(`Downloading audio: ${info.title}`);
91
+ console.log(`Format: ${audioFormat.mimeType}`);
92
+
93
+ // Nutze Request Spoofing
94
+ const referer = `https://www.youtube.com/watch?v=${info.videoId}`;
95
+ const writer = fs.createWriteStream(filepath);
96
+
97
+ const stream = await this.spoofing.downloadStream(audioFormat.url, referer, (percent) => {
98
+ process.stdout.write(`\rProgress: ${percent}%`);
99
+ });
100
+
101
+ stream.pipe(writer);
102
+
103
+ await new Promise((resolve, reject) => {
104
+ writer.on('finish', resolve);
105
+ writer.on('error', reject);
106
+ });
107
+
108
+ console.log('\n');
109
+
110
+ return { filepath, info, format: audioFormat };
111
+ }
112
+
113
+ selectAudioFormat(formats) {
114
+ // Nutze InnerTube Helper falls verfügbar
115
+ if (this.detectPlatform && formats.length > 0) {
116
+ const platform = this.detectPlatform(formats[0].url || '');
117
+ if (platform && platform.innerTube) {
118
+ return platform.innerTube.selectBestAudio(formats);
119
+ }
120
+ }
121
+
122
+ // Nur Audio-Formate
123
+ const audioFormats = formats.filter(f =>
124
+ f.mimeType && f.mimeType.includes('audio') && f.url
125
+ );
126
+
127
+ if (audioFormats.length === 0) return null;
128
+
129
+ // Sortiere nach Bitrate (höher = besser)
130
+ audioFormats.sort((a, b) => (b.bitrate || 0) - (a.bitrate || 0));
131
+
132
+ return audioFormats[0];
133
+ }
134
+
135
+ async downloadStream(url, filepath, onProgress) {
136
+ // Prüfe ob URL verschlüsselt ist
137
+ if (url.startsWith('encrypted://')) {
138
+ url = this.encryption.decryptUrl(url);
139
+ }
140
+
141
+ const writer = fs.createWriteStream(filepath);
142
+
143
+ const response = await axios({
144
+ url,
145
+ method: 'GET',
146
+ responseType: 'stream',
147
+ headers: {
148
+ 'User-Agent': this.options.userAgent
149
+ },
150
+ httpsAgent: new (require('https').Agent)({
151
+ rejectUnauthorized: false
152
+ })
153
+ });
154
+
155
+ const totalLength = response.headers['content-length'];
156
+ let downloadedLength = 0;
157
+
158
+ response.data.on('data', (chunk) => {
159
+ downloadedLength += chunk.length;
160
+ if (onProgress && totalLength) {
161
+ const percent = Math.round((downloadedLength / totalLength) * 100);
162
+ onProgress(percent);
163
+ }
164
+ });
165
+
166
+ response.data.pipe(writer);
167
+
168
+ return new Promise((resolve, reject) => {
169
+ writer.on('finish', resolve);
170
+ writer.on('error', reject);
171
+ });
172
+ }
173
+
174
+ detectPlatform(url) {
175
+ for (const [name, platform] of Object.entries(platforms)) {
176
+ if (platform.canHandle(url)) {
177
+ return platform;
178
+ }
179
+ }
180
+ return null;
181
+ }
182
+
183
+ sanitizeFilename(filename) {
184
+ return filename.replace(/[^a-z0-9]/gi, '_').toLowerCase();
185
+ }
186
+
187
+ // Verschlüsselungs-Utilities exportieren
188
+ encryptUrl(url, expiresIn = 3600) {
189
+ return this.encryption.encryptUrl(url, expiresIn);
190
+ }
191
+
192
+ signUrl(url, expiresIn = 3600) {
193
+ return this.encryption.signUrl(url, expiresIn);
194
+ }
195
+ }
196
+
197
+ module.exports = VideoDownloader;