const express = require('express'); const { streamHandler } = require('./src/streamHandler'); const { geocodeCity } = require('./src/geocode'); const { getAllMusicFiles } = require('./src/musicPlaylist'); const app = express(); const PORT = process.env.PORT || 3000; const MUSIC_PATH = process.env.MUSIC_PATH || '/music'; /** * 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 ws4kpBaseUrl = process.env.WS4KP_URL || 'http://localhost:8080'; 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 (no music) app.get('/stream', (req, res) => { streamHandler(req, res, { useMusic: false, musicPath: MUSIC_PATH }); }); // 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 }; let lateGeocodePromise = null; let initialUrl = 'data:text/html,'; if (city && city !== 'Toronto, ON, CAN') { // Try quick geocode first const geocodePromise = Promise.race([ geocodeCity(city), new Promise((_, reject) => setTimeout(() => reject(new Error('Geocoding timeout')), 1000)) ]).then(geoResult => { console.log(`Geocoded: ${city} -> ${geoResult.displayName}`); const finalUrl = buildWeatherUrl(geoResult.lat, geoResult.lon, weatherSettings); console.log(`URL: ${finalUrl}`); return { url: finalUrl, lat: geoResult.lat, lon: geoResult.lon }; }).catch(error => { // Continue geocoding in background return geocodeCity(city).then(geoResult => { const finalUrl = buildWeatherUrl(geoResult.lat, geoResult.lon, weatherSettings); console.log(`Geocoding completed: ${geoResult.displayName} (${geoResult.lat}, ${geoResult.lon})`); console.log(`Final URL: ${finalUrl}`); return { url: finalUrl, lat: geoResult.lat, lon: geoResult.lon, isLate: true }; }).catch(err => { console.warn(`Geocoding failed: ${err.message}`); // Fallback to Toronto const fallbackUrl = buildWeatherUrl(43.6532, -79.3832, weatherSettings); return { url: fallbackUrl, lat: 43.6532, lon: -79.3832, isLate: true }; }); }); lateGeocodePromise = geocodePromise.then(result => result.url); } else { // Toronto default initialUrl = buildWeatherUrl(43.6532, -79.3832, weatherSettings); console.log(`URL: ${initialUrl}`); } console.log(`Stream starting: ${city}`); // 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 return streamHandler(req, res, { useMusic: true, musicPath: MUSIC_PATH, lateGeocodePromise }); }); // 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}`); console.log(`Usage: 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(`\n🎵 Pre-validating music library at ${MUSIC_PATH}...`); const validFiles = getAllMusicFiles(MUSIC_PATH); if (validFiles.length > 0) { console.log(`✅ Music library ready: ${validFiles.length} valid tracks cached\n`); } else { console.log(`⚠️ No valid music files found in ${MUSIC_PATH}\n`); } } });