1
0

Env Vars & Geocode Data Caching

This commit is contained in:
2025-11-17 08:31:23 -05:00
parent 1f89e8d243
commit ef0ddbe778
5 changed files with 115 additions and 9 deletions

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@ node_modules
*.log *.log
.DS_Store .DS_Store
.env .env
cache/

View File

@@ -20,6 +20,34 @@ docker-compose up
# ws4kp interface available at http://localhost:8080 # ws4kp interface available at http://localhost:8080
``` ```
### Environment Variables
Configure ports and services via environment variables:
```bash
# Default values
PORT=3000 # Main streaming server port
WS4KP_PORT=8080 # WS4KP weather service port
MUSIC_PATH=/music # Path to music files
# Example with custom ports
PORT=8000 WS4KP_PORT=9090 docker-compose up
```
Or use a `.env` file with docker-compose:
```env
PORT=8000
WS4KP_PORT=9090
```
### Persistent Geocoding Cache
Geocoding results are cached in the `./cache` directory. When using docker-compose, this directory is automatically mounted to persist between container restarts. If using Docker directly, mount the cache volume to persist data:
```bash
docker run -p 3000:3000 -p 8080:8080 -v $(pwd)/cache:/streaming-app/cache ghcr.io/sethwv/ws4kp-to-hls:latest
```
### Using Docker directly ### Using Docker directly
```bash ```bash
@@ -72,7 +100,7 @@ http://localhost:3000/weather?city=Miami,FL,USA&width=1280&height=720&fps=25
http://localhost:3000/weather?city=Toronto,ON,Canada&hideLogo=true http://localhost:3000/weather?city=Toronto,ON,Canada&hideLogo=true
``` ```
**City Format**: Use `City,State,Country` format for best accuracy (e.g., `Toronto,ON,Canada` or `Miami,FL,USA`). The service automatically geocodes the city using OpenStreetMap's Nominatim API and falls back to Toronto coordinates if geocoding fails. **City Format**: Use `City,State,Country` format for best accuracy (e.g., `Toronto,ON,Canada` or `Miami,FL,USA`). The service automatically geocodes the city using OpenStreetMap's Nominatim API and falls back to Toronto coordinates if geocoding fails. Geocoding results are cached in the `./cache` directory to improve performance and reduce API calls.
**Units**: Use `units=metric` (default) for Celsius/kph/km/mb or `units=imperial` for Fahrenheit/mph/miles/inHg. **Units**: Use `units=metric` (default) for Celsius/kph/km/mb or `units=imperial` for Fahrenheit/mph/miles/inHg.
@@ -146,4 +174,13 @@ ffmpeg -i "http://localhost:3000/stream?url=http://example.com" \
3. FFmpeg encodes the screenshots into an HLS stream (H.264 video, AAC audio for weather) 3. FFmpeg encodes the screenshots into an HLS stream (H.264 video, AAC audio for weather)
4. For weather streams: background music is shuffled and played from the Weatherscan collection 4. For weather streams: background music is shuffled and played from the Weatherscan collection
5. The HLS stream is piped directly to the HTTP response 5. The HLS stream is piped directly to the HTTP response
6. City names are automatically geocoded to coordinates via OpenStreetMap's Nominatim API 6. City names are automatically geocoded to coordinates via OpenStreetMap's Nominatim API (results are cached locally for performance)
## Features
- **Configurable Ports**: Both streaming server and WS4KP service ports are configurable via environment variables
- **Geocoding Cache**: City geocoding results are cached on the filesystem to reduce API calls and improve response times
- **Background Music**: Weather streams include shuffled Weatherscan music collection
- **Multi-platform**: Docker images for AMD64 and ARM64 architectures
- **Automatic Geocoding**: City names are converted to coordinates automatically
- **Flexible Display Options**: Control which weather forecast sections to show/hide

View File

@@ -2,10 +2,12 @@ services:
app: app:
build: . build: .
ports: ports:
- "3000:3000" - "${PORT:-3000}:${PORT:-3000}"
- "8080:8080" - "${WS4KP_PORT:-8080}:${WS4KP_PORT:-8080}"
shm_size: 2gb shm_size: 2gb
environment: environment:
- PORT=3000 - PORT=${PORT:-3000}
- WS4KP_PORT=8080 - WS4KP_PORT=${WS4KP_PORT:-8080}
volumes:
- ./cache:/streaming-app/cache
restart: unless-stopped restart: unless-stopped

View File

