Files
shpm/worker/hash.js
T
nmemmertandClaude Sonnet 4.6 e0f4e8ff0d feat: initial Luma implementation
Full self-hosted photo manager with:
- Ingestion worker (chokidar + sharp + exifr, dHash perceptual hashing)
- SQLite schema with WAL mode (photos, tags, collections, duplicate_pairs)
- Express API with keyset pagination, tag/collection/search/date filters
- React + Vite UI: timeline, lightbox, detail panel, tag/collection editors
- Duplicate detection (Union-Find grouping) with review + dismiss UI
- Map view (Leaflet + OpenStreetMap, marker clustering)
- Docker multi-stage build + Compose with inline env config
- GitHub Actions: CI build check + ghcr.io image publish on main/tags

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-17 16:33:55 -04:00

32 lines
854 B
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import sharp from 'sharp';
// dHash (difference hash): 9×8 → 64-bit comparison → 16-char hex string.
// Two images with Hamming distance ≤ 10 are near-duplicates.
export async function computeDHash(filepath) {
const { data } = await sharp(filepath)
.resize(9, 8, { fit: 'fill' })
.greyscale()
.raw()
.toBuffer({ resolveWithObject: true });
let hash = 0n;
for (let row = 0; row < 8; row++) {
for (let col = 0; col < 8; col++) {
const left = data[row * 9 + col];
const right = data[row * 9 + col + 1];
hash = (hash << 1n) | (left < right ? 1n : 0n);
}
}
return hash.toString(16).padStart(16, '0');
}
export function hammingDistance(a, b) {
let xor = BigInt('0x' + a) ^ BigInt('0x' + b);
let dist = 0;
while (xor > 0n) {
dist += Number(xor & 1n);
xor >>= 1n;
}
return dist;
}