Initial commit: media-to-text app
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
import { pipeline, env } from 'https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.2';
|
||||
import * as OpenCC from 'https://cdn.jsdelivr.net/npm/opencc-js@1.0.5/+esm';
|
||||
|
||||
env.allowLocalModels = false;
|
||||
|
||||
let progressCallback = (progress) => {
|
||||
const status = document.getElementById('status');
|
||||
if (progress.status === 'progress') {
|
||||
status.textContent = `Downloading model... ${progress.file}`;
|
||||
} else if (progress.status === 'done') {
|
||||
status.textContent = 'Model loaded';
|
||||
}
|
||||
};
|
||||
|
||||
env.progress_callback = progressCallback;
|
||||
|
||||
const languageSelect = document.getElementById('language');
|
||||
const recordBtn = document.getElementById('record-btn');
|
||||
const stopBtn = document.getElementById('stop-btn');
|
||||
const fileInput = document.getElementById('file-input');
|
||||
const uploadBtn = document.getElementById('upload-btn');
|
||||
const output = document.getElementById('output');
|
||||
const status = document.getElementById('status');
|
||||
const copyBtn = document.getElementById('copy-btn');
|
||||
|
||||
let recognition;
|
||||
let transcriber = null;
|
||||
let currentModel = null;
|
||||
|
||||
const getModelForLanguage = () => 'Xenova/whisper-small';
|
||||
|
||||
fileInput.addEventListener('change', () => {
|
||||
uploadBtn.disabled = !fileInput.files.length;
|
||||
});
|
||||
|
||||
const extractAudio = async (file) => {
|
||||
status.textContent = `Reading file... (${(file.size / 1024 / 1024).toFixed(1)} MB)`;
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
status.textContent = 'Decoding audio... (page may be briefly unresponsive)';
|
||||
await new Promise(r => setTimeout(r, 100));
|
||||
const audioContext = new AudioContext({ sampleRate: 16000 });
|
||||
const audioBuffer = await new Promise((resolve, reject) => {
|
||||
audioContext.decodeAudioData(arrayBuffer, resolve, reject);
|
||||
});
|
||||
status.textContent = `Audio decoded (${Math.round(audioBuffer.duration)}s)`;
|
||||
await audioContext.close();
|
||||
return audioBuffer.getChannelData(0);
|
||||
};
|
||||
|
||||
uploadBtn.addEventListener('click', async () => {
|
||||
const file = fileInput.files[0];
|
||||
if (!file) return;
|
||||
|
||||
uploadBtn.disabled = true;
|
||||
output.textContent = '';
|
||||
|
||||
try {
|
||||
const selectedLang = languageSelect.value;
|
||||
const modelName = getModelForLanguage(selectedLang);
|
||||
|
||||
if (!transcriber || currentModel !== modelName) {
|
||||
status.textContent = 'Downloading model...';
|
||||
transcriber = await pipeline('automatic-speech-recognition', modelName);
|
||||
currentModel = modelName;
|
||||
}
|
||||
|
||||
status.textContent = 'Processing audio...';
|
||||
const audio = await extractAudio(file);
|
||||
|
||||
status.textContent = 'Transcribing...';
|
||||
const langCode = selectedLang.split('-')[0];
|
||||
const result = await transcriber(audio, {
|
||||
language: langCode,
|
||||
chunk_length_s: 30,
|
||||
stride_length_s: 5,
|
||||
return_timestamps: false
|
||||
});
|
||||
let text = result.text;
|
||||
if (selectedLang === 'zh-CN') {
|
||||
const converter = OpenCC.Converter({ from: 'tw', to: 'cn' });
|
||||
text = converter(text);
|
||||
}
|
||||
output.textContent = text;
|
||||
copyBtn.style.display = 'block';
|
||||
status.textContent = 'Complete';
|
||||
} catch (err) {
|
||||
status.textContent = `Error: ${err.message}`;
|
||||
} finally {
|
||||
uploadBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
recordBtn.addEventListener('click', async () => {
|
||||
try {
|
||||
await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
|
||||
recognition = new webkitSpeechRecognition();
|
||||
recognition.continuous = true;
|
||||
recognition.interimResults = true;
|
||||
recognition.lang = languageSelect.value;
|
||||
|
||||
let transcript = '';
|
||||
|
||||
recognition.onresult = (event) => {
|
||||
let interim = '';
|
||||
for (let i = event.resultIndex; i < event.results.length; i++) {
|
||||
if (event.results[i].isFinal) {
|
||||
transcript += event.results[i][0].transcript + ' ';
|
||||
} else {
|
||||
interim += event.results[i][0].transcript;
|
||||
}
|
||||
}
|
||||
output.textContent = transcript + interim;
|
||||
copyBtn.style.display = 'block';
|
||||
};
|
||||
|
||||
recognition.start();
|
||||
recordBtn.disabled = true;
|
||||
stopBtn.disabled = false;
|
||||
status.textContent = 'Recording...';
|
||||
|
||||
} catch (err) {
|
||||
status.textContent = 'Microphone access denied';
|
||||
}
|
||||
});
|
||||
|
||||
stopBtn.addEventListener('click', () => {
|
||||
if (recognition) {
|
||||
recognition.stop();
|
||||
recordBtn.disabled = false;
|
||||
stopBtn.disabled = true;
|
||||
status.textContent = 'Recording stopped';
|
||||
}
|
||||
});
|
||||
|
||||
copyBtn.addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(output.textContent);
|
||||
status.textContent = 'Copied to clipboard';
|
||||
});
|
||||
Reference in New Issue
Block a user