flux-dl 1.1.5 → 1.1.6
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
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,8 +445,35 @@ module.exports = {
|
|
|
429
445
|
return res.data;
|
|
430
446
|
},
|
|
431
447
|
|
|
432
|
-
selectBestFormat(formats) {
|
|
433
|
-
//
|
|
448
|
+
selectBestFormat(formats, targetQuality = null) {
|
|
449
|
+
// Wenn targetQuality angegeben, nutze Audio-only Format mit passender Bitrate
|
|
450
|
+
if (targetQuality) {
|
|
451
|
+
const audioFormats = formats.filter(f =>
|
|
452
|
+
f.url && f.mimeType && f.mimeType.includes('audio')
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
if (audioFormats.length > 0) {
|
|
456
|
+
// Sortiere nach Bitrate-Nähe zur Ziel-Qualität
|
|
457
|
+
audioFormats.sort((a, b) => {
|
|
458
|
+
const bitrateA = this.getBitrate(a);
|
|
459
|
+
const bitrateB = this.getBitrate(b);
|
|
460
|
+
return Math.abs(bitrateA - targetQuality) - Math.abs(bitrateB - targetQuality);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
// Finde Format das unter oder gleich der Ziel-Qualität ist
|
|
464
|
+
for (const format of audioFormats) {
|
|
465
|
+
const bitrate = this.getBitrate(format);
|
|
466
|
+
if (bitrate <= targetQuality) {
|
|
467
|
+
return format;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Fallback: Nehme das kleinste Format
|
|
472
|
+
return audioFormats[audioFormats.length - 1];
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Standard: Mit Audio + Video
|
|
434
477
|
const withBoth = formats.filter(f =>
|
|
435
478
|
f.url && f.mimeType && f.mimeType.includes('video') && f.audioQuality
|
|
436
479
|
);
|
|
@@ -451,6 +494,31 @@ module.exports = {
|
|
|
451
494
|
return formats[0];
|
|
452
495
|
},
|
|
453
496
|
|
|
497
|
+
getBitrate(format) {
|
|
498
|
+
if (format.bitrate) {
|
|
499
|
+
return Math.round(format.bitrate / 1000); // Bits zu kbps
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (format.audioBitrate) {
|
|
503
|
+
return format.audioBitrate;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (format.averageBitrate) {
|
|
507
|
+
return Math.round(format.averageBitrate / 1000);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Fallback: Schätze aus Quality-Label
|
|
511
|
+
if (format.qualityLabel) {
|
|
512
|
+
const match = format.qualityLabel.match(/(\d+)kbps/i);
|
|
513
|
+
if (match) {
|
|
514
|
+
return parseInt(match[1]);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Default: Mittel
|
|
519
|
+
return 192;
|
|
520
|
+
},
|
|
521
|
+
|
|
454
522
|
extractVideoId(url) {
|
|
455
523
|
let m = url.match(/[?&]v=([^&]+)/);
|
|
456
524
|
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
|
}
|