PluginProbe
Gutenberg / trunk
Gutenberg vtrunk
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 → trunk 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