diff --git a/package-lock.json b/package-lock.json index 789df5e..2bd2fe4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@types/google.accounts": "^0.0.17", "@types/uuid": "^10.0.0", "fontkit": "^2.0.4", - "heic2any": "^0.0.4", + "heic-convert": "^2.1.0", "idb": "^8.0.3", "pdf-lib": "^1.17.1", "uuid": "^11.1.0" @@ -1084,12 +1084,6 @@ "tslib": "^2.8.0" } }, - "node_modules/@swc/helpers/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, "node_modules/@tailwindcss/node": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", @@ -2256,11 +2250,31 @@ "node": ">= 0.4" } }, - "node_modules/heic2any": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/heic2any/-/heic2any-0.0.4.tgz", - "integrity": "sha512-3lLnZiDELfabVH87htnRolZ2iehX9zwpRyGNz22GKXIu0fznlblf0/ftppXKNqS26dqFSeqfIBhAmAj/uSp0cA==", - "license": "MIT" + "node_modules/heic-convert": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/heic-convert/-/heic-convert-2.1.0.tgz", + "integrity": "sha512-1qDuRvEHifTVAj3pFIgkqGgJIr0M3X7cxEPjEp0oG4mo8GFjq99DpCo8Eg3kg17Cy0MTjxpFdoBHOatj7ZVKtg==", + "license": "ISC", + "dependencies": { + "heic-decode": "^2.0.0", + "jpeg-js": "^0.4.4", + "pngjs": "^6.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/heic-decode": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/heic-decode/-/heic-decode-2.1.0.tgz", + "integrity": "sha512-0fB3O3WMk38+PScbHLVp66jcNhsZ/ErtQ6u2lMYu/YxXgbBtl+oKOhGQHa4RpvE68k8IzbWkABzHnyAIjR758A==", + "license": "ISC", + "dependencies": { + "libheif-js": "^1.19.8" + }, + "engines": { + "node": ">=8.0.0" + } }, "node_modules/idb": { "version": "8.0.3", @@ -2320,6 +2334,12 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jpeg-js": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", + "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", + "license": "BSD-3-Clause" + }, "node_modules/kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", @@ -2330,6 +2350,15 @@ "node": ">=6" } }, + "node_modules/libheif-js": { + "version": "1.19.8", + "resolved": "https://registry.npmjs.org/libheif-js/-/libheif-js-1.19.8.tgz", + "integrity": "sha512-vQJWusIxO7wavpON1dusciL8Go9jsIQ+EUrckauFYAiSTjcmLAsuJh3SszLpvkwPci3JcL41ek2n+LUZGFpPIQ==", + "license": "LGPL-3.0", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/lightningcss": { "version": "1.30.1", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", @@ -2752,6 +2781,12 @@ "tslib": "^1.11.1" } }, + "node_modules/pdf-lib/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -2772,6 +2807,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "license": "MIT", + "engines": { + "node": ">=12.13.0" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -3149,9 +3193,9 @@ } }, "node_modules/svelte": { - "version": "5.36.6", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.36.6.tgz", - "integrity": "sha512-THFCC4RzI45IHXVruiG/9RpyhapGdsMTm0lpd/1NEBkNUszGsuTET0ifiaYLfP2P6Y6Y4LRdrzloRhZGIIzphA==", + "version": "5.36.7", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.36.7.tgz", + "integrity": "sha512-QsaFAxL1PZvo9hwaN+x7Sq2U8oJARmsEuM8TEZVy98nx5D5IKzRi8FKkPvmOx9NXScSYnItDGLErBBn/ieIn2A==", "dev": true, "license": "MIT", "dependencies": { @@ -3175,9 +3219,9 @@ } }, "node_modules/svelte-check": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.2.2.tgz", - "integrity": "sha512-1+31EOYZ7NKN0YDMKusav2hhEoA51GD9Ws6o//0SphMT0ve9mBTsTUEX7OmDMadUP3KjNHsSKtJrqdSaD8CrGQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.0.tgz", + "integrity": "sha512-Iz8dFXzBNAM7XlEIsUjUGQhbEE+Pvv9odb9+0+ITTgFWZBGeJRRYqHUUglwe2EkLD5LIsQaAc4IUJyvtKuOO5w==", "dev": true, "license": "MIT", "dependencies": { @@ -3283,9 +3327,9 @@ "license": "MIT" }, "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, "node_modules/typescript": { diff --git a/package.json b/package.json index 016b904..94441e3 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "@types/google.accounts": "^0.0.17", "@types/uuid": "^10.0.0", "fontkit": "^2.0.4", - "heic2any": "^0.0.4", + "heic-convert": "^2.1.0", "idb": "^8.0.3", "pdf-lib": "^1.17.1", "uuid": "^11.1.0" diff --git a/src/lib/components/wizard/StepGallery.svelte b/src/lib/components/wizard/StepGallery.svelte index 8973807..cd03d31 100644 --- a/src/lib/components/wizard/StepGallery.svelte +++ b/src/lib/components/wizard/StepGallery.svelte @@ -129,28 +129,59 @@ blob = await response.blob(); } - // Check for HEIC/HEIF format and convert if necessary + // Check for HEIC/HEIF format. If so, start conversion but don't block. if ( blob.type === 'image/heic' || blob.type === 'image/heif' || photo.url.toLowerCase().endsWith('.heic') ) { - console.log(`Converting HEIC image for ${photo.name}...`); - try { - const { default: heic2any } = await import('heic2any'); - const conversionResult = await heic2any({ - blob, - toType: 'image/jpeg', - quality: 0.9 - }); - blob = Array.isArray(conversionResult) ? conversionResult[0] : conversionResult; - console.log(`Successfully converted HEIC for ${photo.name}`); - } catch (e) { - console.error(`Failed to convert HEIC image for ${photo.name}:`, e); - throw new Error('HEIC conversion failed'); - } + console.log(`HEIC detected for ${photo.name}. Starting conversion in background.`); + photo.status = 'loading'; // Visually indicate something is happening + // Don't await this, let it run in the background + convertHeicPhoto(index, blob); + return; // End loadPhoto here for HEIC, conversion will handle the rest } + // For non-HEIC images, proceed as normal + await processLoadedBlob(index, blob); + + } catch (error) { + console.error(`Failed to load photo for ${photo.name}:`, error); + photo.status = 'error'; + } + } + + async function convertHeicPhoto(index: number, blob: Blob) { + const photo = photos[index]; + try { + console.log(`Converting HEIC with heic-convert for ${photo.name}...`); + + // Dynamically import the browser-specific version of the library + const { default: convert } = await import('heic-convert/browser'); + + const inputBuffer = await blob.arrayBuffer(); + const outputBuffer = await convert({ + buffer: new Uint8Array(inputBuffer), // heic-convert expects a Uint8Array + format: 'JPEG', + quality: 0.9 + }); + + const convertedBlob = new Blob([outputBuffer], { type: 'image/jpeg' }); + + console.log(`Successfully converted HEIC for ${photo.name}`); + + // Now that it's converted, process it like any other image + await processLoadedBlob(index, convertedBlob); + + } catch (e) { + console.error(`Failed to convert HEIC image for ${photo.name}:`, e); + photo.status = 'error'; + } + } + + async function processLoadedBlob(index: number, blob: Blob) { + const photo = photos[index]; + try { const objectUrl = createImageObjectUrl(blob); // Test if image loads properly @@ -184,11 +215,9 @@ // Automatically run face detection to generate crop await detectFaceForPhoto(index); } catch (error) { - console.error(`Failed to load photo for ${photo.name}:`, error); + console.error(`Failed to process blob for ${photo.name}:`, error); photo.status = 'error'; } - - // No need to reassign photos array with $state reactivity } async function detectFaceForPhoto(index: number) { @@ -441,7 +470,7 @@
{:else} -