81 lines
2.6 KiB
TypeScript
81 lines
2.6 KiB
TypeScript
|
|
export const base64ToUint8Array = (base64: string) => {
|
|
const binaryString = atob(base64);
|
|
const len = binaryString.length;
|
|
const bytes = new Uint8Array(len);
|
|
for (let i = 0; i < len; i++) {
|
|
bytes[i] = binaryString.charCodeAt(i);
|
|
}
|
|
return bytes;
|
|
};
|
|
|
|
export const uint8ArrayToBase64 = (bytes: Uint8Array) => {
|
|
let binary = '';
|
|
const len = bytes.byteLength;
|
|
for (let i = 0; i < len; i++) {
|
|
binary += String.fromCharCode(bytes[i]);
|
|
}
|
|
return btoa(binary);
|
|
};
|
|
|
|
export const triggerDownload = (blob: Blob, filename: string) => {
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = filename;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(url);
|
|
};
|
|
|
|
export const createWavFileFromPcm = (pcmData: Uint8Array, sampleRate: number = 24000, numChannels: number = 1): Blob => {
|
|
const header = new ArrayBuffer(44);
|
|
const view = new DataView(header);
|
|
|
|
const writeString = (view: DataView, offset: number, string: string) => {
|
|
for (let i = 0; i < string.length; i++) {
|
|
view.setUint8(offset + i, string.charCodeAt(i));
|
|
}
|
|
};
|
|
|
|
writeString(view, 0, 'RIFF');
|
|
view.setUint32(4, 36 + pcmData.length, true);
|
|
writeString(view, 8, 'WAVE');
|
|
writeString(view, 12, 'fmt ');
|
|
view.setUint32(16, 16, true);
|
|
view.setUint16(20, 1, true);
|
|
view.setUint16(22, numChannels, true);
|
|
view.setUint32(24, sampleRate, true);
|
|
view.setUint32(28, sampleRate * numChannels * 2, true);
|
|
view.setUint16(32, numChannels * 2, true);
|
|
view.setUint16(34, 16, true);
|
|
writeString(view, 36, 'data');
|
|
view.setUint32(40, pcmData.length, true);
|
|
|
|
return new Blob([view, pcmData], { type: 'audio/wav' });
|
|
};
|
|
|
|
export const processAndDownloadAudio = (base64Data: string, filename: string) => {
|
|
try {
|
|
// Check for RIFF header (WAV)
|
|
// Some base64 strings might have newlines, strip them if necessary,
|
|
// but generally atob handles it or we assume clean base64.
|
|
const binaryString = atob(base64Data.substring(0, 50).replace(/\s/g, ''));
|
|
const isWav = binaryString.startsWith('RIFF');
|
|
|
|
if (isWav) {
|
|
const bytes = base64ToUint8Array(base64Data);
|
|
const blob = new Blob([bytes], { type: 'audio/wav' });
|
|
triggerDownload(blob, filename);
|
|
} else {
|
|
// Assume Raw PCM 24kHz 16-bit Mono (Gemini Flash TTS default)
|
|
const bytes = base64ToUint8Array(base64Data);
|
|
const blob = createWavFileFromPcm(bytes, 24000, 1);
|
|
triggerDownload(blob, filename);
|
|
}
|
|
} catch (e) {
|
|
console.error("Error downloading audio", e);
|
|
}
|
|
};
|