From 19aa5c58ba1d3fc049970a2acbebf863ac2a8677 Mon Sep 17 00:00:00 2001 From: Ivan Buryak Date: Fri, 24 Apr 2026 16:41:00 +0500 Subject: [PATCH] Add support for exif canonical_names option --- .changeset/exif-canonical-names.md | 5 ++++ src/optionsImageInfo/exif.ts | 19 ++++++++++++- src/typesImageInfo/exif.ts | 41 ++++++++++++++++++++++++----- tests/optionsImageInfo/exif.test.ts | 39 ++++++++++++++++++++++++++- 4 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 .changeset/exif-canonical-names.md diff --git a/.changeset/exif-canonical-names.md b/.changeset/exif-canonical-names.md new file mode 100644 index 00000000..a09fca68 --- /dev/null +++ b/.changeset/exif-canonical-names.md @@ -0,0 +1,5 @@ +--- +"@imgproxy/imgproxy-js-core": minor +--- + +Add support for the `canonical_names` parameter of the `exif` image-info option. The `exif` option now also accepts an object `{ enabled, canonical_names }`; when `canonical_names` is `1`, `"t"`, or `true`, imgproxy returns EXIF field names in a canonical form (e.g. `DateTimeOriginal`) instead of the human-readable form. The existing boolean-style input is still supported. diff --git a/src/optionsImageInfo/exif.ts b/src/optionsImageInfo/exif.ts index 1460237a..db8ed06d 100644 --- a/src/optionsImageInfo/exif.ts +++ b/src/optionsImageInfo/exif.ts @@ -12,10 +12,27 @@ const getOpt = (options: ExifImageInfoOptionsPartial): Exif | undefined => { const test = (options: ExifImageInfoOptionsPartial): boolean => getOpt(options) !== undefined; +const isExifObject = ( + value: Exclude +): value is Exclude => + typeof value === "object" && value !== null; + const build = (options: ExifImageInfoOptionsPartial): string => { const exifOpts = getOpt(options); guardIsUndef(exifOpts, "EXIF"); - return `exif:${normalizeBoolean(exifOpts)}`; + + if (!isExifObject(exifOpts)) { + return `exif:${normalizeBoolean(exifOpts)}`; + } + + const { enabled, canonical_names } = exifOpts; + const enabledPart = enabled !== undefined ? normalizeBoolean(enabled) : "t"; + + if (canonical_names === undefined) { + return `exif:${enabledPart}`; + } + + return `exif:${enabledPart}:${normalizeBoolean(canonical_names)}`; }; export { test, build }; diff --git a/src/typesImageInfo/exif.ts b/src/typesImageInfo/exif.ts index c2af4139..cfd81401 100644 --- a/src/typesImageInfo/exif.ts +++ b/src/typesImageInfo/exif.ts @@ -1,7 +1,36 @@ +/** + * Boolean-like values for EXIF option fields. + * + * @note Only `1`, `"t"`, or `true` are recognized as truthy values. + * Any other value (including `"true"` or `"1"` as strings) will be treated as false. + */ +type ExifBooleanValue = 1 | 0 | "t" | "f" | boolean | string; + +/** + * *EXIF option (object form)* + * + * @param {ExifBooleanValue} enabled - When set to `1`, `"t"` or `true`, + * imgproxy will return the image’s EXIF metadata. Default: `true`. + * @param {ExifBooleanValue} canonical_names - When set to `1`, `"t"` or `true`, + * imgproxy will return the EXIF metadata field names in a canonical form + * (e.g. `DateTimeOriginal`) instead of a human-readable form + * (e.g. `Date and Time (Original)`). Default: `false`. + */ +interface ExifObject { + enabled?: ExifBooleanValue; + canonical_names?: ExifBooleanValue; +} + /** * *EXIF option* * - * When set to `1`, `"t"` or `true`, imgproxy will return the image’s EXIF metadata. + * When `enabled` is set to `1`, `"t"` or `true`, imgproxy will return the image’s EXIF metadata. + * When `canonical_names` is set to `1`, `"t"` or `true`, imgproxy will return the EXIF + * metadata field names in a canonical form (e.g. `DateTimeOriginal`) instead of a + * human-readable form (e.g. `Date and Time (Original)`). + * + * Accepts a plain boolean-like value (controls `enabled` only) or an object + * `{ enabled, canonical_names }`. * * @note If any value other than `1`, `"t"`, or `true` is passed, it will be recognized as `false`. * @@ -16,22 +45,22 @@ * } * } * - * @default true + * @default true:false * * - * @see {@link https://docs.imgproxy.net/getting_the_image_info?id=exif | EXIF imgproxy docs} + * @see {@link https://docs.imgproxy.net/usage/getting_info#exif | EXIF imgproxy docs} */ -type Exif = 1 | "t" | true | false | string; +type Exif = ExifBooleanValue | ExifObject; /** * *EXIF option* * * To describe the EXIF option, you can use the keyword `exif`. * - * @see https://docs.imgproxy.net/getting_the_image_info?id=exif + * @see https://docs.imgproxy.net/usage/getting_info#exif */ interface ExifImageInfoOptionsPartial { exif?: Exif; } -export { Exif, ExifImageInfoOptionsPartial }; +export { Exif, ExifObject, ExifBooleanValue, ExifImageInfoOptionsPartial }; diff --git a/tests/optionsImageInfo/exif.test.ts b/tests/optionsImageInfo/exif.test.ts index bacdff38..b699e6d2 100644 --- a/tests/optionsImageInfo/exif.test.ts +++ b/tests/optionsImageInfo/exif.test.ts @@ -11,6 +11,10 @@ describe("EXIF", () => { expect(test({ exif: false })).toEqual(true); }); + it("should return true if EXIF option is an object", () => { + expect(test({ exif: { enabled: true } })).toEqual(true); + }); + it("should return false if EXIF option is undefined", () => { expect(test({})).toEqual(false); }); @@ -38,12 +42,45 @@ describe("EXIF", () => { }); it("should return 'f' if EXIF option is 0", () => { - // @ts-expect-error: Let's ignore an error. expect(build({ exif: 0 })).toEqual("exif:f"); }); it("should return 'f' if EXIF option is string (except 't')", () => { expect(build({ exif: "true" })).toEqual("exif:f"); }); + + it("should return 'exif:t' if EXIF option is an object with enabled=true", () => { + expect(build({ exif: { enabled: true } })).toEqual("exif:t"); + }); + + it("should return 'exif:f' if EXIF option is an object with enabled=false", () => { + expect(build({ exif: { enabled: false } })).toEqual("exif:f"); + }); + + it("should default enabled to 't' if only canonical_names is passed", () => { + expect(build({ exif: { canonical_names: true } })).toEqual("exif:t:t"); + }); + + it("should return 'exif:t:t' if both fields are true", () => { + expect(build({ exif: { enabled: true, canonical_names: true } })).toEqual( + "exif:t:t" + ); + }); + + it("should return 'exif:f:t' if enabled=false and canonical_names=true", () => { + expect( + build({ exif: { enabled: false, canonical_names: true } }) + ).toEqual("exif:f:t"); + }); + + it("should return 'exif:t:f' if enabled=true and canonical_names=false", () => { + expect( + build({ exif: { enabled: true, canonical_names: false } }) + ).toEqual("exif:t:f"); + }); + + it("should return 'exif:t' for an empty object", () => { + expect(build({ exif: {} })).toEqual("exif:t"); + }); }); });