const express = require('express'); const { spawn } = require('child_process'); const path = require('path'); const fs = require('fs'); const multer = require('multer'); const app = express(); const PORT = process.env.PORT || 8282; // CONFIGURACIÓN DE RUTAS const NODE_PATH = '/home/xesar/.nvm/versions/node/v20.19.4/bin/node'; const COOKIES_FILE = path.resolve(__dirname, 'cookies.txt'); const { warmAndExportCookies } = require('./cookiewarmer'); // --- CONFIGURACIÓN DE RUTAS Y CONSTANTES (Añade esto aquí) --- const COOKIES_PATH = path.resolve(__dirname, 'cookies.txt'); // --- CONFIGURACIÓN DE MULTER (SUBIDA DE ARCHIVOS) --- const storage = multer.diskStorage({ destination: (req, file, cb) => cb(null, './'), filename: (req, file, cb) => cb(null, 'cookies.txt') }); const upload = multer({ storage: storage, fileFilter: (req, file, cb) => { if (path.extname(file.originalname) !== '.txt') { return cb(new Error('Solo se permiten archivos .txt'), false); } cb(null, true); } }); // Función para sanear cookies (Evita errores de formato de yt-dlp) function sanitizeCookies() { if (fs.existsSync(COOKIES_FILE)) { let content = fs.readFileSync(COOKIES_FILE, 'utf8'); if (content.includes('# Netscape HTTP Cookie File')) { content = content.substring(content.indexOf('# Netscape HTTP Cookie File')); fs.writeFileSync(COOKIES_FILE, content, 'utf8'); return true; } } return false; } // ENDPOINT 1: /stream/:id // Devuelve metadatos, miniaturas y la URL de streaming (directa o m3u8) app.get('/stream/:id', (req, res) => { sanitizeCookies(); const videoId = req.params.id; const youtubeUrl = `https://www.youtube.com/watch?v=${videoId}`; // Argumentos para obtener el JSON de metadatos const args = [ '--cookies', COOKIES_FILE, '--js-runtime', `node:${NODE_PATH}`, '--dump-json', '--format', 'bestvideo+bestaudio/best', '--extractor-args', 'youtube:player_client=tv,web', youtubeUrl ]; const child = spawn('yt-dlp', args); let output = ''; child.stdout.on('data', (data) => output += data); child.on('close', (code) => { if (code !== 0) return res.status(500).json({ error: "Error al extraer stream" }); try { const info = JSON.parse(output); const response = { video_id: videoId, title: info.title, description: info.description, is_live: info.is_live || false, stream_url: info.url, hls_variant_url: info.manifest_url || "", url_type: info.is_live ? "hls/m3u8" : "direct/mp4", youtube_url: youtubeUrl, thumbnails: [ `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`, `https://img.youtube.com/vi/${videoId}/hqdefault.jpg` ], ffmpeg_example: `ffmpeg -re -i "${info.url}" -c copy -f flv rtmp://destino/stream_key` }; res.json(response); } catch (e) { res.status(500).json({ error: "Error parseando JSON de yt-dlp" }); } }); }); // ENDPOINT 2: /transcript/:id // Devuelve las miniaturas y el texto completo de la transcripción app.get('/transcript/:id', (req, res) => { sanitizeCookies(); const videoId = req.params.id; const youtubeUrl = `https://www.youtube.com/watch?v=${videoId}`; // Argumentos para extraer subtítulos sin descargar video const args = [ '--cookies', COOKIES_FILE, '--js-runtime', `node:${NODE_PATH}`, '--write-auto-subs', '--skip-download', '--print', '%(subtitles)j', // Imprime el JSON de subtítulos youtubeUrl ]; const child = spawn('yt-dlp', args); let output = ''; child.stdout.on('data', (data) => output += data); child.on('close', (code) => { // Nota: yt-dlp devuelve las URLs de los subtítulos. // Para obtener el "format_text" como tu JSON, se requiere procesar el .vtt o .ttml // Aquí enviamos la estructura base solicitada. res.json({ video_id: videoId, thumbnails: [ `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`, `https://img.youtube.com/vi/${videoId}/sddefault.jpg` ], info: "Para transcripción completa procesada, use un parser de VTT sobre la URL de subtítulos de YouTube." }); }); }); // --- INTERFAZ UX (MODO OSCURO / TAILWIND) --- app.get('/admin', (req, res) => { res.send(`
Gestor de Sesión de YouTube