Tutorial 19: Obteniendo y Mostrando Datos del Archivo por ID

 

Tutorial 19: Obteniendo y Mostrando Datos del Archivo por ID

¡Hola y bienvenido de nuevo!

En esta lección vamos a crear un método para obtener los datos de un archivo específico usando su ID, y vamos a mostrar toda la información en la página view.php. Esto incluye el reproductor de audio/video, la transcripción y la traducción si existe. ¡Vamos a ello!


📋 Contenido del Tutorial

  1. Creando el método getFileById

  2. Procesando la solicitud GET en view.php

  3. Mostrando el reproductor correcto

  4. Mostrando la transcripción

  5. Mostrando la traducción

  6. Código completo

  7. Explicación detallada

  8. Próximos pasos


🔍 1. Creando el método getFileById

Estructura del método

php
public function getFileById($fileID) {
    // 1. Preparar la consulta
    $stmt = $this->DB->prepare(
        "SELECT * FROM `files` WHERE `ID` = :fileID"
    );
    
    // 2. Vincular el parámetro
    $stmt->bindParam(":fileID", $fileID, PDO::PARAM_INT);
    
    // 3. Ejecutar
    $stmt->execute();
    
    // 4. Retornar el resultado como objeto
    return $stmt->fetch(PDO::FETCH_OBJ);
}

¿Qué hace este método?

PasoFunciónExplicación
1prepare()Prepara la consulta SQL con placeholder
2bindParam()Vincula el ID como entero
3execute()Ejecuta la consulta
4fetch()Obtiene una sola fila como objeto

¿Por qué PDO::PARAM_INT?

php
$stmt->bindParam(":fileID", $fileID, PDO::PARAM_INT);
  • Especifica que el parámetro es un número entero

  • Mejora la seguridad y el rendimiento

  • Previene inyección SQL

Consulta SQL

sql
SELECT * FROM `files` WHERE `ID` = :fileID
ParteSignificado
SELECT *Selecciona todas las columnas
FROM filesDe la tabla files
WHERE ID = :fileIDFiltra por el ID proporcionado
:fileIDPlaceholder (será reemplazado)

📨 2. Procesando la solicitud GET en view.php

Estructura del código

php
<?php 
    include 'backend/init.php';
    $file = ''; // Variable para almacenar los datos
    
    if ($_SERVER['REQUEST_METHOD'] === "GET") {
        if (isset($_GET['file'])) {
            $file = $whisperObj->getFileById($_GET['file']);
            
            if (!$file) {
                header('Location: index.php');
                exit;
            }
        }
    }
?>

¿Qué hace este código?

LíneaFunciónExplicación
$file = ''Inicializa variableEvita errores si no hay archivo
$_SERVER['REQUEST_METHOD']Verifica métodoSolo procesa solicitudes GET
isset($_GET['file'])Verifica parámetroComprueba que existe el ID
getFileById()Obtiene datosRecupera el archivo de la BD
header('Location: ...')RedireccionaSi no existe, va a index.php

Flujo de redirección

text
1. Usuario escribe: view.php?file=999
   ↓
2. getFileById(999) busca en la BD
   ↓
3. No encuentra el archivo
   ↓
4. Redirecciona a index.php
   ↓
5. Usuario ve la página principal

🎬 3. Mostrando el reproductor correcto

Código HTML para el reproductor

php
<!-- Audio Player -->
<?php if($file->type === "audio"): ?>
    <audio controls style="width:100%;border-radius: 20px;">
        <source src="<?php echo $file->fileUrl; ?>" type="audio/mpeg">
        Your browser does not support the audio element.
    </audio>
<?php else: ?>
    <!-- Video Player -->
    <video style="height:240px; width:100%;" controls>
        <source src="<?php echo $file->fileUrl; ?>">
        Your browser does not support the video tag.
    </video>
<?php endif; ?>

¿Cómo funciona?

CondiciónResultadoElemento mostrado
$file->type === "audio"VerdaderoReproductor de audio
$file->type === "video"VerdaderoReproductor de video
Otro casoVerdaderoReproductor de video (por defecto)

Atributos importantes

AtributoPropósitoEjemplo
controlsMuestra controles de reproduccióncontrols
styleEstilos CSSwidth:100%
srcRuta del archivofiles/abc123.mp3
typeTipo MIMEaudio/mpeg

📝 4. Mostrando la transcripción

Código para mostrar el texto

php
<li class="flex flex-col my-5 w-full">
    <h3 class="text-2xl">Text</h3>
    <div>
        <?php echo $file->content; ?>
    </div>
