/** * Simple Express server to display translated episodes with speaker colors. */ const express = require('express'); const path = require('path'); const fs = require('fs'); const app = express(); const PORT = process.env.PORT || 5000; // Configuration const TRANSLATED_DIR = path.join(__dirname, '..', '_translated'); const COLORS_FILE = path.join(__dirname, '..', '_colors.json'); // Set up EJS as templating engine app.set('view engine', 'ejs'); app.set('views', path.join(__dirname, 'views')); // Serve static files app.use(express.static(path.join(__dirname, 'public'))); /** * Load speaker color mapping. */ function loadColors() { try { if (fs.existsSync(COLORS_FILE)) { const data = fs.readFileSync(COLORS_FILE, 'utf-8'); return JSON.parse(data); } } catch (err) { console.error('Error loading colors:', err); } return {}; } /** * Get list of all episodes from _translated folder. */ function getEpisodes() { const episodes = []; try { if (fs.existsSync(TRANSLATED_DIR)) { const files = fs.readdirSync(TRANSLATED_DIR); files .filter(f => f.endsWith('_translated.json')) .sort() .forEach(file => { // Extract episode name from filename (e.g., "S02E01_translated.json" -> "S02E01") const episodeId = file.replace('_translated.json', ''); episodes.push({ id: episodeId, filename: file, title: episodeId }); }); } } catch (err) { console.error('Error reading episodes:', err); } return episodes; } /** * Load a specific episode's data. */ function loadEpisode(episodeId) { const jsonFile = path.join(TRANSLATED_DIR, `${episodeId}_translated.json`); try { if (fs.existsSync(jsonFile)) { const data = fs.readFileSync(jsonFile, 'utf-8'); return JSON.parse(data); } } catch (err) { console.error('Error loading episode:', err); } return null; } /** * Escape HTML special characters. */ function escapeHtml(text) { if (!text) return ''; return text .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } // Make escapeHtml available to templates app.locals.escapeHtml = escapeHtml; // Routes app.get('/', (req, res) => { const episodes = getEpisodes(); if (episodes.length === 0) { return res.status(404).send('No episodes found'); } // Get first episode data const firstEpisode = episodes[0]; const lines = loadEpisode(firstEpisode.id); const colors = loadColors(); res.render('episode', { episodes, currentEpisode: firstEpisode, lines, colors }); }); app.get('/episode/:episodeId', (req, res) => { const episodeId = req.params.episodeId; const lines = loadEpisode(episodeId); if (!lines) { return res.status(404).send('Episode not found'); } const episodes = getEpisodes(); const currentEpisode = { id: episodeId, filename: `${episodeId}_translated.json`, title: episodeId }; const colors = loadColors(); res.render('episode', { episodes, currentEpisode, lines, colors }); }); // Start server app.listen(PORT, '0.0.0.0', () => { console.log(`Episode Web Viewer running at http://localhost:${PORT}`); });