creadas dos versiones. la primera exporta solo a wav, la segunda, exporta igual al formato de entrada
This commit is contained in:
154
index.html.v1
Normal file
154
index.html.v1
Normal file
@@ -0,0 +1,154 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user