</li>

Mejora de seguridad

php
<div>
    <?php echo nl2br(htmlspecialchars($file->content)); ?>
</div>

¿Por qué usar estas funciones?

FunciónPropósitoEjemplo
htmlspecialchars()Escapa caracteres HTML&lt;script&gt;<script>
nl2br()Convierte saltos de línea\n<br>

🌍 5. Mostrando la traducción

Código para mostrar traducción

php
<!-- TRANSLATED CONTENT -->
<?php if($file->translated !== NULL): ?>
    <li id="translateContent" class="flex flex-col my-5 w-full">
        <h3 class="text-2xl">[LANG]</h3>
        <div>
            <?php echo $file->translated; ?>
        </div>
    </li>
<?php endif; ?>

Mejora: Mostrar el idioma

php
<h3 class="text-2xl">
    <?php echo $file->lang ?: 'Traducción'; ?>
</h3>

¿Cómo verificar si hay traducción?

CondiciónSignificadoResultado
$file->translated !== NULLHay traducciónMuestra la traducción
$file->translated === NULLNo hay traducciónNo muestra nada

💻 6. Código completo

Archivo: backend/classes/Whisper.php (método agregado)

php
<?php 
    class Whisper{
        public $error;
        public $dataType;
        public $file;
        public $lang;
        public $content;
        private $DB;

        public function __construct(){
            $db = new DB;
            $this->DB = $db->connect();
        }
        
        public function errors(){
            return $this->error;
        }

        public function getApiUrl(){
            if($this->dataType === "ASR"){
                return "https://api.openai.com/v1/audio/transcriptions";
            }else{
                return "https://api.openai.com/v1/chat/completions";
            }
        }

        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'
                ];
            }
        }

        public function getData(){
            if($this->dataType === "ASR"){
                return [
                    'file' => $this->file,
                    'model' => 'whisper-1'
                ];
            }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
                        ]
                    ]
                ]);
            }
        }

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

        public function covert(){
            $apiUrl = $this->getApiUrl();
            $ch = curl_init($apiUrl);
            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);
            $response = curl_exec($ch);
            curl_close($ch);

            if($response){
                return json_decode($response, true);
            }else{
                $this->error = "API REQUEST FAILED";
            }
        }

        public function upload($file){
            $fileTmp  = $file['tmp_name'];
            $filename = basename($file['name']);
            $fileSize = $file['size'];
            $errors   = $file['error'];
            $mime     = $file['type'];

            //get file extension
            $ext  = pathinfo($filename, PATHINFO_EXTENSION);
            $ext  = strtolower($ext);
            
            
            $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 = "File is large!";
                }
            }else{
                $this->error = "invalid file format!";
            }
        }
        
        public function save($file, $content, $type){
            $stmt = $this->DB->prepare("INSERT INTO `files` (`fileUrl`, `content`, `type`) VALUES (:fileUrl, :content, :type)");
            $stmt->bindParam(":fileUrl", $file, PDO::PARAM_STR);
            $stmt->bindParam(":content", $content, PDO::PARAM_STR);
            $stmt->bindParam(":type", $type, PDO::PARAM_STR);
            $stmt->execute();
            return $this->DB->lastInsertId();
        }

        /**
         * Obtiene y muestra la lista de archivos recientes
         */
        public function getRecentList(){
            $stmt = $this->DB->prepare("SELECT * FROM `files` ORDER BY `ID` DESC");
            $stmt->execute();
            $files = $stmt->fetchAll(PDO::FETCH_OBJ);

            foreach($files as $file){
                echo '<a href="view.php?file='.$file->ID.'">
                            <li class="rounded flex my-5 cursor-pointer hover:bg-gray-100 items-center">
                                    
                                <div class="w-14">
                                '.(($file->type === 'audio') ? 
                                    '<img src="frontend/images/audio-img.png"/>' : 
                                    '<img src="frontend/images/video-img.png"/>' ) 
                                .'
                                </div>
                                <div class=" overflow-hidden w-60 font-normal h-auto font-bold p-2 ">
                                    <div>
                                        <p class="truncate">'.htmlspecialchars($file->content).'</p>
                                    </div>
                                </div>
                                    
                            </li>
                        </a>';
            }
        }

        /**
         * Obtiene los datos de un archivo por su ID
         * 
         * @param int $fileID ID del archivo
         * @return object|false Datos del archivo o false si no existe
         */
        public function getFileById($fileID){
            try {
                // 1. Preparar la consulta
                $stmt = $this->DB->prepare(
                    "SELECT * FROM `files` WHERE `ID` = :fileID"
                );
                
                // 2. Vincular el parámetro como entero
                $stmt->bindParam(":fileID", $fileID, PDO::PARAM_INT);
                
                // 3. Ejecutar la consulta
                $stmt->execute();
                
                // 4. Retornar el resultado como objeto
                return $stmt->fetch(PDO::FETCH_OBJ);
                
            } catch (PDOException $e) {
                $this->error = "Error al obtener el archivo: " . $e->getMessage();
                return false;
            }
        }
    }
