false, 'error' => 'Método no permitido']); exit; } try { // Verificar que se subió un archivo if (!isset($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) { // Log for debugging error_log('upload_media.php - FILES content: ' . print_r($_FILES, true)); // Make sure any buffered content is cleared before sending JSON if (ob_get_length()) ob_clean(); http_response_code(400); echo json_encode(['success' => false, 'error' => 'No se recibió ningún archivo o hubo un error en la carga']); exit; } $file = $_FILES['file']; $fileSize = $file['size']; $fileName = basename($file['name']); $fileTmpPath = $file['tmp_name']; $fileType = $file['type']; // Validar tamaño según tipo $maxSize = 16 * 1024 * 1024; // 16MB por defecto if (strpos($fileType, 'image/') === 0) { $maxSize = 5 * 1024 * 1024; // 5MB para imágenes } elseif (strpos($fileType, 'application/') === 0) { $maxSize = 100 * 1024 * 1024; // 100MB para documentos } if ($fileSize > $maxSize) { throw new Exception('El archivo es demasiado grande. Máximo: ' . ($maxSize / 1024 / 1024) . 'MB'); } // Validar tipos de archivo permitidos $allowedTypes = [ // Imágenes 'image/jpeg', 'image/jpg', 'image/png', 'image/webp', // Videos (webm será convertido a mp4) 'video/mp4', 'video/3gpp', 'video/quicktime', 'video/webm', // Audios (webm será convertido a ogg) 'audio/aac', 'audio/mp3', 'audio/mpeg', 'audio/ogg', 'audio/amr', 'audio/webm', // Documentos 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation' ]; if (!in_array($fileType, $allowedTypes)) { throw new Exception('Tipo de archivo no permitido: ' . $fileType); } // Crear directorio de uploads si no existe $uploadDir = __DIR__ . '/../uploads/'; if (!is_dir($uploadDir)) { mkdir($uploadDir, 0755, true); } // Generar nombre único $extension = pathinfo($fileName, PATHINFO_EXTENSION); $uniqueName = uniqid('media_', true) . '.' . $extension; $uploadPath = $uploadDir . $uniqueName; // Mover archivo if (!move_uploaded_file($fileTmpPath, $uploadPath)) { throw new Exception('Error al guardar el archivo en el servidor'); } // Convertir archivos WebM (audio y video) a formatos compatibles con WhatsApp $converted = false; $conversion_error = null; if (strpos($fileType, 'audio/webm') === 0 || strpos($fileType, 'video/webm') === 0) { // Evitar fatal si shell_exec está deshabilitado en el hosting if (function_exists('shell_exec')) { $ffmpeg = trim(@shell_exec('which ffmpeg 2>/dev/null')); if ($ffmpeg) { try { if (strpos($fileType, 'audio/webm') === 0) { // Audio WebM -> OGG/Opus (Compatible con WhatsApp Voice Messages) $convertedName = uniqid('media_', true) . '.ogg'; $convertedPath = $uploadDir . $convertedName; // Parámetros WhatsApp Voice: // -c:a libopus = códec OPUS (requerido para transcripción) // -b:a 32k = bitrate bajo para mantener archivo < 512KB (ícono play) // -ar 48000 = sample rate 48kHz (estándar OPUS) // -ac 1 = mono (reduce tamaño) // -vbr on = variable bitrate $cmd = escapeshellcmd($ffmpeg) . ' -y -i ' . escapeshellarg($uploadPath) . ' -map 0:a:0 -c:a libopus -b:a 32k -vbr on -ar 48000 -ac 1 -f ogg ' . escapeshellarg($convertedPath) . ' 2>&1'; $newType = 'audio/ogg'; $logType = 'Audio'; } else { // Video WebM -> MP4 $convertedName = uniqid('media_', true) . '.mp4'; $convertedPath = $uploadDir . $convertedName; // Convertir a MP4 con H.264 (compatible con WhatsApp) $cmd = escapeshellcmd($ffmpeg) . ' -y -i ' . escapeshellarg($uploadPath) . ' -c:v libx264 -preset fast -crf 23 -c:a aac -b:a 128k -movflags +faststart ' . escapeshellarg($convertedPath) . ' 2>&1'; $newType = 'video/mp4'; $logType = 'Video'; } $out = @shell_exec($cmd); // Añadir comprobación diagnóstica con "ffmpeg -i" sobre el archivo convertido $probe = @shell_exec(escapeshellcmd($ffmpeg) . ' -i ' . escapeshellarg($convertedPath) . ' 2>&1'); if (file_exists($convertedPath) && filesize($convertedPath) > 0) { // Reemplazar archivo original por el convertido @unlink($uploadPath); $uploadPath = $convertedPath; $uniqueName = $convertedName; $fileType = $newType; $converted = true; error_log("upload_media.php - $logType convertido: $convertedName"); error_log('upload_media.php - ffmpeg output: ' . substr($out ?: '', 0, 2000)); error_log('upload_media.php - ffmpeg probe: ' . substr($probe ?: '', 0, 2000)); } else { $conversion_error = 'Conversion failed or no output'; error_log('upload_media.php - Falló conversión con ffmpeg. Output: ' . substr($out ?: '', 0, 2000)); error_log('upload_media.php - ffmpeg probe after failure: ' . substr($probe ?: '', 0, 2000)); } } catch (Exception $ex) { $conversion_error = 'Conversion exception: ' . $ex->getMessage(); error_log('upload_media.php - Conversion exception: ' . $ex->getMessage()); } } else { $conversion_error = 'ffmpeg not found'; error_log('upload_media.php - ffmpeg no encontrado, no se puede convertir webm.'); } } else { $conversion_error = 'shell_exec disabled'; error_log('upload_media.php - shell_exec disabled, no se puede convertir webm.'); } } // Construir URL pública usando APP_URL del .env si está disponible // Esto asegura que los archivos sean accesibles públicamente desde WhatsApp (datos móviles) $appUrl = getenv('APP_URL'); if ($appUrl && $appUrl !== '') { // Usar APP_URL del .env (ej: https://bot.u-s.app/) $publicUrl = rtrim($appUrl, '/') . '/uploads/' . $uniqueName; } else { // Fallback: auto-detectar desde HTTP_HOST (desarrollo local) $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http'; $host = $_SERVER['HTTP_HOST']; $publicUrl = $protocol . '://' . $host . '/uploads/' . $uniqueName; } // Determinar tipo de medio $mediaType = 'document'; if (strpos($fileType, 'image/') === 0) { $mediaType = 'image'; } elseif (strpos($fileType, 'video/') === 0) { $mediaType = 'video'; } elseif (strpos($fileType, 'audio/') === 0) { $mediaType = 'audio'; } // Guardar información en base de datos (opcional) $db = Database::getInstance(); $mediaId = $db->insert('media_files', [ 'filename' => $fileName, 'unique_name' => $uniqueName, 'file_type' => $fileType, 'file_size' => $fileSize, 'media_type' => $mediaType, 'file_path' => $uploadPath, 'public_url' => $publicUrl, 'uploaded_by' => $_SESSION['user']['id'] ?? null, 'created_at' => date('Y-m-d H:i:s') ]); echo json_encode([ 'success' => true, 'message' => 'Archivo subido correctamente', 'id' => $mediaId, 'filename' => $fileName, 'public_url' => $publicUrl, 'media_type' => $mediaType, 'mime_type' => $fileType, 'size' => $fileSize, 'converted' => isset($converted) ? (bool)$converted : false, 'conversion_error' => isset($conversion_error) ? $conversion_error : null, // Mantener compatibilidad con código existente 'data' => [ 'id' => $mediaId, 'filename' => $fileName, 'url' => $publicUrl, 'type' => $mediaType, 'mime_type' => $fileType, 'size' => $fileSize ] ]); } catch (Exception $e) { error_log("Error en upload_media.php: " . $e->getMessage()); http_response_code(500); echo json_encode([ 'success' => false, 'error' => $e->getMessage() ]); } ?>