171 lines
4.5 KiB
Text
171 lines
4.5 KiB
Text
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);
|
|
|
|
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 & 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;
|
|
|
|
// --- ROTAZIONE VIDEO (robusta, compatibile Xiaomi) ---
|
|
rotation = 0;
|
|
|
|
// 1) rotate standard
|
|
if (stream?.tags?.rotate) {
|
|
rotation = Number(stream.tags.rotate);
|
|
}
|
|
|
|
// 2) side_data_list rotation
|
|
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);
|
|
}
|
|
|
|
// 3) Xiaomi: displaymatrix string
|
|
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]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Normalizza (Xiaomi usa -90 → 270)
|
|
rotation = ((rotation % 360) + 360) % 360;
|
|
}
|
|
|
|
duration = info.format?.duration || null;
|
|
duration_ms = duration ? Math.round(duration * 1000) : null;
|
|
|
|
} else {
|
|
// FOTO
|
|
try {
|
|
const meta = await sharp(absPath).metadata();
|
|
width = meta.width || null;
|
|
height = meta.height || null;
|
|
} catch {}
|
|
|
|
// --- ROTAZIONE EXIF FOTO ---
|
|
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,
|
|
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: isVideo ? duration : null,
|
|
duration_ms: isVideo ? duration_ms : null,
|
|
location
|
|
};
|
|
}
|
|
|
|
module.exports = processFile;
|