Tutorial 16: Enviando la Solicitud HTTP a la API de OpenAI

 

Tutorial 16: Enviando la Solicitud HTTP a la API de OpenAI

¡Hola y bienvenido de nuevo!

En esta lección vamos a crear el método final que enviará la solicitud HTTP a la API de OpenAI usando cURL. Este es el paso crucial donde nuestra aplicación se comunica con la IA para transcribir el audio. ¡Vamos a ello!


📋 Contenido del Tutorial

  1. ¿Qué es cURL y por qué lo usamos?

  2. Corrigiendo errores en el código

  3. Creando el método convert

  4. El método getFile para manejar archivos

  5. Configurando cURL paso a paso

  6. Código completo

  7. Explicación detallada

  8. Probando la aplicación

  9. Próximos pasos


🔗 1. ¿Qué es cURL y por qué lo usamos?

¿Qué es cURL?

cURL (Client URL) es una biblioteca y herramienta de línea de comandos que permite transferir datos usando varios protocolos (HTTP, HTTPS, FTP, etc.). En PHP, cURL nos permite hacer solicitudes HTTP a servidores externos.

¿Por qué usar cURL?

RazónExplicación
FlexibilidadSoporta múltiples métodos HTTP (GET, POST, PUT, DELETE)
HeadersPodemos enviar headers personalizados
ArchivosSoporta subida de archivos
SeguridadSoporta HTTPS y autenticación
ControlConfiguraciones detalladas (timeouts, etc.)

Alternativas a cURL

OpciónProsContras
cURLPoderoso, flexibleMás complejo
file_get_contents()SimpleLimitado, no soporta POST fácilmente
GuzzleModerno, orientado a objetosRequiere librería externa
AJAX/JavaScriptDel lado del clienteNo aplica para PHP

🐛 2. Corrigiendo errores en el código

Errores comunes en los headers

Header incorrecto:

php
'Authorication: Bearer ' . API_TOKEN  // ❌ Mal escrito

Header correcto:

php
'Authorization: Bearer ' . API_TOKEN  // ✅ Bien escrito

Content-Type incorrecto:

php
'Content-Type: mutlipart/form-data'  // ❌ Mal escrito

Content-Type correcto:

php
'Content-Type: multipart/form-data'  // ✅ Bien escrito

Código corregido para getHeader()

php
public function getHeader() {
    if ($this->dataType === "ASR") {
        return [
            'Authorization: Bearer ' . API_TOKEN,  // ← Corregido
            'Content-Type: multipart/form-data'    // ← Corregido
        ];
    } else {
        return [
            'Authorization: Bearer ' . API_TOKEN,  // ← Corregido
            'Content-Type: application/json'
        ];
    }
}

🚀 3. Creando el método convert

Estructura del método

php
public function convert() {
    // 1. Obtener la URL de la API
    $apiUrl = $this->getApiUrl();
    
    // 2. Inicializar cURL
    $ch = curl_init($apiUrl);
    
    // 3. Configurar opciones de cURL
    curl_setopt($ch, CURLOPT_POST, true);
    $this->getFile();
    curl_setopt($ch, CURLOPT_POSTFIELDS, $this->getData());
    curl_setopt($ch, CURLOPT_HTTPHEADER, $this->getHeader());
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    
    // 4. Ejecutar la solicitud
    $response = curl_exec($ch);
    
    // 5. Cerrar la conexión
    curl_close($ch);
    
    // 6. Procesar la respuesta
    if ($response) {
        return json_decode($response, true);
    } else {
        $this->error = "API REQUEST FAILED";
        return false;
    }
}

¿Qué hace cada paso?

PasoFunciónDescripción
1getApiUrl()Obtiene el endpoint correcto
2curl_init()Inicia una sesión cURL
3curl_setopt()Configura las opciones
4curl_exec()Ejecuta la solicitud
5curl_close()Cierra la sesión
6json_decode()Procesa la respuesta JSON

📁 4. El método getFile para manejar archivos

¿Por qué necesitamos getFile()?

Cuando enviamos un archivo a la API, necesitamos crear un objeto CURLFile que represente el archivo. Esto le dice a cURL que debe enviar un archivo binario.

