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 +1119 -115 23.4.0 → 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;
@@ -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