Spaces:
Sleeping
Sleeping
File size: 10,018 Bytes
b10e9c4 a7bb3b2 b10e9c4 a7bb3b2 b10e9c4 a7bb3b2 b10e9c4 e7f17e0 b10e9c4 e7f17e0 b10e9c4 a7bb3b2 b10e9c4 a7bb3b2 e7f17e0 b10e9c4 e7f17e0 b10e9c4 e7f17e0 b10e9c4 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 |
const express = require('express');
const cors = require('cors');
const { exec } = require('child_process');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const os = require('os');
const app = express();
const PORT = process.env.PORT || 7860; // Hugging Face Spaces sử dụng port 7860
// Middleware
app.use(cors());
app.use(express.json());
// Serve static files (for demo.html)
app.use(express.static(__dirname));
// Tạo thư mục downloads nếu chưa tồn tại với fallback cho container
let downloadsDir;
// Thử tạo thư mục downloads trong app directory
try {
downloadsDir = path.join(__dirname, 'downloads');
if (!fs.existsSync(downloadsDir)) {
fs.mkdirSync(downloadsDir, { recursive: true });
}
// Test write permission
const testFile = path.join(downloadsDir, 'test_write.tmp');
fs.writeFileSync(testFile, 'test');
fs.unlinkSync(testFile);
} catch (error) {
// Nếu không thể tạo trong app dir, dùng temp directory
console.log('⚠️ Không thể ghi vào thư mục app, sử dụng temp directory');
downloadsDir = path.join(os.tmpdir(), 'ytdlp_downloads');
if (!fs.existsSync(downloadsDir)) {
fs.mkdirSync(downloadsDir, { recursive: true });
}
}
console.log(`📁 Thư mục downloads: ${downloadsDir}`);
// Auto-cleanup function - xóa file sau 5 phút
function scheduleFileCleanup(filePath, delay = 5 * 60 * 1000) { // 5 phút
setTimeout(() => {
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
console.log(`🗑️ Đã xóa file: ${path.basename(filePath)}`);
}
}, delay);
}
// Generate unique ID for files
function generateFileId() {
return crypto.randomBytes(8).toString('hex');
}
// Route để lấy thông tin video
app.post('/video-info', async (req, res) => {
const { url } = req.body;
if (!url) {
return res.status(400).json({ error: 'URL là bắt buộc' });
}
const command = `yt-dlp --dump-json "${url}"`;
exec(command, (error, stdout, stderr) => {
if (error) {
console.error('Lỗi:', error);
return res.status(500).json({ error: 'Không thể lấy thông tin video', details: stderr });
}
try {
const videoInfo = JSON.parse(stdout);
res.json({
title: videoInfo.title,
duration: videoInfo.duration,
uploader: videoInfo.uploader,
view_count: videoInfo.view_count,
thumbnail: videoInfo.thumbnail,
formats: videoInfo.formats.map(format => ({
format_id: format.format_id,
ext: format.ext,
resolution: format.resolution,
filesize: format.filesize,
quality: format.quality
}))
});
} catch (parseError) {
res.status(500).json({ error: 'Lỗi phân tích dữ liệu video' });
}
});
});
// Route để tải video
app.post('/download', async (req, res) => {
const { url, format = 'video', quality = 'best' } = req.body;
if (!url) {
return res.status(400).json({ error: 'URL là bắt buộc' });
}
// Tạo ID duy nhất cho file
const fileId = generateFileId();
const timestamp = Date.now();
let command;
let expectedExtension;
if (format === 'audio') {
// Tải audio
expectedExtension = 'mp3';
const outputTemplate = path.join(downloadsDir, `${fileId}.%(ext)s`);
command = `yt-dlp -x --audio-format mp3 --audio-quality ${quality} -o "${outputTemplate}" "${url}"`;
} else {
// Tải video với format selection tối ưu
expectedExtension = 'mp4';
const outputTemplate = path.join(downloadsDir, `${fileId}.%(ext)s`);
// Xử lý format selection để tránh warning
let formatFlag = '';
if (quality === 'best') {
formatFlag = ''; // Không cần -f flag, để yt-dlp tự chọn best
} else if (quality === 'worst') {
formatFlag = '-f "worst"';
} else {
formatFlag = `-f "bestvideo[height<=${quality.replace('p', '')}]+bestaudio/best[height<=${quality.replace('p', '')}]"`;
}
command = `yt-dlp ${formatFlag} -o "${outputTemplate}" "${url}"`.replace(/\s+/g, ' ').trim();
}
console.log('Đang thực thi lệnh:', command);
exec(command, (error, stdout, stderr) => {
if (error) {
console.error('Lỗi tải xuống:', error);
return res.status(500).json({ error: 'Không thể tải video', details: stderr });
}
console.log('Kết quả:', stdout);
// Tìm file đã tải với fileId
const files = fs.readdirSync(downloadsDir).filter(file =>
file.startsWith(fileId)
);
if (files.length > 0) {
const downloadedFile = files[0];
const filePath = path.join(downloadsDir, downloadedFile);
const stats = fs.statSync(filePath);
// Lên lịch xóa file sau 5 phút
scheduleFileCleanup(filePath);
// Lấy thông tin video để trả về
const getVideoInfoCommand = `yt-dlp --dump-json --no-download "${url}"`;
exec(getVideoInfoCommand, (infoError, infoStdout) => {
let videoInfo = null;
if (!infoError) {
try {
videoInfo = JSON.parse(infoStdout);
} catch (e) {
console.log('Không thể parse thông tin video');
}
}
res.json({
success: true,
message: 'Tải video thành công',
fileId: fileId,
filename: downloadedFile,
originalTitle: videoInfo?.title || 'Unknown',
size: stats.size,
format: format,
quality: quality,
duration: videoInfo?.duration || null,
thumbnail: videoInfo?.thumbnail || null,
uploader: videoInfo?.uploader || null,
download_url: `/download-file/${downloadedFile}`,
direct_link: `${req.protocol}://${req.get('host')}/download-file/${downloadedFile}`,
expires_in: '5 phút',
created_at: new Date().toISOString()
});
});
} else {
res.status(500).json({ error: 'Không tìm thấy file đã tải' });
}
});
});
// Route để tải file đã download
app.get('/download-file/:filename', (req, res) => {
const { filename } = req.params;
const filePath = path.join(downloadsDir, filename);
if (fs.existsSync(filePath)) {
res.download(filePath);
} else {
res.status(404).json({ error: 'File không tồn tại' });
}
});
// Route để liệt kê các file đã tải (bỏ route này)
// app.get('/downloads', (req, res) => {
// const files = fs.readdirSync(downloadsDir).map(filename => {
// const filePath = path.join(downloadsDir, filename);
// const stats = fs.statSync(filePath);
// return {
// filename,
// size: stats.size,
// created: stats.birthtime,
// download_url: `/download-file/${filename}`
// };
// });
//
// res.json(files);
// });
// Route để xóa file
app.delete('/delete/:filename', (req, res) => {
const { filename } = req.params;
const filePath = path.join(downloadsDir, filename);
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
res.json({ success: true, message: 'Đã xóa file thành công' });
} else {
res.status(404).json({ error: 'File không tồn tại' });
}
});
// Route kiểm tra trạng thái
app.get('/status', (req, res) => {
exec('yt-dlp --version', (error, stdout, stderr) => {
if (error) {
res.json({
status: 'error',
message: 'yt-dlp không được cài đặt hoặc không hoạt động',
error: error.message
});
} else {
res.json({
status: 'ok',
message: 'API hoạt động bình thường',
yt_dlp_version: stdout.trim()
});
}
});
});
// Route mặc định - redirect to demo
app.get('/', (req, res) => {
res.redirect('/demo.html');
});
// API info route
app.get('/api', (req, res) => {
res.json({
message: 'YouTube Downloader API',
version: '2.0.0',
endpoints: {
'GET /status': 'Kiểm tra trạng thái API',
'POST /video-info': 'Lấy thông tin video (body: {url})',
'POST /download': 'Tải video (body: {url, format?, quality?})',
'GET /downloads': 'Liệt kê các file đã tải',
'GET /download-file/:filename': 'Tải file đã download',
'DELETE /delete/:filename': 'Xóa file'
},
demo: 'http://localhost:' + PORT + '/demo.html'
});
});
app.listen(PORT, '0.0.0.0', () => {
console.log(`🚀 YouTube Downloader API đang chạy tại http://localhost:${PORT}`);
console.log(`📱 Giao diện web: http://localhost:${PORT}`);
console.log(`📁 Thư mục tải xuống: ${downloadsDir}`);
});
|