|
<!DOCTYPE html>
|
|
<html lang="vi">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Video Downloader Online - yt-dlp API</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<style>
|
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
|
body { font-family: 'Inter', sans-serif; }
|
|
.gradient-bg {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
}
|
|
.glass-effect {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
backdrop-filter: blur(10px);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
}
|
|
.preview-placeholder {
|
|
background: linear-gradient(45deg, #f0f0f0 25%, transparent 25%),
|
|
linear-gradient(-45deg, #f0f0f0 25%, transparent 25%),
|
|
linear-gradient(45deg, transparent 75%, #f0f0f0 75%),
|
|
linear-gradient(-45deg, transparent 75%, #f0f0f0 75%);
|
|
background-size: 20px 20px;
|
|
background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
|
|
}
|
|
.spinner {
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
@keyframes spin {
|
|
from { transform: rotate(0deg); }
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
video {
|
|
max-width: 100%;
|
|
height: auto;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="gradient-bg min-h-screen">
|
|
|
|
<div id="apiStatus" class="fixed top-4 right-4 z-50"></div>
|
|
|
|
<div class="container mx-auto px-4 py-8">
|
|
|
|
<div class="text-center mb-12">
|
|
<h1 class="text-5xl font-bold text-white mb-4">
|
|
🎬 Video Downloader
|
|
</h1>
|
|
<p class="text-xl text-white/80 max-w-2xl mx-auto">
|
|
Tải video từ YouTube và các trang web khác sử dụng yt-dlp API
|
|
</p>
|
|
</div>
|
|
|
|
|
|
<div class="max-w-6xl mx-auto">
|
|
<div class="grid lg:grid-cols-2 gap-8">
|
|
|
|
<div class="glass-effect rounded-2xl p-8">
|
|
<h2 class="text-2xl font-semibold text-white mb-6 flex items-center">
|
|
📥 Nhập Liên Kết
|
|
</h2>
|
|
|
|
<div class="space-y-6">
|
|
<div>
|
|
<label class="block text-white/90 text-sm font-medium mb-3">
|
|
URL Video
|
|
</label>
|
|
<input
|
|
type="url"
|
|
id="videoUrl"
|
|
placeholder="https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
|
value="https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
|
class="w-full px-4 py-3 rounded-xl bg-white/10 border border-white/20 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-transparent transition-all"
|
|
>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-white/90 text-sm font-medium mb-3">
|
|
Chất Lượng
|
|
</label>
|
|
<select id="qualitySelect" class="w-full px-4 py-3 rounded-xl bg-white/10 border border-white/20 text-white focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-transparent transition-all">
|
|
<option value="best" class="bg-gray-800">Tốt nhất</option>
|
|
<option value="worst" class="bg-gray-800">Thấp nhất (nhanh hơn)</option>
|
|
<option value="720p" class="bg-gray-800">720p (HD)</option>
|
|
<option value="480p" class="bg-gray-800">480p (SD)</option>
|
|
<option value="360p" class="bg-gray-800">360p</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-white/90 text-sm font-medium mb-3">
|
|
Định Dạng
|
|
</label>
|
|
<div class="flex gap-3">
|
|
<label class="flex items-center cursor-pointer">
|
|
<input type="radio" name="format" value="video" checked class="sr-only">
|
|
<div class="w-5 h-5 rounded-full border-2 border-white/40 flex items-center justify-center mr-2">
|
|
<div class="w-2 h-2 rounded-full bg-blue-400"></div>
|
|
</div>
|
|
<span class="text-white/90">Video</span>
|
|
</label>
|
|
<label class="flex items-center cursor-pointer">
|
|
<input type="radio" name="format" value="audio" class="sr-only">
|
|
<div class="w-5 h-5 rounded-full border-2 border-white/40 flex items-center justify-center mr-2">
|
|
<div class="w-2 h-2 rounded-full bg-transparent"></div>
|
|
</div>
|
|
<span class="text-white/90">Audio MP3</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex gap-3">
|
|
<button
|
|
onclick="getVideoInfo()"
|
|
class="flex-1 bg-gradient-to-r from-green-500 to-teal-600 hover:from-green-600 hover:to-teal-700 text-white font-semibold py-4 px-6 rounded-xl transition-all duration-300 transform hover:scale-105 shadow-lg"
|
|
>
|
|
🔍 Xem Thông Tin
|
|
</button>
|
|
<button
|
|
onclick="downloadVideo()"
|
|
class="flex-1 bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white font-semibold py-4 px-6 rounded-xl transition-all duration-300 transform hover:scale-105 shadow-lg"
|
|
>
|
|
🚀 Tải Xuống
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div class="glass-effect rounded-2xl p-8">
|
|
<h2 class="text-2xl font-semibold text-white mb-6 flex items-center">
|
|
👁️ Thông Tin Video
|
|
</h2>
|
|
|
|
<div id="previewContainer" class="space-y-4">
|
|
<div class="preview-placeholder rounded-xl h-48 flex items-center justify-center">
|
|
<div class="text-center text-gray-500">
|
|
<div class="text-4xl mb-2">🎥</div>
|
|
<p>Nhấn "Xem Thông Tin" để hiển thị chi tiết video</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white/5 rounded-xl p-4">
|
|
<h3 id="videoTitle" class="text-white font-medium mb-2">Tiêu đề video sẽ hiển thị ở đây</h3>
|
|
<div class="flex justify-between text-sm text-white/70">
|
|
<span id="videoDuration">Thời lượng: --:--</span>
|
|
<span id="videoViews">Lượt xem: --</span>
|
|
</div>
|
|
<div class="mt-2 text-sm text-white/70">
|
|
<span id="videoUploader">Kênh: --</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div class="glass-effect rounded-2xl p-8 mt-8">
|
|
<h2 class="text-2xl font-semibold text-white mb-6 flex items-center">
|
|
📤 Kết Quả Tải Xuống
|
|
</h2>
|
|
|
|
<div id="outputContainer" class="space-y-4">
|
|
<div class="bg-white/5 rounded-xl p-6 text-center">
|
|
<div class="text-4xl mb-4">⏳</div>
|
|
<p class="text-white/70">Chưa có video nào được xử lý</p>
|
|
<p class="text-sm text-white/50 mt-2">Nhập URL và nhấn "Tải Xuống" để bắt đầu</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div class="glass-effect rounded-2xl p-8 mt-8">
|
|
<h2 class="text-2xl font-semibold text-white mb-6 text-center">
|
|
🌐 Trang Web Được Hỗ Trợ (yt-dlp)
|
|
</h2>
|
|
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-4">
|
|
<div class="bg-white/10 rounded-lg p-4 text-center">
|
|
<div class="text-2xl mb-2">📺</div>
|
|
<span class="text-white/80 text-sm">YouTube</span>
|
|
</div>
|
|
<div class="bg-white/10 rounded-lg p-4 text-center">
|
|
<div class="text-2xl mb-2">📱</div>
|
|
<span class="text-white/80 text-sm">TikTok</span>
|
|
</div>
|
|
<div class="bg-white/10 rounded-lg p-4 text-center">
|
|
<div class="text-2xl mb-2">📘</div>
|
|
<span class="text-white/80 text-sm">Facebook</span>
|
|
</div>
|
|
<div class="bg-white/10 rounded-lg p-4 text-center">
|
|
<div class="text-2xl mb-2">📷</div>
|
|
<span class="text-white/80 text-sm">Instagram</span>
|
|
</div>
|
|
<div class="bg-white/10 rounded-lg p-4 text-center">
|
|
<div class="text-2xl mb-2">🐦</div>
|
|
<span class="text-white/80 text-sm">Twitter</span>
|
|
</div>
|
|
<div class="bg-white/10 rounded-lg p-4 text-center">
|
|
<div class="text-2xl mb-2">🎵</div>
|
|
<span class="text-white/80 text-sm">Vimeo</span>
|
|
</div>
|
|
</div>
|
|
<div class="text-center mt-4">
|
|
<p class="text-white/60 text-sm">và hàng nghìn trang web khác được hỗ trợ bởi yt-dlp</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Auto-detect API base URL
|
|
const API_BASE = window.location.origin;
|
|
let currentVideoInfo = null;
|
|
|
|
// Handle radio button visual feedback
|
|
document.querySelectorAll('input[name="format"]').forEach(radio => {
|
|
radio.addEventListener('change', function() {
|
|
document.querySelectorAll('input[name="format"]').forEach(r => {
|
|
const circle = r.parentElement.querySelector('div div');
|
|
if (r.checked) {
|
|
circle.classList.add('bg-blue-400');
|
|
circle.classList.remove('bg-transparent');
|
|
} else {
|
|
circle.classList.remove('bg-blue-400');
|
|
circle.classList.add('bg-transparent');
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
// Show API status indicator
|
|
function showApiStatus(status, message) {
|
|
const statusDiv = document.getElementById('apiStatus');
|
|
const statusClass = status === 'ok' ? 'bg-green-500' : status === 'loading' ? 'bg-yellow-500' : 'bg-red-500';
|
|
statusDiv.innerHTML = `
|
|
<div class="${statusClass} text-white px-4 py-2 rounded-lg shadow-lg flex items-center">
|
|
<div class="mr-2">${status === 'ok' ? '✅' : status === 'loading' ? '⏳' : '❌'}</div>
|
|
<span class="text-sm">${message}</span>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Check API status on load
|
|
async function checkApiStatus() {
|
|
showApiStatus('loading', 'Đang kiểm tra API...');
|
|
try {
|
|
const response = await fetch(`${API_BASE}/status`);
|
|
const data = await response.json();
|
|
if (data.status === 'ok') {
|
|
showApiStatus('ok', `API sẵn sàng - yt-dlp ${data.yt_dlp_version}`);
|
|
} else {
|
|
showApiStatus('error', 'API có lỗi');
|
|
}
|
|
} catch (error) {
|
|
showApiStatus('error', 'Không thể kết nối API');
|
|
}
|
|
}
|
|
|
|
// Get video information
|
|
async function getVideoInfo() {
|
|
const url = document.getElementById('videoUrl').value.trim();
|
|
if (!url) {
|
|
alert('Vui lòng nhập URL video!');
|
|
return;
|
|
}
|
|
|
|
// Show loading state
|
|
const previewContainer = document.getElementById('previewContainer');
|
|
previewContainer.innerHTML = `
|
|
<div class="bg-gray-800 rounded-xl h-48 flex items-center justify-center">
|
|
<div class="text-center text-white">
|
|
<div class="text-4xl mb-4 spinner">⚙️</div>
|
|
<p>Đang lấy thông tin video...</p>
|
|
</div>
|
|
</div>
|
|
<div class="bg-white/5 rounded-xl p-4">
|
|
<div class="animate-pulse">
|
|
<div class="h-4 bg-white/20 rounded mb-2"></div>
|
|
<div class="h-3 bg-white/10 rounded"></div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
try {
|
|
const response = await fetch(`${API_BASE}/video-info`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ url })
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
currentVideoInfo = data;
|
|
updatePreview(data);
|
|
} else {
|
|
showError(data.error || 'Không thể lấy thông tin video');
|
|
}
|
|
} catch (error) {
|
|
showError(`Lỗi kết nối: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// Update preview with video info
|
|
function updatePreview(videoInfo) {
|
|
const duration = videoInfo.duration ?
|
|
`${Math.floor(videoInfo.duration / 60)}:${(videoInfo.duration % 60).toString().padStart(2, '0')}` :
|
|
'N/A';
|
|
|
|
const views = videoInfo.view_count ?
|
|
videoInfo.view_count.toLocaleString('vi-VN') :
|
|
'N/A';
|
|
|
|
document.getElementById('previewContainer').innerHTML = `
|
|
<div class="bg-gray-800 rounded-xl h-48 flex items-center justify-center relative overflow-hidden">
|
|
${videoInfo.thumbnail ?
|
|
`<img src="${videoInfo.thumbnail}" alt="Thumbnail" class="w-full h-full object-cover rounded-xl">` :
|
|
`<div class="text-center text-white">
|
|
<div class="text-6xl mb-2">🎬</div>
|
|
<p class="text-sm opacity-75">Video Preview</p>
|
|
</div>`
|
|
}
|
|
<div class="absolute inset-0 bg-black/20 rounded-xl"></div>
|
|
<div class="absolute bottom-4 right-4 bg-black/70 text-white px-2 py-1 rounded text-sm">
|
|
${duration}
|
|
</div>
|
|
</div>
|
|
<div class="bg-white/5 rounded-xl p-4">
|
|
<h3 class="text-white font-medium mb-2">${videoInfo.title || 'Không có tiêu đề'}</h3>
|
|
<div class="flex justify-between text-sm text-white/70">
|
|
<span>Thời lượng: ${duration}</span>
|
|
<span>Lượt xem: ${views}</span>
|
|
</div>
|
|
<div class="mt-2 text-sm text-white/70">
|
|
<span>Kênh: ${videoInfo.uploader || 'N/A'}</span>
|
|
</div>
|
|
<div class="mt-2 text-xs text-white/50">
|
|
${videoInfo.formats?.length || 0} định dạng có sẵn
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Download video
|
|
async function downloadVideo() {
|
|
const url = document.getElementById('videoUrl').value.trim();
|
|
const quality = document.getElementById('qualitySelect').value;
|
|
const format = document.querySelector('input[name="format"]:checked').value;
|
|
|
|
if (!url) {
|
|
alert('Vui lòng nhập URL video!');
|
|
return;
|
|
}
|
|
|
|
// Show loading state
|
|
const outputContainer = document.getElementById('outputContainer');
|
|
outputContainer.innerHTML = `
|
|
<div class="bg-blue-500/10 border border-blue-500/30 rounded-xl p-6 text-center">
|
|
<div class="text-4xl mb-4 spinner">⚙️</div>
|
|
<p class="text-white mb-2">Đang tải video...</p>
|
|
<p class="text-white/70 text-sm">Chất lượng: ${quality} | Định dạng: ${format === 'audio' ? 'MP3' : 'Video'}</p>
|
|
<div class="w-full bg-white/10 rounded-full h-2 mt-4">
|
|
<div class="bg-blue-500 h-2 rounded-full animate-pulse" style="width: 60%"></div>
|
|
</div>
|
|
<p class="text-white/60 text-xs mt-2">Thời gian tải phụ thuộc vào kích thước và chất lượng video</p>
|
|
</div>
|
|
`;
|
|
|
|
try {
|
|
const response = await fetch(`${API_BASE}/download`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ url, format, quality })
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
showDownloadSuccess(data);
|
|
} else {
|
|
showError(data.error || 'Không thể tải video');
|
|
}
|
|
} catch (error) {
|
|
showError(`Lỗi kết nối: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// Show download success with video player
|
|
function showDownloadSuccess(data) {
|
|
const outputContainer = document.getElementById('outputContainer');
|
|
const isVideo = data.format === 'video';
|
|
const fileSize = (data.size / (1024 * 1024)).toFixed(2);
|
|
|
|
outputContainer.innerHTML = `
|
|
<div class="bg-green-500/10 border border-green-500/30 rounded-xl p-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div class="flex items-center">
|
|
<div class="text-2xl mr-3">✅</div>
|
|
<div>
|
|
<h3 class="text-white font-medium">Tải xuống thành công!</h3>
|
|
<p class="text-white/70 text-sm">${data.originalTitle}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
${isVideo ? `
|
|
<div class="bg-black rounded-xl p-4 mb-4">
|
|
<video controls class="w-full rounded-lg" poster="${data.thumbnail || ''}">
|
|
<source src="${data.direct_link}" type="video/mp4">
|
|
Trình duyệt của bạn không hỗ trợ video HTML5.
|
|
</video>
|
|
</div>
|
|
` : `
|
|
<div class="bg-black/20 rounded-xl p-4 mb-4 flex items-center justify-center">
|
|
<div class="text-center text-white">
|
|
<div class="text-6xl mb-2">🎵</div>
|
|
<p class="text-lg font-medium">Audio MP3</p>
|
|
<audio controls class="mt-4">
|
|
<source src="${data.direct_link}" type="audio/mpeg">
|
|
Trình duyệt của bạn không hỗ trợ audio HTML5.
|
|
</audio>
|
|
</div>
|
|
</div>
|
|
`}
|
|
|
|
<div class="bg-white/5 rounded-lg p-4 mb-4">
|
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm text-white/70">
|
|
<div>
|
|
<span class="block font-medium text-white">ID File:</span>
|
|
${data.fileId}
|
|
</div>
|
|
<div>
|
|
<span class="block font-medium text-white">Kích thước:</span>
|
|
${fileSize} MB
|
|
</div>
|
|
<div>
|
|
<span class="block font-medium text-white">Định dạng:</span>
|
|
${data.format === 'audio' ? 'MP3' : 'MP4'}
|
|
</div>
|
|
<div>
|
|
<span class="block font-medium text-white">Thời gian xóa:</span>
|
|
${data.expires_in}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex gap-3">
|
|
<a href="${data.direct_link}"
|
|
download="${data.filename}"
|
|
class="flex-1 bg-blue-500 hover:bg-blue-600 text-white px-6 py-3 rounded-lg transition-colors text-center font-medium">
|
|
📥 Tải về máy
|
|
</a>
|
|
<button onclick="copyLink('${data.direct_link}')"
|
|
class="flex-1 bg-purple-500 hover:bg-purple-600 text-white px-6 py-3 rounded-lg transition-colors font-medium">
|
|
🔗 Copy Link
|
|
</button>
|
|
</div>
|
|
|
|
<div class="text-center mt-4">
|
|
<p class="text-white/60 text-sm">
|
|
⚠️ File sẽ tự động xóa sau ${data.expires_in} kể từ khi tải xuống
|
|
</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Copy link to clipboard
|
|
function copyLink(link) {
|
|
navigator.clipboard.writeText(link).then(() => {
|
|
alert('Link đã được copy vào clipboard!');
|
|
}).catch(() => {
|
|
alert('Không thể copy link. Hãy copy thủ công: ' + link);
|
|
});
|
|
}
|
|
|
|
// Show error
|
|
function showError(message) {
|
|
const outputContainer = document.getElementById('outputContainer');
|
|
outputContainer.innerHTML = `
|
|
<div class="bg-red-500/10 border border-red-500/30 rounded-xl p-6 text-center">
|
|
<div class="text-4xl mb-4">❌</div>
|
|
<p class="text-white mb-2">Có lỗi xảy ra</p>
|
|
<p class="text-red-300 text-sm">${message}</p>
|
|
<button onclick="checkApiStatus()" class="mt-4 bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg transition-colors text-sm">
|
|
🔄 Kiểm tra lại API
|
|
</button>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Initialize app
|
|
window.onload = function() {
|
|
checkApiStatus();
|
|
document.getElementById('videoUrl').focus();
|
|
};
|
|
</script>
|
|
</body>
|
|
</html>
|
|
|