@@ -14,6 +14,75 @@ export interface OptimizedPhoto {
1414 duration ?: number ;
1515}
1616
17+ /**
18+ * Checks if a file is a HEIF/HEIC image
19+ */
20+ function isHeifFile ( file : File ) : boolean {
21+ const heifMimeTypes = [ 'image/heic' , 'image/heif' ] ;
22+ const heifExtensions = [ '.heic' , '.heif' ] ;
23+
24+ // Check MIME type
25+ if ( heifMimeTypes . includes ( file . type . toLowerCase ( ) ) ) {
26+ return true ;
27+ }
28+
29+ // Check file extension as fallback
30+ const fileName = file . name . toLowerCase ( ) ;
31+ return heifExtensions . some ( ext => fileName . endsWith ( ext ) ) ;
32+ }
33+
34+ /**
35+ * Creates a placeholder image for failed HEIF conversions
36+ */
37+ function createHeifPlaceholderImage ( fileName : string ) : Blob {
38+ const canvas = document . createElement ( 'canvas' ) ;
39+ const ctx = canvas . getContext ( '2d' ) ;
40+
41+ // Set canvas size
42+ canvas . width = 400 ;
43+ canvas . height = 400 ;
44+
45+ if ( ctx ) {
46+ // Draw a gray background
47+ ctx . fillStyle = '#f3f4f6' ;
48+ ctx . fillRect ( 0 , 0 , canvas . width , canvas . height ) ;
49+
50+ // Draw border
51+ ctx . strokeStyle = '#d1d5db' ;
52+ ctx . lineWidth = 2 ;
53+ ctx . strokeRect ( 10 , 10 , canvas . width - 20 , canvas . height - 20 ) ;
54+
55+ // Draw text
56+ ctx . fillStyle = '#6b7280' ;
57+ ctx . font = 'bold 16px Arial' ;
58+ ctx . textAlign = 'center' ;
59+ ctx . fillText ( 'HEIF Format' , canvas . width / 2 , 150 ) ;
60+ ctx . fillText ( 'Not Supported' , canvas . width / 2 , 180 ) ;
61+
62+ ctx . font = '12px Arial' ;
63+ ctx . fillText ( 'Please convert to JPEG' , canvas . width / 2 , 220 ) ;
64+ ctx . fillText ( 'and re-upload' , canvas . width / 2 , 240 ) ;
65+
66+ // Draw file name (truncated if too long)
67+ const displayName = fileName . length > 30 ? fileName . substring ( 0 , 27 ) + '...' : fileName ;
68+ ctx . font = '10px Arial' ;
69+ ctx . fillText ( displayName , canvas . width / 2 , 280 ) ;
70+ }
71+
72+ // Convert to blob synchronously
73+ const dataUrl = canvas . toDataURL ( 'image/jpeg' , 0.8 ) ;
74+ const byteString = atob ( dataUrl . split ( ',' ) [ 1 ] ) ;
75+ const mimeString = dataUrl . split ( ',' ) [ 0 ] . split ( ':' ) [ 1 ] . split ( ';' ) [ 0 ] ;
76+ const ab = new ArrayBuffer ( byteString . length ) ;
77+ const ia = new Uint8Array ( ab ) ;
78+
79+ for ( let i = 0 ; i < byteString . length ; i ++ ) {
80+ ia [ i ] = byteString . charCodeAt ( i ) ;
81+ }
82+
83+ return new Blob ( [ ab ] , { type : mimeString } ) ;
84+ }
85+
1786/**
1887 * Creates a thumbnail version of an image file
1988 */
@@ -132,22 +201,44 @@ export function createVideoThumbnail(file: File, maxSize: number = 400, seekTime
132201 */
133202export async function createOptimizedPhotos ( files : File [ ] ) : Promise < OptimizedPhoto [ ] > {
134203 const promises = files . map ( async ( file ) => {
204+ const id = Math . random ( ) . toString ( 36 ) . substr ( 2 , 9 ) ;
205+
206+ // Check for HEIF files and immediately create placeholder (no conversion attempt)
207+ if ( isHeifFile ( file ) ) {
208+ console . log ( `HEIF file detected: ${ file . name } - Creating placeholder (no conversion attempted)` ) ;
209+
210+ const placeholderBlob = createHeifPlaceholderImage ( file . name ) ;
211+ const placeholderUrl = URL . createObjectURL ( placeholderBlob ) ;
212+
213+ return {
214+ id,
215+ file : new File ( [ placeholderBlob ] , file . name . replace ( / \. ( h e i c | h e i f ) $ / i, '_heif_not_supported.jpg' ) , {
216+ type : 'image/jpeg' ,
217+ lastModified : file . lastModified
218+ } ) ,
219+ url : placeholderUrl ,
220+ thumbnailUrl : placeholderUrl ,
221+ name : file . name + ' (HEIF not supported)' ,
222+ type : 'image' as const
223+ } ;
224+ }
225+
226+ // Process regular image/video files
135227 const isImage = file . type . startsWith ( 'image/' ) ;
136228 const isVideo = file . type . startsWith ( 'video/' ) ;
137229
138230 if ( ! isImage && ! isVideo ) {
139231 throw new Error ( `Invalid file type: ${ file . type } ` ) ;
140232 }
141233
142- const id = Math . random ( ) . toString ( 36 ) . substr ( 2 , 9 ) ;
143234 const url = URL . createObjectURL ( file ) ;
144235
145236 try {
146237 if ( isImage ) {
147238 const thumbnailUrl = await createThumbnail ( file , 400 ) ;
148239 return {
149240 id,
150- file,
241+ file : file ,
151242 url,
152243 thumbnailUrl,
153244 name : file . name ,
@@ -158,7 +249,7 @@ export async function createOptimizedPhotos(files: File[]): Promise<OptimizedPho
158249 const { thumbnailUrl, duration } = await createVideoThumbnail ( file , 400 ) ;
159250 return {
160251 id,
161- file,
252+ file : file ,
162253 url,
163254 thumbnailUrl,
164255 name : file . name ,
@@ -171,7 +262,7 @@ export async function createOptimizedPhotos(files: File[]): Promise<OptimizedPho
171262 console . warn ( 'Thumbnail creation failed for' , file . name , error ) ;
172263 return {
173264 id,
174- file,
265+ file : file ,
175266 url,
176267 thumbnailUrl : url ,
177268 name : file . name ,
0 commit comments