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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flux-dl",
3
- "version": "1.1.5",
3
+ "version": "1.1.6",
4
4
  "description": "Leistungsstarke Video-Downloader Library für YouTube, Vimeo und Dailymotion - komplett in JavaScript, keine externen Binaries",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -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, onProgress);
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');
@@ -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
- const bestFormat = this.selectBestFormat(formatsWithUrl);
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: bestFormat.qualityLabel || bestFormat.quality || 'unknown',
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
- const bestFormat = this.selectBestFormat(validFormats);
132
- console.log(`Selected: ${bestFormat.qualityLabel || 'unknown'} quality\n`);
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: bestFormat.qualityLabel || 'unknown',
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
- // Mit Audio + Video
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
- // Even progress callback errors won't crash us
284
- console.warn('⚠️ Progress callback error (handled)');
283
+ // Silently ignore progress callback errors
285
284
  }
286
285
  });
287
286
  }