Env Vars & Geocode Data Caching
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@ node_modules
|
||||
*.log
|
||||
.DS_Store
|
||||
.env
|
||||
cache/
|
||||
|
||||
41
README.md
41
README.md
@@ -20,6 +20,34 @@ docker-compose up
|
||||
# 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
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
**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.
|
||||
|
||||
@@ -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)
|
||||
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
|
||||
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
|
||||
@@ -2,10 +2,12 @@ services:
|
||||
app:
|
||||
build: .
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "8080:8080"
|
||||
- "${PORT:-3000}:${PORT:-3000}"
|
||||
- "${WS4KP_PORT:-8080}:${WS4KP_PORT:-8080}"
|
||||
shm_size: 2gb
|
||||
environment:
|
||||
- PORT=3000
|
||||
- WS4KP_PORT=8080
|
||||
- PORT=${PORT:-3000}
|
||||
- WS4KP_PORT=${WS4KP_PORT:-8080}
|
||||
volumes:
|
||||
- ./cache:/streaming-app/cache
|
||||
restart: unless-stopped
|
||||
|
||||
5
index.js
5
index.js
@@ -5,6 +5,7 @@ const { getAllMusicFiles } = 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';
|
||||
|
||||
/**
|
||||
@@ -37,7 +38,8 @@ function buildWeatherUrl(latitude, longitude, settings) {
|
||||
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 ws4kpPort = process.env.WS4KP_PORT || 8080;
|
||||
const ws4kpBaseUrl = process.env.WS4KP_URL || `http://localhost:${ws4kpPort}`;
|
||||
|
||||
const ws4kpParams = new URLSearchParams({
|
||||
'hazards-checkbox': showHazards,
|
||||
@@ -187,6 +189,7 @@ app.get('/health', (req, res) => {
|
||||
// Start server
|
||||
app.listen(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(`Weather: http://localhost:${PORT}/weather?city=YourCity`);
|
||||
|
||||
|
||||
@@ -1,4 +1,59 @@
|
||||
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)
|
||||
@@ -6,6 +61,11 @@ const https = require('https');
|
||||
* @returns {Promise<{lat: number, lon: number, displayName: string}>}
|
||||
*/
|
||||
async function geocodeCity(cityQuery) {
|
||||
// Check cache first
|
||||
const cached = getCachedGeocode(cityQuery);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const encodedQuery = encodeURIComponent(cityQuery);
|
||||
const url = `https://nominatim.openstreetmap.org/search?q=${encodedQuery}&format=json&limit=1`;
|
||||
@@ -27,11 +87,14 @@ async function geocodeCity(cityQuery) {
|
||||
try {
|
||||
const results = JSON.parse(data);
|
||||
if (results && results.length > 0) {
|
||||
resolve({
|
||||
const geocodeResult = {
|
||||
lat: parseFloat(results[0].lat),
|
||||
lon: parseFloat(results[0].lon),
|
||||
displayName: results[0].display_name
|
||||
});
|
||||
};
|
||||
// Save to cache
|
||||
saveCachedGeocode(cityQuery, geocodeResult);
|
||||
resolve(geocodeResult);
|
||||
} else {
|
||||
reject(new Error('No results found'));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user