webnode
This commit is contained in:
144
webnode/server.js
Normal file
144
webnode/server.js
Normal file
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* 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, '"')
|
||||
.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}`);
|
||||
});
|
||||
Reference in New Issue
Block a user