From a1f25c73fbb681cab3a3333c97dae81f4c3f78d4 Mon Sep 17 00:00:00 2001 From: Jeremy Nguyen Date: Tue, 29 Sep 2026 22:40:35 -0700 Subject: [PATCH 1/4] feat(image-utils): replace heicdec with libheif - Remove heicdec port and implementation - Add libheif port that produces optimized build - Add heif_get_exif_data to get Exif data for libheif-supported images --- packages/image-utils/ports/heicdec.py | 35 ---- packages/image-utils/ports/libheif.py | 228 +++++++++++++++++++++++ packages/image-utils/scripts/build.sh | 6 +- packages/image-utils/src/codecs/heic.cpp | 36 ---- packages/image-utils/src/codecs/heic.h | 6 - packages/image-utils/src/codecs/heif.cpp | 54 ++++++ packages/image-utils/src/codecs/heif.h | 6 + packages/image-utils/src/index.ts | 2 +- packages/image-utils/src/main.cpp | 6 +- 9 files changed, 295 insertions(+), 84 deletions(-) delete mode 100644 packages/image-utils/ports/heicdec.py create mode 100644 packages/image-utils/ports/libheif.py delete mode 100644 packages/image-utils/src/codecs/heic.cpp delete mode 100644 packages/image-utils/src/codecs/heic.h create mode 100644 packages/image-utils/src/codecs/heif.cpp create mode 100644 packages/image-utils/src/codecs/heif.h diff --git a/packages/image-utils/ports/heicdec.py b/packages/image-utils/ports/heicdec.py deleted file mode 100644 index 41aa216b..00000000 --- a/packages/image-utils/ports/heicdec.py +++ /dev/null @@ -1,35 +0,0 @@ -# https://github.com/emscripten-core/emscripten/pull/27680 - -import os - -URL = 'https://chromium.googlesource.com/webm/heicdec' -DESCRIPTION = 'heicdec is a library for encoding and decoding images in the WebP format, including animation, alpha and metadata (mux/demux) support' -LICENSE = 'BSD-3-Clause license' - -TAG = '9c44152ee711785316cae54e5879748e7bc653fb' # 0.1.0-dev -HASH = '42f41806423f80b576439f11a74c96ed8d613692bc40905b87f5bfe5d2fb913804afc92876aee4a356e710e2498130e21f0a8ccebd8d25d8dc5fde8ba04af36d' - -port_name = 'heicdec' - -def get_lib_name(settings): - return 'heicdec.a' - - -def get(ports, settings, shared): - ports.fetch_project(port_name, f'https://github.com/kjk/heicdec/archive/{TAG}.tar.gz', sha512hash=HASH) - - def create(final): - root_path = ports.get_dir(port_name, f'heicdec-{TAG}') - - ports.install_headers(os.path.join(root_path, 'dist')) - - ports.build_port( - root_path, final, port_name, - exclude_dirs=['test', '.github', 'fuzz', 'dist'], - ) - - return [shared.cache.get_lib(get_lib_name(settings), create, what='port')] - - -def clear(ports, settings, shared): - shared.cache.erase_lib(get_lib_name(settings)) diff --git a/packages/image-utils/ports/libheif.py b/packages/image-utils/ports/libheif.py new file mode 100644 index 00000000..f72b3f4f --- /dev/null +++ b/packages/image-utils/ports/libheif.py @@ -0,0 +1,228 @@ +import os +import re +from pathlib import Path +from string import Template + +URL = "https://github.com/strukturag/libheif" +DESCRIPTION = "libheif is an ISO/IEC 23008-12 HEIF/HEIC and AVIF image container format decoder and encoder" +LICENSE = "GNU Lesser General Public License (LGPL) v3 or later" + +TAG = "1.23.5" +HASH = "a7b4a7ecc093f6b453939e093abef391f88bb371303183a91e431cba8f4b590131c05cc80a28733ac4822cce4a69ca1475251b2d4679890330a603de04c6c77c" + +port_name = "libheif" + + +variants = { + "libheif-mt": {"PTHREADS": 1}, +} + + +def get_lib_name(settings): + return "libheif-mt.a" if settings.PTHREADS else "libheif.a" + + +# srcs obtained from emcmake +# https://gist.github.com/jeremy-code/27ccbfbb5188422d94530da941cc04ca +srcs = [ + "libheif/bitstream.cc", + "libheif/box.cc", + "libheif/error.cc", + "libheif/context.cc", + "libheif/file.cc", + "libheif/file_layout.cc", + "libheif/image/pixelimage.cc", + "libheif/image/image_description.cc", + "libheif/plugin_registry.cc", + "libheif/nclx.cc", + "libheif/security_limits.cc", + "libheif/init.cc", + "libheif/logging.cc", + "libheif/compression.cc", + "libheif/compression_brotli.cc", + "libheif/compression_zlib.cc", + "libheif/common_utils.cc", + "libheif/region.cc", + "libheif/brands.cc", + "libheif/id_creator.cc", + "libheif/text.cc", + "libheif/api/libheif/heif.cc", + "libheif/api/libheif/heif_library.cc", + "libheif/api/libheif/heif_image.cc", + "libheif/api/libheif/heif_color.cc", + "libheif/api/libheif/heif_regions.cc", + "libheif/api/libheif/heif_plugin.cc", + "libheif/api/libheif/heif_properties.cc", + "libheif/api/libheif/heif_items.cc", + "libheif/api/libheif/heif_sequences.cc", + "libheif/api/libheif/heif_tai_timestamps.cc", + "libheif/api/libheif/heif_brands.cc", + "libheif/api/libheif/heif_metadata.cc", + "libheif/api/libheif/heif_aux_images.cc", + "libheif/api/libheif/heif_entity_groups.cc", + "libheif/api/libheif/heif_security.cc", + "libheif/api/libheif/heif_encoding.cc", + "libheif/api/libheif/heif_decoding.cc", + "libheif/api/libheif/heif_image_handle.cc", + "libheif/api/libheif/heif_context.cc", + "libheif/api/libheif/heif_tiling.cc", + "libheif/api/libheif/heif_components.cc", + "libheif/api/libheif/heif_uncompressed.cc", + "libheif/api/libheif/heif_text.cc", + "libheif/api/libheif/heif_omaf.cc", + "libheif/codecs/decoder.cc", + "libheif/codecs/encoder.cc", + "libheif/image-items/hevc.cc", + "libheif/codecs/hevc_boxes.cc", + "libheif/codecs/hevc_dec.cc", + "libheif/codecs/hevc_enc.cc", + "libheif/image-items/avif.cc", + "libheif/codecs/avif_enc.cc", + "libheif/codecs/avif_dec.cc", + "libheif/codecs/avif_boxes.cc", + "libheif/image-items/jpeg.cc", + "libheif/codecs/jpeg_boxes.cc", + "libheif/codecs/jpeg_dec.cc", + "libheif/codecs/jpeg_enc.cc", + "libheif/image-items/jpeg2000.cc", + "libheif/codecs/jpeg2000_dec.cc", + "libheif/codecs/jpeg2000_enc.cc", + "libheif/codecs/jpeg2000_boxes.cc", + "libheif/image-items/vvc.cc", + "libheif/codecs/vvc_dec.cc", + "libheif/codecs/vvc_enc.cc", + "libheif/codecs/vvc_boxes.cc", + "libheif/image-items/avc.cc", + "libheif/codecs/avc_boxes.cc", + "libheif/codecs/avc_dec.cc", + "libheif/codecs/avc_enc.cc", + "libheif/image-items/mask_image.cc", + "libheif/image-items/image_item.cc", + "libheif/image-items/grid.cc", + "libheif/image-items/overlay.cc", + "libheif/image-items/iden.cc", + "libheif/image-items/tiled.cc", + "libheif/color-conversion/colorconversion.cc", + "libheif/color-conversion/rgb2yuv.cc", + "libheif/color-conversion/rgb2yuv_sharp.cc", + "libheif/color-conversion/yuv2rgb.cc", + "libheif/color-conversion/rgb2rgb.cc", + "libheif/color-conversion/monochrome.cc", + "libheif/color-conversion/hdr_sdr.cc", + "libheif/color-conversion/alpha.cc", + "libheif/color-conversion/chroma_sampling.cc", + "libheif/color-conversion/bayer_bilinear.cc", + "libheif/sequences/seq_boxes.cc", + "libheif/sequences/chunk.cc", + "libheif/sequences/track.cc", + "libheif/sequences/track_visual.cc", + "libheif/sequences/track_metadata.cc", + "libheif/plugins/encoder_mask.cc", + "libheif/plugins/nalu_utils.cc", + "libheif/mini.cc", + "libheif/omaf_boxes.cc", +] + + +def get(ports, settings, shared): + ports.fetch_project( + port_name, + f"https://github.com/strukturag/libheif/archive/refs/tags/v{TAG}.tar.gz", + sha512hash=HASH, + ) + + def create(final): + root_path = ports.get_dir(port_name, f"libheif-{TAG}") + source_path = os.path.join(root_path, "libheif") + api_path = os.path.join(source_path, "api", "libheif") + + # cflags generated when using emcmake + # https://gist.github.com/jeremy-code/27ccbfbb5188422d94530da941cc04ca + cflags = [ + "-DHAVE_VISIBILITY", + "-DIS_BIG_ENDIAN=0", # https://github.com/strukturag/libheif/blob/bc292d97abed6b1ba1fc3d95c4191829ee99b088/CMakeLists.txt#L88 + "-DLIBHEIF_EXPORTS", + "-DNDEBUG", + "-std=c++20", # https://github.com/strukturag/libheif/blob/bc292d97abed6b1ba1fc3d95c4191829ee99b088/CMakeLists.txt#L64 + "-fPIC", + "-fvisibility=hidden", # https://github.com/strukturag/libheif/blob/bc292d97abed6b1ba1fc3d95c4191829ee99b088/CMakeLists.txt#L602-L603 + "-fvisibility-inlines-hidden", + "-Wall", # https://github.com/strukturag/libheif/blob/bc292d97abed6b1ba1fc3d95c4191829ee99b088/CMakeLists.txt#L41-L47 + "-Wsign-compare", + "-Wconversion", + "-Wno-sign-conversion", + "-Wno-error=conversion", + "-Wno-error=unused-parameter", + "-Wno-error=deprecated-declarations", + "-Wno-error=tautological-compare", # https://github.com/strukturag/libheif/blob/bc292d97abed6b1ba1fc3d95c4191829ee99b088/CMakeLists.txt#L58-L61 + "-Wno-error=tautological-constant-out-of-range-compare", + ] + cflags += [ + # Do not compile Emscripten bindings for libheif library + # https://github.com/strukturag/libheif/blob/bc292d97abed6b1ba1fc3d95c4191829ee99b088/libheif/api/libheif/heif.cc#L23-L26 + # https://github.com/strukturag/libheif/blob/bc292d97abed6b1ba1fc3d95c4191829ee99b088/libheif/api/libheif/heif_emscripten.h + # https://github.com/strukturag/libheif/blob/bc292d97abed6b1ba1fc3d95c4191829ee99b088/build-emscripten.sh + "-D__EMSCRIPTEN_STANDALONE_WASM__", + # This error happens while compiling libheif + "-Wno-shorten-64-to-32", + # When running `heif_context_read_from_memory_without_copy`, libheif + # may eventually constructs an ImageItem instance. Since that class + # has virtual functions including `decode_image` and + # `decode_compressed_image`, the bundle size balloons immediately + # once that function is introduced. Hence, to optimize bundle size, + # enable virtual-function-elimination, which also requires lto=full. + # + # `heif_context_read_from_memory_without_copy` + # -> `HeifContext::read` -> `HeifContext::interpret_heif_file` + # -> `HeifContext::interpret_heif_file_images` + # -> `HeifContext::interpret_heif_file_images` + # -> `ImageItem::alloc_for_infe_box` + # + # https://github.com/strukturag/libheif/blob/bc292d97abed6b1ba1fc3d95c4191829ee99b088/libheif/image-items/image_item.h#L375 + # https://github.com/strukturag/libheif/blob/bc292d97abed6b1ba1fc3d95c4191829ee99b088/libheif/context.cc#L630 + # https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-fvirtual-function-elimination + "-fvirtual-function-elimination", + "-Oz", + ] + if settings.PTHREADS: + cflags += ["-pthread", "-DENABLE_MULTITHREADING_SUPPORT=ON"] + else: + cflags += ["-DENABLE_MULTITHREADING_SUPPORT=OFF"] + + # Upstream generates this header from heif_version.h.in via CMake's + # configure_file() -- reproduce that substitution here + major, minor, patch = TAG.split(".") + heif_version_h = Template( + re.sub( + r"@([^@\n]+)@", + r"$\1", + (Path(api_path) / "heif_version.h.in").read_text(), + ) + ).substitute( + { + "PROJECT_VERSION_MAJOR": major, + "PROJECT_VERSION_MINOR": minor, + "PROJECT_VERSION_PATCH": patch, + # Plugin loading is disabled + "PLUGIN_DIRECTORY": "", + } + ) + ports.write_file(os.path.join(api_path, "heif_version.h"), heif_version_h) + + ports.install_headers(api_path, target="libheif") + + ports.build_port( + root_path, final, port_name, includes=[source_path], flags=cflags, srcs=srcs + ) + + return [ + shared.cache.get_lib( + get_lib_name(settings), + create, + what="port", + ) + ] + + +def clear(ports, settings, shared): + shared.cache.erase_lib(get_lib_name(settings)) diff --git a/packages/image-utils/scripts/build.sh b/packages/image-utils/scripts/build.sh index 4b0d0a1b..6381b1c1 100755 --- a/packages/image-utils/scripts/build.sh +++ b/packages/image-utils/scripts/build.sh @@ -17,11 +17,12 @@ ENVIRONMENTS=( COMPILE_FLAGS=( -Oz # https://clang.llvm.org/docs/CommandGuide/clang.html#cmdoption-O0 -g1 # Do not generate debug information, but preserve whitespace in JavaScript + -flto # Necessary for -fvirtual-function-elimination in ports/libheif.py + -fwasm-exceptions -lembind --emit-tsd "${OUTPUT_DIR}/imageUtils.d.ts" - --use-port="${PWD}/ports/heicdec.py" + --use-port="${PWD}/ports/libheif.py" --use-port=contrib.libwebp - -fwasm-exceptions -sSTACK_SIZE=$((2 ** 16)) -sALLOW_MEMORY_GROWTH=1 -sFILESYSTEM=0 @@ -36,7 +37,6 @@ COMPILE_FLAGS=( wasm_files=() for environment in "${ENVIRONMENTS[@]}"; do em++ \ - -fwasm-exceptions \ "${COMPILE_FLAGS[@]}" \ -sENVIRONMENT="$environment" \ src/main.cpp \ diff --git a/packages/image-utils/src/codecs/heic.cpp b/packages/image-utils/src/codecs/heic.cpp deleted file mode 100644 index 416e608f..00000000 --- a/packages/image-utils/src/codecs/heic.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include - -#include "../constants.hpp" -#include "heic.h" - -using namespace emscripten; - -std::optional heic_get_exif_data(const std::string heic_data) { - heic_init(); - heic_ctx *ctx = heic_ctx_new(NULL, NULL, NULL, NULL); - heic_doc *doc = - heic_doc_open(ctx, reinterpret_cast(heic_data.data()), - heic_data.length()); - heic_image_info info; - heic_doc_info(doc, &info); - - uint8_t *exif_data; - size_t exif_data_size; - if (heic_doc_exif(doc, &exif_data, &exif_data_size)) { - auto *output = static_cast( - malloc(std::size(constants::ExifHeader) + exif_data_size)); - - memcpy(output, constants::ExifHeader, std::size(constants::ExifHeader)); - memcpy(output + std::size(constants::ExifHeader), exif_data, - exif_data_size); - - heic_free(ctx, exif_data); - heic_doc_close(doc); - heic_ctx_free(ctx); - return std::optional{Uint8Array(val(typed_memory_view( - std::size(constants::ExifHeader) + exif_data_size, output)))}; - } - heic_doc_close(doc); - heic_ctx_free(ctx); - return std::nullopt; -} diff --git a/packages/image-utils/src/codecs/heic.h b/packages/image-utils/src/codecs/heic.h deleted file mode 100644 index 104a8935..00000000 --- a/packages/image-utils/src/codecs/heic.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include "../common.h" -#include - -std::optional heic_get_exif_data(const std::string heic_data); diff --git a/packages/image-utils/src/codecs/heif.cpp b/packages/image-utils/src/codecs/heif.cpp new file mode 100644 index 00000000..d3ef5d2e --- /dev/null +++ b/packages/image-utils/src/codecs/heif.cpp @@ -0,0 +1,54 @@ +#include +#include +#include + +#include "heif.h" + +using namespace emscripten; + +std::optional heif_get_exif_data(const std::string heif_data) { + struct heif_context *ctx = heif_context_alloc(); + + struct heif_error read_err = heif_context_read_from_memory_without_copy( + ctx, heif_data.data(), heif_data.size(), + /* heif_reading_options */ nullptr); + + if (read_err.code != heif_error_Ok) { + return std::nullopt; + } + + struct heif_image_handle *image_handle; + heif_context_get_primary_image_handle(ctx, &image_handle); + + heif_item_id exif_item_id; + int n = heif_image_handle_get_list_of_metadata_block_IDs( + image_handle, "Exif", &exif_item_id, /* count */ 1); + + std::optional output = std::nullopt; + + if (n == 1) { + size_t exif_size = + heif_image_handle_get_metadata_size(image_handle, exif_item_id); + uint8_t *exif_data = static_cast(malloc(exif_size)); + + if (exif_data != nullptr) { + struct heif_error metadata_err = + heif_image_handle_get_metadata(image_handle, exif_item_id, exif_data); + + if (metadata_err.code != heif_error_Ok) { + free(exif_data); + } else { + // The first four bytes are the offset to the start of the TIFF header + // of the Exif data + exif_size = exif_size - 4; + memmove(exif_data, exif_data + 4, exif_size); + realloc(exif_data, exif_size); + output = Uint8Array(val(typed_memory_view(exif_size, exif_data))); + } + } + } + + heif_image_handle_release(image_handle); + heif_context_free(ctx); + return output; +} diff --git a/packages/image-utils/src/codecs/heif.h b/packages/image-utils/src/codecs/heif.h new file mode 100644 index 00000000..34f1dd0c --- /dev/null +++ b/packages/image-utils/src/codecs/heif.h @@ -0,0 +1,6 @@ +#pragma once + +#include "../common.h" +#include + +std::optional heif_get_exif_data(const std::string heif_data); diff --git a/packages/image-utils/src/index.ts b/packages/image-utils/src/index.ts index 47014d22..1e26b819 100644 --- a/packages/image-utils/src/index.ts +++ b/packages/image-utils/src/index.ts @@ -8,11 +8,11 @@ const ImageUtils = await ImageUtilsModule(); /* oxlint-disable typescript/unbound-method -- Methods do not rely on this */ export const { - heic_get_exif_data, jpeg_set_exif_data, png_get_exif_data, png_set_exif_data, webp_get_exif_data, webp_set_exif_data, + heif_get_exif_data, } = ImageUtils; /* oxlint-enable typescript/unbound-method */ diff --git a/packages/image-utils/src/main.cpp b/packages/image-utils/src/main.cpp index 84c5bfa1..597647b7 100644 --- a/packages/image-utils/src/main.cpp +++ b/packages/image-utils/src/main.cpp @@ -1,6 +1,6 @@ #include -#include "codecs/heic.h" +#include "codecs/heif.h" #include "codecs/jpeg.h" #include "codecs/png.h" #include "codecs/webp.h" @@ -12,8 +12,6 @@ EMSCRIPTEN_BINDINGS(image_utils) { register_type("Uint8Array"); register_optional(); - function("heic_get_exif_data(heic_data)", &heic_get_exif_data); - function("jpeg_set_exif_data(jpeg_data, exif_data)", &jpeg_set_exif_data); function("png_get_exif_data(png_data)", &png_get_exif_data); @@ -21,4 +19,6 @@ EMSCRIPTEN_BINDINGS(image_utils) { function("webp_get_exif_data(webp_data)", &webp_get_exif_data); function("webp_set_exif_data(webp_data, exif_data)", &webp_set_exif_data); + + function("heif_get_exif_data(heif_data)", &heif_get_exif_data); } From a4e54e25eb7ba52dba7327154c7d5975612f8f61 Mon Sep 17 00:00:00 2001 From: Jeremy Nguyen Date: Tue, 29 Sep 2026 22:42:14 -0700 Subject: [PATCH 2/4] chore(client,core): rewrite imports to heif codec --- .../src/components/file/ExifDateTimeInformation.test.tsx | 4 ++-- packages/core/src/exif/utils/getExifData.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/client/src/components/file/ExifDateTimeInformation.test.tsx b/apps/client/src/components/file/ExifDateTimeInformation.test.tsx index 203b9604..43a12b48 100644 --- a/apps/client/src/components/file/ExifDateTimeInformation.test.tsx +++ b/apps/client/src/components/file/ExifDateTimeInformation.test.tsx @@ -12,7 +12,7 @@ import { parseDateStamp } from "@exifi/core/exif/date/dateStamp"; import { parseDateTime } from "@exifi/core/exif/date/dateTime"; import { parseTimeStamp } from "@exifi/core/exif/date/timeStamp"; import type { ExifDataObject } from "@exifi/core/exif/interfaces"; -import { heic_get_exif_data } from "@exifi/image-utils"; +import { heif_get_exif_data } from "@exifi/image-utils"; import { getFixture } from "@exifi/test-fixtures"; import { ExifDateTimeInformation } from "./ExifDateTimeInformation"; @@ -125,7 +125,7 @@ describe("ExifDateTimeInformation", () => { plainAvifWithExif, }) => { using exifData = ExifData.newFromData( - heic_get_exif_data(plainAvifWithExif.image)!, + heif_get_exif_data(plainAvifWithExif.image)!, ); const screen = await render( diff --git a/packages/core/src/exif/utils/getExifData.ts b/packages/core/src/exif/utils/getExifData.ts index 814041a3..2d76b749 100644 --- a/packages/core/src/exif/utils/getExifData.ts +++ b/packages/core/src/exif/utils/getExifData.ts @@ -4,7 +4,7 @@ import { ExifData } from "libexif-wasm"; import { png_get_exif_data, webp_get_exif_data, - heic_get_exif_data, + heif_get_exif_data, } from "@exifi/image-utils"; import { concatUint8Arrays } from "@exifi/utils/concatUint8Arrays"; @@ -46,7 +46,7 @@ const getExifData = async (file: File): Promise => { : mimeType === "image/heif" || mimeType === "image/heic" || mimeType === "image/avif" - ? heic_get_exif_data(fileBytes) + ? heif_get_exif_data(fileBytes) : undefined; if (exifData !== undefined) { From c14899c2d87bf42b8174eabe32e43f5432a8e42b Mon Sep 17 00:00:00 2001 From: Jeremy Nguyen Date: Wed, 30 Sep 2026 02:29:19 -0700 Subject: [PATCH 3/4] fix(image-utils): conditionally add Exif header if it does not exists when decoding HEIF --- packages/image-utils/src/codecs/heif.cpp | 38 ++++++++++++++++++++---- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/packages/image-utils/src/codecs/heif.cpp b/packages/image-utils/src/codecs/heif.cpp index d3ef5d2e..94994012 100644 --- a/packages/image-utils/src/codecs/heif.cpp +++ b/packages/image-utils/src/codecs/heif.cpp @@ -2,6 +2,7 @@ #include #include +#include "../constants.hpp" #include "heif.h" using namespace emscripten; @@ -14,6 +15,7 @@ std::optional heif_get_exif_data(const std::string heif_data) { /* heif_reading_options */ nullptr); if (read_err.code != heif_error_Ok) { + heif_context_free(ctx); return std::nullopt; } @@ -35,15 +37,39 @@ std::optional heif_get_exif_data(const std::string heif_data) { struct heif_error metadata_err = heif_image_handle_get_metadata(image_handle, exif_item_id, exif_data); - if (metadata_err.code != heif_error_Ok) { - free(exif_data); - } else { + if (metadata_err.code == heif_error_Ok && exif_size >= 4) { // The first four bytes are the offset to the start of the TIFF header // of the Exif data - exif_size = exif_size - 4; + exif_size -= 4; memmove(exif_data, exif_data + 4, exif_size); - realloc(exif_data, exif_size); - output = Uint8Array(val(typed_memory_view(exif_size, exif_data))); + + const bool has_exif_header = + exif_size >= std::size(constants::ExifHeader) && + std::equal(std::begin(constants::ExifHeader), + std::end(constants::ExifHeader), exif_data); + + // From the very small sample size I've seen, it seems that HEIC files + // begin with the Exif header and AVIF files do not + if (has_exif_header) { + output = Uint8Array(val(typed_memory_view(exif_size, exif_data))); + } else { + auto *exif_data_with_header = static_cast( + malloc(std::size(constants::ExifHeader) + exif_size)); + + if (exif_data_with_header != nullptr) { + memcpy(exif_data_with_header, constants::ExifHeader, + std::size(constants::ExifHeader)); + memcpy(exif_data_with_header + std::size(constants::ExifHeader), + exif_data, exif_size); + + output = Uint8Array(val( + typed_memory_view(std::size(constants::ExifHeader) + exif_size, + exif_data_with_header))); + } + free(exif_data); + } + } else { + free(exif_data); } } } From de15e1af083d3dbf070b2e47d5b467c7b864759c Mon Sep 17 00:00:00 2001 From: Jeremy Nguyen Date: Wed, 30 Sep 2026 02:42:35 -0700 Subject: [PATCH 4/4] chore(api,client): update netlify.toml to sequentially run builds --- apps/api/netlify.toml | 2 +- apps/client/netlify.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api/netlify.toml b/apps/api/netlify.toml index 5f516c55..103504e4 100644 --- a/apps/api/netlify.toml +++ b/apps/api/netlify.toml @@ -1,7 +1,7 @@ [build] base = "apps/api" publish = "dist" -command = "pnpm --workspace-root exec turbo run build --filter=@exifi/api..." +command = "pnpm --workspace-root exec turbo run build --filter=@exifi/api... --concurrency=1" ignore = "corepack pnpm --workspace-root exec turbo query affected --packages @exifi/api --base=$CACHED_COMMIT_REF --head=$COMMIT_REF --exit-code" [[plugins]] diff --git a/apps/client/netlify.toml b/apps/client/netlify.toml index d9f3eb7f..96e736f1 100644 --- a/apps/client/netlify.toml +++ b/apps/client/netlify.toml @@ -1,7 +1,7 @@ [build] base = "apps/client" publish = "dist/client" -command = "pnpm --workspace-root exec turbo run build --filter=@exifi/client..." +command = "pnpm --workspace-root exec turbo run build --filter=@exifi/client... --concurrency=1" # When using pnpm in the ignore command, it simply responds with "pnpm: command not found" ignore = "corepack pnpm --workspace-root exec turbo query affected --packages @exifi/client --base=$CACHED_COMMIT_REF --head=$COMMIT_REF --exit-code"