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 +103 -12 24.0.0 → trunk View file →
@@ -107,8 +107,9 @@
107 107 getHeicConversionAdvice: () => getHeicConversionAdvice,
108 108 getHeicUnsupportedMessage: () => getHeicUnsupportedMessage,
109 109 isClientSideMediaSupported: () => isClientSideMediaSupported,
110 110 isHeicCanvasSupported: () => isHeicCanvasSupported,
111 + isHeicFile: () => isHeicFile,
111 112 store: () => store
112 113 });
113 114
114 115 // packages/upload-media/build-module/store/index.mjs
@@ -259,9 +260,20 @@
259 260 ...item,
260 261 status: ItemStatus.Processing,
261 262 error: void 0,
262 263 retryCount: (item.retryCount ?? 0) + 1,
263 - 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
264 276 } : item
265 277 )
266 278 };
267 279 case Type.ScheduleRetry:
@@ -665,8 +677,56 @@
665 677 }
666 678 }
667 679 return false;
668 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 + }
669 729
670 730 // packages/upload-media/build-module/store/utils/index.mjs
671 731 var vipsModulePromise;
672 732 var vipsModule;
@@ -2073,8 +2133,9 @@
2073 2133 return `${cause} ${getHeicConversionAdvice()}`;
2074 2134 }
2075 2135
2076 2136 // packages/upload-media/build-module/canvas-utils.mjs
2137 + var IMAGE_DECODER_TIMEOUT = 15e3;
2077 2138 var HeicUnsupportedError = class extends Error {
2078 2139 };
2079 2140 async function canvasConvertToJpeg(file, quality = 0.82) {
2080 2141 const baseName = getFileBasename(file.name);
@@ -2105,10 +2166,36 @@
2105 2166 const decoder = new ImageDecoder({
2106 2167 type: file.type,
2107 2168 data: file.stream()
2108 2169 });
2170 + let videoFrame;
2171 + let timeoutId;
2172 + let timedOut = false;
2109 2173 try {
2110 - 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) {
2111 2198 try {
2112 2199 const canvas = new OffscreenCanvas(
2113 2200 videoFrame.displayWidth,
2114 2201 videoFrame.displayHeight
@@ -2127,10 +2214,8 @@
2127 2214 });
2128 2215 } finally {
2129 2216 videoFrame.close();
2130 2217 }
2131 - } finally {
2132 - decoder.close();
2133 2218 }
2134 2219 }
2135 2220 }
2136 2221 let heicData;
@@ -2554,8 +2639,11 @@
2554 2639 const item = select2.getItem(id);
2555 2640 if (!item) {
2556 2641 return;
2557 2642 }
2643 + if (item.currentOperation) {
2644 + return;
2645 + }
2558 2646 const {
2559 2647 attachment,
2560 2648 onChange,
2561 2649 onSuccess,
@@ -2833,13 +2921,16 @@
2833 2921 }
2834 2922 }
2835 2923 }
2836 2924 let heicJpeg = null;
2837 - const isImage = file.type.startsWith("image/");
2838 - const isVipsSupported = CLIENT_SIDE_SUPPORTED_MIME_TYPES.includes(
2839 - file.type
2840 - );
2841 - 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;
2842 2933 let tooLargeForClient = false;
2843 2934 if (isImage && isVipsSupported) {
2844 2935 const dimensions = await getImageDimensions(file);
2845 2936 if (dimensions && exceedsClientProcessingMemory(dimensions)) {
@@ -2845,9 +2936,9 @@
2845 2936 if (dimensions && exceedsClientProcessingMemory(dimensions)) {
2846 2937 tooLargeForClient = true;
2847 2938 }
2848 2939 }
2849 - if (file.type === "image/jpeg" && !tooLargeForClient) {
2940 + if (file.type === "image/jpeg" && !isHeic && !tooLargeForClient) {
2850 2941 operations.push(OperationType.DetectUltraHdr);
2851 2942 }
2852 2943 if (isImage && isVipsSupported && !tooLargeForClient) {
2853 2944 operations.push(
@@ -2857,9 +2948,9 @@
2857 2948 );
2858 2949 } else if (isImage && isHeic) {
2859 2950 try {
2860 2951 heicJpeg = await canvasConvertToJpeg(
2861 - file,
2952 + heicFile,
2862 2953 settings.imageQuality ?? DEFAULT_OUTPUT_QUALITY
2863 2954 );
2864 2955 } catch (error) {
2865 2956 const unsupported = error instanceof HeicUnsupportedError;
@@ -2894,9 +2985,9 @@
2894 2985 const vipsAvailable = isClientSideMediaSupported();
2895 2986 updates = {
2896 2987 file: heicJpeg,
2897 2988 sourceFile: heicJpeg,
2898 - originalHeicFile: item.file,
2989 + originalHeicFile: heicFile,
2899 2990 additionalData: {
2900 2991 ...item.additionalData,
2901 2992 generate_sub_sizes: !vipsAvailable,
2902 2993 convert_format: true