flux-dl 1.0.3 → 1.0.8

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.0.3",
3
+ "version": "1.0.8",
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": {
@@ -4,6 +4,7 @@ const path = require('path');
4
4
  const platforms = require('./platforms');
5
5
  const VideoEncryption = require('./utils/encryption');
6
6
  const RequestSpoofing = require('./utils/requestSpoofing');
7
+ const BrowserEmulation = require('./utils/browserEmulation');
7
8
 
8
9
  class VideoDownloader {
9
10
  constructor(options = {}) {
@@ -11,11 +12,18 @@ class VideoDownloader {
11
12
  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
13
  timeout: 30000,
13
14
  encryptionKey: options.encryptionKey || 'default-key',
15
+ cookiesFile: options.cookiesFile,
14
16
  ...options
15
17
  };
16
18
 
17
19
  this.encryption = new VideoEncryption(this.options.encryptionKey);
18
20
  this.spoofing = new RequestSpoofing();
21
+ this.browser = new BrowserEmulation();
22
+
23
+ // Load cookies if provided
24
+ if (this.options.cookiesFile) {
25
+ this.browser.cookieManager.loadFromFile(this.options.cookiesFile);
26
+ }
19
27
  }
20
28
 
21
29
  async getVideoInfo(url) {
@@ -72,31 +80,36 @@ class VideoDownloader {
72
80
  async downloadAudio(url, outputPath = './downloads') {
73
81
  const info = await this.getVideoInfo(url);
74
82
 
75
- // Audio-Format auswählen
76
- const audioFormat = this.selectAudioFormat(info.allFormats);
83
+ // WICHTIG: Audio-only Formate bekommen 403!
84
+ // Stattdessen: Nutze Format mit Video+Audio (funktioniert ohne 403)
85
+ // Das ist info.videoUrl - das beste Format mit beiden
77
86
 
78
- if (!audioFormat) {
79
- throw new Error('No audio format available');
87
+ if (!info.videoUrl) {
88
+ throw new Error('No download URL available');
80
89
  }
81
90
 
82
91
  if (!fs.existsSync(outputPath)) {
83
92
  fs.mkdirSync(outputPath, { recursive: true });
84
93
  }
85
94
 
86
- const ext = audioFormat.mimeType.includes('mp4') ? '.m4a' : '.webm';
87
- const filename = this.sanitizeFilename(info.title) + ext;
95
+ // Immer .mp3 für Audio
96
+ const filename = this.sanitizeFilename(info.title) + '.mp3';
88
97
  const filepath = path.join(outputPath, filename);
89
98
 
90
99
  console.log(`Downloading audio: ${info.title}`);
91
- console.log(`Format: ${audioFormat.mimeType}`);
100
+ console.log(`Quality: ${info.quality}`);
92
101
 
93
- // Nutze Request Spoofing
102
+ // Nutze Browser Emulation mit Cookies
94
103
  const referer = `https://www.youtube.com/watch?v=${info.videoId}`;
95
104
  const writer = fs.createWriteStream(filepath);
96
105
 
97
106
  let bytesReceived = 0;
98
- const stream = await this.spoofing.downloadStream(audioFormat.url, referer, (percent) => {
99
- process.stdout.write(`\rProgress: ${percent}%`);
107
+ const stream = await this.browser.downloadStream(info.videoUrl, referer, (percent, downloaded, total) => {
108
+ if (total) {
109
+ process.stdout.write(`\rProgress: ${percent}% (${Math.round(downloaded / 1024 / 1024)}MB / ${Math.round(total / 1024 / 1024)}MB)`);
110
+ } else {
111
+ process.stdout.write(`\rProgress: ${percent}%`);
112
+ }
100
113
  });
101
114
 
102
115
  // Track bytes to detect 403 with empty response
@@ -121,7 +134,37 @@ class VideoDownloader {
121
134
 
122
135
  console.log('\n');
123
136
 
124
- return { filepath, info, format: audioFormat };
137
+ return { filepath, info };
138
+ }
139
+
140
+ /**
141
+ * Get audio stream without saving to file
142
+ * Returns a readable stream that can be piped or consumed directly
143
+ * @param {string} url - Video URL
144
+ * @param {function} onProgress - Optional progress callback (percent, downloaded, total)
145
+ * @returns {Promise<{stream: ReadableStream, info: Object}>}
146
+ */
147
+ async getAudioStream(url, onProgress = null) {
148
+ const info = await this.getVideoInfo(url);
149
+
150
+ if (!info.videoUrl) {
151
+ throw new Error('No download URL available');
152
+ }
153
+
154
+ console.log(`Streaming audio: ${info.title}`);
155
+ console.log(`Quality: ${info.quality}`);
156
+
157
+ // Nutze Browser Emulation mit Cookies
158
+ const referer = `https://www.youtube.com/watch?v=${info.videoId}`;
159
+
160
+ const stream = await this.browser.downloadStream(info.videoUrl, referer, onProgress);
161
+
162
+ return {
163
+ stream,
164
+ info,
165
+ contentType: 'audio/mpeg',
166
+ filename: this.sanitizeFilename(info.title) + '.mp3'
167
+ };
125
168
  }
126
169
 
127
170
  selectAudioFormat(formats) {
@@ -43,13 +43,20 @@ module.exports = {
43
43
 
44
44
  const bestFormat = this.selectBestFormat(formatsWithUrl);
45
45
 
46
+ // Get highest quality thumbnail (last one in array)
47
+ const thumbnails = data.videoDetails.thumbnail.thumbnails;
48
+ const bestThumbnail = thumbnails[thumbnails.length - 1].url;
49
+
46
50
  return {
47
51
  title: data.videoDetails.title,
48
52
  videoId: videoId,
49
53
  duration: parseInt(data.videoDetails.lengthSeconds),
50
- thumbnail: data.videoDetails.thumbnail.thumbnails[0].url,
54
+ thumbnail: bestThumbnail,
51
55
  author: data.videoDetails.author,
56
+ description: data.videoDetails.shortDescription || data.videoDetails.description || '',
52
57
  viewCount: parseInt(data.videoDetails.viewCount || 0),
58
+ likeCount: parseInt(data.videoDetails.likeCount || 0),
59
+ uploadDate: data.videoDetails.uploadDate || data.videoDetails.publishDate || null,
53
60
  videoUrl: bestFormat.url,
54
61
  quality: bestFormat.qualityLabel || bestFormat.quality || 'unknown',
55
62
  format: bestFormat,
@@ -124,13 +131,20 @@ module.exports = {
124
131
  const bestFormat = this.selectBestFormat(validFormats);
125
132
  console.log(`Selected: ${bestFormat.qualityLabel || 'unknown'} quality\n`);
126
133
 
134
+ // Get highest quality thumbnail (last one in array)
135
+ const thumbnails = videoDetails.thumbnail.thumbnails;
136
+ const bestThumbnail = thumbnails[thumbnails.length - 1].url;
137
+
127
138
  return {
128
139
  title: videoDetails.title,
129
140
  videoId: videoId,
130
141
  duration: parseInt(videoDetails.lengthSeconds),
131
- thumbnail: videoDetails.thumbnail.thumbnails[0].url,
142
+ thumbnail: bestThumbnail,
132
143
  author: videoDetails.author,
144
+ description: videoDetails.shortDescription || videoDetails.description || '',
133
145
  viewCount: parseInt(videoDetails.viewCount || 0),
146
+ likeCount: parseInt(videoDetails.likeCount || 0),
147
+ uploadDate: videoDetails.uploadDate || videoDetails.publishDate || null,
134
148
  videoUrl: bestFormat.url,
135
149
  quality: bestFormat.qualityLabel || 'unknown',
136
150
  format: bestFormat,