galleria_photo_face_rknn_op.../api_v1/scanner/processFile.js
2026-10-03 12:16:58 +02:00

207 lines
5.6 KiB
JavaScript

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 { 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 });
// --- 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`);
const absThumbCast = path.join(thumbBase, `${baseName}_cast.jpg`);
if (isVideo) {
await createVideoThumbnail(absPath, absThumbMin, absThumbAvg);
} else {
await createThumbnails(absPath, absThumbMin, absThumbAvg, absThumbCast);
}
// --- 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 info = await probeVideo(absPath);
// Cerca la data nei punti standard
const creationFormat = info.format?.tags?.creation_time || null;
const creationStream0 = info.streams?.[0]?.tags?.creation_time || null;
const creationStream1 = info.streams?.[1]?.tags?.creation_time || null;
// Scegli la prima disponibile
const videoDate =
creationFormat || creationStream0 || creationStream1 || null;
if (videoDate) {
// NON convertire, NON usare new Date()
takenAtIso = videoDate;
timeRaw = videoDate;
} else {
// Fallback finale: mtime del file
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);
}
// L'altitudine EXIF viene mantenuta.
// Se assente, sarà recuperata dal geocode worker.
// --- 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}`);
// Reverse geocoding eseguito in modo asincrono.
const location = null;
//
// --- GESTIONE PATH FULL / RELATIVI ---
//
const relPath = fileRelPath;
const relThub1 = fileRelPath.replace(/\.[^.]+$/, "_min.jpg");
const relThub2 = fileRelPath.replace(/\.[^.]+$/, "_avg.jpg");
const relCast = isVideo ? "" : fileRelPath.replace(/\.[^.]+$/, "_cast.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;
const fullCast = isVideo
? ""
: PATH_FULL
? path.posix.join("/photos", userName, "thumbs", cartella, relCast)
: relCast;
return {
id,
user: userName,
cartella,
name: baseName + extName, // 👈 SEMPRE CORRETTO
path: fullPath,
thub1: fullThub1,
thub2: fullThub2,
cast: fullCast,
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;