community_icon_html = """""" loading_icon_html = """""" share_js = """async () => { async function uploadFile(file){ const UPLOAD_URL = 'https://huggingface.co/uploads'; const response = await fetch(UPLOAD_URL, { method: 'POST', headers: { 'Content-Type': 'audio/wav', 'X-Requested-With': 'XMLHttpRequest', }, body: file, /// <- File inherits from Blob }); const url = await response.text(); return url; } function audioResample(buffer, sampleRate){ const offlineCtx = new OfflineAudioContext(2, (buffer.length / buffer.sampleRate) * sampleRate, sampleRate); const source = offlineCtx.createBufferSource(); source.buffer = buffer; source.connect(offlineCtx.destination); source.start(); return offlineCtx.startRendering(); }; function audioReduceChannels(buffer, targetChannelOpt){ if(targetChannelOpt === 'both' || buffer.numberOfChannels < 2) return buffer; const outBuffer = new AudioBuffer({ sampleRate: buffer.sampleRate, length: buffer.length, numberOfChannels: 1 }); const data = [buffer.getChannelData(0), buffer.getChannelData(1)]; const newData = new Float32Array(buffer.length); for(let i = 0; i < buffer.length; ++i) newData[i] = targetChannelOpt === 'left'? data[0][i] : targetChannelOpt === 'right'? data[1][i] : (data[0][i] + data[1][i]) / 2 ; outBuffer.copyToChannel(newData, 0); return outBuffer; }; function audioNormalize(buffer){ const data = Array.from(Array(buffer.numberOfChannels)).map((_, idx) => buffer.getChannelData(idx)); const maxAmplitude = Math.max(...data.map(chan => chan.reduce((acc, cur) => Math.max(acc, Math.abs(cur)), 0))); if(maxAmplitude >= 1.0) return buffer; const coeff = 1.0 / maxAmplitude; data.forEach(chan => { chan.forEach((v, idx) => chan[idx] = v*coeff); buffer.copyToChannel(chan, 0); }); return buffer; }; async function processAudioFile( audioBufferIn, targetChannelOpt, targetSampleRate ) { const resampled = await audioResample(audioBufferIn, targetSampleRate); const reduced = audioReduceChannels(resampled, targetChannelOpt); const normalized = audioNormalize(reduced); return normalized; } function audioToRawWave(audioChannels, bytesPerSample, mixChannels=false) { const bufferLength = audioChannels[0].length; const numberOfChannels = audioChannels.length === 1 ? 1 : 2; const reducedData = new Uint8Array( bufferLength * numberOfChannels * bytesPerSample ); for (let i = 0; i < bufferLength; ++i) { for ( let channel = 0; channel < (mixChannels ? 1 : numberOfChannels); ++channel ) { const outputIndex = (i * numberOfChannels + channel) * bytesPerSample; let sample; if (!mixChannels) sample = audioChannels[channel][i]; else sample = audioChannels.reduce((prv, cur) => prv + cur[i], 0) / numberOfChannels; sample = sample > 1 ? 1 : sample < -1 ? -1 : sample; //check for clipping //bit reduce and convert to Uint8 switch (bytesPerSample) { case 2: sample = sample * 32767; reducedData[outputIndex] = sample; reducedData[outputIndex + 1] = sample >> 8; break; case 1: reducedData[outputIndex] = (sample + 1) * 127; break; default: throw "Only 8, 16 bits per sample are supported"; } } } return reducedData; } function makeWav(data, channels, sampleRate, bytesPerSample) { const headerLength = 44; var wav = new Uint8Array(headerLength + data.length); var view = new DataView(wav.buffer); view.setUint32(0, 1380533830, false); // RIFF identifier 'RIFF' view.setUint32(4, 36 + data.length, true); // file length minus RIFF identifier length and file description length view.setUint32(8, 1463899717, false); // RIFF type 'WAVE' view.setUint32(12, 1718449184, false); // format chunk identifier 'fmt ' view.setUint32(16, 16, true); // format chunk length view.setUint16(20, 1, true); // sample format (raw) view.setUint16(22, channels, true); // channel count view.setUint32(24, sampleRate, true); // sample rate view.setUint32(28, sampleRate * bytesPerSample * channels, true); // byte rate (sample rate * block align) view.setUint16(32, bytesPerSample * channels, true); // block align (channel count * bytes per sample) view.setUint16(34, bytesPerSample * 8, true); // bits per sample view.setUint32(36, 1684108385, false); // data chunk identifier 'data' view.setUint32(40, data.length, true); // data chunk length wav.set(data, headerLength); return new Blob([wav.buffer], { type: "audio/wav" }); } const gradioEl = document.querySelector('body > gradio-app'); const audioEl = gradioEl.querySelector('audio'); const resultTxt = gradioEl.querySelector('#result-textarea textarea').value; const shareBtnEl = gradioEl.querySelector('#share-btn'); const shareIconEl = gradioEl.querySelector('#share-btn-share-icon'); const loadingIconEl = gradioEl.querySelector('#share-btn-loading-icon'); if(!audioEl){ return; }; shareBtnEl.style.pointerEvents = 'none'; shareIconEl.style.display = 'none'; loadingIconEl.style.removeProperty('display'); const res = await fetch(audioEl.src); const blob = await res.blob(); const channelOpt = "both"; const sampleRate = 48000; const bytesPerSample = 1; // or 2 const audioBufferIn = await new AudioContext().decodeAudioData( await blob.arrayBuffer() ); const audioBuffer = await processAudioFile( audioBufferIn, channelOpt, sampleRate ); const rawData = audioToRawWave( channelOpt === "both" ? [audioBuffer.getChannelData(0), audioBuffer.getChannelData(1)] : [audioBuffer.getChannelData(0)], bytesPerSample ); const blobWav = makeWav( rawData, channelOpt === "both" ? 2 : 1, sampleRate, bytesPerSample ); const fileName = `whisper-demo-input.wav`; const audioFile = new File([blobWav], fileName, { type: 'audio/wav' }); const url = await uploadFile(audioFile); const descriptionMd = `#### Input audio: #### Transcription: > ${resultTxt}`; const params = new URLSearchParams({ description: descriptionMd, }); const paramsStr = params.toString(); window.open(`https://huggingface.co/spaces/openai/whisper/discussions/new?${paramsStr}`, '_blank'); shareBtnEl.style.removeProperty('pointer-events'); shareIconEl.style.removeProperty('display'); loadingIconEl.style.display = 'none'; }"""