PluginProbe
Gutenberg / 24.1.0
Gutenberg v24.1.0
24.1.0 24.0.0 23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 All 404 releases
← All changes | build/scripts/upload-media/index.js +1467 -143 23.2.2 → 24.1.0 View file →
@@ -1,4 +1,5 @@
1 +(function() {
1 2 "use strict";
2 3 var wp;
3 4 (wp ||= {}).uploadMedia = (() => {
4 5 var __create = Object.create;
@@ -44,8 +45,15 @@
44 45 module.exports = window.wp.data;
45 46 }
46 47 });
47 48
49 + // package-external:@wordpress/i18n
50 + var require_i18n = __commonJS({
51 + "package-external:@wordpress/i18n"(exports, module) {
52 + module.exports = window.wp.i18n;
53 + }
54 + });
55 +
48 56 // package-external:@wordpress/url
49 57 var require_url = __commonJS({
50 58 "package-external:@wordpress/url"(exports, module) {
51 59 module.exports = window.wp.url;
@@ -51,15 +59,8 @@
51 59 module.exports = window.wp.url;
52 60 }
53 61 });
54 62
55 - // package-external:@wordpress/i18n
56 - var require_i18n = __commonJS({
57 - "package-external:@wordpress/i18n"(exports, module) {
58 - module.exports = window.wp.i18n;
59 - }
60 - });
61 -
62 63 // package-external:@wordpress/blob
63 64 var require_blob = __commonJS({
64 65 "package-external:@wordpress/blob"(exports, module) {
65 66 module.exports = window.wp.blob;
@@ -96,14 +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,
105 110 isHeicCanvasSupported: () => isHeicCanvasSupported,
111 + isHeicFile: () => isHeicFile,
106 112 store: () => store
107 113 });
108 114
109 115 // packages/upload-media/build-module/store/index.mjs
@@ -116,8 +122,9 @@
116 122 Type2["Prepare"] = "PREPARE_ITEM";
117 123 Type2["Cancel"] = "CANCEL_ITEM";
118 124 Type2["Remove"] = "REMOVE_ITEM";
119 125 Type2["RetryItem"] = "RETRY_ITEM";
126 + Type2["ScheduleRetry"] = "SCHEDULE_RETRY";
120 127 Type2["PauseItem"] = "PAUSE_ITEM";
121 128 Type2["ResumeItem"] = "RESUME_ITEM";
122 129 Type2["PauseQueue"] = "PAUSE_QUEUE";
123 130 Type2["ResumeQueue"] = "RESUME_QUEUE";
@@ -134,8 +141,9 @@
134 141 var ItemStatus = /* @__PURE__ */ ((ItemStatus2) => {
135 142 ItemStatus2["Queued"] = "QUEUED";
136 143 ItemStatus2["Processing"] = "PROCESSING";
137 144 ItemStatus2["Paused"] = "PAUSED";
145 + ItemStatus2["PendingRetry"] = "PENDING_RETRY";
138 146 ItemStatus2["Uploaded"] = "UPLOADED";
139 147 ItemStatus2["Error"] = "ERROR";
140 148 return ItemStatus2;
141 149 })(ItemStatus || {});
@@ -144,10 +152,12 @@
144 152 OperationType2["Upload"] = "UPLOAD";
145 153 OperationType2["ResizeCrop"] = "RESIZE_CROP";
146 154 OperationType2["Rotate"] = "ROTATE";
147 155 OperationType2["TranscodeImage"] = "TRANSCODE_IMAGE";
156 + OperationType2["TranscodeGif"] = "TRANSCODE_GIF";
148 157 OperationType2["ThumbnailGeneration"] = "THUMBNAIL_GENERATION";
149 158 OperationType2["Finalize"] = "FINALIZE";
159 + OperationType2["DetectUltraHdr"] = "DETECT_ULTRAHDR";
150 160 return OperationType2;
151 161 })(OperationType || {});
152 162
153 163 // packages/upload-media/build-module/store/constants.mjs
@@ -153,8 +163,15 @@
153 163 // packages/upload-media/build-module/store/constants.mjs
154 164 var STORE_NAME = "core/upload-media";
155 165 var DEFAULT_MAX_CONCURRENT_UPLOADS = 5;
156 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 + };
157 174 var CLIENT_SIDE_SUPPORTED_MIME_TYPES = [
158 175 "image/jpeg",
159 176 "image/png",
160 177 "image/gif",
@@ -175,10 +192,12 @@
175 192 blobUrls: {},
176 193 settings: {
177 194 mediaUpload: noop,
178 195 maxConcurrentUploads: DEFAULT_MAX_CONCURRENT_UPLOADS,
179 - maxConcurrentImageProcessing: DEFAULT_MAX_CONCURRENT_IMAGE_PROCESSING
180 - }
196 + maxConcurrentImageProcessing: DEFAULT_MAX_CONCURRENT_IMAGE_PROCESSING,
197 + retry: { ...DEFAULT_RETRY_SETTINGS }
198 + },
199 + failureCount: 0
181 200 };
182 201 function reducer(state = DEFAULT_STATE, action = { type: Type.Unknown }) {
183 202 switch (action.type) {
184 203 case Type.PauseQueue: {
@@ -217,11 +236,15 @@
217 236 return {
218 237 ...state,
219 238 queue: [...state.queue, action.item]
220 239 };
221 - case Type.Cancel:
240 + case Type.Cancel: {
241 + const cancelled = state.queue.find(
242 + (item) => item.id === action.id
243 + );
222 244 return {
223 245 ...state,
246 + failureCount: cancelled && !cancelled.parentId ? state.failureCount + 1 : state.failureCount,
224 247 queue: state.queue.map(
225 248 (item) => item.id === action.id ? {
226 249 ...item,
227 250 error: action.error
@@ -227,8 +250,9 @@
227 250 error: action.error
228 251 } : item
229 252 )
230 253 };
254 + }
231 255 case Type.RetryItem:
232 256 return {
233 257 ...state,
234 258 queue: state.queue.map(
@@ -235,12 +259,37 @@
235 259 (item) => item.id === action.id ? {
236 260 ...item,
237 261 status: ItemStatus.Processing,
238 262 error: void 0,
239 - 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
240 276 } : item
241 277 )
242 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 + };
243 292 case Type.Remove:
244 293 return {
245 294 ...state,
246 295 queue: state.queue.filter((item) => item.id !== action.id)
@@ -385,15 +434,18 @@
385 434 var private_selectors_exports = {};
386 435 __export(private_selectors_exports, {
387 436 getActiveImageProcessingCount: () => getActiveImageProcessingCount,
388 437 getActiveUploadCount: () => getActiveUploadCount,
438 + getActiveVideoProcessingCount: () => getActiveVideoProcessingCount,
389 439 getAllItems: () => getAllItems,
390 440 getBlobUrls: () => getBlobUrls,
391 441 getFailedItems: () => getFailedItems,
442 + getFailureCount: () => getFailureCount,
392 443 getItem: () => getItem,
393 444 getItemProgress: () => getItemProgress,
394 445 getPendingImageProcessing: () => getPendingImageProcessing,
395 446 getPendingUploads: () => getPendingUploads,
447 + getPendingVideoProcessing: () => getPendingVideoProcessing,
396 448 hasPendingItemsByParentId: () => hasPendingItemsByParentId,
397 449 isBatchUploaded: () => isBatchUploaded,
398 450 isPaused: () => isPaused
399 451 });
@@ -399,8 +451,11 @@
399 451 });
400 452 function getAllItems(state) {
401 453 return state.queue;
402 454 }
455 + function getFailureCount(state) {
456 + return state.failureCount;
457 + }
403 458 function getItem(state, id) {
404 459 return state.queue.find((item) => item.id === id);
405 460 }
406 461 function isBatchUploaded(state, batchId) {
@@ -430,8 +485,13 @@
430 485 return state.queue.filter(
431 486 (item) => item.currentOperation === OperationType.ResizeCrop || item.currentOperation === OperationType.Rotate
432 487 ).length;
433 488 }
489 + function getActiveVideoProcessingCount(state) {
490 + return state.queue.filter(
491 + (item) => item.currentOperation === OperationType.TranscodeGif
492 + ).length;
493 + }
434 494 function getPendingImageProcessing(state) {
435 495 return state.queue.filter((item) => {
436 496 const nextOperation = Array.isArray(item.operations?.[0]) ? item.operations[0][0] : item.operations?.[0];
437 497 return (nextOperation === OperationType.ResizeCrop || nextOperation === OperationType.Rotate) && item.currentOperation !== OperationType.ResizeCrop && item.currentOperation !== OperationType.Rotate;
@@ -436,8 +496,14 @@
436 496 const nextOperation = Array.isArray(item.operations?.[0]) ? item.operations[0][0] : item.operations?.[0];
437 497 return (nextOperation === OperationType.ResizeCrop || nextOperation === OperationType.Rotate) && item.currentOperation !== OperationType.ResizeCrop && item.currentOperation !== OperationType.Rotate;
438 498 });
439 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 + }
440 506 function getFailedItems(state) {
441 507 return state.queue.filter((item) => item.error !== void 0);
442 508 }
443 509 function hasPendingItemsByParentId(state, parentId) {
@@ -452,9 +518,11 @@
452 518 var actions_exports = {};
453 519 __export(actions_exports, {
454 520 addItems: () => addItems,
455 521 cancelItem: () => cancelItem,
456 - retryItem: () => retryItem
522 + executeRetry: () => executeRetry,
523 + retryItem: () => retryItem,
524 + scheduleRetry: () => scheduleRetry
457 525 });
458 526
459 527 // node_modules/uuid/dist/stringify.js
460 528 var byteToHex = [];
@@ -499,8 +567,50 @@
499 567 return unsafeStringify(rnds);
500 568 }
501 569 var v4_default = v4;
502 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 +
503 613 // packages/upload-media/build-module/image-file.mjs
504 614 var ImageFile = class extends File {
505 615 width = 0;
506 616 height = 0;
@@ -551,8 +661,72 @@
551 661 }
552 662 function getFileBasename(name) {
553 663 return name.includes(".") ? name.split(".").slice(0, -1).join(".") : name;
554 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 + }
555 729
556 730 // packages/upload-media/build-module/store/utils/index.mjs
557 731 var vipsModulePromise;
558 732 var vipsModule;
@@ -566,9 +740,9 @@
566 740 );
567 741 }
568 742 return vipsModulePromise;
569 743 }
570 - async function vipsConvertImageFormat(id, file, type, quality, interlaced) {
744 + async function vipsConvertImageFormat(id, file, type, options = {}) {
571 745 const { vipsConvertImageFormat: convertImageFormat } = await loadVipsModule();
572 746 const buffer = await convertImageFormat(
573 747 id,
574 748 await file.arrayBuffer(),
@@ -573,10 +747,9 @@
573 747 id,
574 748 await file.arrayBuffer(),
575 749 file.type,
576 750 type,
577 - quality,
578 - interlaced
751 + options
579 752 );
580 753 const ext = type.split("/")[1];
581 754 const fileName = `${getFileBasename(file.name)}.${ext}`;
582 755 return new File([new Blob([buffer])], fileName, {
@@ -590,21 +763,32 @@
590 763 throw new Error(`Failed to fetch image: ${response.status}`);
591 764 }
592 765 return hasTransparency(await response.arrayBuffer());
593 766 }
594 - 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;
595 781 if (signal?.aborted) {
596 782 throw new Error("Operation aborted");
597 783 }
598 784 const { vipsResizeImage: resizeImage } = await loadVipsModule();
599 - const { buffer, width, height, originalWidth, originalHeight } = await resizeImage(
600 - id,
601 - await file.arrayBuffer(),
602 - file.type,
603 - resize,
785 + const { buffer, width, height, originalWidth, originalHeight } = await resizeImage(id, await file.arrayBuffer(), file.type, resize, {
604 786 smartCrop,
605 - quality
606 - );
787 + quality,
788 + stripMeta,
789 + maxBitdepth
790 + });
607 791 let fileName = file.name;
608 792 const wasResized = originalWidth > width || originalHeight > height;
609 793 if (wasResized) {
610 794 const basename = getFileBasename(file.name);
@@ -671,13 +855,172 @@
671 855 if (vipsModule) {
672 856 vipsModule.terminateVipsWorker();
673 857 }
674 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 + }
675 868
676 - // packages/upload-media/build-module/validate-mime-type.mjs
677 - 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 + }
678 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 +
679 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 || {});
680 1023 var UploadError = class extends Error {
681 1024 code;
682 1025 file;
683 1026 constructor({ code, message, file, cause }) {
@@ -688,8 +1031,9 @@
688 1031 }
689 1032 };
690 1033
691 1034 // packages/upload-media/build-module/validate-mime-type.mjs
1035 + var import_i18n2 = __toESM(require_i18n(), 1);
692 1036 function validateMimeType(file, allowedTypes) {
693 1037 if (!allowedTypes) {
694 1038 return;
695 1039 }
@@ -700,9 +1044,9 @@
700 1044 return file.type.startsWith(`${allowedType}/`);
701 1045 });
702 1046 if (file.type && !isAllowedType) {
703 1047 throw new UploadError({
704 - code: "MIME_TYPE_NOT_SUPPORTED",
1048 + code: ErrorCode.MIME_TYPE_NOT_SUPPORTED,
705 1049 message: (0, import_i18n2.sprintf)(
706 1050 // translators: %s: file name.
707 1051 (0, import_i18n2.__)("%s: Sorry, this file type is not supported here."),
708 1052 file.name
@@ -744,9 +1088,9 @@
744 1088 file.type
745 1089 );
746 1090 if (file.type && !isAllowedMimeTypeForUser) {
747 1091 throw new UploadError({
748 - code: "MIME_TYPE_NOT_ALLOWED_FOR_USER",
1092 + code: ErrorCode.MIME_TYPE_NOT_ALLOWED_FOR_USER,
749 1093 message: (0, import_i18n3.sprintf)(
750 1094 // translators: %s: file name.
751 1095 (0, import_i18n3.__)(
752 1096 "%s: Sorry, you are not allowed to upload this file type."
@@ -762,9 +1106,9 @@
762 1106 var import_i18n4 = __toESM(require_i18n(), 1);
763 1107 function validateFileSize(file, maxUploadFileSize) {
764 1108 if (file.size <= 0) {
765 1109 throw new UploadError({
766 - code: "EMPTY_FILE",
1110 + code: ErrorCode.EMPTY_FILE,
767 1111 message: (0, import_i18n4.sprintf)(
768 1112 // translators: %s: file name.
769 1113 (0, import_i18n4.__)("%s: This file is empty."),
770 1114 file.name
@@ -773,9 +1117,9 @@
773 1117 });
774 1118 }
775 1119 if (maxUploadFileSize && file.size > maxUploadFileSize) {
776 1120 throw new UploadError({
777 - code: "SIZE_ABOVE_LIMIT",
1121 + code: ErrorCode.SIZE_ABOVE_LIMIT,
778 1122 message: (0, import_i18n4.sprintf)(
779 1123 // translators: %s: file name.
780 1124 (0, import_i18n4.__)(
781 1125 "%s: This file exceeds the maximum upload size for this site."
@@ -836,17 +1180,38 @@
836 1180 const item = select2.getItem(id);
837 1181 if (!item) {
838 1182 return;
839 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 + }
840 1197 item.abortController?.abort();
841 - await vipsCancelOperations(id);
1198 + vipsCancelOperations(id).catch(() => {
1199 + });
1200 + cancelGifToVideoOperations(id).catch(() => {
1201 + });
842 1202 if (!silent) {
843 1203 const { onError } = item;
844 1204 onError?.(error ?? new Error("Upload cancelled"));
845 - if (!onError && error) {
1205 + if (!onError && error && !item.parentId) {
846 1206 console.error("Upload cancelled", error);
847 1207 }
1208 + } else {
1209 + debug(
1210 + `Item cancelled: ${item.file.name} (item ${id}): ${error instanceof Error ? error.message : error}`
1211 + );
848 1212 }
1213 + const { currentOperation, parentId, batchId } = item;
849 1214 dispatch({
850 1215 type: Type.Cancel,
851 1216 id,
852 1217 error
@@ -852,9 +1217,59 @@
852 1217 error
853 1218 });
854 1219 dispatch.removeItem(id);
855 1220 dispatch.revokeBlobUrls(id);
856 - 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}`);
857 1272 item.onBatchSuccess?.();
858 1273 }
859 1274 };
860 1275 }
@@ -873,8 +1288,57 @@
873 1288 });
874 1289 dispatch.processItem(id);
875 1290 };
876 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 + }
877 1341
878 1342 // packages/upload-media/build-module/store/private-actions.mjs
879 1343 var private_actions_exports = {};
880 1344 __export(private_actions_exports, {
@@ -879,8 +1343,9 @@
879 1343 var private_actions_exports = {};
880 1344 __export(private_actions_exports, {
881 1345 addItem: () => addItem,
882 1346 addSideloadItem: () => addSideloadItem,
1347 + detectUltraHdr: () => detectUltraHdr,
883 1348 finalizeItem: () => finalizeItem,
884 1349 finishOperation: () => finishOperation,
885 1350 generateThumbnails: () => generateThumbnails,
886 1351 getTranscodeImageOperation: () => getTranscodeImageOperation,
@@ -893,8 +1358,9 @@
893 1358 resumeQueue: () => resumeQueue,
894 1359 revokeBlobUrls: () => revokeBlobUrls,
895 1360 rotateItem: () => rotateItem,
896 1361 sideloadItem: () => sideloadItem,
1362 + transcodeGifItem: () => transcodeGifItem,
897 1363 transcodeImageItem: () => transcodeImageItem,
898 1364 updateItemProgress: () => updateItemProgress,
899 1365 updateSettings: () => updateSettings,
900 1366 uploadItem: () => uploadItem
@@ -899,8 +1365,9 @@
899 1365 updateSettings: () => updateSettings,
900 1366 uploadItem: () => uploadItem
901 1367 });
902 1368 var import_blob = __toESM(require_blob(), 1);
1369 + var import_i18n7 = __toESM(require_i18n(), 1);
903 1370
904 1371 // packages/upload-media/build-module/heic-parser.mjs
905 1372 var Reader = class {
906 1373 view;
@@ -1171,8 +1638,15 @@
1171 1638 return `hvc1.${spacePrefix}${profileIdc}.${compatHex}.${tierChar}${levelIdc}${constraintStr}`;
1172 1639 }
1173 1640 function readItemData(buffer, loc, idatOffset) {
1174 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 + }
1175 1649 if (loc.extents.length === 1) {
1176 1650 const ext = loc.extents[0];
1177 1651 const start = baseOffset + ext.offset;
1178 1652 return new Uint8Array(buffer.slice(start, start + ext.length));
@@ -1307,9 +1781,10 @@
1307 1781 tileWidth: width,
1308 1782 tileHeight: height,
1309 1783 outputWidth: width,
1310 1784 outputHeight: height,
1311 - rotation
1785 + rotation,
1786 + exifOrientation: rotation === 0 ? getUnappliedExifOrientation(buffer) : 1
1312 1787 };
1313 1788 }
1314 1789 function parseGridImage(r, buffer, gridItemId, locations, allAssoc, properties, irefBox, idatOffset) {
1315 1790 const gridLoc = locations.get(gridItemId);
@@ -1400,13 +1875,269 @@
1400 1875 tileWidth,
1401 1876 tileHeight,
1402 1877 outputWidth,
1403 1878 outputHeight,
1404 - rotation
1879 + rotation,
1880 + exifOrientation: rotation === 0 ? getUnappliedExifOrientation(buffer) : 1
1405 1881 };
1406 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 + }
1407 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 +
1408 2136 // packages/upload-media/build-module/canvas-utils.mjs
2137 + var IMAGE_DECODER_TIMEOUT = 15e3;
2138 + var HeicUnsupportedError = class extends Error {
2139 + };
1409 2140 async function canvasConvertToJpeg(file, quality = 0.82) {
1410 2141 const baseName = getFileBasename(file.name);
1411 2142 try {
1412 2143 const bitmap = await createImageBitmap(file);
@@ -1435,10 +2166,36 @@
1435 2166 const decoder = new ImageDecoder({
1436 2167 type: file.type,
1437 2168 data: file.stream()
1438 2169 });
2170 + let videoFrame;
2171 + let timeoutId;
2172 + let timedOut = false;
1439 2173 try {
1440 - const { image: videoFrame } = await decoder.decode();
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) {
1441 2198 try {
1442 2199 const canvas = new OffscreenCanvas(
1443 2200 videoFrame.displayWidth,
1444 2201 videoFrame.displayHeight
@@ -1457,57 +2214,62 @@
1457 2214 });
1458 2215 } finally {
1459 2216 videoFrame.close();
1460 2217 }
1461 - } finally {
1462 - decoder.close();
1463 2218 }
1464 2219 }
1465 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 + }
1466 2229 if (typeof VideoDecoder !== "undefined") {
2230 + let supported = false;
1467 2231 try {
1468 - const heicData = parseHeic(await file.arrayBuffer());
1469 2232 const support = await VideoDecoder.isConfigSupported({
1470 2233 codec: heicData.codecString
1471 2234 });
1472 - if (support.supported) {
1473 - const canvas = new OffscreenCanvas(
1474 - heicData.outputWidth,
1475 - heicData.outputHeight
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
1476 2254 );
1477 - const ctx = canvas.getContext("2d");
1478 - if (!ctx) {
1479 - throw new Error("Could not get canvas 2d context");
2255 + try {
2256 + ctx.drawImage(frame, tile.x, tile.y);
2257 + } finally {
2258 + frame.close();
1480 2259 }
1481 - for (const tile of heicData.tiles) {
1482 - const frame = await decodeHevcFrame(
1483 - heicData.codecString,
1484 - heicData.description,
1485 - heicData.tileWidth,
1486 - heicData.tileHeight,
1487 - tile.data
1488 - );
1489 - try {
1490 - ctx.drawImage(frame, tile.x, tile.y);
1491 - } finally {
1492 - frame.close();
1493 - }
1494 - }
1495 - const outputCanvas = applyRotation(canvas, heicData.rotation);
1496 - const jpegBlob = await outputCanvas.convertToBlob({
1497 - type: "image/jpeg",
1498 - quality
1499 - });
1500 - return new File([jpegBlob], `${baseName}.jpg`, {
1501 - type: "image/jpeg"
1502 - });
1503 2260 }
1504 - } catch {
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 + });
1505 2269 }
1506 2270 }
1507 - throw new Error(
1508 - "This browser cannot decode HEIC images. Please use Safari or convert to JPEG before uploading."
1509 - );
2271 + throw new HeicUnsupportedError(getHeicUnsupportedMessage());
1510 2272 }
1511 2273 function applyRotation(source, rotation) {
1512 2274 if (rotation === 0) {
1513 2275 return source;
@@ -1524,8 +2286,45 @@
1524 2286 ctx.rotate(-rotation * Math.PI / 180);
1525 2287 ctx.drawImage(source, -source.width / 2, -source.height / 2);
1526 2288 return rotated;
1527 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 + }
1528 2327 function decodeHevcFrame(codec, description, width, height, data) {
1529 2328 return new Promise((resolve, reject) => {
1530 2329 const decoder = new VideoDecoder({
1531 2330 output: (frame) => {
@@ -1652,9 +2451,94 @@
1652 2451 }
1653 2452 function clearFeatureDetectionCache() {
1654 2453 cachedResult = null;
1655 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 + }
1656 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 +
1657 2541 // packages/upload-media/build-module/stub-file.mjs
1658 2542 var StubFile = class extends File {
1659 2543 constructor(fileName = "stub-file") {
1660 2544 super([], fileName);
@@ -1662,8 +2546,9 @@
1662 2546 };
1663 2547
1664 2548 // packages/upload-media/build-module/store/private-actions.mjs
1665 2549 var DEFAULT_OUTPUT_QUALITY = 0.82;
2550 + var ultraHdrItems = /* @__PURE__ */ new Set();
1666 2551 function addItem({
1667 2552 file: fileOrBlob,
1668 2553 batchId,
1669 2554 onChange,
@@ -1754,8 +2639,11 @@
1754 2639 const item = select2.getItem(id);
1755 2640 if (!item) {
1756 2641 return;
1757 2642 }
2643 + if (item.currentOperation) {
2644 + return;
2645 + }
1758 2646 const {
1759 2647 attachment,
1760 2648 onChange,
1761 2649 onSuccess,
@@ -1778,8 +2666,14 @@
1778 2666 if (activeCount >= settings.maxConcurrentImageProcessing) {
1779 2667 return;
1780 2668 }
1781 2669 }
2670 + if (operation === OperationType.TranscodeGif) {
2671 + const activeCount = select2.getActiveVideoProcessingCount();
2672 + if (activeCount >= 1) {
2673 + return;
2674 + }
2675 + }
1782 2676 if (attachment) {
1783 2677 const isHeicUrl = attachment.url && /\.hei[cf]$/i.test(attachment.url);
1784 2678 if (!isHeicUrl) {
1785 2679 onChange?.([attachment]);
@@ -1823,8 +2717,11 @@
1823 2717 type: Type.OperationStart,
1824 2718 id,
1825 2719 operation
1826 2720 });
2721 + debug(
2722 + `Starting operation ${operation} for ${item.file.name} (item ${item.id})`
2723 + );
1827 2724 switch (operation) {
1828 2725 case OperationType.Prepare:
1829 2726 dispatch.prepareItem(item.id);
1830 2727 break;
@@ -1845,8 +2742,14 @@
1845 2742 item.id,
1846 2743 operationArgs
1847 2744 );
1848 2745 break;
2746 + case OperationType.TranscodeGif:
2747 + dispatch.transcodeGifItem(
2748 + item.id,
2749 + operationArgs
2750 + );
2751 + break;
1849 2752 case OperationType.Upload:
1850 2753 if (item.parentId) {
1851 2754 dispatch.sideloadItem(id);
1852 2755 } else {
@@ -1858,8 +2761,11 @@
1858 2761 break;
1859 2762 case OperationType.Finalize:
1860 2763 dispatch.finalizeItem(id);
1861 2764 break;
2765 + case OperationType.DetectUltraHdr:
2766 + dispatch.detectUltraHdr(id);
2767 + break;
1862 2768 }
1863 2769 };
1864 2770 }
1865 2771 function pauseQueue() {
@@ -1872,9 +2778,13 @@
1872 2778 dispatch({
1873 2779 type: Type.ResumeQueue
1874 2780 });
1875 2781 for (const item of select2.getAllItems()) {
1876 - dispatch.processItem(item.id);
2782 + if (item.status === ItemStatus.PendingRetry) {
2783 + dispatch.executeRetry(item.id);
2784 + } else {
2785 + dispatch.processItem(item.id);
2786 + }
1877 2787 }
1878 2788 };
1879 2789 }
1880 2790 function pauseItem(id) {
@@ -1890,8 +2800,10 @@
1890 2800 const item = select2.getItem(id);
1891 2801 if (!item) {
1892 2802 return;
1893 2803 }
2804 + ultraHdrItems.delete(id);
2805 + clearRetryTimer(id);
1894 2806 dispatch({
1895 2807 type: Type.Remove,
1896 2808 id
1897 2809 });
@@ -1896,8 +2808,9 @@
1896 2808 id
1897 2809 });
1898 2810 if (select2.getAllItems().length === 0) {
1899 2811 terminateVipsWorker();
2812 + terminateVideoConversionWorker();
1900 2813 }
1901 2814 };
1902 2815 }
1903 2816 function finishOperation(id, updates) {
@@ -1921,8 +2834,17 @@
1921 2834 for (const pendingItem of pendingItems) {
1922 2835 dispatch.processItem(pendingItem.id);
1923 2836 }
1924 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 + }
1925 2847 };
1926 2848 }
1927 2849 var VALID_IMAGE_FORMATS = ["jpeg", "webp", "avif", "png", "gif"];
1928 2850 function isValidImageFormat(format) {
@@ -1927,9 +2849,9 @@
1927 2849 var VALID_IMAGE_FORMATS = ["jpeg", "webp", "avif", "png", "gif"];
1928 2850 function isValidImageFormat(format) {
1929 2851 return VALID_IMAGE_FORMATS.includes(format);
1930 2852 }
1931 - async function getTranscodeImageOperation(file, outputMimeType, interlaced = false) {
2853 + async function getTranscodeImageOperation(file, outputMimeType, interlaced = false, quality = DEFAULT_OUTPUT_QUALITY) {
1932 2854 if (file.type === "image/png" && outputMimeType === "image/jpeg") {
1933 2855 const blobUrl = (0, import_blob.createBlobURL)(file);
1934 2856 try {
1935 2857 const hasAlpha = await vipsHasTransparency(blobUrl);
@@ -1949,9 +2871,9 @@
1949 2871 return [
1950 2872 OperationType.TranscodeImage,
1951 2873 {
1952 2874 outputFormat: formatPart,
1953 - outputQuality: DEFAULT_OUTPUT_QUALITY,
2875 + outputQuality: quality,
1954 2876 interlaced
1955 2877 }
1956 2878 ];
1957 2879 }
@@ -1963,28 +2885,63 @@
1963 2885 }
1964 2886 const { file } = item;
1965 2887 const operations = [];
1966 2888 const settings = select2.getSettings();
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 + }
1967 2924 let heicJpeg = null;
1968 - const isImage = file.type.startsWith("image/");
1969 - const isVipsSupported = CLIENT_SIDE_SUPPORTED_MIME_TYPES.includes(
1970 - file.type
1971 - );
1972 - const isHeic = HEIC_MIME_TYPES.includes(file.type);
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;
1973 2934 if (isImage && isVipsSupported) {
1974 - const { bigImageSizeThreshold } = settings;
1975 - if (bigImageSizeThreshold) {
1976 - operations.push([
1977 - OperationType.ResizeCrop,
1978 - {
1979 - resize: {
1980 - width: bigImageSizeThreshold,
1981 - height: bigImageSizeThreshold
1982 - },
1983 - isThresholdResize: true
1984 - }
1985 - ]);
2935 + const dimensions = await getImageDimensions(file);
2936 + if (dimensions && exceedsClientProcessingMemory(dimensions)) {
2937 + tooLargeForClient = true;
1986 2938 }
2939 + }
2940 + if (file.type === "image/jpeg" && !isHeic && !tooLargeForClient) {
2941 + operations.push(OperationType.DetectUltraHdr);
2942 + }
2943 + if (isImage && isVipsSupported && !tooLargeForClient) {
1987 2944 operations.push(
1988 2945 OperationType.Upload,
1989 2946 OperationType.ThumbnailGeneration,
1990 2947 OperationType.Finalize
@@ -1991,18 +2948,22 @@
1991 2948 );
1992 2949 } else if (isImage && isHeic) {
1993 2950 try {
1994 2951 heicJpeg = await canvasConvertToJpeg(
1995 - file,
2952 + heicFile,
1996 2953 settings.imageQuality ?? DEFAULT_OUTPUT_QUALITY
1997 2954 );
1998 - } catch {
2955 + } catch (error) {
2956 + const unsupported = error instanceof HeicUnsupportedError;
1999 2957 dispatch.cancelItem(
2000 2958 id,
2001 2959 new UploadError({
2002 - code: "HEIC_DECODE_ERROR",
2003 - message: "This browser cannot decode HEIC images and the server does not support them either. Please convert to JPEG before uploading.",
2004 - file
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
2005 2966 })
2006 2967 );
2007 2968 return;
2008 2969 }
@@ -2018,15 +2979,15 @@
2018 2979 type: Type.AddOperations,
2019 2980 id,
2020 2981 operations
2021 2982 });
2022 - let updates = {};
2983 + let updates;
2023 2984 if (isHeic && heicJpeg) {
2024 2985 const vipsAvailable = isClientSideMediaSupported();
2025 2986 updates = {
2026 2987 file: heicJpeg,
2027 2988 sourceFile: heicJpeg,
2028 - originalHeicFile: item.file,
2989 + originalHeicFile: heicFile,
2029 2990 additionalData: {
2030 2991 ...item.additionalData,
2031 2992 generate_sub_sizes: !vipsAvailable,
2032 2993 convert_format: true
@@ -2031,9 +2992,9 @@
2031 2992 generate_sub_sizes: !vipsAvailable,
2032 2993 convert_format: true
2033 2994 }
2034 2995 };
2035 - } else if (!isVipsSupported || !isImage) {
2996 + } else if (!isVipsSupported || !isImage || tooLargeForClient) {
2036 2997 updates = {
2037 2998 additionalData: {
2038 2999 ...item.additionalData,
2039 3000 generate_sub_sizes: true,
@@ -2039,12 +3000,37 @@
2039 3000 generate_sub_sizes: true,
2040 3001 convert_format: true
2041 3002 }
2042 3003 };
3004 + } else {
3005 + updates = {
3006 + additionalData: {
3007 + ...item.additionalData,
3008 + generate_sub_sizes: false
3009 + }
3010 + };
2043 3011 }
2044 3012 dispatch.finishOperation(id, updates);
2045 3013 };
2046 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 + }
2047 3033 function uploadItem(id) {
2048 3034 return async ({ select: select2, dispatch }) => {
2049 3035 const item = select2.getItem(id);
2050 3036 if (!item) {
@@ -2049,23 +3035,42 @@
2049 3035 const item = select2.getItem(id);
2050 3036 if (!item) {
2051 3037 return;
2052 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 + };
2053 3057 select2.getSettings().mediaUpload({
2054 3058 filesList: [item.file],
2055 3059 additionalData: item.additionalData,
2056 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,
2057 3066 onFileChange: ([attachment]) => {
2058 3067 if (attachment && !(0, import_blob.isBlobURL)(attachment.url)) {
2059 - dispatch.finishOperation(id, {
2060 - attachment
2061 - });
3068 + finishUpload(attachment);
2062 3069 }
2063 3070 },
2064 3071 onSuccess: ([attachment]) => {
2065 - dispatch.finishOperation(id, {
2066 - attachment
2067 - });
3072 + finishUpload(attachment);
2068 3073 },
2069 3074 onError: (error) => {
2070 3075 dispatch.cancelItem(id, error);
2071 3076 }
@@ -2083,8 +3088,9 @@
2083 3088 if (!mediaSideload) {
2084 3089 dispatch.finishOperation(id, {});
2085 3090 return;
2086 3091 }
3092 + const startTime = performance.now();
2087 3093 mediaSideload({
2088 3094 file: item.file,
2089 3095 attachmentId: post,
2090 3096 additionalData,
@@ -2089,8 +3095,17 @@
2089 3095 attachmentId: post,
2090 3096 additionalData,
2091 3097 signal: item.abortController?.signal,
2092 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 + });
2093 3108 if (item.parentId) {
2094 3109 dispatch({
2095 3110 type: Type.AccumulateSubSize,
2096 3111 id: item.parentId,
@@ -2116,21 +3131,36 @@
2116 3131 file: item.file
2117 3132 });
2118 3133 return;
2119 3134 }
3135 + const startTime = performance.now();
2120 3136 const addSuffix = Boolean(item.parentId);
2121 3137 const scaledSuffix = Boolean(args.isThresholdResize);
3138 + const { imageStripMeta, imageMaxBitDepth } = select2.getSettings();
2122 3139 try {
2123 3140 const file = await vipsResizeImage(
2124 3141 item.id,
2125 3142 item.file,
2126 3143 args.resize,
2127 - false,
2128 - // smartCrop
2129 - addSuffix,
2130 - item.abortController?.signal,
2131 - 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 + }
2132 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 + });
2133 3163 const blobUrl = (0, import_blob.createBlobURL)(file);
2134 3164 dispatch({
2135 3165 type: Type.CacheBlobUrl,
2136 3166 id,
@@ -2145,10 +3175,12 @@
2145 3175 } catch (error) {
2146 3176 dispatch.cancelItem(
2147 3177 id,
2148 3178 new UploadError({
2149 - code: "IMAGE_TRANSCODING_ERROR",
2150 - 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 + ),
2151 3183 file: item.file,
2152 3184 cause: error instanceof Error ? error : void 0
2153 3185 })
2154 3186 );
@@ -2166,8 +3198,9 @@
2166 3198 file: item.file
2167 3199 });
2168 3200 return;
2169 3201 }
3202 + const startTime = performance.now();
2170 3203 try {
2171 3204 const file = await vipsRotateImage(
2172 3205 item.id,
2173 3206 item.file,
@@ -2173,8 +3206,17 @@
2173 3206 item.file,
2174 3207 args.orientation,
2175 3208 item.abortController?.signal
2176 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 + });
2177 3219 const blobUrl = (0, import_blob.createBlobURL)(file);
2178 3220 dispatch({
2179 3221 type: Type.CacheBlobUrl,
2180 3222 id,
@@ -2189,10 +3231,12 @@
2189 3231 } catch (error) {
2190 3232 dispatch.cancelItem(
2191 3233 id,
2192 3234 new UploadError({
2193 - code: "IMAGE_ROTATION_ERROR",
2194 - 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 + ),
2195 3239 file: item.file,
2196 3240 cause: error instanceof Error ? error : void 0
2197 3241 })
2198 3242 );
@@ -2210,19 +3254,34 @@
2210 3254 file: item.file
2211 3255 });
2212 3256 return;
2213 3257 }
3258 + const startTime = performance.now();
2214 3259 const outputMimeType = `image/${args.outputFormat}`;
2215 3260 const quality = args.outputQuality ?? DEFAULT_OUTPUT_QUALITY;
2216 3261 const interlaced = args.interlaced ?? false;
3262 + const { imageStripMeta, imageMaxBitDepth } = select2.getSettings();
2217 3263 try {
2218 3264 const file = await vipsConvertImageFormat(
2219 3265 item.id,
2220 3266 item.file,
2221 3267 outputMimeType,
2222 - quality,
2223 - interlaced
3268 + {
3269 + quality,
3270 + interlaced,
3271 + stripMeta: imageStripMeta,
3272 + maxBitdepth: imageMaxBitDepth
3273 + }
2224 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 + });
2225 3284 const blobUrl = (0, import_blob.createBlobURL)(file);
2226 3285 dispatch({
2227 3286 type: Type.CacheBlobUrl,
2228 3287 id,
@@ -2237,9 +3296,9 @@
2237 3296 } catch (error) {
2238 3297 dispatch.cancelItem(
2239 3298 id,
2240 3299 new UploadError({
2241 - code: "MEDIA_TRANSCODING_ERROR",
3300 + code: ErrorCode.MEDIA_TRANSCODING_ERROR,
2242 3301 message: "Image could not be transcoded to the target format",
2243 3302 file: item.file,
2244 3303 cause: error instanceof Error ? error : void 0
2245 3304 })
@@ -2246,8 +3305,91 @@
2246 3305 );
2247 3306 }
2248 3307 };
2249 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 + }
2250 3392 function generateThumbnails(id) {
2251 3393 return async ({ select: select2, dispatch }) => {
2252 3394 const item = select2.getItem(id);
2253 3395 if (!item) {
@@ -2265,35 +3407,57 @@
2265 3407 batchId: v4_default(),
2266 3408 parentId: item.id,
2267 3409 additionalData: {
2268 3410 post: attachment.id,
2269 - image_size: "original-heic",
3411 + image_size: "source_original",
2270 3412 convert_format: false
2271 3413 },
2272 3414 operations: [OperationType.Upload]
2273 3415 });
2274 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;
2275 3450 {
2276 - const needsRotation = attachment.exif_orientation && attachment.exif_orientation !== 1 && !item.file.name.includes("-scaled");
2277 - if (needsRotation && attachment.id) {
3451 + const needsRotation = exifOrientation !== 1 && !item.file.name.includes("-scaled");
3452 + if ((needsRotation || needsClientRotation) && attachment.id) {
2278 3453 try {
2279 - const rotatedFile = await vipsRotateImage(
3454 + rotatedSource = await vipsRotateImage(
2280 3455 item.id,
2281 3456 item.sourceFile,
2282 - attachment.exif_orientation,
3457 + exifOrientation,
2283 3458 item.abortController?.signal
2284 3459 );
2285 - dispatch.addSideloadItem({
2286 - file: rotatedFile,
2287 - batchId: v4_default(),
2288 - parentId: item.id,
2289 - additionalData: {
2290 - post: attachment.id,
2291 - image_size: "original",
2292 - convert_format: false
2293 - },
2294 - operations: [OperationType.Upload]
2295 - });
2296 3460 } catch {
2297 3461 console.warn(
2298 3462 "Failed to rotate image, continuing with thumbnails"
2299 3463 );
@@ -2298,23 +3462,48 @@
2298 3462 "Failed to rotate image, continuing with thumbnails"
2299 3463 );
2300 3464 }
2301 3465 }
3466 + if (needsRotation && rotatedSource && attachment.id) {
3467 + dispatch.addSideloadItem({
3468 + file: rotatedSource,
3469 + batchId: v4_default(),
3470 + parentId: item.id,
3471 + additionalData: {
3472 + post: attachment.id,
3473 + image_size: "original",
3474 + convert_format: false
3475 + },
3476 + operations: [OperationType.Upload]
3477 + });
3478 + }
2302 3479 }
2303 3480 if (!item.parentId && attachment.missing_image_sizes && attachment.missing_image_sizes.length > 0) {
2304 3481 const allImageSizes = settings.allImageSizes || {};
2305 3482 const sizesToGenerate = attachment.missing_image_sizes;
2306 - const thumbnailSource = item.sourceFile;
3483 + const thumbnailSource = needsClientRotation && rotatedSource ? rotatedSource : item.sourceFile;
2307 3484 const file = attachment.filename ? renameFile(thumbnailSource, attachment.filename) : thumbnailSource;
2308 3485 const batchId = v4_default();
3486 + const isUltraHdr = ultraHdrItems.has(item.id);
2309 3487 const outputMimeType = attachment.image_output_format;
2310 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 + };
2311 3499 let thumbnailTranscodeOperation = null;
2312 - if (outputMimeType) {
3500 + if (!isUltraHdr && outputMimeType) {
2313 3501 thumbnailTranscodeOperation = await getTranscodeImageOperation(
2314 3502 thumbnailSource,
2315 3503 outputMimeType,
2316 - interlaced
3504 + interlaced,
3505 + defaultQuality
2317 3506 );
2318 3507 }
2319 3508 const dimensionGroups = /* @__PURE__ */ new Map();
2320 3509 for (const name of sizesToGenerate) {
@@ -2334,13 +3523,23 @@
2334 3523 }
2335 3524 }
2336 3525 for (const [, names] of dimensionGroups) {
2337 3526 const imageSize = allImageSizes[names[0]];
3527 + const sizeQuality = qualityForSize(names[0]);
2338 3528 const thumbnailOperations = [
2339 - [OperationType.ResizeCrop, { resize: imageSize }]
3529 + [
3530 + OperationType.ResizeCrop,
3531 + { resize: imageSize, quality: sizeQuality }
3532 + ]
2340 3533 ];
2341 - if (thumbnailTranscodeOperation) {
2342 - thumbnailOperations.push(thumbnailTranscodeOperation);
3534 + if (!isUltraHdr && thumbnailTranscodeOperation) {
3535 + thumbnailOperations.push([
3536 + thumbnailTranscodeOperation[0],
3537 + {
3538 + ...thumbnailTranscodeOperation[1],
3539 + outputQuality: sizeQuality
3540 + }
3541 + ]);
2343 3542 }
2344 3543 thumbnailOperations.push(OperationType.Upload);
2345 3544 const imageSizeParam = names.length === 1 ? names[0] : names;
2346 3545 dispatch.addSideloadItem({
@@ -2372,13 +3571,14 @@
2372 3571 resize: {
2373 3572 width: bigImageSizeThreshold,
2374 3573 height: bigImageSizeThreshold
2375 3574 },
2376 - isThresholdResize: true
3575 + isThresholdResize: true,
3576 + quality: defaultQuality
2377 3577 }
2378 3578 ]
2379 3579 ];
2380 - if (thumbnailTranscodeOperation) {
3580 + if (!isUltraHdr && thumbnailTranscodeOperation) {
2381 3581 scaledOperations.push(
2382 3582 thumbnailTranscodeOperation
2383 3583 );
2384 3584 }
@@ -2408,16 +3608,32 @@
2408 3608 return;
2409 3609 }
2410 3610 const attachment = item.attachment;
2411 3611 const { mediaFinalize } = select2.getSettings();
3612 + const updates = {};
2412 3613 if (attachment?.id && mediaFinalize) {
2413 3614 try {
2414 - await mediaFinalize(attachment.id, item.subSizes || []);
3615 + const updatedAttachment = await mediaFinalize(
3616 + attachment.id,
3617 + item.subSizes || []
3618 + );
3619 + if (updatedAttachment) {
3620 + updates.attachment = updatedAttachment;
3621 + }
2415 3622 } catch (error) {
2416 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;
2417 3633 }
2418 3634 }
2419 - dispatch.finishOperation(id, {});
3635 + dispatch.finishOperation(id, updates);
2420 3636 };
2421 3637 }
2422 3638 function revokeBlobUrls(id) {
2423 3639 return async ({ select: select2, dispatch }) => {
@@ -2520,7 +3736,115 @@
2520 3736 }, [settings, updateSettings2]);
2521 3737 return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
2522 3738 });
2523 3739 var provider_default = MediaUploadProvider;
3740 +
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.")
3834 + }
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 + };
3845 + }
2524 3846 return __toCommonJS(index_exports);
3847 +})();
3848 +(window.wp ||= {}).uploadMedia = wp.uploadMedia;
2525 3849 })();
2526 3850 //# sourceMappingURL=index.js.map