feat: Add backfill script for image brightness stats and display them in the UI.
This commit is contained in:
112
backfill_stats.js
Normal file
112
backfill_stats.js
Normal file
@@ -0,0 +1,112 @@
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import sharp from 'sharp';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const UPLOAD_DIR = process.env.PICUPPER_UPLOAD_DIR || path.join(__dirname, 'uploads');
|
||||
|
||||
async function processDirectory(dir) {
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
|
||||
// Check if we are in a leaf "day" directory (has images)
|
||||
const images = entries.filter(e => e.isFile() && /\.(jpg|jpeg|png|webp)$/i.test(e.name) && !e.name.includes('_thumb'));
|
||||
|
||||
if (images.length > 0) {
|
||||
console.log(`Processing directory: ${dir}`);
|
||||
const statsFile = path.join(dir, 'stats.json');
|
||||
let stats = [];
|
||||
|
||||
if (fs.existsSync(statsFile)) {
|
||||
try {
|
||||
stats = JSON.parse(fs.readFileSync(statsFile, 'utf8'));
|
||||
} catch (e) {
|
||||
console.error(`Error reading ${statsFile}`, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Create a set of existing filenames for fast lookup
|
||||
const processedFiles = new Set(stats.map(s => s.filename));
|
||||
let updated = false;
|
||||
|
||||
for (const img of images) {
|
||||
if (processedFiles.has(img.name)) continue;
|
||||
|
||||
const imgPath = path.join(dir, img.name);
|
||||
console.log(` Computing brightness for ${img.name}...`);
|
||||
|
||||
try {
|
||||
const image = sharp(imgPath);
|
||||
const s = await image.stats();
|
||||
const brightness = Math.round((s.channels[0].mean + s.channels[1].mean + s.channels[2].mean) / 3);
|
||||
|
||||
// Try to extract timestamp from filename: {cameraId}_{timestamp}.{ext}
|
||||
// Timestamp in filename is usually ISO like 2024-12-19T14-00-00-000Z but with - instead of :
|
||||
// We need a valid ISO string for consistency.
|
||||
|
||||
// Filename format: {cameraId}_YYYY-MM-DDTHH-mm-ss-sssZ.{ext}
|
||||
// extract YYYY-MM-DDTHH-mm-ss-sssZ
|
||||
let timestamp = new Date().toISOString();
|
||||
const match = img.name.match(/_(\d{4}-\d{2}-\d{2}T[\d-]+Z)\./);
|
||||
if (match) {
|
||||
// Replace - with : in time part for standard ISO parseability if needed,
|
||||
// or just store as is if that's what we did in server.js.
|
||||
// In server.js: new Date().toISOString().replace(/[:.]/g, '-') was used for filename.
|
||||
// For the stats entry, server.js uses new Date().toISOString() (standard ISO with colons).
|
||||
|
||||
// Reconstruct standard ISO
|
||||
// stored format: 2025-12-19T14-25-00-123Z
|
||||
// target: 2025-12-19T14:25:00.123Z
|
||||
const rawTs = match[1];
|
||||
const parts = rawTs.match(/(\d{4})-(\d{2})-(\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z/);
|
||||
if (parts) {
|
||||
timestamp = `${parts[1]}-${parts[2]}-${parts[3]}T${parts[4]}:${parts[5]}:${parts[6]}.${parts[7]}Z`;
|
||||
} else {
|
||||
// Fallback for older formats if any
|
||||
timestamp = rawTs;
|
||||
}
|
||||
} else {
|
||||
// Fallback to file mtime
|
||||
const fstats = fs.statSync(imgPath);
|
||||
timestamp = fstats.mtime.toISOString();
|
||||
}
|
||||
|
||||
stats.push({
|
||||
timestamp: timestamp,
|
||||
filename: img.name,
|
||||
brightness: brightness
|
||||
});
|
||||
updated = true;
|
||||
} catch (err) {
|
||||
console.error(` Failed to process ${img.name}:`, err.message);
|
||||
}
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
// Sort by timestamp
|
||||
stats.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
||||
fs.writeFileSync(statsFile, JSON.stringify(stats, null, 2));
|
||||
console.log(` Updated ${statsFile} with new entries.`);
|
||||
} else {
|
||||
console.log(` No new images to process.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Recurse into subdirectories
|
||||
for (const entry of entries) {
|
||||
if (entry.isDirectory()) {
|
||||
await processDirectory(path.join(dir, entry.name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start
|
||||
console.log(`Starting brightness backfill in ${UPLOAD_DIR}...`);
|
||||
processDirectory(UPLOAD_DIR).then(() => {
|
||||
console.log('Done!');
|
||||
}).catch(err => {
|
||||
console.error('Fatal error:', err);
|
||||
});
|
||||
Reference in New Issue
Block a user