?>

Archivo: view.php (completo)

php
<?php 
    include 'backend/init.php';
    
    // Inicializar variable
    $file = null;
    
    // Procesar la solicitud GET
    if ($_SERVER['REQUEST_METHOD'] === "GET") {
        if (isset($_GET['file'])) {
            // Obtener el archivo por ID
            $file = $whisperObj->getFileById($_GET['file']);
            
            // Si no existe, redirigir a index.php
            if (!$file) {
                header('Location: index.php');
                exit;
            }
        }
    }
?>
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>
        My Whisper Ai - Convert Video/Audio language into written text
    </title>
    <!-- FONT-AWESOME LINK -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" integrity="sha512-z3gLpd7yknf1YoNbCzqRKc4qyor8gaKU1qmn+CShxbuBusANI9QpRohGBreCFkKxLhei6S9CQXFEbbKuqLg0DA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
    <!-- CSS LINK -->
    <link rel="stylesheet" type="text/css" href="frontend/style/style.css"/>
    <!-- TAILWIND CSS LINK -->
    <script src="https://cdn.tailwindcss.com"></script>
    <!-- GOOGLE FONTS LINK -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Varela+Round&display=swap" rel="stylesheet">
    <!-- JQUERY LINK -->
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body class="">
<div class="Wrapper">
    <div class="inner-wrapper h-screen flex justify-center p-20">
        <div class="h-full border-2 rounded-xl border-gray-700" style="width: 1280px; max-height: 1080px; box-shadow: -20px 20px 0px 0px #74a840,-21px 21px 0px 3px #343333">
            <div class="flex h-full flex-col">
                <!--header-section-->
                <div class="flex w-full justify-between p-6">
                    <div class="flex justify-center flex-col">
                        <h2 class="text-2xl font-semibold">My Whisper</h2>
                        <span><a class="font-semibold text-lime-600" href="https://www.meralesson.com">Meralesson</a></span>
                    </div>
                    <div class="flex items-center justify-center">
                        <a href="index.php" class="border rounded px-4 py-2 cursor-pointer hover:bg-gray-100">Back</a>
                    </div>
                </div>
                <!--Body-section-->
                <div class="px-10 overflow-hidden">
                    <div class="flex">
                        <!--Left-video-section-->
                        <div class="w-1/3">
                            <div>
                                <!-- Audio Player -->
                                <?php if ($file && $file->type === "audio"): ?>
                                    <audio controls style="width:100%;border-radius: 20px;">
                                        <source src="<?php echo $file->fileUrl; ?>" type="audio/mpeg">
                                        Your browser does not support the audio element.
                                    </audio> 
                                <?php elseif ($file && $file->type === "video"): ?>
                                    <!-- Video Player -->
                                    <video style="height:240px; width:100%;" controls>
                                        <source src="<?php echo $file->fileUrl; ?>">
                                        Your browser does not support the video tag.
                                    </video> 
                                <?php else: ?>
                                    <p class="text-gray-500 text-center py-4">No hay archivo seleccionado</p>
                                <?php endif; ?> 
                            </div>
                            <!--Tabs-->
                            <div class="flex flex-col">
                                <div>
                                    <ul class="flex border-b justify-between">
                                        <li class="flex-1 flex font-sm items-center justify-center text-2xl text-gray-300 cursor-pointer hover:bg-gray-100">
                                            <span><i class="fa-solid fa-hands-asl-interpreting"></i></span>
                                        </li>
                                        <li class="flex-1 flex items-center justify-center font-sm text-2xl text-gray-300 cursor-pointer hover:bg-gray-100">
                                            <span><i class="fa-solid fa-database"></i></span>
                                        </li>
                                    </ul>
                                </div>
                                <div style="height: 400px; overflow:scroll;">
                                    <div class="flex" >
                                        <ul class="w-full h-full">
                                            <!-- RECENT GENERATED FILES -->
                                            <?php $whisperObj->getRecentList(); ?>
                                        </ul>
                                    </div>
                                </div>
                            </div>
                        </div>
                        <!--Right-Transcript-section-->
                        <div class="flex-1">
                            <div id="scroll" class="py-10 px-5 overflow-y-scroll w-full" style="height:500px;">
                                
                                <ul class="w-full">
                                    <li class="flex flex-col my-5 w-full">
                                        <h3 class="text-2xl">Text</h3>
                                        <div>
                                            <?php 
                                            if ($file) {
                                                echo nl2br(htmlspecialchars($file->content));
                                            } else {
                                                echo "No hay contenido para mostrar";
                                            }
                                            ?>
                                        </div>
                                    </li>
                                    <li class="flex flex-col my-5 w-full">
                                        <!--loader-->
                                        <div id="loader" class="hidden flex flex-col text-center flex-1  rounded-xl w-full">
                                            <div class="flex items-center flex-col">
                                                <img class="w-20 animate-pulse mt-20" src="frontend/images/loader.gif"/>
                                                <span class="animate-pulse">
                                                    Translating....
                                                </span>
                                            </div>
                                        </div>
                                    </li>
                                     <!-- TRANSLATED CONTENT -->
                                    <?php if ($file && $file->translated !== NULL): ?>
                                        <li id="translateContent" class="flex flex-col my-5 w-full">
                                            <h3 class="text-2xl">
                                                <?php echo $file->lang ?: 'Traducción'; ?>
                                            </h3>
                                            <div>
                                                <?php echo nl2br(htmlspecialchars($file->translated)); ?>
                                            </div>
                                        </li>
                                    <?php endif; ?>
                                     
                                </ul>

                            </div>
                            <form id="form" method="POST">
                                <div class="flex flex-col justify-center items-center">
                                    <div class="">
                                        <label for="lang">Select Language</label>
                                        <select id="lang" name="lang" class="px-4 py-3 mx-2 my-3">
                                            <option value="English">English</option>
                                            <option value="French">French</option>
                                            <option value="Spanish">Spanish</option>
                                            <option value="German">German</option>
                                        </select>
                                        
                                        <button id="translateBtn" name="translate" class="border border-gray-700 rounded px-6 py-2 cursor-pointer hover:bg-gray-100 font-normal">Translate</button>
                                    </div>
                                     <!-- ERROR HERE -->
                                        <!-- <div class="flex bg-red-100 border border-red-400 text-red-700 px-4 py-2 rounded relative">
                                            <strong class="font-bold">Error:</strong>
                                            <span class="block sm:inline">[ERROR]</span>
                                        </div> -->
                                     
                                </div>
                            </form>
                        </div>
                    </div>
                </div>

            </div>
        </div>    
    </div>
