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 +1119 -115 23.4.0 → 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;
@@ -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
@@ -146,8 +152,9 @@
146 152 OperationType2["Upload"] = "UPLOAD";
147 153 OperationType2["ResizeCrop"] = "RESIZE_CROP";
148 154 OperationType2["Rotate"] = "ROTATE";
149 155 OperationType2["TranscodeImage"] = "TRANSCODE_IMAGE";
156 + OperationType2["TranscodeGif"] = "TRANSCODE_GIF";
150 157 OperationType2["ThumbnailGeneration"] = "THUMBNAIL_GENERATION";
151 158 OperationType2["Finalize"] = "FINALIZE";
152 159 OperationType2["DetectUltraHdr"] = "DETECT_ULTRAHDR";
153 160 return OperationType2;
@@ -187,9 +194,10 @@
187 194 mediaUpload: noop,
188 195 maxConcurrentUploads: DEFAULT_MAX_CONCURRENT_UPLOADS,
189 196 maxConcurrentImageProcessing: DEFAULT_MAX_CONCURRENT_IMAGE_PROCESSING,
190 197 retry: { ...DEFAULT_RETRY_SETTINGS }
191 - }
198 + },
199 + failureCount: 0
192 200 };
193 201 function reducer(state = DEFAULT_STATE, action = { type: Type.Unknown }) {
194 202 switch (action.type) {
195 203 case Type.PauseQueue: {
@@ -228,11 +236,15 @@
228 236 return {
229 237 ...state,
230 238 queue: [...state.queue, action.item]
231 239 };
232 - case Type.Cancel:
240 + case Type.Cancel: {
241 + const cancelled = state.queue.find(
242 + (item) => item.id === action.id
243 + );
233 244 return {
234 245 ...state,
246 + failureCount: cancelled && !cancelled.parentId ? state.failureCount + 1 : state.failureCount,
235 247 queue: state.queue.map(
236 248 (item) => item.id === action.id ? {
237 249 ...item,
238 250 error: action.error
@@ -238,8 +250,9 @@
238 250 error: action.error
239 251 } : item
240 252 )
241 253 };
254 + }
242 255 case Type.RetryItem:
243 256 return {
244 257 ...state,
245 258 queue: state.queue.map(
@@ -247,9 +260,20 @@
247 260 ...item,
248 261 status: ItemStatus.Processing,
249 262 error: void 0,
250 263 retryCount: (item.retryCount ?? 0) + 1,
251 - abortController: new AbortController()
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
252 276 } : item
253 277 )
254 278 };
255 279 case Type.ScheduleRetry:
@@ -410,15 +434,18 @@
410 434 var private_selectors_exports = {};
411 435 __export(private_selectors_exports, {
412 436 getActiveImageProcessingCount: () => getActiveImageProcessingCount,
413 437 getActiveUploadCount: () => getActiveUploadCount,
438 + getActiveVideoProcessingCount: () => getActiveVideoProcessingCount,
414 439 getAllItems: () => getAllItems,
415 440 getBlobUrls: () => getBlobUrls,
416 441 getFailedItems: () => getFailedItems,
442 + getFailureCount: () => getFailureCount,
417 443 getItem: () => getItem,
418 444 getItemProgress: () => getItemProgress,
419 445 getPendingImageProcessing: () => getPendingImageProcessing,
420 446 getPendingUploads: () => getPendingUploads,
447 + getPendingVideoProcessing: () => getPendingVideoProcessing,
421 448 hasPendingItemsByParentId: () => hasPendingItemsByParentId,
422 449 isBatchUploaded: () => isBatchUploaded,
423 450 isPaused: () => isPaused
424 451 });
@@ -424,8 +451,11 @@
424 451 });
425 452 function getAllItems(state) {
426 453 return state.queue;
427 454 }
455 + function getFailureCount(state) {
456 + return state.failureCount;
457 + }
428 458 function getItem(state, id) {
429 459 return state.queue.find((item) => item.id === id);
430 460 }
431 461 function isBatchUploaded(state, batchId) {
@@ -455,8 +485,13 @@
455 485 return state.queue.filter(
456 486 (item) => item.currentOperation === OperationType.ResizeCrop || item.currentOperation === OperationType.Rotate
457 487 ).length;
458 488 }
489 + function getActiveVideoProcessingCount(state) {
490 + return state.queue.filter(
491 + (item) => item.currentOperation === OperationType.TranscodeGif
492 + ).length;
493 + }
459 494 function getPendingImageProcessing(state) {
460 495 return state.queue.filter((item) => {
461 496 const nextOperation = Array.isArray(item.operations?.[0]) ? item.operations[0][0] : item.operations?.[0];
462 497 return (nextOperation === OperationType.ResizeCrop || nextOperation === OperationType.Rotate) && item.currentOperation !== OperationType.ResizeCrop && item.currentOperation !== OperationType.Rotate;
@@ -461,8 +496,14 @@
461 496 const nextOperation = Array.isArray(item.operations?.[0]) ? item.operations[0][0] : item.operations?.[0];
462 497 return (nextOperation === OperationType.ResizeCrop || nextOperation === OperationType.Rotate) && item.currentOperation !== OperationType.ResizeCrop && item.currentOperation !== OperationType.Rotate;
463 498 });
464 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 + }
465 506 function getFailedItems(state) {
466 507 return state.queue.filter((item) => item.error !== void 0);
467 508 }
468 509 function hasPendingItemsByParentId(state, parentId) {
@@ -620,8 +661,72 @@
620 661 }
621 662 function getFileBasename(name) {
622 663 return name.includes(".") ? name.split(".").slice(0, -1).join(".") : name;
623 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 + }
624 729
625 730 // packages/upload-media/build-module/store/utils/index.mjs
626 731 var vipsModulePromise;
627 732 var vipsModule;
@@ -635,9 +740,9 @@
635 740 );
636 741 }
637 742 return vipsModulePromise;
638 743 }
639 - async function vipsConvertImageFormat(id, file, type, quality, interlaced) {
744 + async function vipsConvertImageFormat(id, file, type, options = {}) {
640 745 const { vipsConvertImageFormat: convertImageFormat } = await loadVipsModule();
641 746 const buffer = await convertImageFormat(
642 747 id,
643 748 await file.arrayBuffer(),
@@ -642,10 +747,9 @@
642 747 id,
643 748 await file.arrayBuffer(),
644 749 file.type,
645 750 type,
646 - quality,
647 - interlaced
751 + options
648 752 );
649 753 const ext = type.split("/")[1];
650 754 const fileName = `${getFileBasename(file.name)}.${ext}`;
651 755 return new File([new Blob([buffer])], fileName, {
@@ -663,21 +767,28 @@
663 767 async function vipsGetUltraHdrInfo(buffer) {
664 768 const { vipsGetUltraHdrInfo: getUltraHdrInfo } = await loadVipsModule();
665 769 return getUltraHdrInfo(buffer);
666 770 }
667 - async function vipsResizeImage(id, file, resize, smartCrop, addSuffix, signal, scaledSuffix, quality) {
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;
668 781 if (signal?.aborted) {
669 782 throw new Error("Operation aborted");
670 783 }
671 784 const { vipsResizeImage: resizeImage } = await loadVipsModule();
672 - const { buffer, width, height, originalWidth, originalHeight } = await resizeImage(
673 - id,
674 - await file.arrayBuffer(),
675 - file.type,
676 - resize,
785 + const { buffer, width, height, originalWidth, originalHeight } = await resizeImage(id, await file.arrayBuffer(), file.type, resize, {
677 786 smartCrop,
678 - quality
679 - );
787 + quality,
788 + stripMeta,
789 + maxBitdepth
790 + });
680 791 let fileName = file.name;
681 792 const wasResized = originalWidth > width || originalHeight > height;
682 793 if (wasResized) {
683 794 const basename = getFileBasename(file.name);
@@ -754,9 +865,162 @@
754 865 completedVipsOperations = 0;
755 866 }
756 867 }
757 868
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 + }
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 +
758 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 || {});
759 1023 var UploadError = class extends Error {
760 1024 code;
761 1025 file;
762 1026 constructor({ code, message, file, cause }) {
@@ -780,9 +1044,9 @@
780 1044 return file.type.startsWith(`${allowedType}/`);
781 1045 });
782 1046 if (file.type && !isAllowedType) {
783 1047 throw new UploadError({
784 - code: "MIME_TYPE_NOT_SUPPORTED",
1048 + code: ErrorCode.MIME_TYPE_NOT_SUPPORTED,
785 1049 message: (0, import_i18n2.sprintf)(
786 1050 // translators: %s: file name.
787 1051 (0, import_i18n2.__)("%s: Sorry, this file type is not supported here."),
788 1052 file.name
@@ -824,9 +1088,9 @@
824 1088 file.type
825 1089 );
826 1090 if (file.type && !isAllowedMimeTypeForUser) {
827 1091 throw new UploadError({
828 - code: "MIME_TYPE_NOT_ALLOWED_FOR_USER",
1092 + code: ErrorCode.MIME_TYPE_NOT_ALLOWED_FOR_USER,
829 1093 message: (0, import_i18n3.sprintf)(
830 1094 // translators: %s: file name.
831 1095 (0, import_i18n3.__)(
832 1096 "%s: Sorry, you are not allowed to upload this file type."
@@ -842,9 +1106,9 @@
842 1106 var import_i18n4 = __toESM(require_i18n(), 1);
843 1107 function validateFileSize(file, maxUploadFileSize) {
844 1108 if (file.size <= 0) {
845 1109 throw new UploadError({
846 - code: "EMPTY_FILE",
1110 + code: ErrorCode.EMPTY_FILE,
847 1111 message: (0, import_i18n4.sprintf)(
848 1112 // translators: %s: file name.
849 1113 (0, import_i18n4.__)("%s: This file is empty."),
850 1114 file.name
@@ -853,9 +1117,9 @@
853 1117 });
854 1118 }
855 1119 if (maxUploadFileSize && file.size > maxUploadFileSize) {
856 1120 throw new UploadError({
857 - code: "SIZE_ABOVE_LIMIT",
1121 + code: ErrorCode.SIZE_ABOVE_LIMIT,
858 1122 message: (0, import_i18n4.sprintf)(
859 1123 // translators: %s: file name.
860 1124 (0, import_i18n4.__)(
861 1125 "%s: This file exceeds the maximum upload size for this site."
@@ -930,9 +1194,12 @@
930 1194 }
931 1195 }
932 1196 }
933 1197 item.abortController?.abort();
934 - await vipsCancelOperations(id);
1198 + vipsCancelOperations(id).catch(() => {
1199 + });
1200 + cancelGifToVideoOperations(id).catch(() => {
1201 + });
935 1202 if (!silent) {
936 1203 const { onError } = item;
937 1204 onError?.(error ?? new Error("Upload cancelled"));
938 1205 if (!onError && error && !item.parentId) {
@@ -937,8 +1204,12 @@
937 1204 onError?.(error ?? new Error("Upload cancelled"));
938 1205 if (!onError && error && !item.parentId) {
939 1206 console.error("Upload cancelled", error);
940 1207 }
1208 + } else {
1209 + debug(
1210 + `Item cancelled: ${item.file.name} (item ${id}): ${error instanceof Error ? error.message : error}`
1211 + );
941 1212 }
942 1213 const { currentOperation, parentId, batchId } = item;
943 1214 dispatch({
944 1215 type: Type.Cancel,
@@ -956,8 +1227,13 @@
956 1227 for (const pending of select2.getPendingUploads()) {
957 1228 dispatch.processItem(pending.id);
958 1229 }
959 1230 }
1231 + if (currentOperation === OperationType.TranscodeGif) {
1232 + for (const pending of select2.getPendingVideoProcessing()) {
1233 + dispatch.processItem(pending.id);
1234 + }
1235 + }
960 1236 if (currentOperation === OperationType.ResizeCrop || currentOperation === OperationType.Rotate || currentOperation === OperationType.TranscodeImage) {
961 1237 maybeRecycleVipsWorker(select2.getActiveImageProcessingCount());
962 1238 }
963 1239 if (parentId) {
@@ -962,13 +1238,14 @@
962 1238 }
963 1239 if (parentId) {
964 1240 const parentItem = select2.getItem(parentId);
965 1241 if (parentItem) {
1242 + const isOptionalCompanion = item.additionalData?.image_size === "animated_video" || item.additionalData?.image_size === "animated_video_poster";
966 1243 if (select2.hasPendingItemsByParentId(parentId)) {
967 1244 if (parentItem.operations && parentItem.operations.length > 0) {
968 1245 dispatch.processItem(parentId);
969 1246 }
970 - } else if (parentItem.subSizes && parentItem.subSizes.length > 0) {
1247 + } else if (parentItem.subSizes && parentItem.subSizes.length > 0 || isOptionalCompanion) {
971 1248 if (parentItem.operations && parentItem.operations.length > 0) {
972 1249 dispatch.processItem(parentId);
973 1250 }
974 1251 } else {
@@ -977,12 +1254,12 @@
977 1254 if (parentAttachmentId && mediaDelete) {
978 1255 mediaDelete(parentAttachmentId).catch(() => {
979 1256 });
980 1257 }
981 - dispatch.cancelItem(
1258 + await dispatch.cancelItem(
982 1259 parentId,
983 1260 new UploadError({
984 - code: error instanceof UploadError && error.code || "UPLOAD_ERROR",
1261 + code: error instanceof UploadError && error.code || ErrorCode.GENERAL,
985 1262 message: error?.message || (0, import_i18n5.__)("The image could not be uploaded."),
986 1263 file: parentItem.file,
987 1264 cause: error instanceof Error ? error : void 0
988 1265 })
@@ -990,8 +1267,9 @@
990 1267 }
991 1268 }
992 1269 }
993 1270 if (batchId && select2.isBatchUploaded(batchId)) {
1271 + debug(`Batch completed: ${batchId}`);
994 1272 item.onBatchSuccess?.();
995 1273 }
996 1274 };
997 1275 }
@@ -1080,8 +1358,9 @@
1080 1358 resumeQueue: () => resumeQueue,
1081 1359 revokeBlobUrls: () => revokeBlobUrls,
1082 1360 rotateItem: () => rotateItem,
1083 1361 sideloadItem: () => sideloadItem,
1362 + transcodeGifItem: () => transcodeGifItem,
1084 1363 transcodeImageItem: () => transcodeImageItem,
1085 1364 updateItemProgress: () => updateItemProgress,
1086 1365 updateSettings: () => updateSettings,
1087 1366 uploadItem: () => uploadItem
@@ -1086,9 +1365,9 @@
1086 1365 updateSettings: () => updateSettings,
1087 1366 uploadItem: () => uploadItem
1088 1367 });
1089 1368 var import_blob = __toESM(require_blob(), 1);
1090 - var import_i18n6 = __toESM(require_i18n(), 1);
1369 + var import_i18n7 = __toESM(require_i18n(), 1);
1091 1370
1092 1371 // packages/upload-media/build-module/heic-parser.mjs
1093 1372 var Reader = class {
1094 1373 view;
@@ -1359,8 +1638,15 @@
1359 1638 return `hvc1.${spacePrefix}${profileIdc}.${compatHex}.${tierChar}${levelIdc}${constraintStr}`;
1360 1639 }
1361 1640 function readItemData(buffer, loc, idatOffset) {
1362 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 + }
1363 1649 if (loc.extents.length === 1) {
1364 1650 const ext = loc.extents[0];
1365 1651 const start = baseOffset + ext.offset;
1366 1652 return new Uint8Array(buffer.slice(start, start + ext.length));
@@ -1495,9 +1781,10 @@
1495 1781 tileWidth: width,
1496 1782 tileHeight: height,
1497 1783 outputWidth: width,
1498 1784 outputHeight: height,
1499 - rotation
1785 + rotation,
1786 + exifOrientation: rotation === 0 ? getUnappliedExifOrientation(buffer) : 1
1500 1787 };
1501 1788 }
1502 1789 function parseGridImage(r, buffer, gridItemId, locations, allAssoc, properties, irefBox, idatOffset) {
1503 1790 const gridLoc = locations.get(gridItemId);
@@ -1588,13 +1875,269 @@
1588 1875 tileWidth,
1589 1876 tileHeight,
1590 1877 outputWidth,
1591 1878 outputHeight,
1592 - rotation
1879 + rotation,
1880 + exifOrientation: rotation === 0 ? getUnappliedExifOrientation(buffer) : 1
1593 1881 };
1594 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 + }
1595 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 +
1596 2136 // packages/upload-media/build-module/canvas-utils.mjs
2137 + var IMAGE_DECODER_TIMEOUT = 15e3;
2138 + var HeicUnsupportedError = class extends Error {
2139 + };
1597 2140 async function canvasConvertToJpeg(file, quality = 0.82) {
1598 2141 const baseName = getFileBasename(file.name);
1599 2142 try {
1600 2143 const bitmap = await createImageBitmap(file);
@@ -1623,10 +2166,36 @@
1623 2166 const decoder = new ImageDecoder({
1624 2167 type: file.type,
1625 2168 data: file.stream()
1626 2169 });
2170 + let videoFrame;
2171 + let timeoutId;
2172 + let timedOut = false;
1627 2173 try {
1628 - 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) {
1629 2198 try {
1630 2199 const canvas = new OffscreenCanvas(
1631 2200 videoFrame.displayWidth,
1632 2201 videoFrame.displayHeight
@@ -1645,57 +2214,62 @@
1645 2214 });
1646 2215 } finally {
1647 2216 videoFrame.close();
1648 2217 }
1649 - } finally {
1650 - decoder.close();
1651 2218 }
1652 2219 }
1653 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 + }
1654 2229 if (typeof VideoDecoder !== "undefined") {
2230 + let supported = false;
1655 2231 try {
1656 - const heicData = parseHeic(await file.arrayBuffer());
1657 2232 const support = await VideoDecoder.isConfigSupported({
1658 2233 codec: heicData.codecString
1659 2234 });
1660 - if (support.supported) {
1661 - const canvas = new OffscreenCanvas(
1662 - heicData.outputWidth,
1663 - 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
1664 2254 );
1665 - const ctx = canvas.getContext("2d");
1666 - if (!ctx) {
1667 - throw new Error("Could not get canvas 2d context");
2255 + try {
2256 + ctx.drawImage(frame, tile.x, tile.y);
2257 + } finally {
2258 + frame.close();
1668 2259 }
1669 - for (const tile of heicData.tiles) {
1670 - const frame = await decodeHevcFrame(
1671 - heicData.codecString,
1672 - heicData.description,
1673 - heicData.tileWidth,
1674 - heicData.tileHeight,
1675 - tile.data
1676 - );
1677 - try {
1678 - ctx.drawImage(frame, tile.x, tile.y);
1679 - } finally {
1680 - frame.close();
1681 - }
1682 - }
1683 - const outputCanvas = applyRotation(canvas, heicData.rotation);
1684 - const jpegBlob = await outputCanvas.convertToBlob({
1685 - type: "image/jpeg",
1686 - quality
1687 - });
1688 - return new File([jpegBlob], `${baseName}.jpg`, {
1689 - type: "image/jpeg"
1690 - });
1691 2260 }
1692 - } 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 + });
1693 2269 }
1694 2270 }
1695 - throw new Error(
1696 - "This browser cannot decode HEIC images. Please use Safari or convert to JPEG before uploading."
1697 - );
2271 + throw new HeicUnsupportedError(getHeicUnsupportedMessage());
1698 2272 }
1699 2273 function applyRotation(source, rotation) {
1700 2274 if (rotation === 0) {
1701 2275 return source;
@@ -1712,8 +2286,45 @@
1712 2286 ctx.rotate(-rotation * Math.PI / 180);
1713 2287 ctx.drawImage(source, -source.width / 2, -source.height / 2);
1714 2288 return rotated;
1715 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 + }
1716 2327 function decodeHevcFrame(codec, description, width, height, data) {
1717 2328 return new Promise((resolve, reject) => {
1718 2329 const decoder = new VideoDecoder({
1719 2330 output: (frame) => {
@@ -2028,8 +2639,11 @@
2028 2639 const item = select2.getItem(id);
2029 2640 if (!item) {
2030 2641 return;
2031 2642 }
2643 + if (item.currentOperation) {
2644 + return;
2645 + }
2032 2646 const {
2033 2647 attachment,
2034 2648 onChange,
2035 2649 onSuccess,
@@ -2052,8 +2666,14 @@
2052 2666 if (activeCount >= settings.maxConcurrentImageProcessing) {
2053 2667 return;
2054 2668 }
2055 2669 }
2670 + if (operation === OperationType.TranscodeGif) {
2671 + const activeCount = select2.getActiveVideoProcessingCount();
2672 + if (activeCount >= 1) {
2673 + return;
2674 + }
2675 + }
2056 2676 if (attachment) {
2057 2677 const isHeicUrl = attachment.url && /\.hei[cf]$/i.test(attachment.url);
2058 2678 if (!isHeicUrl) {
2059 2679 onChange?.([attachment]);
@@ -2097,8 +2717,11 @@
2097 2717 type: Type.OperationStart,
2098 2718 id,
2099 2719 operation
2100 2720 });
2721 + debug(
2722 + `Starting operation ${operation} for ${item.file.name} (item ${item.id})`
2723 + );
2101 2724 switch (operation) {
2102 2725 case OperationType.Prepare:
2103 2726 dispatch.prepareItem(item.id);
2104 2727 break;
@@ -2119,8 +2742,14 @@
2119 2742 item.id,
2120 2743 operationArgs
2121 2744 );
2122 2745 break;
2746 + case OperationType.TranscodeGif:
2747 + dispatch.transcodeGifItem(
2748 + item.id,
2749 + operationArgs
2750 + );
2751 + break;
2123 2752 case OperationType.Upload:
2124 2753 if (item.parentId) {
2125 2754 dispatch.sideloadItem(id);
2126 2755 } else {
@@ -2179,8 +2808,9 @@
2179 2808 id
2180 2809 });
2181 2810 if (select2.getAllItems().length === 0) {
2182 2811 terminateVipsWorker();
2812 + terminateVideoConversionWorker();
2183 2813 }
2184 2814 };
2185 2815 }
2186 2816 function finishOperation(id, updates) {
@@ -2204,8 +2834,14 @@
2204 2834 for (const pendingItem of pendingItems) {
2205 2835 dispatch.processItem(pendingItem.id);
2206 2836 }
2207 2837 }
2838 + if (previousOperation === OperationType.TranscodeGif) {
2839 + const pendingItems = select2.getPendingVideoProcessing();
2840 + for (const pendingItem of pendingItems) {
2841 + dispatch.processItem(pendingItem.id);
2842 + }
2843 + }
2208 2844 if (previousOperation === OperationType.ResizeCrop || previousOperation === OperationType.Rotate || previousOperation === OperationType.TranscodeImage) {
2209 2845 maybeRecycleVipsWorker(select2.getActiveImageProcessingCount());
2210 2846 }
2211 2847 };
@@ -2213,9 +2849,9 @@
2213 2849 var VALID_IMAGE_FORMATS = ["jpeg", "webp", "avif", "png", "gif"];
2214 2850 function isValidImageFormat(format) {
2215 2851 return VALID_IMAGE_FORMATS.includes(format);
2216 2852 }
2217 - async function getTranscodeImageOperation(file, outputMimeType, interlaced = false) {
2853 + async function getTranscodeImageOperation(file, outputMimeType, interlaced = false, quality = DEFAULT_OUTPUT_QUALITY) {
2218 2854 if (file.type === "image/png" && outputMimeType === "image/jpeg") {
2219 2855 const blobUrl = (0, import_blob.createBlobURL)(file);
2220 2856 try {
2221 2857 const hasAlpha = await vipsHasTransparency(blobUrl);
@@ -2235,9 +2871,9 @@
2235 2871 return [
2236 2872 OperationType.TranscodeImage,
2237 2873 {
2238 2874 outputFormat: formatPart,
2239 - outputQuality: DEFAULT_OUTPUT_QUALITY,
2875 + outputQuality: quality,
2240 2876 interlaced
2241 2877 }
2242 2878 ];
2243 2879 }
@@ -2249,14 +2885,52 @@
2249 2885 }
2250 2886 const { file } = item;
2251 2887 const operations = [];
2252 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 + }
2253 2924 let heicJpeg = null;
2254 - const isImage = file.type.startsWith("image/");
2255 - const isVipsSupported = CLIENT_SIDE_SUPPORTED_MIME_TYPES.includes(
2256 - file.type
2257 - );
2258 - 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;
2259 2933 let tooLargeForClient = false;
2260 2934 if (isImage && isVipsSupported) {
2261 2935 const dimensions = await getImageDimensions(file);
2262 2936 if (dimensions && exceedsClientProcessingMemory(dimensions)) {
@@ -2262,9 +2936,9 @@
2262 2936 if (dimensions && exceedsClientProcessingMemory(dimensions)) {
2263 2937 tooLargeForClient = true;
2264 2938 }
2265 2939 }
2266 - if (file.type === "image/jpeg" && !tooLargeForClient) {
2940 + if (file.type === "image/jpeg" && !isHeic && !tooLargeForClient) {
2267 2941 operations.push(OperationType.DetectUltraHdr);
2268 2942 }
2269 2943 if (isImage && isVipsSupported && !tooLargeForClient) {
2270 2944 operations.push(
@@ -2274,18 +2948,22 @@
2274 2948 );
2275 2949 } else if (isImage && isHeic) {
2276 2950 try {
2277 2951 heicJpeg = await canvasConvertToJpeg(
2278 - file,
2952 + heicFile,
2279 2953 settings.imageQuality ?? DEFAULT_OUTPUT_QUALITY
2280 2954 );
2281 - } catch {
2955 + } catch (error) {
2956 + const unsupported = error instanceof HeicUnsupportedError;
2282 2957 dispatch.cancelItem(
2283 2958 id,
2284 2959 new UploadError({
2285 - code: "HEIC_DECODE_ERROR",
2286 - message: "This browser cannot decode HEIC images and the server does not support them either. Please convert to JPEG before uploading.",
2287 - 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
2288 2966 })
2289 2967 );
2290 2968 return;
2291 2969 }
@@ -2307,9 +2985,9 @@
2307 2985 const vipsAvailable = isClientSideMediaSupported();
2308 2986 updates = {
2309 2987 file: heicJpeg,
2310 2988 sourceFile: heicJpeg,
2311 - originalHeicFile: item.file,
2989 + originalHeicFile: heicFile,
2312 2990 additionalData: {
2313 2991 ...item.additionalData,
2314 2992 generate_sub_sizes: !vipsAvailable,
2315 2993 convert_format: true
@@ -2357,23 +3035,42 @@
2357 3035 const item = select2.getItem(id);
2358 3036 if (!item) {
2359 3037 return;
2360 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 + };
2361 3057 select2.getSettings().mediaUpload({
2362 3058 filesList: [item.file],
2363 3059 additionalData: item.additionalData,
2364 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,
2365 3066 onFileChange: ([attachment]) => {
2366 3067 if (attachment && !(0, import_blob.isBlobURL)(attachment.url)) {
2367 - dispatch.finishOperation(id, {
2368 - attachment
2369 - });
3068 + finishUpload(attachment);
2370 3069 }
2371 3070 },
2372 3071 onSuccess: ([attachment]) => {
2373 - dispatch.finishOperation(id, {
2374 - attachment
2375 - });
3072 + finishUpload(attachment);
2376 3073 },
2377 3074 onError: (error) => {
2378 3075 dispatch.cancelItem(id, error);
2379 3076 }
@@ -2391,8 +3088,9 @@
2391 3088 if (!mediaSideload) {
2392 3089 dispatch.finishOperation(id, {});
2393 3090 return;
2394 3091 }
3092 + const startTime = performance.now();
2395 3093 mediaSideload({
2396 3094 file: item.file,
2397 3095 attachmentId: post,
2398 3096 additionalData,
@@ -2397,8 +3095,17 @@
2397 3095 attachmentId: post,
2398 3096 additionalData,
2399 3097 signal: item.abortController?.signal,
2400 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 + });
2401 3108 if (item.parentId) {
2402 3109 dispatch({
2403 3110 type: Type.AccumulateSubSize,
2404 3111 id: item.parentId,
@@ -2424,21 +3131,36 @@
2424 3131 file: item.file
2425 3132 });
2426 3133 return;
2427 3134 }
3135 + const startTime = performance.now();
2428 3136 const addSuffix = Boolean(item.parentId);
2429 3137 const scaledSuffix = Boolean(args.isThresholdResize);
3138 + const { imageStripMeta, imageMaxBitDepth } = select2.getSettings();
2430 3139 try {
2431 3140 const file = await vipsResizeImage(
2432 3141 item.id,
2433 3142 item.file,
2434 3143 args.resize,
2435 - false,
2436 - // smartCrop
2437 - addSuffix,
2438 - item.abortController?.signal,
2439 - 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 + }
2440 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 + });
2441 3163 const blobUrl = (0, import_blob.createBlobURL)(file);
2442 3164 dispatch({
2443 3165 type: Type.CacheBlobUrl,
2444 3166 id,
@@ -2453,10 +3175,10 @@
2453 3175 } catch (error) {
2454 3176 dispatch.cancelItem(
2455 3177 id,
2456 3178 new UploadError({
2457 - code: "IMAGE_TRANSCODING_ERROR",
2458 - message: (0, import_i18n6.__)(
3179 + code: ErrorCode.IMAGE_TRANSCODING_ERROR,
3180 + message: (0, import_i18n7.__)(
2459 3181 "The web server cannot generate responsive image sizes for this image. Convert it to JPEG or PNG before uploading."
2460 3182 ),
2461 3183 file: item.file,
2462 3184 cause: error instanceof Error ? error : void 0
@@ -2476,8 +3198,9 @@
2476 3198 file: item.file
2477 3199 });
2478 3200 return;
2479 3201 }
3202 + const startTime = performance.now();
2480 3203 try {
2481 3204 const file = await vipsRotateImage(
2482 3205 item.id,
2483 3206 item.file,
@@ -2483,8 +3206,17 @@
2483 3206 item.file,
2484 3207 args.orientation,
2485 3208 item.abortController?.signal
2486 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 + });
2487 3219 const blobUrl = (0, import_blob.createBlobURL)(file);
2488 3220 dispatch({
2489 3221 type: Type.CacheBlobUrl,
2490 3222 id,
@@ -2499,10 +3231,10 @@
2499 3231 } catch (error) {
2500 3232 dispatch.cancelItem(
2501 3233 id,
2502 3234 new UploadError({
2503 - code: "IMAGE_ROTATION_ERROR",
2504 - message: (0, import_i18n6.__)(
3235 + code: ErrorCode.IMAGE_ROTATION_ERROR,
3236 + message: (0, import_i18n7.__)(
2505 3237 "The web server cannot generate responsive image sizes for this image. Convert it to JPEG or PNG before uploading."
2506 3238 ),
2507 3239 file: item.file,
2508 3240 cause: error instanceof Error ? error : void 0
@@ -2522,19 +3254,34 @@
2522 3254 file: item.file
2523 3255 });
2524 3256 return;
2525 3257 }
3258 + const startTime = performance.now();
2526 3259 const outputMimeType = `image/${args.outputFormat}`;
2527 3260 const quality = args.outputQuality ?? DEFAULT_OUTPUT_QUALITY;
2528 3261 const interlaced = args.interlaced ?? false;
3262 + const { imageStripMeta, imageMaxBitDepth } = select2.getSettings();
2529 3263 try {
2530 3264 const file = await vipsConvertImageFormat(
2531 3265 item.id,
2532 3266 item.file,
2533 3267 outputMimeType,
2534 - quality,
2535 - interlaced
3268 + {
3269 + quality,
3270 + interlaced,
3271 + stripMeta: imageStripMeta,
3272 + maxBitdepth: imageMaxBitDepth
3273 + }
2536 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 + });
2537 3284 const blobUrl = (0, import_blob.createBlobURL)(file);
2538 3285 dispatch({
2539 3286 type: Type.CacheBlobUrl,
2540 3287 id,
@@ -2549,9 +3296,9 @@
2549 3296 } catch (error) {
2550 3297 dispatch.cancelItem(
2551 3298 id,
2552 3299 new UploadError({
2553 - code: "MEDIA_TRANSCODING_ERROR",
3300 + code: ErrorCode.MEDIA_TRANSCODING_ERROR,
2554 3301 message: "Image could not be transcoded to the target format",
2555 3302 file: item.file,
2556 3303 cause: error instanceof Error ? error : void 0
2557 3304 })
@@ -2558,8 +3305,91 @@
2558 3305 );
2559 3306 }
2560 3307 };
2561 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 + }
2562 3392 function generateThumbnails(id) {
2563 3393 return async ({ select: select2, dispatch }) => {
2564 3394 const item = select2.getItem(id);
2565 3395 if (!item) {
@@ -2577,35 +3407,57 @@
2577 3407 batchId: v4_default(),
2578 3408 parentId: item.id,
2579 3409 additionalData: {
2580 3410 post: attachment.id,
2581 - image_size: "original-heic",
3411 + image_size: "source_original",
2582 3412 convert_format: false
2583 3413 },
2584 3414 operations: [OperationType.Upload]
2585 3415 });
2586 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;
2587 3450 {
2588 - const needsRotation = attachment.exif_orientation && attachment.exif_orientation !== 1 && !item.file.name.includes("-scaled");
2589 - if (needsRotation && attachment.id) {
3451 + const needsRotation = exifOrientation !== 1 && !item.file.name.includes("-scaled");
3452 + if ((needsRotation || needsClientRotation) && attachment.id) {
2590 3453 try {
2591 - const rotatedFile = await vipsRotateImage(
3454 + rotatedSource = await vipsRotateImage(
2592 3455 item.id,
2593 3456 item.sourceFile,
2594 - attachment.exif_orientation,
3457 + exifOrientation,
2595 3458 item.abortController?.signal
2596 3459 );
2597 - dispatch.addSideloadItem({
2598 - file: rotatedFile,
2599 - batchId: v4_default(),
2600 - parentId: item.id,
2601 - additionalData: {
2602 - post: attachment.id,
2603 - image_size: "original",
2604 - convert_format: false
2605 - },
2606 - operations: [OperationType.Upload]
2607 - });
2608 3460 } catch {
2609 3461 console.warn(
2610 3462 "Failed to rotate image, continuing with thumbnails"
2611 3463 );
@@ -2610,24 +3462,48 @@
2610 3462 "Failed to rotate image, continuing with thumbnails"
2611 3463 );
2612 3464 }
2613 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 + }
2614 3479 }
2615 3480 if (!item.parentId && attachment.missing_image_sizes && attachment.missing_image_sizes.length > 0) {
2616 3481 const allImageSizes = settings.allImageSizes || {};
2617 3482 const sizesToGenerate = attachment.missing_image_sizes;
2618 - const thumbnailSource = item.sourceFile;
3483 + const thumbnailSource = needsClientRotation && rotatedSource ? rotatedSource : item.sourceFile;
2619 3484 const file = attachment.filename ? renameFile(thumbnailSource, attachment.filename) : thumbnailSource;
2620 3485 const batchId = v4_default();
2621 3486 const isUltraHdr = ultraHdrItems.has(item.id);
2622 3487 const outputMimeType = attachment.image_output_format;
2623 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 + };
2624 3499 let thumbnailTranscodeOperation = null;
2625 3500 if (!isUltraHdr && outputMimeType) {
2626 3501 thumbnailTranscodeOperation = await getTranscodeImageOperation(
2627 3502 thumbnailSource,
2628 3503 outputMimeType,
2629 - interlaced
3504 + interlaced,
3505 + defaultQuality
2630 3506 );
2631 3507 }
2632 3508 const dimensionGroups = /* @__PURE__ */ new Map();
2633 3509 for (const name of sizesToGenerate) {
@@ -2647,13 +3523,23 @@
2647 3523 }
2648 3524 }
2649 3525 for (const [, names] of dimensionGroups) {
2650 3526 const imageSize = allImageSizes[names[0]];
3527 + const sizeQuality = qualityForSize(names[0]);
2651 3528 const thumbnailOperations = [
2652 - [OperationType.ResizeCrop, { resize: imageSize }]
3529 + [
3530 + OperationType.ResizeCrop,
3531 + { resize: imageSize, quality: sizeQuality }
3532 + ]
2653 3533 ];
2654 3534 if (!isUltraHdr && thumbnailTranscodeOperation) {
2655 - thumbnailOperations.push(thumbnailTranscodeOperation);
3535 + thumbnailOperations.push([
3536 + thumbnailTranscodeOperation[0],
3537 + {
3538 + ...thumbnailTranscodeOperation[1],
3539 + outputQuality: sizeQuality
3540 + }
3541 + ]);
2656 3542 }
2657 3543 thumbnailOperations.push(OperationType.Upload);
2658 3544 const imageSizeParam = names.length === 1 ? names[0] : names;
2659 3545 dispatch.addSideloadItem({
@@ -2685,9 +3571,10 @@
2685 3571 resize: {
2686 3572 width: bigImageSizeThreshold,
2687 3573 height: bigImageSizeThreshold
2688 3574 },
2689 - isThresholdResize: true
3575 + isThresholdResize: true,
3576 + quality: defaultQuality
2690 3577 }
2691 3578 ]
2692 3579 ];
2693 3580 if (!isUltraHdr && thumbnailTranscodeOperation) {
@@ -2733,8 +3620,17 @@
2733 3620 updates.attachment = updatedAttachment;
2734 3621 }
2735 3622 } catch (error) {
2736 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;
2737 3633 }
2738 3634 }
2739 3635 dispatch.finishOperation(id, updates);
2740 3636 };
@@ -2840,7 +3736,115 @@
2840 3736 }, [settings, updateSettings2]);
2841 3737 return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
2842 3738 });
2843 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 + }
2844 3846 return __toCommonJS(index_exports);
3847 +})();
3848 +(window.wp ||= {}).uploadMedia = wp.uploadMedia;
2845 3849 })();
2846 3850 //# sourceMappingURL=index.js.map