Files

99 lines
3.4 KiB
JavaScript

const fs = require('fs');
const { exiftool } = require('exiftool-vendored');
const piexif = require('piexifjs');
/**
* Parses GPS coordinates from an image file (JPEG or DNG) using ExifTool
* @param {string} filePath - Path to the image file
* @returns {Promise<{lat: number, lng: number}|null>} GPS coordinates or null if not found
*/
const getGPSCoordinates = async (filePath) => {
try {
const tags = await exiftool.read(filePath);
if (tags && typeof tags.GPSLatitude === 'number' && typeof tags.GPSLongitude === 'number') {
return {
lat: tags.GPSLatitude,
lng: tags.GPSLongitude
};
}
return null;
} catch (error) {
console.warn(`ExifTool could not read GPS from ${filePath}: ${error.message}`);
return null;
}
};
/**
* Converts decimal degrees to Degrees, Minutes, Seconds rational format for piexifjs
* @param {number} deg - Decimal degrees coordinate
* @returns {Array} Array of rational numbers [[D, 1], [M, 1], [S * 100, 100]]
*/
const degToDmsRational = (deg) => {
const absolute = Math.abs(deg);
const d = Math.floor(absolute);
const m = Math.floor((absolute - d) * 60);
const s = Math.round((absolute - d - m / 60) * 3600 * 100);
return [
[d, 1],
[m, 1],
[s, 100]
];
};
/**
* Injects GPS coordinates into a JPEG image file using piexifjs
* @param {string} filePath - Path to the JPEG file
* @param {number} lat - Latitude
* @param {number} lng - Longitude
*/
const injectGPSCoordinates = async (filePath, lat, lng) => {
try {
const data = fs.readFileSync(filePath);
// Kiểm tra marker SOI (Start of Image) trực tiếp trên Buffer
if (data[0] !== 0xFF || data[1] !== 0xD8) {
throw new Error("Tệp tin không phải là định dạng JPEG hợp lệ (thiếu SOI marker).");
}
const jpegBinary = data.toString('binary');
let exifObj;
try {
exifObj = piexif.load(jpegBinary);
} catch (e) {
// Nếu không có EXIF hoặc lỗi khi nạp, khởi tạo đối tượng sạch
exifObj = { "0th": {}, "Exif": {}, "GPS": {} };
}
// Đảm bảo các cấu trúc IFD tồn tại trước khi ghi đè
exifObj["GPS"] = exifObj["GPS"] || {};
const latRef = lat >= 0 ? 'N' : 'S';
const lngRef = lng >= 0 ? 'E' : 'W';
// Thêm Version ID (Bắt buộc để một số trình đọc nhận diện khối GPS)
exifObj["GPS"][piexif.GPSIFD.GPSVersionID] = [2, 2, 0, 0];
exifObj["GPS"][piexif.GPSIFD.GPSLatitudeRef] = latRef;
exifObj["GPS"][piexif.GPSIFD.GPSLatitude] = degToDmsRational(lat);
exifObj["GPS"][piexif.GPSIFD.GPSLongitudeRef] = lngRef;
exifObj["GPS"][piexif.GPSIFD.GPSLongitude] = degToDmsRational(lng);
// Chỉ đóng gói các IFD cần thiết để tránh lỗi 'pack' từ dữ liệu rác
const exifBytes = piexif.dump({
"0th": exifObj["0th"] || {},
"Exif": exifObj["Exif"] || {},
"GPS": exifObj["GPS"] || {}
});
const newJpegBinary = piexif.insert(exifBytes, jpegBinary);
fs.writeFileSync(filePath, Buffer.from(newJpegBinary, 'binary'));
} catch (error) {
throw new Error(`Failed to inject EXIF GPS: ${error.message}`);
}
};
module.exports = {
getGPSCoordinates,
injectGPSCoordinates
};