import express from 'express'; import { YoutubeTranscript } from 'youtube-transcript'; import path from 'path'; import { fileURLToPath } from 'url'; // Configuration pour __dirname avec ES Modules const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const app = express(); // Hugging Face Spaces utilise le port 7860 par défaut const port = process.env.PORT || 7860; // Servir les fichiers statiques du dossier 'public' app.use(express.static(path.join(__dirname, 'public'))); app.get('/get-transcript', async (req, res) => { const videoUrl = req.query.url; if (!videoUrl) { return res.status(400).json({ error: 'URL de la vidéo manquante' }); } try { // Essayer d'extraire l'ID de différentes formes d'URL YouTube let videoId = ''; if (videoUrl.includes('v=')) { videoId = videoUrl.split('v=')[1].split('&')[0]; } else if (videoUrl.includes('youtu.be/')) { videoId = videoUrl.split('youtu.be/')[1].split('?')[0]; } else { // On pourrait ajouter d'autres formats d'URL ici (shorts, etc.) // Pour l'instant, on assume que c'est un ID direct si ce n'est pas une URL connue videoId = videoUrl; } if (!videoId) { return res.status(400).json({ error: "Impossible d'extraire l'ID de la vidéo depuis l'URL." }); } console.log(`Fetching transcript for video ID: ${videoId}`); // Ajout d'un timeout pour éviter que la requête reste bloquée trop longtemps const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout dépassé lors de la récupération de la transcription')), 15000) ); // Utilisation de Promise.race pour implémenter un timeout const transcript = await Promise.race([ YoutubeTranscript.fetchTranscript(videoId), timeoutPromise ]); if (!transcript || transcript.length === 0) { return res.status(404).json({ error: 'Transcription non trouvée ou vide pour cette vidéo.' }); } // Concaténer les textes de la transcription const fullText = transcript.map(item => item.text).join(' '); res.json({ transcript: fullText }); } catch (error) { // Log détaillé de l'erreur pour le débogage console.error('Erreur détaillée lors de la récupération de la transcription:', { message: error.message, stack: error.stack, name: error.name }); // Gestion des cas d'erreur spécifiques if (error.message && error.message.includes('Could not find transcripts')) { return res.status(404).json({ error: "Aucune transcription disponible pour cette vidéo (elles sont peut-être désactivées ou n'existent pas en auto-généré)." }); } if (error.message && error.message.includes('is not a valid video ID')) { return res.status(400).json({ error: `L'ID vidéo extrait ('${error.message.split("'")[1]}') n'est pas valide. Vérifiez l'URL.` }); } if (error.message && error.message.includes('Timeout dépassé')) { return res.status(504).json({ error: 'Délai d\'attente dépassé lors de la récupération de la transcription. Veuillez réessayer.' }); } // Nouvelle gestion spécifique pour les transcriptions désactivées if (error.message && error.message.includes('Transcript is disabled')) { const videoId = error.message.match(/\(([^)]+)\)/)?.[1] || "cette vidéo"; return res.status(404).json({ error: `Les sous-titres/transcriptions sont désactivés sur cette vidéo (ID: ${videoId}).`, solution: "Essayez une autre vidéo qui possède des sous-titres activés." }); } // Message d'erreur plus détaillé pour les autres cas res.status(500).json({ error: 'Erreur interne du serveur lors de la récupération de la transcription.', details: error.message }); } }); // Route pour vérifier l'état du serveur (utile pour Hugging Face) app.get('/health', (req, res) => { res.status(200).json({ status: 'ok' }); }); // Route par défaut app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'index.html')); }); app.listen(port, '0.0.0.0', () => { console.log(`Serveur démarré sur http://0.0.0.0:${port}`); });