Implementación

php
public function getFile() {
    if ($this->dataType === 'ASR') {
        $this->file = curl_file_create($this->file);
    }
}

¿Qué hace curl_file_create()?

php
// Antes de curl_file_create()
$this->file = "files/video.mp4";  // Solo un string

// Después de curl_file_create()
$this->file = CURLFile Object {
    [name] => files/video.mp4
    [mime] => video/mp4
    [postname] => video.mp4
}

Uso en el método convert

php
public function convert() {
    $apiUrl = $this->getApiUrl();
    $ch = curl_init($apiUrl);
    
    // Crear el objeto CURLFile antes de enviar
    $this->getFile();  // ← Convierte $this->file a CURLFile
    
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $this->getData());
    // ... resto de la configuración
}

⚙️ 5. Configurando cURL paso a paso

Opciones de cURL usadas

OpciónConstanteValorDescripción
POSTCURLOPT_POSTtrueHace una solicitud POST
POSTFIELDSCURLOPT_POSTFIELDSDatosLos datos a enviar
HTTPHEADERCURLOPT_HTTPHEADERArrayHeaders HTTP
RETURNTRANSFERCURLOPT_RETURNTRANSFERtrueRetorna la respuesta como string

Explicación de cada opción

1. CURLOPT_POST

php
curl_setopt($ch, CURLOPT_POST, true);
  • Indica que haremos una solicitud POST

  • Es el método correcto para enviar archivos

2. CURLOPT_POSTFIELDS

php
curl_setopt($ch, CURLOPT_POSTFIELDS, $this->getData());
  • Los datos que enviaremos en el POST

  • Para ASR: un array con el archivo y el modelo

  • Para traducción: un string JSON

3. CURLOPT_HTTPHEADER

php
curl_setopt($ch, CURLOPT_HTTPHEADER, $this->getHeader());
  • Los headers HTTP que enviaremos

  • Incluye autenticación y tipo de contenido

4. CURLOPT_RETURNTRANSFER

php
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  • Hace que curl_exec() retorne la respuesta como string

  • Si es false, muestra la respuesta directamente

Opciones adicionales recomendadas

php
// Timeout (límite de tiempo)
curl_setopt($ch, CURLOPT_TIMEOUT, 60);

// Verificar SSL (en producción)
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);

// User Agent
curl_setopt($ch, CURLOPT_USERAGENT, 'MyWhisper/1.0');

💻 6. Código completo

Archivo: backend/classes/Whisper.php

php
<?php
/**
 * Clase Whisper
 * 
 * Maneja la subida, transcripción y traducción de archivos
 * utilizando la API de OpenAI
 */
class Whisper {
    /**
     * @var string Mensajes de error
     */
    public $error;
    
    /**
     * @var string Tipo de operación (ASR o TRANSLATE)
     */
    public $dataType;
    
    /**
     * @var string Ruta del archivo para transcripción
     */
    public $file;
    
    /**
     * @var string Texto para traducción
     */
    public $content;
    
    /**
     * @var string Idioma destino para la traducción
     */
    public $lang;
    
    /**
     * @var PDO Conexión a la base de datos
     */
    private $DB;
    
    /**
     * Constructor - Establece conexión a la base de datos
     */
    public function __construct() {
        $db = new DB();
        $this->DB = $db->connect();
    }
    
    /**
     * Obtiene el mensaje de error actual
     * 
     * @return string El mensaje de error
     */
    public function errors() {
        return $this->error;
    }
    
    /**
     * Obtiene la URL de la API según el tipo de operación
     * 
     * @return string URL del endpoint de la API
     */
    public function getApiUrl() {
        if ($this->dataType === "ASR") {
            return "https://api.openai.com/v1/audio/transcriptions";
        } else {
            return "https://api.openai.com/v1/chat/completions";
        }
    }
    
    /**
     * Obtiene los headers necesarios para la API
     * 
     * @return array Headers para la solicitud HTTP
     */
    public function getHeader() {
        if ($this->dataType === "ASR") {
            return [
                'Authorization: Bearer ' . API_TOKEN,
                'Content-Type: multipart/form-data'
            ];
        } else {
            return [
                'Authorization: Bearer ' . API_TOKEN,
                'Content-Type: application/json'
            ];
        }
    }
    
