haaaaus commited on
Commit
a7bb3b2
·
verified ·
1 Parent(s): b10e9c4

Upload 8 files

Browse files
Files changed (5) hide show
  1. DEPLOY_GUIDE.md +25 -0
  2. Dockerfile +8 -2
  3. app.js +53 -10
  4. config.json +14 -1
  5. proxy-config.js +91 -0
DEPLOY_GUIDE.md CHANGED
@@ -62,10 +62,14 @@ git push
62
  - Install yt-dlp qua pip
63
  - Port 7860 (mặc định của HF)
64
  - Bind `0.0.0.0` để accessible
 
 
65
 
66
  ### App.js changes:
67
  - Port: `process.env.PORT || 7860`
68
  - Listen: `app.listen(PORT, '0.0.0.0')`
 
 
69
 
70
  ### Demo.html changes:
71
  - API_BASE: `window.location.origin` (tự động)
@@ -89,3 +93,24 @@ https://huggingface.co/spaces/[YOUR_USERNAME]/ytdlp-web
89
  - **App không start**: Kiểm tra port 7860
90
  - **API không hoạt động**: Kiểm tra CORS và API_BASE
91
  - **No space left**: Bật auto-cleanup trong config.json
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  - Install yt-dlp qua pip
63
  - Port 7860 (mặc định của HF)
64
  - Bind `0.0.0.0` để accessible
65
+ - **Fix permission**: Tạo user và set chmod 777 cho downloads/
66
+ - **Fallback storage**: Dùng temp directory nếu app dir không writable
67
 
68
  ### App.js changes:
69
  - Port: `process.env.PORT || 7860`
70
  - Listen: `app.listen(PORT, '0.0.0.0')`
71
+ - **Permission handling**: Auto fallback to temp directory
72
+ - **Format selection**: Tối ưu để tránh yt-dlp warnings
73
 
74
  ### Demo.html changes:
75
  - API_BASE: `window.location.origin` (tự động)
 
93
  - **App không start**: Kiểm tra port 7860
94
  - **API không hoạt động**: Kiểm tra CORS và API_BASE
95
  - **No space left**: Bật auto-cleanup trong config.json
96
+ - **Permission denied**: App tự động fallback sang temp directory
97
+ - **yt-dlp warnings**: Đã tối ưu format selection
98
+ - **Facebook/Instagram links**: Một số platform có thể block download
99
+
100
+ ## 🔄 Common Issues & Solutions
101
+
102
+ ### "Permission denied" error:
103
+ ```
104
+ ERROR: unable to open for writing: [Errno 13] Permission denied
105
+ ```
106
+ **Solution**: App đã được cập nhật để tự động sử dụng temp directory
107
+
108
+ ### "Command failed" với yt-dlp:
109
+ - Kiểm tra URL có hợp lệ không
110
+ - Một số platform có thể thay đổi API
111
+ - Thử quality khác (worst thay vì best)
112
+
113
+ ### App sleep trên HF Spaces:
114
+ - Apps miễn phí sẽ sleep sau 1 giờ không dùng
115
+ - Truy cập lại để wake up
116
+ - Upgrade Pro để avoid sleeping
Dockerfile CHANGED
@@ -25,8 +25,14 @@ RUN npm install
25
  # Copy application files
26
  COPY . .
27
 
28
- # Create downloads directory
29
- RUN mkdir -p downloads
 
 
 
 
 
 
30
 
31
  # Expose port 7860 (Hugging Face Spaces default)
32
  EXPOSE 7860
 
25
  # Copy application files
26
  COPY . .
27
 
28
+ # Create downloads directory with proper permissions
29
+ RUN mkdir -p downloads && chmod 777 downloads
30
+
31
+ # Create user for security
32
+ RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
33
+
34
+ # Switch to non-root user
35
+ USER appuser
36
 
37
  # Expose port 7860 (Hugging Face Spaces default)
38
  EXPOSE 7860
app.js CHANGED
@@ -4,6 +4,8 @@ const { exec } = require('child_process');
4
  const fs = require('fs');
