improve exif parsing and error handling (#1084)
This commit is contained in:
commit
865643aa9e
|
@ -19,9 +19,7 @@ export const FILE_TYPE_LIB_MISSED_FORMATS: FileTypeInfo[] = [
|
||||||
|
|
||||||
export const KNOWN_NON_MEDIA_FORMATS = ['xmp', 'html', 'txt'];
|
export const KNOWN_NON_MEDIA_FORMATS = ['xmp', 'html', 'txt'];
|
||||||
|
|
||||||
export const EXIFLESS_FORMATS = ['image/gif'];
|
export const EXIFLESS_FORMATS = ['gif', 'bmp'];
|
||||||
|
|
||||||
export const EXIF_LIBRARY_UNSUPPORTED_FORMATS = ['image/webp'];
|
|
||||||
|
|
||||||
// this is the chunk size of the un-encrypted file which is read and encrypted before uploading it as a single part.
|
// this is the chunk size of the un-encrypted file which is read and encrypted before uploading it as a single part.
|
||||||
export const MULTIPART_PART_SIZE = 20 * 1024 * 1024;
|
export const MULTIPART_PART_SIZE = 20 * 1024 * 1024;
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
import {
|
import { EXIFLESS_FORMATS, NULL_LOCATION } from 'constants/upload';
|
||||||
EXIFLESS_FORMATS,
|
|
||||||
EXIF_LIBRARY_UNSUPPORTED_FORMATS,
|
|
||||||
NULL_LOCATION,
|
|
||||||
} from 'constants/upload';
|
|
||||||
import { Location } from 'types/upload';
|
import { Location } from 'types/upload';
|
||||||
import exifr from 'exifr';
|
import exifr from 'exifr';
|
||||||
import piexif from 'piexifjs';
|
import piexif from 'piexifjs';
|
||||||
|
@ -11,6 +7,8 @@ import { logError } from 'utils/sentry';
|
||||||
import { getUnixTimeInMicroSeconds } from 'utils/time';
|
import { getUnixTimeInMicroSeconds } from 'utils/time';
|
||||||
import { CustomError } from 'utils/error';
|
import { CustomError } from 'utils/error';
|
||||||
|
|
||||||
|
const EXIFR_UNSUPPORTED_FILE_FORMAT_MESSAGE = 'Unknown file format';
|
||||||
|
|
||||||
type ParsedEXIFData = Record<string, any> &
|
type ParsedEXIFData = Record<string, any> &
|
||||||
Partial<{
|
Partial<{
|
||||||
DateTimeOriginal: Date;
|
DateTimeOriginal: Date;
|
||||||
|
@ -37,6 +35,9 @@ export async function getParsedExifData(
|
||||||
tags?: string[]
|
tags?: string[]
|
||||||
): Promise<ParsedEXIFData> {
|
): Promise<ParsedEXIFData> {
|
||||||
try {
|
try {
|
||||||
|
if (EXIFLESS_FORMATS.includes(fileTypeInfo.exactType)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const exifData: RawEXIFData = await exifr.parse(receivedFile, {
|
const exifData: RawEXIFData = await exifr.parse(receivedFile, {
|
||||||
reviveValues: false,
|
reviveValues: false,
|
||||||
tiff: true,
|
tiff: true,
|
||||||
|
@ -56,10 +57,7 @@ export async function getParsedExifData(
|
||||||
: exifData;
|
: exifData;
|
||||||
return parseExifData(filteredExifData);
|
return parseExifData(filteredExifData);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!EXIFLESS_FORMATS.includes(fileTypeInfo.mimeType)) {
|
if (e.message === EXIFR_UNSUPPORTED_FILE_FORMAT_MESSAGE) {
|
||||||
if (
|
|
||||||
EXIF_LIBRARY_UNSUPPORTED_FORMATS.includes(fileTypeInfo.mimeType)
|
|
||||||
) {
|
|
||||||
logError(e, 'exif library unsupported format', {
|
logError(e, 'exif library unsupported format', {
|
||||||
fileType: fileTypeInfo.exactType,
|
fileType: fileTypeInfo.exactType,
|
||||||
});
|
});
|
||||||
|
@ -67,11 +65,10 @@ export async function getParsedExifData(
|
||||||
logError(e, 'get parsed exif data failed', {
|
logError(e, 'get parsed exif data failed', {
|
||||||
fileType: fileTypeInfo.exactType,
|
fileType: fileTypeInfo.exactType,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function parseExifData(exifData: RawEXIFData): ParsedEXIFData {
|
function parseExifData(exifData: RawEXIFData): ParsedEXIFData {
|
||||||
if (!exifData) {
|
if (!exifData) {
|
||||||
|
@ -112,15 +109,15 @@ function parseExifData(exifData: RawEXIFData): ParsedEXIFData {
|
||||||
|
|
||||||
function parseEXIFDate(dateTimeString: string) {
|
function parseEXIFDate(dateTimeString: string) {
|
||||||
try {
|
try {
|
||||||
if (typeof dateTimeString !== 'string') {
|
if (typeof dateTimeString !== 'string' || dateTimeString === '') {
|
||||||
throw Error(CustomError.NOT_A_DATE);
|
throw Error(CustomError.NOT_A_DATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check and parse date in the format YYYYMMDD
|
// Check and parse date in the format YYYYMMDD
|
||||||
if (dateTimeString.length === 8) {
|
if (dateTimeString.length === 8) {
|
||||||
const year = parseInt(dateTimeString.slice(0, 4), 10);
|
const year = Number(dateTimeString.slice(0, 4));
|
||||||
const month = parseInt(dateTimeString.slice(4, 6), 10);
|
const month = Number(dateTimeString.slice(4, 6));
|
||||||
const day = parseInt(dateTimeString.slice(6, 8), 10);
|
const day = Number(dateTimeString.slice(6, 8));
|
||||||
if (
|
if (
|
||||||
!Number.isNaN(year) &&
|
!Number.isNaN(year) &&
|
||||||
!Number.isNaN(month) &&
|
!Number.isNaN(month) &&
|
||||||
|
@ -134,15 +131,25 @@ function parseEXIFDate(dateTimeString: string) {
|
||||||
}
|
}
|
||||||
const [year, month, day, hour, minute, second] = dateTimeString
|
const [year, month, day, hour, minute, second] = dateTimeString
|
||||||
.match(/\d+/g)
|
.match(/\d+/g)
|
||||||
.map((component) => parseInt(component, 10));
|
.map(Number);
|
||||||
|
|
||||||
if (Number.isNaN(year) || Number.isNaN(month) || Number.isNaN(day)) {
|
if (
|
||||||
|
typeof year === 'undefined' ||
|
||||||
|
Number.isNaN(year) ||
|
||||||
|
typeof month === 'undefined' ||
|
||||||
|
Number.isNaN(month) ||
|
||||||
|
typeof day === 'undefined' ||
|
||||||
|
Number.isNaN(day)
|
||||||
|
) {
|
||||||
throw Error(CustomError.NOT_A_DATE);
|
throw Error(CustomError.NOT_A_DATE);
|
||||||
}
|
}
|
||||||
let date: Date;
|
let date: Date;
|
||||||
if (
|
if (
|
||||||
|
typeof hour === 'undefined' ||
|
||||||
Number.isNaN(hour) ||
|
Number.isNaN(hour) ||
|
||||||
|
typeof minute === 'undefined' ||
|
||||||
Number.isNaN(minute) ||
|
Number.isNaN(minute) ||
|
||||||
|
typeof second === 'undefined' ||
|
||||||
Number.isNaN(second)
|
Number.isNaN(second)
|
||||||
) {
|
) {
|
||||||
date = new Date(year, month - 1, day);
|
date = new Date(year, month - 1, day);
|
||||||
|
@ -168,8 +175,13 @@ export function parseEXIFLocation(
|
||||||
gpsLongitudeRef: string
|
gpsLongitudeRef: string
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
if (!gpsLatitude || !gpsLongitude) {
|
if (
|
||||||
return NULL_LOCATION;
|
!Array.isArray(gpsLatitudeRef) ||
|
||||||
|
!Array.isArray(gpsLongitudeRef) ||
|
||||||
|
gpsLatitude.length !== 3 ||
|
||||||
|
gpsLongitude.length !== 3
|
||||||
|
) {
|
||||||
|
throw Error(CustomError.NOT_A_LOCATION);
|
||||||
}
|
}
|
||||||
const latitude = convertDMSToDD(
|
const latitude = convertDMSToDD(
|
||||||
gpsLatitude[0],
|
gpsLatitude[0],
|
||||||
|
@ -207,13 +219,16 @@ function convertDMSToDD(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getEXIFLocation(exifData: ParsedEXIFData): Location {
|
export function getEXIFLocation(exifData: ParsedEXIFData): Location {
|
||||||
if (!exifData.latitude || !exifData.longitude) {
|
if (!exifData || (!exifData.latitude && exifData.latitude !== 0)) {
|
||||||
return NULL_LOCATION;
|
return NULL_LOCATION;
|
||||||
}
|
}
|
||||||
return { latitude: exifData.latitude, longitude: exifData.longitude };
|
return { latitude: exifData.latitude, longitude: exifData.longitude };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getEXIFTime(exifData: ParsedEXIFData): number {
|
export function getEXIFTime(exifData: ParsedEXIFData): number {
|
||||||
|
if (!exifData) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const dateTime =
|
const dateTime =
|
||||||
exifData.DateTimeOriginal ??
|
exifData.DateTimeOriginal ??
|
||||||
exifData.DateCreated ??
|
exifData.DateCreated ??
|
||||||
|
|
|
@ -40,6 +40,7 @@ export const CustomError = {
|
||||||
NO_METADATA: 'no metadata',
|
NO_METADATA: 'no metadata',
|
||||||
TOO_LARGE_LIVE_PHOTO_ASSETS: 'too large live photo assets',
|
TOO_LARGE_LIVE_PHOTO_ASSETS: 'too large live photo assets',
|
||||||
NOT_A_DATE: 'not a date',
|
NOT_A_DATE: 'not a date',
|
||||||
|
NOT_A_LOCATION: 'not a location',
|
||||||
FILE_ID_NOT_FOUND: 'file with id not found',
|
FILE_ID_NOT_FOUND: 'file with id not found',
|
||||||
WEAK_DEVICE: 'password decryption failed on the device',
|
WEAK_DEVICE: 'password decryption failed on the device',
|
||||||
INCORRECT_PASSWORD: 'incorrect password',
|
INCORRECT_PASSWORD: 'incorrect password',
|
||||||
|
|
Loading…
Reference in a new issue