|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>MP4 to MP3 Converter</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
|
<style> |
|
|
.dropzone { |
|
|
border: 2px dashed #3b82f6; |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
.dropzone.active { |
|
|
border-color: #10b981; |
|
|
background-color: #f0f9ff; |
|
|
} |
|
|
.waveform { |
|
|
height: 60px; |
|
|
background: linear-gradient(90deg, #3b82f6 0%, #8b5cf6 50%, #ec4899 100%); |
|
|
mask-image: url('data:image/svg+xml;utf8,<svg viewBox="0 0 100 20" xmlns="http://www.w3.org/2000/svg"><path d="M0,10 Q5,15 10,10 T20,10 T30,10 T40,10 T50,10 T60,10 T70,10 T80,10 T90,10 T100,10" stroke="black" fill="none" stroke-width="2"/></svg>'); |
|
|
mask-size: 100% 100%; |
|
|
animation: wave 2s linear infinite; |
|
|
} |
|
|
@keyframes wave { |
|
|
0% { mask-position: 0% 0%; } |
|
|
100% { mask-position: 100% 0%; } |
|
|
} |
|
|
.progress-bar { |
|
|
transition: width 0.3s ease; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gray-100 min-h-screen"> |
|
|
<div class="container mx-auto px-4 py-12 max-w-4xl"> |
|
|
<div class="text-center mb-12"> |
|
|
<h1 class="text-4xl font-bold text-indigo-700 mb-2">MP4 to MP3 Converter</h1> |
|
|
<p class="text-gray-600">Convert your video files to high-quality audio in seconds</p> |
|
|
<div class="waveform w-full mt-6 rounded-lg"></div> |
|
|
</div> |
|
|
|
|
|
<div class="bg-white rounded-xl shadow-xl overflow-hidden"> |
|
|
<div class="p-6"> |
|
|
<div class="flex flex-col md:flex-row gap-6"> |
|
|
<div class="flex-1"> |
|
|
<div id="dropzone" class="dropzone rounded-lg p-8 text-center cursor-pointer"> |
|
|
<div class="flex flex-col items-center justify-center h-full"> |
|
|
<i class="fas fa-file-video text-5xl text-indigo-500 mb-4"></i> |
|
|
<h3 class="text-xl font-semibold text-gray-700 mb-2">Drop MP4 file here</h3> |
|
|
<p class="text-gray-500 mb-4">or click to browse files</p> |
|
|
<input type="file" id="fileInput" accept="video/mp4" class="hidden"> |
|
|
<button id="browseBtn" class="bg-indigo-600 hover:bg-indigo-700 text-white px-6 py-2 rounded-lg transition"> |
|
|
Select File |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="flex-1 flex flex-col justify-center"> |
|
|
<div class="bg-gray-50 p-6 rounded-lg"> |
|
|
<h3 class="text-lg font-medium text-gray-800 mb-4">Conversion Settings</h3> |
|
|
|
|
|
<div class="space-y-4"> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Audio Quality</label> |
|
|
<select id="qualitySelect" class="w-full p-2 border border-gray-300 rounded-md focus:ring-indigo-500 focus:border-indigo-500"> |
|
|
<option value="high">High (320 kbps)</option> |
|
|
<option value="medium" selected>Medium (192 kbps)</option> |
|
|
<option value="low">Low (128 kbps)</option> |
|
|
</select> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Output Format</label> |
|
|
<div class="flex items-center space-x-4"> |
|
|
<div class="flex items-center"> |
|
|
<input type="radio" id="formatMp3" name="format" value="mp3" checked class="h-4 w-4 text-indigo-600 focus:ring-indigo-500"> |
|
|
<label for="formatMp3" class="ml-2 block text-sm text-gray-700">MP3</label> |
|
|
</div> |
|
|
<div class="flex items-center"> |
|
|
<input type="radio" id="formatWav" name="format" value="wav" class="h-4 w-4 text-indigo-600 focus:ring-indigo-500"> |
|
|
<label for="formatWav" class="ml-2 block text-sm text-gray-700">WAV</label> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="pt-2"> |
|
|
<button id="convertBtn" disabled class="w-full bg-indigo-600 hover:bg-indigo-700 text-white py-3 px-4 rounded-lg font-medium transition opacity-50 cursor-not-allowed"> |
|
|
Convert to MP3 |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="fileInfo" class="hidden mt-6 bg-blue-50 border-l-4 border-blue-500 p-4 rounded"> |
|
|
<div class="flex items-start"> |
|
|
<div class="flex-shrink-0 pt-1"> |
|
|
<i class="fas fa-info-circle text-blue-500"></i> |
|
|
</div> |
|
|
<div class="ml-3"> |
|
|
<h3 class="text-sm font-medium text-blue-800">File Selected</h3> |
|
|
<div class="mt-1 text-sm text-blue-700"> |
|
|
<p id="fileName" class="font-medium"></p> |
|
|
<p id="fileSize" class="text-xs mt-1"></p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="progressContainer" class="hidden mt-6"> |
|
|
<div class="flex justify-between mb-1"> |
|
|
<span class="text-sm font-medium text-gray-700">Conversion Progress</span> |
|
|
<span id="progressPercent" class="text-sm font-medium text-gray-700">0%</span> |
|
|
</div> |
|
|
<div class="w-full bg-gray-200 rounded-full h-2.5"> |
|
|
<div id="progressBar" class="progress-bar bg-indigo-600 h-2.5 rounded-full" style="width: 0%"></div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="resultContainer" class="hidden mt-6 bg-green-50 border-l-4 border-green-500 p-4 rounded"> |
|
|
<div class="flex items-start"> |
|
|
<div class="flex-shrink-0 pt-1"> |
|
|
<i class="fas fa-check-circle text-green-500"></i> |
|
|
</div> |
|
|
<div class="ml-3"> |
|
|
<h3 class="text-sm font-medium text-green-800">Conversion Complete!</h3> |
|
|
<div class="mt-2 flex items-center"> |
|
|
<audio id="audioPreview" controls class="mr-4"></audio> |
|
|
<a id="downloadBtn" href="#" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"> |
|
|
<i class="fas fa-download mr-2"></i> Download MP3 |
|
|
</a> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="bg-gray-50 px-6 py-4 border-t border-gray-200"> |
|
|
<div class="flex items-center justify-between"> |
|
|
<p class="text-sm text-gray-500"> |
|
|
<i class="fas fa-lock mr-1"></i> Your files are processed locally and never uploaded |
|
|
</p> |
|
|
<button id="resetBtn" class="text-sm text-indigo-600 hover:text-indigo-800 hidden"> |
|
|
<i class="fas fa-redo mr-1"></i> Start Over |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="mt-12 text-center text-gray-500 text-sm"> |
|
|
<p>Convert unlimited MP4 files to MP3 for free. No registration required.</p> |
|
|
<p class="mt-1">Works entirely in your browser - no server processing.</p> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
const dropzone = document.getElementById('dropzone'); |
|
|
const fileInput = document.getElementById('fileInput'); |
|
|
const browseBtn = document.getElementById('browseBtn'); |
|
|
const convertBtn = document.getElementById('convertBtn'); |
|
|
const fileInfo = document.getElementById('fileInfo'); |
|
|
const fileName = document.getElementById('fileName'); |
|
|
const fileSize = document.getElementById('fileSize'); |
|
|
const progressContainer = document.getElementById('progressContainer'); |
|
|
const progressBar = document.getElementById('progressBar'); |
|
|
const progressPercent = document.getElementById('progressPercent'); |
|
|
const resultContainer = document.getElementById('resultContainer'); |
|
|
const audioPreview = document.getElementById('audioPreview'); |
|
|
const downloadBtn = document.getElementById('downloadBtn'); |
|
|
const resetBtn = document.getElementById('resetBtn'); |
|
|
const qualitySelect = document.getElementById('qualitySelect'); |
|
|
|
|
|
let currentFile = null; |
|
|
let audioBlob = null; |
|
|
|
|
|
|
|
|
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { |
|
|
dropzone.addEventListener(eventName, preventDefaults, false); |
|
|
}); |
|
|
|
|
|
function preventDefaults(e) { |
|
|
e.preventDefault(); |
|
|
e.stopPropagation(); |
|
|
} |
|
|
|
|
|
['dragenter', 'dragover'].forEach(eventName => { |
|
|
dropzone.addEventListener(eventName, highlight, false); |
|
|
}); |
|
|
|
|
|
['dragleave', 'drop'].forEach(eventName => { |
|
|
dropzone.addEventListener(eventName, unhighlight, false); |
|
|
}); |
|
|
|
|
|
function highlight() { |
|
|
dropzone.classList.add('active'); |
|
|
} |
|
|
|
|
|
function unhighlight() { |
|
|
dropzone.classList.remove('active'); |
|
|
} |
|
|
|
|
|
dropzone.addEventListener('drop', handleDrop, false); |
|
|
|
|
|
function handleDrop(e) { |
|
|
const dt = e.dataTransfer; |
|
|
const files = dt.files; |
|
|
handleFiles(files); |
|
|
} |
|
|
|
|
|
|
|
|
browseBtn.addEventListener('click', () => { |
|
|
fileInput.click(); |
|
|
}); |
|
|
|
|
|
fileInput.addEventListener('change', () => { |
|
|
if (fileInput.files.length) { |
|
|
handleFiles(fileInput.files); |
|
|
} |
|
|
}); |
|
|
|
|
|
function handleFiles(files) { |
|
|
const file = files[0]; |
|
|
|
|
|
|
|
|
if (!file.type.includes('mp4') && !file.name.toLowerCase().endsWith('.mp4')) { |
|
|
showError('Please select an MP4 video file.'); |
|
|
return; |
|
|
} |
|
|
|
|
|
currentFile = file; |
|
|
|
|
|
|
|
|
fileName.textContent = file.name; |
|
|
fileSize.textContent = formatFileSize(file.size); |
|
|
fileInfo.classList.remove('hidden'); |
|
|
|
|
|
|
|
|
convertBtn.disabled = false; |
|
|
convertBtn.classList.remove('opacity-50', 'cursor-not-allowed'); |
|
|
|
|
|
|
|
|
resultContainer.classList.add('hidden'); |
|
|
resetBtn.classList.add('hidden'); |
|
|
} |
|
|
|
|
|
function formatFileSize(bytes) { |
|
|
if (bytes === 0) return '0 Bytes'; |
|
|
const k = 1024; |
|
|
const sizes = ['Bytes', 'KB', 'MB', 'GB']; |
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k)); |
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; |
|
|
} |
|
|
|
|
|
|
|
|
convertBtn.addEventListener('click', startConversion); |
|
|
|
|
|
function startConversion() { |
|
|
if (!currentFile) return; |
|
|
|
|
|
|
|
|
progressContainer.classList.remove('hidden'); |
|
|
progressBar.style.width = '0%'; |
|
|
progressPercent.textContent = '0%'; |
|
|
|
|
|
|
|
|
convertBtn.disabled = true; |
|
|
convertBtn.classList.add('opacity-50', 'cursor-not-allowed'); |
|
|
|
|
|
|
|
|
let progress = 0; |
|
|
const interval = setInterval(() => { |
|
|
progress += Math.random() * 10; |
|
|
if (progress >= 100) { |
|
|
progress = 100; |
|
|
clearInterval(interval); |
|
|
completeConversion(); |
|
|
} |
|
|
|
|
|
progressBar.style.width = `${progress}%`; |
|
|
progressPercent.textContent = `${Math.round(progress)}%`; |
|
|
}, 300); |
|
|
} |
|
|
|
|
|
function completeConversion() { |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
|
const audioUrl = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3'; |
|
|
|
|
|
fetch(audioUrl) |
|
|
.then(response => response.blob()) |
|
|
.then(blob => { |
|
|
audioBlob = blob; |
|
|
|
|
|
|
|
|
const audioUrl = URL.createObjectURL(blob); |
|
|
audioPreview.src = audioUrl; |
|
|
|
|
|
|
|
|
const downloadUrl = URL.createObjectURL(blob); |
|
|
const originalName = currentFile.name.replace(/\.[^/.]+$/, ""); |
|
|
downloadBtn.href = downloadUrl; |
|
|
downloadBtn.download = `${originalName}.mp3`; |
|
|
|
|
|
|
|
|
resultContainer.classList.remove('hidden'); |
|
|
resetBtn.classList.remove('hidden'); |
|
|
|
|
|
|
|
|
progressContainer.classList.add('hidden'); |
|
|
}); |
|
|
}, 500); |
|
|
} |
|
|
|
|
|
|
|
|
resetBtn.addEventListener('click', resetConverter); |
|
|
|
|
|
function resetConverter() { |
|
|
|
|
|
currentFile = null; |
|
|
audioBlob = null; |
|
|
|
|
|
fileInfo.classList.add('hidden'); |
|
|
progressContainer.classList.add('hidden'); |
|
|
resultContainer.classList.add('hidden'); |
|
|
resetBtn.classList.add('hidden'); |
|
|
|
|
|
convertBtn.disabled = true; |
|
|
convertBtn.classList.add('opacity-50', 'cursor-not-allowed'); |
|
|
|
|
|
fileInput.value = ''; |
|
|
audioPreview.src = ''; |
|
|
} |
|
|
|
|
|
|
|
|
function showError(message) { |
|
|
const errorDiv = document.createElement('div'); |
|
|
errorDiv.className = 'fixed top-4 right-4 bg-red-100 border-l-4 border-red-500 text-red-700 p-4 rounded shadow-lg z-50'; |
|
|
errorDiv.innerHTML = ` |
|
|
<div class="flex items-center"> |
|
|
<i class="fas fa-exclamation-circle mr-2"></i> |
|
|
<span>${message}</span> |
|
|
</div> |
|
|
`; |
|
|
document.body.appendChild(errorDiv); |
|
|
|
|
|
setTimeout(() => { |
|
|
errorDiv.classList.add('opacity-0', 'transition-opacity', 'duration-500'); |
|
|
setTimeout(() => errorDiv.remove(), 500); |
|
|
}, 3000); |
|
|
} |
|
|
}); |
|
|
</script> |
|
|
</body> |
|
|
</html> |