// ===== FUNÇÕES PARA UPLOAD DIRETO AO CLOUDINARY ===== /** * Função para fazer upload direto de arquivo para Cloudinary * @param {File} file - Arquivo a ser enviado * @param {Function} progressCallback - Callback para atualizar progresso * @returns {Promise} - Resultado do upload com URL */ async function uploadToCloudinary(file, progressCallback = null) { console.log('📤 [CLOUDINARY-UPLOAD] Iniciando upload direto para Cloudinary'); console.log('📤 [CLOUDINARY-UPLOAD] Arquivo:', file.name); console.log('📤 [CLOUDINARY-UPLOAD] Tamanho:', (file.size / (1024 * 1024)).toFixed(2), 'MB'); try { // Obter assinatura do servidor console.log('🔐 [CLOUDINARY-UPLOAD] Obtendo assinatura do servidor...'); const signatureResponse = await fetch('/api/cloudinary/signature', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ folder: 'temp_transcriptions', resource_type: 'video' // Suporta áudio e vídeo }) }); if (!signatureResponse.ok) { throw new Error('Erro ao obter assinatura do Cloudinary'); } const signatureData = await signatureResponse.json(); console.log('✅ [CLOUDINARY-UPLOAD] Assinatura obtida com sucesso'); // Preparar FormData para upload const formData = new FormData(); formData.append('file', file); formData.append('api_key', signatureData.apiKey); formData.append('timestamp', signatureData.timestamp); formData.append('signature', signatureData.signature); formData.append('folder', signatureData.folder); formData.append('resource_type', signatureData.resource_type); // Fazer upload com progresso e heartbeat console.log('📤 [CLOUDINARY-UPLOAD] Iniciando upload...'); return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); let heartbeatInterval = null; let lastProgressTime = Date.now(); // Heartbeat para manter conexão viva durante uploads grandes const startHeartbeat = () => { heartbeatInterval = setInterval(() => { const now = Date.now(); const timeSinceLastProgress = now - lastProgressTime; // Se não houve progresso por mais de 30 segundos, enviar heartbeat if (timeSinceLastProgress > 30000) { console.log('💓 [CLOUDINARY-UPLOAD] Enviando heartbeat...'); // Fazer uma pequena requisição para manter conexão viva fetch('/api/cloudinary/signature', { method: 'HEAD' }).catch(() => {}); lastProgressTime = now; } }, 15000); // Verificar a cada 15 segundos }; const stopHeartbeat = () => { if (heartbeatInterval) { clearInterval(heartbeatInterval); heartbeatInterval = null; } }; // Configurar progresso xhr.upload.addEventListener('progress', (event) => { lastProgressTime = Date.now(); if (event.lengthComputable && progressCallback) { const percentComplete = Math.round((event.loaded / event.total) * 100); console.log(`📤 [CLOUDINARY-UPLOAD] Progresso: ${percentComplete}%`); progressCallback(percentComplete); } }); // Configurar resposta xhr.addEventListener('load', () => { stopHeartbeat(); if (xhr.status === 200) { try { const result = JSON.parse(xhr.responseText); console.log('✅ [CLOUDINARY-UPLOAD] Upload concluído com sucesso!'); console.log('✅ [CLOUDINARY-UPLOAD] URL:', result.secure_url); console.log('✅ [CLOUDINARY-UPLOAD] Public ID:', result.public_id); resolve(result); } catch (parseError) { console.error('❌ [CLOUDINARY-UPLOAD] Erro ao processar resposta:', parseError); reject(new Error('Erro ao processar resposta do Cloudinary')); } } else { console.error('❌ [CLOUDINARY-UPLOAD] Erro HTTP:', xhr.status, xhr.statusText); reject(new Error(`Erro no upload: ${xhr.status} ${xhr.statusText}`)); } }); // Configurar erro xhr.addEventListener('error', () => { stopHeartbeat(); console.error('❌ [CLOUDINARY-UPLOAD] Erro de rede durante upload'); reject(new Error('Erro de rede durante upload para Cloudinary')); }); // Configurar timeout xhr.addEventListener('timeout', () => { stopHeartbeat(); console.error('❌ [CLOUDINARY-UPLOAD] Timeout durante upload'); reject(new Error('Timeout durante upload para Cloudinary')); }); // Configurar e enviar requisição xhr.open('POST', `https://api.cloudinary.com/v1_1/${signatureData.cloudName}/video/upload`); // Timeout mais longo para arquivos grandes (10 minutos) const timeoutDuration = Math.max(600000, file.size / 1024 / 1024 * 2000); // 2 segundos por MB, mínimo 10 minutos xhr.timeout = timeoutDuration; console.log(`⏱️ [CLOUDINARY-UPLOAD] Timeout configurado para: ${Math.round(timeoutDuration/1000)}s`); // Iniciar heartbeat para arquivos grandes (>50MB) if (file.size > 50 * 1024 * 1024) { console.log('💓 [CLOUDINARY-UPLOAD] Iniciando heartbeat para arquivo grande'); startHeartbeat(); } xhr.send(formData); }); } catch (error) { console.error('❌ [CLOUDINARY-UPLOAD] Erro geral:', error); throw new Error(`Falha no upload para Cloudinary: ${error.message}`); } } /** * Função para deletar arquivo do Cloudinary * @param {String} publicId - Public ID do arquivo no Cloudinary * @returns {Promise} */ async function deleteFromCloudinary(publicId) { if (!publicId) { console.warn('🗑️ [CLOUDINARY-DELETE] Public ID não fornecido'); return; } try { console.log('🗑️ [CLOUDINARY-DELETE] Deletando arquivo:', publicId); const response = await fetch('/api/cloudinary/delete', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ public_id: publicId }) }); if (response.ok) { console.log('✅ [CLOUDINARY-DELETE] Arquivo deletado com sucesso'); } else { console.warn('⚠️ [CLOUDINARY-DELETE] Erro ao deletar (não crítico):', response.status); } } catch (error) { console.warn('⚠️ [CLOUDINARY-DELETE] Erro ao deletar (não crítico):', error.message); // Não propagar erro de limpeza } }