236 lines
7.6 KiB
JavaScript
236 lines
7.6 KiB
JavaScript
const express = require('express');
|
|
const { streamHandler } = require('./src/streamHandler');
|
|
const { geocodeCity } = require('./src/geocode');
|
|
const { getAllMusicFiles, initializeSharedPlaylist } = require('./src/musicPlaylist');
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 3000;
|
|
const WS4KP_PORT = process.env.WS4KP_PORT || 8080;
|
|
const MUSIC_PATH = process.env.MUSIC_PATH || '/music';
|
|
const DEFAULT_FPS = parseInt(process.env.DEFAULT_FPS || '30');
|
|
const SCREENSHOT_FORMAT = process.env.SCREENSHOT_FORMAT || 'jpeg';
|
|
const SCREENSHOT_QUALITY = parseInt(process.env.SCREENSHOT_QUALITY || '95');
|
|
const DEBUG_MODE = process.env.DEBUG_MODE === 'true';
|
|
|
|
/**
|
|
* Build WS4KP weather URL with given coordinates and settings
|
|
*/
|
|
function buildWeatherUrl(latitude, longitude, settings) {
|
|
const {
|
|
city,
|
|
showHazards,
|
|
showCurrent,
|
|
showLatestObservations,
|
|
showHourly,
|
|
showHourlyGraph,
|
|
showTravel,
|
|
showRegionalForecast,
|
|
showLocalForecast,
|
|
showExtendedForecast,
|
|
showAlmanac,
|
|
showRadar,
|
|
showMarineForecast,
|
|
showAQI,
|
|
units,
|
|
timeFormat
|
|
} = settings;
|
|
|
|
const isMetric = units.toLowerCase() === 'metric';
|
|
const temperatureUnit = isMetric ? '1.00' : '2.00';
|
|
const windUnit = isMetric ? '1.00' : '2.00';
|
|
const distanceUnit = isMetric ? '1.00' : '2.00';
|
|
const pressureUnit = isMetric ? '1.00' : '2.00';
|
|
const hoursFormat = timeFormat === '12h' ? '1.00' : '2.00';
|
|
|
|
const ws4kpPort = process.env.WS4KP_PORT || 8080;
|
|
const ws4kpBaseUrl = process.env.WS4KP_URL || `http://localhost:${ws4kpPort}`;
|
|
|
|
const ws4kpParams = new URLSearchParams({
|
|
'hazards-checkbox': showHazards,
|
|
'current-weather-checkbox': showCurrent,
|
|
'latest-observations-checkbox': showLatestObservations,
|
|
'hourly-checkbox': showHourly,
|
|
'hourly-graph-checkbox': showHourlyGraph,
|
|
'travel-checkbox': showTravel,
|
|
'regional-forecast-checkbox': showRegionalForecast,
|
|
'local-forecast-checkbox': showLocalForecast,
|
|
'extended-forecast-checkbox': showExtendedForecast,
|
|
'almanac-checkbox': showAlmanac,
|
|
'radar-checkbox': showRadar,
|
|
'marine-forecast-checkbox': showMarineForecast,
|
|
'aqi-forecast-checkbox': showAQI,
|
|
'settings-experimentalFeatures-checkbox': 'false',
|
|
'settings-hideWebamp-checkbox': 'true',
|
|
'settings-kiosk-checkbox': 'false',
|
|
'settings-scanLines-checkbox': 'false',
|
|
'settings-wide-checkbox': 'true',
|
|
'chkAutoRefresh': 'true',
|
|
'settings-windUnits-select': windUnit,
|
|
'settings-marineWindUnits-select': '1.00',
|
|
'settings-marineWaveHeightUnits-select': '1.00',
|
|
'settings-temperatureUnits-select': temperatureUnit,
|
|
'settings-distanceUnits-select': distanceUnit,
|
|
'settings-pressureUnits-select': pressureUnit,
|
|
'settings-hoursFormat-select': hoursFormat,
|
|
'settings-speed-select': '1.00',
|
|
'latLonQuery': city,
|
|
'latLon': JSON.stringify({ lat: latitude, lon: longitude }),
|
|
'kiosk': 'true'
|
|
});
|
|
|
|
return `${ws4kpBaseUrl}/?${ws4kpParams.toString()}`;
|
|
}
|
|
|
|
// Basic stream endpoint (with optional music and scroll parameters)
|
|
app.get('/stream', (req, res) => {
|
|
const { music = 'false', debug = DEBUG_MODE ? 'true' : 'false' } = req.query;
|
|
const useMusic = music === 'true';
|
|
const startTime = Date.now();
|
|
streamHandler(req, res, {
|
|
useMusic,
|
|
musicPath: MUSIC_PATH,
|
|
startTime,
|
|
defaultFps: DEFAULT_FPS,
|
|
screenshotFormat: SCREENSHOT_FORMAT,
|
|
screenshotQuality: SCREENSHOT_QUALITY,
|
|
debugMode: debug === 'true'
|
|
});
|
|
});
|
|
|
|
// Weather endpoint (with music)
|
|
app.get('/weather', async (req, res) => {
|
|
const {
|
|
city = 'Toronto, ON, CAN',
|
|
width = 1920,
|
|
height = 1080,
|
|
fps = 30,
|
|
hideLogo = 'false',
|
|
units = 'metric',
|
|
timeFormat = '24h',
|
|
showHazards = 'true',
|
|
showCurrent = 'true',
|
|
showHourly = 'true',
|
|
showHourlyGraph = 'true',
|
|
showLocalForecast = 'true',
|
|
showExtendedForecast = 'true',
|
|
showRadar = 'true',
|
|
showAQI = 'true',
|
|
showAlmanac = 'true',
|
|
showLatestObservations = 'false',
|
|
showRegionalForecast = 'false',
|
|
showTravel = 'false',
|
|
showMarineForecast = 'false'
|
|
} = req.query;
|
|
|
|
const weatherSettings = {
|
|
city,
|
|
showHazards,
|
|
showCurrent,
|
|
showLatestObservations,
|
|
showHourly,
|
|
showHourlyGraph,
|
|
showTravel,
|
|
showRegionalForecast,
|
|
showLocalForecast,
|
|
showExtendedForecast,
|
|
showAlmanac,
|
|
showRadar,
|
|
showMarineForecast,
|
|
showAQI,
|
|
units,
|
|
timeFormat
|
|
};
|
|
|
|
// Generate session ID for logging
|
|
const sessionId = Math.floor(Math.random() * 100000);
|
|
|
|
let lateGeocodePromise = null;
|
|
let geocodeDataPromise = null;
|
|
let initialUrl = 'data:text/html,<html><body style="margin:0;padding:0;background:#000"></body></html>';
|
|
|
|
if (city && city !== 'Toronto, ON, CAN') {
|
|
// Start geocoding in background - don't wait for it
|
|
const geocodePromise = geocodeCity(city, sessionId);
|
|
geocodeDataPromise = geocodePromise; // Save for city name overlay
|
|
|
|
// Always start with black screen immediately
|
|
initialUrl = 'data:text/html,<html><body style="margin:0;padding:0;background:#000"></body></html>';
|
|
|
|
// Late geocode promise will load the actual weather page
|
|
lateGeocodePromise = geocodePromise.then(geoResult => {
|
|
return buildWeatherUrl(geoResult.lat, geoResult.lon, weatherSettings);
|
|
}).catch(err => {
|
|
console.warn(`Geocoding failed for ${city}, using fallback`);
|
|
// Fallback to Toronto
|
|
return buildWeatherUrl(43.6532, -79.3832, weatherSettings);
|
|
});
|
|
} else {
|
|
// Toronto default
|
|
initialUrl = buildWeatherUrl(43.6532, -79.3832, weatherSettings);
|
|
// Create resolved promise with Toronto data
|
|
geocodeDataPromise = Promise.resolve({ cityName: 'Toronto' });
|
|
}
|
|
|
|
const startTime = Date.now();
|
|
|
|
// Update request query for stream handler
|
|
req.query.url = initialUrl;
|
|
req.query.width = width;
|
|
req.query.height = height;
|
|
req.query.fps = fps;
|
|
req.query.hideLogo = hideLogo;
|
|
|
|
// Call stream handler with music enabled
|
|
const { debug = DEBUG_MODE ? 'true' : 'false' } = req.query;
|
|
|
|
// Build request path for logging
|
|
const requestPath = `/weather?city=${encodeURIComponent(city)}`;
|
|
|
|
return streamHandler(req, res, {
|
|
useMusic: true,
|
|
musicPath: MUSIC_PATH,
|
|
lateGeocodePromise,
|
|
geocodeDataPromise,
|
|
startTime,
|
|
defaultFps: DEFAULT_FPS,
|
|
screenshotFormat: SCREENSHOT_FORMAT,
|
|
screenshotQuality: SCREENSHOT_QUALITY,
|
|
debugMode: debug === 'true',
|
|
sessionId,
|
|
requestPath
|
|
});
|
|
});
|
|
|
|
// Health check endpoint
|
|
app.get('/health', (req, res) => {
|
|
res.send('OK');
|
|
});
|
|
|
|
// Start server
|
|
app.listen(PORT, () => {
|
|
console.log(`Webpage to HLS server running on port ${PORT}`);
|
|
if (process.env.WS4KP_EXTERNAL_PORT) {
|
|
console.log(`WS4KP weather service on port ${process.env.WS4KP_EXTERNAL_PORT}`);
|
|
}
|
|
console.log(`\nConfiguration:`);
|
|
console.log(` Default FPS: ${DEFAULT_FPS}`);
|
|
console.log(` Screenshot Format: ${SCREENSHOT_FORMAT}`);
|
|
console.log(` Screenshot Quality: ${SCREENSHOT_QUALITY}${SCREENSHOT_FORMAT === 'jpeg' ? '%' : ' (ignored for PNG)'}`);
|
|
console.log(`\nUsage: http://localhost:${PORT}/stream?url=http://example.com`);
|
|
console.log(`Weather: http://localhost:${PORT}/weather?city=YourCity`);
|
|
|
|
// Pre-validate music files on startup to cache results
|
|
if (MUSIC_PATH) {
|
|
console.log(`\nInitializing music library at ${MUSIC_PATH}...`);
|
|
const validFiles = getAllMusicFiles(MUSIC_PATH);
|
|
if (validFiles.length > 0) {
|
|
console.log(`Music library ready: ${validFiles.length} tracks validated`);
|
|
// Initialize shared playlist at startup
|
|
initializeSharedPlaylist(MUSIC_PATH);
|
|
console.log('');
|
|
} else {
|
|
console.log(`Warning: No valid music files found in ${MUSIC_PATH}\n`);
|
|
}
|
|
}
|
|
});
|