5
  const path = require('path');
6
  const crypto = require('crypto');
 
 
7
 
8
  const app = express();
9
  const PORT = process.env.PORT || 7860; // Hugging Face Spaces sử dụng port 7860
@@ -15,12 +17,30 @@ app.use(express.json());
15
  // Serve static files (for demo.html)
16
  app.use(express.static(__dirname));
17
 
18
- // Tạo thư mục downloads nếu chưa tồn tại
19
- const downloadsDir = path.join(__dirname, 'downloads');
20
- if (!fs.existsSync(downloadsDir)) {
21
- fs.mkdirSync(downloadsDir);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  }
23
 
 
 
24
  // Auto-cleanup function - xóa file sau 5 phút
25
  function scheduleFileCleanup(filePath, delay = 5 * 60 * 1000) { // 5 phút
26
  setTimeout(() => {
@@ -44,7 +64,11 @@ app.post('/video-info', async (req, res) => {
44
  return res.status(400).json({ error: 'URL là bắt buộc' });
45
  }
46
 
47
- const command = `yt-dlp --dump-json "${url}"`;
 
 
 
 
48
 
49
  exec(command, (error, stdout, stderr) => {
50
  if (error) {
@@ -86,6 +110,10 @@ app.post('/download', async (req, res) => {
86
  const fileId = generateFileId();
87
  const timestamp = Date.now();
88
 
 
 
 
 
89
  let command;
90
  let expectedExtension;
91
 
@@ -93,15 +121,30 @@ app.post('/download', async (req, res) => {
93
  // Tải audio
94
  expectedExtension = 'mp3';
95
  const outputTemplate = path.join(downloadsDir, `${fileId}.%(ext)s`);
96
- command = `yt-dlp -x --audio-format mp3 --audio-quality ${quality} -o "${outputTemplate}" "${url}"`;
97
  } else {
98
- // Tải video
99
  expectedExtension = 'mp4';
100
  const outputTemplate = path.join(downloadsDir, `${fileId}.%(ext)s`);
101
- command = `yt-dlp -f "${quality}" -o "${outputTemplate}" "${url}"`;
 
 
 
 
 
 
 
 
 
 
 
102
  }
103
 
104
- console.log('Đang thực thi lệnh:', command);
 
 
 
 
105
 
106
  exec(command, (error, stdout, stderr) => {
107
  if (error) {
@@ -125,7 +168,7 @@ app.post('/download', async (req, res) => {
125
  scheduleFileCleanup(filePath);
126
 
127
  // Lấy thông tin video để trả về
128
- const getVideoInfoCommand = `yt-dlp --dump-json --no-download "${url}"`;
129
  exec(getVideoInfoCommand, (infoError, infoStdout) => {
130
  let videoInfo = null;
131
  if (!infoError) {
 
4
  const fs = require('fs');
5
  const path = require('path');
6
  const crypto = require('crypto');
7
+ const os = require('os');
8
+ const { getProxyForUrl, validateProxy } = require('./proxy-config');
9
 
10
  const app = express();
11
  const PORT = process.env.PORT || 7860; // Hugging Face Spaces sử dụng port 7860
 
17
  // Serve static files (for demo.html)
18
  app.use(express.static(__dirname));
19
 
20
+ // Tạo thư mục downloads nếu chưa tồn tại với fallback cho container
21
+ let downloadsDir;
22
+
23
+ // Thử tạo thư mục downloads trong app directory
24
+ try {
25
+ downloadsDir = path.join(__dirname, 'downloads');
26
+ if (!fs.existsSync(downloadsDir)) {
27
+ fs.mkdirSync(downloadsDir, { recursive: true });
28
+ }
29
+ // Test write permission
30
+ const testFile = path.join(downloadsDir, 'test_write.tmp');
31
+ fs.writeFileSync(testFile, 'test');
32
+ fs.unlinkSync(testFile);
33
+ } catch (error) {
34
+ // Nếu không thể tạo trong app dir, dùng temp directory
35
+ console.log('⚠️ Không thể ghi vào thư mục app, sử dụng temp directory');
36
+ downloadsDir = path.join(os.tmpdir(), 'ytdlp_downloads');
37
+ if (!fs.existsSync(downloadsDir)) {
38
+ fs.mkdirSync(downloadsDir, { recursive: true });
39
+ }
40
  }
41
 
42
+ console.log(`📁 Thư mục downloads: ${downloadsDir}`);
43
+
44
  // Auto-cleanup function - xóa file sau 5 phút
45
  function scheduleFileCleanup(filePath, delay = 5 * 60 * 1000) { // 5 phút
46
  setTimeout(() => {
 
64
  return res.status(400).json({ error: 'URL là bắt buộc' });
65
  }
66
 
67
+ // Lấy proxy cho URL này
68
+ const proxyUrl = getProxyForUrl(url);
69
+ const proxyFlag = proxyUrl && validateProxy(proxyUrl) ? `--proxy "${proxyUrl}"` : '';
70
+
71
+ const command = `yt-dlp ${proxyFlag} --dump-json "${url}"`.replace(/\s+/g, ' ').trim();
72
 
73
  exec(command, (error, stdout, stderr) => {
74
  if (error) {
 
110
  const fileId = generateFileId();
111
  const timestamp = Date.now();
112
 
113
+ // Lấy proxy cho URL này
114
+ const proxyUrl = getProxyForUrl(url);
115
+ const proxyFlag = proxyUrl && validateProxy(proxyUrl) ? `--proxy "${proxyUrl}"` : '';
116
+
117
  let command;
118
  let expectedExtension;
119
 
 
121
  // Tải audio
122
  expectedExtension = 'mp3';
123
  const outputTemplate = path.join(downloadsDir, `${fileId}.%(ext)s`);
124
+ command = `yt-dlp ${proxyFlag} -x --audio-format mp3 --audio-quality ${quality} -o "${outputTemplate}" "${url}"`;
125
  } else {
126
+ // Tải video với format selection tối ưu
127
  expectedExtension = 'mp4';
128
  const outputTemplate = path.join(downloadsDir, `${fileId}.%(ext)s`);
129
+
130
+ // Xử lý format selection để tránh warning
131
+ let formatFlag = '';
132
+ if (quality === 'best') {
133
+ formatFlag = ''; // Không cần -f flag, để yt-dlp tự chọn best
134
+ } else if (quality === 'worst') {
135
+ formatFlag = '-f "worst"';
136
+ } else {
137
+ formatFlag = `-f "bestvideo[height<=${quality.replace('p', '')}]+bestaudio/best[height<=${quality.replace('p', '')}]"`;
138
+ }
139
+
140
+ command = `yt-dlp ${proxyFlag} ${formatFlag} -o "${outputTemplate}" "${url}"`.replace(/\s+/g, ' ').trim();
141
  }
142
 
143
+ // Log proxy usage
144
+ if (proxyUrl) {
145
+ console.log(`🌐 Sử dụng proxy: ${proxyUrl.replace(/\/\/[^@]+@/, '//***:***@')}`);
146
+ }
147
+ console.log('Đang thực thi lệnh:', command.replace(/--proxy "[^"]+"/, '--proxy "***"'));
148
 
149
  exec(command, (error, stdout, stderr) => {
150
  if (error) {
 
168
  scheduleFileCleanup(filePath);
169
 
170
  // Lấy thông tin video để trả về
171
+ const getVideoInfoCommand = `yt-dlp ${proxyFlag} --dump-json --no-download "${url}"`.replace(/\s+/g, ' ').trim();
172
  exec(getVideoInfoCommand, (infoError, infoStdout) => {
173
  let videoInfo = null;
174
  if (!infoError) {
config.json CHANGED
@@ -25,6 +25,19 @@
25
  "audioPlayer": true,
26
  "directDownload": true,
27
  "copyLink": true,
28
- "fileIdSystem": true
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  }
30
  }
 
25
  "audioPlayer": true,
26
  "directDownload": true,
27
  "copyLink": true,
28
+ "fileIdSystem": true,
29
+ "proxySupport": true
30
+ },
31
+ "proxy": {
32
+ "enabled": false,
33
+ "type": "socks5",
34
+ "host": "74.226.201.156",
35
+ "port": 1080,
36
+ "username": "dunn",
37
+ "password": "1234",
38
+ "poolEnabled": false,
39
+ "pool": [],
40
+ "platformSpecific": {},
41
+ "regions": {}
42
  }
43
  }
proxy-config.js ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Proxy Configuration for yt-dlp
2
+ const proxyConfig = {
3
+ enabled: false, // Tắt proxy theo yêu cầu
4
+
5
+ // Single proxy - Proxy mới của chủ
6
+ single: {
7
+ type: 'socks5',
8
+ host: '74.226.201.156',
9
+ port: 1080,
10
+ username: 'dunn',
11
+ password: '1234',
12
+ },
13
+
14
+ // Proxy pool cho load balancing - Bao gồm proxy mới
15
+ pool: [
16
+ 'socks5://dunn:[email protected]:1080', // Proxy mới của chủ
17
+ 'socks5://user1:[email protected]:1080',
18
+ 'socks5://user2:[email protected]:1080',
19
+ 'http://user3:[email protected]:8080',
20
+ // Thêm nhiều proxy khác...
21
+ ],
22
+
23
+ // Cấu hình retry
24
+ retry: {
25
+ maxAttempts: 3,
26
+ rotateOnFailure: true
27
+ },
28
+
29
+ // Proxy cho từng platform - Sử dụng proxy mới
30
+ platformSpecific: {
31
+ 'youtube.com': 'socks5://dunn:[email protected]:1080',
32
+ 'facebook.com': 'socks5://dunn:[email protected]:1080',
33
+ 'instagram.com': 'socks5://dunn:[email protected]:1080',
34
+ 'tiktok.com': 'socks5://dunn:[email protected]:1080'
35
+ },
36
+
37
+ // Geo-targeting
38
+ regions: {
39
+ 'US': ['socks5://us1.proxy.com:1080', 'socks5://us2.proxy.com:1080'],
40
+ 'EU': ['socks5://eu1.proxy.com:1080', 'socks5://eu2.proxy.com:1080'],
41
+ 'ASIA': ['socks5://asia1.proxy.com:1080', 'socks5://asia2.proxy.com:1080']
42
+ }
43
+ };
44
+
45
+ // Utility functions
46
+ function getProxyUrl(config) {
47
+ if (!config) return null;
48
+
49
+ const auth = config.username && config.password
50
+ ? `${config.username}:${config.password}@`
51
+ : '';
52
+
53
+ return `${config.type}://${auth}${config.host}:${config.port}`;
54
+ }
55
+
56
+ function getRandomProxy(pool) {
57
+ if (!pool || pool.length === 0) return null;
58
+ return pool[Math.floor(Math.random() * pool.length)];
59
+ }
60
+
61
+ function getProxyForUrl(url) {
62
+ if (!proxyConfig.enabled) return null;
63
+
64
+ // Kiểm tra platform-specific proxy
65
+ for (const [platform, proxy] of Object.entries(proxyConfig.platformSpecific)) {
66
+ if (url.includes(platform)) {
67
+ return proxy;
68
+ }
69
+ }
70
+
71
+ // Dùng proxy pool hoặc single proxy
72
+ if (proxyConfig.pool && proxyConfig.pool.length > 0) {
73
+ return getRandomProxy(proxyConfig.pool);
74
+ }
75
+
76
+ return getProxyUrl(proxyConfig.single);
77
+ }
78
+
79
+ function validateProxy(proxyUrl) {
80
+ // Basic validation
81
+ const proxyRegex = /^(socks5|http|https):\/\/(?:([^:]+):([^@]+)@)?([^:]+):(\d+)$/;
82
+ return proxyRegex.test(proxyUrl);
83
+ }
84
+
85
+ module.exports = {
86
+ proxyConfig,
87
+ getProxyUrl,
88
+ getRandomProxy,
89
+ getProxyForUrl,
90
+ validateProxy
91
+ };