| @@ -1,4 +1,5 @@ | ||
| 1 | +(function() { | |
| 1 | 2 | "use strict"; |
| 2 | 3 | var wp; |
| 3 | 4 | (wp ||= {}).uploadMedia = (() => { |
| 4 | 5 | var __create = Object.create; |
| @@ -44,8 +45,15 @@ | ||
| 44 | 45 | module.exports = window.wp.data; |
| 45 | 46 | } |
| 46 | 47 | }); |
| 47 | 48 | |
| 49 | + // package-external:@wordpress/i18n | |
| 50 | + var require_i18n = __commonJS({ | |
| 51 | + "package-external:@wordpress/i18n"(exports, module) { | |
| 52 | + module.exports = window.wp.i18n; | |
| 53 | + } | |
| 54 | + }); | |
| 55 | + | |
| 48 | 56 | // package-external:@wordpress/url |
| 49 | 57 | var require_url = __commonJS({ |
| 50 | 58 | "package-external:@wordpress/url"(exports, module) { |
| 51 | 59 | module.exports = window.wp.url; |
| @@ -51,15 +59,8 @@ | ||
| 51 | 59 | module.exports = window.wp.url; |
| 52 | 60 | } |
| 53 | 61 | }); |
| 54 | 62 | |
| 55 | - // package-external:@wordpress/i18n | |
| 56 | - var require_i18n = __commonJS({ | |
| 57 | - "package-external:@wordpress/i18n"(exports, module) { | |
| 58 | - module.exports = window.wp.i18n; | |
| 59 | - } | |
| 60 | - }); | |
| 61 | - | |
| 62 | 63 | // package-external:@wordpress/blob |
| 63 | 64 | var require_blob = __commonJS({ |
| 64 | 65 | "package-external:@wordpress/blob"(exports, module) { |
| 65 | 66 | module.exports = window.wp.blob; |
| @@ -96,14 +97,19 @@ | ||
| 96 | 97 | |
| 97 | 98 | // packages/upload-media/build-module/index.mjs |
| 98 | 99 | var index_exports = {}; |
| 99 | 100 | __export(index_exports, { |
| 101 | + ErrorCode: () => ErrorCode, | |
| 100 | 102 | MediaUploadProvider: () => provider_default, |
| 101 | 103 | UploadError: () => UploadError, |
| 102 | 104 | clearFeatureDetectionCache: () => clearFeatureDetectionCache, |
| 103 | 105 | detectClientSideMediaSupport: () => detectClientSideMediaSupport, |
| 106 | + getErrorMessage: () => getErrorMessage, | |
| 107 | + getHeicConversionAdvice: () => getHeicConversionAdvice, | |
| 108 | + getHeicUnsupportedMessage: () => getHeicUnsupportedMessage, | |
| 104 | 109 | isClientSideMediaSupported: () => isClientSideMediaSupported, |
| 105 | 110 | isHeicCanvasSupported: () => isHeicCanvasSupported, |
| 111 | + isHeicFile: () => isHeicFile, | |
| 106 | 112 | store: () => store |
| 107 | 113 | }); |
| 108 | 114 | |
| 109 | 115 | // packages/upload-media/build-module/store/index.mjs |
| @@ -116,8 +122,9 @@ | ||
| 116 | 122 | Type2["Prepare"] = "PREPARE_ITEM"; |
| 117 | 123 | Type2["Cancel"] = "CANCEL_ITEM"; |
| 118 | 124 | Type2["Remove"] = "REMOVE_ITEM"; |
| 119 | 125 | Type2["RetryItem"] = "RETRY_ITEM"; |
| 126 | + Type2["ScheduleRetry"] = "SCHEDULE_RETRY"; | |
| 120 | 127 | Type2["PauseItem"] = "PAUSE_ITEM"; |
| 121 | 128 | Type2["ResumeItem"] = "RESUME_ITEM"; |
| 122 | 129 | Type2["PauseQueue"] = "PAUSE_QUEUE"; |
| 123 | 130 | Type2["ResumeQueue"] = "RESUME_QUEUE"; |
| @@ -134,8 +141,9 @@ | ||
| 134 | 141 | var ItemStatus = /* @__PURE__ */ ((ItemStatus2) => { |
| 135 | 142 | ItemStatus2["Queued"] = "QUEUED"; |
| 136 | 143 | ItemStatus2["Processing"] = "PROCESSING"; |
| 137 | 144 | ItemStatus2["Paused"] = "PAUSED"; |
| 145 | + ItemStatus2["PendingRetry"] = "PENDING_RETRY"; | |
| 138 | 146 | ItemStatus2["Uploaded"] = "UPLOADED"; |
| 139 | 147 | ItemStatus2["Error"] = "ERROR"; |
| 140 | 148 | return ItemStatus2; |
| 141 | 149 | })(ItemStatus || {}); |
| @@ -144,10 +152,12 @@ | ||
| 144 | 152 | OperationType2["Upload"] = "UPLOAD"; |
| 145 | 153 | OperationType2["ResizeCrop"] = "RESIZE_CROP"; |
| 146 | 154 | OperationType2["Rotate"] = "ROTATE"; |
| 147 | 155 | OperationType2["TranscodeImage"] = "TRANSCODE_IMAGE"; |
| 156 | + OperationType2["TranscodeGif"] = "TRANSCODE_GIF"; | |
| 148 | 157 | OperationType2["ThumbnailGeneration"] = "THUMBNAIL_GENERATION"; |
| 149 | 158 | OperationType2["Finalize"] = "FINALIZE"; |
| 159 | + OperationType2["DetectUltraHdr"] = "DETECT_ULTRAHDR"; | |
| 150 | 160 | return OperationType2; |
| 151 | 161 | })(OperationType || {}); |
| 152 | 162 | |
| 153 | 163 | // packages/upload-media/build-module/store/constants.mjs |
| @@ -153,8 +163,15 @@ | ||
| 153 | 163 | // packages/upload-media/build-module/store/constants.mjs |
| 154 | 164 | var STORE_NAME = "core/upload-media"; |
| 155 | 165 | var DEFAULT_MAX_CONCURRENT_UPLOADS = 5; |
| 156 | 166 | var DEFAULT_MAX_CONCURRENT_IMAGE_PROCESSING = 2; |
| 167 | + var DEFAULT_RETRY_SETTINGS = { | |
| 168 | + maxRetryAttempts: 3, | |
| 169 | + initialRetryDelayMs: 1e3, | |
| 170 | + maxRetryDelayMs: 3e4, | |
| 171 | + backoffMultiplier: 2, | |
| 172 | + retryJitter: 0.1 | |
| 173 | + }; | |
| 157 | 174 | var CLIENT_SIDE_SUPPORTED_MIME_TYPES = [ |
| 158 | 175 | "image/jpeg", |
| 159 | 176 | "image/png", |
| 160 | 177 | "image/gif", |
| @@ -175,10 +192,12 @@ | ||
| 175 | 192 | blobUrls: {}, |
| 176 | 193 | settings: { |
| 177 | 194 | mediaUpload: noop, |
| 178 | 195 | maxConcurrentUploads: DEFAULT_MAX_CONCURRENT_UPLOADS, |
| 179 | - maxConcurrentImageProcessing: DEFAULT_MAX_CONCURRENT_IMAGE_PROCESSING | |
| 180 | - } | |
| 196 | + maxConcurrentImageProcessing: DEFAULT_MAX_CONCURRENT_IMAGE_PROCESSING, | |
| 197 | + retry: { ...DEFAULT_RETRY_SETTINGS } | |
| 198 | + }, | |
| 199 | + failureCount: 0 | |
| 181 | 200 | }; |
| 182 | 201 | function reducer(state = DEFAULT_STATE, action = { type: Type.Unknown }) { |
| 183 | 202 | switch (action.type) { |
| 184 | 203 | case Type.PauseQueue: { |
| @@ -217,11 +236,15 @@ | ||
| 217 | 236 | return { |
| 218 | 237 | ...state, |
| 219 | 238 | queue: [...state.queue, action.item] |
| 220 | 239 | }; |
| 221 | - case Type.Cancel: | |
| 240 | + case Type.Cancel: { | |
| 241 | + const cancelled = state.queue.find( | |
| 242 | + (item) => item.id === action.id | |
| 243 | + ); | |
| 222 | 244 | return { |
| 223 | 245 | ...state, |
| 246 | + failureCount: cancelled && !cancelled.parentId ? state.failureCount + 1 : state.failureCount, | |
| 224 | 247 | queue: state.queue.map( |
| 225 | 248 | (item) => item.id === action.id ? { |
| 226 | 249 | ...item, |
| 227 | 250 | error: action.error |
| @@ -227,8 +250,9 @@ | ||
| 227 | 250 | error: action.error |
| 228 | 251 | } : item |
| 229 | 252 | ) |
| 230 | 253 | }; |
| 254 | + } | |
| 231 | 255 | case Type.RetryItem: |
| 232 | 256 | return { |
| 233 | 257 | ...state, |
| 234 | 258 | queue: state.queue.map( |
| @@ -235,12 +259,37 @@ | ||
| 235 | 259 | (item) => item.id === action.id ? { |
| 236 | 260 | ...item, |
| 237 | 261 | status: ItemStatus.Processing, |
| 238 | 262 | error: void 0, |
| 239 | - retryCount: (item.retryCount ?? 0) + 1 | |
| 263 | + retryCount: (item.retryCount ?? 0) + 1, | |
| 264 | + abortController: new AbortController(), | |
| 265 | + /* | |
| 266 | + * The failed operation is still recorded on | |
| 267 | + * the item: nothing finishes it when it | |
| 268 | + * fails, and while the item waits out the | |
| 269 | + * backoff that is what keeps it out of the | |
| 270 | + * concurrency pools. Clear it now that the | |
| 271 | + * item is about to run again, so processItem | |
| 272 | + * does not mistake it for an operation still | |
| 273 | + * in flight and skip the retry. | |
| 274 | + */ | |
| 275 | + currentOperation: void 0 | |
| 240 | 276 | } : item |
| 241 | 277 | ) |
| 242 | 278 | }; |
| 279 | + case Type.ScheduleRetry: | |
| 280 | + return { | |
| 281 | + ...state, | |
| 282 | + queue: state.queue.map( | |
| 283 | + (item) => item.id === action.id ? { | |
| 284 | + ...item, | |
| 285 | + status: ItemStatus.PendingRetry, | |
| 286 | + error: action.error, | |
| 287 | + retryCount: action.retryCount, | |
| 288 | + nextRetryTimestamp: action.nextRetryTimestamp | |
| 289 | + } : item | |
| 290 | + ) | |
| 291 | + }; | |
| 243 | 292 | case Type.Remove: |
| 244 | 293 | return { |
| 245 | 294 | ...state, |
| 246 | 295 | queue: state.queue.filter((item) => item.id !== action.id) |
| @@ -385,15 +434,18 @@ | ||
| 385 | 434 | var private_selectors_exports = {}; |
| 386 | 435 | __export(private_selectors_exports, { |
| 387 | 436 | getActiveImageProcessingCount: () => getActiveImageProcessingCount, |
| 388 | 437 | getActiveUploadCount: () => getActiveUploadCount, |
| 438 | + getActiveVideoProcessingCount: () => getActiveVideoProcessingCount, | |
| 389 | 439 | getAllItems: () => getAllItems, |
| 390 | 440 | getBlobUrls: () => getBlobUrls, |
| 391 | 441 | getFailedItems: () => getFailedItems, |
| 442 | + getFailureCount: () => getFailureCount, | |
| 392 | 443 | getItem: () => getItem, |
| 393 | 444 | getItemProgress: () => getItemProgress, |
| 394 | 445 | getPendingImageProcessing: () => getPendingImageProcessing, |
| 395 | 446 | getPendingUploads: () => getPendingUploads, |
| 447 | + getPendingVideoProcessing: () => getPendingVideoProcessing, | |
| 396 | 448 | hasPendingItemsByParentId: () => hasPendingItemsByParentId, |
| 397 | 449 | isBatchUploaded: () => isBatchUploaded, |
| 398 | 450 | isPaused: () => isPaused |
| 399 | 451 | }); |
| @@ -399,8 +451,11 @@ | ||
| 399 | 451 | }); |
| 400 | 452 | function getAllItems(state) { |
| 401 | 453 | return state.queue; |
| 402 | 454 | } |
| 455 | + function getFailureCount(state) { | |
| 456 | + return state.failureCount; | |
| 457 | + } | |
| 403 | 458 | function getItem(state, id) { |
| 404 | 459 | return state.queue.find((item) => item.id === id); |
| 405 | 460 | } |
| 406 | 461 | function isBatchUploaded(state, batchId) { |
| @@ -430,8 +485,13 @@ | ||
| 430 | 485 | return state.queue.filter( |
| 431 | 486 | (item) => item.currentOperation === OperationType.ResizeCrop || item.currentOperation === OperationType.Rotate |
| 432 | 487 | ).length; |
| 433 | 488 | } |
| 489 | + function getActiveVideoProcessingCount(state) { | |
| 490 | + return state.queue.filter( | |
| 491 | + (item) => item.currentOperation === OperationType.TranscodeGif | |
| 492 | + ).length; | |
| 493 | + } | |
| 434 | 494 | function getPendingImageProcessing(state) { |
| 435 | 495 | return state.queue.filter((item) => { |
| 436 | 496 | const nextOperation = Array.isArray(item.operations?.[0]) ? item.operations[0][0] : item.operations?.[0]; |
| 437 | 497 | return (nextOperation === OperationType.ResizeCrop || nextOperation === OperationType.Rotate) && item.currentOperation !== OperationType.ResizeCrop && item.currentOperation !== OperationType.Rotate; |
| @@ -436,8 +496,14 @@ | ||
| 436 | 496 | const nextOperation = Array.isArray(item.operations?.[0]) ? item.operations[0][0] : item.operations?.[0]; |
| 437 | 497 | return (nextOperation === OperationType.ResizeCrop || nextOperation === OperationType.Rotate) && item.currentOperation !== OperationType.ResizeCrop && item.currentOperation !== OperationType.Rotate; |
| 438 | 498 | }); |
| 439 | 499 | } |
| 500 | + function getPendingVideoProcessing(state) { | |
| 501 | + return state.queue.filter((item) => { | |
| 502 | + const nextOperation = Array.isArray(item.operations?.[0]) ? item.operations[0][0] : item.operations?.[0]; | |
| 503 | + return nextOperation === OperationType.TranscodeGif && item.currentOperation !== OperationType.TranscodeGif; | |
| 504 | + }); | |
| 505 | + } | |
| 440 | 506 | function getFailedItems(state) { |
| 441 | 507 | return state.queue.filter((item) => item.error !== void 0); |
| 442 | 508 | } |
| 443 | 509 | function hasPendingItemsByParentId(state, parentId) { |
| @@ -452,50 +518,48 @@ | ||
| 452 | 518 | var actions_exports = {}; |
| 453 | 519 | __export(actions_exports, { |
| 454 | 520 | addItems: () => addItems, |
| 455 | 521 | cancelItem: () => cancelItem, |
| 456 | - retryItem: () => retryItem | |
| 522 | + executeRetry: () => executeRetry, | |
| 523 | + retryItem: () => retryItem, | |
| 524 | + scheduleRetry: () => scheduleRetry | |
| 457 | 525 | }); |
| 458 | 526 | |
| 459 | - // node_modules/uuid/dist/esm-browser/rng.js | |
| 460 | - var getRandomValues; | |
| 461 | - var rnds8 = new Uint8Array(16); | |
| 462 | - function rng() { | |
| 463 | - if (!getRandomValues) { | |
| 464 | - getRandomValues = typeof crypto !== "undefined" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto); | |
| 465 | - if (!getRandomValues) { | |
| 466 | - throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported"); | |
| 467 | - } | |
| 468 | - } | |
| 469 | - return getRandomValues(rnds8); | |
| 470 | - } | |
| 471 | - | |
| 472 | - // node_modules/uuid/dist/esm-browser/stringify.js | |
| 527 | + // node_modules/uuid/dist/stringify.js | |
| 473 | 528 | var byteToHex = []; |
| 474 | 529 | for (let i = 0; i < 256; ++i) { |
| 475 | 530 | byteToHex.push((i + 256).toString(16).slice(1)); |
| 476 | 531 | } |
| 477 | 532 | function unsafeStringify(arr, offset = 0) { |
| 478 | - return byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]; | |
| 533 | + return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); | |
| 479 | 534 | } |
| 480 | 535 | |
| 481 | - // node_modules/uuid/dist/esm-browser/native.js | |
| 482 | - var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto); | |
| 483 | - var native_default = { | |
| 484 | - randomUUID | |
| 485 | - }; | |
| 536 | + // node_modules/uuid/dist/rng.js | |
| 537 | + var rnds8 = new Uint8Array(16); | |
| 538 | + function rng() { | |
| 539 | + return crypto.getRandomValues(rnds8); | |
| 540 | + } | |
| 486 | 541 | |
| 487 | - // node_modules/uuid/dist/esm-browser/v4.js | |
| 542 | + // node_modules/uuid/dist/v4.js | |
| 488 | 543 | function v4(options, buf, offset) { |
| 489 | - if (native_default.randomUUID && !buf && !options) { | |
| 490 | - return native_default.randomUUID(); | |
| 544 | + if (!buf && !options && crypto.randomUUID) { | |
| 545 | + return crypto.randomUUID(); | |
| 491 | 546 | } |
| 547 | + return _v4(options, buf, offset); | |
| 548 | + } | |
| 549 | + function _v4(options, buf, offset) { | |
| 492 | 550 | options = options || {}; |
| 493 | - const rnds = options.random || (options.rng || rng)(); | |
| 551 | + const rnds = options.random ?? options.rng?.() ?? rng(); | |
| 552 | + if (rnds.length < 16) { | |
| 553 | + throw new Error("Random bytes length must be >= 16"); | |
| 554 | + } | |
| 494 | 555 | rnds[6] = rnds[6] & 15 | 64; |
| 495 | 556 | rnds[8] = rnds[8] & 63 | 128; |
| 496 | 557 | if (buf) { |
| 497 | 558 | offset = offset || 0; |
| 559 | + if (offset < 0 || offset + 16 > buf.length) { | |
| 560 | + throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`); | |
| 561 | + } | |
| 498 | 562 | for (let i = 0; i < 16; ++i) { |
| 499 | 563 | buf[offset + i] = rnds[i]; |
| 500 | 564 | } |
| 501 | 565 | return buf; |
| @@ -503,8 +567,50 @@ | ||
| 503 | 567 | return unsafeStringify(rnds); |
| 504 | 568 | } |
| 505 | 569 | var v4_default = v4; |
| 506 | 570 | |
| 571 | + // packages/upload-media/build-module/store/actions.mjs | |
| 572 | + var import_i18n5 = __toESM(require_i18n(), 1); | |
| 573 | + | |
| 574 | + // packages/upload-media/build-module/store/utils/retry.mjs | |
| 575 | + function calculateRetryDelay(options) { | |
| 576 | + const { attempt, initialDelay, maxDelay, multiplier, jitter } = options; | |
| 577 | + const exponentialDelay = initialDelay * Math.pow(multiplier, attempt - 1); | |
| 578 | + const cappedDelay = Math.min(exponentialDelay, maxDelay); | |
| 579 | + const jitterFactor = 1 + (Math.random() * 2 - 1) * jitter; | |
| 580 | + return Math.floor(cappedDelay * jitterFactor); | |
| 581 | + } | |
| 582 | + var RETRYABLE_MESSAGE_PATTERNS = [ | |
| 583 | + /network/i, | |
| 584 | + /timeout/i, | |
| 585 | + /ECONNRESET/i, | |
| 586 | + /fetch failed/i, | |
| 587 | + /connection/i, | |
| 588 | + /socket/i, | |
| 589 | + /ETIMEDOUT/i, | |
| 590 | + /ENOTFOUND/i, | |
| 591 | + /Could not get a valid response/i, | |
| 592 | + /Failed to fetch/i, | |
| 593 | + /Load failed/i | |
| 594 | + ]; | |
| 595 | + function shouldRetryError(error, retryCount, maxRetries) { | |
| 596 | + if (retryCount >= maxRetries) { | |
| 597 | + return false; | |
| 598 | + } | |
| 599 | + const message = typeof error === "string" ? error : error?.message || ""; | |
| 600 | + return RETRYABLE_MESSAGE_PATTERNS.some( | |
| 601 | + (pattern) => pattern.test(message) | |
| 602 | + ); | |
| 603 | + } | |
| 604 | + var retryTimers = /* @__PURE__ */ new Map(); | |
| 605 | + function clearRetryTimer(id) { | |
| 606 | + const pendingTimer = retryTimers.get(id); | |
| 607 | + if (pendingTimer !== void 0) { | |
| 608 | + clearTimeout(pendingTimer); | |
| 609 | + retryTimers.delete(id); | |
| 610 | + } | |
| 611 | + } | |
| 612 | + | |
| 507 | 613 | // packages/upload-media/build-module/image-file.mjs |
| 508 | 614 | var ImageFile = class extends File { |
| 509 | 615 | width = 0; |
| 510 | 616 | height = 0; |
| @@ -555,8 +661,72 @@ | ||
| 555 | 661 | } |
| 556 | 662 | function getFileBasename(name) { |
| 557 | 663 | return name.includes(".") ? name.split(".").slice(0, -1).join(".") : name; |
| 558 | 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 | + } | |
| 559 | 729 | |
| 560 | 730 | // packages/upload-media/build-module/store/utils/index.mjs |
| 561 | 731 | var vipsModulePromise; |
| 562 | 732 | var vipsModule; |
| @@ -570,9 +740,9 @@ | ||
| 570 | 740 | ); |
| 571 | 741 | } |
| 572 | 742 | return vipsModulePromise; |
| 573 | 743 | } |
| 574 | - async function vipsConvertImageFormat(id, file, type, quality, interlaced) { | |
| 744 | + async function vipsConvertImageFormat(id, file, type, options = {}) { | |
| 575 | 745 | const { vipsConvertImageFormat: convertImageFormat } = await loadVipsModule(); |
| 576 | 746 | const buffer = await convertImageFormat( |
| 577 | 747 | id, |
| 578 | 748 | await file.arrayBuffer(), |
| @@ -577,10 +747,9 @@ | ||
| 577 | 747 | id, |
| 578 | 748 | await file.arrayBuffer(), |
| 579 | 749 | file.type, |
| 580 | 750 | type, |
| 581 | - quality, | |
| 582 | - interlaced | |
| 751 | + options | |
| 583 | 752 | ); |
| 584 | 753 | const ext = type.split("/")[1]; |
| 585 | 754 | const fileName = `${getFileBasename(file.name)}.${ext}`; |
| 586 | 755 | return new File([new Blob([buffer])], fileName, { |
| @@ -594,21 +763,32 @@ | ||
| 594 | 763 | throw new Error(`Failed to fetch image: ${response.status}`); |
| 595 | 764 | } |
| 596 | 765 | return hasTransparency(await response.arrayBuffer()); |
| 597 | 766 | } |
| 598 | - async function vipsResizeImage(id, file, resize, smartCrop, addSuffix, signal, scaledSuffix, quality) { | |
| 767 | + async function vipsGetUltraHdrInfo(buffer) { | |
| 768 | + const { vipsGetUltraHdrInfo: getUltraHdrInfo } = await loadVipsModule(); | |
| 769 | + return getUltraHdrInfo(buffer); | |
| 770 | + } | |
| 771 | + async function vipsResizeImage(id, file, resize, options = {}) { | |
| 772 | + const { | |
| 773 | + smartCrop = false, | |
| 774 | + addSuffix = false, | |
| 775 | + signal, | |
| 776 | + scaledSuffix, | |
| 777 | + quality, | |
| 778 | + stripMeta, | |
| 779 | + maxBitdepth | |
| 780 | + } = options; | |
| 599 | 781 | if (signal?.aborted) { |
| 600 | 782 | throw new Error("Operation aborted"); |
| 601 | 783 | } |
| 602 | 784 | const { vipsResizeImage: resizeImage } = await loadVipsModule(); |
| 603 | - const { buffer, width, height, originalWidth, originalHeight } = await resizeImage( | |
| 604 | - id, | |
| 605 | - await file.arrayBuffer(), | |
| 606 | - file.type, | |
| 607 | - resize, | |
| 785 | + const { buffer, width, height, originalWidth, originalHeight } = await resizeImage(id, await file.arrayBuffer(), file.type, resize, { | |
| 608 | 786 | smartCrop, |
| 609 | - quality | |
| 610 | - ); | |
| 787 | + quality, | |
| 788 | + stripMeta, | |
| 789 | + maxBitdepth | |
| 790 | + }); | |
| 611 | 791 | let fileName = file.name; |
| 612 | 792 | const wasResized = originalWidth > width || originalHeight > height; |
| 613 | 793 | if (wasResized) { |
| 614 | 794 | const basename = getFileBasename(file.name); |
| @@ -675,13 +855,172 @@ | ||
| 675 | 855 | if (vipsModule) { |
| 676 | 856 | vipsModule.terminateVipsWorker(); |
| 677 | 857 | } |
| 678 | 858 | } |
| 859 | + var completedVipsOperations = 0; | |
| 860 | + var MAX_VIPS_OPS_BEFORE_RECYCLE = 50; | |
| 861 | + function maybeRecycleVipsWorker(activeImageProcessingCount) { | |
| 862 | + completedVipsOperations++; | |
| 863 | + if (completedVipsOperations >= MAX_VIPS_OPS_BEFORE_RECYCLE && activeImageProcessingCount === 0) { | |
| 864 | + terminateVipsWorker(); | |
| 865 | + completedVipsOperations = 0; | |
| 866 | + } | |
| 867 | + } | |
| 679 | 868 | |
| 680 | - // packages/upload-media/build-module/validate-mime-type.mjs | |
| 681 | - var import_i18n2 = __toESM(require_i18n(), 1); | |
| 869 | + // packages/upload-media/build-module/store/utils/video-conversion.mjs | |
| 870 | + var UNSUPPORTED_ERROR_PREFIX = "Unsupported"; | |
| 871 | + var SIZE_LIMIT_ERROR_PREFIX = `${UNSUPPORTED_ERROR_PREFIX}: GIF exceeds maximum conversion size`; | |
| 872 | + var CONVERSION_TIMEOUT_ERROR_PREFIX = "GIF to video conversion timed out"; | |
| 873 | + var DEFAULT_CONVERSION_TIMEOUT = 3e4; | |
| 874 | + function isUnsupportedConversionError(error) { | |
| 875 | + return error instanceof Error && error.message.startsWith(UNSUPPORTED_ERROR_PREFIX); | |
| 876 | + } | |
| 877 | + function isSizeLimitConversionError(error) { | |
| 878 | + return error instanceof Error && error.message.startsWith(SIZE_LIMIT_ERROR_PREFIX); | |
| 879 | + } | |
| 880 | + function isConversionTimeoutError(error) { | |
| 881 | + return error instanceof Error && error.message.startsWith(CONVERSION_TIMEOUT_ERROR_PREFIX); | |
| 882 | + } | |
| 883 | + var videoConversionModulePromise; | |
| 884 | + var videoConversionModule; | |
| 885 | + function loadVideoConversionModule() { | |
| 886 | + if (!videoConversionModulePromise) { | |
| 887 | + videoConversionModulePromise = import("@wordpress/video-conversion/worker").then((mod) => { | |
| 888 | + videoConversionModule = mod; | |
| 889 | + return mod; | |
| 890 | + }).catch((error) => { | |
| 891 | + videoConversionModulePromise = void 0; | |
| 892 | + throw error; | |
| 893 | + }); | |
| 894 | + } | |
| 895 | + return videoConversionModulePromise; | |
| 896 | + } | |
| 897 | + async function convertGifToVideo(id, file, outputMimeType, { | |
| 898 | + maxDimensions, | |
| 899 | + timeout = DEFAULT_CONVERSION_TIMEOUT, | |
| 900 | + maxTotalPixels | |
| 901 | + } = {}) { | |
| 902 | + const mod = await loadVideoConversionModule(); | |
| 903 | + const conversion = mod.convertGifToVideo( | |
| 904 | + id, | |
| 905 | + file, | |
| 906 | + outputMimeType, | |
| 907 | + maxDimensions, | |
| 908 | + maxTotalPixels | |
| 909 | + ); | |
| 910 | + let buffer; | |
| 911 | + if (timeout > 0) { | |
| 912 | + let timer; | |
| 913 | + try { | |
| 914 | + buffer = await Promise.race([ | |
| 915 | + conversion, | |
| 916 | + new Promise((_, reject) => { | |
| 917 | + timer = setTimeout(() => { | |
| 918 | + reject( | |
| 919 | + new Error( | |
| 920 | + `${CONVERSION_TIMEOUT_ERROR_PREFIX} after ${timeout}ms` | |
| 921 | + ) | |
| 922 | + ); | |
| 923 | + }, timeout); | |
| 924 | + }) | |
| 925 | + ]); | |
| 926 | + } catch (error) { | |
| 927 | + if (isConversionTimeoutError(error)) { | |
| 928 | + conversion.catch(() => { | |
| 929 | + }); | |
| 930 | + mod.cancelGifToVideoOperations(id).catch(() => { | |
| 931 | + }); | |
| 932 | + } | |
| 933 | + throw error; | |
| 934 | + } finally { | |
| 935 | + clearTimeout(timer); | |
| 936 | + } | |
| 937 | + } else { | |
| 938 | + buffer = await conversion; | |
| 939 | + } | |
| 940 | + const ext = outputMimeType === "video/webm" ? "webm" : "mp4"; | |
| 941 | + const fileName = `${getFileBasename(file.name)}.${ext}`; | |
| 942 | + return new File( | |
| 943 | + [new Blob([buffer], { type: outputMimeType })], | |
| 944 | + fileName, | |
| 945 | + { type: outputMimeType } | |
| 946 | + ); | |
| 947 | + } | |
| 948 | + async function cancelGifToVideoOperations(id) { | |
| 949 | + const mod = videoConversionModule ?? (videoConversionModulePromise ? await videoConversionModulePromise.catch(() => void 0) : void 0); | |
| 950 | + if (!mod) { | |
| 951 | + return false; | |
| 952 | + } | |
| 953 | + return mod.cancelGifToVideoOperations(id); | |
| 954 | + } | |
| 955 | + function terminateVideoConversionWorker() { | |
| 956 | + if (videoConversionModule) { | |
| 957 | + videoConversionModule.terminateVideoConversionWorker(); | |
| 958 | + return; | |
| 959 | + } | |
| 960 | + if (videoConversionModulePromise) { | |
| 961 | + void videoConversionModulePromise.then((mod) => mod.terminateVideoConversionWorker()).catch(() => { | |
| 962 | + }); | |
| 963 | + } | |
| 964 | + } | |
| 682 | 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 | + | |
| 683 | 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 || {}); | |
| 684 | 1023 | var UploadError = class extends Error { |
| 685 | 1024 | code; |
| 686 | 1025 | file; |
| 687 | 1026 | constructor({ code, message, file, cause }) { |
| @@ -692,8 +1031,9 @@ | ||
| 692 | 1031 | } |
| 693 | 1032 | }; |
| 694 | 1033 | |
| 695 | 1034 | // packages/upload-media/build-module/validate-mime-type.mjs |
| 1035 | + var import_i18n2 = __toESM(require_i18n(), 1); | |
| 696 | 1036 | function validateMimeType(file, allowedTypes) { |
| 697 | 1037 | if (!allowedTypes) { |
| 698 | 1038 | return; |
| 699 | 1039 | } |
| @@ -704,9 +1044,9 @@ | ||
| 704 | 1044 | return file.type.startsWith(`${allowedType}/`); |
| 705 | 1045 | }); |
| 706 | 1046 | if (file.type && !isAllowedType) { |
| 707 | 1047 | throw new UploadError({ |
| 708 | - code: "MIME_TYPE_NOT_SUPPORTED", | |
| 1048 | + code: ErrorCode.MIME_TYPE_NOT_SUPPORTED, | |
| 709 | 1049 | message: (0, import_i18n2.sprintf)( |
| 710 | 1050 | // translators: %s: file name. |
| 711 | 1051 | (0, import_i18n2.__)("%s: Sorry, this file type is not supported here."), |
| 712 | 1052 | file.name |
| @@ -748,9 +1088,9 @@ | ||
| 748 | 1088 | file.type |
| 749 | 1089 | ); |
| 750 | 1090 | if (file.type && !isAllowedMimeTypeForUser) { |
| 751 | 1091 | throw new UploadError({ |
| 752 | - code: "MIME_TYPE_NOT_ALLOWED_FOR_USER", | |
| 1092 | + code: ErrorCode.MIME_TYPE_NOT_ALLOWED_FOR_USER, | |
| 753 | 1093 | message: (0, import_i18n3.sprintf)( |
| 754 | 1094 | // translators: %s: file name. |
| 755 | 1095 | (0, import_i18n3.__)( |
| 756 | 1096 | "%s: Sorry, you are not allowed to upload this file type." |
| @@ -766,9 +1106,9 @@ | ||
| 766 | 1106 | var import_i18n4 = __toESM(require_i18n(), 1); |
| 767 | 1107 | function validateFileSize(file, maxUploadFileSize) { |
| 768 | 1108 | if (file.size <= 0) { |
| 769 | 1109 | throw new UploadError({ |
| 770 | - code: "EMPTY_FILE", | |
| 1110 | + code: ErrorCode.EMPTY_FILE, | |
| 771 | 1111 | message: (0, import_i18n4.sprintf)( |
| 772 | 1112 | // translators: %s: file name. |
| 773 | 1113 | (0, import_i18n4.__)("%s: This file is empty."), |
| 774 | 1114 | file.name |
| @@ -777,9 +1117,9 @@ | ||
| 777 | 1117 | }); |
| 778 | 1118 | } |
| 779 | 1119 | if (maxUploadFileSize && file.size > maxUploadFileSize) { |
| 780 | 1120 | throw new UploadError({ |
| 781 | - code: "SIZE_ABOVE_LIMIT", | |
| 1121 | + code: ErrorCode.SIZE_ABOVE_LIMIT, | |
| 782 | 1122 | message: (0, import_i18n4.sprintf)( |
| 783 | 1123 | // translators: %s: file name. |
| 784 | 1124 | (0, import_i18n4.__)( |
| 785 | 1125 | "%s: This file exceeds the maximum upload size for this site." |
| @@ -840,17 +1180,38 @@ | ||
| 840 | 1180 | const item = select2.getItem(id); |
| 841 | 1181 | if (!item) { |
| 842 | 1182 | return; |
| 843 | 1183 | } |
| 1184 | + clearRetryTimer(id); | |
| 1185 | + if (!silent && error && !item.parentId && !item.attachment?.id) { | |
| 1186 | + const settings = select2.getSettings(); | |
| 1187 | + const retrySettings = settings.retry; | |
| 1188 | + if (retrySettings) { | |
| 1189 | + const retryCount = item.retryCount ?? 0; | |
| 1190 | + const maxRetries = retrySettings.maxRetryAttempts; | |
| 1191 | + if (shouldRetryError(error, retryCount, maxRetries)) { | |
| 1192 | + dispatch.scheduleRetry(id, error); | |
| 1193 | + return; | |
| 1194 | + } | |
| 1195 | + } | |
| 1196 | + } | |
| 844 | 1197 | item.abortController?.abort(); |
| 845 | - await vipsCancelOperations(id); | |
| 1198 | + vipsCancelOperations(id).catch(() => { | |
| 1199 | + }); | |
| 1200 | + cancelGifToVideoOperations(id).catch(() => { | |
| 1201 | + }); | |
| 846 | 1202 | if (!silent) { |
| 847 | 1203 | const { onError } = item; |
| 848 | 1204 | onError?.(error ?? new Error("Upload cancelled")); |
| 849 | - if (!onError && error) { | |
| 1205 | + if (!onError && error && !item.parentId) { | |
| 850 | 1206 | console.error("Upload cancelled", error); |
| 851 | 1207 | } |
| 1208 | + } else { | |
| 1209 | + debug( | |
| 1210 | + `Item cancelled: ${item.file.name} (item ${id}): ${error instanceof Error ? error.message : error}` | |
| 1211 | + ); | |
| 852 | 1212 | } |
| 1213 | + const { currentOperation, parentId, batchId } = item; | |
| 853 | 1214 | dispatch({ |
| 854 | 1215 | type: Type.Cancel, |
| 855 | 1216 | id, |
| 856 | 1217 | error |
| @@ -856,9 +1217,59 @@ | ||
| 856 | 1217 | error |
| 857 | 1218 | }); |
| 858 | 1219 | dispatch.removeItem(id); |
| 859 | 1220 | dispatch.revokeBlobUrls(id); |
| 860 | - if (item.batchId && select2.isBatchUploaded(item.batchId)) { | |
| 1221 | + if (currentOperation === OperationType.ResizeCrop || currentOperation === OperationType.Rotate) { | |
| 1222 | + for (const pending of select2.getPendingImageProcessing()) { | |
| 1223 | + dispatch.processItem(pending.id); | |
| 1224 | + } | |
| 1225 | + } | |
| 1226 | + if (currentOperation === OperationType.Upload) { | |
| 1227 | + for (const pending of select2.getPendingUploads()) { | |
| 1228 | + dispatch.processItem(pending.id); | |
| 1229 | + } | |
| 1230 | + } | |
| 1231 | + if (currentOperation === OperationType.TranscodeGif) { | |
| 1232 | + for (const pending of select2.getPendingVideoProcessing()) { | |
| 1233 | + dispatch.processItem(pending.id); | |
| 1234 | + } | |
| 1235 | + } | |
| 1236 | + if (currentOperation === OperationType.ResizeCrop || currentOperation === OperationType.Rotate || currentOperation === OperationType.TranscodeImage) { | |
| 1237 | + maybeRecycleVipsWorker(select2.getActiveImageProcessingCount()); | |
| 1238 | + } | |
| 1239 | + if (parentId) { | |
| 1240 | + const parentItem = select2.getItem(parentId); | |
| 1241 | + if (parentItem) { | |
| 1242 | + const isOptionalCompanion = item.additionalData?.image_size === "animated_video" || item.additionalData?.image_size === "animated_video_poster"; | |
| 1243 | + if (select2.hasPendingItemsByParentId(parentId)) { | |
| 1244 | + if (parentItem.operations && parentItem.operations.length > 0) { | |
| 1245 | + dispatch.processItem(parentId); | |
| 1246 | + } | |
| 1247 | + } else if (parentItem.subSizes && parentItem.subSizes.length > 0 || isOptionalCompanion) { | |
| 1248 | + if (parentItem.operations && parentItem.operations.length > 0) { | |
| 1249 | + dispatch.processItem(parentId); | |
| 1250 | + } | |
| 1251 | + } else { | |
| 1252 | + const parentAttachmentId = parentItem.attachment?.id; | |
| 1253 | + const { mediaDelete } = select2.getSettings(); | |
| 1254 | + if (parentAttachmentId && mediaDelete) { | |
| 1255 | + mediaDelete(parentAttachmentId).catch(() => { | |
| 1256 | + }); | |
| 1257 | + } | |
| 1258 | + await dispatch.cancelItem( | |
| 1259 | + parentId, | |
| 1260 | + new UploadError({ | |
| 1261 | + code: error instanceof UploadError && error.code || ErrorCode.GENERAL, | |
| 1262 | + message: error?.message || (0, import_i18n5.__)("The image could not be uploaded."), | |
| 1263 | + file: parentItem.file, | |
| 1264 | + cause: error instanceof Error ? error : void 0 | |
| 1265 | + }) | |
| 1266 | + ); | |
| 1267 | + } | |
| 1268 | + } | |
| 1269 | + } | |
| 1270 | + if (batchId && select2.isBatchUploaded(batchId)) { | |
| 1271 | + debug(`Batch completed: ${batchId}`); | |
| 861 | 1272 | item.onBatchSuccess?.(); |
| 862 | 1273 | } |
| 863 | 1274 | }; |
| 864 | 1275 | } |
| @@ -877,8 +1288,57 @@ | ||
| 877 | 1288 | }); |
| 878 | 1289 | dispatch.processItem(id); |
| 879 | 1290 | }; |
| 880 | 1291 | } |
| 1292 | + function scheduleRetry(id, error) { | |
| 1293 | + return async ({ select: select2, dispatch }) => { | |
| 1294 | + const item = select2.getItem(id); | |
| 1295 | + if (!item) { | |
| 1296 | + return; | |
| 1297 | + } | |
| 1298 | + const settings = select2.getSettings(); | |
| 1299 | + const retrySettings = settings.retry; | |
| 1300 | + if (!retrySettings) { | |
| 1301 | + return; | |
| 1302 | + } | |
| 1303 | + const currentRetryCount = item.retryCount ?? 0; | |
| 1304 | + const delay = calculateRetryDelay({ | |
| 1305 | + attempt: currentRetryCount + 1, | |
| 1306 | + initialDelay: retrySettings.initialRetryDelayMs, | |
| 1307 | + maxDelay: retrySettings.maxRetryDelayMs, | |
| 1308 | + multiplier: retrySettings.backoffMultiplier, | |
| 1309 | + jitter: retrySettings.retryJitter | |
| 1310 | + }); | |
| 1311 | + const timerId = setTimeout(() => { | |
| 1312 | + retryTimers.delete(id); | |
| 1313 | + dispatch.executeRetry(id); | |
| 1314 | + }, delay); | |
| 1315 | + retryTimers.set(id, timerId); | |
| 1316 | + dispatch({ | |
| 1317 | + type: Type.ScheduleRetry, | |
| 1318 | + id, | |
| 1319 | + error, | |
| 1320 | + retryCount: currentRetryCount, | |
| 1321 | + nextRetryTimestamp: Date.now() + delay | |
| 1322 | + }); | |
| 1323 | + }; | |
| 1324 | + } | |
| 1325 | + function executeRetry(id) { | |
| 1326 | + return async ({ select: select2, dispatch }) => { | |
| 1327 | + const item = select2.getItem(id); | |
| 1328 | + if (!item || item.status !== ItemStatus.PendingRetry) { | |
| 1329 | + return; | |
| 1330 | + } | |
| 1331 | + if (select2.isPaused()) { | |
| 1332 | + return; | |
| 1333 | + } | |
| 1334 | + dispatch({ | |
| 1335 | + type: Type.RetryItem, | |
| 1336 | + id | |
| 1337 | + }); | |
| 1338 | + dispatch.processItem(id); | |
| 1339 | + }; | |
| 1340 | + } | |
| 881 | 1341 | |
| 882 | 1342 | // packages/upload-media/build-module/store/private-actions.mjs |
| 883 | 1343 | var private_actions_exports = {}; |
| 884 | 1344 | __export(private_actions_exports, { |
| @@ -883,8 +1343,9 @@ | ||
| 883 | 1343 | var private_actions_exports = {}; |
| 884 | 1344 | __export(private_actions_exports, { |
| 885 | 1345 | addItem: () => addItem, |
| 886 | 1346 | addSideloadItem: () => addSideloadItem, |
| 1347 | + detectUltraHdr: () => detectUltraHdr, | |
| 887 | 1348 | finalizeItem: () => finalizeItem, |
| 888 | 1349 | finishOperation: () => finishOperation, |
| 889 | 1350 | generateThumbnails: () => generateThumbnails, |
| 890 | 1351 | getTranscodeImageOperation: () => getTranscodeImageOperation, |
| @@ -897,8 +1358,9 @@ | ||
| 897 | 1358 | resumeQueue: () => resumeQueue, |
| 898 | 1359 | revokeBlobUrls: () => revokeBlobUrls, |
| 899 | 1360 | rotateItem: () => rotateItem, |
| 900 | 1361 | sideloadItem: () => sideloadItem, |
| 1362 | + transcodeGifItem: () => transcodeGifItem, | |
| 901 | 1363 | transcodeImageItem: () => transcodeImageItem, |
| 902 | 1364 | updateItemProgress: () => updateItemProgress, |
| 903 | 1365 | updateSettings: () => updateSettings, |
| 904 | 1366 | uploadItem: () => uploadItem |
| @@ -903,8 +1365,9 @@ | ||
| 903 | 1365 | updateSettings: () => updateSettings, |
| 904 | 1366 | uploadItem: () => uploadItem |
| 905 | 1367 | }); |
| 906 | 1368 | var import_blob = __toESM(require_blob(), 1); |
| 1369 | + var import_i18n7 = __toESM(require_i18n(), 1); | |
| 907 | 1370 | |
| 908 | 1371 | // packages/upload-media/build-module/heic-parser.mjs |
| 909 | 1372 | var Reader = class { |
| 910 | 1373 | view; |
| @@ -1175,8 +1638,15 @@ | ||
| 1175 | 1638 | return `hvc1.${spacePrefix}${profileIdc}.${compatHex}.${tierChar}${levelIdc}${constraintStr}`; |
| 1176 | 1639 | } |
| 1177 | 1640 | function readItemData(buffer, loc, idatOffset) { |
| 1178 | 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 | + } | |
| 1179 | 1649 | if (loc.extents.length === 1) { |
| 1180 | 1650 | const ext = loc.extents[0]; |
| 1181 | 1651 | const start = baseOffset + ext.offset; |
| 1182 | 1652 | return new Uint8Array(buffer.slice(start, start + ext.length)); |
| @@ -1311,9 +1781,10 @@ | ||
| 1311 | 1781 | tileWidth: width, |
| 1312 | 1782 | tileHeight: height, |
| 1313 | 1783 | outputWidth: width, |
| 1314 | 1784 | outputHeight: height, |
| 1315 | - rotation | |
| 1785 | + rotation, | |
| 1786 | + exifOrientation: rotation === 0 ? getUnappliedExifOrientation(buffer) : 1 | |
| 1316 | 1787 | }; |
| 1317 | 1788 | } |
| 1318 | 1789 | function parseGridImage(r, buffer, gridItemId, locations, allAssoc, properties, irefBox, idatOffset) { |
| 1319 | 1790 | const gridLoc = locations.get(gridItemId); |
| @@ -1404,13 +1875,269 @@ | ||
| 1404 | 1875 | tileWidth, |
| 1405 | 1876 | tileHeight, |
| 1406 | 1877 | outputWidth, |
| 1407 | 1878 | outputHeight, |
| 1408 | - rotation | |
| 1879 | + rotation, | |
| 1880 | + exifOrientation: rotation === 0 ? getUnappliedExifOrientation(buffer) : 1 | |
| 1409 | 1881 | }; |
| 1410 | 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 | + } | |
| 1411 | 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 | + | |
| 1412 | 2136 | // packages/upload-media/build-module/canvas-utils.mjs |
| 2137 | + var IMAGE_DECODER_TIMEOUT = 15e3; | |
| 2138 | + var HeicUnsupportedError = class extends Error { | |
| 2139 | + }; | |
| 1413 | 2140 | async function canvasConvertToJpeg(file, quality = 0.82) { |
| 1414 | 2141 | const baseName = getFileBasename(file.name); |
| 1415 | 2142 | try { |
| 1416 | 2143 | const bitmap = await createImageBitmap(file); |
| @@ -1439,10 +2166,36 @@ | ||
| 1439 | 2166 | const decoder = new ImageDecoder({ |
| 1440 | 2167 | type: file.type, |
| 1441 | 2168 | data: file.stream() |
| 1442 | 2169 | }); |
| 2170 | + let videoFrame; | |
| 2171 | + let timeoutId; | |
| 2172 | + let timedOut = false; | |
| 1443 | 2173 | try { |
| 1444 | - 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) { | |
| 1445 | 2198 | try { |
| 1446 | 2199 | const canvas = new OffscreenCanvas( |
| 1447 | 2200 | videoFrame.displayWidth, |
| 1448 | 2201 | videoFrame.displayHeight |
| @@ -1461,57 +2214,62 @@ | ||
| 1461 | 2214 | }); |
| 1462 | 2215 | } finally { |
| 1463 | 2216 | videoFrame.close(); |
| 1464 | 2217 | } |
| 1465 | - } finally { | |
| 1466 | - decoder.close(); | |
| 1467 | 2218 | } |
| 1468 | 2219 | } |
| 1469 | 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 | + } | |
| 1470 | 2229 | if (typeof VideoDecoder !== "undefined") { |
| 2230 | + let supported = false; | |
| 1471 | 2231 | try { |
| 1472 | - const heicData = parseHeic(await file.arrayBuffer()); | |
| 1473 | 2232 | const support = await VideoDecoder.isConfigSupported({ |
| 1474 | 2233 | codec: heicData.codecString |
| 1475 | 2234 | }); |
| 1476 | - if (support.supported) { | |
| 1477 | - const canvas = new OffscreenCanvas( | |
| 1478 | - heicData.outputWidth, | |
| 1479 | - 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 | |
| 1480 | 2254 | ); |
| 1481 | - const ctx = canvas.getContext("2d"); | |
| 1482 | - if (!ctx) { | |
| 1483 | - throw new Error("Could not get canvas 2d context"); | |
| 2255 | + try { | |
| 2256 | + ctx.drawImage(frame, tile.x, tile.y); | |
| 2257 | + } finally { | |
| 2258 | + frame.close(); | |
| 1484 | 2259 | } |
| 1485 | - for (const tile of heicData.tiles) { | |
| 1486 | - const frame = await decodeHevcFrame( | |
| 1487 | - heicData.codecString, | |
| 1488 | - heicData.description, | |
| 1489 | - heicData.tileWidth, | |
| 1490 | - heicData.tileHeight, | |
| 1491 | - tile.data | |
| 1492 | - ); | |
| 1493 | - try { | |
| 1494 | - ctx.drawImage(frame, tile.x, tile.y); | |
| 1495 | - } finally { | |
| 1496 | - frame.close(); | |
| 1497 | - } | |
| 1498 | - } | |
| 1499 | - const outputCanvas = applyRotation(canvas, heicData.rotation); | |
| 1500 | - const jpegBlob = await outputCanvas.convertToBlob({ | |
| 1501 | - type: "image/jpeg", | |
| 1502 | - quality | |
| 1503 | - }); | |
| 1504 | - return new File([jpegBlob], `${baseName}.jpg`, { | |
| 1505 | - type: "image/jpeg" | |
| 1506 | - }); | |
| 1507 | 2260 | } |
| 1508 | - } 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 | + }); | |
| 1509 | 2269 | } |
| 1510 | 2270 | } |
| 1511 | - throw new Error( | |
| 1512 | - "This browser cannot decode HEIC images. Please use Safari or convert to JPEG before uploading." | |
| 1513 | - ); | |
| 2271 | + throw new HeicUnsupportedError(getHeicUnsupportedMessage()); | |
| 1514 | 2272 | } |
| 1515 | 2273 | function applyRotation(source, rotation) { |
| 1516 | 2274 | if (rotation === 0) { |
| 1517 | 2275 | return source; |
| @@ -1528,8 +2286,45 @@ | ||
| 1528 | 2286 | ctx.rotate(-rotation * Math.PI / 180); |
| 1529 | 2287 | ctx.drawImage(source, -source.width / 2, -source.height / 2); |
| 1530 | 2288 | return rotated; |
| 1531 | 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 | + } | |
| 1532 | 2327 | function decodeHevcFrame(codec, description, width, height, data) { |
| 1533 | 2328 | return new Promise((resolve, reject) => { |
| 1534 | 2329 | const decoder = new VideoDecoder({ |
| 1535 | 2330 | output: (frame) => { |
| @@ -1656,9 +2451,94 @@ | ||
| 1656 | 2451 | } |
| 1657 | 2452 | function clearFeatureDetectionCache() { |
| 1658 | 2453 | cachedResult = null; |
| 1659 | 2454 | } |
| 2455 | + var BYTES_PER_PIXEL = 4; | |
| 2456 | + var INTERLACED_MEMORY_BUDGET = 0.5 * 1024 * 1024 * 1024; | |
| 2457 | + var BASELINE_MEMORY_BUDGET = 0.9 * 1024 * 1024 * 1024; | |
| 2458 | + function exceedsClientProcessingMemory(dimensions) { | |
| 2459 | + const { width, height, interlaced } = dimensions; | |
| 2460 | + const estimatedBytes = width * height * BYTES_PER_PIXEL; | |
| 2461 | + const budget = interlaced ? INTERLACED_MEMORY_BUDGET : BASELINE_MEMORY_BUDGET; | |
| 2462 | + return estimatedBytes > budget; | |
| 2463 | + } | |
| 1660 | 2464 | |
| 2465 | + // packages/upload-media/build-module/get-image-dimensions.mjs | |
| 2466 | + var MAX_HEADER_BYTES = 512 * 1024; | |
| 2467 | + function parseJpeg(view) { | |
| 2468 | + let offset = 2; | |
| 2469 | + while (offset < view.byteLength) { | |
| 2470 | + if (view.getUint8(offset) !== 255) { | |
| 2471 | + return null; | |
| 2472 | + } | |
| 2473 | + while (offset < view.byteLength && view.getUint8(offset) === 255) { | |
| 2474 | + offset++; | |
| 2475 | + } | |
| 2476 | + if (offset >= view.byteLength) { | |
| 2477 | + return null; | |
| 2478 | + } | |
| 2479 | + const marker = view.getUint8(offset); | |
| 2480 | + offset++; | |
| 2481 | + if (marker === 1 || marker >= 208 && marker <= 217) { | |
| 2482 | + continue; | |
| 2483 | + } | |
| 2484 | + if (marker === 218) { | |
| 2485 | + return null; | |
| 2486 | + } | |
| 2487 | + if (offset + 2 > view.byteLength) { | |
| 2488 | + return null; | |
| 2489 | + } | |
| 2490 | + const segmentLength = view.getUint16(offset); | |
| 2491 | + if (segmentLength < 2) { | |
| 2492 | + return null; | |
| 2493 | + } | |
| 2494 | + const isStartOfFrame = marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204; | |
| 2495 | + if (isStartOfFrame) { | |
| 2496 | + if (offset + 7 > view.byteLength) { | |
| 2497 | + return null; | |
| 2498 | + } | |
| 2499 | + const height = view.getUint16(offset + 3); | |
| 2500 | + const width = view.getUint16(offset + 5); | |
| 2501 | + const interlaced = marker === 194 || marker === 198 || marker === 202 || marker === 206; | |
| 2502 | + return { width, height, interlaced }; | |
| 2503 | + } | |
| 2504 | + offset += segmentLength; | |
| 2505 | + } | |
| 2506 | + return null; | |
| 2507 | + } | |
| 2508 | + function parsePng(view) { | |
| 2509 | + if (view.byteLength < 29) { | |
| 2510 | + return null; | |
| 2511 | + } | |
| 2512 | + const isIhdr = view.getUint8(12) === 73 && // I | |
| 2513 | + view.getUint8(13) === 72 && // H | |
| 2514 | + view.getUint8(14) === 68 && // D | |
| 2515 | + view.getUint8(15) === 82; | |
| 2516 | + if (!isIhdr) { | |
| 2517 | + return null; | |
| 2518 | + } | |
| 2519 | + const width = view.getUint32(16); | |
| 2520 | + const height = view.getUint32(20); | |
| 2521 | + const interlaceMethod = view.getUint8(28); | |
| 2522 | + return { width, height, interlaced: interlaceMethod !== 0 }; | |
| 2523 | + } | |
| 2524 | + async function getImageDimensions(file) { | |
| 2525 | + try { | |
| 2526 | + const headerBytes = Math.min(file.size, MAX_HEADER_BYTES); | |
| 2527 | + const buffer = await file.slice(0, headerBytes).arrayBuffer(); | |
| 2528 | + const view = new DataView(buffer); | |
| 2529 | + if (view.byteLength >= 3 && view.getUint16(0) === 65496) { | |
| 2530 | + return parseJpeg(view); | |
| 2531 | + } | |
| 2532 | + if (view.byteLength >= 8 && view.getUint32(0) === 2303741511 && view.getUint32(4) === 218765834) { | |
| 2533 | + return parsePng(view); | |
| 2534 | + } | |
| 2535 | + return null; | |
| 2536 | + } catch { | |
| 2537 | + return null; | |
| 2538 | + } | |
| 2539 | + } | |
| 2540 | + | |
| 1661 | 2541 | // packages/upload-media/build-module/stub-file.mjs |
| 1662 | 2542 | var StubFile = class extends File { |
| 1663 | 2543 | constructor(fileName = "stub-file") { |
| 1664 | 2544 | super([], fileName); |
| @@ -1666,8 +2546,9 @@ | ||
| 1666 | 2546 | }; |
| 1667 | 2547 | |
| 1668 | 2548 | // packages/upload-media/build-module/store/private-actions.mjs |
| 1669 | 2549 | var DEFAULT_OUTPUT_QUALITY = 0.82; |
| 2550 | + var ultraHdrItems = /* @__PURE__ */ new Set(); | |
| 1670 | 2551 | function addItem({ |
| 1671 | 2552 | file: fileOrBlob, |
| 1672 | 2553 | batchId, |
| 1673 | 2554 | onChange, |
| @@ -1758,8 +2639,11 @@ | ||
| 1758 | 2639 | const item = select2.getItem(id); |
| 1759 | 2640 | if (!item) { |
| 1760 | 2641 | return; |
| 1761 | 2642 | } |
| 2643 | + if (item.currentOperation) { | |
| 2644 | + return; | |
| 2645 | + } | |
| 1762 | 2646 | const { |
| 1763 | 2647 | attachment, |
| 1764 | 2648 | onChange, |
| 1765 | 2649 | onSuccess, |
| @@ -1782,8 +2666,14 @@ | ||
| 1782 | 2666 | if (activeCount >= settings.maxConcurrentImageProcessing) { |
| 1783 | 2667 | return; |
| 1784 | 2668 | } |
| 1785 | 2669 | } |
| 2670 | + if (operation === OperationType.TranscodeGif) { | |
| 2671 | + const activeCount = select2.getActiveVideoProcessingCount(); | |
| 2672 | + if (activeCount >= 1) { | |
| 2673 | + return; | |
| 2674 | + } | |
| 2675 | + } | |
| 1786 | 2676 | if (attachment) { |
| 1787 | 2677 | const isHeicUrl = attachment.url && /\.hei[cf]$/i.test(attachment.url); |
| 1788 | 2678 | if (!isHeicUrl) { |
| 1789 | 2679 | onChange?.([attachment]); |
| @@ -1827,8 +2717,11 @@ | ||
| 1827 | 2717 | type: Type.OperationStart, |
| 1828 | 2718 | id, |
| 1829 | 2719 | operation |
| 1830 | 2720 | }); |
| 2721 | + debug( | |
| 2722 | + `Starting operation ${operation} for ${item.file.name} (item ${item.id})` | |
| 2723 | + ); | |
| 1831 | 2724 | switch (operation) { |
| 1832 | 2725 | case OperationType.Prepare: |
| 1833 | 2726 | dispatch.prepareItem(item.id); |
| 1834 | 2727 | break; |
| @@ -1849,8 +2742,14 @@ | ||
| 1849 | 2742 | item.id, |
| 1850 | 2743 | operationArgs |
| 1851 | 2744 | ); |
| 1852 | 2745 | break; |
| 2746 | + case OperationType.TranscodeGif: | |
| 2747 | + dispatch.transcodeGifItem( | |
| 2748 | + item.id, | |
| 2749 | + operationArgs | |
| 2750 | + ); | |
| 2751 | + break; | |
| 1853 | 2752 | case OperationType.Upload: |
| 1854 | 2753 | if (item.parentId) { |
| 1855 | 2754 | dispatch.sideloadItem(id); |
| 1856 | 2755 | } else { |
| @@ -1862,8 +2761,11 @@ | ||
| 1862 | 2761 | break; |
| 1863 | 2762 | case OperationType.Finalize: |
| 1864 | 2763 | dispatch.finalizeItem(id); |
| 1865 | 2764 | break; |
| 2765 | + case OperationType.DetectUltraHdr: | |
| 2766 | + dispatch.detectUltraHdr(id); | |
| 2767 | + break; | |
| 1866 | 2768 | } |
| 1867 | 2769 | }; |
| 1868 | 2770 | } |
| 1869 | 2771 | function pauseQueue() { |
| @@ -1876,9 +2778,13 @@ | ||
| 1876 | 2778 | dispatch({ |
| 1877 | 2779 | type: Type.ResumeQueue |
| 1878 | 2780 | }); |
| 1879 | 2781 | for (const item of select2.getAllItems()) { |
| 1880 | - dispatch.processItem(item.id); | |
| 2782 | + if (item.status === ItemStatus.PendingRetry) { | |
| 2783 | + dispatch.executeRetry(item.id); | |
| 2784 | + } else { | |
| 2785 | + dispatch.processItem(item.id); | |
| 2786 | + } | |
| 1881 | 2787 | } |
| 1882 | 2788 | }; |
| 1883 | 2789 | } |
| 1884 | 2790 | function pauseItem(id) { |
| @@ -1894,8 +2800,10 @@ | ||
| 1894 | 2800 | const item = select2.getItem(id); |
| 1895 | 2801 | if (!item) { |
| 1896 | 2802 | return; |
| 1897 | 2803 | } |
| 2804 | + ultraHdrItems.delete(id); | |
| 2805 | + clearRetryTimer(id); | |
| 1898 | 2806 | dispatch({ |
| 1899 | 2807 | type: Type.Remove, |
| 1900 | 2808 | id |
| 1901 | 2809 | }); |
| @@ -1900,8 +2808,9 @@ | ||
| 1900 | 2808 | id |
| 1901 | 2809 | }); |
| 1902 | 2810 | if (select2.getAllItems().length === 0) { |
| 1903 | 2811 | terminateVipsWorker(); |
| 2812 | + terminateVideoConversionWorker(); | |
| 1904 | 2813 | } |
| 1905 | 2814 | }; |
| 1906 | 2815 | } |
| 1907 | 2816 | function finishOperation(id, updates) { |
| @@ -1925,8 +2834,17 @@ | ||
| 1925 | 2834 | for (const pendingItem of pendingItems) { |
| 1926 | 2835 | dispatch.processItem(pendingItem.id); |
| 1927 | 2836 | } |
| 1928 | 2837 | } |
| 2838 | + if (previousOperation === OperationType.TranscodeGif) { | |
| 2839 | + const pendingItems = select2.getPendingVideoProcessing(); | |
| 2840 | + for (const pendingItem of pendingItems) { | |
| 2841 | + dispatch.processItem(pendingItem.id); | |
| 2842 | + } | |
| 2843 | + } | |
| 2844 | + if (previousOperation === OperationType.ResizeCrop || previousOperation === OperationType.Rotate || previousOperation === OperationType.TranscodeImage) { | |
| 2845 | + maybeRecycleVipsWorker(select2.getActiveImageProcessingCount()); | |
| 2846 | + } | |
| 1929 | 2847 | }; |
| 1930 | 2848 | } |
| 1931 | 2849 | var VALID_IMAGE_FORMATS = ["jpeg", "webp", "avif", "png", "gif"]; |
| 1932 | 2850 | function isValidImageFormat(format) { |
| @@ -1931,9 +2849,9 @@ | ||
| 1931 | 2849 | var VALID_IMAGE_FORMATS = ["jpeg", "webp", "avif", "png", "gif"]; |
| 1932 | 2850 | function isValidImageFormat(format) { |
| 1933 | 2851 | return VALID_IMAGE_FORMATS.includes(format); |
| 1934 | 2852 | } |
| 1935 | - async function getTranscodeImageOperation(file, outputMimeType, interlaced = false) { | |
| 2853 | + async function getTranscodeImageOperation(file, outputMimeType, interlaced = false, quality = DEFAULT_OUTPUT_QUALITY) { | |
| 1936 | 2854 | if (file.type === "image/png" && outputMimeType === "image/jpeg") { |
| 1937 | 2855 | const blobUrl = (0, import_blob.createBlobURL)(file); |
| 1938 | 2856 | try { |
| 1939 | 2857 | const hasAlpha = await vipsHasTransparency(blobUrl); |
| @@ -1953,9 +2871,9 @@ | ||
| 1953 | 2871 | return [ |
| 1954 | 2872 | OperationType.TranscodeImage, |
| 1955 | 2873 | { |
| 1956 | 2874 | outputFormat: formatPart, |
| 1957 | - outputQuality: DEFAULT_OUTPUT_QUALITY, | |
| 2875 | + outputQuality: quality, | |
| 1958 | 2876 | interlaced |
| 1959 | 2877 | } |
| 1960 | 2878 | ]; |
| 1961 | 2879 | } |
| @@ -1967,28 +2885,63 @@ | ||
| 1967 | 2885 | } |
| 1968 | 2886 | const { file } = item; |
| 1969 | 2887 | const operations = []; |
| 1970 | 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 | + } | |
| 1971 | 2924 | let heicJpeg = null; |
| 1972 | - const isImage = file.type.startsWith("image/"); | |
| 1973 | - const isVipsSupported = CLIENT_SIDE_SUPPORTED_MIME_TYPES.includes( | |
| 1974 | - file.type | |
| 1975 | - ); | |
| 1976 | - const isHeic = HEIC_MIME_TYPES.includes(file.type); | |
| 2925 | + const isHeic = await isHeicFile(file); | |
| 2926 | + const isMisnamedHeic = isHeic && !HEIC_MIME_TYPES.includes(file.type); | |
| 2927 | + const isImage = file.type.startsWith("image/") || isHeic; | |
| 2928 | + const isVipsSupported = !isHeic && CLIENT_SIDE_SUPPORTED_MIME_TYPES.includes(file.type); | |
| 2929 | + const heicFile = isMisnamedHeic ? new File([file], `${getFileBasename(file.name)}.heic`, { | |
| 2930 | + type: HEIC_MIME_TYPES[0], | |
| 2931 | + lastModified: file.lastModified | |
| 2932 | + }) : file; | |
| 2933 | + let tooLargeForClient = false; | |
| 1977 | 2934 | if (isImage && isVipsSupported) { |
| 1978 | - const { bigImageSizeThreshold } = settings; | |
| 1979 | - if (bigImageSizeThreshold) { | |
| 1980 | - operations.push([ | |
| 1981 | - OperationType.ResizeCrop, | |
| 1982 | - { | |
| 1983 | - resize: { | |
| 1984 | - width: bigImageSizeThreshold, | |
| 1985 | - height: bigImageSizeThreshold | |
| 1986 | - }, | |
| 1987 | - isThresholdResize: true | |
| 1988 | - } | |
| 1989 | - ]); | |
| 2935 | + const dimensions = await getImageDimensions(file); | |
| 2936 | + if (dimensions && exceedsClientProcessingMemory(dimensions)) { | |
| 2937 | + tooLargeForClient = true; | |
| 1990 | 2938 | } |
| 2939 | + } | |
| 2940 | + if (file.type === "image/jpeg" && !isHeic && !tooLargeForClient) { | |
| 2941 | + operations.push(OperationType.DetectUltraHdr); | |
| 2942 | + } | |
| 2943 | + if (isImage && isVipsSupported && !tooLargeForClient) { | |
| 1991 | 2944 | operations.push( |
| 1992 | 2945 | OperationType.Upload, |
| 1993 | 2946 | OperationType.ThumbnailGeneration, |
| 1994 | 2947 | OperationType.Finalize |
| @@ -1995,18 +2948,22 @@ | ||
| 1995 | 2948 | ); |
| 1996 | 2949 | } else if (isImage && isHeic) { |
| 1997 | 2950 | try { |
| 1998 | 2951 | heicJpeg = await canvasConvertToJpeg( |
| 1999 | - file, | |
| 2952 | + heicFile, | |
| 2000 | 2953 | settings.imageQuality ?? DEFAULT_OUTPUT_QUALITY |
| 2001 | 2954 | ); |
| 2002 | - } catch { | |
| 2955 | + } catch (error) { | |
| 2956 | + const unsupported = error instanceof HeicUnsupportedError; | |
| 2003 | 2957 | dispatch.cancelItem( |
| 2004 | 2958 | id, |
| 2005 | 2959 | new UploadError({ |
| 2006 | - code: "HEIC_DECODE_ERROR", | |
| 2007 | - message: "This browser cannot decode HEIC images and the server does not support them either. Please convert to JPEG before uploading.", | |
| 2008 | - 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 | |
| 2009 | 2966 | }) |
| 2010 | 2967 | ); |
| 2011 | 2968 | return; |
| 2012 | 2969 | } |
| @@ -2022,15 +2979,15 @@ | ||
| 2022 | 2979 | type: Type.AddOperations, |
| 2023 | 2980 | id, |
| 2024 | 2981 | operations |
| 2025 | 2982 | }); |
| 2026 | - let updates = {}; | |
| 2983 | + let updates; | |
| 2027 | 2984 | if (isHeic && heicJpeg) { |
| 2028 | 2985 | const vipsAvailable = isClientSideMediaSupported(); |
| 2029 | 2986 | updates = { |
| 2030 | 2987 | file: heicJpeg, |
| 2031 | 2988 | sourceFile: heicJpeg, |
| 2032 | - originalHeicFile: item.file, | |
| 2989 | + originalHeicFile: heicFile, | |
| 2033 | 2990 | additionalData: { |
| 2034 | 2991 | ...item.additionalData, |
| 2035 | 2992 | generate_sub_sizes: !vipsAvailable, |
| 2036 | 2993 | convert_format: true |
| @@ -2035,9 +2992,9 @@ | ||
| 2035 | 2992 | generate_sub_sizes: !vipsAvailable, |
| 2036 | 2993 | convert_format: true |
| 2037 | 2994 | } |
| 2038 | 2995 | }; |
| 2039 | - } else if (!isVipsSupported || !isImage) { | |
| 2996 | + } else if (!isVipsSupported || !isImage || tooLargeForClient) { | |
| 2040 | 2997 | updates = { |
| 2041 | 2998 | additionalData: { |
| 2042 | 2999 | ...item.additionalData, |
| 2043 | 3000 | generate_sub_sizes: true, |
| @@ -2043,12 +3000,37 @@ | ||
| 2043 | 3000 | generate_sub_sizes: true, |
| 2044 | 3001 | convert_format: true |
| 2045 | 3002 | } |
| 2046 | 3003 | }; |
| 3004 | + } else { | |
| 3005 | + updates = { | |
| 3006 | + additionalData: { | |
| 3007 | + ...item.additionalData, | |
| 3008 | + generate_sub_sizes: false | |
| 3009 | + } | |
| 3010 | + }; | |
| 2047 | 3011 | } |
| 2048 | 3012 | dispatch.finishOperation(id, updates); |
| 2049 | 3013 | }; |
| 2050 | 3014 | } |
| 3015 | + function detectUltraHdr(id) { | |
| 3016 | + return async ({ select: select2, dispatch }) => { | |
| 3017 | + const item = select2.getItem(id); | |
| 3018 | + if (!item) { | |
| 3019 | + return; | |
| 3020 | + } | |
| 3021 | + let info; | |
| 3022 | + try { | |
| 3023 | + const buffer = await item.file.arrayBuffer(); | |
| 3024 | + info = await vipsGetUltraHdrInfo(buffer); | |
| 3025 | + } catch { | |
| 3026 | + } | |
| 3027 | + if (info) { | |
| 3028 | + ultraHdrItems.add(id); | |
| 3029 | + } | |
| 3030 | + dispatch.finishOperation(id, {}); | |
| 3031 | + }; | |
| 3032 | + } | |
| 2051 | 3033 | function uploadItem(id) { |
| 2052 | 3034 | return async ({ select: select2, dispatch }) => { |
| 2053 | 3035 | const item = select2.getItem(id); |
| 2054 | 3036 | if (!item) { |
| @@ -2053,23 +3035,42 @@ | ||
| 2053 | 3035 | const item = select2.getItem(id); |
| 2054 | 3036 | if (!item) { |
| 2055 | 3037 | return; |
| 2056 | 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 | + }; | |
| 2057 | 3057 | select2.getSettings().mediaUpload({ |
| 2058 | 3058 | filesList: [item.file], |
| 2059 | 3059 | additionalData: item.additionalData, |
| 2060 | 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, | |
| 2061 | 3066 | onFileChange: ([attachment]) => { |
| 2062 | 3067 | if (attachment && !(0, import_blob.isBlobURL)(attachment.url)) { |
| 2063 | - dispatch.finishOperation(id, { | |
| 2064 | - attachment | |
| 2065 | - }); | |
| 3068 | + finishUpload(attachment); | |
| 2066 | 3069 | } |
| 2067 | 3070 | }, |
| 2068 | 3071 | onSuccess: ([attachment]) => { |
| 2069 | - dispatch.finishOperation(id, { | |
| 2070 | - attachment | |
| 2071 | - }); | |
| 3072 | + finishUpload(attachment); | |
| 2072 | 3073 | }, |
| 2073 | 3074 | onError: (error) => { |
| 2074 | 3075 | dispatch.cancelItem(id, error); |
| 2075 | 3076 | } |
| @@ -2087,8 +3088,9 @@ | ||
| 2087 | 3088 | if (!mediaSideload) { |
| 2088 | 3089 | dispatch.finishOperation(id, {}); |
| 2089 | 3090 | return; |
| 2090 | 3091 | } |
| 3092 | + const startTime = performance.now(); | |
| 2091 | 3093 | mediaSideload({ |
| 2092 | 3094 | file: item.file, |
| 2093 | 3095 | attachmentId: post, |
| 2094 | 3096 | additionalData, |
| @@ -2093,8 +3095,17 @@ | ||
| 2093 | 3095 | attachmentId: post, |
| 2094 | 3096 | additionalData, |
| 2095 | 3097 | signal: item.abortController?.signal, |
| 2096 | 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 | + }); | |
| 2097 | 3108 | if (item.parentId) { |
| 2098 | 3109 | dispatch({ |
| 2099 | 3110 | type: Type.AccumulateSubSize, |
| 2100 | 3111 | id: item.parentId, |
| @@ -2120,21 +3131,36 @@ | ||
| 2120 | 3131 | file: item.file |
| 2121 | 3132 | }); |
| 2122 | 3133 | return; |
| 2123 | 3134 | } |
| 3135 | + const startTime = performance.now(); | |
| 2124 | 3136 | const addSuffix = Boolean(item.parentId); |
| 2125 | 3137 | const scaledSuffix = Boolean(args.isThresholdResize); |
| 3138 | + const { imageStripMeta, imageMaxBitDepth } = select2.getSettings(); | |
| 2126 | 3139 | try { |
| 2127 | 3140 | const file = await vipsResizeImage( |
| 2128 | 3141 | item.id, |
| 2129 | 3142 | item.file, |
| 2130 | 3143 | args.resize, |
| 2131 | - false, | |
| 2132 | - // smartCrop | |
| 2133 | - addSuffix, | |
| 2134 | - item.abortController?.signal, | |
| 2135 | - 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 | + } | |
| 2136 | 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 | + }); | |
| 2137 | 3163 | const blobUrl = (0, import_blob.createBlobURL)(file); |
| 2138 | 3164 | dispatch({ |
| 2139 | 3165 | type: Type.CacheBlobUrl, |
| 2140 | 3166 | id, |
| @@ -2149,10 +3175,12 @@ | ||
| 2149 | 3175 | } catch (error) { |
| 2150 | 3176 | dispatch.cancelItem( |
| 2151 | 3177 | id, |
| 2152 | 3178 | new UploadError({ |
| 2153 | - code: "IMAGE_TRANSCODING_ERROR", | |
| 2154 | - message: "File could not be uploaded", | |
| 3179 | + code: ErrorCode.IMAGE_TRANSCODING_ERROR, | |
| 3180 | + message: (0, import_i18n7.__)( | |
| 3181 | + "The web server cannot generate responsive image sizes for this image. Convert it to JPEG or PNG before uploading." | |
| 3182 | + ), | |
| 2155 | 3183 | file: item.file, |
| 2156 | 3184 | cause: error instanceof Error ? error : void 0 |
| 2157 | 3185 | }) |
| 2158 | 3186 | ); |
| @@ -2170,8 +3198,9 @@ | ||
| 2170 | 3198 | file: item.file |
| 2171 | 3199 | }); |
| 2172 | 3200 | return; |
| 2173 | 3201 | } |
| 3202 | + const startTime = performance.now(); | |
| 2174 | 3203 | try { |
| 2175 | 3204 | const file = await vipsRotateImage( |
| 2176 | 3205 | item.id, |
| 2177 | 3206 | item.file, |
| @@ -2177,8 +3206,17 @@ | ||
| 2177 | 3206 | item.file, |
| 2178 | 3207 | args.orientation, |
| 2179 | 3208 | item.abortController?.signal |
| 2180 | 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 | + }); | |
| 2181 | 3219 | const blobUrl = (0, import_blob.createBlobURL)(file); |
| 2182 | 3220 | dispatch({ |
| 2183 | 3221 | type: Type.CacheBlobUrl, |
| 2184 | 3222 | id, |
| @@ -2193,10 +3231,12 @@ | ||
| 2193 | 3231 | } catch (error) { |
| 2194 | 3232 | dispatch.cancelItem( |
| 2195 | 3233 | id, |
| 2196 | 3234 | new UploadError({ |
| 2197 | - code: "IMAGE_ROTATION_ERROR", | |
| 2198 | - message: "Image could not be rotated", | |
| 3235 | + code: ErrorCode.IMAGE_ROTATION_ERROR, | |
| 3236 | + message: (0, import_i18n7.__)( | |
| 3237 | + "The web server cannot generate responsive image sizes for this image. Convert it to JPEG or PNG before uploading." | |
| 3238 | + ), | |
| 2199 | 3239 | file: item.file, |
| 2200 | 3240 | cause: error instanceof Error ? error : void 0 |
| 2201 | 3241 | }) |
| 2202 | 3242 | ); |
| @@ -2214,19 +3254,34 @@ | ||
| 2214 | 3254 | file: item.file |
| 2215 | 3255 | }); |
| 2216 | 3256 | return; |
| 2217 | 3257 | } |
| 3258 | + const startTime = performance.now(); | |
| 2218 | 3259 | const outputMimeType = `image/${args.outputFormat}`; |
| 2219 | 3260 | const quality = args.outputQuality ?? DEFAULT_OUTPUT_QUALITY; |
| 2220 | 3261 | const interlaced = args.interlaced ?? false; |
| 3262 | + const { imageStripMeta, imageMaxBitDepth } = select2.getSettings(); | |
| 2221 | 3263 | try { |
| 2222 | 3264 | const file = await vipsConvertImageFormat( |
| 2223 | 3265 | item.id, |
| 2224 | 3266 | item.file, |
| 2225 | 3267 | outputMimeType, |
| 2226 | - quality, | |
| 2227 | - interlaced | |
| 3268 | + { | |
| 3269 | + quality, | |
| 3270 | + interlaced, | |
| 3271 | + stripMeta: imageStripMeta, | |
| 3272 | + maxBitdepth: imageMaxBitDepth | |
| 3273 | + } | |
| 2228 | 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 | + }); | |
| 2229 | 3284 | const blobUrl = (0, import_blob.createBlobURL)(file); |
| 2230 | 3285 | dispatch({ |
| 2231 | 3286 | type: Type.CacheBlobUrl, |
| 2232 | 3287 | id, |
| @@ -2241,9 +3296,9 @@ | ||
| 2241 | 3296 | } catch (error) { |
| 2242 | 3297 | dispatch.cancelItem( |
| 2243 | 3298 | id, |
| 2244 | 3299 | new UploadError({ |
| 2245 | - code: "MEDIA_TRANSCODING_ERROR", | |
| 3300 | + code: ErrorCode.MEDIA_TRANSCODING_ERROR, | |
| 2246 | 3301 | message: "Image could not be transcoded to the target format", |
| 2247 | 3302 | file: item.file, |
| 2248 | 3303 | cause: error instanceof Error ? error : void 0 |
| 2249 | 3304 | }) |
| @@ -2250,8 +3305,91 @@ | ||
| 2250 | 3305 | ); |
| 2251 | 3306 | } |
| 2252 | 3307 | }; |
| 2253 | 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 | + } | |
| 2254 | 3392 | function generateThumbnails(id) { |
| 2255 | 3393 | return async ({ select: select2, dispatch }) => { |
| 2256 | 3394 | const item = select2.getItem(id); |
| 2257 | 3395 | if (!item) { |
| @@ -2269,35 +3407,57 @@ | ||
| 2269 | 3407 | batchId: v4_default(), |
| 2270 | 3408 | parentId: item.id, |
| 2271 | 3409 | additionalData: { |
| 2272 | 3410 | post: attachment.id, |
| 2273 | - image_size: "original-heic", | |
| 3411 | + image_size: "source_original", | |
| 2274 | 3412 | convert_format: false |
| 2275 | 3413 | }, |
| 2276 | 3414 | operations: [OperationType.Upload] |
| 2277 | 3415 | }); |
| 2278 | 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; | |
| 2279 | 3450 | { |
| 2280 | - const needsRotation = attachment.exif_orientation && attachment.exif_orientation !== 1 && !item.file.name.includes("-scaled"); | |
| 2281 | - if (needsRotation && attachment.id) { | |
| 3451 | + const needsRotation = exifOrientation !== 1 && !item.file.name.includes("-scaled"); | |
| 3452 | + if ((needsRotation || needsClientRotation) && attachment.id) { | |
| 2282 | 3453 | try { |
| 2283 | - const rotatedFile = await vipsRotateImage( | |
| 3454 | + rotatedSource = await vipsRotateImage( | |
| 2284 | 3455 | item.id, |
| 2285 | 3456 | item.sourceFile, |
| 2286 | - attachment.exif_orientation, | |
| 3457 | + exifOrientation, | |
| 2287 | 3458 | item.abortController?.signal |
| 2288 | 3459 | ); |
| 2289 | - dispatch.addSideloadItem({ | |
| 2290 | - file: rotatedFile, | |
| 2291 | - batchId: v4_default(), | |
| 2292 | - parentId: item.id, | |
| 2293 | - additionalData: { | |
| 2294 | - post: attachment.id, | |
| 2295 | - image_size: "original", | |
| 2296 | - convert_format: false | |
| 2297 | - }, | |
| 2298 | - operations: [OperationType.Upload] | |
| 2299 | - }); | |
| 2300 | 3460 | } catch { |
| 2301 | 3461 | console.warn( |
| 2302 | 3462 | "Failed to rotate image, continuing with thumbnails" |
| 2303 | 3463 | ); |
| @@ -2302,23 +3462,48 @@ | ||
| 2302 | 3462 | "Failed to rotate image, continuing with thumbnails" |
| 2303 | 3463 | ); |
| 2304 | 3464 | } |
| 2305 | 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 | + } | |
| 2306 | 3479 | } |
| 2307 | 3480 | if (!item.parentId && attachment.missing_image_sizes && attachment.missing_image_sizes.length > 0) { |
| 2308 | 3481 | const allImageSizes = settings.allImageSizes || {}; |
| 2309 | 3482 | const sizesToGenerate = attachment.missing_image_sizes; |
| 2310 | - const thumbnailSource = item.sourceFile; | |
| 3483 | + const thumbnailSource = needsClientRotation && rotatedSource ? rotatedSource : item.sourceFile; | |
| 2311 | 3484 | const file = attachment.filename ? renameFile(thumbnailSource, attachment.filename) : thumbnailSource; |
| 2312 | 3485 | const batchId = v4_default(); |
| 3486 | + const isUltraHdr = ultraHdrItems.has(item.id); | |
| 2313 | 3487 | const outputMimeType = attachment.image_output_format; |
| 2314 | 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 | + }; | |
| 2315 | 3499 | let thumbnailTranscodeOperation = null; |
| 2316 | - if (outputMimeType) { | |
| 3500 | + if (!isUltraHdr && outputMimeType) { | |
| 2317 | 3501 | thumbnailTranscodeOperation = await getTranscodeImageOperation( |
| 2318 | 3502 | thumbnailSource, |
| 2319 | 3503 | outputMimeType, |
| 2320 | - interlaced | |
| 3504 | + interlaced, | |
| 3505 | + defaultQuality | |
| 2321 | 3506 | ); |
| 2322 | 3507 | } |
| 2323 | 3508 | const dimensionGroups = /* @__PURE__ */ new Map(); |
| 2324 | 3509 | for (const name of sizesToGenerate) { |
| @@ -2338,13 +3523,23 @@ | ||
| 2338 | 3523 | } |
| 2339 | 3524 | } |
| 2340 | 3525 | for (const [, names] of dimensionGroups) { |
| 2341 | 3526 | const imageSize = allImageSizes[names[0]]; |
| 3527 | + const sizeQuality = qualityForSize(names[0]); | |
| 2342 | 3528 | const thumbnailOperations = [ |
| 2343 | - [OperationType.ResizeCrop, { resize: imageSize }] | |
| 3529 | + [ | |
| 3530 | + OperationType.ResizeCrop, | |
| 3531 | + { resize: imageSize, quality: sizeQuality } | |
| 3532 | + ] | |
| 2344 | 3533 | ]; |
| 2345 | - if (thumbnailTranscodeOperation) { | |
| 2346 | - thumbnailOperations.push(thumbnailTranscodeOperation); | |
| 3534 | + if (!isUltraHdr && thumbnailTranscodeOperation) { | |
| 3535 | + thumbnailOperations.push([ | |
| 3536 | + thumbnailTranscodeOperation[0], | |
| 3537 | + { | |
| 3538 | + ...thumbnailTranscodeOperation[1], | |
| 3539 | + outputQuality: sizeQuality | |
| 3540 | + } | |
| 3541 | + ]); | |
| 2347 | 3542 | } |
| 2348 | 3543 | thumbnailOperations.push(OperationType.Upload); |
| 2349 | 3544 | const imageSizeParam = names.length === 1 ? names[0] : names; |
| 2350 | 3545 | dispatch.addSideloadItem({ |
| @@ -2376,13 +3571,14 @@ | ||
| 2376 | 3571 | resize: { |
| 2377 | 3572 | width: bigImageSizeThreshold, |
| 2378 | 3573 | height: bigImageSizeThreshold |
| 2379 | 3574 | }, |
| 2380 | - isThresholdResize: true | |
| 3575 | + isThresholdResize: true, | |
| 3576 | + quality: defaultQuality | |
| 2381 | 3577 | } |
| 2382 | 3578 | ] |
| 2383 | 3579 | ]; |
| 2384 | - if (thumbnailTranscodeOperation) { | |
| 3580 | + if (!isUltraHdr && thumbnailTranscodeOperation) { | |
| 2385 | 3581 | scaledOperations.push( |
| 2386 | 3582 | thumbnailTranscodeOperation |
| 2387 | 3583 | ); |
| 2388 | 3584 | } |
| @@ -2412,16 +3608,32 @@ | ||
| 2412 | 3608 | return; |
| 2413 | 3609 | } |
| 2414 | 3610 | const attachment = item.attachment; |
| 2415 | 3611 | const { mediaFinalize } = select2.getSettings(); |
| 3612 | + const updates = {}; | |
| 2416 | 3613 | if (attachment?.id && mediaFinalize) { |
| 2417 | 3614 | try { |
| 2418 | - await mediaFinalize(attachment.id, item.subSizes || []); | |
| 3615 | + const updatedAttachment = await mediaFinalize( | |
| 3616 | + attachment.id, | |
| 3617 | + item.subSizes || [] | |
| 3618 | + ); | |
| 3619 | + if (updatedAttachment) { | |
| 3620 | + updates.attachment = updatedAttachment; | |
| 3621 | + } | |
| 2419 | 3622 | } catch (error) { |
| 2420 | 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; | |
| 2421 | 3633 | } |
| 2422 | 3634 | } |
| 2423 | - dispatch.finishOperation(id, {}); | |
| 3635 | + dispatch.finishOperation(id, updates); | |
| 2424 | 3636 | }; |
| 2425 | 3637 | } |
| 2426 | 3638 | function revokeBlobUrls(id) { |
| 2427 | 3639 | return async ({ select: select2, dispatch }) => { |
| @@ -2524,7 +3736,115 @@ | ||
| 2524 | 3736 | }, [settings, updateSettings2]); |
| 2525 | 3737 | return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children }); |
| 2526 | 3738 | }); |
| 2527 | 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 | + } | |
| 2528 | 3846 | return __toCommonJS(index_exports); |
| 3847 | +})(); | |
| 3848 | +(window.wp ||= {}).uploadMedia = wp.uploadMedia; | |
| 2529 | 3849 | })(); |
| 2530 | 3850 | //# sourceMappingURL=index.js.map |