    /**
     * Prepara los datos para enviar a la API
     * 
     * @return array|string Datos preparados según el tipo de operación
     */
    public function getData() {
        if ($this->dataType === "ASR") {
            return [
                'file' => $this->file,
                'model' => 'whisper-1',
                'language' => 'es',
                'response_format' => 'json'
            ];
        } else {
            return json_encode([
                'model' => 'gpt-3.5-turbo',
                'messages' => [
                    [
                        'role' => 'system',
                        'content' => 'You will be provided with a text, and your task is to translate it into ' . $this->lang
                    ],
                    [
                        'role' => 'user',
                        'content' => $this->content
                    ]
                ],
                'temperature' => 0.7
            ]);
        }
    }
    
    /**
     * Convierte el archivo a un objeto CURLFile
     */
    public function getFile() {
        if ($this->dataType === 'ASR') {
            // Verificar que el archivo existe
            if (!file_exists($this->file)) {
                $this->error = "El archivo no existe";
                return false;
            }
            $this->file = curl_file_create($this->file);
            return true;
        }
        return true;
    }
    
    /**
     * Envía la solicitud a la API de OpenAI
     * 
     * @return array|false Respuesta de la API o false en caso de error
     */
    public function convert() {
        // 1. Obtener la URL de la API
        $apiUrl = $this->getApiUrl();
        if (!$apiUrl) {
            $this->error = "No se pudo obtener la URL de la API";
            return false;
        }
        
        // 2. Inicializar cURL
        $ch = curl_init($apiUrl);
        if (!$ch) {
            $this->error = "Error al inicializar cURL";
            return false;
        }
        
        // 3. Configurar opciones de cURL
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 120); // 2 minutos máximo
        
        // 4. Crear el objeto CURLFile
        if (!$this->getFile()) {
            return false;
        }
        
        // 5. Configurar datos y headers
        curl_setopt($ch, CURLOPT_POSTFIELDS, $this->getData());
        curl_setopt($ch, CURLOPT_HTTPHEADER, $this->getHeader());
        
        // 6. Ejecutar la solicitud
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlError = curl_error($ch);
        
        // 7. Cerrar la conexión
        curl_close($ch);
        
        // 8. Verificar errores de cURL
        if ($curlError) {
            $this->error = "Error cURL: " . $curlError;
            return false;
        }
        
        // 9. Verificar código HTTP
        if ($httpCode !== 200) {
            $this->error = "Error HTTP: " . $httpCode . " - " . $response;
            return false;
        }
        
        // 10. Procesar la respuesta
        if ($response) {
            $data = json_decode($response, true);
            
            // Verificar si hay error en la respuesta
            if (isset($data['error'])) {
                $this->error = "Error de API: " . $data['error']['message'];
                return false;
            }
            
            return $data;
        } else {
            $this->error = "API REQUEST FAILED - No se recibió respuesta";
            return false;
        }
    }
    
    /**
     * Sube un archivo al servidor
     * 
     * @param array $file Arreglo $_FILES
     * @return string|false Ruta del archivo o false en caso de error
     */
    public function upload($file) {
        $fileTmp  = $file['tmp_name'];
        $filename = basename($file['name']);
        $fileSize = $file['size'];
        $errors   = $file['error'];
        $mime     = $file['type'];
        
        // Obtener la extensión del archivo
        $ext = pathinfo($filename, PATHINFO_EXTENSION);
        $ext = strtolower($ext);
        
        // Obtener el directorio raíz del proyecto
        $parentDirectoy = dirname(dirname(dirname(__FILE__)));
        
        $allowedMedia = ['video/mp4','video/mpeg', 'audio/mpeg','audio/mpeg3','audio/wav'];
        
        if (in_array($mime, $allowedMedia)) {
            if ($fileSize <= 20000000) {
                $folder = 'files/';
                $file   = $folder . md5(time() . mt_rand()) . '.' . $ext;
                move_uploaded_file($fileTmp, $parentDirectoy . '/' . $file);
                return $file;
            } else {
                $this->error = "El archivo es demasiado grande (máximo 20 MB)";
                return false;
            }
        } else {
            $this->error = "Formato de archivo inválido";
            return false;
        }
    }
}
?>

