const path = require('path'); const fsp = require('fs/promises'); const ExifReader = require('exifreader'); const sharp = require('sharp'); const { sha256, inferMimeFromExt, parseExifDateUtc } = require('./utils'); const { extractGpsFromExif, extractGpsWithExiftool } = require('./gps'); const { createVideoThumbnail, createThumbnails } = require('./thumbs'); const { probeVideo } = require('./video'); const loc = require('../geo.js'); const { WEB_ROOT, PATH_FULL } = require('../config'); async function processFile(userName, cartella, fileRelPath, absPath, ext, st) { const isVideo = ['.mp4', '.mov', '.m4v'].includes(ext); console.log( `PROCESSING → user=${userName} | cartella=${cartella} | file=${fileRelPath}` ); const thumbBase = path.join( WEB_ROOT, 'photos', userName, 'thumbs', cartella, path.dirname(fileRelPath) ); await fsp.mkdir(thumbBase, { recursive: true }); const baseName = path.parse(fileRelPath).name; const absThumbMin = path.join(thumbBase, `${baseName}_min.jpg`); const absThumbAvg = path.join(thumbBase, `${baseName}_avg.jpg`); if (isVideo) { await createVideoThumbnail(absPath, absThumbMin, absThumbAvg); } else { await createThumbnails(absPath, absThumbMin, absThumbAvg); } // --- EXIF --- let tags = {}; try { tags = await ExifReader.load(absPath, { expanded: true }); } catch {} const timeRaw = tags?.exif?.DateTimeOriginal?.value?.[0] || null; const takenAtIso = parseExifDateUtc(timeRaw); // --- GPS --- let gps = isVideo ? await extractGpsWithExiftool(absPath) : extractGpsFromExif(tags); // --- DIMENSIONI --- let width = null, height = null, duration = null; if (isVideo) { const info = await probeVideo(absPath); const stream = info.streams?.find(s => s.width && s.height); if (stream) { width = stream.width; height = stream.height; } duration = info.format?.duration || null; } else { try { const meta = await sharp(absPath).metadata(); width = meta.width || null; height = meta.height || null; } catch {} } // --- ROTAZIONE EXIF → GRADI REALI --- let rotation = null; try { const raw = tags?.exif?.Orientation?.value ?? tags?.image?.Orientation?.value ?? tags?.ifd0?.Orientation?.value ?? null; const val = Array.isArray(raw) ? raw[0] : raw; const map = { 1: 0, 3: 180, 6: 90, 8: 270 }; rotation = map[val] ?? null; } catch {} const mime_type = inferMimeFromExt(ext); const id = sha256(`${userName}/${cartella}/${fileRelPath}`); const location = gps ? await loc(gps.lng, gps.lat) : null; // // --- GESTIONE PATH FULL / RELATIVI --- // const relPath = fileRelPath; const relThub1 = fileRelPath.replace(/\.[^.]+$/, '_min.jpg'); const relThub2 = fileRelPath.replace(/\.[^.]+$/, '_avg.jpg'); const fullPath = PATH_FULL ? path.posix.join('/photos', userName, cartella, fileRelPath) : relPath; const fullThub1 = PATH_FULL ? path.posix.join('/photos', userName, 'thumbs', cartella, relThub1) : relThub1; const fullThub2 = PATH_FULL ? path.posix.join('/photos', userName, 'thumbs', cartella, relThub2) : relThub2; return { id, user: userName, cartella, path: fullPath, thub1: fullThub1, thub2: fullThub2, gps, data: timeRaw, taken_at: takenAtIso, mime_type, width, height, rotation, // <── ROTAZIONE IN GRADI REALI size_bytes: st.size, mtimeMs: st.mtimeMs, duration: isVideo ? duration : null, location }; } module.exports = processFile;