2021-08-06 04:44:48 +00:00
|
|
|
import EXIF from 'exif-js';
|
2021-08-18 05:51:17 +00:00
|
|
|
|
2021-08-06 04:44:48 +00:00
|
|
|
import { logError } from 'utils/sentry';
|
2021-08-09 09:07:47 +00:00
|
|
|
import { NULL_LOCATION, Location } from './metadataService';
|
2021-08-06 04:44:48 +00:00
|
|
|
|
|
|
|
const SOUTH_DIRECTION = 'S';
|
|
|
|
const WEST_DIRECTION = 'W';
|
2021-08-18 05:51:17 +00:00
|
|
|
const EXIF_HAVING_TYPES = new Set(['jpeg', 'jpg', 'tiff']);
|
|
|
|
const CHUNK_SIZE_FOR_EXIF_READING = 4100;
|
2021-08-06 04:44:48 +00:00
|
|
|
interface ParsedEXIFData {
|
|
|
|
location: Location;
|
|
|
|
creationTime: number;
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getExifData(
|
2021-08-18 05:51:17 +00:00
|
|
|
worker,
|
2021-08-06 04:44:48 +00:00
|
|
|
receivedFile: globalThis.File,
|
2021-08-18 05:51:17 +00:00
|
|
|
exactType: string
|
2021-08-06 04:44:48 +00:00
|
|
|
): Promise<ParsedEXIFData> {
|
|
|
|
try {
|
2021-08-18 05:51:17 +00:00
|
|
|
if (!fileHasEXIF(exactType)) {
|
|
|
|
// files don't have exif
|
2021-08-06 04:44:48 +00:00
|
|
|
return { location: NULL_LOCATION, creationTime: null };
|
|
|
|
}
|
2021-08-18 05:51:17 +00:00
|
|
|
const fileChunk = await worker.getUint8ArrayView(
|
|
|
|
receivedFile.slice(0, CHUNK_SIZE_FOR_EXIF_READING)
|
|
|
|
);
|
|
|
|
const exifData = EXIF.readFromBinaryFile(fileChunk.buffer);
|
2021-08-06 04:44:48 +00:00
|
|
|
if (!exifData) {
|
|
|
|
return { location: NULL_LOCATION, creationTime: null };
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
location: getEXIFLocation(exifData),
|
|
|
|
creationTime: getUNIXTime(exifData),
|
|
|
|
};
|
|
|
|
} catch (e) {
|
|
|
|
logError(e, 'error reading exif data');
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-18 05:51:17 +00:00
|
|
|
function fileHasEXIF(type) {
|
|
|
|
return EXIF_HAVING_TYPES.has(type);
|
|
|
|
}
|
|
|
|
|
2021-08-06 04:44:48 +00:00
|
|
|
function getUNIXTime(exifData: any) {
|
|
|
|
const dateString: string = exifData.DateTimeOriginal || exifData.DateTime;
|
2021-08-13 03:38:02 +00:00
|
|
|
if (!dateString || dateString === '0000:00:00 00:00:00') {
|
2021-08-06 04:44:48 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
const parts = dateString.split(' ')[0].split(':');
|
|
|
|
const date = new Date(
|
|
|
|
Number(parts[0]),
|
|
|
|
Number(parts[1]) - 1,
|
2021-08-13 03:38:02 +00:00
|
|
|
Number(parts[2])
|
2021-08-06 04:44:48 +00:00
|
|
|
);
|
|
|
|
return date.getTime() * 1000;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getEXIFLocation(exifData): Location {
|
|
|
|
if (!exifData.GPSLatitude) {
|
|
|
|
return NULL_LOCATION;
|
|
|
|
}
|
|
|
|
|
|
|
|
const latDegree = exifData.GPSLatitude[0];
|
|
|
|
const latMinute = exifData.GPSLatitude[1];
|
|
|
|
const latSecond = exifData.GPSLatitude[2];
|
|
|
|
|
|
|
|
const lonDegree = exifData.GPSLongitude[0];
|
|
|
|
const lonMinute = exifData.GPSLongitude[1];
|
|
|
|
const lonSecond = exifData.GPSLongitude[2];
|
|
|
|
|
|
|
|
const latDirection = exifData.GPSLatitudeRef;
|
|
|
|
const lonDirection = exifData.GPSLongitudeRef;
|
|
|
|
|
|
|
|
const latFinal = convertDMSToDD(
|
|
|
|
latDegree,
|
|
|
|
latMinute,
|
|
|
|
latSecond,
|
2021-08-13 03:38:02 +00:00
|
|
|
latDirection
|
2021-08-06 04:44:48 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
const lonFinal = convertDMSToDD(
|
|
|
|
lonDegree,
|
|
|
|
lonMinute,
|
|
|
|
lonSecond,
|
2021-08-13 03:38:02 +00:00
|
|
|
lonDirection
|
2021-08-06 04:44:48 +00:00
|
|
|
);
|
|
|
|
return { latitude: latFinal * 1.0, longitude: lonFinal * 1.0 };
|
|
|
|
}
|
|
|
|
|
|
|
|
function convertDMSToDD(degrees, minutes, seconds, direction) {
|
|
|
|
let dd = degrees + minutes / 60 + seconds / 3600;
|
|
|
|
|
|
|
|
if (direction === SOUTH_DIRECTION || direction === WEST_DIRECTION) {
|
|
|
|
dd = dd * -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return dd;
|
|
|
|
}
|