Files
audio-cutter-pwa/index.html.v1

155 lines
6.1 KiB
Plaintext

<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Audio Cutter PWA</title>
<link rel="manifest" href="manifest.json">
<script src="wavesurfer.min.js"></script>
<script src="plugins/regions.min.js"></script>
<style>
body { font-family: sans-serif; background: #121212; color: white; display: flex; flex-direction: column; align-items: center; padding: 20px; }
#waveform { width: 100%; max-width: 800px; background: #222; border-radius: 8px; margin: 20px 0; }
.controls { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }
button { padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; font-weight: bold; }
.btn-play { background: #4CAF50; color: white; }
.btn-export { background: #2196F3; color: white; }
input[type="file"] { margin-bottom: 20px; }
</style>
</head>
<body>
<h1>Cortador de Audio PWA</h1>
<input type="file" id="audioInput" accept="audio/*">
<div id="waveform"></div>
<div class="controls">
<button class="btn-play" id="btnPlay">Reproducir Selección</button>
<button class="btn-export" id="btnExport">Exportar Corte</button>
</div>
<script>
let ws, wsRegions;
const audioInput = document.getElementById('audioInput');
const btnPlay = document.getElementById('btnPlay');
const btnExport = document.getElementById('btnExport');
// Inicializar Wavesurfer
ws = WaveSurfer.create({
container: '#waveform',
waveColor: '#4F4A85',
progressColor: '#383351',
responsive: true,
});
// Plugin de regiones (para seleccionar el área)
wsRegions = ws.registerPlugin(WaveSurfer.Regions.create());
audioInput.onchange = (e) => {
const file = e.target.files[0];
if (file) {
const url = URL.createObjectURL(file);
ws.load(url);
}
};
ws.on('ready', () => {
wsRegions.clearRegions();
wsRegions.addRegion({
start: 0,
end: ws.getDuration() / 4,
color: 'rgba(0, 255, 0, 0.3)',
drag: true,
resize: true
});
});
btnPlay.onclick = () => {
const region = Object.values(wsRegions.getRegions())[0];
if (region) region.play();
};
btnExport.onclick = async () => {
const region = Object.values(wsRegions.getRegions())[0];
if (!region) return alert("Selecciona un área primero");
const originalBuffer = ws.getDecodedData();
const start = region.start;
const end = region.end;
const segmentBuffer = cutAudio(originalBuffer, start, end);
downloadAudio(segmentBuffer);
};
function cutAudio(buffer, start, end) {
const sampleRate = buffer.sampleRate;
const frameCount = (end - start) * sampleRate;
const newBuffer = new AudioContext().createBuffer(buffer.numberOfChannels, frameCount, sampleRate);
for (let i = 0; i < buffer.numberOfChannels; i++) {
const channelData = buffer.getChannelData(i).slice(start * sampleRate, end * sampleRate);
newBuffer.copyToChannel(channelData, i);
}
return newBuffer;
}
function downloadAudio(buffer) {
// Conversión simple a WAV para exportación rápida
const wavData = bufferToWav(buffer);
const blob = new Blob([wavData], { type: 'audio/wav' });
const url = URL.createObjectURL(blob);
const anchor = document.createElement('a');
anchor.href = url;
anchor.download = "corte_audio.wav";
anchor.click();
}
// Helper para convertir AudioBuffer a formato WAV
function bufferToWav(buffer) {
let numOfChan = buffer.numberOfChannels,
length = buffer.length * numOfChan * 2 + 44,
bufferArr = new ArrayBuffer(length),
view = new DataView(bufferArr),
channels = [], i, sample,
offset = 0,
pos = 0;
function setUint16(data) { view.setUint16(pos, data, true); pos += 2; }
function setUint32(data) { view.setUint32(pos, data, true); pos += 4; }
setUint32(0x46464952); // "RIFF"
setUint32(length - 8); // file length
setUint32(0x45564157); // "WAVE"
setUint32(0x20746d66); // "fmt " chunk
setUint32(16); // length = 16
setUint16(1); // PCM (uncompressed)
setUint16(numOfChan);
setUint32(buffer.sampleRate);
setUint32(buffer.sampleRate * 2 * numOfChan); // avg. bytes/sec
setUint16(numOfChan * 2); // block-align
setUint16(16); // 16-bit
setUint32(0x61746164); // "data" chunk
setUint32(length - pos - 4); // chunk length
for(i=0; i<buffer.numberOfChannels; i++) channels.push(buffer.getChannelData(i));
while(pos < length) {
for(i=0; i<numOfChan; i++) { // interleave channels
sample = Math.max(-1, Math.min(1, channels[i][offset]));
sample = (sample < 0 ? sample * 0x8000 : sample * 0x7FFF);
view.setInt16(pos, sample, true);
pos += 2;
}
offset++;
}
return bufferArr;
}
// Registro del Service Worker para PWA
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js');
}
</script>
</body>
</html>