Archivo: index.php (sección PHP actualizada)

php
<?php
    include 'backend/init.php';
    
    $error = null;
    $result = null;
    
    if ($_SERVER['REQUEST_METHOD'] === "POST") {
        if (isset($_FILES['file']) && !empty($_FILES['file']['name'])) {
            
            // 1. Subir el archivo
            $file = $whisperObj->upload($_FILES['file']);
            
            if ($file) {
                // 2. Configurar para transcripción
                $whisperObj->dataType = 'ASR';
                $whisperObj->file = $file;
                
                // 3. Enviar a la API
                $response = $whisperObj->convert();
                
                if ($response) {
                    // Mostrar el resultado
                    echo "<div style='max-width:600px;margin:20px auto;padding:20px;background:#f0f9ff;border-radius:10px;'>";
                    echo "<h3>📝 Transcripción completada</h3>";
                    echo "<pre style='white-space:pre-wrap;font-family:inherit;'>" . htmlspecialchars($response['text'] ?? '') . "</pre>";
                    echo "</div>";
                } else {
                    $error = $whisperObj->errors();
                }
                
            } else {
                $error = $whisperObj->errors();
            }
        } else {
            $error = "Por favor selecciona un archivo para convertir a texto";
        }
    }
?>

📖 7. Explicación detallada

Flujo completo de la solicitud

text
1. Usuario selecciona un archivo
   ↓
2. JavaScript envía el formulario
   ↓
3. PHP recibe la solicitud POST
   ↓
4. $whisperObj->upload() guarda el archivo
   ↓
5. Se configura $whisperObj->dataType = 'ASR'
   ↓
6. Se configura $whisperObj->file = $file
   ↓
7. Se llama a $whisperObj->convert()
   ↓
8. convert() obtiene la URL con getApiUrl()
   ↓
9. convert() inicializa cURL con curl_init()
   ↓
10. convert() configura las opciones de cURL
   ↓
11. getFile() convierte el archivo a CURLFile
   ↓
12. getData() prepara los datos para enviar
   ↓
13. getHeader() prepara los headers HTTP
   ↓
14. cURL envía la solicitud a la API
   ↓
15. La API procesa el archivo
   ↓
16. cURL recibe la respuesta
   ↓
17. convert() decodifica el JSON
   ↓
18. La respuesta se muestra al usuario

¿Qué es CURLFile?

CURLFile es una clase en PHP que representa un archivo para ser enviado mediante cURL.

php
// Crear un objeto CURLFile
$fileObject = curl_file_create('/ruta/al/archivo.mp4');

// Equivalente a:
$fileObject = new CURLFile('/ruta/al/archivo.mp4');

// El objeto contiene:
// - name: la ruta del archivo
// - mime: el tipo MIME (detectado automáticamente)
// - postname: el nombre para el campo POST

Manejo de errores completo

php
// 1. Error de cURL
if ($curlError) {
    $this->error = "Error cURL: " . $curlError;
    return false;
}

// 2. Error HTTP (código de estado)
if ($httpCode !== 200) {
    $this->error = "Error HTTP: " . $httpCode;
    return false;
}

// 3. Error de API (respuesta con error)
if (isset($data['error'])) {
    $this->error = "Error de API: " . $data['error']['message'];
    return false;
}

// 4. Error de respuesta vacía
if (!$response) {
    $this->error = "API REQUEST FAILED";
    return false;
}

🧪 8. Probando la aplicación

Preparación del archivo de prueba

Paso 1: Consigue un archivo de audio con voz

  • Puedes grabar un mensaje corto

  • Descargar un audio de prueba

Paso 2: Sube el archivo

  • Haz clic en "Upload File"

  • Selecciona el archivo

Paso 3: Espera el procesamiento

  • El loader se mostrará mientras se procesa

  • Puede tomar unos segundos

