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'); const { getElevation } = require('./elevation'); async function processFile(userName, cartella, fileRelPath, absPath, ext, st) { const isVideo = ['.mp4', '.mov', '.m4v'].includes(ext); const thumbBase = path.join( WEB_ROOT, 'photos', userName, 'thumbs', cartella, path.dirname(fileRelPath) ); await fsp.mkdir(thumbBase, { recursive: true }); // --- Nome file + estensione (dal path ASSOLUTO, sempre corretto) --- const parsed = path.parse(absPath); const baseName = parsed.name; // IMG_0249 const extName = parsed.ext; // .JPG 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 {} let timeRaw = tags?.exif?.DateTimeOriginal?.value?.[0] || null; let takenAtIso = parseExifDateUtc(timeRaw); // Fallback per i video if (isVideo) { const fallback = new Date(st.mtimeMs).toISOString(); takenAtIso = fallback; timeRaw = fallback; } // --- GPS --- let gps = null; if (isVideo) { // i video usano exiftool gps = await extractGpsWithExiftool(absPath); } else { // le foto usano exifreader gps = extractGpsFromExif(tags); } // --- ALTITUDINE DA SERVIZIO ESTERNO SE MANCANTE --- if (gps && gps.lat && gps.lng) { if (gps.alt == null) { gps.alt = await getElevation(gps.lat, gps.lng); } } // --- DIMENSIONI & ROTAZIONE --- let width = null, height = null, duration = null, duration_ms = null, rotation = 0; 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; rotation = 0; if (stream?.tags?.rotate) { rotation = Number(stream.tags.rotate); } const sdl = stream?.side_data_list; if (sdl && Array.isArray(sdl)) { const rotEntry = sdl.find(d => d.rotation !== undefined); if (rotEntry) rotation = Number(rotEntry.rotation); const matrixEntry = sdl.find(d => typeof d.displaymatrix === 'string'); if (matrixEntry) { const match = matrixEntry.displaymatrix.match(/rotation of ([\-0-9]+) degrees/i); if (match) rotation = Number(match[1]); } } rotation = ((rotation % 360) + 360) % 360; } duration = info.format?.duration || null; duration_ms = duration ? Math.round(duration * 1000) : null; } else { try { const meta = await sharp(absPath).metadata(); width = meta.width || null; height = meta.height || null; } catch {} 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] ?? 0; } 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, name: baseName + extName, // 👈 SEMPRE CORRETTO path: fullPath, thub1: fullThub1, thub2: fullThub2, gps, data: timeRaw, taken_at: takenAtIso, mime_type, width, height, rotation, size_bytes: st.size, mtimeMs: st.mtimeMs, duration_ms: isVideo ? duration_ms : null, location }; } module.exports = processFile;