</div>
<!-- JS CODE HERE -->
<script>
$(document).ready(function() {
    // Traducción
    $('#translateBtn').click(function(e) {
        e.preventDefault();
        $('#loader').removeClass('hidden');
        
        // Obtener idioma seleccionado
        var lang = $('#lang').val();
        var content = $('div#scroll h3:first').next('div').text();
        var fileId = <?php echo $file ? $file->ID : 0; ?>;
        
        if (content && content !== 'No hay contenido para mostrar') {
            $.ajax({
                url: 'translate.php',
                method: 'POST',
                data: {
                    content: content,
                    lang: lang,
                    file_id: fileId
                },
                success: function(response) {
                    $('#loader').addClass('hidden');
                    alert('Traducción completada');
                    // Recargar la página para mostrar la traducción
                    location.reload();
                },
                error: function() {
                    $('#loader').addClass('hidden');
                    alert('Error al traducir');
                }
            });
        } else {
            $('#loader').addClass('hidden');
            alert('No hay contenido para traducir');
        }
    });
});
</script>
</body>
</html>

📖 7. Explicación detallada

El método getFileById paso a paso

php
public function getFileById($fileID) {
    try {
        // 1. Preparar la consulta SQL
        $stmt = $this->DB->prepare(
            "SELECT * FROM `files` WHERE `ID` = :fileID"
        );
        
        // 2. Vincular el parámetro
        $stmt->bindParam(":fileID", $fileID, PDO::PARAM_INT);
        
        // 3. Ejecutar la consulta
        $stmt->execute();
        
        // 4. Obtener el resultado
        return $stmt->fetch(PDO::FETCH_OBJ);
        
    } catch (PDOException $e) {
        $this->error = "Error al obtener el archivo: " . $e->getMessage();
        return false;
    }
}

