flux-dl 1.1.5 → 1.1.7
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/package.json +1 -1
- package/src/VideoDownloader.js +10 -1
- package/src/platforms/youtube.js +83 -10
- package/src/utils/browserEmulation.js +1 -2
package/package.json
CHANGED
package/src/VideoDownloader.js
CHANGED
|
@@ -166,8 +166,17 @@ class VideoDownloader {
|
|
|
166
166
|
// Nutze Browser Emulation mit Cookies
|
|
167
167
|
const referer = `https://www.youtube.com/watch?v=${info.videoId}`;
|
|
168
168
|
|
|
169
|
+
// Wrap onProgress to catch errors
|
|
170
|
+
const safeOnProgress = onProgress ? (percent, downloaded, total) => {
|
|
171
|
+
try {
|
|
172
|
+
onProgress(percent, downloaded, total);
|
|
173
|
+
} catch (error) {
|
|
174
|
+
// Silently ignore progress callback errors
|
|
175
|
+
}
|
|
176
|
+
} : null;
|
|
177
|
+
|
|
169
178
|
try {
|
|
170
|
-
const rawStream = await this.browser.downloadStream(info.videoUrl, referer,
|
|
179
|
+
const rawStream = await this.browser.downloadStream(info.videoUrl, referer, safeOnProgress);
|
|
171
180
|
|
|
172
181
|
// Create ULTRA-RESILIENT wrapper stream
|
|
173
182
|
const { PassThrough } = require('stream');
|
package/src/platforms/youtube.js
CHANGED
|
@@ -18,7 +18,7 @@ module.exports = {
|
|
|
18
18
|
throw new Error('Invalid YouTube URL');
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
console.log(`\n=== Extracting YouTube Video: ${videoId} ===`);
|
|
21
|
+
// console.log(`\n=== Extracting YouTube Video: ${videoId} ===`);
|
|
22
22
|
|
|
23
23
|
// Starte Browser-Session (wie echter User)
|
|
24
24
|
await this.browser.startSession();
|
|
@@ -26,9 +26,12 @@ module.exports = {
|
|
|
26
26
|
// Methode 1: InnerTube API mit verschiedenen Clients
|
|
27
27
|
const clientPriority = ['android', 'ios', 'tvEmbedded', 'web'];
|
|
28
28
|
|
|
29
|
+
// Get target quality from options
|
|
30
|
+
const targetQuality = options?.quality || null;
|
|
31
|
+
|
|
29
32
|
for (const clientType of clientPriority) {
|
|
30
33
|
try {
|
|
31
|
-
console.log(`[InnerTube] Trying ${clientType} client...`);
|
|
34
|
+
// console.log(`[InnerTube] Trying ${clientType} client...`);
|
|
32
35
|
const data = await this.innerTube.getVideoInfo(videoId, clientType);
|
|
33
36
|
|
|
34
37
|
if (data.videoDetails && data.streamingData) {
|
|
@@ -41,7 +44,12 @@ module.exports = {
|
|
|
41
44
|
if (formatsWithUrl.length > 0) {
|
|
42
45
|
console.log(`✓ ${clientType} client success! ${formatsWithUrl.length} formats available`);
|
|
43
46
|
|
|
44
|
-
|
|
47
|
+
// Select format with target quality
|
|
48
|
+
const bestFormat = this.selectBestFormat(formatsWithUrl, targetQuality);
|
|
49
|
+
|
|
50
|
+
// Get actual bitrate for quality label
|
|
51
|
+
const actualBitrate = this.getBitrate(bestFormat);
|
|
52
|
+
const qualityLabel = `${actualBitrate}kbps`;
|
|
45
53
|
|
|
46
54
|
// Get highest quality thumbnail (last one in array)
|
|
47
55
|
const thumbnails = data.videoDetails.thumbnail.thumbnails;
|
|
@@ -58,7 +66,8 @@ module.exports = {
|
|
|
58
66
|
likeCount: parseInt(data.videoDetails.likeCount || 0),
|
|
59
67
|
uploadDate: data.videoDetails.uploadDate || data.videoDetails.publishDate || null,
|
|
60
68
|
videoUrl: bestFormat.url,
|
|
61
|
-
quality:
|
|
69
|
+
quality: qualityLabel,
|
|
70
|
+
bitrate: actualBitrate,
|
|
62
71
|
format: bestFormat,
|
|
63
72
|
allFormats: formatsWithUrl,
|
|
64
73
|
platform: this.name,
|
|
@@ -128,8 +137,14 @@ module.exports = {
|
|
|
128
137
|
throw new Error('No valid formats found');
|
|
129
138
|
}
|
|
130
139
|
|
|
131
|
-
|
|
132
|
-
|
|
140
|
+
// Select format with target quality (already declared above)
|
|
141
|
+
const bestFormat = this.selectBestFormat(validFormats, targetQuality);
|
|
142
|
+
|
|
143
|
+
// Get actual bitrate for quality label
|
|
144
|
+
const actualBitrate = this.getBitrate(bestFormat);
|
|
145
|
+
const qualityLabel = `${actualBitrate}kbps`;
|
|
146
|
+
|
|
147
|
+
console.log(`Selected: ${qualityLabel} quality\n`);
|
|
133
148
|
|
|
134
149
|
// Get highest quality thumbnail (last one in array)
|
|
135
150
|
const thumbnails = videoDetails.thumbnail.thumbnails;
|
|
@@ -146,7 +161,8 @@ module.exports = {
|
|
|
146
161
|
likeCount: parseInt(videoDetails.likeCount || 0),
|
|
147
162
|
uploadDate: videoDetails.uploadDate || videoDetails.publishDate || null,
|
|
148
163
|
videoUrl: bestFormat.url,
|
|
149
|
-
quality:
|
|
164
|
+
quality: qualityLabel,
|
|
165
|
+
bitrate: actualBitrate,
|
|
150
166
|
format: bestFormat,
|
|
151
167
|
allFormats: validFormats,
|
|
152
168
|
platform: this.name,
|
|
@@ -429,17 +445,49 @@ module.exports = {
|
|
|
429
445
|
return res.data;
|
|
430
446
|
},
|
|
431
447
|
|
|
432
|
-
selectBestFormat(formats) {
|
|
433
|
-
//
|
|
448
|
+
selectBestFormat(formats, targetQuality = null) {
|
|
449
|
+
// WICHTIG: Nutze IMMER Video+Audio Formate (keine 403 Errors!)
|
|
450
|
+
// Audio-only Formate geben oft 403 Forbidden
|
|
451
|
+
|
|
434
452
|
const withBoth = formats.filter(f =>
|
|
435
453
|
f.url && f.mimeType && f.mimeType.includes('video') && f.audioQuality
|
|
436
454
|
);
|
|
455
|
+
|
|
437
456
|
if (withBoth.length > 0) {
|
|
457
|
+
// Wenn targetQuality angegeben, wähle Format mit passender Audio-Bitrate
|
|
458
|
+
if (targetQuality) {
|
|
459
|
+
// Sortiere nach Audio-Bitrate-Nähe zur Ziel-Qualität
|
|
460
|
+
withBoth.sort((a, b) => {
|
|
461
|
+
const bitrateA = this.getBitrate(a);
|
|
462
|
+
const bitrateB = this.getBitrate(b);
|
|
463
|
+
|
|
464
|
+
// Bevorzuge Formate unter oder gleich der Ziel-Qualität
|
|
465
|
+
const diffA = Math.abs(bitrateA - targetQuality);
|
|
466
|
+
const diffB = Math.abs(bitrateB - targetQuality);
|
|
467
|
+
|
|
468
|
+
// Wenn beide über Ziel, nehme kleineres
|
|
469
|
+
if (bitrateA > targetQuality && bitrateB > targetQuality) {
|
|
470
|
+
return bitrateA - bitrateB;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Wenn beide unter Ziel, nehme größeres
|
|
474
|
+
if (bitrateA <= targetQuality && bitrateB <= targetQuality) {
|
|
475
|
+
return bitrateB - bitrateA;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Sonst nehme das nähere
|
|
479
|
+
return diffA - diffB;
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
return withBoth[0];
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Ohne targetQuality: Nehme höchste Video-Qualität
|
|
438
486
|
withBoth.sort((a, b) => (b.height || 0) - (a.height || 0));
|
|
439
487
|
return withBoth[0];
|
|
440
488
|
}
|
|
441
489
|
|
|
442
|
-
// Nur Video
|
|
490
|
+
// Fallback: Nur Video (sollte nicht passieren)
|
|
443
491
|
const videoOnly = formats.filter(f =>
|
|
444
492
|
f.url && f.mimeType && f.mimeType.includes('video')
|
|
445
493
|
);
|
|
@@ -451,6 +499,31 @@ module.exports = {
|
|
|
451
499
|
return formats[0];
|
|
452
500
|
},
|
|
453
501
|
|
|
502
|
+
getBitrate(format) {
|
|
503
|
+
if (format.bitrate) {
|
|
504
|
+
return Math.round(format.bitrate / 1000); // Bits zu kbps
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (format.audioBitrate) {
|
|
508
|
+
return format.audioBitrate;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (format.averageBitrate) {
|
|
512
|
+
return Math.round(format.averageBitrate / 1000);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Fallback: Schätze aus Quality-Label
|
|
516
|
+
if (format.qualityLabel) {
|
|
517
|
+
const match = format.qualityLabel.match(/(\d+)kbps/i);
|
|
518
|
+
if (match) {
|
|
519
|
+
return parseInt(match[1]);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Default: Mittel
|
|
524
|
+
return 192;
|
|
525
|
+
},
|
|
526
|
+
|
|
454
527
|
extractVideoId(url) {
|
|
455
528
|
let m = url.match(/[?&]v=([^&]+)/);
|
|
456
529
|
if (m) return m[1];
|
|
@@ -280,8 +280,7 @@ class BrowserEmulation {
|
|
|
280
280
|
onProgress(percent, downloadedLength, totalLength);
|
|
281
281
|
}
|
|
282
282
|
} catch (err) {
|
|
283
|
-
//
|
|
284
|
-
console.warn('⚠️ Progress callback error (handled)');
|
|
283
|
+
// Silently ignore progress callback errors
|
|
285
284
|
}
|
|
286
285
|
});
|
|
287
286
|
}
|