From f261e6b050e4aabd340b28087029c3fc1a826086 Mon Sep 17 00:00:00 2001 From: Ivan Buryak Date: Fri, 21 Aug 2026 11:49:53 +0500 Subject: [PATCH] Fix video_thumbnail_animation undefined frame size and zoom 0 `frame_width` and `frame_height` were guarded with `!== undefined` but interpolated unconditionally, so omitting them produced `vta:10:100:5:undefined:undefined`. Both are required by imgproxy and by the option's type, so validate them like the other numeric arguments. `zoom` used `||` in `getOpt`, so `zoom: 0` fell through to `undefined` and the option was dropped without an error. imgproxy requires zoom factors greater than 0, so reject 0 instead of ignoring it. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01G8Aa3xeVUCaxKtQJDzZhMo --- .../fix-vta-frame-size-and-zoom-zero.md | 7 +++++++ src/options/videoThumbnailAnimation.ts | 9 ++------- src/options/zoom.ts | 7 ++++--- .../videoThumbnailAnimation.test.ts | 16 +++++++++++++++ tests/optionsBasic/zoom.test.ts | 20 +++++++++++++++++-- 5 files changed, 47 insertions(+), 12 deletions(-) create mode 100644 .changeset/fix-vta-frame-size-and-zoom-zero.md diff --git a/.changeset/fix-vta-frame-size-and-zoom-zero.md b/.changeset/fix-vta-frame-size-and-zoom-zero.md new file mode 100644 index 00000000..10c1a0f3 --- /dev/null +++ b/.changeset/fix-vta-frame-size-and-zoom-zero.md @@ -0,0 +1,7 @@ +--- +"@imgproxy/imgproxy-js-core": patch +--- + +Fix `video_thumbnail_animation` generating URLs containing the literal string `undefined` when `frame_width` or `frame_height` was omitted. Both arguments are required by imgproxy and by the option's type, so they are now validated like `step`, `delay` and `frames`, and a missing value raises an error instead of producing a broken URL. + +Fix `zoom` silently ignoring a `zoom` value of `0`. The option was dropped before validation, so no error was raised. imgproxy requires zoom factors to be greater than `0`, so `0` is now rejected for `zoom`, `zoom_x` and `zoom_y`. Note that this changes the error message for non-positive values from "can't be less than 0" to "can't be less or equal than 0". diff --git a/src/options/videoThumbnailAnimation.ts b/src/options/videoThumbnailAnimation.ts index d3dff070..87a0a7bf 100644 --- a/src/options/videoThumbnailAnimation.ts +++ b/src/options/videoThumbnailAnimation.ts @@ -17,13 +17,8 @@ function build(options: VideoThumbnailAnimationOptionsPartial) { guardIsNotNum(vta.delay, "video_thumbnail_animation.delay"); guardIsNotNum(vta.frames, "video_thumbnail_animation.frames"); - if (vta.frame_width !== undefined) { - guardIsNotNum(vta.frame_width, "video_thumbnail_animation.frame_width"); - } - - if (vta.frame_height !== undefined) { - guardIsNotNum(vta.frame_height, "video_thumbnail_animation.frame_height"); - } + guardIsNotNum(vta.frame_width, "video_thumbnail_animation.frame_width"); + guardIsNotNum(vta.frame_height, "video_thumbnail_animation.frame_height"); const parts = []; diff --git a/src/options/zoom.ts b/src/options/zoom.ts index 762ca7ec..667b4b74 100644 --- a/src/options/zoom.ts +++ b/src/options/zoom.ts @@ -3,13 +3,14 @@ import { guardIsUndef, guardIsNotNum } from "../utils"; const validateValue = (value: number, optName: string): void => { guardIsUndef(value, optName); - guardIsNotNum(value, optName, { addParam: { min: 0 } }); + guardIsNotNum(value, optName, { addParam: { min: 0, minEqual: true } }); }; const getOpt = (options: ZoomOptionsPartial): Zoom | undefined => - options.zoom || options.z; + options.zoom ?? options.z; -const test = (options: ZoomOptionsPartial): boolean => Boolean(getOpt(options)); +const test = (options: ZoomOptionsPartial): boolean => + getOpt(options) !== undefined; const build = (options: ZoomOptionsPartial): string => { const zoomOpts = getOpt(options); diff --git a/tests/optionsBasic/videoThumbnailAnimation.test.ts b/tests/optionsBasic/videoThumbnailAnimation.test.ts index bfd5e1ad..7937e86b 100644 --- a/tests/optionsBasic/videoThumbnailAnimation.test.ts +++ b/tests/optionsBasic/videoThumbnailAnimation.test.ts @@ -181,4 +181,20 @@ describe("Check `video_thumbnail_animation` type declarations", () => { }, }); }); + + describe("build (required frame size)", () => { + it("should throw an error if frame_width is undefined", () => { + expect(() => + // @ts-expect-error: Let's ignore an error (check for users with vanilla js). + build({ vta: { step: 10, delay: 100, frames: 5, frame_height: 240 } }) + ).toThrow("video_thumbnail_animation.frame_width is not a number"); + }); + + it("should throw an error if frame_height is undefined", () => { + expect(() => + // @ts-expect-error: Let's ignore an error (check for users with vanilla js). + build({ vta: { step: 10, delay: 100, frames: 5, frame_width: 320 } }) + ).toThrow("video_thumbnail_animation.frame_height is not a number"); + }); + }); }); diff --git a/tests/optionsBasic/zoom.test.ts b/tests/optionsBasic/zoom.test.ts index 21e0e219..1e40e285 100644 --- a/tests/optionsBasic/zoom.test.ts +++ b/tests/optionsBasic/zoom.test.ts @@ -7,6 +7,10 @@ describe("zoom", () => { expect(test({ zoom: 1 })).toEqual(true); }); + it("should return true if zoom option is 0", () => { + expect(test({ zoom: 0 })).toEqual(true); + }); + it("should return false if zoom option is undefined", () => { expect(test({})).toEqual(false); }); @@ -30,9 +34,21 @@ describe("zoom", () => { expect(() => build({ zoom: "1" })).toThrow("zoom option is not a number"); }); + it("should throw an error if zoom is 0", () => { + expect(() => build({ zoom: 0 })).toThrow( + "zoom option value can't be less or equal than 0" + ); + }); + + it("should throw an error if zoom_x is 0", () => { + expect(() => build({ zoom: { zoom_x: 0, zoom_y: 0.5 } })).toThrow( + "zoom.zoom_x value can't be less or equal than 0" + ); + }); + it("should throw an error if zoom is less than 0", () => { expect(() => build({ z: -1 })).toThrow( - "zoom option value can't be less than 0" + "zoom option value can't be less or equal than 0" ); }); @@ -45,7 +61,7 @@ describe("zoom", () => { it("should throw an error if zoom_x is less than 0", () => { expect(() => build({ zoom: { zoom_x: -1, zoom_y: 0.5 } })).toThrow( - "zoom.zoom_x value can't be less than 0" + "zoom.zoom_x value can't be less or equal than 0" ); });