AutoWhisper / index.html
luigi12345's picture
Update index.html
eaebd3d verified
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<title>AudioPro X - Ultimate Media Processor</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- CDNs Premium -->
<script src="https://cdn.jsdelivr.net/npm/@xenova/transformers@2.4.0"></script>
<script src="https://cdn.jsdelivr.net/npm/uikit@3.16.22/dist/js/uikit.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/uikit@3.16.22/dist/css/uikit.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@7.2.96/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<style>
:root {
--gradient-primary: linear-gradient(135deg, #6366f1 0%, #a855f7 50%, #ec4899 100%);
}
body {
font-family: 'Inter', sans-serif;
background: #0f172a;
color: #f8fafc;
min-height: 100vh;
}
.glass-panel {
background: rgba(30, 41, 59, 0.7);
backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16px;
}
.waveform {
background: rgba(148, 163, 184, 0.2);
height: 120px;
border-radius: 12px;
position: relative;
overflow: hidden;
}
.waveform canvas {
width: 100%;
height: 100%;
display: block;
}
</style>
</head>
<body class="uk-padding">
<div class="uk-container uk-container-expand uk-margin-medium-top">
<div class="glass-panel uk-padding-large">
<!-- Header -->
<div class="uk-flex uk-flex-middle uk-margin-medium-bottom">
<h1 class="uk-heading-medium uk-margin-remove">AudioPro X</h1>
<div class="uk-margin-left">
<span class="uk-badge" style="background: var(--gradient-primary)">v2.1.0</span>
</div>
</div>
<!-- Model Loader -->
<div class="uk-margin-large">
<button
id="loadModelBtn"
class="uk-button uk-button-large uk-border-pill"
style="background: var(--gradient-primary)"
onclick="loadModel()"
>
<span class="mdi mdi-brain uk-margin-small-right"></span>
Cargar Modelo AI
</button>
<div id="modelStatus" class="uk-text-meta uk-margin-small-top"></div>
</div>
<!-- Main Interface -->
<div class="uk-grid-large" uk-grid>
<!-- File Upload & Recorder -->
<div class="uk-width-1-3@m">
<div class="glass-panel uk-padding">
<div class="uk-text-center">
<!-- Waveform Container -->
<div class="waveform uk-margin-bottom" id="waveform">
<canvas id="waveformCanvas"></canvas>
</div>
<!-- File Input -->
<input
type="file"
id="mediaInput"
hidden
accept="video/*,audio/*"
/>
<button
class="uk-button uk-button-default uk-width-1-1 uk-border-pill"
onclick="document.getElementById('mediaInput').click()"
>
<span class="mdi mdi-upload uk-margin-small-right"></span>
Subir Multimedia
</button>
<!-- Record Button -->
<div class="uk-margin">
<button
id="recordBtn"
class="uk-button uk-button-danger uk-border-pill"
onclick="toggleRecording()"
>
<span class="mdi mdi-microphone"></span>
</button>
</div>
</div>
</div>
</div>
<!-- Processing -->
<div class="uk-width-2-3@m">
<div class="glass-panel uk-padding">
<div id="progressBar" class="uk-progress uk-margin" hidden>
<div
class="uk-progress-bar"
style="width: 0%; background: var(--gradient-primary)"
></div>
</div>
<div id="outputContainer" class="uk-margin"></div>
<div class="uk-grid-small" uk-grid>
<div class="uk-width-auto">
<select id="formatSelect" class="uk-select uk-border-pill">
<option value="wav">WAV</option>
<option value="opus" selected>WhatsApp Audio (OPUS)</option>
<option value="mp3">MP3</option>
</select>
</div>
<div class="uk-width-expand">
<button
class="uk-button uk-button-primary uk-width-1-1 uk-border-pill"
onclick="processMedia()"
>
<span class="mdi mdi-autorenew uk-margin-small-right"></span>
Procesar Media
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
let mediaRecorder;
let audioChunks = [];
let pipeline = null;
// For Visualization
let audioCtx;
let analyser;
let dataArray;
let animationId;
/**
* Carga el modelo Whisper.
*/
async function loadModel() {
try {
document.getElementById('modelStatus').textContent = '⏳ Cargando modelo...';
document.getElementById('loadModelBtn').disabled = true;
// Carga de modelo con Transformers.js
pipeline = await transformers.pipeline(
'automatic-speech-recognition',
'Xenova/whisper-small'
);
document.getElementById('modelStatus').innerHTML =
'✅ Modelo cargado: <span class="uk-text-bold">Whisper v2</span>';
} catch (error) {
showError('Error al cargar el modelo: ' + error.message);
document.getElementById('loadModelBtn').disabled = false;
}
}
/**
* Procesa un archivo multimedia (audio/video) ya cargado.
*/
async function processMedia() {
const file = document.getElementById('mediaInput').files[0];
if (!file) {
return showError('Selecciona un archivo primero');
}
if (!pipeline) {
return showError('Carga el modelo primero');
}
try {
toggleUI(false);
updateProgress(30);
const output = await pipeline(file, {
chunk_length_s: 30,
stride_length_s: 5,
return_timestamps: true
});
updateProgress(100);
showResult(output.text);
} catch (error) {
showError('Error de procesamiento: ' + error.message);
} finally {
toggleUI(true);
}
}
/**
* Graba o detiene la grabación de audio a través del micrófono.
*/
async function toggleRecording() {
// Si no existe mediaRecorder aún, inicializar
if (!mediaRecorder) {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
// Configurar MediaRecorder
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.ondataavailable = e => audioChunks.push(e.data);
mediaRecorder.onstop = async () => {
// Detener la visualización y cerrar el contexto de audio
stopVisualizer();
// Crear Blob final
const audioBlob = new Blob(audioChunks, { type: 'audio/ogg; codecs=opus' });
// Si el modelo está cargado, procesamos inmediatamente el audio grabado
if (pipeline) {
toggleUI(false);
updateProgress(30);
try {
const output = await pipeline(audioBlob, {
chunk_length_s: 30,
stride_length_s: 5,
return_timestamps: true
});
updateProgress(100);
showResult(output.text);
} catch (err) {
showError('Error al procesar audio grabado: ' + err.message);
} finally {
toggleUI(true);
}
} else {
showError('El modelo no está cargado aún. Por favor, cárgalo antes de grabar.');
}
};
// Inicializar y comenzar la grabación
audioChunks = [];
mediaRecorder.start();
document.getElementById('recordBtn').classList.add('uk-button-danger');
// Iniciar visualización
startVisualizer(stream);
} catch (error) {
showError('No se pudo acceder al micrófono: ' + error.message);
}
} else {
// Si ya existe un recorder, alternar entre grabar y parar
if (mediaRecorder.state === 'recording') {
mediaRecorder.stop();
document.getElementById('recordBtn').classList.remove('uk-button-danger');
} else {
audioChunks = [];
mediaRecorder.start();
document.getElementById('recordBtn').classList.add('uk-button-danger');
}
}
}
/**
* Inicia el visualizador de forma de onda usando un AnalyserNode.
*/
function startVisualizer(stream) {
audioCtx = new AudioContext();
const source = audioCtx.createMediaStreamSource(stream);
analyser = audioCtx.createAnalyser();
analyser.fftSize = 2048;
const bufferLength = analyser.fftSize;
dataArray = new Uint8Array(bufferLength);
source.connect(analyser);
drawWaveform();
}
/**
* Dibujado del waveform en el canvas por cada frame.
*/
function drawWaveform() {
const canvas = document.getElementById('waveformCanvas');
const canvasCtx = canvas.getContext('2d');
const width = canvas.width = canvas.offsetWidth;
const height = canvas.height = canvas.offsetHeight;
// Animación
function draw() {
animationId = requestAnimationFrame(draw);
analyser.getByteTimeDomainData(dataArray);
// Limpiar canvas
canvasCtx.clearRect(0, 0, width, height);
// Dibujar waveform
canvasCtx.lineWidth = 2;
canvasCtx.beginPath();
let sliceWidth = width * 1.0 / dataArray.length;
let x = 0;
for (let i = 0; i < dataArray.length; i++) {
const v = dataArray[i] / 128.0;
const y = (v * height) / 2;
if (i === 0) {
canvasCtx.moveTo(x, y);
} else {
canvasCtx.lineTo(x, y);
}
x += sliceWidth;
}
canvasCtx.lineTo(width, height / 2);
canvasCtx.stroke();
}
draw();
}
/**
* Detiene la visualización de la forma de onda y cierra el audio context.
*/
function stopVisualizer() {
if (animationId) {
cancelAnimationFrame(animationId);
animationId = null;
}
if (audioCtx) {
audioCtx.close();
audioCtx = null;
}
}
// Funciones auxiliares
function toggleUI(enabled) {
document.querySelectorAll('button, select').forEach(el => el.disabled = !enabled);
document.getElementById('progressBar').hidden = enabled;
}
function updateProgress(percent) {
document.querySelector('.uk-progress-bar').style.width = percent + '%';
}
function showResult(text) {
const output = `
<div class="uk-alert-success" uk-alert>
<h3>Resultado del procesamiento:</h3>
<p>${text}</p>
</div>`;
document.getElementById('outputContainer').innerHTML = output;
}
function showError(message) {
UIkit.notification({
message: `<span class="mdi mdi-alert-circle"></span> ${message}`,
status: 'danger',
pos: 'top-center'
});
}
</script>
</body>
</html>