Come ho usato LLM per costruire una pagina web per il mio archivio di Zoo di 105 Come ho usato LLM per costruire una pagina web per il mio archivio di Zoo di 105

Come ho usato LLM per costruire una pagina web per il mio archivio di Zoo di 105

Gestire un vasto archivio di episodi podcast può essere impegnativo, specialmente quando si vuole che gli utenti possano facilmente cercare, navigare e accedere alle trascrizioni in tempo reale. Per il mio archivio de Lo Zoo di 105, ho costruito zoo.mdrzn.it, una pagina che elenca dinamicamente tutti i file .mp3 disponibili con trascrizioni .vtt sincronizzate. In questo post, spiego come ho utilizzato i large language models (LLM) per automatizzare compiti chiave e ottimizzare lo sviluppo.

Panoramica della Pagina

Su zoo.mdrzn.it, gli utenti possono:

  1. Visualizzare una lista degli episodi disponibili: Tutti i file .mp3 memorizzati sul mio server sono mostrati in una lista strutturata.
  2. Accedere alle trascrizioni in tempo reale: Ogni episodio ha un file .vtt associato, creato usando Whisper, che mostra la riga corrente della trascrizione mentre la traccia viene riprodotta.
  3. Cercare in tutte le trascrizioni: Una funzione di ricerca permette agli utenti di trovare parole chiave e frasi nell’intero archivio degli episodi.

Ecco uno screenshot dell’interfaccia:

Screenshot dell'interfaccia

Fase 1: Preparazione dei File Audio e delle Trascrizioni

Prima di costruire la pagina web, avevo bisogno di trascrizioni di alta qualità per ogni episodio. Ecco come ho automatizzato il processo di trascrizione:

Utilizzo di Whisper per Generare File .vtt

Sul mio PC desktop, ho utilizzato OpenAI Whisper per trascrivere l’audio. Il processo è stato abbastanza semplice:

Terminal window
whisper "path/to/audio/*.mp3" --output_dir "path/to/vtt_files/" --format vtt

Whisper ha generato automaticamente i file .vtt, che sono sottotitoli con timestamp adatti per visualizzare le trascrizioni durante la riproduzione dell’audio.

Fase 2: Costruzione della Pagina Web

La pagina web doveva elencare dinamicamente tutti gli episodi e fornire un’esperienza utente fluida per la riproduzione e la trascrizione in tempo reale. Ecco come l’ho affrontata:

Setup Lato Server

Dato che lo ospito su un server Hetzner con Debian 12 e Virtualmin, ho creato un semplice endpoint API che scansiona la directory contenente i file audio e le trascrizioni .vtt:

const fs = require('fs');
const express = require('express');
const app = express();
const port = 3000;
app.get('/api/episodes', (req, res) => {
const files = fs.readdirSync('/path/to/audio');
const episodes = files.filter(file => file.endsWith('.mp3')).map(file => ({
name: file.replace('.mp3', ''),
audioPath: `/audio/${file}`,
vttPath: `/vtt/${file.replace('.mp3', '.vtt')}`
}));
res.json(episodes);
});
app.listen(port, () => console.log(`API in esecuzione sulla porta ${port}`));

Questo endpoint restituisce la lista degli episodi e i loro percorsi file associati, che il front-end può consumare.

Design Front-End

Ho utilizzato React con Tailwind CSS per creare un’interfaccia pulita. La pagina principale recupera la lista degli episodi e li visualizza in una griglia:

import React, { useState, useEffect } from 'react';
import TranscriptViewer from './TranscriptViewer';
function App() {
const [episodes, setEpisodes] = useState([]);
const [currentEpisode, setCurrentEpisode] = useState(null);
useEffect(() => {
fetch('/api/episodes')
.then(response => response.json())
.then(data => setEpisodes(data));
}, []);
return (
<div className="container mx-auto p-4">
<h1 className="text-2xl font-bold">Archivio Zoo di 105</h1>
<ul className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mt-4">
{episodes.map(episode => (
<li key={episode.name} onClick={() => setCurrentEpisode(episode)}>
<div className="p-4 border rounded-lg cursor-pointer hover:bg-gray-100">
{episode.name}
</div>
</li>
))}
</ul>
{currentEpisode && (
<TranscriptViewer episode={currentEpisode} />
)}
</div>
);
}
export default App;

Sincronizzazione delle Trascrizioni

Per la visualizzazione delle trascrizioni in tempo reale, ho scritto un componente semplice che recupera il file .vtt, lo analizza e evidenzia la riga corrente in base al progresso dell’audio:

import React, { useEffect, useState } from 'react';
function TranscriptViewer({ episode }) {
const [transcript, setTranscript] = useState([]);
const [currentLine, setCurrentLine] = useState(null);
useEffect(() => {
fetch(episode.vttPath)
.then(response => response.text())
.then(vtt => parseVTT(vtt));
}, [episode]);
const parseVTT = (vtt) => {
const lines = vtt.split('\n\n').map(block => {
const [time, ...text] = block.split('\n');
return { time, text: text.join(' ') };
});
setTranscript(lines);
};
const handleAudioTimeUpdate = (e) => {
const currentTime = e.target.currentTime;
const line = transcript.find(t => parseTime(t.time) <= currentTime);
setCurrentLine(line);
};
return (
<div className="mt-6">
<h2 className="text-xl">In riproduzione: {episode.name}</h2>
<audio src={episode.audioPath} controls onTimeUpdate={handleAudioTimeUpdate}></audio>
<div className="mt-4">
{transcript.map((line, index) => (
<p key={index} className={line === currentLine ? 'text-blue-500' : ''}>
{line.text}
</p>
))}
</div>
</div>
);
}
export default TranscriptViewer;

La Funzione di Ricerca

Per abilitare la ricerca nelle trascrizioni, ho sfruttato un algoritmo di ricerca indicizzato. Le trascrizioni sono indicizzate sul server e quando viene effettuata una ricerca, il server restituisce i segmenti corrispondenti insieme ai loro codici temporali.

Fase 3: Deployment dell’Applicazione

Il deployment è stato semplice grazie al mio setup esistente con Virtualmin sul server Hetzner. Ecco un riepilogo di ciò che ho fatto:

  1. Build dell’app React usando:

    Terminal window
    npm run build
  2. Spostamento dei file di build nella directory web del server:

    Terminal window
    scp -r build/* user@myserver:/var/www/zoo.mdrzn.it
  3. Configurazione di Nginx per servire i file statici e fare il proxy dell’API.

Conclusione

L’utilizzo di LLM e strumenti AI come Whisper mi ha risparmiato innumerevoli ore di lavoro di trascrizione manuale, e la costruzione della pagina usando React ha fornito un’esperienza utente fluida e dinamica. Se stai gestendo un vasto archivio audio, considera di automatizzare le funzioni di trascrizione e ricerca—rimarrai sorpreso da quanto più semplice diventa l’intero processo.

Fammi sapere nei commenti se desideri che approfondisca una sezione specifica, come la configurazione di Whisper, il design dei componenti React o l’implementazione della ricerca!


← Torna al blog