@@ -5,6 +5,7 @@ const { getAllMusicFiles } = require('./src/musicPlaylist');
const app = express(); const app = express();
const PORT = process.env.PORT || 3000; const PORT = process.env.PORT || 3000;
const WS4KP_PORT = process.env.WS4KP_PORT || 8080;
const MUSIC_PATH = process.env.MUSIC_PATH || '/music'; const MUSIC_PATH = process.env.MUSIC_PATH || '/music';
/** /**
@@ -37,7 +38,8 @@ function buildWeatherUrl(latitude, longitude, settings) {
const pressureUnit = isMetric ? '1.00' : '2.00'; const pressureUnit = isMetric ? '1.00' : '2.00';
const hoursFormat = timeFormat === '12h' ? '1.00' : '2.00'; const hoursFormat = timeFormat === '12h' ? '1.00' : '2.00';
const ws4kpBaseUrl = process.env.WS4KP_URL || 'http://localhost:8080'; const ws4kpPort = process.env.WS4KP_PORT || 8080;
const ws4kpBaseUrl = process.env.WS4KP_URL || `http://localhost:${ws4kpPort}`;
const ws4kpParams = new URLSearchParams({ const ws4kpParams = new URLSearchParams({
'hazards-checkbox': showHazards, 'hazards-checkbox': showHazards,
@@ -187,6 +189,7 @@ app.get('/health', (req, res) => {
// Start server // Start server
app.listen(PORT, () => { app.listen(PORT, () => {
console.log(`Webpage to HLS server running on port ${PORT}`); console.log(`Webpage to HLS server running on port ${PORT}`);
console.log(`WS4KP weather service on port ${WS4KP_PORT}`);
console.log(`Usage: http://localhost:${PORT}/stream?url=http://example.com`); console.log(`Usage: http://localhost:${PORT}/stream?url=http://example.com`);
console.log(`Weather: http://localhost:${PORT}/weather?city=YourCity`); console.log(`Weather: http://localhost:${PORT}/weather?city=YourCity`);

View File

@@ -1,4 +1,59 @@
const https = require('https'); const https = require('https');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const CACHE_DIR = path.join(__dirname, '..', 'cache');
// Ensure cache directory exists
if (!fs.existsSync(CACHE_DIR)) {
fs.mkdirSync(CACHE_DIR, { recursive: true });
}
/**
* Generate a safe filename from a city query
* @param {string} cityQuery - City name
* @returns {string} Safe filename
*/
function getCacheFileName(cityQuery) {
const hash = crypto.createHash('md5').update(cityQuery.toLowerCase().trim()).digest('hex');
return `geocode_${hash}.json`;
}
/**
* Get cached geocode data if available
* @param {string} cityQuery - City name
* @returns {Object|null} Cached data or null
*/
function getCachedGeocode(cityQuery) {
try {
const cacheFile = path.join(CACHE_DIR, getCacheFileName(cityQuery));
if (fs.existsSync(cacheFile)) {
const data = fs.readFileSync(cacheFile, 'utf8');
const cached = JSON.parse(data);
console.log(`Using cached geocode for: ${cityQuery}`);
return cached;
}
} catch (error) {
console.warn(`Cache read error for ${cityQuery}:`, error.message);
}
return null;
}
/**
* Save geocode data to cache
* @param {string} cityQuery - City name
* @param {Object} data - Geocode data to cache
*/
function saveCachedGeocode(cityQuery, data) {
try {
const cacheFile = path.join(CACHE_DIR, getCacheFileName(cityQuery));
fs.writeFileSync(cacheFile, JSON.stringify(data, null, 2), 'utf8');
console.log(`Cached geocode for: ${cityQuery}`);
} catch (error) {
console.warn(`Cache write error for ${cityQuery}:`, error.message);
}
}
/** /**
* Geocode city to lat/lon using Nominatim (OpenStreetMap) * Geocode city to lat/lon using Nominatim (OpenStreetMap)
@@ -6,6 +61,11 @@ const https = require('https');
* @returns {Promise<{lat: number, lon: number, displayName: string}>} * @returns {Promise<{lat: number, lon: number, displayName: string}>}
*/ */
async function geocodeCity(cityQuery) { async function geocodeCity(cityQuery) {
// Check cache first
const cached = getCachedGeocode(cityQuery);
if (cached) {
return cached;
}
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const encodedQuery = encodeURIComponent(cityQuery); const encodedQuery = encodeURIComponent(cityQuery);
const url = `https://nominatim.openstreetmap.org/search?q=${encodedQuery}&format=json&limit=1`; const url = `https://nominatim.openstreetmap.org/search?q=${encodedQuery}&format=json&limit=1`;
@@ -27,11 +87,14 @@ async function geocodeCity(cityQuery) {
try { try {
const results = JSON.parse(data); const results = JSON.parse(data);
if (results && results.length > 0) { if (results && results.length > 0) {
resolve({ const geocodeResult = {
lat: parseFloat(results[0].lat), lat: parseFloat(results[0].lat),
lon: parseFloat(results[0].lon), lon: parseFloat(results[0].lon),
displayName: results[0].display_name displayName: results[0].display_name
}); };
// Save to cache
saveCachedGeocode(cityQuery, geocodeResult);
resolve(geocodeResult);
} else { } else {
reject(new Error('No results found')); reject(new Error('No results found'));
} }