PluginProbe
Gutenberg / 24.1.0
Gutenberg v24.1.0
24.1.0 24.0.0 23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 All 404 releases
← All changes | build/scripts/upload-media/index.js +2347 -438 23.0.1 → 24.1.0 View file →
@@ -1,4 +1,5 @@
1 +(function() {
1 2 "use strict";
2 3 var wp;
3 4 (wp ||= {}).uploadMedia = (() => {
4 5 var __create = Object.create;
@@ -44,8 +45,15 @@
44 45 module.exports = window.wp.data;
45 46 }
46 47 });
47 48
49 + // package-external:@wordpress/i18n
50 + var require_i18n = __commonJS({
51 + "package-external:@wordpress/i18n"(exports, module) {
52 + module.exports = window.wp.i18n;
53 + }
54 + });
55 +
48 56 // package-external:@wordpress/url
49 57 var require_url = __commonJS({
50 58 "package-external:@wordpress/url"(exports, module) {
51 59 module.exports = window.wp.url;
@@ -51,15 +59,8 @@
51 59 module.exports = window.wp.url;
52 60 }
53 61 });
54 62
55 - // package-external:@wordpress/i18n
56 - var require_i18n = __commonJS({
57 - "package-external:@wordpress/i18n"(exports, module) {
58 - module.exports = window.wp.i18n;
59 - }
60 - });
61 -
62 63 // package-external:@wordpress/blob
63 64 var require_blob = __commonJS({
64 65 "package-external:@wordpress/blob"(exports, module) {
65 66 module.exports = window.wp.blob;
@@ -96,13 +97,19 @@
96 97
97 98 // packages/upload-media/build-module/index.mjs
98 99 var index_exports = {};
99 100 __export(index_exports, {
101 + ErrorCode: () => ErrorCode,
100 102 MediaUploadProvider: () => provider_default,
101 103 UploadError: () => UploadError,
102 104 clearFeatureDetectionCache: () => clearFeatureDetectionCache,
103 105 detectClientSideMediaSupport: () => detectClientSideMediaSupport,
106 + getErrorMessage: () => getErrorMessage,
107 + getHeicConversionAdvice: () => getHeicConversionAdvice,
108 + getHeicUnsupportedMessage: () => getHeicUnsupportedMessage,
104 109 isClientSideMediaSupported: () => isClientSideMediaSupported,
110 + isHeicCanvasSupported: () => isHeicCanvasSupported,
111 + isHeicFile: () => isHeicFile,
105 112 store: () => store
106 113 });
107 114
108 115 // packages/upload-media/build-module/store/index.mjs
@@ -115,8 +122,9 @@
115 122 Type2["Prepare"] = "PREPARE_ITEM";
116 123 Type2["Cancel"] = "CANCEL_ITEM";
117 124 Type2["Remove"] = "REMOVE_ITEM";
118 125 Type2["RetryItem"] = "RETRY_ITEM";
126 + Type2["ScheduleRetry"] = "SCHEDULE_RETRY";
119 127 Type2["PauseItem"] = "PAUSE_ITEM";
120 128 Type2["ResumeItem"] = "RESUME_ITEM";
121 129 Type2["PauseQueue"] = "PAUSE_QUEUE";
122 130 Type2["ResumeQueue"] = "RESUME_QUEUE";
@@ -125,8 +133,9 @@
125 133 Type2["AddOperations"] = "ADD_OPERATIONS";
126 134 Type2["CacheBlobUrl"] = "CACHE_BLOB_URL";
127 135 Type2["RevokeBlobUrls"] = "REVOKE_BLOB_URLS";
128 136 Type2["UpdateProgress"] = "UPDATE_PROGRESS";
137 + Type2["AccumulateSubSize"] = "ACCUMULATE_SUB_SIZE";
129 138 Type2["UpdateSettings"] = "UPDATE_SETTINGS";
130 139 return Type2;
131 140 })(Type || {});
132 141 var ItemStatus = /* @__PURE__ */ ((ItemStatus2) => {
@@ -132,8 +141,9 @@
132 141 var ItemStatus = /* @__PURE__ */ ((ItemStatus2) => {
133 142 ItemStatus2["Queued"] = "QUEUED";
134 143 ItemStatus2["Processing"] = "PROCESSING";
135 144 ItemStatus2["Paused"] = "PAUSED";
145 + ItemStatus2["PendingRetry"] = "PENDING_RETRY";
136 146 ItemStatus2["Uploaded"] = "UPLOADED";
137 147 ItemStatus2["Error"] = "ERROR";
138 148 return ItemStatus2;
139 149 })(ItemStatus || {});
@@ -142,10 +152,12 @@
142 152 OperationType2["Upload"] = "UPLOAD";
143 153 OperationType2["ResizeCrop"] = "RESIZE_CROP";
144 154 OperationType2["Rotate"] = "ROTATE";
145 155 OperationType2["TranscodeImage"] = "TRANSCODE_IMAGE";
156 + OperationType2["TranscodeGif"] = "TRANSCODE_GIF";
146 157 OperationType2["ThumbnailGeneration"] = "THUMBNAIL_GENERATION";
147 158 OperationType2["Finalize"] = "FINALIZE";
159 + OperationType2["DetectUltraHdr"] = "DETECT_ULTRAHDR";
148 160 return OperationType2;
149 161 })(OperationType || {});
150 162
151 163 // packages/upload-media/build-module/store/constants.mjs
@@ -151,8 +163,15 @@
151 163 // packages/upload-media/build-module/store/constants.mjs
152 164 var STORE_NAME = "core/upload-media";
153 165 var DEFAULT_MAX_CONCURRENT_UPLOADS = 5;
154 166 var DEFAULT_MAX_CONCURRENT_IMAGE_PROCESSING = 2;
167 + var DEFAULT_RETRY_SETTINGS = {
168 + maxRetryAttempts: 3,
169 + initialRetryDelayMs: 1e3,
170 + maxRetryDelayMs: 3e4,
171 + backoffMultiplier: 2,
172 + retryJitter: 0.1
173 + };
155 174 var CLIENT_SIDE_SUPPORTED_MIME_TYPES = [
156 175 "image/jpeg",
157 176 "image/png",
158 177 "image/gif",
@@ -158,8 +177,12 @@
158 177 "image/gif",
159 178 "image/webp",
160 179 "image/avif"
161 180 ];
181 + var HEIC_MIME_TYPES = [
182 + "image/heic",
183 + "image/heif"
184 + ];
162 185
163 186 // packages/upload-media/build-module/store/reducer.mjs
164 187 var noop = () => {
165 188 };
@@ -169,10 +192,12 @@
169 192 blobUrls: {},
170 193 settings: {
171 194 mediaUpload: noop,
172 195 maxConcurrentUploads: DEFAULT_MAX_CONCURRENT_UPLOADS,
173 - maxConcurrentImageProcessing: DEFAULT_MAX_CONCURRENT_IMAGE_PROCESSING
174 - }
196 + maxConcurrentImageProcessing: DEFAULT_MAX_CONCURRENT_IMAGE_PROCESSING,
197 + retry: { ...DEFAULT_RETRY_SETTINGS }
198 + },
199 + failureCount: 0
175 200 };
176 201 function reducer(state = DEFAULT_STATE, action = { type: Type.Unknown }) {
177 202 switch (action.type) {
178 203 case Type.PauseQueue: {
@@ -211,11 +236,15 @@
211 236 return {
212 237 ...state,
213 238 queue: [...state.queue, action.item]
214 239 };
215 - case Type.Cancel:
240 + case Type.Cancel: {
241 + const cancelled = state.queue.find(
242 + (item) => item.id === action.id
243 + );
216 244 return {
217 245 ...state,
246 + failureCount: cancelled && !cancelled.parentId ? state.failureCount + 1 : state.failureCount,
218 247 queue: state.queue.map(
219 248 (item) => item.id === action.id ? {
220 249 ...item,
221 250 error: action.error
@@ -221,8 +250,9 @@
221 250 error: action.error
222 251 } : item
223 252 )
224 253 };
254 + }
225 255 case Type.RetryItem:
226 256 return {
227 257 ...state,
228 258 queue: state.queue.map(
@@ -229,12 +259,37 @@
229 259 (item) => item.id === action.id ? {
230 260 ...item,
231 261 status: ItemStatus.Processing,
232 262 error: void 0,
233 - retryCount: (item.retryCount ?? 0) + 1
263 + retryCount: (item.retryCount ?? 0) + 1,
264 + abortController: new AbortController(),
265 + /*
266 + * The failed operation is still recorded on
267 + * the item: nothing finishes it when it
268 + * fails, and while the item waits out the
269 + * backoff that is what keeps it out of the
270 + * concurrency pools. Clear it now that the
271 + * item is about to run again, so processItem
272 + * does not mistake it for an operation still
273 + * in flight and skip the retry.
274 + */
275 + currentOperation: void 0
234 276 } : item
235 277 )
236 278 };
279 + case Type.ScheduleRetry:
280 + return {
281 + ...state,
282 + queue: state.queue.map(
283 + (item) => item.id === action.id ? {
284 + ...item,
285 + status: ItemStatus.PendingRetry,
286 + error: action.error,
287 + retryCount: action.retryCount,
288 + nextRetryTimestamp: action.nextRetryTimestamp
289 + } : item
290 + )
291 + };
237 292 case Type.Remove:
238 293 return {
239 294 ...state,
240 295 queue: state.queue.filter((item) => item.id !== action.id)
@@ -318,8 +373,21 @@
318 373 progress: action.progress
319 374 } : item
320 375 )
321 376 };
377 + case Type.AccumulateSubSize:
378 + return {
379 + ...state,
380 + queue: state.queue.map(
381 + (item) => item.id === action.id ? {
382 + ...item,
383 + subSizes: [
384 + ...item.subSizes || [],
385 + action.subSize
386 + ]
387 + } : item
388 + )
389 + };
322 390 case Type.UpdateSettings: {
323 391 return {
324 392 ...state,
325 393 settings: {
@@ -366,24 +434,28 @@
366 434 var private_selectors_exports = {};
367 435 __export(private_selectors_exports, {
368 436 getActiveImageProcessingCount: () => getActiveImageProcessingCount,
369 437 getActiveUploadCount: () => getActiveUploadCount,
438 + getActiveVideoProcessingCount: () => getActiveVideoProcessingCount,
370 439 getAllItems: () => getAllItems,
371 440 getBlobUrls: () => getBlobUrls,
372 441 getFailedItems: () => getFailedItems,
442 + getFailureCount: () => getFailureCount,
373 443 getItem: () => getItem,
374 444 getItemProgress: () => getItemProgress,
375 - getPausedUploadForPost: () => getPausedUploadForPost,
376 445 getPendingImageProcessing: () => getPendingImageProcessing,
377 446 getPendingUploads: () => getPendingUploads,
447 + getPendingVideoProcessing: () => getPendingVideoProcessing,
378 448 hasPendingItemsByParentId: () => hasPendingItemsByParentId,
379 449 isBatchUploaded: () => isBatchUploaded,
380 - isPaused: () => isPaused,
381 - isUploadingToPost: () => isUploadingToPost
450 + isPaused: () => isPaused
382 451 });
383 452 function getAllItems(state) {
384 453 return state.queue;
385 454 }
455 + function getFailureCount(state) {
456 + return state.failureCount;
457 + }
386 458 function getItem(state, id) {
387 459 return state.queue.find((item) => item.id === id);
388 460 }
389 461 function isBatchUploaded(state, batchId) {
@@ -391,18 +463,8 @@
391 463 (item) => batchId === item.batchId
392 464 );
393 465 return batchItems.length === 0;
394 466 }
395 - function isUploadingToPost(state, postOrAttachmentId) {
396 - return state.queue.some(
397 - (item) => item.currentOperation === OperationType.Upload && item.additionalData.post === postOrAttachmentId
398 - );
399 - }
400 - function getPausedUploadForPost(state, postOrAttachmentId) {
401 - return state.queue.find(
402 - (item) => item.status === ItemStatus.Paused && item.additionalData.post === postOrAttachmentId
403 - );
404 - }
405 467 function isPaused(state) {
406 468 return state.queueStatus === "paused";
407 469 }
408 470 function getBlobUrls(state, id) {
@@ -423,8 +485,13 @@
423 485 return state.queue.filter(
424 486 (item) => item.currentOperation === OperationType.ResizeCrop || item.currentOperation === OperationType.Rotate
425 487 ).length;
426 488 }
489 + function getActiveVideoProcessingCount(state) {
490 + return state.queue.filter(
491 + (item) => item.currentOperation === OperationType.TranscodeGif
492 + ).length;
493 + }
427 494 function getPendingImageProcessing(state) {
428 495 return state.queue.filter((item) => {
429 496 const nextOperation = Array.isArray(item.operations?.[0]) ? item.operations[0][0] : item.operations?.[0];
430 497 return (nextOperation === OperationType.ResizeCrop || nextOperation === OperationType.Rotate) && item.currentOperation !== OperationType.ResizeCrop && item.currentOperation !== OperationType.Rotate;
@@ -429,8 +496,14 @@
429 496 const nextOperation = Array.isArray(item.operations?.[0]) ? item.operations[0][0] : item.operations?.[0];
430 497 return (nextOperation === OperationType.ResizeCrop || nextOperation === OperationType.Rotate) && item.currentOperation !== OperationType.ResizeCrop && item.currentOperation !== OperationType.Rotate;
431 498 });
432 499 }
500 + function getPendingVideoProcessing(state) {
501 + return state.queue.filter((item) => {
502 + const nextOperation = Array.isArray(item.operations?.[0]) ? item.operations[0][0] : item.operations?.[0];
503 + return nextOperation === OperationType.TranscodeGif && item.currentOperation !== OperationType.TranscodeGif;
504 + });
505 + }
433 506 function getFailedItems(state) {
434 507 return state.queue.filter((item) => item.error !== void 0);
435 508 }
436 509 function hasPendingItemsByParentId(state, parentId) {
@@ -445,50 +518,48 @@
445 518 var actions_exports = {};
446 519 __export(actions_exports, {
447 520 addItems: () => addItems,
448 521 cancelItem: () => cancelItem,
449 - retryItem: () => retryItem
522 + executeRetry: () => executeRetry,
523 + retryItem: () => retryItem,
524 + scheduleRetry: () => scheduleRetry
450 525 });
451 526
452 - // node_modules/uuid/dist/esm-browser/rng.js
453 - var getRandomValues;
454 - var rnds8 = new Uint8Array(16);
455 - function rng() {
456 - if (!getRandomValues) {
457 - getRandomValues = typeof crypto !== "undefined" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto);
458 - if (!getRandomValues) {
459 - throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
460 - }
461 - }
462 - return getRandomValues(rnds8);
463 - }
464 -
465 - // node_modules/uuid/dist/esm-browser/stringify.js
527 + // node_modules/uuid/dist/stringify.js
466 528 var byteToHex = [];
467 529 for (let i = 0; i < 256; ++i) {
468 530 byteToHex.push((i + 256).toString(16).slice(1));
469 531 }
470 532 function unsafeStringify(arr, offset = 0) {
471 - return byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]];
533 + return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
472 534 }
473 535
474 - // node_modules/uuid/dist/esm-browser/native.js
475 - var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
476 - var native_default = {
477 - randomUUID
478 - };
536 + // node_modules/uuid/dist/rng.js
537 + var rnds8 = new Uint8Array(16);
538 + function rng() {
539 + return crypto.getRandomValues(rnds8);
540 + }
479 541
480 - // node_modules/uuid/dist/esm-browser/v4.js
542 + // node_modules/uuid/dist/v4.js
481 543 function v4(options, buf, offset) {
482 - if (native_default.randomUUID && !buf && !options) {
483 - return native_default.randomUUID();
544 + if (!buf && !options && crypto.randomUUID) {
545 + return crypto.randomUUID();
484 546 }
547 + return _v4(options, buf, offset);
548 + }
549 + function _v4(options, buf, offset) {
485 550 options = options || {};
486 - const rnds = options.random || (options.rng || rng)();
551 + const rnds = options.random ?? options.rng?.() ?? rng();
552 + if (rnds.length < 16) {
553 + throw new Error("Random bytes length must be >= 16");
554 + }
487 555 rnds[6] = rnds[6] & 15 | 64;
488 556 rnds[8] = rnds[8] & 63 | 128;
489 557 if (buf) {
490 558 offset = offset || 0;
559 + if (offset < 0 || offset + 16 > buf.length) {
560 + throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
561 + }
491 562 for (let i = 0; i < 16; ++i) {
492 563 buf[offset + i] = rnds[i];
493 564 }
494 565 return buf;
@@ -496,8 +567,50 @@
496 567 return unsafeStringify(rnds);
497 568 }
498 569 var v4_default = v4;
499 570
571 + // packages/upload-media/build-module/store/actions.mjs
572 + var import_i18n5 = __toESM(require_i18n(), 1);
573 +
574 + // packages/upload-media/build-module/store/utils/retry.mjs
575 + function calculateRetryDelay(options) {
576 + const { attempt, initialDelay, maxDelay, multiplier, jitter } = options;
577 + const exponentialDelay = initialDelay * Math.pow(multiplier, attempt - 1);
578 + const cappedDelay = Math.min(exponentialDelay, maxDelay);
579 + const jitterFactor = 1 + (Math.random() * 2 - 1) * jitter;
580 + return Math.floor(cappedDelay * jitterFactor);
581 + }
582 + var RETRYABLE_MESSAGE_PATTERNS = [
583 + /network/i,
584 + /timeout/i,
585 + /ECONNRESET/i,
586 + /fetch failed/i,
587 + /connection/i,
588 + /socket/i,
589 + /ETIMEDOUT/i,
590 + /ENOTFOUND/i,
591 + /Could not get a valid response/i,
592 + /Failed to fetch/i,
593 + /Load failed/i
594 + ];
595 + function shouldRetryError(error, retryCount, maxRetries) {
596 + if (retryCount >= maxRetries) {
597 + return false;
598 + }
599 + const message = typeof error === "string" ? error : error?.message || "";
600 + return RETRYABLE_MESSAGE_PATTERNS.some(
601 + (pattern) => pattern.test(message)
602 + );
603 + }
604 + var retryTimers = /* @__PURE__ */ new Map();
605 + function clearRetryTimer(id) {
606 + const pendingTimer = retryTimers.get(id);
607 + if (pendingTimer !== void 0) {
608 + clearTimeout(pendingTimer);
609 + retryTimers.delete(id);
610 + }
611 + }
612 +
500 613 // packages/upload-media/build-module/image-file.mjs
501 614 var ImageFile = class extends File {
502 615 width = 0;
503 616 height = 0;
@@ -548,8 +661,72 @@
548 661 }
549 662 function getFileBasename(name) {
550 663 return name.includes(".") ? name.split(".").slice(0, -1).join(".") : name;
551 664 }
665 + function isAnimatedGif(buffer) {
666 + const view = new Uint8Array(buffer);
667 + if (view.length < 4 || view[0] !== 71 || view[1] !== 73 || view[2] !== 70 || view[3] !== 56) {
668 + return false;
669 + }
670 + let frameCount = 0;
671 + for (let i = 0; i < view.length - 2; i++) {
672 + if (view[i] === 0 && view[i + 1] === 33 && view[i + 2] === 249) {
673 + frameCount++;
674 + if (frameCount > 1) {
675 + return true;
676 + }
677 + }
678 + }
679 + return false;
680 + }
681 + var FILE_TYPE_BOX_BYTES = 64;
682 + var HEIC_BRANDS = [
683 + "heic",
684 + "heix",
685 + "heim",
686 + "heis",
687 + "hevc",
688 + "hevx",
689 + "hevm",
690 + "hevs",
691 + "mif1",
692 + "msf1"
693 + ];
694 + var AVIF_BRANDS = ["avif", "avis"];
695 + function isHeicBuffer(buffer) {
696 + const view = new Uint8Array(buffer);
697 + const readBrand = (offset) => String.fromCharCode(...view.subarray(offset, offset + 4));
698 + if (view.length < 12 || readBrand(4) !== "ftyp") {
699 + return false;
700 + }
701 + const boxEnd = Math.min(
702 + view.length,
703 + new DataView(buffer).getUint32(0)
704 + );
705 + const brands = [readBrand(8)];
706 + for (let offset = 16; offset + 4 <= boxEnd; offset += 4) {
707 + brands.push(readBrand(offset));
708 + }
709 + if (brands.some((brand) => AVIF_BRANDS.includes(brand))) {
710 + return false;
711 + }
712 + return brands.some((brand) => HEIC_BRANDS.includes(brand));
713 + }
714 + async function isHeicFile(file) {
715 + if (HEIC_MIME_TYPES.includes(file.type)) {
716 + return true;
717 + }
718 + if (file.type && !file.type.startsWith("image/")) {
719 + return false;
720 + }
721 + try {
722 + return isHeicBuffer(
723 + await file.slice(0, FILE_TYPE_BOX_BYTES).arrayBuffer()
724 + );
725 + } catch {
726 + return false;
727 + }
728 + }
552 729
553 730 // packages/upload-media/build-module/store/utils/index.mjs
554 731 var vipsModulePromise;
555 732 var vipsModule;
@@ -563,9 +740,9 @@
563 740 );
564 741 }
565 742 return vipsModulePromise;
566 743 }
567 - async function vipsConvertImageFormat(id, file, type, quality, interlaced) {
744 + async function vipsConvertImageFormat(id, file, type, options = {}) {
568 745 const { vipsConvertImageFormat: convertImageFormat } = await loadVipsModule();
569 746 const buffer = await convertImageFormat(
570 747 id,
571 748 await file.arrayBuffer(),
@@ -570,10 +747,9 @@
570 747 id,
571 748 await file.arrayBuffer(),
572 749 file.type,
573 750 type,
574 - quality,
575 - interlaced
751 + options
576 752 );
577 753 const ext = type.split("/")[1];
578 754 const fileName = `${getFileBasename(file.name)}.${ext}`;
579 755 return new File([new Blob([buffer])], fileName, {
@@ -587,21 +763,32 @@
587 763 throw new Error(`Failed to fetch image: ${response.status}`);
588 764 }
589 765 return hasTransparency(await response.arrayBuffer());
590 766 }
591 - async function vipsResizeImage(id, file, resize, smartCrop, addSuffix, signal, scaledSuffix, quality) {
767 + async function vipsGetUltraHdrInfo(buffer) {
768 + const { vipsGetUltraHdrInfo: getUltraHdrInfo } = await loadVipsModule();
769 + return getUltraHdrInfo(buffer);
770 + }
771 + async function vipsResizeImage(id, file, resize, options = {}) {
772 + const {
773 + smartCrop = false,
774 + addSuffix = false,
775 + signal,
776 + scaledSuffix,
777 + quality,
778 + stripMeta,
779 + maxBitdepth
780 + } = options;
592 781 if (signal?.aborted) {
593 782 throw new Error("Operation aborted");
594 783 }
595 784 const { vipsResizeImage: resizeImage } = await loadVipsModule();
596 - const { buffer, width, height, originalWidth, originalHeight } = await resizeImage(
597 - id,
598 - await file.arrayBuffer(),
599 - file.type,
600 - resize,
785 + const { buffer, width, height, originalWidth, originalHeight } = await resizeImage(id, await file.arrayBuffer(), file.type, resize, {
601 786 smartCrop,
602 - quality
603 - );
787 + quality,
788 + stripMeta,
789 + maxBitdepth
790 + });
604 791 let fileName = file.name;
605 792 const wasResized = originalWidth > width || originalHeight > height;
606 793 if (wasResized) {
607 794 const basename = getFileBasename(file.name);
@@ -628,56 +815,8 @@
628 815 originalHeight
629 816 );
630 817 return resultFile;
631 818 }
632 - async function vipsBatchResizeImage(id, file, outputType, configs, smartCrop = false) {
633 - const { vipsBatchResizeImage: batchResize } = await loadVipsModule();
634 - const resizes = configs.map((c) => ({
635 - resize: { ...c.resize },
636 - quality: c.quality
637 - }));
638 - const results = await batchResize(
639 - id,
640 - await file.arrayBuffer(),
641 - file.type,
642 - outputType,
643 - resizes,
644 - smartCrop
645 - );
646 - const ext = outputType.split("/")[1];
647 - const sourceBasename = getFileBasename(file.name);
648 - return results.map((result, i) => {
649 - const config = configs[i];
650 - const { width, height, originalWidth, originalHeight } = result;
651 - const wasResized = originalWidth > width || originalHeight > height;
652 - let fileName = `${sourceBasename}.${ext}`;
653 - if (wasResized) {
654 - if (config.scaledSuffix) {
655 - fileName = `${sourceBasename}-scaled.${ext}`;
656 - } else {
657 - fileName = `${sourceBasename}-${width}x${height}.${ext}`;
658 - }
659 - }
660 - return {
661 - name: config.name,
662 - file: new ImageFile(
663 - new File(
664 - [
665 - new Blob([result.buffer], {
666 - type: outputType
667 - })
668 - ],
669 - fileName,
670 - { type: outputType }
671 - ),
672 - width,
673 - height,
674 - originalWidth,
675 - originalHeight
676 - )
677 - };
678 - });
679 - }
680 819 async function vipsRotateImage(id, file, orientation, signal) {
681 820 if (signal?.aborted) {
682 821 throw new Error("Operation aborted");
683 822 }
@@ -716,13 +855,172 @@
716 855 if (vipsModule) {
717 856 vipsModule.terminateVipsWorker();
718 857 }
719 858 }
859 + var completedVipsOperations = 0;
860 + var MAX_VIPS_OPS_BEFORE_RECYCLE = 50;
861 + function maybeRecycleVipsWorker(activeImageProcessingCount) {
862 + completedVipsOperations++;
863 + if (completedVipsOperations >= MAX_VIPS_OPS_BEFORE_RECYCLE && activeImageProcessingCount === 0) {
864 + terminateVipsWorker();
865 + completedVipsOperations = 0;
866 + }
867 + }
720 868
721 - // packages/upload-media/build-module/validate-mime-type.mjs
722 - var import_i18n2 = __toESM(require_i18n(), 1);
869 + // packages/upload-media/build-module/store/utils/video-conversion.mjs
870 + var UNSUPPORTED_ERROR_PREFIX = "Unsupported";
871 + var SIZE_LIMIT_ERROR_PREFIX = `${UNSUPPORTED_ERROR_PREFIX}: GIF exceeds maximum conversion size`;
872 + var CONVERSION_TIMEOUT_ERROR_PREFIX = "GIF to video conversion timed out";
873 + var DEFAULT_CONVERSION_TIMEOUT = 3e4;
874 + function isUnsupportedConversionError(error) {
875 + return error instanceof Error && error.message.startsWith(UNSUPPORTED_ERROR_PREFIX);
876 + }
877 + function isSizeLimitConversionError(error) {
878 + return error instanceof Error && error.message.startsWith(SIZE_LIMIT_ERROR_PREFIX);
879 + }
880 + function isConversionTimeoutError(error) {
881 + return error instanceof Error && error.message.startsWith(CONVERSION_TIMEOUT_ERROR_PREFIX);
882 + }
883 + var videoConversionModulePromise;
884 + var videoConversionModule;
885 + function loadVideoConversionModule() {
886 + if (!videoConversionModulePromise) {
887 + videoConversionModulePromise = import("@wordpress/video-conversion/worker").then((mod) => {
888 + videoConversionModule = mod;
889 + return mod;
890 + }).catch((error) => {
891 + videoConversionModulePromise = void 0;
892 + throw error;
893 + });
894 + }
895 + return videoConversionModulePromise;
896 + }
897 + async function convertGifToVideo(id, file, outputMimeType, {
898 + maxDimensions,
899 + timeout = DEFAULT_CONVERSION_TIMEOUT,
900 + maxTotalPixels
901 + } = {}) {
902 + const mod = await loadVideoConversionModule();
903 + const conversion = mod.convertGifToVideo(
904 + id,
905 + file,
906 + outputMimeType,
907 + maxDimensions,
908 + maxTotalPixels
909 + );
910 + let buffer;
911 + if (timeout > 0) {
912 + let timer;
913 + try {
914 + buffer = await Promise.race([
915 + conversion,
916 + new Promise((_, reject) => {
917 + timer = setTimeout(() => {
918 + reject(
919 + new Error(
920 + `${CONVERSION_TIMEOUT_ERROR_PREFIX} after ${timeout}ms`
921 + )
922 + );
923 + }, timeout);
924 + })
925 + ]);
926 + } catch (error) {
927 + if (isConversionTimeoutError(error)) {
928 + conversion.catch(() => {
929 + });
930 + mod.cancelGifToVideoOperations(id).catch(() => {
931 + });
932 + }
933 + throw error;
934 + } finally {
935 + clearTimeout(timer);
936 + }
937 + } else {
938 + buffer = await conversion;
939 + }
940 + const ext = outputMimeType === "video/webm" ? "webm" : "mp4";
941 + const fileName = `${getFileBasename(file.name)}.${ext}`;
942 + return new File(
943 + [new Blob([buffer], { type: outputMimeType })],
944 + fileName,
945 + { type: outputMimeType }
946 + );
947 + }
948 + async function cancelGifToVideoOperations(id) {
949 + const mod = videoConversionModule ?? (videoConversionModulePromise ? await videoConversionModulePromise.catch(() => void 0) : void 0);
950 + if (!mod) {
951 + return false;
952 + }
953 + return mod.cancelGifToVideoOperations(id);
954 + }
955 + function terminateVideoConversionWorker() {
956 + if (videoConversionModule) {
957 + videoConversionModule.terminateVideoConversionWorker();
958 + return;
959 + }
960 + if (videoConversionModulePromise) {
961 + void videoConversionModulePromise.then((mod) => mod.terminateVideoConversionWorker()).catch(() => {
962 + });
963 + }
964 + }
723 965
966 + // packages/upload-media/build-module/store/utils/debug-logger.mjs
967 + function isDebugEnabled() {
968 + return true;
969 + }
970 + function debug(message, ...args) {
971 + if (!isDebugEnabled()) {
972 + return;
973 + }
974 + console.debug(`[upload-media] ${message}`, ...args);
975 + }
976 + function measure(options) {
977 + if (!isDebugEnabled()) {
978 + return;
979 + }
980 + const {
981 + measureName,
982 + startTime,
983 + endTime = performance.now(),
984 + tooltipText,
985 + properties
986 + } = options;
987 + const detail = {
988 + devtools: {
989 + dataType: "track-entry",
990 + track: "Upload Media",
991 + tooltipText,
992 + properties: properties?.map(([key, value]) => ({
993 + key,
994 + value
995 + }))
996 + }
997 + };
998 + try {
999 + performance.measure(measureName, {
1000 + start: startTime,
1001 + end: endTime,
1002 + detail
1003 + });
1004 + } catch {
1005 + }
1006 + }
1007 +
724 1008 // packages/upload-media/build-module/upload-error.mjs
1009 + var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
1010 + ErrorCode2["EMPTY_FILE"] = "EMPTY_FILE";
1011 + ErrorCode2["SIZE_ABOVE_LIMIT"] = "SIZE_ABOVE_LIMIT";
1012 + ErrorCode2["MIME_TYPE_NOT_SUPPORTED"] = "MIME_TYPE_NOT_SUPPORTED";
1013 + ErrorCode2["MIME_TYPE_NOT_ALLOWED_FOR_USER"] = "MIME_TYPE_NOT_ALLOWED_FOR_USER";
1014 + ErrorCode2["HEIC_DECODE_ERROR"] = "HEIC_DECODE_ERROR";
1015 + ErrorCode2["IMAGE_TRANSCODING_ERROR"] = "IMAGE_TRANSCODING_ERROR";
1016 + ErrorCode2["IMAGE_ROTATION_ERROR"] = "IMAGE_ROTATION_ERROR";
1017 + ErrorCode2["MEDIA_TRANSCODING_ERROR"] = "MEDIA_TRANSCODING_ERROR";
1018 + ErrorCode2["GIF_TRANSCODING_ERROR"] = "GIF_TRANSCODING_ERROR";
1019 + ErrorCode2["MEDIA_FINALIZE_ERROR"] = "MEDIA_FINALIZE_ERROR";
1020 + ErrorCode2["GENERAL"] = "GENERAL";
1021 + return ErrorCode2;
1022 + })(ErrorCode || {});
725 1023 var UploadError = class extends Error {
726 1024 code;
727 1025 file;
728 1026 constructor({ code, message, file, cause }) {
@@ -733,8 +1031,9 @@
733 1031 }
734 1032 };
735 1033
736 1034 // packages/upload-media/build-module/validate-mime-type.mjs
1035 + var import_i18n2 = __toESM(require_i18n(), 1);
737 1036 function validateMimeType(file, allowedTypes) {
738 1037 if (!allowedTypes) {
739 1038 return;
740 1039 }
@@ -745,9 +1044,9 @@
745 1044 return file.type.startsWith(`${allowedType}/`);
746 1045 });
747 1046 if (file.type && !isAllowedType) {
748 1047 throw new UploadError({
749 - code: "MIME_TYPE_NOT_SUPPORTED",
1048 + code: ErrorCode.MIME_TYPE_NOT_SUPPORTED,
750 1049 message: (0, import_i18n2.sprintf)(
751 1050 // translators: %s: file name.
752 1051 (0, import_i18n2.__)("%s: Sorry, this file type is not supported here."),
753 1052 file.name
@@ -789,9 +1088,9 @@
789 1088 file.type
790 1089 );
791 1090 if (file.type && !isAllowedMimeTypeForUser) {
792 1091 throw new UploadError({
793 - code: "MIME_TYPE_NOT_ALLOWED_FOR_USER",
1092 + code: ErrorCode.MIME_TYPE_NOT_ALLOWED_FOR_USER,
794 1093 message: (0, import_i18n3.sprintf)(
795 1094 // translators: %s: file name.
796 1095 (0, import_i18n3.__)(
797 1096 "%s: Sorry, you are not allowed to upload this file type."
@@ -807,9 +1106,9 @@
807 1106 var import_i18n4 = __toESM(require_i18n(), 1);
808 1107 function validateFileSize(file, maxUploadFileSize) {
809 1108 if (file.size <= 0) {
810 1109 throw new UploadError({
811 - code: "EMPTY_FILE",
1110 + code: ErrorCode.EMPTY_FILE,
812 1111 message: (0, import_i18n4.sprintf)(
813 1112 // translators: %s: file name.
814 1113 (0, import_i18n4.__)("%s: This file is empty."),
815 1114 file.name
@@ -818,9 +1117,9 @@
818 1117 });
819 1118 }
820 1119 if (maxUploadFileSize && file.size > maxUploadFileSize) {
821 1120 throw new UploadError({
822 - code: "SIZE_ABOVE_LIMIT",
1121 + code: ErrorCode.SIZE_ABOVE_LIMIT,
823 1122 message: (0, import_i18n4.sprintf)(
824 1123 // translators: %s: file name.
825 1124 (0, import_i18n4.__)(
826 1125 "%s: This file exceeds the maximum upload size for this site."
@@ -881,17 +1180,38 @@
881 1180 const item = select2.getItem(id);
882 1181 if (!item) {
883 1182 return;
884 1183 }
1184 + clearRetryTimer(id);
1185 + if (!silent && error && !item.parentId && !item.attachment?.id) {
1186 + const settings = select2.getSettings();
1187 + const retrySettings = settings.retry;
1188 + if (retrySettings) {
1189 + const retryCount = item.retryCount ?? 0;
1190 + const maxRetries = retrySettings.maxRetryAttempts;
1191 + if (shouldRetryError(error, retryCount, maxRetries)) {
1192 + dispatch.scheduleRetry(id, error);
1193 + return;
1194 + }
1195 + }
1196 + }
885 1197 item.abortController?.abort();
886 - await vipsCancelOperations(id);
1198 + vipsCancelOperations(id).catch(() => {
1199 + });
1200 + cancelGifToVideoOperations(id).catch(() => {
1201 + });
887 1202 if (!silent) {
888 1203 const { onError } = item;
889 1204 onError?.(error ?? new Error("Upload cancelled"));
890 - if (!onError && error) {
1205 + if (!onError && error && !item.parentId) {
891 1206 console.error("Upload cancelled", error);
892 1207 }
1208 + } else {
1209 + debug(
1210 + `Item cancelled: ${item.file.name} (item ${id}): ${error instanceof Error ? error.message : error}`
1211 + );
893 1212 }
1213 + const { currentOperation, parentId, batchId } = item;
894 1214 dispatch({
895 1215 type: Type.Cancel,
896 1216 id,
897 1217 error
@@ -897,9 +1217,59 @@
897 1217 error
898 1218 });
899 1219 dispatch.removeItem(id);
900 1220 dispatch.revokeBlobUrls(id);
901 - if (item.batchId && select2.isBatchUploaded(item.batchId)) {
1221 + if (currentOperation === OperationType.ResizeCrop || currentOperation === OperationType.Rotate) {
1222 + for (const pending of select2.getPendingImageProcessing()) {
1223 + dispatch.processItem(pending.id);
1224 + }
1225 + }
1226 + if (currentOperation === OperationType.Upload) {
1227 + for (const pending of select2.getPendingUploads()) {
1228 + dispatch.processItem(pending.id);
1229 + }
1230 + }
1231 + if (currentOperation === OperationType.TranscodeGif) {
1232 + for (const pending of select2.getPendingVideoProcessing()) {
1233 + dispatch.processItem(pending.id);
1234 + }
1235 + }
1236 + if (currentOperation === OperationType.ResizeCrop || currentOperation === OperationType.Rotate || currentOperation === OperationType.TranscodeImage) {
1237 + maybeRecycleVipsWorker(select2.getActiveImageProcessingCount());
1238 + }
1239 + if (parentId) {
1240 + const parentItem = select2.getItem(parentId);
1241 + if (parentItem) {
1242 + const isOptionalCompanion = item.additionalData?.image_size === "animated_video" || item.additionalData?.image_size === "animated_video_poster";
1243 + if (select2.hasPendingItemsByParentId(parentId)) {
1244 + if (parentItem.operations && parentItem.operations.length > 0) {
1245 + dispatch.processItem(parentId);
1246 + }
1247 + } else if (parentItem.subSizes && parentItem.subSizes.length > 0 || isOptionalCompanion) {
1248 + if (parentItem.operations && parentItem.operations.length > 0) {
1249 + dispatch.processItem(parentId);
1250 + }
1251 + } else {
1252 + const parentAttachmentId = parentItem.attachment?.id;
1253 + const { mediaDelete } = select2.getSettings();
1254 + if (parentAttachmentId && mediaDelete) {
1255 + mediaDelete(parentAttachmentId).catch(() => {
1256 + });
1257 + }
1258 + await dispatch.cancelItem(
1259 + parentId,
1260 + new UploadError({
1261 + code: error instanceof UploadError && error.code || ErrorCode.GENERAL,
1262 + message: error?.message || (0, import_i18n5.__)("The image could not be uploaded."),
1263 + file: parentItem.file,
1264 + cause: error instanceof Error ? error : void 0
1265 + })
1266 + );
1267 + }
1268 + }
1269 + }
1270 + if (batchId && select2.isBatchUploaded(batchId)) {
1271 + debug(`Batch completed: ${batchId}`);
902 1272 item.onBatchSuccess?.();
903 1273 }
904 1274 };
905 1275 }
@@ -918,8 +1288,57 @@
918 1288 });
919 1289 dispatch.processItem(id);
920 1290 };
921 1291 }
1292 + function scheduleRetry(id, error) {
1293 + return async ({ select: select2, dispatch }) => {
1294 + const item = select2.getItem(id);
1295 + if (!item) {
1296 + return;
1297 + }
1298 + const settings = select2.getSettings();
1299 + const retrySettings = settings.retry;
1300 + if (!retrySettings) {
1301 + return;
1302 + }
1303 + const currentRetryCount = item.retryCount ?? 0;
1304 + const delay = calculateRetryDelay({
1305 + attempt: currentRetryCount + 1,
1306 + initialDelay: retrySettings.initialRetryDelayMs,
1307 + maxDelay: retrySettings.maxRetryDelayMs,
1308 + multiplier: retrySettings.backoffMultiplier,
1309 + jitter: retrySettings.retryJitter
1310 + });
1311 + const timerId = setTimeout(() => {
1312 + retryTimers.delete(id);
1313 + dispatch.executeRetry(id);
1314 + }, delay);
1315 + retryTimers.set(id, timerId);
1316 + dispatch({
1317 + type: Type.ScheduleRetry,
1318 + id,
1319 + error,
1320 + retryCount: currentRetryCount,
1321 + nextRetryTimestamp: Date.now() + delay
1322 + });
1323 + };
1324 + }
1325 + function executeRetry(id) {
1326 + return async ({ select: select2, dispatch }) => {
1327 + const item = select2.getItem(id);
1328 + if (!item || item.status !== ItemStatus.PendingRetry) {
1329 + return;
1330 + }
1331 + if (select2.isPaused()) {
1332 + return;
1333 + }
1334 + dispatch({
1335 + type: Type.RetryItem,
1336 + id
1337 + });
1338 + dispatch.processItem(id);
1339 + };
1340 + }
922 1341
923 1342 // packages/upload-media/build-module/store/private-actions.mjs
924 1343 var private_actions_exports = {};
925 1344 __export(private_actions_exports, {
@@ -924,8 +1343,9 @@
924 1343 var private_actions_exports = {};
925 1344 __export(private_actions_exports, {
926 1345 addItem: () => addItem,
927 1346 addSideloadItem: () => addSideloadItem,
1347 + detectUltraHdr: () => detectUltraHdr,
928 1348 finalizeItem: () => finalizeItem,
929 1349 finishOperation: () => finishOperation,
930 1350 generateThumbnails: () => generateThumbnails,
931 1351 getTranscodeImageOperation: () => getTranscodeImageOperation,
@@ -934,13 +1354,13 @@
934 1354 prepareItem: () => prepareItem,
935 1355 processItem: () => processItem,
936 1356 removeItem: () => removeItem,
937 1357 resizeCropItem: () => resizeCropItem,
938 - resumeItemByPostId: () => resumeItemByPostId,
939 1358 resumeQueue: () => resumeQueue,
940 1359 revokeBlobUrls: () => revokeBlobUrls,
941 1360 rotateItem: () => rotateItem,
942 1361 sideloadItem: () => sideloadItem,
1362 + transcodeGifItem: () => transcodeGifItem,
943 1363 transcodeImageItem: () => transcodeImageItem,
944 1364 updateItemProgress: () => updateItemProgress,
945 1365 updateSettings: () => updateSettings,
946 1366 uploadItem: () => uploadItem
@@ -945,9 +1365,1180 @@
945 1365 updateSettings: () => updateSettings,
946 1366 uploadItem: () => uploadItem
947 1367 });
948 1368 var import_blob = __toESM(require_blob(), 1);
1369 + var import_i18n7 = __toESM(require_i18n(), 1);
949 1370
1371 + // packages/upload-media/build-module/heic-parser.mjs
1372 + var Reader = class {
1373 + view;
1374 + buffer;
1375 + pos;
1376 + constructor(buffer, offset = 0) {
1377 + this.buffer = buffer;
1378 + this.view = new DataView(buffer);
1379 + this.pos = offset;
1380 + }
1381 + u8() {
1382 + const v = this.view.getUint8(this.pos);
1383 + this.pos += 1;
1384 + return v;
1385 + }
1386 + u16() {
1387 + const v = this.view.getUint16(this.pos);
1388 + this.pos += 2;
1389 + return v;
1390 + }
1391 + u32() {
1392 + const v = this.view.getUint32(this.pos);
1393 + this.pos += 4;
1394 + return v;
1395 + }
1396 + u64() {
1397 + const hi = this.view.getUint32(this.pos);
1398 + const lo = this.view.getUint32(this.pos + 4);
1399 + this.pos += 8;
1400 + return hi * 4294967296 + lo;
1401 + }
1402 + /**
1403 + * Read a variable-width unsigned integer (0, 4 or 8 bytes).
1404 + *
1405 + * @param size Byte width to read (0, 4, or 8).
1406 + */
1407 + uN(size) {
1408 + if (size === 0) {
1409 + return 0;
1410 + }
1411 + if (size === 4) {
1412 + return this.u32();
1413 + }
1414 + if (size === 8) {
1415 + return this.u64();
1416 + }
1417 + throw new Error(`Unsupported uint size: ${size}`);
1418 + }
1419 + str(len) {
1420 + let s = "";
1421 + for (let i = 0; i < len; i++) {
1422 + s += String.fromCharCode(this.view.getUint8(this.pos + i));
1423 + }
1424 + this.pos += len;
1425 + return s;
1426 + }
1427 + bytes(len) {
1428 + const b = new Uint8Array(this.buffer, this.pos, len);
1429 + this.pos += len;
1430 + return new Uint8Array(b);
1431 + }
1432 + };
1433 + function readBox(r) {
1434 + if (r.pos + 8 > r.view.byteLength) {
1435 + return null;
1436 + }
1437 + const offset = r.pos;
1438 + let size = r.u32();
1439 + const type = r.str(4);
1440 + let headerSize = 8;
1441 + if (size === 1) {
1442 + size = r.u64();
1443 + headerSize = 16;
1444 + } else if (size === 0) {
1445 + size = r.view.byteLength - offset;
1446 + }
1447 + return { type, offset, size, headerSize };
1448 + }
1449 + function findBoxes(r, start, end) {
1450 + const boxes = [];
1451 + r.pos = start;
1452 + while (r.pos < end) {
1453 + const box = readBox(r);
1454 + if (!box || box.size < 8) {
1455 + break;
1456 + }
1457 + boxes.push(box);
1458 + r.pos = box.offset + box.size;
1459 + }
1460 + return boxes;
1461 + }
1462 + function findBox(r, start, end, type) {
1463 + r.pos = start;
1464 + while (r.pos < end) {
1465 + const box = readBox(r);
1466 + if (!box || box.size < 8) {
1467 + break;
1468 + }
1469 + if (box.type === type) {
1470 + return box;
1471 + }
1472 + r.pos = box.offset + box.size;
1473 + }
1474 + return void 0;
1475 + }
1476 + function parsePitm(r, box) {
1477 + r.pos = box.offset + box.headerSize;
1478 + const version = r.u8();
1479 + r.pos += 3;
1480 + return version === 0 ? r.u16() : r.u32();
1481 + }
1482 + function parseIloc(r, box) {
1483 + r.pos = box.offset + box.headerSize;
1484 + const version = r.u8();
1485 + r.pos += 3;
1486 + const byte1 = r.u8();
1487 + const offsetSize = byte1 >> 4 & 15;
1488 + const lengthSize = byte1 & 15;
1489 + const byte2 = r.u8();
1490 + const baseOffsetSize = byte2 >> 4 & 15;
1491 + const indexSize = version >= 1 ? byte2 & 15 : 0;
1492 + const itemCount = version < 2 ? r.u16() : r.u32();
1493 + const items = /* @__PURE__ */ new Map();
1494 + for (let i = 0; i < itemCount; i++) {
1495 + const itemId = version < 2 ? r.u16() : r.u32();
1496 + let constructionMethod = 0;
1497 + if (version === 1 || version === 2) {
1498 + const cm = r.u16();
1499 + constructionMethod = cm & 15;
1500 + }
1501 + r.u16();
1502 + const baseOffset = r.uN(baseOffsetSize);
1503 + const extentCount = r.u16();
1504 + const extents = [];
1505 + for (let j = 0; j < extentCount; j++) {
1506 + if (version >= 1) {
1507 + r.uN(indexSize);
1508 + }
1509 + const extOffset = r.uN(offsetSize);
1510 + const extLength = r.uN(lengthSize);
1511 + extents.push({
1512 + offset: baseOffset + extOffset,
1513 + length: extLength
1514 + });
1515 + }
1516 + items.set(itemId, { constructionMethod, extents });
1517 + }
1518 + return items;
1519 + }
1520 + function parseIpma(r, box) {
1521 + r.pos = box.offset + box.headerSize;
1522 + const vf = r.u32();
1523 + const version = vf >>> 24;
1524 + const flags = vf & 16777215;
1525 + const largeIndex = (flags & 1) !== 0;
1526 + const entryCount = r.u32();
1527 + const associations = /* @__PURE__ */ new Map();
1528 + for (let i = 0; i < entryCount; i++) {
1529 + const itemId = version < 1 ? r.u16() : r.u32();
1530 + const assocCount = r.u8();
1531 + const indices = [];
1532 + for (let j = 0; j < assocCount; j++) {
1533 + if (largeIndex) {
1534 + indices.push(r.u16() & 32767);
1535 + } else {
1536 + indices.push(r.u8() & 127);
1537 + }
1538 + }
1539 + associations.set(itemId, indices);
1540 + }
1541 + return associations;
1542 + }
1543 + function parseIspe(r, box) {
1544 + r.pos = box.offset + box.headerSize + 4;
1545 + return { width: r.u32(), height: r.u32() };
1546 + }
1547 + function parseIrot(r, box) {
1548 + r.pos = box.offset + box.headerSize;
1549 + return (r.u8() & 3) * 90;
1550 + }
1551 + function parseIinf(r, box) {
1552 + r.pos = box.offset + box.headerSize;
1553 + const version = r.u8();
1554 + r.pos += 3;
1555 + const entryCount = version === 0 ? r.u16() : r.u32();
1556 + const itemTypes = /* @__PURE__ */ new Map();
1557 + const entriesStart = r.pos;
1558 + const boxEnd = box.offset + box.size;
1559 + const infeBoxes = findBoxes(r, entriesStart, boxEnd);
1560 + for (let i = 0; i < Math.min(entryCount, infeBoxes.length); i++) {
1561 + const infe = infeBoxes[i];
1562 + if (infe.type !== "infe") {
1563 + continue;
1564 + }
1565 + r.pos = infe.offset + infe.headerSize;
1566 + const infeVersion = r.u8();
1567 + r.pos += 3;
1568 + if (infeVersion >= 2) {
1569 + const itemId = infeVersion === 2 ? r.u16() : r.u32();
1570 + r.u16();
1571 + const itemType = r.str(4);
1572 + itemTypes.set(itemId, itemType);
1573 + }
1574 + }
1575 + return itemTypes;
1576 + }
1577 + function parseIref(r, box, refType) {
1578 + r.pos = box.offset + box.headerSize;
1579 + const version = r.u8();
1580 + r.pos += 3;
1581 + const refs = /* @__PURE__ */ new Map();
1582 + const boxEnd = box.offset + box.size;
1583 + while (r.pos < boxEnd) {
1584 + const refBox = readBox(r);
1585 + if (!refBox || refBox.size < 8) {
1586 + break;
1587 + }
1588 + r.pos = refBox.offset + refBox.headerSize;
1589 + const fromId = version === 0 ? r.u16() : r.u32();
1590 + const refCount = r.u16();
1591 + const toIds = [];
1592 + for (let i = 0; i < refCount; i++) {
1593 + toIds.push(version === 0 ? r.u16() : r.u32());
1594 + }
1595 + if (refBox.type === refType) {
1596 + refs.set(fromId, toIds);
1597 + }
1598 + r.pos = refBox.offset + refBox.size;
1599 + }
1600 + return refs;
1601 + }
1602 + function reverseBits32(n) {
1603 + n = n >>> 1 & 1431655765 | (n & 1431655765) << 1;
1604 + n = n >>> 2 & 858993459 | (n & 858993459) << 2;
1605 + n = n >>> 4 & 252645135 | (n & 252645135) << 4;
1606 + n = n >>> 8 & 16711935 | (n & 16711935) << 8;
1607 + n = n >>> 16 | n << 16;
1608 + return n >>> 0;
1609 + }
1610 + function buildCodecString(r, recordOffset) {
1611 + r.pos = recordOffset;
1612 + r.u8();
1613 + const byte1 = r.u8();
1614 + const profileSpace = byte1 >> 6 & 3;
1615 + const tierFlag = byte1 >> 5 & 1;
1616 + const profileIdc = byte1 & 31;
1617 + const compatFlags = r.u32();
1618 + const constraintBytes = r.bytes(6);
1619 + const levelIdc = r.u8();
1620 + const spacePrefix = profileSpace > 0 ? String.fromCharCode(64 + profileSpace) : "";
1621 + const compatHex = reverseBits32(compatFlags).toString(16).toUpperCase();
1622 + const tierChar = tierFlag ? "H" : "L";
1623 + let lastNonZero = -1;
1624 + for (let i = 5; i >= 0; i--) {
1625 + if (constraintBytes[i] !== 0) {
1626 + lastNonZero = i;
1627 + break;
1628 + }
1629 + }
1630 + let constraintStr = "";
1631 + if (lastNonZero >= 0) {
1632 + const parts = [];
1633 + for (let i = 0; i <= lastNonZero; i++) {
1634 + parts.push(constraintBytes[i].toString(16).toUpperCase());
1635 + }
1636 + constraintStr = "." + parts.join(".");
1637 + }
1638 + return `hvc1.${spacePrefix}${profileIdc}.${compatHex}.${tierChar}${levelIdc}${constraintStr}`;
1639 + }
1640 + function readItemData(buffer, loc, idatOffset) {
1641 + const baseOffset = loc.constructionMethod === 1 ? idatOffset : 0;
1642 + for (const ext of loc.extents) {
1643 + if (baseOffset + ext.offset + ext.length > buffer.byteLength) {
1644 + throw new Error(
1645 + "HEIC item data extends past the end of the file"
1646 + );
1647 + }
1648 + }
1649 + if (loc.extents.length === 1) {
1650 + const ext = loc.extents[0];
1651 + const start = baseOffset + ext.offset;
1652 + return new Uint8Array(buffer.slice(start, start + ext.length));
1653 + }
1654 + let totalLength = 0;
1655 + for (const ext of loc.extents) {
1656 + totalLength += ext.length;
1657 + }
1658 + const data = new Uint8Array(totalLength);
1659 + let pos = 0;
1660 + for (const ext of loc.extents) {
1661 + const start = baseOffset + ext.offset;
1662 + data.set(
1663 + new Uint8Array(buffer.slice(start, start + ext.length)),
1664 + pos
1665 + );
1666 + pos += ext.length;
1667 + }
1668 + return data;
1669 + }
1670 + function findHvcProperties(propIndices, properties) {
1671 + let hvcCBox;
1672 + let ispeBox;
1673 + let irotBox;
1674 + for (const idx of propIndices) {
1675 + if (idx < 1 || idx > properties.length) {
1676 + continue;
1677 + }
1678 + const prop = properties[idx - 1];
1679 + if (prop.type === "hvcC" && !hvcCBox) {
1680 + hvcCBox = prop;
1681 + }
1682 + if (prop.type === "ispe" && !ispeBox) {
1683 + ispeBox = prop;
1684 + }
1685 + if (prop.type === "irot" && !irotBox) {
1686 + irotBox = prop;
1687 + }
1688 + }
1689 + if (!hvcCBox) {
1690 + throw new Error("No HEVC configuration (hvcC) found");
1691 + }
1692 + if (!ispeBox) {
1693 + throw new Error("No image dimensions (ispe) found");
1694 + }
1695 + return { hvcCBox, ispeBox, irotBox };
1696 + }
1697 + function parseHeic(buffer) {
1698 + const r = new Reader(buffer);
1699 + const fileEnd = buffer.byteLength;
1700 + const metaBox = findBox(r, 0, fileEnd, "meta");
1701 + if (!metaBox) {
1702 + throw new Error("No meta box found in HEIC file");
1703 + }
1704 + const metaChildStart = metaBox.offset + metaBox.headerSize + 4;
1705 + const metaEnd = metaBox.offset + metaBox.size;
1706 + const children = findBoxes(r, metaChildStart, metaEnd);
1707 + const pitmBox = children.find((b) => b.type === "pitm");
1708 + const ilocBox = children.find((b) => b.type === "iloc");
1709 + const iprpBox = children.find((b) => b.type === "iprp");
1710 + const iinfBox = children.find((b) => b.type === "iinf");
1711 + const irefBox = children.find((b) => b.type === "iref");
1712 + const idatBox = children.find((b) => b.type === "idat");
1713 + const idatOffset = idatBox ? idatBox.offset + idatBox.headerSize : 0;
1714 + if (!pitmBox || !ilocBox || !iprpBox) {
1715 + throw new Error("Missing required boxes (pitm, iloc, iprp) in HEIC");
1716 + }
1717 + const primaryId = parsePitm(r, pitmBox);
1718 + const locations = parseIloc(r, ilocBox);
1719 + const iprpStart = iprpBox.offset + iprpBox.headerSize;
1720 + const iprpEnd = iprpBox.offset + iprpBox.size;
1721 + const iprpChildren = findBoxes(r, iprpStart, iprpEnd);
1722 + const ipcoBox = iprpChildren.find((b) => b.type === "ipco");
1723 + const ipmaBox = iprpChildren.find((b) => b.type === "ipma");
1724 + if (!ipcoBox || !ipmaBox) {
1725 + throw new Error("Missing ipco or ipma in HEIC properties");
1726 + }
1727 + const allAssoc = parseIpma(r, ipmaBox);
1728 + const ipcoStart = ipcoBox.offset + ipcoBox.headerSize;
1729 + const ipcoEnd = ipcoBox.offset + ipcoBox.size;
1730 + const properties = findBoxes(r, ipcoStart, ipcoEnd);
1731 + let primaryItemType = "hvc1";
1732 + if (iinfBox) {
1733 + const itemTypes = parseIinf(r, iinfBox);
1734 + const t = itemTypes.get(primaryId);
1735 + if (t) {
1736 + primaryItemType = t;
1737 + }
1738 + }
1739 + if (primaryItemType === "grid") {
1740 + return parseGridImage(
1741 + r,
1742 + buffer,
1743 + primaryId,
1744 + locations,
1745 + allAssoc,
1746 + properties,
1747 + irefBox,
1748 + idatOffset
1749 + );
1750 + }
1751 + const primaryLoc = locations.get(primaryId);
1752 + if (!primaryLoc || primaryLoc.extents.length === 0) {
1753 + throw new Error(`No location data for primary item ${primaryId}`);
1754 + }
1755 + const primaryPropIndices = allAssoc.get(primaryId);
1756 + if (!primaryPropIndices || primaryPropIndices.length === 0) {
1757 + throw new Error("No property associations for primary item");
1758 + }
1759 + const { hvcCBox, ispeBox, irotBox } = findHvcProperties(
1760 + primaryPropIndices,
1761 + properties
1762 + );
1763 + const hvcCDataStart = hvcCBox.offset + hvcCBox.headerSize;
1764 + const hvcCDataSize = hvcCBox.size - hvcCBox.headerSize;
1765 + const description = new Uint8Array(
1766 + buffer.slice(hvcCDataStart, hvcCDataStart + hvcCDataSize)
1767 + );
1768 + const codecString = buildCodecString(r, hvcCDataStart);
1769 + const { width, height } = parseIspe(r, ispeBox);
1770 + const rotation = irotBox ? parseIrot(r, irotBox) : 0;
1771 + return {
1772 + codecString,
1773 + description,
1774 + tiles: [
1775 + {
1776 + data: readItemData(buffer, primaryLoc, idatOffset),
1777 + x: 0,
1778 + y: 0
1779 + }
1780 + ],
1781 + tileWidth: width,
1782 + tileHeight: height,
1783 + outputWidth: width,
1784 + outputHeight: height,
1785 + rotation,
1786 + exifOrientation: rotation === 0 ? getUnappliedExifOrientation(buffer) : 1
1787 + };
1788 + }
1789 + function parseGridImage(r, buffer, gridItemId, locations, allAssoc, properties, irefBox, idatOffset) {
1790 + const gridLoc = locations.get(gridItemId);
1791 + if (!gridLoc || gridLoc.extents.length === 0) {
1792 + throw new Error("No location data for grid item");
1793 + }
1794 + const gridData = readItemData(buffer, gridLoc, idatOffset);
1795 + const largeFields = gridData.length > 1 && (gridData[1] & 1) !== 0;
1796 + const minGridSize = largeFields ? 12 : 8;
1797 + if (gridData.length < minGridSize) {
1798 + throw new Error(
1799 + `Grid descriptor too short: ${gridData.length} bytes`
1800 + );
1801 + }
1802 + const rows = gridData[2] + 1;
1803 + const columns = gridData[3] + 1;
1804 + const gv = new DataView(gridData.buffer, gridData.byteOffset);
1805 + let outputWidth;
1806 + let outputHeight;
1807 + if (largeFields) {
1808 + outputWidth = gv.getUint32(4);
1809 + outputHeight = gv.getUint32(8);
1810 + } else {
1811 + outputWidth = gv.getUint16(4);
1812 + outputHeight = gv.getUint16(6);
1813 + }
1814 + if (!irefBox) {
1815 + throw new Error("Grid image requires iref box");
1816 + }
1817 + const dimgRefs = parseIref(r, irefBox, "dimg");
1818 + const tileItemIds = dimgRefs.get(gridItemId);
1819 + if (!tileItemIds || tileItemIds.length === 0) {
1820 + throw new Error("No tile references found for grid item");
1821 + }
1822 + const expectedTiles = rows * columns;
1823 + if (tileItemIds.length < expectedTiles) {
1824 + throw new Error(
1825 + `Grid expects ${expectedTiles} tiles but found ${tileItemIds.length}`
1826 + );
1827 + }
1828 + const firstTileProps = allAssoc.get(tileItemIds[0]);
1829 + if (!firstTileProps || firstTileProps.length === 0) {
1830 + throw new Error("No property associations for tile item");
1831 + }
1832 + const { hvcCBox, ispeBox } = findHvcProperties(
1833 + firstTileProps,
1834 + properties
1835 + );
1836 + const gridProps = allAssoc.get(gridItemId) || [];
1837 + let irotBox;
1838 + for (const idx of gridProps) {
1839 + if (idx >= 1 && idx <= properties.length) {
1840 + const prop = properties[idx - 1];
1841 + if (prop.type === "irot") {
1842 + irotBox = prop;
1843 + break;
1844 + }
1845 + }
1846 + }
1847 + const hvcCDataStart = hvcCBox.offset + hvcCBox.headerSize;
1848 + const hvcCDataSize = hvcCBox.size - hvcCBox.headerSize;
1849 + const description = new Uint8Array(
1850 + buffer.slice(hvcCDataStart, hvcCDataStart + hvcCDataSize)
1851 + );
1852 + const codecString = buildCodecString(r, hvcCDataStart);
1853 + const { width: tileWidth, height: tileHeight } = parseIspe(r, ispeBox);
1854 + const tiles = [];
1855 + for (let row = 0; row < rows; row++) {
1856 + for (let col = 0; col < columns; col++) {
1857 + const tileIdx = row * columns + col;
1858 + const tileId = tileItemIds[tileIdx];
1859 + const tileLoc = locations.get(tileId);
1860 + if (!tileLoc || tileLoc.extents.length === 0) {
1861 + throw new Error(`No location data for tile item ${tileId}`);
1862 + }
1863 + tiles.push({
1864 + data: readItemData(buffer, tileLoc, idatOffset),
1865 + x: col * tileWidth,
1866 + y: row * tileHeight
1867 + });
1868 + }
1869 + }
1870 + const rotation = irotBox ? parseIrot(r, irotBox) : 0;
1871 + return {
1872 + codecString,
1873 + description,
1874 + tiles,
1875 + tileWidth,
1876 + tileHeight,
1877 + outputWidth,
1878 + outputHeight,
1879 + rotation,
1880 + exifOrientation: rotation === 0 ? getUnappliedExifOrientation(buffer) : 1
1881 + };
1882 + }
1883 + function readTiffOrientation(payload) {
1884 + if (payload.length < 8) {
1885 + return 1;
1886 + }
1887 + const view = new DataView(
1888 + payload.buffer,
1889 + payload.byteOffset,
1890 + payload.byteLength
1891 + );
1892 + let tiffStart = 0;
1893 + const firstWord = view.getUint16(0);
1894 + if (firstWord !== 18761 && firstWord !== 19789) {
1895 + tiffStart = view.getUint32(0) + 4;
1896 + }
1897 + if (tiffStart + 8 > payload.length) {
1898 + return 1;
1899 + }
1900 + const byteOrder = view.getUint16(tiffStart);
1901 + let little;
1902 + if (byteOrder === 18761) {
1903 + little = true;
1904 + } else if (byteOrder === 19789) {
1905 + little = false;
1906 + } else {
1907 + return 1;
1908 + }
1909 + const ifd0 = tiffStart + view.getUint32(tiffStart + 4, little);
1910 + if (ifd0 + 2 > payload.length) {
1911 + return 1;
1912 + }
1913 + const entryCount = view.getUint16(ifd0, little);
1914 + for (let i = 0; i < entryCount; i++) {
1915 + const entry = ifd0 + 2 + i * 12;
1916 + if (entry + 12 > payload.length) {
1917 + break;
1918 + }
1919 + if (view.getUint16(entry, little) === 274) {
1920 + const value = view.getUint16(entry + 8, little);
1921 + return value >= 1 && value <= 8 ? value : 1;
1922 + }
1923 + }
1924 + return 1;
1925 + }
1926 + function findMeta(r) {
1927 + const metaBox = findBox(r, 0, r.view.byteLength, "meta");
1928 + if (!metaBox) {
1929 + return null;
1930 + }
1931 + const start = metaBox.offset + metaBox.headerSize + 4;
1932 + const end = metaBox.offset + metaBox.size;
1933 + return { box: metaBox, children: findBoxes(r, start, end) };
1934 + }
1935 + function parseExifOrientation(buffer) {
1936 + try {
1937 + const r = new Reader(buffer);
1938 + const meta = findMeta(r);
1939 + if (!meta) {
1940 + return 1;
1941 + }
1942 + const iinfBox = meta.children.find((b) => b.type === "iinf");
1943 + const ilocBox = meta.children.find((b) => b.type === "iloc");
1944 + if (!iinfBox || !ilocBox) {
1945 + return 1;
1946 + }
1947 + const itemTypes = parseIinf(r, iinfBox);
1948 + let exifItemId;
1949 + for (const [id, type] of itemTypes) {
1950 + if (type === "Exif") {
1951 + exifItemId = id;
1952 + break;
1953 + }
1954 + }
1955 + if (exifItemId === void 0) {
1956 + return 1;
1957 + }
1958 + const loc = parseIloc(r, ilocBox).get(exifItemId);
1959 + if (!loc || loc.extents.length === 0) {
1960 + return 1;
1961 + }
1962 + const idatBox = meta.children.find((b) => b.type === "idat");
1963 + const idatOffset = idatBox ? idatBox.offset + idatBox.headerSize : 0;
1964 + return readTiffOrientation(readItemData(buffer, loc, idatOffset));
1965 + } catch {
1966 + return 1;
1967 + }
1968 + }
1969 + function hasNativeTransform(r) {
1970 + const meta = findMeta(r);
1971 + if (!meta) {
1972 + return false;
1973 + }
1974 + const iprp = meta.children.find((b) => b.type === "iprp");
1975 + if (!iprp) {
1976 + return false;
1977 + }
1978 + const ipco = findBox(
1979 + r,
1980 + iprp.offset + iprp.headerSize,
1981 + iprp.offset + iprp.size,
1982 + "ipco"
1983 + );
1984 + if (!ipco) {
1985 + return false;
1986 + }
1987 + const start = ipco.offset + ipco.headerSize;
1988 + const end = ipco.offset + ipco.size;
1989 + return Boolean(
1990 + findBox(r, start, end, "irot") || findBox(r, start, end, "imir")
1991 + );
1992 + }
1993 + function getUnappliedExifOrientation(buffer) {
1994 + try {
1995 + if (hasNativeTransform(new Reader(buffer))) {
1996 + return 1;
1997 + }
1998 + } catch {
1999 + return 1;
2000 + }
2001 + return parseExifOrientation(buffer);
2002 + }
2003 +
2004 + // packages/upload-media/build-module/heic-support.mjs
2005 + var import_i18n6 = __toESM(require_i18n(), 1);
2006 + function detectBrowserFamily() {
2007 + if (typeof navigator === "undefined") {
2008 + return null;
2009 + }
2010 + const brands = navigator.userAgentData?.brands;
2011 + if (brands?.length) {
2012 + if (brands.some(({ brand }) => /Chromium/i.test(brand))) {
2013 + return "chromium";
2014 + }
2015 + }
2016 + const userAgent = navigator.userAgent || "";
2017 + if (/Firefox|FxiOS/i.test(userAgent)) {
2018 + return "firefox";
2019 + }
2020 + if (/Chrome|Chromium|CriOS|Edg|OPR/i.test(userAgent)) {
2021 + return "chromium";
2022 + }
2023 + if (/Safari/i.test(userAgent)) {
2024 + return "safari";
2025 + }
2026 + return null;
2027 + }
2028 + function detectPlatform() {
2029 + if (typeof navigator === "undefined") {
2030 + return "other";
2031 + }
2032 + const platform = navigator.userAgentData?.platform || navigator.userAgent || "";
2033 + if (/iPhone|iPad|iPod|iOS/i.test(platform)) {
2034 + return "other";
2035 + }
2036 + if (/macOS|Mac OS X|Macintosh/i.test(platform)) {
2037 + return "macos";
2038 + }
2039 + if (/Windows/i.test(platform)) {
2040 + return "windows";
2041 + }
2042 + if (/Android|CrOS|Chrome OS/i.test(platform)) {
2043 + return "other";
2044 + }
2045 + if (/Linux|X11/i.test(platform)) {
2046 + return "linux";
2047 + }
2048 + return "other";
2049 + }
2050 + function getBrowserName() {
2051 + if (detectBrowserFamily() === "chromium") {
2052 + const brands = navigator.userAgentData?.brands ?? [];
2053 + const userAgent = navigator.userAgent || "";
2054 + if (brands.some(({ brand }) => /Edge/i.test(brand)) || /Edg\//i.test(userAgent)) {
2055 + return "Edge";
2056 + }
2057 + if (brands.some(({ brand }) => /Opera/i.test(brand)) || /OPR\//i.test(userAgent)) {
2058 + return "Opera";
2059 + }
2060 + return "Chrome";
2061 + }
2062 + switch (detectBrowserFamily()) {
2063 + case "firefox":
2064 + return "Firefox";
2065 + case "safari":
2066 + return "Safari";
2067 + default:
2068 + return null;
2069 + }
2070 + }
2071 + function getHeicConversionAdvice() {
2072 + const platform = detectPlatform();
2073 + const family = detectBrowserFamily();
2074 + if (platform === "macos") {
2075 + if (family === "chromium") {
2076 + return (0, import_i18n6.__)(
2077 + "Safari usually can, or you can upload a JPEG instead."
2078 + );
2079 + }
2080 + if (family !== "safari") {
2081 + return (0, import_i18n6.__)(
2082 + "Safari or Chrome usually can, or you can upload a JPEG instead."
2083 + );
2084 + }
2085 + }
2086 + if (platform === "windows" && family === "firefox") {
2087 + return (0, import_i18n6.__)("Chrome or Edge might, or you can upload a JPEG instead.");
2088 + }
2089 + return (0, import_i18n6.__)("You can upload a JPEG instead.");
2090 + }
2091 + function getHeicUnsupportedMessage() {
2092 + const platform = detectPlatform();
2093 + const family = detectBrowserFamily();
2094 + const browserName = getBrowserName();
2095 + let cause;
2096 + if (platform === "linux") {
2097 + cause = (0, import_i18n6.__)(
2098 + "HEIC photos can't be decoded on Linux, so unfortunately we couldn't convert this one."
2099 + );
2100 + } else if (family === "firefox") {
2101 + cause = (0, import_i18n6.__)(
2102 + "Firefox can't read HEIC photos, so we couldn't convert this one."
2103 + );
2104 + } else if (browserName && platform === "windows") {
2105 + cause = (0, import_i18n6.sprintf)(
2106 + /* translators: %s: browser name, e.g. "Chrome". */
2107 + (0, import_i18n6.__)(
2108 + "%s couldn't decode HEIC on this PC, so we couldn't convert this one."
2109 + ),
2110 + browserName
2111 + );
2112 + } else if (browserName && platform === "macos") {
2113 + cause = (0, import_i18n6.sprintf)(
2114 + /* translators: %s: browser name, e.g. "Safari". */
2115 + (0, import_i18n6.__)(
2116 + "%s couldn't decode HEIC on this Mac, so we couldn't convert this one."
2117 + ),
2118 + browserName
2119 + );
2120 + } else if (browserName) {
2121 + cause = (0, import_i18n6.sprintf)(
2122 + /* translators: %s: browser name, e.g. "Chrome". */
2123 + (0, import_i18n6.__)(
2124 + "%s couldn't decode HEIC on this device, so we couldn't convert this one."
2125 + ),
2126 + browserName
2127 + );
2128 + } else {
2129 + cause = (0, import_i18n6.__)(
2130 + "This browser can't read HEIC photos, so we couldn't convert this one."
2131 + );
2132 + }
2133 + return `${cause} ${getHeicConversionAdvice()}`;
2134 + }
2135 +
2136 + // packages/upload-media/build-module/canvas-utils.mjs
2137 + var IMAGE_DECODER_TIMEOUT = 15e3;
2138 + var HeicUnsupportedError = class extends Error {
2139 + };
2140 + async function canvasConvertToJpeg(file, quality = 0.82) {
2141 + const baseName = getFileBasename(file.name);
2142 + try {
2143 + const bitmap = await createImageBitmap(file);
2144 + try {
2145 + const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
2146 + const ctx = canvas.getContext("2d");
2147 + if (!ctx) {
2148 + throw new Error("Could not get canvas 2d context");
2149 + }
2150 + ctx.drawImage(bitmap, 0, 0);
2151 + const jpegBlob = await canvas.convertToBlob({
2152 + type: "image/jpeg",
2153 + quality
2154 + });
2155 + return new File([jpegBlob], `${baseName}.jpg`, {
2156 + type: "image/jpeg"
2157 + });
2158 + } finally {
2159 + bitmap.close();
2160 + }
2161 + } catch {
2162 + }
2163 + if (typeof ImageDecoder !== "undefined") {
2164 + const supported = await ImageDecoder.isTypeSupported(file.type);
2165 + if (supported) {
2166 + const decoder = new ImageDecoder({
2167 + type: file.type,
2168 + data: file.stream()
2169 + });
2170 + let videoFrame;
2171 + let timeoutId;
2172 + let timedOut = false;
2173 + try {
2174 + const decoded = await Promise.race([
2175 + decoder.decode(),
2176 + new Promise((_resolve, reject) => {
2177 + timeoutId = setTimeout(() => {
2178 + timedOut = true;
2179 + decoder.close();
2180 + reject(
2181 + new Error("ImageDecoder decode timed out")
2182 + );
2183 + }, IMAGE_DECODER_TIMEOUT);
2184 + })
2185 + ]);
2186 + videoFrame = decoded.image;
2187 + } catch (error) {
2188 + if (!timedOut) {
2189 + throw error;
2190 + }
2191 + } finally {
2192 + clearTimeout(timeoutId);
2193 + if (!timedOut) {
2194 + decoder.close();
2195 + }
2196 + }
2197 + if (videoFrame) {
2198 + try {
2199 + const canvas = new OffscreenCanvas(
2200 + videoFrame.displayWidth,
2201 + videoFrame.displayHeight
2202 + );
2203 + const ctx = canvas.getContext("2d");
2204 + if (!ctx) {
2205 + throw new Error("Could not get canvas 2d context");
2206 + }
2207 + ctx.drawImage(videoFrame, 0, 0);
2208 + const jpegBlob = await canvas.convertToBlob({
2209 + type: "image/jpeg",
2210 + quality
2211 + });
2212 + return new File([jpegBlob], `${baseName}.jpg`, {
2213 + type: "image/jpeg"
2214 + });
2215 + } finally {
2216 + videoFrame.close();
2217 + }
2218 + }
2219 + }
2220 + }
2221 + let heicData;
2222 + try {
2223 + heicData = parseHeic(await file.arrayBuffer());
2224 + } catch (error) {
2225 + throw new Error("The HEIC file could not be parsed", {
2226 + cause: error
2227 + });
2228 + }
2229 + if (typeof VideoDecoder !== "undefined") {
2230 + let supported = false;
2231 + try {
2232 + const support = await VideoDecoder.isConfigSupported({
2233 + codec: heicData.codecString
2234 + });
2235 + supported = support.supported === true;
2236 + } catch {
2237 + }
2238 + if (supported) {
2239 + const canvas = new OffscreenCanvas(
2240 + heicData.outputWidth,
2241 + heicData.outputHeight
2242 + );
2243 + const ctx = canvas.getContext("2d");
2244 + if (!ctx) {
2245 + throw new Error("Could not get canvas 2d context");
2246 + }
2247 + for (const tile of heicData.tiles) {
2248 + const frame = await decodeHevcFrame(
2249 + heicData.codecString,
2250 + heicData.description,
2251 + heicData.tileWidth,
2252 + heicData.tileHeight,
2253 + tile.data
2254 + );
2255 + try {
2256 + ctx.drawImage(frame, tile.x, tile.y);
2257 + } finally {
2258 + frame.close();
2259 + }
2260 + }
2261 + const outputCanvas = heicData.rotation !== 0 ? applyRotation(canvas, heicData.rotation) : applyExifOrientation(canvas, heicData.exifOrientation);
2262 + const jpegBlob = await outputCanvas.convertToBlob({
2263 + type: "image/jpeg",
2264 + quality
2265 + });
2266 + return new File([jpegBlob], `${baseName}.jpg`, {
2267 + type: "image/jpeg"
2268 + });
2269 + }
2270 + }
2271 + throw new HeicUnsupportedError(getHeicUnsupportedMessage());
2272 + }
2273 + function applyRotation(source, rotation) {
2274 + if (rotation === 0) {
2275 + return source;
2276 + }
2277 + const swap = rotation === 90 || rotation === 270;
2278 + const w = swap ? source.height : source.width;
2279 + const h = swap ? source.width : source.height;
2280 + const rotated = new OffscreenCanvas(w, h);
2281 + const ctx = rotated.getContext("2d");
2282 + if (!ctx) {
2283 + return source;
2284 + }
2285 + ctx.translate(w / 2, h / 2);
2286 + ctx.rotate(-rotation * Math.PI / 180);
2287 + ctx.drawImage(source, -source.width / 2, -source.height / 2);
2288 + return rotated;
2289 + }
2290 + function applyExifOrientation(source, orientation) {
2291 + if (orientation <= 1 || orientation > 8) {
2292 + return source;
2293 + }
2294 + const { width: sw, height: sh } = source;
2295 + const swap = orientation >= 5;
2296 + const out = new OffscreenCanvas(swap ? sh : sw, swap ? sw : sh);
2297 + const ctx = out.getContext("2d");
2298 + if (!ctx) {
2299 + return source;
2300 + }
2301 + switch (orientation) {
2302 + case 2:
2303 + ctx.transform(-1, 0, 0, 1, sw, 0);
2304 + break;
2305 + case 3:
2306 + ctx.transform(-1, 0, 0, -1, sw, sh);
2307 + break;
2308 + case 4:
2309 + ctx.transform(1, 0, 0, -1, 0, sh);
2310 + break;
2311 + case 5:
2312 + ctx.transform(0, 1, 1, 0, 0, 0);
2313 + break;
2314 + case 6:
2315 + ctx.transform(0, 1, -1, 0, sh, 0);
2316 + break;
2317 + case 7:
2318 + ctx.transform(0, -1, -1, 0, sh, sw);
2319 + break;
2320 + case 8:
2321 + ctx.transform(0, -1, 1, 0, 0, sw);
2322 + break;
2323 + }
2324 + ctx.drawImage(source, 0, 0);
2325 + return out;
2326 + }
2327 + function decodeHevcFrame(codec, description, width, height, data) {
2328 + return new Promise((resolve, reject) => {
2329 + const decoder = new VideoDecoder({
2330 + output: (frame) => {
2331 + decoder.close();
2332 + resolve(frame);
2333 + },
2334 + error: (e) => {
2335 + if (decoder.state !== "closed") {
2336 + decoder.close();
2337 + }
2338 + reject(e);
2339 + }
2340 + });
2341 + decoder.configure({
2342 + codec,
2343 + codedWidth: width,
2344 + codedHeight: height,
2345 + description
2346 + });
2347 + decoder.decode(
2348 + new EncodedVideoChunk({
2349 + type: "key",
2350 + timestamp: 0,
2351 + data
2352 + })
2353 + );
2354 + decoder.flush().catch((e) => {
2355 + if (decoder.state !== "closed") {
2356 + decoder.close();
2357 + }
2358 + reject(e);
2359 + });
2360 + });
2361 + }
2362 +
2363 + // packages/upload-media/build-module/feature-detection.mjs
2364 + var cachedResult = null;
2365 + function detectClientSideMediaSupport() {
2366 + if (cachedResult !== null) {
2367 + return cachedResult;
2368 + }
2369 + if (typeof WebAssembly === "undefined") {
2370 + cachedResult = {
2371 + supported: false,
2372 + reason: "WebAssembly is not supported in this browser."
2373 + };
2374 + return cachedResult;
2375 + }
2376 + if (typeof SharedArrayBuffer === "undefined") {
2377 + cachedResult = {
2378 + supported: false,
2379 + reason: "SharedArrayBuffer is not available. This may be due to missing cross-origin isolation headers."
2380 + };
2381 + return cachedResult;
2382 + }
2383 + if (typeof Worker === "undefined") {
2384 + cachedResult = {
2385 + supported: false,
2386 + reason: "Web Workers are not supported in this browser."
2387 + };
2388 + return cachedResult;
2389 + }
2390 + if (typeof navigator !== "undefined" && "deviceMemory" in navigator && navigator.deviceMemory <= 2) {
2391 + cachedResult = {
2392 + supported: false,
2393 + reason: "Device has insufficient memory for client-side media processing."
2394 + };
2395 + return cachedResult;
2396 + }
2397 + if (typeof navigator !== "undefined" && "hardwareConcurrency" in navigator && navigator.hardwareConcurrency < 2) {
2398 + cachedResult = {
2399 + supported: false,
2400 + reason: "Device has insufficient CPU cores for client-side media processing."
2401 + };
2402 + return cachedResult;
2403 + }
2404 + if (typeof navigator !== "undefined") {
2405 + const connection = navigator.connection;
2406 + if (connection) {
2407 + if (connection.saveData) {
2408 + cachedResult = {
2409 + supported: false,
2410 + reason: "Data saver mode is enabled."
2411 + };
2412 + return cachedResult;
2413 + }
2414 + if (connection.effectiveType === "slow-2g" || connection.effectiveType === "2g") {
2415 + cachedResult = {
2416 + supported: false,
2417 + reason: "Network connection is too slow for client-side media processing."
2418 + };
2419 + return cachedResult;
2420 + }
2421 + }
2422 + }
2423 + if (typeof window !== "undefined") {
2424 + try {
2425 + const testBlob = new Blob([""], {
2426 + type: "application/javascript"
2427 + });
2428 + const testUrl = URL.createObjectURL(testBlob);
2429 + try {
2430 + const testWorker = new Worker(testUrl);
2431 + testWorker.terminate();
2432 + } finally {
2433 + URL.revokeObjectURL(testUrl);
2434 + }
2435 + } catch {
2436 + cachedResult = {
2437 + supported: false,
2438 + reason: "The site's Content Security Policy (CSP) does not allow blob: workers. The worker-src directive must include blob: to enable client-side media processing."
2439 + };
2440 + return cachedResult;
2441 + }
2442 + }
2443 + cachedResult = { supported: true };
2444 + return cachedResult;
2445 + }
2446 + function isClientSideMediaSupported() {
2447 + return detectClientSideMediaSupport().supported;
2448 + }
2449 + function isHeicCanvasSupported() {
2450 + return typeof createImageBitmap !== "undefined" && typeof OffscreenCanvas !== "undefined";
2451 + }
2452 + function clearFeatureDetectionCache() {
2453 + cachedResult = null;
2454 + }
2455 + var BYTES_PER_PIXEL = 4;
2456 + var INTERLACED_MEMORY_BUDGET = 0.5 * 1024 * 1024 * 1024;
2457 + var BASELINE_MEMORY_BUDGET = 0.9 * 1024 * 1024 * 1024;
2458 + function exceedsClientProcessingMemory(dimensions) {
2459 + const { width, height, interlaced } = dimensions;
2460 + const estimatedBytes = width * height * BYTES_PER_PIXEL;
2461 + const budget = interlaced ? INTERLACED_MEMORY_BUDGET : BASELINE_MEMORY_BUDGET;
2462 + return estimatedBytes > budget;
2463 + }
2464 +
2465 + // packages/upload-media/build-module/get-image-dimensions.mjs
2466 + var MAX_HEADER_BYTES = 512 * 1024;
2467 + function parseJpeg(view) {
2468 + let offset = 2;
2469 + while (offset < view.byteLength) {
2470 + if (view.getUint8(offset) !== 255) {
2471 + return null;
2472 + }
2473 + while (offset < view.byteLength && view.getUint8(offset) === 255) {
2474 + offset++;
2475 + }
2476 + if (offset >= view.byteLength) {
2477 + return null;
2478 + }
2479 + const marker = view.getUint8(offset);
2480 + offset++;
2481 + if (marker === 1 || marker >= 208 && marker <= 217) {
2482 + continue;
2483 + }
2484 + if (marker === 218) {
2485 + return null;
2486 + }
2487 + if (offset + 2 > view.byteLength) {
2488 + return null;
2489 + }
2490 + const segmentLength = view.getUint16(offset);
2491 + if (segmentLength < 2) {
2492 + return null;
2493 + }
2494 + const isStartOfFrame = marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204;
2495 + if (isStartOfFrame) {
2496 + if (offset + 7 > view.byteLength) {
2497 + return null;
2498 + }
2499 + const height = view.getUint16(offset + 3);
2500 + const width = view.getUint16(offset + 5);
2501 + const interlaced = marker === 194 || marker === 198 || marker === 202 || marker === 206;
2502 + return { width, height, interlaced };
2503 + }
2504 + offset += segmentLength;
2505 + }
2506 + return null;
2507 + }
2508 + function parsePng(view) {
2509 + if (view.byteLength < 29) {
2510 + return null;
2511 + }
2512 + const isIhdr = view.getUint8(12) === 73 && // I
2513 + view.getUint8(13) === 72 && // H
2514 + view.getUint8(14) === 68 && // D
2515 + view.getUint8(15) === 82;
2516 + if (!isIhdr) {
2517 + return null;
2518 + }
2519 + const width = view.getUint32(16);
2520 + const height = view.getUint32(20);
2521 + const interlaceMethod = view.getUint8(28);
2522 + return { width, height, interlaced: interlaceMethod !== 0 };
2523 + }
2524 + async function getImageDimensions(file) {
2525 + try {
2526 + const headerBytes = Math.min(file.size, MAX_HEADER_BYTES);
2527 + const buffer = await file.slice(0, headerBytes).arrayBuffer();
2528 + const view = new DataView(buffer);
2529 + if (view.byteLength >= 3 && view.getUint16(0) === 65496) {
2530 + return parseJpeg(view);
2531 + }
2532 + if (view.byteLength >= 8 && view.getUint32(0) === 2303741511 && view.getUint32(4) === 218765834) {
2533 + return parsePng(view);
2534 + }
2535 + return null;
2536 + } catch {
2537 + return null;
2538 + }
2539 + }
2540 +
950 2541 // packages/upload-media/build-module/stub-file.mjs
951 2542 var StubFile = class extends File {
952 2543 constructor(fileName = "stub-file") {
953 2544 super([], fileName);
@@ -955,14 +2546,9 @@
955 2546 };
956 2547
957 2548 // packages/upload-media/build-module/store/private-actions.mjs
958 2549 var DEFAULT_OUTPUT_QUALITY = 0.82;
959 - function shouldPauseForSideload(item, operation, select2) {
960 - if (operation !== OperationType.Upload || !item.parentId || !item.additionalData.post) {
961 - return false;
962 - }
963 - return select2.isUploadingToPost(item.additionalData.post);
964 - }
2550 + var ultraHdrItems = /* @__PURE__ */ new Set();
965 2551 function addItem({
966 2552 file: fileOrBlob,
967 2553 batchId,
968 2554 onChange,
@@ -998,9 +2584,8 @@
998 2584 attachment: {
999 2585 url: blobUrl
1000 2586 },
1001 2587 additionalData: {
1002 - convert_format: false,
1003 2588 generate_sub_sizes: false,
1004 2589 ...additionalData
1005 2590 },
1006 2591 onChange,
@@ -1054,8 +2639,11 @@
1054 2639 const item = select2.getItem(id);
1055 2640 if (!item) {
1056 2641 return;
1057 2642 }
2643 + if (item.currentOperation) {
2644 + return;
2645 + }
1058 2646 const {
1059 2647 attachment,
1060 2648 onChange,
1061 2649 onSuccess,
@@ -1064,15 +2652,8 @@
1064 2652 parentId
1065 2653 } = item;
1066 2654 const operation = Array.isArray(item.operations?.[0]) ? item.operations[0][0] : item.operations?.[0];
1067 2655 const operationArgs = Array.isArray(item.operations?.[0]) ? item.operations[0][1] : void 0;
1068 - if (shouldPauseForSideload(item, operation, select2)) {
1069 - dispatch({
1070 - type: Type.PauseItem,
1071 - id
1072 - });
1073 - return;
1074 - }
1075 2656 if (operation === OperationType.Upload) {
1076 2657 const settings = select2.getSettings();
1077 2658 const activeCount = select2.getActiveUploadCount();
1078 2659 if (activeCount >= settings.maxConcurrentUploads) {
@@ -1085,10 +2666,19 @@
1085 2666 if (activeCount >= settings.maxConcurrentImageProcessing) {
1086 2667 return;
1087 2668 }
1088 2669 }
2670 + if (operation === OperationType.TranscodeGif) {
2671 + const activeCount = select2.getActiveVideoProcessingCount();
2672 + if (activeCount >= 1) {
2673 + return;
2674 + }
2675 + }
1089 2676 if (attachment) {
1090 - onChange?.([attachment]);
2677 + const isHeicUrl = attachment.url && /\.hei[cf]$/i.test(attachment.url);
2678 + if (!isHeicUrl) {
2679 + onChange?.([attachment]);
2680 + }
1091 2681 }
1092 2682 if (!operation) {
1093 2683 if (parentId || !parentId && !select2.hasPendingItemsByParentId(id)) {
1094 2684 if (attachment) {
@@ -1127,8 +2717,11 @@
1127 2717 type: Type.OperationStart,
1128 2718 id,
1129 2719 operation
1130 2720 });
2721 + debug(
2722 + `Starting operation ${operation} for ${item.file.name} (item ${item.id})`
2723 + );
1131 2724 switch (operation) {
1132 2725 case OperationType.Prepare:
1133 2726 dispatch.prepareItem(item.id);
1134 2727 break;
@@ -1149,8 +2742,14 @@
1149 2742 item.id,
1150 2743 operationArgs
1151 2744 );
1152 2745 break;
2746 + case OperationType.TranscodeGif:
2747 + dispatch.transcodeGifItem(
2748 + item.id,
2749 + operationArgs
2750 + );
2751 + break;
1153 2752 case OperationType.Upload:
1154 2753 if (item.parentId) {
1155 2754 dispatch.sideloadItem(id);
1156 2755 } else {
@@ -1162,8 +2761,11 @@
1162 2761 break;
1163 2762 case OperationType.Finalize:
1164 2763 dispatch.finalizeItem(id);
1165 2764 break;
2765 + case OperationType.DetectUltraHdr:
2766 + dispatch.detectUltraHdr(id);
2767 + break;
1166 2768 }
1167 2769 };
1168 2770 }
1169 2771 function pauseQueue() {
@@ -1176,9 +2778,13 @@
1176 2778 dispatch({
1177 2779 type: Type.ResumeQueue
1178 2780 });
1179 2781 for (const item of select2.getAllItems()) {
1180 - dispatch.processItem(item.id);
2782 + if (item.status === ItemStatus.PendingRetry) {
2783 + dispatch.executeRetry(item.id);
2784 + } else {
2785 + dispatch.processItem(item.id);
2786 + }
1181 2787 }
1182 2788 };
1183 2789 }
1184 2790 function pauseItem(id) {
@@ -1188,20 +2794,8 @@
1188 2794 id
1189 2795 });
1190 2796 };
1191 2797 }
1192 - function resumeItemByPostId(postOrAttachmentId) {
1193 - return async ({ select: select2, dispatch }) => {
1194 - const item = select2.getPausedUploadForPost(postOrAttachmentId);
1195 - if (item) {
1196 - dispatch({
1197 - type: Type.ResumeItem,
1198 - id: item.id
1199 - });
1200 - dispatch.processItem(item.id);
1201 - }
1202 - };
1203 - }
1204 2798 function removeItem(id) {
1205 2799 return async ({ select: select2, dispatch }) => {
1206 2800 const item = select2.getItem(id);
1207 2801 if (!item) {
@@ -1206,8 +2800,10 @@
1206 2800 const item = select2.getItem(id);
1207 2801 if (!item) {
1208 2802 return;
1209 2803 }
2804 + ultraHdrItems.delete(id);
2805 + clearRetryTimer(id);
1210 2806 dispatch({
1211 2807 type: Type.Remove,
1212 2808 id
1213 2809 });
@@ -1212,8 +2808,9 @@
1212 2808 id
1213 2809 });
1214 2810 if (select2.getAllItems().length === 0) {
1215 2811 terminateVipsWorker();
2812 + terminateVideoConversionWorker();
1216 2813 }
1217 2814 };
1218 2815 }
1219 2816 function finishOperation(id, updates) {
@@ -1237,8 +2834,17 @@
1237 2834 for (const pendingItem of pendingItems) {
1238 2835 dispatch.processItem(pendingItem.id);
1239 2836 }
1240 2837 }
2838 + if (previousOperation === OperationType.TranscodeGif) {
2839 + const pendingItems = select2.getPendingVideoProcessing();
2840 + for (const pendingItem of pendingItems) {
2841 + dispatch.processItem(pendingItem.id);
2842 + }
2843 + }
2844 + if (previousOperation === OperationType.ResizeCrop || previousOperation === OperationType.Rotate || previousOperation === OperationType.TranscodeImage) {
2845 + maybeRecycleVipsWorker(select2.getActiveImageProcessingCount());
2846 + }
1241 2847 };
1242 2848 }
1243 2849 var VALID_IMAGE_FORMATS = ["jpeg", "webp", "avif", "png", "gif"];
1244 2850 function isValidImageFormat(format) {
@@ -1243,21 +2849,9 @@
1243 2849 var VALID_IMAGE_FORMATS = ["jpeg", "webp", "avif", "png", "gif"];
1244 2850 function isValidImageFormat(format) {
1245 2851 return VALID_IMAGE_FORMATS.includes(format);
1246 2852 }
1247 - function getInterlacedSetting(outputMimeType, settings) {
1248 - switch (outputMimeType) {
1249 - case "image/jpeg":
1250 - return settings.jpegInterlaced ?? false;
1251 - case "image/png":
1252 - return settings.pngInterlaced ?? false;
1253 - case "image/gif":
1254 - return settings.gifInterlaced ?? false;
1255 - default:
1256 - return false;
1257 - }
1258 - }
1259 - async function getTranscodeImageOperation(file, outputMimeType, settings) {
2853 + async function getTranscodeImageOperation(file, outputMimeType, interlaced = false, quality = DEFAULT_OUTPUT_QUALITY) {
1260 2854 if (file.type === "image/png" && outputMimeType === "image/jpeg") {
1261 2855 const blobUrl = (0, import_blob.createBlobURL)(file);
1262 2856 try {
1263 2857 const hasAlpha = await vipsHasTransparency(blobUrl);
@@ -1277,10 +2871,10 @@
1277 2871 return [
1278 2872 OperationType.TranscodeImage,
1279 2873 {
1280 2874 outputFormat: formatPart,
1281 - outputQuality: DEFAULT_OUTPUT_QUALITY,
1282 - interlaced: getInterlacedSetting(outputMimeType, settings)
2875 + outputQuality: quality,
2876 + interlaced
1283 2877 }
1284 2878 ];
1285 2879 }
1286 2880 function prepareItem(id) {
@@ -1291,24 +2885,88 @@
1291 2885 }
1292 2886 const { file } = item;
1293 2887 const operations = [];
1294 2888 const settings = select2.getSettings();
1295 - const isImage = file.type.startsWith("image/");
1296 - const isVipsSupported = CLIENT_SIDE_SUPPORTED_MIME_TYPES.includes(
1297 - file.type
1298 - );
2889 + if (file.type === "image/gif" && settings.gifConvert !== false && typeof ImageDecoder !== "undefined" && typeof VideoEncoder !== "undefined") {
2890 + let isAnimated = false;
2891 + try {
2892 + isAnimated = isAnimatedGif(await file.arrayBuffer());
2893 + } catch {
2894 + isAnimated = false;
2895 + }
2896 + if (isAnimated) {
2897 + let hasTransparency = false;
2898 + const blobUrl = (0, import_blob.createBlobURL)(file);
2899 + try {
2900 + hasTransparency = await vipsHasTransparency(blobUrl);
2901 + } catch {
2902 + hasTransparency = true;
2903 + } finally {
2904 + (0, import_blob.revokeBlobURL)(blobUrl);
2905 + }
2906 + if (!hasTransparency) {
2907 + operations.push(
2908 + OperationType.Upload,
2909 + OperationType.ThumbnailGeneration,
2910 + OperationType.Finalize
2911 + );
2912 + dispatch({
2913 + type: Type.AddOperations,
2914 + id,
2915 + operations
2916 + });
2917 + dispatch.finishOperation(id, {
2918 + animatedGifFile: item.file
2919 + });
2920 + return;
2921 + }
2922 + }
2923 + }
2924 + let heicJpeg = null;
2925 + const isHeic = await isHeicFile(file);
2926 + const isMisnamedHeic = isHeic && !HEIC_MIME_TYPES.includes(file.type);
2927 + const isImage = file.type.startsWith("image/") || isHeic;
2928 + const isVipsSupported = !isHeic && CLIENT_SIDE_SUPPORTED_MIME_TYPES.includes(file.type);
2929 + const heicFile = isMisnamedHeic ? new File([file], `${getFileBasename(file.name)}.heic`, {
2930 + type: HEIC_MIME_TYPES[0],
2931 + lastModified: file.lastModified
2932 + }) : file;
2933 + let tooLargeForClient = false;
1299 2934 if (isImage && isVipsSupported) {
1300 - const { imageOutputFormats } = settings;
1301 - const outputMimeType = imageOutputFormats?.[file.type];
1302 - if (outputMimeType && outputMimeType !== file.type) {
1303 - const transcodeOperation = await getTranscodeImageOperation(
1304 - file,
1305 - outputMimeType,
1306 - settings
2935 + const dimensions = await getImageDimensions(file);
2936 + if (dimensions && exceedsClientProcessingMemory(dimensions)) {
2937 + tooLargeForClient = true;
2938 + }
2939 + }
2940 + if (file.type === "image/jpeg" && !isHeic && !tooLargeForClient) {
2941 + operations.push(OperationType.DetectUltraHdr);
2942 + }
2943 + if (isImage && isVipsSupported && !tooLargeForClient) {
2944 + operations.push(
2945 + OperationType.Upload,
2946 + OperationType.ThumbnailGeneration,
2947 + OperationType.Finalize
2948 + );
2949 + } else if (isImage && isHeic) {
2950 + try {
2951 + heicJpeg = await canvasConvertToJpeg(
2952 + heicFile,
2953 + settings.imageQuality ?? DEFAULT_OUTPUT_QUALITY
1307 2954 );
1308 - if (transcodeOperation) {
1309 - operations.push(transcodeOperation);
1310 - }
2955 + } catch (error) {
2956 + const unsupported = error instanceof HeicUnsupportedError;
2957 + dispatch.cancelItem(
2958 + id,
2959 + new UploadError({
2960 + code: unsupported ? ErrorCode.HEIC_DECODE_ERROR : ErrorCode.IMAGE_TRANSCODING_ERROR,
2961 + message: unsupported ? getHeicUnsupportedMessage() : (0, import_i18n7.__)(
2962 + "This HEIC image could not be converted. Try converting it to JPEG before uploading."
2963 + ),
2964 + file,
2965 + cause: error instanceof Error ? error : void 0
2966 + })
2967 + );
2968 + return;
1311 2969 }
1312 2970 operations.push(
1313 2971 OperationType.Upload,
1314 2972 OperationType.ThumbnailGeneration,
@@ -1321,18 +2979,58 @@
1321 2979 type: Type.AddOperations,
1322 2980 id,
1323 2981 operations
1324 2982 });
1325 - const updates = !isVipsSupported || !isImage ? {
1326 - additionalData: {
1327 - ...item.additionalData,
1328 - generate_sub_sizes: true,
1329 - convert_format: true
1330 - }
1331 - } : {};
2983 + let updates;
2984 + if (isHeic && heicJpeg) {
2985 + const vipsAvailable = isClientSideMediaSupported();
2986 + updates = {
2987 + file: heicJpeg,
2988 + sourceFile: heicJpeg,
2989 + originalHeicFile: heicFile,
2990 + additionalData: {
2991 + ...item.additionalData,
2992 + generate_sub_sizes: !vipsAvailable,
2993 + convert_format: true
2994 + }
2995 + };
2996 + } else if (!isVipsSupported || !isImage || tooLargeForClient) {
2997 + updates = {
2998 + additionalData: {
2999 + ...item.additionalData,
3000 + generate_sub_sizes: true,
3001 + convert_format: true
3002 + }
3003 + };
3004 + } else {
3005 + updates = {
3006 + additionalData: {
3007 + ...item.additionalData,
3008 + generate_sub_sizes: false
3009 + }
3010 + };
3011 + }
1332 3012 dispatch.finishOperation(id, updates);
1333 3013 };
1334 3014 }
3015 + function detectUltraHdr(id) {
3016 + return async ({ select: select2, dispatch }) => {
3017 + const item = select2.getItem(id);
3018 + if (!item) {
3019 + return;
3020 + }
3021 + let info;
3022 + try {
3023 + const buffer = await item.file.arrayBuffer();
3024 + info = await vipsGetUltraHdrInfo(buffer);
3025 + } catch {
3026 + }
3027 + if (info) {
3028 + ultraHdrItems.add(id);
3029 + }
3030 + dispatch.finishOperation(id, {});
3031 + };
3032 + }
1335 3033 function uploadItem(id) {
1336 3034 return async ({ select: select2, dispatch }) => {
1337 3035 const item = select2.getItem(id);
1338 3036 if (!item) {
@@ -1337,23 +3035,42 @@
1337 3035 const item = select2.getItem(id);
1338 3036 if (!item) {
1339 3037 return;
1340 3038 }
3039 + const startTime = performance.now();
3040 + let finished = false;
3041 + const finishUpload = (attachment) => {
3042 + if (finished) {
3043 + return;
3044 + }
3045 + finished = true;
3046 + measure({
3047 + measureName: `Upload ${item.file.name}`,
3048 + startTime,
3049 + tooltipText: item.file.name,
3050 + properties: [
3051 + ["Item ID", item.id],
3052 + ["File name", item.file.name]
3053 + ]
3054 + });
3055 + dispatch.finishOperation(id, { attachment });
3056 + };
1341 3057 select2.getSettings().mediaUpload({
1342 3058 filesList: [item.file],
1343 3059 additionalData: item.additionalData,
1344 3060 signal: item.abortController?.signal,
3061 + // The queue's own items drive upload progress UI and save
3062 + // locking; without this, consumers that track uploads themselves
3063 + // (e.g. the editor's progress snackbar) would count this file a
3064 + // second time.
3065 + isTransportOnly: true,
1345 3066 onFileChange: ([attachment]) => {
1346 3067 if (attachment && !(0, import_blob.isBlobURL)(attachment.url)) {
1347 - dispatch.finishOperation(id, {
1348 - attachment
1349 - });
3068 + finishUpload(attachment);
1350 3069 }
1351 3070 },
1352 3071 onSuccess: ([attachment]) => {
1353 - dispatch.finishOperation(id, {
1354 - attachment
1355 - });
3072 + finishUpload(attachment);
1356 3073 },
1357 3074 onError: (error) => {
1358 3075 dispatch.cancelItem(id, error);
1359 3076 }
@@ -1371,20 +3088,35 @@
1371 3088 if (!mediaSideload) {
1372 3089 dispatch.finishOperation(id, {});
1373 3090 return;
1374 3091 }
3092 + const startTime = performance.now();
1375 3093 mediaSideload({
1376 3094 file: item.file,
1377 3095 attachmentId: post,
1378 3096 additionalData,
1379 3097 signal: item.abortController?.signal,
1380 - onFileChange: ([attachment]) => {
1381 - dispatch.finishOperation(id, { attachment });
1382 - dispatch.resumeItemByPostId(post);
3098 + onSuccess: (subSize) => {
3099 + measure({
3100 + measureName: `Sideload ${item.file.name}`,
3101 + startTime,
3102 + tooltipText: item.file.name,
3103 + properties: [
3104 + ["Item ID", item.id],
3105 + ["File name", item.file.name]
3106 + ]
3107 + });
3108 + if (item.parentId) {
3109 + dispatch({
3110 + type: Type.AccumulateSubSize,
3111 + id: item.parentId,
3112 + subSize
3113 + });
3114 + }
3115 + dispatch.finishOperation(id, {});
1383 3116 },
1384 3117 onError: (error) => {
1385 3118 dispatch.cancelItem(id, error);
1386 - dispatch.resumeItemByPostId(post);
1387 3119 }
1388 3120 });
1389 3121 };
1390 3122 }
@@ -1399,21 +3131,36 @@
1399 3131 file: item.file
1400 3132 });
1401 3133 return;
1402 3134 }
3135 + const startTime = performance.now();
1403 3136 const addSuffix = Boolean(item.parentId);
1404 3137 const scaledSuffix = Boolean(args.isThresholdResize);
3138 + const { imageStripMeta, imageMaxBitDepth } = select2.getSettings();
1405 3139 try {
1406 3140 const file = await vipsResizeImage(
1407 3141 item.id,
1408 3142 item.file,
1409 3143 args.resize,
1410 - false,
1411 - // smartCrop
1412 - addSuffix,
1413 - item.abortController?.signal,
1414 - scaledSuffix
3144 + {
3145 + smartCrop: false,
3146 + addSuffix,
3147 + signal: item.abortController?.signal,
3148 + scaledSuffix,
3149 + quality: args.quality,
3150 + stripMeta: imageStripMeta,
3151 + maxBitdepth: imageMaxBitDepth
3152 + }
1415 3153 );
3154 + measure({
3155 + measureName: `ResizeCrop ${item.file.name}`,
3156 + startTime,
3157 + tooltipText: item.file.name,
3158 + properties: [
3159 + ["Item ID", item.id],
3160 + ["File name", item.file.name]
3161 + ]
3162 + });
1416 3163 const blobUrl = (0, import_blob.createBlobURL)(file);
1417 3164 dispatch({
1418 3165 type: Type.CacheBlobUrl,
1419 3166 id,
@@ -1428,10 +3175,12 @@
1428 3175 } catch (error) {
1429 3176 dispatch.cancelItem(
1430 3177 id,
1431 3178 new UploadError({
1432 - code: "IMAGE_TRANSCODING_ERROR",
1433 - message: "File could not be uploaded",
3179 + code: ErrorCode.IMAGE_TRANSCODING_ERROR,
3180 + message: (0, import_i18n7.__)(
3181 + "The web server cannot generate responsive image sizes for this image. Convert it to JPEG or PNG before uploading."
3182 + ),
1434 3183 file: item.file,
1435 3184 cause: error instanceof Error ? error : void 0
1436 3185 })
1437 3186 );
@@ -1449,8 +3198,9 @@
1449 3198 file: item.file
1450 3199 });
1451 3200 return;
1452 3201 }
3202 + const startTime = performance.now();
1453 3203 try {
1454 3204 const file = await vipsRotateImage(
1455 3205 item.id,
1456 3206 item.file,
@@ -1456,8 +3206,17 @@
1456 3206 item.file,
1457 3207 args.orientation,
1458 3208 item.abortController?.signal
1459 3209 );
3210 + measure({
3211 + measureName: `Rotate ${item.file.name}`,
3212 + startTime,
3213 + tooltipText: item.file.name,
3214 + properties: [
3215 + ["Item ID", item.id],
3216 + ["File name", item.file.name]
3217 + ]
3218 + });
1460 3219 const blobUrl = (0, import_blob.createBlobURL)(file);
1461 3220 dispatch({
1462 3221 type: Type.CacheBlobUrl,
1463 3222 id,
@@ -1472,10 +3231,12 @@
1472 3231 } catch (error) {
1473 3232 dispatch.cancelItem(
1474 3233 id,
1475 3234 new UploadError({
1476 - code: "IMAGE_ROTATION_ERROR",
1477 - message: "Image could not be rotated",
3235 + code: ErrorCode.IMAGE_ROTATION_ERROR,
3236 + message: (0, import_i18n7.__)(
3237 + "The web server cannot generate responsive image sizes for this image. Convert it to JPEG or PNG before uploading."
3238 + ),
1478 3239 file: item.file,
1479 3240 cause: error instanceof Error ? error : void 0
1480 3241 })
1481 3242 );
@@ -1493,19 +3254,34 @@
1493 3254 file: item.file
1494 3255 });
1495 3256 return;
1496 3257 }
3258 + const startTime = performance.now();
1497 3259 const outputMimeType = `image/${args.outputFormat}`;
1498 3260 const quality = args.outputQuality ?? DEFAULT_OUTPUT_QUALITY;
1499 3261 const interlaced = args.interlaced ?? false;
3262 + const { imageStripMeta, imageMaxBitDepth } = select2.getSettings();
1500 3263 try {
1501 3264 const file = await vipsConvertImageFormat(
1502 3265 item.id,
1503 3266 item.file,
1504 3267 outputMimeType,
1505 - quality,
1506 - interlaced
3268 + {
3269 + quality,
3270 + interlaced,
3271 + stripMeta: imageStripMeta,
3272 + maxBitdepth: imageMaxBitDepth
3273 + }
1507 3274 );
3275 + measure({
3276 + measureName: `Transcode ${item.file.name}`,
3277 + startTime,
3278 + tooltipText: item.file.name,
3279 + properties: [
3280 + ["Item ID", item.id],
3281 + ["File name", item.file.name]
3282 + ]
3283 + });
1508 3284 const blobUrl = (0, import_blob.createBlobURL)(file);
1509 3285 dispatch({
1510 3286 type: Type.CacheBlobUrl,
1511 3287 id,
@@ -1520,9 +3296,9 @@
1520 3296 } catch (error) {
1521 3297 dispatch.cancelItem(
1522 3298 id,
1523 3299 new UploadError({
1524 - code: "MEDIA_TRANSCODING_ERROR",
3300 + code: ErrorCode.MEDIA_TRANSCODING_ERROR,
1525 3301 message: "Image could not be transcoded to the target format",
1526 3302 file: item.file,
1527 3303 cause: error instanceof Error ? error : void 0
1528 3304 })
@@ -1529,8 +3305,91 @@
1529 3305 );
1530 3306 }
1531 3307 };
1532 3308 }
3309 + function transcodeGifItem(id, args) {
3310 + return async ({ select: select2, dispatch }) => {
3311 + const item = select2.getItem(id);
3312 + if (!item) {
3313 + return;
3314 + }
3315 + const outputFormat = args?.outputFormat ?? "mp4";
3316 + const outputMimeType = `video/${outputFormat}`;
3317 + const gifFile = item.file;
3318 + try {
3319 + const file = await convertGifToVideo(
3320 + item.id,
3321 + gifFile,
3322 + outputMimeType,
3323 + {
3324 + timeout: args?.timeout,
3325 + maxTotalPixels: args?.maxTotalPixels
3326 + }
3327 + );
3328 + dispatch.finishOperation(id, { file });
3329 + dispatch.addSideloadItem({
3330 + file: gifFile,
3331 + batchId: v4_default(),
3332 + parentId: item.parentId,
3333 + additionalData: {
3334 + post: item.additionalData?.post,
3335 + image_size: "animated_video_poster",
3336 + convert_format: false
3337 + },
3338 + operations: [
3339 + [
3340 + OperationType.TranscodeImage,
3341 + {
3342 + outputFormat: "jpeg",
3343 + outputQuality: DEFAULT_OUTPUT_QUALITY,
3344 + interlaced: false
3345 + }
3346 + ],
3347 + OperationType.Upload
3348 + ]
3349 + });
3350 + } catch (error) {
3351 + if (isUnsupportedConversionError(error)) {
3352 + if (isSizeLimitConversionError(error)) {
3353 + debug(
3354 + `Skipping GIF to video conversion: ${error instanceof Error ? error.message : error}`
3355 + );
3356 + }
3357 + dispatch.cancelItem(
3358 + id,
3359 + new Error("Animated GIF conversion unsupported"),
3360 + true
3361 + );
3362 + return;
3363 + }
3364 + if (isConversionTimeoutError(error)) {
3365 + debug(
3366 + `GIF to video conversion timed out; keeping the original GIF only: ${error instanceof Error ? error.message : error}`
3367 + );
3368 + dispatch.cancelItem(
3369 + id,
3370 + new Error("Animated GIF conversion timed out"),
3371 + true
3372 + );
3373 + return;
3374 + }
3375 + console.error(
3376 + "[video-conversion] GIF to video conversion failed:",
3377 + error
3378 + );
3379 + dispatch.cancelItem(
3380 + id,
3381 + new UploadError({
3382 + code: ErrorCode.GIF_TRANSCODING_ERROR,
3383 + message: "Animated GIF could not be converted to video",
3384 + file: item.file,
3385 + cause: error instanceof Error ? error : void 0
3386 + }),
3387 + true
3388 + );
3389 + }
3390 + };
3391 + }
1533 3392 function generateThumbnails(id) {
1534 3393 return async ({ select: select2, dispatch }) => {
1535 3394 const item = select2.getItem(id);
1536 3395 if (!item) {
@@ -1540,19 +3399,74 @@
1540 3399 dispatch.finishOperation(id, {});
1541 3400 return;
1542 3401 }
1543 3402 const attachment = item.attachment;
1544 - const needsRotation = attachment.exif_orientation && attachment.exif_orientation !== 1 && !item.file.name.includes("-scaled");
1545 - if (needsRotation && attachment.id) {
1546 - try {
1547 - const rotatedFile = await vipsRotateImage(
1548 - item.id,
1549 - item.sourceFile,
1550 - attachment.exif_orientation,
1551 - item.abortController?.signal
1552 - );
3403 + const settings = select2.getSettings();
3404 + if (item.originalHeicFile && attachment.id) {
3405 + dispatch.addSideloadItem({
3406 + file: item.originalHeicFile,
3407 + batchId: v4_default(),
3408 + parentId: item.id,
3409 + additionalData: {
3410 + post: attachment.id,
3411 + image_size: "source_original",
3412 + convert_format: false
3413 + },
3414 + operations: [OperationType.Upload]
3415 + });
3416 + }
3417 + if (item.animatedGifFile && attachment.id) {
3418 + const outputFormat = settings.videoOutputFormat === "video/webm" ? "webm" : "mp4";
3419 + dispatch.addSideloadItem({
3420 + file: item.animatedGifFile,
3421 + batchId: v4_default(),
3422 + parentId: item.id,
3423 + additionalData: {
3424 + post: attachment.id,
3425 + image_size: "animated_video",
3426 + convert_format: false
3427 + },
3428 + operations: [
3429 + [
3430 + OperationType.TranscodeGif,
3431 + {
3432 + outputFormat
3433 + }
3434 + ],
3435 + OperationType.Upload
3436 + ]
3437 + });
3438 + }
3439 + let exifOrientation = attachment.exif_orientation || 1;
3440 + const sourceType = item.sourceFile.type;
3441 + const isHeifFamily = sourceType === "image/avif" || sourceType === "image/heif";
3442 + let needsClientRotation = false;
3443 + if (isHeifFamily) {
3444 + exifOrientation = getUnappliedExifOrientation(
3445 + await item.sourceFile.arrayBuffer()
3446 + );
3447 + needsClientRotation = exifOrientation !== 1;
3448 + }
3449 + let rotatedSource;
3450 + {
3451 + const needsRotation = exifOrientation !== 1 && !item.file.name.includes("-scaled");
3452 + if ((needsRotation || needsClientRotation) && attachment.id) {
3453 + try {
3454 + rotatedSource = await vipsRotateImage(
3455 + item.id,
3456 + item.sourceFile,
3457 + exifOrientation,
3458 + item.abortController?.signal
3459 + );
3460 + } catch {
3461 + console.warn(
3462 + "Failed to rotate image, continuing with thumbnails"
3463 + );
3464 + }
3465 + }
3466 + if (needsRotation && rotatedSource && attachment.id) {
1553 3467 dispatch.addSideloadItem({
1554 - file: rotatedFile,
3468 + file: rotatedSource,
1555 3469 batchId: v4_default(),
1556 3470 parentId: item.id,
1557 3471 additionalData: {
1558 3472 post: attachment.id,
@@ -1560,34 +3474,39 @@
1560 3474 convert_format: false
1561 3475 },
1562 3476 operations: [OperationType.Upload]
1563 3477 });
1564 - } catch {
1565 - console.warn(
1566 - "Failed to rotate image, continuing with thumbnails"
1567 - );
1568 3478 }
1569 3479 }
1570 3480 if (!item.parentId && attachment.missing_image_sizes && attachment.missing_image_sizes.length > 0) {
1571 - const settings = select2.getSettings();
1572 3481 const allImageSizes = settings.allImageSizes || {};
1573 3482 const sizesToGenerate = attachment.missing_image_sizes;
1574 - const file = attachment.filename ? renameFile(item.sourceFile, attachment.filename) : item.sourceFile;
3483 + const thumbnailSource = needsClientRotation && rotatedSource ? rotatedSource : item.sourceFile;
3484 + const file = attachment.filename ? renameFile(thumbnailSource, attachment.filename) : thumbnailSource;
1575 3485 const batchId = v4_default();
1576 - const { imageOutputFormats } = settings;
1577 - const sourceType = item.sourceFile.type;
1578 - const outputMimeType = imageOutputFormats?.[sourceType];
3486 + const isUltraHdr = ultraHdrItems.has(item.id);
3487 + const outputMimeType = attachment.image_output_format;
3488 + const interlaced = attachment.image_save_progressive ?? false;
3489 + const imageQuality = attachment.image_quality;
3490 + const fallbackQuality = settings.imageQuality ?? DEFAULT_OUTPUT_QUALITY;
3491 + const defaultQuality = typeof imageQuality?.default === "number" ? imageQuality.default / 100 : fallbackQuality;
3492 + const qualityForSize = (sizeName) => {
3493 + const sized = imageQuality?.sizes?.[sizeName];
3494 + if (typeof sized === "number") {
3495 + return sized / 100;
3496 + }
3497 + return defaultQuality;
3498 + };
1579 3499 let thumbnailTranscodeOperation = null;
1580 - if (outputMimeType && outputMimeType !== sourceType) {
3500 + if (!isUltraHdr && outputMimeType) {
1581 3501 thumbnailTranscodeOperation = await getTranscodeImageOperation(
1582 - item.sourceFile,
3502 + thumbnailSource,
1583 3503 outputMimeType,
1584 - settings
3504 + interlaced,
3505 + defaultQuality
1585 3506 );
1586 3507 }
1587 - const thumbnailOutputType = thumbnailTranscodeOperation ? `image/${thumbnailTranscodeOperation[1].outputFormat}` : sourceType;
1588 - const quality = thumbnailTranscodeOperation ? thumbnailTranscodeOperation[1].outputQuality ?? DEFAULT_OUTPUT_QUALITY : DEFAULT_OUTPUT_QUALITY;
1589 - const batchConfigs = [];
3508 + const dimensionGroups = /* @__PURE__ */ new Map();
1590 3509 for (const name of sizesToGenerate) {
1591 3510 const imageSize = allImageSizes[name];
1592 3511 if (!imageSize) {
1593 3512 console.warn(
@@ -1594,134 +3513,90 @@
1594 3513 `Image size "${name}" not found in configuration`
1595 3514 );
1596 3515 continue;
1597 3516 }
1598 - batchConfigs.push({
1599 - name,
1600 - resize: imageSize,
1601 - quality
1602 - });
1603 - }
1604 - const { bigImageSizeThreshold } = settings;
1605 - let needsScaling = false;
1606 - if (bigImageSizeThreshold && attachment.id) {
1607 - const bitmap = await createImageBitmap(item.sourceFile);
1608 - needsScaling = bitmap.width > bigImageSizeThreshold || bitmap.height > bigImageSizeThreshold;
1609 - bitmap.close();
1610 - if (needsScaling) {
1611 - batchConfigs.push({
1612 - name: "scaled",
1613 - resize: {
1614 - width: bigImageSizeThreshold,
1615 - height: bigImageSizeThreshold
1616 - },
1617 - quality,
1618 - scaledSuffix: true
1619 - });
3517 + const key = `${imageSize.width}x${imageSize.height}x${imageSize.crop}`;
3518 + const group = dimensionGroups.get(key);
3519 + if (group) {
3520 + group.push(name);
3521 + } else {
3522 + dimensionGroups.set(key, [name]);
1620 3523 }
1621 3524 }
1622 - let batchResults = null;
1623 - if (batchConfigs.length > 0) {
1624 - try {
1625 - batchResults = await vipsBatchResizeImage(
1626 - item.id,
1627 - file,
1628 - thumbnailOutputType,
1629 - batchConfigs,
1630 - false
1631 - );
1632 - } catch {
1633 - console.warn(
1634 - "Batch resize failed, falling back to per-thumbnail processing"
1635 - );
3525 + for (const [, names] of dimensionGroups) {
3526 + const imageSize = allImageSizes[names[0]];
3527 + const sizeQuality = qualityForSize(names[0]);
3528 + const thumbnailOperations = [
3529 + [
3530 + OperationType.ResizeCrop,
3531 + { resize: imageSize, quality: sizeQuality }
3532 + ]
3533 + ];
3534 + if (!isUltraHdr && thumbnailTranscodeOperation) {
3535 + thumbnailOperations.push([
3536 + thumbnailTranscodeOperation[0],
3537 + {
3538 + ...thumbnailTranscodeOperation[1],
3539 + outputQuality: sizeQuality
3540 + }
3541 + ]);
1636 3542 }
3543 + thumbnailOperations.push(OperationType.Upload);
3544 + const imageSizeParam = names.length === 1 ? names[0] : names;
3545 + dispatch.addSideloadItem({
3546 + file,
3547 + batchId,
3548 + parentId: item.id,
3549 + additionalData: {
3550 + // Sideloading does not use the parent post ID but the
3551 + // attachment ID as the image sizes need to be added to it.
3552 + post: attachment.id,
3553 + image_size: imageSizeParam,
3554 + convert_format: false
3555 + },
3556 + operations: thumbnailOperations
3557 + });
1637 3558 }
1638 - if (batchResults) {
1639 - for (const result of batchResults) {
1640 - dispatch.addSideloadItem({
1641 - file: result.file,
1642 - onChange: ([updatedAttachment]) => {
1643 - if ((0, import_blob.isBlobURL)(updatedAttachment.url)) {
1644 - return;
1645 - }
1646 - item.onChange?.([updatedAttachment]);
1647 - },
1648 - batchId,
1649 - parentId: item.id,
1650 - additionalData: {
1651 - post: attachment.id,
1652 - image_size: result.name,
1653 - convert_format: false
1654 - },
1655 - operations: [OperationType.Upload]
1656 - });
1657 - }
1658 - } else {
1659 - for (const name of sizesToGenerate) {
1660 - const imageSize = allImageSizes[name];
1661 - if (!imageSize) {
1662 - continue;
3559 + {
3560 + const { bigImageSizeThreshold } = settings;
3561 + if (bigImageSizeThreshold && attachment.id) {
3562 + const bitmap = await createImageBitmap(thumbnailSource);
3563 + const needsScaling = bitmap.width > bigImageSizeThreshold || bitmap.height > bigImageSizeThreshold;
3564 + bitmap.close();
3565 + if (needsScaling) {
3566 + const sourceForScaled = attachment.filename ? renameFile(thumbnailSource, attachment.filename) : thumbnailSource;
3567 + const scaledOperations = [
3568 + [
3569 + OperationType.ResizeCrop,
3570 + {
3571 + resize: {
3572 + width: bigImageSizeThreshold,
3573 + height: bigImageSizeThreshold
3574 + },
3575 + isThresholdResize: true,
3576 + quality: defaultQuality
3577 + }
3578 + ]
3579 + ];
3580 + if (!isUltraHdr && thumbnailTranscodeOperation) {
3581 + scaledOperations.push(
3582 + thumbnailTranscodeOperation
3583 + );
3584 + }
3585 + scaledOperations.push(OperationType.Upload);
3586 + dispatch.addSideloadItem({
3587 + file: sourceForScaled,
3588 + batchId,
3589 + parentId: item.id,
3590 + additionalData: {
3591 + post: attachment.id,
3592 + image_size: "scaled",
3593 + convert_format: false
3594 + },
3595 + operations: scaledOperations
3596 + });
1663 3597 }
1664 - const thumbnailOperations = [
1665 - [OperationType.ResizeCrop, { resize: imageSize }]
1666 - ];
1667 - if (thumbnailTranscodeOperation) {
1668 - thumbnailOperations.push(thumbnailTranscodeOperation);
1669 - }
1670 - thumbnailOperations.push(OperationType.Upload);
1671 - dispatch.addSideloadItem({
1672 - file,
1673 - onChange: ([updatedAttachment]) => {
1674 - if ((0, import_blob.isBlobURL)(updatedAttachment.url)) {
1675 - return;
1676 - }
1677 - item.onChange?.([updatedAttachment]);
1678 - },
1679 - batchId,
1680 - parentId: item.id,
1681 - additionalData: {
1682 - post: attachment.id,
1683 - image_size: name,
1684 - convert_format: false
1685 - },
1686 - operations: thumbnailOperations
1687 - });
1688 3598 }
1689 - if (needsScaling && bigImageSizeThreshold && attachment.id) {
1690 - const scaledOperations = [
1691 - [
1692 - OperationType.ResizeCrop,
1693 - {
1694 - resize: {
1695 - width: bigImageSizeThreshold,
1696 - height: bigImageSizeThreshold
1697 - },
1698 - isThresholdResize: true
1699 - }
1700 - ]
1701 - ];
1702 - if (thumbnailTranscodeOperation) {
1703 - scaledOperations.push(thumbnailTranscodeOperation);
1704 - }
1705 - scaledOperations.push(OperationType.Upload);
1706 - dispatch.addSideloadItem({
1707 - file,
1708 - onChange: ([updatedAttachment]) => {
1709 - if ((0, import_blob.isBlobURL)(updatedAttachment.url)) {
1710 - return;
1711 - }
1712 - item.onChange?.([updatedAttachment]);
1713 - },
1714 - batchId,
1715 - parentId: item.id,
1716 - additionalData: {
1717 - post: attachment.id,
1718 - image_size: "scaled",
1719 - convert_format: false
1720 - },
1721 - operations: scaledOperations
1722 - });
1723 - }
1724 3599 }
1725 3600 }
1726 3601 dispatch.finishOperation(id, {});
1727 3602 };
@@ -1733,16 +3608,32 @@
1733 3608 return;
1734 3609 }
1735 3610 const attachment = item.attachment;
1736 3611 const { mediaFinalize } = select2.getSettings();
3612 + const updates = {};
1737 3613 if (attachment?.id && mediaFinalize) {
1738 3614 try {
1739 - await mediaFinalize(attachment.id);
3615 + const updatedAttachment = await mediaFinalize(
3616 + attachment.id,
3617 + item.subSizes || []
3618 + );
3619 + if (updatedAttachment) {
3620 + updates.attachment = updatedAttachment;
3621 + }
1740 3622 } catch (error) {
1741 3623 console.warn("Media finalization failed:", error);
3624 + dispatch.cancelItem(
3625 + id,
3626 + new UploadError({
3627 + code: ErrorCode.MEDIA_FINALIZE_ERROR,
3628 + message: (0, import_i18n7.__)("Could not finalize the upload."),
3629 + file: item.file
3630 + })
3631 + );
3632 + return;
1742 3633 }
1743 3634 }
1744 - dispatch.finishOperation(id, {});
3635 + dispatch.finishOperation(id, updates);
1745 3636 };
1746 3637 }
1747 3638 function revokeBlobUrls(id) {
1748 3639 return async ({ select: select2, dispatch }) => {
@@ -1846,96 +3737,114 @@
1846 3737 return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
1847 3738 });
1848 3739 var provider_default = MediaUploadProvider;
1849 3740
1850 - // packages/upload-media/build-module/feature-detection.mjs
1851 - var cachedResult = null;
1852 - function detectClientSideMediaSupport() {
1853 - if (cachedResult !== null) {
1854 - return cachedResult;
1855 - }
1856 - if (typeof WebAssembly === "undefined") {
1857 - cachedResult = {
1858 - supported: false,
1859 - reason: "WebAssembly is not supported in this browser."
1860 - };
1861 - return cachedResult;
1862 - }
1863 - if (typeof SharedArrayBuffer === "undefined") {
1864 - cachedResult = {
1865 - supported: false,
1866 - reason: "SharedArrayBuffer is not available. This may be due to missing cross-origin isolation headers."
1867 - };
1868 - return cachedResult;
1869 - }
1870 - if (typeof Worker === "undefined") {
1871 - cachedResult = {
1872 - supported: false,
1873 - reason: "Web Workers are not supported in this browser."
1874 - };
1875 - return cachedResult;
1876 - }
1877 - if (typeof navigator !== "undefined" && "deviceMemory" in navigator && navigator.deviceMemory <= 2) {
1878 - cachedResult = {
1879 - supported: false,
1880 - reason: "Device has insufficient memory for client-side media processing."
1881 - };
1882 - return cachedResult;
1883 - }
1884 - if (typeof navigator !== "undefined" && "hardwareConcurrency" in navigator && navigator.hardwareConcurrency < 2) {
1885 - cachedResult = {
1886 - supported: false,
1887 - reason: "Device has insufficient CPU cores for client-side media processing."
1888 - };
1889 - return cachedResult;
1890 - }
1891 - if (typeof navigator !== "undefined") {
1892 - const connection = navigator.connection;
1893 - if (connection) {
1894 - if (connection.saveData) {
1895 - cachedResult = {
1896 - supported: false,
1897 - reason: "Data saver mode is enabled."
1898 - };
1899 - return cachedResult;
1900 - }
1901 - if (connection.effectiveType === "slow-2g" || connection.effectiveType === "2g") {
1902 - cachedResult = {
1903 - supported: false,
1904 - reason: "Network connection is too slow for client-side media processing."
1905 - };
1906 - return cachedResult;
1907 - }
3741 + // packages/upload-media/build-module/error-messages.mjs
3742 + var import_i18n8 = __toESM(require_i18n(), 1);
3743 + function getErrorMessage(code, fileName) {
3744 + const messages = {
3745 + [ErrorCode.EMPTY_FILE]: {
3746 + title: (0, import_i18n8.__)("Empty file"),
3747 + description: (0, import_i18n8.sprintf)(
3748 + /* translators: %s: file name */
3749 + (0, import_i18n8.__)('"%s" is empty.'),
3750 + fileName
3751 + ),
3752 + action: (0, import_i18n8.__)("Please choose a different file.")
3753 + },
3754 + [ErrorCode.SIZE_ABOVE_LIMIT]: {
3755 + title: (0, import_i18n8.__)("File too large"),
3756 + description: (0, import_i18n8.sprintf)(
3757 + /* translators: %s: file name */
3758 + (0, import_i18n8.__)('"%s" exceeds the maximum upload size.'),
3759 + fileName
3760 + ),
3761 + action: (0, import_i18n8.__)("Please reduce the file size and try again.")
3762 + },
3763 + [ErrorCode.MIME_TYPE_NOT_SUPPORTED]: {
3764 + title: (0, import_i18n8.__)("Unsupported file type"),
3765 + description: (0, import_i18n8.sprintf)(
3766 + /* translators: %s: file name */
3767 + (0, import_i18n8.__)('"%s" is not a supported file type.'),
3768 + fileName
3769 + ),
3770 + action: (0, import_i18n8.__)("Please upload a different file format.")
3771 + },
3772 + [ErrorCode.MIME_TYPE_NOT_ALLOWED_FOR_USER]: {
3773 + title: (0, import_i18n8.__)("File type not allowed"),
3774 + description: (0, import_i18n8.sprintf)(
3775 + /* translators: %s: file name */
3776 + (0, import_i18n8.__)('You are not allowed to upload "%s".'),
3777 + fileName
3778 + ),
3779 + action: (0, import_i18n8.__)("Please contact your site administrator.")
3780 + },
3781 + [ErrorCode.HEIC_DECODE_ERROR]: {
3782 + title: (0, import_i18n8.__)("HEIC decode failed"),
3783 + description: (0, import_i18n8.sprintf)(
3784 + /* translators: %s: file name */
3785 + (0, import_i18n8.__)('Failed to decode HEIC file "%s".'),
3786 + fileName
3787 + ),
3788 + action: getHeicConversionAdvice()
3789 + },
3790 + [ErrorCode.IMAGE_TRANSCODING_ERROR]: {
3791 + title: (0, import_i18n8.__)("Image processing failed"),
3792 + description: (0, import_i18n8.sprintf)(
3793 + /* translators: %s: file name */
3794 + (0, import_i18n8.__)('Failed to process "%s".'),
3795 + fileName
3796 + ),
3797 + action: (0, import_i18n8.__)("The image may be corrupted. Try a different file.")
3798 + },
3799 + [ErrorCode.IMAGE_ROTATION_ERROR]: {
3800 + title: (0, import_i18n8.__)("Image rotation failed"),
3801 + description: (0, import_i18n8.sprintf)(
3802 + /* translators: %s: file name */
3803 + (0, import_i18n8.__)('Failed to rotate "%s".'),
3804 + fileName
3805 + ),
3806 + action: (0, import_i18n8.__)("The image may be corrupted. Try a different file.")
3807 + },
3808 + [ErrorCode.MEDIA_TRANSCODING_ERROR]: {
3809 + title: (0, import_i18n8.__)("Media processing failed"),
3810 + description: (0, import_i18n8.sprintf)(
3811 + /* translators: %s: file name */
3812 + (0, import_i18n8.__)('Failed to convert "%s" to the target format.'),
3813 + fileName
3814 + ),
3815 + action: (0, import_i18n8.__)("The file may be corrupted. Try a different file.")
3816 + },
3817 + [ErrorCode.MEDIA_FINALIZE_ERROR]: {
3818 + title: (0, import_i18n8.__)("Upload failed"),
3819 + description: (0, import_i18n8.sprintf)(
3820 + /* translators: %s: file name */
3821 + (0, import_i18n8.__)('Could not finalize the upload of "%s".'),
3822 + fileName
3823 + ),
3824 + action: (0, import_i18n8.__)("Please try again.")
3825 + },
3826 + [ErrorCode.GENERAL]: {
3827 + title: (0, import_i18n8.__)("Upload failed"),
3828 + description: (0, import_i18n8.sprintf)(
3829 + /* translators: %s: file name */
3830 + (0, import_i18n8.__)('Failed to upload "%s".'),
3831 + fileName
3832 + ),
3833 + action: (0, import_i18n8.__)("Please try again.")
1908 3834 }
1909 - }
1910 - if (typeof window !== "undefined") {
1911 - try {
1912 - const testBlob = new Blob([""], {
1913 - type: "application/javascript"
1914 - });
1915 - const testUrl = URL.createObjectURL(testBlob);
1916 - try {
1917 - const testWorker = new Worker(testUrl);
1918 - testWorker.terminate();
1919 - } finally {
1920 - URL.revokeObjectURL(testUrl);
1921 - }
1922 - } catch {
1923 - cachedResult = {
1924 - supported: false,
1925 - reason: "The site's Content Security Policy (CSP) does not allow blob: workers. The worker-src directive must include blob: to enable client-side media processing."
1926 - };
1927 - return cachedResult;
1928 - }
1929 - }
1930 - cachedResult = { supported: true };
1931 - return cachedResult;
3835 + };
3836 + return messages[code] || {
3837 + title: (0, import_i18n8.__)("Upload failed"),
3838 + description: (0, import_i18n8.sprintf)(
3839 + /* translators: %s: file name */
3840 + (0, import_i18n8.__)('Failed to upload "%s".'),
3841 + fileName
3842 + ),
3843 + action: (0, import_i18n8.__)("Please try again.")
3844 + };
1932 3845 }
1933 - function isClientSideMediaSupported() {
1934 - return detectClientSideMediaSupport().supported;
1935 - }
1936 - function clearFeatureDetectionCache() {
1937 - cachedResult = null;
1938 - }
1939 3846 return __toCommonJS(index_exports);
3847 +})();
3848 +(window.wp ||= {}).uploadMedia = wp.uploadMedia;
1940 3849 })();
1941 3850 //# sourceMappingURL=index.js.map