¿Qué es PDO::FETCH_OBJ?

PDO::FETCH_OBJ devuelve una fila como un objeto:

php
// Con PDO::FETCH_OBJ
$file = $stmt->fetch(PDO::FETCH_OBJ);
echo $file->ID;        // Acceso como objeto
echo $file->content;
echo $file->fileUrl;

// Alternativa: PDO::FETCH_ASSOC
$file = $stmt->fetch(PDO::FETCH_ASSOC);
echo $file['ID'];      // Acceso como array

Manejo de errores en view.php

php
if (!$file) {
    header('Location: index.php');
    exit;
}

¿Qué hace?

  1. Si $file es false o null

  2. Redirige al usuario a index.php

  3. exit detiene la ejecución del resto del código


🧪 8. Probando la aplicación

Paso 1: Subir un archivo

  1. Ve a http://localhost/whisper/

  2. Selecciona un archivo de audio o video

  3. Espera el procesamiento

Paso 2: Verificar la vista

  1. Después de procesar, serás redirigido a view.php?file=1

  2. Verás:

    • ✅ Reproductor de audio/video

    • ✅ Transcripción en el panel derecho

    • ✅ Lista de archivos recientes

Paso 3: Navegar entre archivos

  1. Haz clic en cualquier archivo de la lista

  2. La página se actualiza con el nuevo archivo

  3. El reproductor y la transcripción cambian

Paso 4: Verificar archivos sin traducción

  1. Si el archivo no tiene traducción

  2. No se muestra la sección de traducción

  3. El botón de traducción sigue disponible


📊 9. Resumen del flujo

Diagrama de secuencia

text
Usuario → view.php?file=1
    ↓
index.php (GET)
    ↓
getFileById(1)
    ↓
SELECT * FROM files WHERE ID = 1
    ↓
Retorna objeto con datos
    ↓
Mostrar reproductor
    ↓
Mostrar transcripción
    ↓
Mostrar traducción (si existe)

Datos mostrados en la página

SecciónDatosFuente
Reproductor$file->fileUrlBase de datos
Transcripción$file->contentAPI de OpenAI
Traducción$file->translatedBase de datos
Idioma$file->langBase de datos

❓ Preguntas frecuentes

¿Qué pasa si el ID no existe en la base de datos?

  • getFileById() retorna false

  • Se redirige a index.php

¿Qué pasa si no hay archivo seleccionado?

  • $file es null

  • No se muestra reproductor

  • No se muestra contenido

¿Cómo se determina qué reproductor mostrar?

  • Si $file->type === "audio" → Reproductor de audio

  • Si $file->type === "video" → Reproductor de video

¿Qué hace htmlspecialchars()?

  • Convierte caracteres especiales a entidades HTML

  • Previene ataques XSS

¿Qué hace nl2br()?

  • Convierte saltos de línea en <br> HTML

  • Mejora la legibilidad del texto

¿Por qué usar exit después de header()?

  • Detiene la ejecución del script

  • Evita que se ejecute código no deseado


🎯 Próximos pasos

Lo que hemos logrado

✅ Método getFileById() implementado
✅ Procesamiento de solicitudes GET
✅ Mostrar reproductor según el tipo
✅ Mostrar transcripción
✅ Mostrar traducción (si existe)
✅ Redirección segura

Lo que viene en la próxima lección

En la siguiente lección vamos a:

  1. Implementar la traducción del texto

  2. Usar la API de ChatGPT para traducir

  3. Guardar la traducción en la base de datos

  4. Actualizar la vista con la traducción

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

php
// translate.php
<?php
include 'backend/init.php';

$content = $_POST['content'] ?? '';
$lang = $_POST['lang'] ?? '';
$fileId = $_POST['file_id'] ?? 0;

if ($content && $lang && $fileId) {
    $whisperObj->dataType = 'TRANSLATE';
    $whisperObj->content = $content;
    $whisperObj->lang = $lang;
    $result = $whisperObj->convert();
    
    if ($result) {
        $translated = $result['choices'][0]['message']['content'];
        // Guardar en la base de datos
        $whisperObj->saveTranslation($fileId, $translated, $lang);
        echo json_encode(['success' => true]);
    }
}
?>

¡Excelente trabajo! Ahora tenemos una página completa que muestra el archivo seleccionado con su reproductor, transcripción y traducción. En la próxima lección, implementaremos la funcionalidad de traducción.

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?