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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flux-dl",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
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,17 +445,49 @@ module.exports = {
429
445
  return res.data;
430
446
  },
431
447
 
432
- selectBestFormat(formats) {
433
- // Mit Audio + Video
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
- // 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
  }