Paso 4: Verifica el resultado

  • La transcripción se mostrará en la página

Posibles errores y soluciones

ErrorCausaSolución
API REQUEST FAILEDNo hay respuesta de la APIVerificar conexión a internet
Error HTTP: 401API Key inválidaVerificar API_KEY en .env
Error HTTP: 429Demasiadas solicitudesEsperar unos minutos
Error HTTP: 413Archivo demasiado grandeUsar archivos más pequeños
Error cURL: 28TimeoutAumentar CURLOPT_TIMEOUT

📊 9. Resumen de la clase Whisper

Métodos completos

MétodoDescripciónLlama a
__construct()Constructor, conecta a BD-
errors()Devuelve el error-
getApiUrl()Obtiene URL de la API-
getHeader()Obtiene headers HTTP-
getData()Prepara los datos-
getFile()Convierte archivo a CURLFile-
convert()Envía solicitud a la APIgetApiUrl(), getFile(), getData(), getHeader()
upload()Sube archivo al servidor-

Diagrama de dependencias

text
convert()
    ↓
    ├── getApiUrl()
    │       ↓
    │       └── dataType
    │
    ├── getFile()
    │       ↓
    │       └── file → CURLFile
    │
    ├── getData()
    │       ↓
    │       ├── dataType → ASR → file + model
    │       └── dataType → TRANSLATE → JSON
    │
    └── getHeader()
            ↓
            ├── dataType → ASR → multipart/form-data
            └── dataType → TRANSLATE → application/json

🎯 10. Próximos pasos

Lo que hemos logrado

✅ Método convert() implementado
✅ Comunicación con la API usando cURL
✅ Manejo de archivos con CURLFile
✅ Procesamiento de respuestas JSON
✅ Manejo de errores completo
✅ Transcripción funcional

Lo que viene en la próxima lección

En la siguiente lección vamos a:

  1. Guardar la transcripción en la base de datos

  2. Mostrar la lista de archivos recientes

  3. Crear la página de vista para ver transcripciones

  4. Implementar el reproductor de audio/video

Avance del código de la próxima lección

php
// Guardar transcripción en la base de datos
public function saveTranscription($fileId, $content) {
    $sql = "UPDATE files SET content = :content WHERE ID = :id";
    $stmt = $this->DB->prepare($sql);
    return $stmt->execute([
        ':content' => $content,
        ':id' => $fileId
    ]);
}

// Obtener archivos recientes
public function getRecentFiles($limit = 10) {
    $sql = "SELECT * FROM files ORDER BY ID DESC LIMIT :limit";
    $stmt = $this->DB->prepare($sql);
    $stmt->execute([':limit' => $limit]);
    return $stmt->fetchAll();
}

❓ Preguntas frecuentes

¿Qué es cURL?

  • Es una biblioteca que permite hacer solicitudes HTTP desde PHP

  • Usada para comunicarse con APIs externas

¿Por qué necesito curl_file_create()?

  • Para que cURL sepa que está enviando un archivo

  • Crea un objeto CURLFile con la información del archivo

¿Qué pasa si la API no responde?

  • cURL tendrá un timeout y devolverá un error

  • Se maneja con CURLOPT_TIMEOUT

¿Por qué usar json_decode()?

  • La API devuelve una respuesta en JSON

  • json_decode() convierte a array para trabajar en PHP

¿Qué significa el código HTTP 200?

  • Es el código de éxito ("OK")

  • Indica que la solicitud fue procesada correctamente

¿Cómo sé si la transcripción fue exitosa?

  • La respuesta tendrá un campo 'text' con el texto transcrito

  • Si hay error, la respuesta tendrá un campo 'error'


¡Excelente trabajo! Ahora tenemos una aplicación completamente funcional que puede transcribir audio/video usando la API de OpenAI. En la próxima lección, añadiremos la funcionalidad de guardar y mostrar las transcripciones.

Comentarios

Entradas más populares de este blog

Cómo usar Whisper para sacar el texto de un video

Tutorial 18: Creando la Página de Visualización de Archivos Recientes

Tutorial 11: ¿Qué es Whisper AI y Cómo Funciona?