Skip to content

Commit

Permalink
v25.1.0. Fixes #178
Browse files Browse the repository at this point in the history
- deleteAllTags now takes a retain array
- add tests for deleteAllTags with and without tag retention
- add js docs for Tag interfaces
- shove the orphan Tag fields into correct sub-interfaces
- export `isExifToolTag`, `isGeolocationTag`, ...
  • Loading branch information
mceachen committed Mar 28, 2024
1 parent 7dd2239 commit 7e13bc8
Show file tree
Hide file tree
Showing 13 changed files with 460 additions and 107 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ vendored versions of ExifTool match the version that they vendor.

## Version history

### v25.1.0

- ✨ Added `retain` field to [`ExifTool.deleteAllTags`](https://photostructure.github.io/exiftool-vendored.js/classes/ExifTool.html#deleteAllTags) to address [#178](https://github.com/photostructure/exiftool-vendored.js/issues/178)

- 📦 Added jsdocs for many `Tag` interface types

- 📦 Expose `GeolocationTags` and `isGeolocationTag()`

### v25.0.0

- 🌱/✨ ExifTool upgraded to [v12.80](https://exiftool.org/history.html#v12.80), which **adds support for reverse-geo lookups** and [several other geolocation features](https://exiftool.org/geolocation.html
Expand Down
1 change: 1 addition & 0 deletions src/ErrorsAndWarnings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface ErrorsAndWarnings {
* process.
*/
errors?: string[]

/**
* This is a list of all non-critical errors raised by ExifTool during the
* read process.
Expand Down
16 changes: 13 additions & 3 deletions src/ExifTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { DeleteAllTagsArgs } from "./DeleteAllTagsArgs"
import { ErrorsAndWarnings } from "./ErrorsAndWarnings"
import { ExifToolOptions, handleDeprecatedOptions } from "./ExifToolOptions"
import { ExifToolTask } from "./ExifToolTask"
import { GeolocationTags } from "./GeolocationTags"
import { ExifToolVendoredTags } from "./ExifToolVendoredTags"
import { ICCProfileTags } from "./ICCProfileTags"
import { isWin32 } from "./IsWin32"
import { lazy } from "./Lazy"
Expand Down Expand Up @@ -48,6 +48,7 @@ import {
ExifToolTags,
FileTags,
FlashPixTags,
GeolocationTags,
IPTCTags,
JFIFTags,
MPFTags,
Expand Down Expand Up @@ -83,6 +84,7 @@ export { ExifDate } from "./ExifDate"
export { ExifDateTime } from "./ExifDateTime"
export { ExifTime } from "./ExifTime"
export { ExifToolTask } from "./ExifToolTask"
export { isGeolocationTag } from "./GeolocationTags"
export { parseJSON } from "./JSON"
export { DefaultReadTaskOptions } from "./ReadTask"
export {
Expand Down Expand Up @@ -110,6 +112,7 @@ export type {
ErrorsAndWarnings,
ExifToolOptions,
ExifToolTags,
ExifToolVendoredTags,
ExpandedDateTags,
FileTags,
FlashPixTags,
Expand Down Expand Up @@ -333,8 +336,15 @@ export class ExifTool {
* stat information and image dimensions, are intrinsic to the file and will
* continue to exist if you re-`read` the file.
*/
deleteAllTags(file: string): Promise<WriteTaskResult> {
return this.write(file, {}, DeleteAllTagsArgs)
deleteAllTags(
file: string,
opts?: { retain?: (keyof Tags | string)[] }
): Promise<WriteTaskResult> {
const args = [...DeleteAllTagsArgs]
for (const ea of opts?.retain ?? []) {
args.push(`-${ea}<${ea}`)
}
return this.write(file, {}, args)
}

/**
Expand Down
19 changes: 19 additions & 0 deletions src/ExifToolTags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* eslint-disable @typescript-eslint/no-explicit-any */

import { keysOf } from "./Object"
import { ExifToolTags } from "./Tags"

export const ExifToolTagNames = keysOf<ExifToolTags>({
ExifToolVersion: true,
SourceFile: true,
Error: true,
Warning: true,
})

/**
* Is the given tag name intrinsic to the content of a given file? In other
* words, is it not an artifact of a metadata field?
*/
export function isExifToolTag(name: string): name is keyof ExifToolTags {
return ExifToolTagNames.includes(name as any)
}
37 changes: 37 additions & 0 deletions src/ExifToolVendoredTags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ErrorsAndWarnings } from "./ErrorsAndWarnings"
import { keysOf } from "./Object"

/**
* This tags are added to {@link Tags} from this library.
*/
export interface ExifToolVendoredTags extends ErrorsAndWarnings {
/**
* Either an offset, like `UTC-7`, or an actual IANA timezone, like
* `America/Los_Angeles`.
*
* This will be missing if we can't intuit a timezone from the metadata.
*/
tz?: string

/**
* Description of where and how `tz` was extracted
*/
tzSource?: string
}

export const ExifToolVendoredTagNames = keysOf<ExifToolVendoredTags>({
tz: true,
tzSource: true,
errors: true,
warnings: true,
})

/**
* Is the given tag name intrinsic to the content of a given file? In other
* words, is it not an artifact of a metadata field?
*/
export function isExifToolVendoredTag(
name: string
): name is keyof ExifToolVendoredTags {
return ExifToolVendoredTagNames.includes(name as any)
}
38 changes: 38 additions & 0 deletions src/FileTags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { keysOf } from "./Object"
import { FileTags } from "./Tags"

const FileTagNames = keysOf<FileTags>({
BMPVersion: true,
BitsPerSample: true,
ColorComponents: true,
CurrentIPTCDigest: true,
Directory: true,
EncodingProcess: true,
ExifByteOrder: true,
FileAccessDate: true,
FileInodeChangeDate: true,
FileModifyDate: true,
FileName: true,
FilePermissions: true,
FileSize: true,
FileType: true,
FileTypeExtension: true,
ImageDataMD5: true,
ImageHeight: true,
ImageWidth: true,
MIMEType: true,
NumColors: true,
NumImportantColors: true,
PixelsPerMeterX: true,
PixelsPerMeterY: true,
Planes: true,
YCbCrSubSampling: true,
})

/**
* Is the given tag name intrinsic to the content of a given file? In other
* words, is it not an artifact of a metadata field?
*/
export function isFileTag(name: string): name is keyof FileTags {
return FileTagNames.includes(name as any)
}
24 changes: 24 additions & 0 deletions src/GeolocationTags.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
import { keysOf } from "./Object"

export const GeolocationTagNames = keysOf<GeolocationTags>({
GeolocationBearing: true,
GeolocationCity: true,
GeolocationCountry: true,
GeolocationCountryCode: true,
GeolocationDistance: true,
GeolocationFeatureCode: true,
GeolocationPopulation: true,
GeolocationPosition: true,
GeolocationRegion: true,
GeolocationSubregion: true,
GeolocationTimeZone: true,
})

/**
* Is the given tag name intrinsic to the content of a given file? In other
* words, is it not an artifact of a metadata field?
*/
export function isGeolocationTag(name: string): name is keyof GeolocationTags {
return GeolocationTagNames.includes(name as any)
}

/**
* These tags are only available if {@link ExifToolOptions.geolocation} is true and the file
* has valid GPS location data.
Expand Down
13 changes: 13 additions & 0 deletions src/Object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,16 @@ export function omit<T extends Record<string, any>, S extends string>(
}
return result
}

/**
* Provides a type-safe exhaustive array of keys for a given interface.
*
* Unfortunately, `satisfies (keyof T)[]` doesn't ensure all keys are present,
* and doesn't guard against duplicates. This function does.
*
* @param t - The interface to extract keys from. This is a Record of keys to
* `true`, which ensures the returned key array is unique.
*/
export function keysOf<T>(t: Required<Record<keyof T, true>>): (keyof T)[] {
return Object.keys(t) as any
}
Loading

0 comments on commit 7e13bc8

Please sign in to comment.