Tutorial 18: Creando la Página de Visualización de Archivos Recientes
Tutorial 18: Creando la Página de Visualización de Archivos Recientes
¡Hola y bienvenido de nuevo!
En esta lección vamos a crear la página view.php donde mostraremos los archivos generados recientemente, su transcripción y un reproductor de audio/video. Además, implementaremos la funcionalidad para listar todos los archivos guardados en la base de datos. ¡Vamos a ello!
📋 Contenido del Tutorial
Creando la página view.php
El método getRecentList
Mostrando la lista de archivos
Usando el operador ternario
Corrigiendo la base de datos
Código completo
Explicación detallada
Próximos pasos
📄 1. Creando la página view.php
Estructura de la página
La página view.php tiene varias secciones importantes:
┌─────────────────────────────────────────────────────┐ │ My Whisper AI [Back] │ ├─────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ ┌──────────────────────────┐ │ │ │ Reproductor │ │ Transcripción │ │ │ │ (Audio/Vid) │ │ Texto completo │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├──────────────┤ │ [Select Language] │ │ │ │ Archivos │ │ [Translate Button] │ │ │ │ Recientes │ │ │ │ │ │ - archivo1 │ │ │ │ │ │ - archivo2 │ │ │ │ │ │ - archivo3 │ │ │ │ │ └──────────────┘ └──────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────┘
Creando el archivo
Crea un nuevo archivo en la raíz del proyecto
Nómbralo
view.phpCopia el código de
view.html(proporcionado en los archivos)
Estructura del HTML
<?php include 'backend/init.php'; ?> <!DOCTYPE html> <html> <head> <!-- Headers y estilos --> </head> <body> <!-- Header con título y botón Back --> <!-- Reproductor de audio/video --> <!-- Lista de archivos recientes --> <!-- Transcripción --> <!-- Formulario de traducción --> </body> </html>
📊 2. El método getRecentList
Estructura del método
public function getRecentList() { // 1. Preparar la consulta $stmt = $this->DB->prepare("SELECT * FROM `files` ORDER BY `ID` DESC"); // 2. Ejecutar $stmt->execute(); // 3. Obtener resultados como objetos $files = $stmt->fetchAll(PDO::FETCH_OBJ); // 4. Generar el HTML foreach($files as $file) { // Mostrar cada archivo } }
¿Qué hace PDO::FETCH_OBJ?
PDO::FETCH_OBJ devuelve cada fila como un objeto:
// Con PDO::FETCH_OBJ $files = $stmt->fetchAll(PDO::FETCH_OBJ); foreach($files as $file) { echo $file->ID; // Acceso a propiedades echo $file->content; echo $file->fileUrl; } // Alternativa: PDO::FETCH_ASSOC (array asociativo) $files = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach($files as $file) { echo $file['ID']; // Acceso a array echo $file['content']; }
Consulta SQL
SELECT * FROM `files` ORDER BY `ID` DESC
| Parte | Significado |
|---|---|
SELECT * | Selecciona todas las columnas |
FROM files | De la tabla files |
ORDER BY ID DESC | Ordena por ID descendente (más reciente primero) |
🎨 3. Mostrando la lista de archivos
El HTML de cada elemento
<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"> <img src="frontend/images/[IMAGEN]"/> </div> <div class="overflow-hidden w-60 font-normal h-auto font-bold p-2"> <div> <p class="truncate">[CONTENT]</p> </div> </div> </li> </a>
Generando dinámicamente
foreach($files as $file) { echo '<a href="view.php?file=' . $file->ID . '">'; echo '<li class="rounded flex my-5 cursor-pointer hover:bg-gray-100 items-center">'; echo '<div class="w-14">'; // Mostrar ícono según el tipo if ($file->type === 'audio') { echo '<img src="frontend/images/audio-img.png"/>'; } else { echo '<img src="frontend/images/video-img.png"/>'; } echo '</div>'; echo '<div class="overflow-hidden w-60 font-normal h-auto font-bold p-2">'; echo '<div><p class="truncate">' . $file->content . '</p></div>'; echo '</div>'; echo '</li>'; echo '</a>'; }
🔀 4. Usando el operador ternario
¿Qué es el operador ternario?
Es una forma abreviada de escribir un if-else:
// Forma larga (if-else) if ($file->type === 'audio') { echo '<img src="frontend/images/audio-img.png"/>'; } else { echo '<img src="frontend/images/video-img.png"/>'; } // Forma corta (operador ternario) echo ($file->type === 'audio') ? '<img src="frontend/images/audio-img.png"/>' : '<img src="frontend/images/video-img.png"/>';
Sintaxis
(condición) ? (valor_si_verdadero) : (valor_si_falso)
En nuestro código
echo '<div class="w-14">' . (($file->type === 'audio') ? '<img src="frontend/images/audio-img.png"/>' : '<img src="frontend/images/video-img.png"/>') . '</div>';
Ventajas del operador ternario
| Ventaja | Explicación |
|---|---|
| Conciso | Menos líneas de código |
| Legible | Fácil de leer para condiciones simples |
| In-line | Se puede usar dentro de echo |
🗄️ 5. Corrigiendo la base de datos
Problema común
Si la columna content no existe en la tabla files, verás un error como:
Column 'content' not found in field list
Solución
Verificar la estructura de la tabla:
DESCRIBE files;
Si falta la columna content:
ALTER TABLE `files` ADD COLUMN `content` TEXT DEFAULT NULL AFTER `fileUrl`;
Estructura correcta:
+------------+----------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+----------------------+------+-----+---------+----------------+
| ID | int(11) | NO | PRI | NULL | auto_increment |
| fileUrl | varchar(255) | NO | | NULL | |
| content | text | YES | | NULL | |
| translated | text | YES | | NULL | |
| lang | varchar(255) | YES | | NULL | |
| type | enum('audio','video')| NO | | NULL | |
+------------+----------------------+------+-----+---------+----------------+💻 6. Código completo
Archivo: backend/classes/Whisper.php (método getRecentList agregado)
<?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(){ try { // 1. Preparar la consulta $stmt = $this->DB->prepare( "SELECT * FROM `files` ORDER BY `ID` DESC" ); // 2. Ejecutar $stmt->execute(); // 3. Obtener resultados como objetos $files = $stmt->fetchAll(PDO::FETCH_OBJ); // 4. Generar la lista HTML foreach($files as $file){ // Enlace al archivo echo '<a href="view.php?file=' . $file->ID . '">'; echo '<li class="rounded flex my-5 cursor-pointer hover:bg-gray-100 items-center">'; echo '<div class="w-14">'; // Mostrar ícono según el tipo (operador ternario) echo ($file->type === 'audio') ? '<img src="frontend/images/audio-img.png"/>' : '<img src="frontend/images/video-img.png"/>'; echo '</div>'; echo '<div class="overflow-hidden w-60 font-normal h-auto font-bold p-2">'; echo '<div>'; // Mostrar preview del contenido $preview = strlen($file->content) > 50 ? substr($file->content, 0, 50) . '...' : $file->content; echo '<p class="truncate">' . htmlspecialchars($preview) . '</p>'; echo '</div>'; echo '</div>'; echo '</li>'; echo '</a>'; } } catch (PDOException $e) { echo '<li class="text-red-500">Error al cargar archivos: ' . $e->getMessage() . '</li>'; } } } ?>
Archivo: view.php (completo)
<?php include 'backend/init.php'; // Obtener el ID del archivo $fileId = isset($_GET['file']) ? (int)$_GET['file'] : 0; // Obtener los datos del archivo $fileData = null; if ($fileId > 0) { $stmt = $whisperObj->DB->prepare("SELECT * FROM `files` WHERE ID = :id"); $stmt->bindParam(':id', $fileId, PDO::PARAM_INT); $stmt->execute(); $fileData = $stmt->fetch(PDO::FETCH_OBJ); } ?> <!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 ($fileData && $fileData->type === 'audio'): ?> <audio controls style="width:100%;border-radius: 20px;"> <source src="<?php echo $fileData->fileUrl; ?>" type="audio/mpeg"> Your browser does not support the audio element. </audio> <?php endif; ?> <!-- Video Player --> <?php if ($fileData && $fileData->type === 'video'): ?> <video style="height:240px; width:100%;" controls> <source src="<?php echo $fileData->fileUrl; ?>"> Your browser does not support the video tag. </video> <?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 ($fileData) { echo nl2br(htmlspecialchars($fileData->content)); } else { echo "No file selected. Please select a file from the list."; } ?> </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 --> <!-- <li id="translateContent" class="flex flex-col my-5 w-full"> <h3 class="text-2xl">[LANG]</h3> <div> [TRANSLATED] </div> </li> --> </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 $fileId; ?>; if (content && content !== 'No file selected.') { $.ajax({ url: 'translate.php', method: 'POST', data: { content: content, lang: lang, file_id: fileId }, success: function(response) { $('#loader').addClass('hidden'); // Aquí se mostraría el resultado de la traducción alert('Traducción completada'); }, 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 getRecentList paso a paso
public function getRecentList() { // 1. Preparar y ejecutar la consulta $stmt = $this->DB->prepare( "SELECT * FROM `files` ORDER BY `ID` DESC" ); $stmt->execute(); // 2. Obtener resultados como objetos $files = $stmt->fetchAll(PDO::FETCH_OBJ); // 3. Iterar sobre cada archivo foreach($files as $file) { // 4. Generar el HTML } }
¿Qué hace htmlspecialchars()?
htmlspecialchars() convierte caracteres especiales en entidades HTML:
$text = "Hello & welcome <script>"; echo htmlspecialchars($text); // Resultado: Hello & welcome <script>
¿Por qué es importante?
Previene ataques XSS (Cross-Site Scripting)
Los caracteres especiales se muestran correctamente
¿Qué hace nl2br()?
nl2br() convierte saltos de línea en <br> HTML:
$text = "Línea 1\nLínea 2"; echo nl2br($text); // Resultado: Línea 1<br>Línea 2
🧪 8. Probando la aplicación
Paso 1: Verificar la base de datos
-- Verificar la estructura DESCRIBE files; -- Verificar que hay datos SELECT * FROM files;
Paso 2: Subir un archivo
Ve a
http://localhost/whisper/Selecciona un archivo de audio o video
Espera el procesamiento
Paso 3: Verificar la lista
Después de procesar, serás redirigido a
view.phpEn el panel izquierdo, verás la lista de archivos
Cada archivo muestra un preview de la transcripción
Paso 4: Ver el contenido
Haz clic en un archivo de la lista
El reproductor mostrará el audio/video
La transcripción se mostrará en el panel derecho
📊 9. Resumen del flujo
Diagrama de navegación
index.php
↓
[Upload File]
↓
[Procesar con Whisper]
↓
[Guardar en BD]
↓
view.php?file=ID
↓
[Mostrar archivo]
↓
[Mostrar lista de archivos]Tabla de archivos mostrados
| Columna | Uso en la vista |
|---|---|
ID | Enlace: view.php?file=ID |
fileUrl | Reproductor de audio/video |
content | Transcripción mostrada |
type | Ícono (audio/video) |
❓ Preguntas frecuentes
¿Por qué usar PDO::FETCH_OBJ?
Devuelve objetos, permitiendo acceso con
->Más limpio que arrays asociativos
¿Qué es htmlspecialchars() y por qué usarlo?
Escapa caracteres HTML para prevenir XSS
Convierte
&,<,>,"y'
¿Por qué usar nl2br()?
Convierte saltos de línea a HTML
Mejora la legibilidad del texto
¿Qué es el operador ternario?
Una forma abreviada de
if-elseÚtil para condiciones simples en
echo
¿Cómo funciona la redirección en view.php?
header("location: view.php?file={$fileID}")Redirige al archivo recién creado
¿Por qué separar la lógica en un método?
Reutilización: se usa en varias páginas
Mantenimiento: cambios en un solo lugar
🎯 Próximos pasos
Lo que hemos logrado
✅ Página view.php creada
✅ Método getRecentList implementado
✅ Lista de archivos recientes mostrada
✅ Reproductor de audio/video
✅ Visualización de transcripciones
Lo que viene en la próxima lección
En la siguiente lección vamos a:
Implementar la traducción del texto
Usar la API de ChatGPT para traducir
Guardar la traducción en la base de datos
Mostrar la traducción al usuario
Avance del código de la próxima lección
// translate.php <?php include 'backend/init.php'; $content = $_POST['content'] ?? ''; $lang = $_POST['lang'] ?? 'Spanish'; $fileId = $_POST['file_id'] ?? 0; if ($content && $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, 'translated' => $translated]); } } ?>
¡Excelente trabajo! Ahora tenemos una página completa para ver los archivos generados recientemente, con reproductor y lista de archivos. En la próxima lección, añadiremos la funcionalidad de traducción.
Comentarios
Publicar un comentario