1
0
Files
ws4kp-to-hls/index.js

247 lines
8.0 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';
const LOG_WS4KP_URL = process.env.LOG_WS4KP_URL === '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 including coordinates
geocodeDataPromise = Promise.resolve({
cityName: 'Toronto',
lat: 43.6532,
lon: -79.3832
});
}
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 - optionally include full ws4kp URL
let requestPath = `/weather?city=${encodeURIComponent(city)}`;
if (LOG_WS4KP_URL && initialUrl.startsWith('http')) {
requestPath = initialUrl;
} else if (LOG_WS4KP_URL && lateGeocodePromise) {
// For late geocoded URLs, we'll need to pass a promise that resolves to the URL
requestPath = lateGeocodePromise;
}
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`);
}
}
});