stud-manager / utils /mediaHelpers.ts
dvc890's picture
Upload 67 files
801f0a7 verified
// Utility to handle Base64 conversion
export const blobToBase64 = (blob: Blob): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => {
if (typeof reader.result === 'string') {
// Remove data URL prefix
const base64 = reader.result.split(',')[1];
resolve(base64);
} else {
reject(new Error('Failed to convert blob to base64'));
}
};
reader.onerror = reject;
reader.readAsDataURL(blob);
});
};
// Optimized Image Compression Utility
export const compressImage = (file: File, maxWidth = 1600, quality = 0.7): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = (event) => {
const img = new Image();
img.src = event.target?.result as string;
img.onload = () => {
const canvas = document.createElement('canvas');
let width = img.width;
let height = img.height;
if (width > maxWidth) {
height = Math.round((height * maxWidth) / width);
width = maxWidth;
}
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
if (!ctx) {
reject(new Error('Canvas context failed'));
return;
}
ctx.drawImage(img, 0, 0, width, height);
// Output as JPEG with quality reduction
const dataUrl = canvas.toDataURL('image/jpeg', quality);
// Remove prefix to get raw base64
const base64 = dataUrl.split(',')[1];
resolve(base64);
};
img.onerror = (err) => reject(err);
};
reader.onerror = (err) => reject(err);
});
};
// --- RAW PCM Decoding Logic ---
export const base64ToUint8Array = (base64: string) => {
const binaryString = window.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;
};
// Gemini TTS typically returns 24000Hz, single channel, Int16 PCM
export const decodePCM = (data: Uint8Array, ctx: AudioContext) => {
const sampleRate = 24000;
const int16Data = new Int16Array(data.buffer);
const float32Data = new Float32Array(int16Data.length);
// Convert Int16 to Float32 [-1.0, 1.0]
for (let i = 0; i < int16Data.length; i++) {
float32Data[i] = int16Data[i] / 32768.0;
}
const buffer = ctx.createBuffer(1, float32Data.length, sampleRate);
buffer.copyToChannel(float32Data, 0);
return buffer;
};
export const cleanTextForTTS = (text: string) => {
let clean = text
// 1. Remove Markdown headers, bold, italic markers, and blockquotes
.replace(/([#*`~>_])/g, '')
// 2. Remove links [text](url) -> text
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
// 4. Remove common Kaomoji patterns (Moved up)
.replace(/\([^\u4e00-\u9fa5A-Za-z,\.,。]{1,8}\)/g, '');
// 3. Remove Emojis (Safe way for older Safari)
try {
// Use constructor to avoid parse-time SyntaxError if unsupported
// 'v' flag is better but 'u' is more supported.
const emojiRegex = new RegExp('\\p{Extended_Pictographic}', 'gu');
clean = clean.replace(emojiRegex, '');
} catch (e) {
// Fallback for older browsers (approximate ranges)
// This is a basic range for common emojis
clean = clean.replace(/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}]/gu, '');
}
// 5. Consolidate whitespace
return clean.replace(/\s+/g, ' ').trim();
};