diff --git a/src/DefaultExifToolOptions.ts b/src/DefaultExifToolOptions.ts index d2dc2bbe..2a8b0bf0 100644 --- a/src/DefaultExifToolOptions.ts +++ b/src/DefaultExifToolOptions.ts @@ -36,7 +36,16 @@ export const DefaultExifToolOptions: Omit< versionCommand: new VersionTask().command, healthCheckIntervalMillis: 30000, healthCheckCommand: "-ver\n-execute\n", - useMWG: false, + + backfillTimezones: true, + defaultVideosToUTC: true, + geoTz: geoTz, + ignoreZeroZeroLatLon: true, + imageHashType: false, + includeImageDataMD5: undefined, + inferTimezoneFromDatestamps: false, // to retain prior behavior + inferTimezoneFromDatestampTags: [...CapturedAtTagNames], + isIgnorableError: isIgnorableWarning, numericTags: [ "*Duration*", "GPSAltitude", @@ -46,12 +55,5 @@ export const DefaultExifToolOptions: Omit< "Orientation", "Rotation", ], - includeImageDataMD5: undefined, - imageHashType: false, - defaultVideosToUTC: true, - backfillTimezones: true, - inferTimezoneFromDatestamps: false, // to retain prior behavior - inferTimezoneFromDatestampTags: [...CapturedAtTagNames], - geoTz: geoTz, - isIgnorableError: isIgnorableWarning, + useMWG: false, }) diff --git a/src/ExifTool.ts b/src/ExifTool.ts index a6e7c15d..3abf892c 100644 --- a/src/ExifTool.ts +++ b/src/ExifTool.ts @@ -22,7 +22,7 @@ import { pick } from "./Pick" import { PreviewTag } from "./PreviewTag" import { Json, Literal, RawTags } from "./RawTags" import { ReadRawTask } from "./ReadRawTask" -import { ReadTask, ReadTaskOptions } from "./ReadTask" +import { ReadTask, ReadTaskOptionFields, ReadTaskOptions } from "./ReadTask" import { ResourceEvent } from "./ResourceEvent" import { RewriteAllTagsTask } from "./RewriteAllTagsTask" import { blank, notBlank } from "./String" @@ -258,14 +258,7 @@ export class ExifTool { return this.enqueueTask(() => ReadTask.for(file, { optionalArgs, - ...pick( - this.options, - "numericTags", - "useMWG", - "imageHashType", - "defaultVideosToUTC", - "geoTz" - ), + ...pick(this.options, ...ReadTaskOptionFields), ...options, }) ) as any // < no way to know at compile time if we're going to get back a T! diff --git a/src/ExifToolOptions.ts b/src/ExifToolOptions.ts index 3a335745..e6115cea 100644 --- a/src/ExifToolOptions.ts +++ b/src/ExifToolOptions.ts @@ -175,6 +175,14 @@ export interface ExifToolOptions */ inferTimezoneFromDatestampTags: (keyof Tags)[] + /** + * Some software uses a GPS position of (0,0) as a synonym for "unset". If + * this option is true, and GPSLatitude and GPSLongitude are both 0, then + * those values will be returned, but the TZ will not be inferred from that + * location. + */ + ignoreZeroZeroLatLon: boolean + /** * `ExifTool` has a shebang line that assumes a valid `perl` is installed at * `/usr/bin/perl`. diff --git a/src/ReadTask.ts b/src/ReadTask.ts index 9a3ef143..7f11d465 100644 --- a/src/ReadTask.ts +++ b/src/ReadTask.ts @@ -34,6 +34,19 @@ const PassthroughTags = [ "DateDisplayFormat", ] +export const ReadTaskOptionFields = [ + "backfillTimezones", + "defaultVideosToUTC", + "geoTz", + "ignoreZeroZeroLatLon", + "imageHashType", + "includeImageDataMD5", + "inferTimezoneFromDatestamps", + "inferTimezoneFromDatestampTags", + "numericTags", + "useMWG", +] as const + const NullIsh = ["undef", "null", "undefined"] export function nullish(s: string | undefined): s is undefined { @@ -42,18 +55,7 @@ export function nullish(s: string | undefined): s is undefined { export const DefaultReadTaskOptions = { optionalArgs: [] as string[], - ...pick( - DefaultExifToolOptions, - "numericTags", - "useMWG", - "includeImageDataMD5", - "imageHashType", - "defaultVideosToUTC", - "backfillTimezones", - "inferTimezoneFromDatestamps", - "inferTimezoneFromDatestampTags", - "geoTz" - ), + ...pick(DefaultExifToolOptions, ...ReadTaskOptionFields), } as const export type ReadTaskOptions = typeof DefaultReadTaskOptions @@ -186,6 +188,9 @@ export class ReadTask extends ExifToolTask { #extractLatLon = lazy(() => { this.lat ??= this.#latlon("GPSLatitude", "S", 90) this.lon ??= this.#latlon("GPSLongitude", "W", 180) + if (this.options.ignoreZeroZeroLatLon && this.lat === 0 && this.lon === 0) { + this.invalidLatLon = true + } if (this.invalidLatLon) { this.lat = this.lon = undefined } diff --git a/src/WriteTask.spec.ts b/src/WriteTask.spec.ts index 6aafb169..9b1f54b4 100644 --- a/src/WriteTask.spec.ts +++ b/src/WriteTask.spec.ts @@ -281,31 +281,57 @@ describe("WriteTask", function () { return Math.random() * (max - min) + min } - describe("round-trips GPS values (attempt to reproduce #131)", () => { - // Verify there's no shenanigans with negative, zero, or positive - // lat/lon combinations: - for (const GPSLatitude of [ - randomFloat(-89, 1), - 0, - 39.1132577, - randomFloat(1, 89), - ]) { - for (const GPSLongitude of [ - randomFloat(-179, 1), - -84.6907715, - 0, - randomFloat(1, 179), - ]) { - it(JSON.stringify({ GPSLatitude, GPSLongitude }), async () => { - const f = await dest() - await exiftool.write(f, { GPSLatitude, GPSLongitude }) - const tags = await exiftool.read(f) - expect(tags.GPSLatitude).to.be.closeTo(GPSLatitude, 0.001) - expect(tags.GPSLongitude).to.be.closeTo(GPSLongitude, 0.001) - }) + for (const ignoreZeroZeroLatLon of [false, true]) { + describe( + "round-trips GPS values (attempt to reproduce #131): " + + JSON.stringify({ ignoreZeroZeroLatLon }), + () => { + // Verify there's no shenanigans with negative, zero, or positive + // lat/lon combinations: + for (const GPSLatitude of [ + randomFloat(-89, -1), + 0, + 39.1132577, + randomFloat(1, 89), + ]) { + for (const GPSLongitude of [ + randomFloat(-179, -1), + -84.6907715, + 0, + randomFloat(1, 179), + ]) { + it( + JSON.stringify({ GPSLatitude, GPSLongitude }), + async () => { + exiftool.options.ignoreZeroZeroLatLon = + ignoreZeroZeroLatLon + const f = await dest() + await exiftool.write(f, { GPSLatitude, GPSLongitude }) + const tags = await exiftool.read(f) + if ( + ignoreZeroZeroLatLon && + GPSLatitude === 0 && + GPSLongitude === 0 + ) { + expect(tags.GPSLatitude).to.eql(undefined) + expect(tags.GPSLongitude).to.eql(undefined) + } else { + expect(tags.GPSLatitude).to.be.closeTo( + GPSLatitude, + 0.001 + ) + expect(tags.GPSLongitude).to.be.closeTo( + GPSLongitude, + 0.001 + ) + } + } + ) + } + } } - } - }) + ) + } it("round-trips a struct tag", async () => { const struct: Struct[] = [