| @@ -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,13 +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, |
| 110 | + isHeicCanvasSupported: () => isHeicCanvasSupported, | |
| 111 | + isHeicFile: () => isHeicFile, | |
| 105 | 112 | store: () => store |
| 106 | 113 | }); |
| 107 | 114 | |
| 108 | 115 | // packages/upload-media/build-module/store/index.mjs |
| @@ -115,8 +122,9 @@ | ||
| 115 | 122 | Type2["Prepare"] = "PREPARE_ITEM"; |
| 116 | 123 | Type2["Cancel"] = "CANCEL_ITEM"; |
| 117 | 124 | Type2["Remove"] = "REMOVE_ITEM"; |
| 118 | 125 | Type2["RetryItem"] = "RETRY_ITEM"; |
| 126 | + Type2["ScheduleRetry"] = "SCHEDULE_RETRY"; | |
| 119 | 127 | Type2["PauseItem"] = "PAUSE_ITEM"; |
| 120 | 128 | Type2["ResumeItem"] = "RESUME_ITEM"; |
| 121 | 129 | Type2["PauseQueue"] = "PAUSE_QUEUE"; |
| 122 | 130 | Type2["ResumeQueue"] = "RESUME_QUEUE"; |
| @@ -125,8 +133,9 @@ | ||
| 125 | 133 | Type2["AddOperations"] = "ADD_OPERATIONS"; |
| 126 | 134 | Type2["CacheBlobUrl"] = "CACHE_BLOB_URL"; |
| 127 | 135 | Type2["RevokeBlobUrls"] = "REVOKE_BLOB_URLS"; |
| 128 | 136 | Type2["UpdateProgress"] = "UPDATE_PROGRESS"; |
| 137 | + Type2["AccumulateSubSize"] = "ACCUMULATE_SUB_SIZE"; | |
| 129 | 138 | Type2["UpdateSettings"] = "UPDATE_SETTINGS"; |
| 130 | 139 | return Type2; |
| 131 | 140 | })(Type || {}); |
| 132 | 141 | var ItemStatus = /* @__PURE__ */ ((ItemStatus2) => { |
| @@ -132,8 +141,9 @@ | ||
| 132 | 141 | var ItemStatus = /* @__PURE__ */ ((ItemStatus2) => { |
| 133 | 142 | ItemStatus2["Queued"] = "QUEUED"; |
| 134 | 143 | ItemStatus2["Processing"] = "PROCESSING"; |
| 135 | 144 | ItemStatus2["Paused"] = "PAUSED"; |
| 145 | + ItemStatus2["PendingRetry"] = "PENDING_RETRY"; | |
| 136 | 146 | ItemStatus2["Uploaded"] = "UPLOADED"; |
| 137 | 147 | ItemStatus2["Error"] = "ERROR"; |
| 138 | 148 | return ItemStatus2; |
| 139 | 149 | })(ItemStatus || {}); |
| @@ -142,10 +152,12 @@ | ||
| 142 | 152 | OperationType2["Upload"] = "UPLOAD"; |
| 143 | 153 | OperationType2["ResizeCrop"] = "RESIZE_CROP"; |
| 144 | 154 | OperationType2["Rotate"] = "ROTATE"; |
| 145 | 155 | OperationType2["TranscodeImage"] = "TRANSCODE_IMAGE"; |
| 156 | + OperationType2["TranscodeGif"] = "TRANSCODE_GIF"; | |
| 146 | 157 | OperationType2["ThumbnailGeneration"] = "THUMBNAIL_GENERATION"; |
| 147 | 158 | OperationType2["Finalize"] = "FINALIZE"; |
| 159 | + OperationType2["DetectUltraHdr"] = "DETECT_ULTRAHDR"; | |
| 148 | 160 | return OperationType2; |
| 149 | 161 | })(OperationType || {}); |
| 150 | 162 | |
| 151 | 163 | // packages/upload-media/build-module/store/constants.mjs |
| @@ -151,8 +163,15 @@ | ||
| 151 | 163 | // packages/upload-media/build-module/store/constants.mjs |
| 152 | 164 | var STORE_NAME = "core/upload-media"; |
| 153 | 165 | var DEFAULT_MAX_CONCURRENT_UPLOADS = 5; |
| 154 | 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 | + }; | |
| 155 | 174 | var CLIENT_SIDE_SUPPORTED_MIME_TYPES = [ |
| 156 | 175 | "image/jpeg", |
| 157 | 176 | "image/png", |
| 158 | 177 | "image/gif", |
| @@ -158,8 +177,12 @@ | ||
| 158 | 177 | "image/gif", |
| 159 | 178 | "image/webp", |
| 160 | 179 | "image/avif" |
| 161 | 180 | ]; |
| 181 | + var HEIC_MIME_TYPES = [ | |
| 182 | + "image/heic", | |
| 183 | + "image/heif" | |
| 184 | + ]; | |
| 162 | 185 | |
| 163 | 186 | // packages/upload-media/build-module/store/reducer.mjs |
| 164 | 187 | var noop = () => { |
| 165 | 188 | }; |
| @@ -169,10 +192,12 @@ | ||
| 169 | 192 | blobUrls: {}, |
| 170 | 193 | settings: { |
| 171 | 194 | mediaUpload: noop, |
| 172 | 195 | maxConcurrentUploads: DEFAULT_MAX_CONCURRENT_UPLOADS, |
| 173 | - maxConcurrentImageProcessing: DEFAULT_MAX_CONCURRENT_IMAGE_PROCESSING | |
| 174 | - } | |
| 196 | + maxConcurrentImageProcessing: DEFAULT_MAX_CONCURRENT_IMAGE_PROCESSING, | |
| 197 | + retry: { ...DEFAULT_RETRY_SETTINGS } | |
| 198 | + }, | |
| 199 | + failureCount: 0 | |
| 175 | 200 | }; |
| 176 | 201 | function reducer(state = DEFAULT_STATE, action = { type: Type.Unknown }) { |
| 177 | 202 | switch (action.type) { |
| 178 | 203 | case Type.PauseQueue: { |
| @@ -211,11 +236,15 @@ | ||
| 211 | 236 | return { |
| 212 | 237 | ...state, |
| 213 | 238 | queue: [...state.queue, action.item] |
| 214 | 239 | }; |
| 215 | - case Type.Cancel: | |
| 240 | + case Type.Cancel: { | |
| 241 | + const cancelled = state.queue.find( | |
| 242 | + (item) => item.id === action.id | |
| 243 | + ); | |
| 216 | 244 | return { |
| 217 | 245 | ...state, |
| 246 | + failureCount: cancelled && !cancelled.parentId ? state.failureCount + 1 : state.failureCount, | |
| 218 | 247 | queue: state.queue.map( |
| 219 | 248 | (item) => item.id === action.id ? { |
| 220 | 249 | ...item, |
| 221 | 250 | error: action.error |
| @@ -221,8 +250,9 @@ | ||
| 221 | 250 | error: action.error |
| 222 | 251 | } : item |
| 223 | 252 | ) |
| 224 | 253 | }; |
| 254 | + } | |
| 225 | 255 | case Type.RetryItem: |
| 226 | 256 | return { |
| 227 | 257 | ...state, |
| 228 | 258 | queue: state.queue.map( |
| @@ -229,12 +259,37 @@ | ||
| 229 | 259 | (item) => item.id === action.id ? { |
| 230 | 260 | ...item, |
| 231 | 261 | status: ItemStatus.Processing, |
| 232 | 262 | error: void 0, |
| 233 | - 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 | |
| 234 | 276 | } : item |
| 235 | 277 | ) |
| 236 | 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 | + }; | |
| 237 | 292 | case Type.Remove: |
| 238 | 293 | return { |
| 239 | 294 | ...state, |
| 240 | 295 | queue: state.queue.filter((item) => item.id !== action.id) |
| @@ -318,8 +373,21 @@ | ||
| 318 | 373 | progress: action.progress |
| 319 | 374 | } : item |
| 320 | 375 | ) |
| 321 | 376 | }; |
| 377 | + case Type.AccumulateSubSize: | |
| 378 | + return { | |
| 379 | + ...state, | |
| 380 | + queue: state.queue.map( | |
| 381 | + (item) => item.id === action.id ? { | |
| 382 | + ...item, | |
| 383 | + subSizes: [ | |
| 384 | + ...item.subSizes || [], | |
| 385 | + action.subSize | |
| 386 | + ] | |
| 387 | + } : item | |
| 388 | + ) | |
| 389 | + }; | |
| 322 | 390 | case Type.UpdateSettings: { |
| 323 | 391 | return { |
| 324 | 392 | ...state, |
| 325 | 393 | settings: { |
| @@ -366,24 +434,28 @@ | ||
| 366 | 434 | var private_selectors_exports = {}; |
| 367 | 435 | __export(private_selectors_exports, { |
| 368 | 436 | getActiveImageProcessingCount: () => getActiveImageProcessingCount, |
| 369 | 437 | getActiveUploadCount: () => getActiveUploadCount, |
| 438 | + getActiveVideoProcessingCount: () => getActiveVideoProcessingCount, | |
| 370 | 439 | getAllItems: () => getAllItems, |
| 371 | 440 | getBlobUrls: () => getBlobUrls, |
| 372 | 441 | getFailedItems: () => getFailedItems, |
| 442 | + getFailureCount: () => getFailureCount, | |
| 373 | 443 | getItem: () => getItem, |
| 374 | 444 | getItemProgress: () => getItemProgress, |
| 375 | - getPausedUploadForPost: () => getPausedUploadForPost, | |
| 376 | 445 | getPendingImageProcessing: () => getPendingImageProcessing, |
| 377 | 446 | getPendingUploads: () => getPendingUploads, |
| 447 | + getPendingVideoProcessing: () => getPendingVideoProcessing, | |
| 378 | 448 | hasPendingItemsByParentId: () => hasPendingItemsByParentId, |
| 379 | 449 | isBatchUploaded: () => isBatchUploaded, |
| 380 | - isPaused: () => isPaused, | |
| 381 | - isUploadingToPost: () => isUploadingToPost | |
| 450 | + isPaused: () => isPaused | |
| 382 | 451 | }); |
| 383 | 452 | function getAllItems(state) { |
| 384 | 453 | return state.queue; |
| 385 | 454 | } |
| 455 | + function getFailureCount(state) { | |
| 456 | + return state.failureCount; | |
| 457 | + } | |
| 386 | 458 | function getItem(state, id) { |
| 387 | 459 | return state.queue.find((item) => item.id === id); |
| 388 | 460 | } |
| 389 | 461 | function isBatchUploaded(state, batchId) { |
| @@ -391,18 +463,8 @@ | ||
| 391 | 463 | (item) => batchId === item.batchId |
| 392 | 464 | ); |
| 393 | 465 | return batchItems.length === 0; |
| 394 | 466 | } |
| 395 | - function isUploadingToPost(state, postOrAttachmentId) { | |
| 396 | - return state.queue.some( | |
| 397 | - (item) => item.currentOperation === OperationType.Upload && item.additionalData.post === postOrAttachmentId | |
| 398 | - ); | |
| 399 | - } | |
| 400 | - function getPausedUploadForPost(state, postOrAttachmentId) { | |
| 401 | - return state.queue.find( | |
| 402 | - (item) => item.status === ItemStatus.Paused && item.additionalData.post === postOrAttachmentId | |
| 403 | - ); | |
| 404 | - } | |
| 405 | 467 | function isPaused(state) { |
| 406 | 468 | return state.queueStatus === "paused"; |
| 407 | 469 | } |
| 408 | 470 | function getBlobUrls(state, id) { |
| @@ -423,8 +485,13 @@ | ||
| 423 | 485 | return state.queue.filter( |
| 424 | 486 | (item) => item.currentOperation === OperationType.ResizeCrop || item.currentOperation === OperationType.Rotate |
| 425 | 487 | ).length; |
| 426 | 488 | } |
| 489 | + function getActiveVideoProcessingCount(state) { | |
| 490 | + return state.queue.filter( | |
| 491 | + (item) => item.currentOperation === OperationType.TranscodeGif | |
| 492 | + ).length; | |
| 493 | + } | |
| 427 | 494 | function getPendingImageProcessing(state) { |
| 428 | 495 | return state.queue.filter((item) => { |
| 429 | 496 | const nextOperation = Array.isArray(item.operations?.[0]) ? item.operations[0][0] : item.operations?.[0]; |
| 430 | 497 | return (nextOperation === OperationType.ResizeCrop || nextOperation === OperationType.Rotate) && item.currentOperation !== OperationType.ResizeCrop && item.currentOperation !== OperationType.Rotate; |
| @@ -429,8 +496,14 @@ | ||
| 429 | 496 | const nextOperation = Array.isArray(item.operations?.[0]) ? item.operations[0][0] : item.operations?.[0]; |
| 430 | 497 | return (nextOperation === OperationType.ResizeCrop || nextOperation === OperationType.Rotate) && item.currentOperation !== OperationType.ResizeCrop && item.currentOperation !== OperationType.Rotate; |
| 431 | 498 | }); |
| 432 | 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 | + } | |
| 433 | 506 | function getFailedItems(state) { |
| 434 | 507 | return state.queue.filter((item) => item.error !== void 0); |
| 435 | 508 | } |
| 436 | 509 | function hasPendingItemsByParentId(state, parentId) { |
| @@ -445,50 +518,48 @@ | ||
| 445 | 518 | var actions_exports = {}; |
| 446 | 519 | __export(actions_exports, { |
| 447 | 520 | addItems: () => addItems, |
| 448 | 521 | cancelItem: () => cancelItem, |
| 449 | - retryItem: () => retryItem | |
| 522 | + executeRetry: () => executeRetry, | |
| 523 | + retryItem: () => retryItem, | |
| 524 | + scheduleRetry: () => scheduleRetry | |
| 450 | 525 | }); |
| 451 | 526 | |
| 452 | - // node_modules/uuid/dist/esm-browser/rng.js | |
| 453 | - var getRandomValues; | |
| 454 | - var rnds8 = new Uint8Array(16); | |
| 455 | - function rng() { | |
| 456 | - if (!getRandomValues) { | |
| 457 | - getRandomValues = typeof crypto !== "undefined" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto); | |
| 458 | - if (!getRandomValues) { | |
| 459 | - throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported"); | |
| 460 | - } | |
| 461 | - } | |
| 462 | - return getRandomValues(rnds8); | |
| 463 | - } | |
| 464 | - | |
| 465 | - // node_modules/uuid/dist/esm-browser/stringify.js | |
| 527 | + // node_modules/uuid/dist/stringify.js | |
| 466 | 528 | var byteToHex = []; |
| 467 | 529 | for (let i = 0; i < 256; ++i) { |
| 468 | 530 | byteToHex.push((i + 256).toString(16).slice(1)); |
| 469 | 531 | } |
| 470 | 532 | function unsafeStringify(arr, offset = 0) { |
| 471 | - 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(); | |
| 472 | 534 | } |
| 473 | 535 | |
| 474 | - // node_modules/uuid/dist/esm-browser/native.js | |
| 475 | - var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto); | |
| 476 | - var native_default = { | |
| 477 | - randomUUID | |
| 478 | - }; | |
| 536 | + // node_modules/uuid/dist/rng.js | |
| 537 | + var rnds8 = new Uint8Array(16); | |
| 538 | + function rng() { | |
| 539 | + return crypto.getRandomValues(rnds8); | |
| 540 | + } | |
| 479 | 541 | |
| 480 | - // node_modules/uuid/dist/esm-browser/v4.js | |
| 542 | + // node_modules/uuid/dist/v4.js | |
| 481 | 543 | function v4(options, buf, offset) { |
| 482 | - if (native_default.randomUUID && !buf && !options) { | |
| 483 | - return native_default.randomUUID(); | |
| 544 | + if (!buf && !options && crypto.randomUUID) { | |
| 545 | + return crypto.randomUUID(); | |
| 484 | 546 | } |
| 547 | + return _v4(options, buf, offset); | |
| 548 | + } | |
| 549 | + function _v4(options, buf, offset) { | |
| 485 | 550 | options = options || {}; |
| 486 | - 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 | + } | |
| 487 | 555 | rnds[6] = rnds[6] & 15 | 64; |
| 488 | 556 | rnds[8] = rnds[8] & 63 | 128; |
| 489 | 557 | if (buf) { |
| 490 | 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 | + } | |
| 491 | 562 | for (let i = 0; i < 16; ++i) { |
| 492 | 563 | buf[offset + i] = rnds[i]; |
| 493 | 564 | } |
| 494 | 565 | return buf; |
| @@ -496,8 +567,50 @@ | ||
| 496 | 567 | return unsafeStringify(rnds); |
| 497 | 568 | } |
| 498 | 569 | var v4_default = v4; |
| 499 | 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 | + | |
| 500 | 613 | // packages/upload-media/build-module/image-file.mjs |
| 501 | 614 | var ImageFile = class extends File { |
| 502 | 615 | width = 0; |
| 503 | 616 | height = 0; |
| @@ -548,8 +661,72 @@ | ||
| 548 | 661 | } |
| 549 | 662 | function getFileBasename(name) { |
| 550 | 663 | return name.includes(".") ? name.split(".").slice(0, -1).join(".") : name; |
| 551 | 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 | + } | |
| 552 | 729 | |
| 553 | 730 | // packages/upload-media/build-module/store/utils/index.mjs |
| 554 | 731 | var vipsModulePromise; |
| 555 | 732 | var vipsModule; |
| @@ -563,9 +740,9 @@ | ||
| 563 | 740 | ); |
| 564 | 741 | } |
| 565 | 742 | return vipsModulePromise; |
| 566 | 743 | } |
| 567 | - async function vipsConvertImageFormat(id, file, type, quality, interlaced) { | |
| 744 | + async function vipsConvertImageFormat(id, file, type, options = {}) { | |
| 568 | 745 | const { vipsConvertImageFormat: convertImageFormat } = await loadVipsModule(); |
| 569 | 746 | const buffer = await convertImageFormat( |
| 570 | 747 | id, |
| 571 | 748 | await file.arrayBuffer(), |
| @@ -570,10 +747,9 @@ | ||
| 570 | 747 | id, |
| 571 | 748 | await file.arrayBuffer(), |
| 572 | 749 | file.type, |
| 573 | 750 | type, |
| 574 | - quality, | |
| 575 | - interlaced | |
| 751 | + options | |
| 576 | 752 | ); |
| 577 | 753 | const ext = type.split("/")[1]; |
| 578 | 754 | const fileName = `${getFileBasename(file.name)}.${ext}`; |
| 579 | 755 | return new File([new Blob([buffer])], fileName, { |
| @@ -587,21 +763,32 @@ | ||
| 587 | 763 | throw new Error(`Failed to fetch image: ${response.status}`); |
| 588 | 764 | } |
| 589 | 765 | return hasTransparency(await response.arrayBuffer()); |
| 590 | 766 | } |
| 591 | - 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; | |
| 592 | 781 | if (signal?.aborted) { |
| 593 | 782 | throw new Error("Operation aborted"); |
| 594 | 783 | } |
| 595 | 784 | const { vipsResizeImage: resizeImage } = await loadVipsModule(); |
| 596 | - const { buffer, width, height, originalWidth, originalHeight } = await resizeImage( | |
| 597 | - id, | |
| 598 | - await file.arrayBuffer(), | |
| 599 | - file.type, | |
| 600 | - resize, | |
| 785 | + const { buffer, width, height, originalWidth, originalHeight } = await resizeImage(id, await file.arrayBuffer(), file.type, resize, { | |
| 601 | 786 | smartCrop, |
| 602 | - quality | |
| 603 | - ); | |
| 787 | + quality, | |
| 788 | + stripMeta, | |
| 789 | + maxBitdepth | |
| 790 | + }); | |
| 604 | 791 | let fileName = file.name; |
| 605 | 792 | const wasResized = originalWidth > width || originalHeight > height; |
| 606 | 793 | if (wasResized) { |
| 607 | 794 | const basename = getFileBasename(file.name); |
| @@ -628,56 +815,8 @@ | ||
| 628 | 815 | originalHeight |
| 629 | 816 | ); |
| 630 | 817 | return resultFile; |
| 631 | 818 | } |
| 632 | - async function vipsBatchResizeImage(id, file, outputType, configs, smartCrop = false) { | |
| 633 | - const { vipsBatchResizeImage: batchResize } = await loadVipsModule(); | |
| 634 | - const resizes = configs.map((c) => ({ | |
| 635 | - resize: { ...c.resize }, | |
| 636 | - quality: c.quality | |
| 637 | - })); | |
| 638 | - const results = await batchResize( | |
| 639 | - id, | |
| 640 | - await file.arrayBuffer(), | |
| 641 | - file.type, | |
| 642 | - outputType, | |
| 643 | - resizes, | |
| 644 | - smartCrop | |
| 645 | - ); | |
| 646 | - const ext = outputType.split("/")[1]; | |
| 647 | - const sourceBasename = getFileBasename(file.name); | |
| 648 | - return results.map((result, i) => { | |
| 649 | - const config = configs[i]; | |
| 650 | - const { width, height, originalWidth, originalHeight } = result; | |
| 651 | - const wasResized = originalWidth > width || originalHeight > height; | |
| 652 | - let fileName = `${sourceBasename}.${ext}`; | |
| 653 | - if (wasResized) { | |
| 654 | - if (config.scaledSuffix) { | |
| 655 | - fileName = `${sourceBasename}-scaled.${ext}`; | |
| 656 | - } else { | |
| 657 | - fileName = `${sourceBasename}-${width}x${height}.${ext}`; | |
| 658 | - } | |
| 659 | - } | |
| 660 | - return { | |
| 661 | - name: config.name, | |
| 662 | - file: new ImageFile( | |
| 663 | - new File( | |
| 664 | - [ | |
| 665 | - new Blob([result.buffer], { | |
| 666 | - type: outputType | |
| 667 | - }) | |
| 668 | - ], | |
| 669 | - fileName, | |
| 670 | - { type: outputType } | |
| 671 | - ), | |
| 672 | - width, | |
| 673 | - height, | |
| 674 | - originalWidth, | |
| 675 | - originalHeight | |
| 676 | - ) | |
| 677 | - }; | |
| 678 | - }); | |
| 679 | - } | |
| 680 | 819 | async function vipsRotateImage(id, file, orientation, signal) { |
| 681 | 820 | if (signal?.aborted) { |
| 682 | 821 | throw new Error("Operation aborted"); |
| 683 | 822 | } |
| @@ -716,13 +855,172 @@ | ||
| 716 | 855 | if (vipsModule) { |
| 717 | 856 | vipsModule.terminateVipsWorker(); |
| 718 | 857 | } |
| 719 | 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 | + } | |
| 720 | 868 | |
| 721 | - // packages/upload-media/build-module/validate-mime-type.mjs | |
| 722 | - 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 | + } | |
| 723 | 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 | + | |
| 724 | 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 || {}); | |
| 725 | 1023 | var UploadError = class extends Error { |
| 726 | 1024 | code; |
| 727 | 1025 | file; |
| 728 | 1026 | constructor({ code, message, file, cause }) { |
| @@ -733,8 +1031,9 @@ | ||
| 733 | 1031 | } |
| 734 | 1032 | }; |
| 735 | 1033 | |
| 736 | 1034 | // packages/upload-media/build-module/validate-mime-type.mjs |
| 1035 | + var import_i18n2 = __toESM(require_i18n(), 1); | |
| 737 | 1036 | function validateMimeType(file, allowedTypes) { |
| 738 | 1037 | if (!allowedTypes) { |
| 739 | 1038 | return; |
| 740 | 1039 | } |
| @@ -745,9 +1044,9 @@ | ||
| 745 | 1044 | return file.type.startsWith(`${allowedType}/`); |
| 746 | 1045 | }); |
| 747 | 1046 | if (file.type && !isAllowedType) { |
| 748 | 1047 | throw new UploadError({ |
| 749 | - code: "MIME_TYPE_NOT_SUPPORTED", | |
| 1048 | + code: ErrorCode.MIME_TYPE_NOT_SUPPORTED, | |
| 750 | 1049 | message: (0, import_i18n2.sprintf)( |
| 751 | 1050 | // translators: %s: file name. |
| 752 | 1051 | (0, import_i18n2.__)("%s: Sorry, this file type is not supported here."), |
| 753 | 1052 | file.name |
| @@ -789,9 +1088,9 @@ | ||
| 789 | 1088 | file.type |
| 790 | 1089 | ); |
| 791 | 1090 | if (file.type && !isAllowedMimeTypeForUser) { |
| 792 | 1091 | throw new UploadError({ |
| 793 | - code: "MIME_TYPE_NOT_ALLOWED_FOR_USER", | |
| 1092 | + code: ErrorCode.MIME_TYPE_NOT_ALLOWED_FOR_USER, | |
| 794 | 1093 | message: (0, import_i18n3.sprintf)( |
| 795 | 1094 | // translators: %s: file name. |
| 796 | 1095 | (0, import_i18n3.__)( |
| 797 | 1096 | "%s: Sorry, you are not allowed to upload this file type." |
| @@ -807,9 +1106,9 @@ | ||
| 807 | 1106 | var import_i18n4 = __toESM(require_i18n(), 1); |
| 808 | 1107 | function validateFileSize(file, maxUploadFileSize) { |
| 809 | 1108 | if (file.size <= 0) { |
| 810 | 1109 | throw new UploadError({ |
| 811 | - code: "EMPTY_FILE", | |
| 1110 | + code: ErrorCode.EMPTY_FILE, | |
| 812 | 1111 | message: (0, import_i18n4.sprintf)( |
| 813 | 1112 | // translators: %s: file name. |
| 814 | 1113 | (0, import_i18n4.__)("%s: This file is empty."), |
| 815 | 1114 | file.name |
| @@ -818,9 +1117,9 @@ | ||
| 818 | 1117 | }); |
| 819 | 1118 | } |
| 820 | 1119 | if (maxUploadFileSize && file.size > maxUploadFileSize) { |
| 821 | 1120 | throw new UploadError({ |
| 822 | - code: "SIZE_ABOVE_LIMIT", | |
| 1121 | + code: ErrorCode.SIZE_ABOVE_LIMIT, | |
| 823 | 1122 | message: (0, import_i18n4.sprintf)( |
| 824 | 1123 | // translators: %s: file name. |
| 825 | 1124 | (0, import_i18n4.__)( |
| 826 | 1125 | "%s: This file exceeds the maximum upload size for this site." |
| @@ -881,17 +1180,38 @@ | ||
| 881 | 1180 | const item = select2.getItem(id); |
| 882 | 1181 | if (!item) { |
| 883 | 1182 | return; |
| 884 | 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 | + } | |
| 885 | 1197 | item.abortController?.abort(); |
| 886 | - await vipsCancelOperations(id); | |
| 1198 | + vipsCancelOperations(id).catch(() => { | |
| 1199 | + }); | |
| 1200 | + cancelGifToVideoOperations(id).catch(() => { | |
| 1201 | + }); | |
| 887 | 1202 | if (!silent) { |
| 888 | 1203 | const { onError } = item; |
| 889 | 1204 | onError?.(error ?? new Error("Upload cancelled")); |
| 890 | - if (!onError && error) { | |
| 1205 | + if (!onError && error && !item.parentId) { | |
| 891 | 1206 | console.error("Upload cancelled", error); |
| 892 | 1207 | } |
| 1208 | + } else { | |
| 1209 | + debug( | |
| 1210 | + `Item cancelled: ${item.file.name} (item ${id}): ${error instanceof Error ? error.message : error}` | |
| 1211 | + ); | |
| 893 | 1212 | } |
| 1213 | + const { currentOperation, parentId, batchId } = item; | |
| 894 | 1214 | dispatch({ |
| 895 | 1215 | type: Type.Cancel, |
| 896 | 1216 | id, |
| 897 | 1217 | error |
| @@ -897,9 +1217,59 @@ | ||
| 897 | 1217 | error |
| 898 | 1218 | }); |
| 899 | 1219 | dispatch.removeItem(id); |
| 900 | 1220 | dispatch.revokeBlobUrls(id); |
| 901 | - 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}`); | |
| 902 | 1272 | item.onBatchSuccess?.(); |
| 903 | 1273 | } |
| 904 | 1274 | }; |
| 905 | 1275 | } |
| @@ -918,8 +1288,57 @@ | ||
| 918 | 1288 | }); |
| 919 | 1289 | dispatch.processItem(id); |
| 920 | 1290 | }; |
| 921 | 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 | + } | |
| 922 | 1341 | |
| 923 | 1342 | // packages/upload-media/build-module/store/private-actions.mjs |
| 924 | 1343 | var private_actions_exports = {}; |
| 925 | 1344 | __export(private_actions_exports, { |
| @@ -924,8 +1343,9 @@ | ||
| 924 | 1343 | var private_actions_exports = {}; |
| 925 | 1344 | __export(private_actions_exports, { |
| 926 | 1345 | addItem: () => addItem, |
| 927 | 1346 | addSideloadItem: () => addSideloadItem, |
| 1347 | + detectUltraHdr: () => detectUltraHdr, | |
| 928 | 1348 | finalizeItem: () => finalizeItem, |
| 929 | 1349 | finishOperation: () => finishOperation, |
| 930 | 1350 | generateThumbnails: () => generateThumbnails, |
| 931 | 1351 | getTranscodeImageOperation: () => getTranscodeImageOperation, |
| @@ -934,13 +1354,13 @@ | ||
| 934 | 1354 | prepareItem: () => prepareItem, |
| 935 | 1355 | processItem: () => processItem, |
| 936 | 1356 | removeItem: () => removeItem, |
| 937 | 1357 | resizeCropItem: () => resizeCropItem, |
| 938 | - resumeItemByPostId: () => resumeItemByPostId, | |
| 939 | 1358 | resumeQueue: () => resumeQueue, |
| 940 | 1359 | revokeBlobUrls: () => revokeBlobUrls, |
| 941 | 1360 | rotateItem: () => rotateItem, |
| 942 | 1361 | sideloadItem: () => sideloadItem, |
| 1362 | + transcodeGifItem: () => transcodeGifItem, | |
| 943 | 1363 | transcodeImageItem: () => transcodeImageItem, |
| 944 | 1364 | updateItemProgress: () => updateItemProgress, |
| 945 | 1365 | updateSettings: () => updateSettings, |
| 946 | 1366 | uploadItem: () => uploadItem |
| @@ -945,9 +1365,1180 @@ | ||
| 945 | 1365 | updateSettings: () => updateSettings, |
| 946 | 1366 | uploadItem: () => uploadItem |
| 947 | 1367 | }); |
| 948 | 1368 | var import_blob = __toESM(require_blob(), 1); |
| 1369 | + var import_i18n7 = __toESM(require_i18n(), 1); | |
| 949 | 1370 | |
| 1371 | + // packages/upload-media/build-module/heic-parser.mjs | |
| 1372 | + var Reader = class { | |
| 1373 | + view; | |
| 1374 | + buffer; | |
| 1375 | + pos; | |
| 1376 | + constructor(buffer, offset = 0) { | |
| 1377 | + this.buffer = buffer; | |
| 1378 | + this.view = new DataView(buffer); | |
| 1379 | + this.pos = offset; | |
| 1380 | + } | |
| 1381 | + u8() { | |
| 1382 | + const v = this.view.getUint8(this.pos); | |
| 1383 | + this.pos += 1; | |
| 1384 | + return v; | |
| 1385 | + } | |
| 1386 | + u16() { | |
| 1387 | + const v = this.view.getUint16(this.pos); | |
| 1388 | + this.pos += 2; | |
| 1389 | + return v; | |
| 1390 | + } | |
| 1391 | + u32() { | |
| 1392 | + const v = this.view.getUint32(this.pos); | |
| 1393 | + this.pos += 4; | |
| 1394 | + return v; | |
| 1395 | + } | |
| 1396 | + u64() { | |
| 1397 | + const hi = this.view.getUint32(this.pos); | |
| 1398 | + const lo = this.view.getUint32(this.pos + 4); | |
| 1399 | + this.pos += 8; | |
| 1400 | + return hi * 4294967296 + lo; | |
| 1401 | + } | |
| 1402 | + /** | |
| 1403 | + * Read a variable-width unsigned integer (0, 4 or 8 bytes). | |
| 1404 | + * | |
| 1405 | + * @param size Byte width to read (0, 4, or 8). | |
| 1406 | + */ | |
| 1407 | + uN(size) { | |
| 1408 | + if (size === 0) { | |
| 1409 | + return 0; | |
| 1410 | + } | |
| 1411 | + if (size === 4) { | |
| 1412 | + return this.u32(); | |
| 1413 | + } | |
| 1414 | + if (size === 8) { | |
| 1415 | + return this.u64(); | |
| 1416 | + } | |
| 1417 | + throw new Error(`Unsupported uint size: ${size}`); | |
| 1418 | + } | |
| 1419 | + str(len) { | |
| 1420 | + let s = ""; | |
| 1421 | + for (let i = 0; i < len; i++) { | |
| 1422 | + s += String.fromCharCode(this.view.getUint8(this.pos + i)); | |
| 1423 | + } | |
| 1424 | + this.pos += len; | |
| 1425 | + return s; | |
| 1426 | + } | |
| 1427 | + bytes(len) { | |
| 1428 | + const b = new Uint8Array(this.buffer, this.pos, len); | |
| 1429 | + this.pos += len; | |
| 1430 | + return new Uint8Array(b); | |
| 1431 | + } | |
| 1432 | + }; | |
| 1433 | + function readBox(r) { | |
| 1434 | + if (r.pos + 8 > r.view.byteLength) { | |
| 1435 | + return null; | |
| 1436 | + } | |
| 1437 | + const offset = r.pos; | |
| 1438 | + let size = r.u32(); | |
| 1439 | + const type = r.str(4); | |
| 1440 | + let headerSize = 8; | |
| 1441 | + if (size === 1) { | |
| 1442 | + size = r.u64(); | |
| 1443 | + headerSize = 16; | |
| 1444 | + } else if (size === 0) { | |
| 1445 | + size = r.view.byteLength - offset; | |
| 1446 | + } | |
| 1447 | + return { type, offset, size, headerSize }; | |
| 1448 | + } | |
| 1449 | + function findBoxes(r, start, end) { | |
| 1450 | + const boxes = []; | |
| 1451 | + r.pos = start; | |
| 1452 | + while (r.pos < end) { | |
| 1453 | + const box = readBox(r); | |
| 1454 | + if (!box || box.size < 8) { | |
| 1455 | + break; | |
| 1456 | + } | |
| 1457 | + boxes.push(box); | |
| 1458 | + r.pos = box.offset + box.size; | |
| 1459 | + } | |
| 1460 | + return boxes; | |
| 1461 | + } | |
| 1462 | + function findBox(r, start, end, type) { | |
| 1463 | + r.pos = start; | |
| 1464 | + while (r.pos < end) { | |
| 1465 | + const box = readBox(r); | |
| 1466 | + if (!box || box.size < 8) { | |
| 1467 | + break; | |
| 1468 | + } | |
| 1469 | + if (box.type === type) { | |
| 1470 | + return box; | |
| 1471 | + } | |
| 1472 | + r.pos = box.offset + box.size; | |
| 1473 | + } | |
| 1474 | + return void 0; | |
| 1475 | + } | |
| 1476 | + function parsePitm(r, box) { | |
| 1477 | + r.pos = box.offset + box.headerSize; | |
| 1478 | + const version = r.u8(); | |
| 1479 | + r.pos += 3; | |
| 1480 | + return version === 0 ? r.u16() : r.u32(); | |
| 1481 | + } | |
| 1482 | + function parseIloc(r, box) { | |
| 1483 | + r.pos = box.offset + box.headerSize; | |
| 1484 | + const version = r.u8(); | |
| 1485 | + r.pos += 3; | |
| 1486 | + const byte1 = r.u8(); | |
| 1487 | + const offsetSize = byte1 >> 4 & 15; | |
| 1488 | + const lengthSize = byte1 & 15; | |
| 1489 | + const byte2 = r.u8(); | |
| 1490 | + const baseOffsetSize = byte2 >> 4 & 15; | |
| 1491 | + const indexSize = version >= 1 ? byte2 & 15 : 0; | |
| 1492 | + const itemCount = version < 2 ? r.u16() : r.u32(); | |
| 1493 | + const items = /* @__PURE__ */ new Map(); | |
| 1494 | + for (let i = 0; i < itemCount; i++) { | |
| 1495 | + const itemId = version < 2 ? r.u16() : r.u32(); | |
| 1496 | + let constructionMethod = 0; | |
| 1497 | + if (version === 1 || version === 2) { | |
| 1498 | + const cm = r.u16(); | |
| 1499 | + constructionMethod = cm & 15; | |
| 1500 | + } | |
| 1501 | + r.u16(); | |
| 1502 | + const baseOffset = r.uN(baseOffsetSize); | |
| 1503 | + const extentCount = r.u16(); | |
| 1504 | + const extents = []; | |
| 1505 | + for (let j = 0; j < extentCount; j++) { | |
| 1506 | + if (version >= 1) { | |
| 1507 | + r.uN(indexSize); | |
| 1508 | + } | |
| 1509 | + const extOffset = r.uN(offsetSize); | |
| 1510 | + const extLength = r.uN(lengthSize); | |
| 1511 | + extents.push({ | |
| 1512 | + offset: baseOffset + extOffset, | |
| 1513 | + length: extLength | |
| 1514 | + }); | |
| 1515 | + } | |
| 1516 | + items.set(itemId, { constructionMethod, extents }); | |
| 1517 | + } | |
| 1518 | + return items; | |
| 1519 | + } | |
| 1520 | + function parseIpma(r, box) { | |
| 1521 | + r.pos = box.offset + box.headerSize; | |
| 1522 | + const vf = r.u32(); | |
| 1523 | + const version = vf >>> 24; | |
| 1524 | + const flags = vf & 16777215; | |
| 1525 | + const largeIndex = (flags & 1) !== 0; | |
| 1526 | + const entryCount = r.u32(); | |
| 1527 | + const associations = /* @__PURE__ */ new Map(); | |
| 1528 | + for (let i = 0; i < entryCount; i++) { | |
| 1529 | + const itemId = version < 1 ? r.u16() : r.u32(); | |
| 1530 | + const assocCount = r.u8(); | |
| 1531 | + const indices = []; | |
| 1532 | + for (let j = 0; j < assocCount; j++) { | |
| 1533 | + if (largeIndex) { | |
| 1534 | + indices.push(r.u16() & 32767); | |
| 1535 | + } else { | |
| 1536 | + indices.push(r.u8() & 127); | |
| 1537 | + } | |
| 1538 | + } | |
| 1539 | + associations.set(itemId, indices); | |
| 1540 | + } | |
| 1541 | + return associations; | |
| 1542 | + } | |
| 1543 | + function parseIspe(r, box) { | |
| 1544 | + r.pos = box.offset + box.headerSize + 4; | |
| 1545 | + return { width: r.u32(), height: r.u32() }; | |
| 1546 | + } | |
| 1547 | + function parseIrot(r, box) { | |
| 1548 | + r.pos = box.offset + box.headerSize; | |
| 1549 | + return (r.u8() & 3) * 90; | |
| 1550 | + } | |
| 1551 | + function parseIinf(r, box) { | |
| 1552 | + r.pos = box.offset + box.headerSize; | |
| 1553 | + const version = r.u8(); | |
| 1554 | + r.pos += 3; | |
| 1555 | + const entryCount = version === 0 ? r.u16() : r.u32(); | |
| 1556 | + const itemTypes = /* @__PURE__ */ new Map(); | |
| 1557 | + const entriesStart = r.pos; | |
| 1558 | + const boxEnd = box.offset + box.size; | |
| 1559 | + const infeBoxes = findBoxes(r, entriesStart, boxEnd); | |
| 1560 | + for (let i = 0; i < Math.min(entryCount, infeBoxes.length); i++) { | |
| 1561 | + const infe = infeBoxes[i]; | |
| 1562 | + if (infe.type !== "infe") { | |
| 1563 | + continue; | |
| 1564 | + } | |
| 1565 | + r.pos = infe.offset + infe.headerSize; | |
| 1566 | + const infeVersion = r.u8(); | |
| 1567 | + r.pos += 3; | |
| 1568 | + if (infeVersion >= 2) { | |
| 1569 | + const itemId = infeVersion === 2 ? r.u16() : r.u32(); | |
| 1570 | + r.u16(); | |
| 1571 | + const itemType = r.str(4); | |
| 1572 | + itemTypes.set(itemId, itemType); | |
| 1573 | + } | |
| 1574 | + } | |
| 1575 | + return itemTypes; | |
| 1576 | + } | |
| 1577 | + function parseIref(r, box, refType) { | |
| 1578 | + r.pos = box.offset + box.headerSize; | |
| 1579 | + const version = r.u8(); | |
| 1580 | + r.pos += 3; | |
| 1581 | + const refs = /* @__PURE__ */ new Map(); | |
| 1582 | + const boxEnd = box.offset + box.size; | |
| 1583 | + while (r.pos < boxEnd) { | |
| 1584 | + const refBox = readBox(r); | |
| 1585 | + if (!refBox || refBox.size < 8) { | |
| 1586 | + break; | |
| 1587 | + } | |
| 1588 | + r.pos = refBox.offset + refBox.headerSize; | |
| 1589 | + const fromId = version === 0 ? r.u16() : r.u32(); | |
| 1590 | + const refCount = r.u16(); | |
| 1591 | + const toIds = []; | |
| 1592 | + for (let i = 0; i < refCount; i++) { | |
| 1593 | + toIds.push(version === 0 ? r.u16() : r.u32()); | |
| 1594 | + } | |
| 1595 | + if (refBox.type === refType) { | |
| 1596 | + refs.set(fromId, toIds); | |
| 1597 | + } | |
| 1598 | + r.pos = refBox.offset + refBox.size; | |
| 1599 | + } | |
| 1600 | + return refs; | |
| 1601 | + } | |
| 1602 | + function reverseBits32(n) { | |
| 1603 | + n = n >>> 1 & 1431655765 | (n & 1431655765) << 1; | |
| 1604 | + n = n >>> 2 & 858993459 | (n & 858993459) << 2; | |
| 1605 | + n = n >>> 4 & 252645135 | (n & 252645135) << 4; | |
| 1606 | + n = n >>> 8 & 16711935 | (n & 16711935) << 8; | |
| 1607 | + n = n >>> 16 | n << 16; | |
| 1608 | + return n >>> 0; | |
| 1609 | + } | |
| 1610 | + function buildCodecString(r, recordOffset) { | |
| 1611 | + r.pos = recordOffset; | |
| 1612 | + r.u8(); | |
| 1613 | + const byte1 = r.u8(); | |
| 1614 | + const profileSpace = byte1 >> 6 & 3; | |
| 1615 | + const tierFlag = byte1 >> 5 & 1; | |
| 1616 | + const profileIdc = byte1 & 31; | |
| 1617 | + const compatFlags = r.u32(); | |
| 1618 | + const constraintBytes = r.bytes(6); | |
| 1619 | + const levelIdc = r.u8(); | |
| 1620 | + const spacePrefix = profileSpace > 0 ? String.fromCharCode(64 + profileSpace) : ""; | |
| 1621 | + const compatHex = reverseBits32(compatFlags).toString(16).toUpperCase(); | |
| 1622 | + const tierChar = tierFlag ? "H" : "L"; | |
| 1623 | + let lastNonZero = -1; | |
| 1624 | + for (let i = 5; i >= 0; i--) { | |
| 1625 | + if (constraintBytes[i] !== 0) { | |
| 1626 | + lastNonZero = i; | |
| 1627 | + break; | |
| 1628 | + } | |
| 1629 | + } | |
| 1630 | + let constraintStr = ""; | |
| 1631 | + if (lastNonZero >= 0) { | |
| 1632 | + const parts = []; | |
| 1633 | + for (let i = 0; i <= lastNonZero; i++) { | |
| 1634 | + parts.push(constraintBytes[i].toString(16).toUpperCase()); | |
| 1635 | + } | |
| 1636 | + constraintStr = "." + parts.join("."); | |
| 1637 | + } | |
| 1638 | + return `hvc1.${spacePrefix}${profileIdc}.${compatHex}.${tierChar}${levelIdc}${constraintStr}`; | |
| 1639 | + } | |
| 1640 | + function readItemData(buffer, loc, idatOffset) { | |
| 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 | + } | |
| 1649 | + if (loc.extents.length === 1) { | |
| 1650 | + const ext = loc.extents[0]; | |
| 1651 | + const start = baseOffset + ext.offset; | |
| 1652 | + return new Uint8Array(buffer.slice(start, start + ext.length)); | |
| 1653 | + } | |
| 1654 | + let totalLength = 0; | |
| 1655 | + for (const ext of loc.extents) { | |
| 1656 | + totalLength += ext.length; | |
| 1657 | + } | |
| 1658 | + const data = new Uint8Array(totalLength); | |
| 1659 | + let pos = 0; | |
| 1660 | + for (const ext of loc.extents) { | |
| 1661 | + const start = baseOffset + ext.offset; | |
| 1662 | + data.set( | |
| 1663 | + new Uint8Array(buffer.slice(start, start + ext.length)), | |
| 1664 | + pos | |
| 1665 | + ); | |
| 1666 | + pos += ext.length; | |
| 1667 | + } | |
| 1668 | + return data; | |
| 1669 | + } | |
| 1670 | + function findHvcProperties(propIndices, properties) { | |
| 1671 | + let hvcCBox; | |
| 1672 | + let ispeBox; | |
| 1673 | + let irotBox; | |
| 1674 | + for (const idx of propIndices) { | |
| 1675 | + if (idx < 1 || idx > properties.length) { | |
| 1676 | + continue; | |
| 1677 | + } | |
| 1678 | + const prop = properties[idx - 1]; | |
| 1679 | + if (prop.type === "hvcC" && !hvcCBox) { | |
| 1680 | + hvcCBox = prop; | |
| 1681 | + } | |
| 1682 | + if (prop.type === "ispe" && !ispeBox) { | |
| 1683 | + ispeBox = prop; | |
| 1684 | + } | |
| 1685 | + if (prop.type === "irot" && !irotBox) { | |
| 1686 | + irotBox = prop; | |
| 1687 | + } | |
| 1688 | + } | |
| 1689 | + if (!hvcCBox) { | |
| 1690 | + throw new Error("No HEVC configuration (hvcC) found"); | |
| 1691 | + } | |
| 1692 | + if (!ispeBox) { | |
| 1693 | + throw new Error("No image dimensions (ispe) found"); | |
| 1694 | + } | |
| 1695 | + return { hvcCBox, ispeBox, irotBox }; | |
| 1696 | + } | |
| 1697 | + function parseHeic(buffer) { | |
| 1698 | + const r = new Reader(buffer); | |
| 1699 | + const fileEnd = buffer.byteLength; | |
| 1700 | + const metaBox = findBox(r, 0, fileEnd, "meta"); | |
| 1701 | + if (!metaBox) { | |
| 1702 | + throw new Error("No meta box found in HEIC file"); | |
| 1703 | + } | |
| 1704 | + const metaChildStart = metaBox.offset + metaBox.headerSize + 4; | |
| 1705 | + const metaEnd = metaBox.offset + metaBox.size; | |
| 1706 | + const children = findBoxes(r, metaChildStart, metaEnd); | |
| 1707 | + const pitmBox = children.find((b) => b.type === "pitm"); | |
| 1708 | + const ilocBox = children.find((b) => b.type === "iloc"); | |
| 1709 | + const iprpBox = children.find((b) => b.type === "iprp"); | |
| 1710 | + const iinfBox = children.find((b) => b.type === "iinf"); | |
| 1711 | + const irefBox = children.find((b) => b.type === "iref"); | |
| 1712 | + const idatBox = children.find((b) => b.type === "idat"); | |
| 1713 | + const idatOffset = idatBox ? idatBox.offset + idatBox.headerSize : 0; | |
| 1714 | + if (!pitmBox || !ilocBox || !iprpBox) { | |
| 1715 | + throw new Error("Missing required boxes (pitm, iloc, iprp) in HEIC"); | |
| 1716 | + } | |
| 1717 | + const primaryId = parsePitm(r, pitmBox); | |
| 1718 | + const locations = parseIloc(r, ilocBox); | |
| 1719 | + const iprpStart = iprpBox.offset + iprpBox.headerSize; | |
| 1720 | + const iprpEnd = iprpBox.offset + iprpBox.size; | |
| 1721 | + const iprpChildren = findBoxes(r, iprpStart, iprpEnd); | |
| 1722 | + const ipcoBox = iprpChildren.find((b) => b.type === "ipco"); | |
| 1723 | + const ipmaBox = iprpChildren.find((b) => b.type === "ipma"); | |
| 1724 | + if (!ipcoBox || !ipmaBox) { | |
| 1725 | + throw new Error("Missing ipco or ipma in HEIC properties"); | |
| 1726 | + } | |
| 1727 | + const allAssoc = parseIpma(r, ipmaBox); | |
| 1728 | + const ipcoStart = ipcoBox.offset + ipcoBox.headerSize; | |
| 1729 | + const ipcoEnd = ipcoBox.offset + ipcoBox.size; | |
| 1730 | + const properties = findBoxes(r, ipcoStart, ipcoEnd); | |
| 1731 | + let primaryItemType = "hvc1"; | |
| 1732 | + if (iinfBox) { | |
| 1733 | + const itemTypes = parseIinf(r, iinfBox); | |
| 1734 | + const t = itemTypes.get(primaryId); | |
| 1735 | + if (t) { | |
| 1736 | + primaryItemType = t; | |
| 1737 | + } | |
| 1738 | + } | |
| 1739 | + if (primaryItemType === "grid") { | |
| 1740 | + return parseGridImage( | |
| 1741 | + r, | |
| 1742 | + buffer, | |
| 1743 | + primaryId, | |
| 1744 | + locations, | |
| 1745 | + allAssoc, | |
| 1746 | + properties, | |
| 1747 | + irefBox, | |
| 1748 | + idatOffset | |
| 1749 | + ); | |
| 1750 | + } | |
| 1751 | + const primaryLoc = locations.get(primaryId); | |
| 1752 | + if (!primaryLoc || primaryLoc.extents.length === 0) { | |
| 1753 | + throw new Error(`No location data for primary item ${primaryId}`); | |
| 1754 | + } | |
| 1755 | + const primaryPropIndices = allAssoc.get(primaryId); | |
| 1756 | + if (!primaryPropIndices || primaryPropIndices.length === 0) { | |
| 1757 | + throw new Error("No property associations for primary item"); | |
| 1758 | + } | |
| 1759 | + const { hvcCBox, ispeBox, irotBox } = findHvcProperties( | |
| 1760 | + primaryPropIndices, | |
| 1761 | + properties | |
| 1762 | + ); | |
| 1763 | + const hvcCDataStart = hvcCBox.offset + hvcCBox.headerSize; | |
| 1764 | + const hvcCDataSize = hvcCBox.size - hvcCBox.headerSize; | |
| 1765 | + const description = new Uint8Array( | |
| 1766 | + buffer.slice(hvcCDataStart, hvcCDataStart + hvcCDataSize) | |
| 1767 | + ); | |
| 1768 | + const codecString = buildCodecString(r, hvcCDataStart); | |
| 1769 | + const { width, height } = parseIspe(r, ispeBox); | |
| 1770 | + const rotation = irotBox ? parseIrot(r, irotBox) : 0; | |
| 1771 | + return { | |
| 1772 | + codecString, | |
| 1773 | + description, | |
| 1774 | + tiles: [ | |
| 1775 | + { | |
| 1776 | + data: readItemData(buffer, primaryLoc, idatOffset), | |
| 1777 | + x: 0, | |
| 1778 | + y: 0 | |
| 1779 | + } | |
| 1780 | + ], | |
| 1781 | + tileWidth: width, | |
| 1782 | + tileHeight: height, | |
| 1783 | + outputWidth: width, | |
| 1784 | + outputHeight: height, | |
| 1785 | + rotation, | |
| 1786 | + exifOrientation: rotation === 0 ? getUnappliedExifOrientation(buffer) : 1 | |
| 1787 | + }; | |
| 1788 | + } | |
| 1789 | + function parseGridImage(r, buffer, gridItemId, locations, allAssoc, properties, irefBox, idatOffset) { | |
| 1790 | + const gridLoc = locations.get(gridItemId); | |
| 1791 | + if (!gridLoc || gridLoc.extents.length === 0) { | |
| 1792 | + throw new Error("No location data for grid item"); | |
| 1793 | + } | |
| 1794 | + const gridData = readItemData(buffer, gridLoc, idatOffset); | |
| 1795 | + const largeFields = gridData.length > 1 && (gridData[1] & 1) !== 0; | |
| 1796 | + const minGridSize = largeFields ? 12 : 8; | |
| 1797 | + if (gridData.length < minGridSize) { | |
| 1798 | + throw new Error( | |
| 1799 | + `Grid descriptor too short: ${gridData.length} bytes` | |
| 1800 | + ); | |
| 1801 | + } | |
| 1802 | + const rows = gridData[2] + 1; | |
| 1803 | + const columns = gridData[3] + 1; | |
| 1804 | + const gv = new DataView(gridData.buffer, gridData.byteOffset); | |
| 1805 | + let outputWidth; | |
| 1806 | + let outputHeight; | |
| 1807 | + if (largeFields) { | |
| 1808 | + outputWidth = gv.getUint32(4); | |
| 1809 | + outputHeight = gv.getUint32(8); | |
| 1810 | + } else { | |
| 1811 | + outputWidth = gv.getUint16(4); | |
| 1812 | + outputHeight = gv.getUint16(6); | |
| 1813 | + } | |
| 1814 | + if (!irefBox) { | |
| 1815 | + throw new Error("Grid image requires iref box"); | |
| 1816 | + } | |
| 1817 | + const dimgRefs = parseIref(r, irefBox, "dimg"); | |
| 1818 | + const tileItemIds = dimgRefs.get(gridItemId); | |
| 1819 | + if (!tileItemIds || tileItemIds.length === 0) { | |
| 1820 | + throw new Error("No tile references found for grid item"); | |
| 1821 | + } | |
| 1822 | + const expectedTiles = rows * columns; | |
| 1823 | + if (tileItemIds.length < expectedTiles) { | |
| 1824 | + throw new Error( | |
| 1825 | + `Grid expects ${expectedTiles} tiles but found ${tileItemIds.length}` | |
| 1826 | + ); | |
| 1827 | + } | |
| 1828 | + const firstTileProps = allAssoc.get(tileItemIds[0]); | |
| 1829 | + if (!firstTileProps || firstTileProps.length === 0) { | |
| 1830 | + throw new Error("No property associations for tile item"); | |
| 1831 | + } | |
| 1832 | + const { hvcCBox, ispeBox } = findHvcProperties( | |
| 1833 | + firstTileProps, | |
| 1834 | + properties | |
| 1835 | + ); | |
| 1836 | + const gridProps = allAssoc.get(gridItemId) || []; | |
| 1837 | + let irotBox; | |
| 1838 | + for (const idx of gridProps) { | |
| 1839 | + if (idx >= 1 && idx <= properties.length) { | |
| 1840 | + const prop = properties[idx - 1]; | |
| 1841 | + if (prop.type === "irot") { | |
| 1842 | + irotBox = prop; | |
| 1843 | + break; | |
| 1844 | + } | |
| 1845 | + } | |
| 1846 | + } | |
| 1847 | + const hvcCDataStart = hvcCBox.offset + hvcCBox.headerSize; | |
| 1848 | + const hvcCDataSize = hvcCBox.size - hvcCBox.headerSize; | |
| 1849 | + const description = new Uint8Array( | |
| 1850 | + buffer.slice(hvcCDataStart, hvcCDataStart + hvcCDataSize) | |
| 1851 | + ); | |
| 1852 | + const codecString = buildCodecString(r, hvcCDataStart); | |
| 1853 | + const { width: tileWidth, height: tileHeight } = parseIspe(r, ispeBox); | |
| 1854 | + const tiles = []; | |
| 1855 | + for (let row = 0; row < rows; row++) { | |
| 1856 | + for (let col = 0; col < columns; col++) { | |
| 1857 | + const tileIdx = row * columns + col; | |
| 1858 | + const tileId = tileItemIds[tileIdx]; | |
| 1859 | + const tileLoc = locations.get(tileId); | |
| 1860 | + if (!tileLoc || tileLoc.extents.length === 0) { | |
| 1861 | + throw new Error(`No location data for tile item ${tileId}`); | |
| 1862 | + } | |
| 1863 | + tiles.push({ | |
| 1864 | + data: readItemData(buffer, tileLoc, idatOffset), | |
| 1865 | + x: col * tileWidth, | |
| 1866 | + y: row * tileHeight | |
| 1867 | + }); | |
| 1868 | + } | |
| 1869 | + } | |
| 1870 | + const rotation = irotBox ? parseIrot(r, irotBox) : 0; | |
| 1871 | + return { | |
| 1872 | + codecString, | |
| 1873 | + description, | |
| 1874 | + tiles, | |
| 1875 | + tileWidth, | |
| 1876 | + tileHeight, | |
| 1877 | + outputWidth, | |
| 1878 | + outputHeight, | |
| 1879 | + rotation, | |
| 1880 | + exifOrientation: rotation === 0 ? getUnappliedExifOrientation(buffer) : 1 | |
| 1881 | + }; | |
| 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 | + } | |
| 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 | + | |
| 2136 | + // packages/upload-media/build-module/canvas-utils.mjs | |
| 2137 | + var IMAGE_DECODER_TIMEOUT = 15e3; | |
| 2138 | + var HeicUnsupportedError = class extends Error { | |
| 2139 | + }; | |
| 2140 | + async function canvasConvertToJpeg(file, quality = 0.82) { | |
| 2141 | + const baseName = getFileBasename(file.name); | |
| 2142 | + try { | |
| 2143 | + const bitmap = await createImageBitmap(file); | |
| 2144 | + try { | |
| 2145 | + const canvas = new OffscreenCanvas(bitmap.width, bitmap.height); | |
| 2146 | + const ctx = canvas.getContext("2d"); | |
| 2147 | + if (!ctx) { | |
| 2148 | + throw new Error("Could not get canvas 2d context"); | |
| 2149 | + } | |
| 2150 | + ctx.drawImage(bitmap, 0, 0); | |
| 2151 | + const jpegBlob = await canvas.convertToBlob({ | |
| 2152 | + type: "image/jpeg", | |
| 2153 | + quality | |
| 2154 | + }); | |
| 2155 | + return new File([jpegBlob], `${baseName}.jpg`, { | |
| 2156 | + type: "image/jpeg" | |
| 2157 | + }); | |
| 2158 | + } finally { | |
| 2159 | + bitmap.close(); | |
| 2160 | + } | |
| 2161 | + } catch { | |
| 2162 | + } | |
| 2163 | + if (typeof ImageDecoder !== "undefined") { | |
| 2164 | + const supported = await ImageDecoder.isTypeSupported(file.type); | |
| 2165 | + if (supported) { | |
| 2166 | + const decoder = new ImageDecoder({ | |
| 2167 | + type: file.type, | |
| 2168 | + data: file.stream() | |
| 2169 | + }); | |
| 2170 | + let videoFrame; | |
| 2171 | + let timeoutId; | |
| 2172 | + let timedOut = false; | |
| 2173 | + try { | |
| 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) { | |
| 2198 | + try { | |
| 2199 | + const canvas = new OffscreenCanvas( | |
| 2200 | + videoFrame.displayWidth, | |
| 2201 | + videoFrame.displayHeight | |
| 2202 | + ); | |
| 2203 | + const ctx = canvas.getContext("2d"); | |
| 2204 | + if (!ctx) { | |
| 2205 | + throw new Error("Could not get canvas 2d context"); | |
| 2206 | + } | |
| 2207 | + ctx.drawImage(videoFrame, 0, 0); | |
| 2208 | + const jpegBlob = await canvas.convertToBlob({ | |
| 2209 | + type: "image/jpeg", | |
| 2210 | + quality | |
| 2211 | + }); | |
| 2212 | + return new File([jpegBlob], `${baseName}.jpg`, { | |
| 2213 | + type: "image/jpeg" | |
| 2214 | + }); | |
| 2215 | + } finally { | |
| 2216 | + videoFrame.close(); | |
| 2217 | + } | |
| 2218 | + } | |
| 2219 | + } | |
| 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 | + } | |
| 2229 | + if (typeof VideoDecoder !== "undefined") { | |
| 2230 | + let supported = false; | |
| 2231 | + try { | |
| 2232 | + const support = await VideoDecoder.isConfigSupported({ | |
| 2233 | + codec: heicData.codecString | |
| 2234 | + }); | |
| 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 | |
| 2254 | + ); | |
| 2255 | + try { | |
| 2256 | + ctx.drawImage(frame, tile.x, tile.y); | |
| 2257 | + } finally { | |
| 2258 | + frame.close(); | |
| 2259 | + } | |
| 2260 | + } | |
| 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 | + }); | |
| 2269 | + } | |
| 2270 | + } | |
| 2271 | + throw new HeicUnsupportedError(getHeicUnsupportedMessage()); | |
| 2272 | + } | |
| 2273 | + function applyRotation(source, rotation) { | |
| 2274 | + if (rotation === 0) { | |
| 2275 | + return source; | |
| 2276 | + } | |
| 2277 | + const swap = rotation === 90 || rotation === 270; | |
| 2278 | + const w = swap ? source.height : source.width; | |
| 2279 | + const h = swap ? source.width : source.height; | |
| 2280 | + const rotated = new OffscreenCanvas(w, h); | |
| 2281 | + const ctx = rotated.getContext("2d"); | |
| 2282 | + if (!ctx) { | |
| 2283 | + return source; | |
| 2284 | + } | |
| 2285 | + ctx.translate(w / 2, h / 2); | |
| 2286 | + ctx.rotate(-rotation * Math.PI / 180); | |
| 2287 | + ctx.drawImage(source, -source.width / 2, -source.height / 2); | |
| 2288 | + return rotated; | |
| 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 | + } | |
| 2327 | + function decodeHevcFrame(codec, description, width, height, data) { | |
| 2328 | + return new Promise((resolve, reject) => { | |
| 2329 | + const decoder = new VideoDecoder({ | |
| 2330 | + output: (frame) => { | |
| 2331 | + decoder.close(); | |
| 2332 | + resolve(frame); | |
| 2333 | + }, | |
| 2334 | + error: (e) => { | |
| 2335 | + if (decoder.state !== "closed") { | |
| 2336 | + decoder.close(); | |
| 2337 | + } | |
| 2338 | + reject(e); | |
| 2339 | + } | |
| 2340 | + }); | |
| 2341 | + decoder.configure({ | |
| 2342 | + codec, | |
| 2343 | + codedWidth: width, | |
| 2344 | + codedHeight: height, | |
| 2345 | + description | |
| 2346 | + }); | |
| 2347 | + decoder.decode( | |
| 2348 | + new EncodedVideoChunk({ | |
| 2349 | + type: "key", | |
| 2350 | + timestamp: 0, | |
| 2351 | + data | |
| 2352 | + }) | |
| 2353 | + ); | |
| 2354 | + decoder.flush().catch((e) => { | |
| 2355 | + if (decoder.state !== "closed") { | |
| 2356 | + decoder.close(); | |
| 2357 | + } | |
| 2358 | + reject(e); | |
| 2359 | + }); | |
| 2360 | + }); | |
| 2361 | + } | |
| 2362 | + | |
| 2363 | + // packages/upload-media/build-module/feature-detection.mjs | |
| 2364 | + var cachedResult = null; | |
| 2365 | + function detectClientSideMediaSupport() { | |
| 2366 | + if (cachedResult !== null) { | |
| 2367 | + return cachedResult; | |
| 2368 | + } | |
| 2369 | + if (typeof WebAssembly === "undefined") { | |
| 2370 | + cachedResult = { | |
| 2371 | + supported: false, | |
| 2372 | + reason: "WebAssembly is not supported in this browser." | |
| 2373 | + }; | |
| 2374 | + return cachedResult; | |
| 2375 | + } | |
| 2376 | + if (typeof SharedArrayBuffer === "undefined") { | |
| 2377 | + cachedResult = { | |
| 2378 | + supported: false, | |
| 2379 | + reason: "SharedArrayBuffer is not available. This may be due to missing cross-origin isolation headers." | |
| 2380 | + }; | |
| 2381 | + return cachedResult; | |
| 2382 | + } | |
| 2383 | + if (typeof Worker === "undefined") { | |
| 2384 | + cachedResult = { | |
| 2385 | + supported: false, | |
| 2386 | + reason: "Web Workers are not supported in this browser." | |
| 2387 | + }; | |
| 2388 | + return cachedResult; | |
| 2389 | + } | |
| 2390 | + if (typeof navigator !== "undefined" && "deviceMemory" in navigator && navigator.deviceMemory <= 2) { | |
| 2391 | + cachedResult = { | |
| 2392 | + supported: false, | |
| 2393 | + reason: "Device has insufficient memory for client-side media processing." | |
| 2394 | + }; | |
| 2395 | + return cachedResult; | |
| 2396 | + } | |
| 2397 | + if (typeof navigator !== "undefined" && "hardwareConcurrency" in navigator && navigator.hardwareConcurrency < 2) { | |
| 2398 | + cachedResult = { | |
| 2399 | + supported: false, | |
| 2400 | + reason: "Device has insufficient CPU cores for client-side media processing." | |
| 2401 | + }; | |
| 2402 | + return cachedResult; | |
| 2403 | + } | |
| 2404 | + if (typeof navigator !== "undefined") { | |
| 2405 | + const connection = navigator.connection; | |
| 2406 | + if (connection) { | |
| 2407 | + if (connection.saveData) { | |
| 2408 | + cachedResult = { | |
| 2409 | + supported: false, | |
| 2410 | + reason: "Data saver mode is enabled." | |
| 2411 | + }; | |
| 2412 | + return cachedResult; | |
| 2413 | + } | |
| 2414 | + if (connection.effectiveType === "slow-2g" || connection.effectiveType === "2g") { | |
| 2415 | + cachedResult = { | |
| 2416 | + supported: false, | |
| 2417 | + reason: "Network connection is too slow for client-side media processing." | |
| 2418 | + }; | |
| 2419 | + return cachedResult; | |
| 2420 | + } | |
| 2421 | + } | |
| 2422 | + } | |
| 2423 | + if (typeof window !== "undefined") { | |
| 2424 | + try { | |
| 2425 | + const testBlob = new Blob([""], { | |
| 2426 | + type: "application/javascript" | |
| 2427 | + }); | |
| 2428 | + const testUrl = URL.createObjectURL(testBlob); | |
| 2429 | + try { | |
| 2430 | + const testWorker = new Worker(testUrl); | |
| 2431 | + testWorker.terminate(); | |
| 2432 | + } finally { | |
| 2433 | + URL.revokeObjectURL(testUrl); | |
| 2434 | + } | |
| 2435 | + } catch { | |
| 2436 | + cachedResult = { | |
| 2437 | + supported: false, | |
| 2438 | + reason: "The site's Content Security Policy (CSP) does not allow blob: workers. The worker-src directive must include blob: to enable client-side media processing." | |
| 2439 | + }; | |
| 2440 | + return cachedResult; | |
| 2441 | + } | |
| 2442 | + } | |
| 2443 | + cachedResult = { supported: true }; | |
| 2444 | + return cachedResult; | |
| 2445 | + } | |
| 2446 | + function isClientSideMediaSupported() { | |
| 2447 | + return detectClientSideMediaSupport().supported; | |
| 2448 | + } | |
| 2449 | + function isHeicCanvasSupported() { | |
| 2450 | + return typeof createImageBitmap !== "undefined" && typeof OffscreenCanvas !== "undefined"; | |
| 2451 | + } | |
| 2452 | + function clearFeatureDetectionCache() { | |
| 2453 | + cachedResult = null; | |
| 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 | + } | |
| 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 | + | |
| 950 | 2541 | // packages/upload-media/build-module/stub-file.mjs |
| 951 | 2542 | var StubFile = class extends File { |
| 952 | 2543 | constructor(fileName = "stub-file") { |
| 953 | 2544 | super([], fileName); |
| @@ -955,14 +2546,9 @@ | ||
| 955 | 2546 | }; |
| 956 | 2547 | |
| 957 | 2548 | // packages/upload-media/build-module/store/private-actions.mjs |
| 958 | 2549 | var DEFAULT_OUTPUT_QUALITY = 0.82; |
| 959 | - function shouldPauseForSideload(item, operation, select2) { | |
| 960 | - if (operation !== OperationType.Upload || !item.parentId || !item.additionalData.post) { | |
| 961 | - return false; | |
| 962 | - } | |
| 963 | - return select2.isUploadingToPost(item.additionalData.post); | |
| 964 | - } | |
| 2550 | + var ultraHdrItems = /* @__PURE__ */ new Set(); | |
| 965 | 2551 | function addItem({ |
| 966 | 2552 | file: fileOrBlob, |
| 967 | 2553 | batchId, |
| 968 | 2554 | onChange, |
| @@ -998,9 +2584,8 @@ | ||
| 998 | 2584 | attachment: { |
| 999 | 2585 | url: blobUrl |
| 1000 | 2586 | }, |
| 1001 | 2587 | additionalData: { |
| 1002 | - convert_format: false, | |
| 1003 | 2588 | generate_sub_sizes: false, |
| 1004 | 2589 | ...additionalData |
| 1005 | 2590 | }, |
| 1006 | 2591 | onChange, |
| @@ -1054,8 +2639,11 @@ | ||
| 1054 | 2639 | const item = select2.getItem(id); |
| 1055 | 2640 | if (!item) { |
| 1056 | 2641 | return; |
| 1057 | 2642 | } |
| 2643 | + if (item.currentOperation) { | |
| 2644 | + return; | |
| 2645 | + } | |
| 1058 | 2646 | const { |
| 1059 | 2647 | attachment, |
| 1060 | 2648 | onChange, |
| 1061 | 2649 | onSuccess, |
| @@ -1064,15 +2652,8 @@ | ||
| 1064 | 2652 | parentId |
| 1065 | 2653 | } = item; |
| 1066 | 2654 | const operation = Array.isArray(item.operations?.[0]) ? item.operations[0][0] : item.operations?.[0]; |
| 1067 | 2655 | const operationArgs = Array.isArray(item.operations?.[0]) ? item.operations[0][1] : void 0; |
| 1068 | - if (shouldPauseForSideload(item, operation, select2)) { | |
| 1069 | - dispatch({ | |
| 1070 | - type: Type.PauseItem, | |
| 1071 | - id | |
| 1072 | - }); | |
| 1073 | - return; | |
| 1074 | - } | |
| 1075 | 2656 | if (operation === OperationType.Upload) { |
| 1076 | 2657 | const settings = select2.getSettings(); |
| 1077 | 2658 | const activeCount = select2.getActiveUploadCount(); |
| 1078 | 2659 | if (activeCount >= settings.maxConcurrentUploads) { |
| @@ -1085,10 +2666,19 @@ | ||
| 1085 | 2666 | if (activeCount >= settings.maxConcurrentImageProcessing) { |
| 1086 | 2667 | return; |
| 1087 | 2668 | } |
| 1088 | 2669 | } |
| 2670 | + if (operation === OperationType.TranscodeGif) { | |
| 2671 | + const activeCount = select2.getActiveVideoProcessingCount(); | |
| 2672 | + if (activeCount >= 1) { | |
| 2673 | + return; | |
| 2674 | + } | |
| 2675 | + } | |
| 1089 | 2676 | if (attachment) { |
| 1090 | - onChange?.([attachment]); | |
| 2677 | + const isHeicUrl = attachment.url && /\.hei[cf]$/i.test(attachment.url); | |
| 2678 | + if (!isHeicUrl) { | |
| 2679 | + onChange?.([attachment]); | |
| 2680 | + } | |
| 1091 | 2681 | } |
| 1092 | 2682 | if (!operation) { |
| 1093 | 2683 | if (parentId || !parentId && !select2.hasPendingItemsByParentId(id)) { |
| 1094 | 2684 | if (attachment) { |
| @@ -1127,8 +2717,11 @@ | ||
| 1127 | 2717 | type: Type.OperationStart, |
| 1128 | 2718 | id, |
| 1129 | 2719 | operation |
| 1130 | 2720 | }); |
| 2721 | + debug( | |
| 2722 | + `Starting operation ${operation} for ${item.file.name} (item ${item.id})` | |
| 2723 | + ); | |
| 1131 | 2724 | switch (operation) { |
| 1132 | 2725 | case OperationType.Prepare: |
| 1133 | 2726 | dispatch.prepareItem(item.id); |
| 1134 | 2727 | break; |
| @@ -1149,8 +2742,14 @@ | ||
| 1149 | 2742 | item.id, |
| 1150 | 2743 | operationArgs |
| 1151 | 2744 | ); |
| 1152 | 2745 | break; |
| 2746 | + case OperationType.TranscodeGif: | |
| 2747 | + dispatch.transcodeGifItem( | |
| 2748 | + item.id, | |
| 2749 | + operationArgs | |
| 2750 | + ); | |
| 2751 | + break; | |
| 1153 | 2752 | case OperationType.Upload: |
| 1154 | 2753 | if (item.parentId) { |
| 1155 | 2754 | dispatch.sideloadItem(id); |
| 1156 | 2755 | } else { |
| @@ -1162,8 +2761,11 @@ | ||
| 1162 | 2761 | break; |
| 1163 | 2762 | case OperationType.Finalize: |
| 1164 | 2763 | dispatch.finalizeItem(id); |
| 1165 | 2764 | break; |
| 2765 | + case OperationType.DetectUltraHdr: | |
| 2766 | + dispatch.detectUltraHdr(id); | |
| 2767 | + break; | |
| 1166 | 2768 | } |
| 1167 | 2769 | }; |
| 1168 | 2770 | } |
| 1169 | 2771 | function pauseQueue() { |
| @@ -1176,9 +2778,13 @@ | ||
| 1176 | 2778 | dispatch({ |
| 1177 | 2779 | type: Type.ResumeQueue |
| 1178 | 2780 | }); |
| 1179 | 2781 | for (const item of select2.getAllItems()) { |
| 1180 | - dispatch.processItem(item.id); | |
| 2782 | + if (item.status === ItemStatus.PendingRetry) { | |
| 2783 | + dispatch.executeRetry(item.id); | |
| 2784 | + } else { | |
| 2785 | + dispatch.processItem(item.id); | |
| 2786 | + } | |
| 1181 | 2787 | } |
| 1182 | 2788 | }; |
| 1183 | 2789 | } |
| 1184 | 2790 | function pauseItem(id) { |
| @@ -1188,20 +2794,8 @@ | ||
| 1188 | 2794 | id |
| 1189 | 2795 | }); |
| 1190 | 2796 | }; |
| 1191 | 2797 | } |
| 1192 | - function resumeItemByPostId(postOrAttachmentId) { | |
| 1193 | - return async ({ select: select2, dispatch }) => { | |
| 1194 | - const item = select2.getPausedUploadForPost(postOrAttachmentId); | |
| 1195 | - if (item) { | |
| 1196 | - dispatch({ | |
| 1197 | - type: Type.ResumeItem, | |
| 1198 | - id: item.id | |
| 1199 | - }); | |
| 1200 | - dispatch.processItem(item.id); | |
| 1201 | - } | |
| 1202 | - }; | |
| 1203 | - } | |
| 1204 | 2798 | function removeItem(id) { |
| 1205 | 2799 | return async ({ select: select2, dispatch }) => { |
| 1206 | 2800 | const item = select2.getItem(id); |
| 1207 | 2801 | if (!item) { |
| @@ -1206,8 +2800,10 @@ | ||
| 1206 | 2800 | const item = select2.getItem(id); |
| 1207 | 2801 | if (!item) { |
| 1208 | 2802 | return; |
| 1209 | 2803 | } |
| 2804 | + ultraHdrItems.delete(id); | |
| 2805 | + clearRetryTimer(id); | |
| 1210 | 2806 | dispatch({ |
| 1211 | 2807 | type: Type.Remove, |
| 1212 | 2808 | id |
| 1213 | 2809 | }); |
| @@ -1212,8 +2808,9 @@ | ||
| 1212 | 2808 | id |
| 1213 | 2809 | }); |
| 1214 | 2810 | if (select2.getAllItems().length === 0) { |
| 1215 | 2811 | terminateVipsWorker(); |
| 2812 | + terminateVideoConversionWorker(); | |
| 1216 | 2813 | } |
| 1217 | 2814 | }; |
| 1218 | 2815 | } |
| 1219 | 2816 | function finishOperation(id, updates) { |
| @@ -1237,8 +2834,17 @@ | ||
| 1237 | 2834 | for (const pendingItem of pendingItems) { |
| 1238 | 2835 | dispatch.processItem(pendingItem.id); |
| 1239 | 2836 | } |
| 1240 | 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 | + } | |
| 1241 | 2847 | }; |
| 1242 | 2848 | } |
| 1243 | 2849 | var VALID_IMAGE_FORMATS = ["jpeg", "webp", "avif", "png", "gif"]; |
| 1244 | 2850 | function isValidImageFormat(format) { |
| @@ -1243,21 +2849,9 @@ | ||
| 1243 | 2849 | var VALID_IMAGE_FORMATS = ["jpeg", "webp", "avif", "png", "gif"]; |
| 1244 | 2850 | function isValidImageFormat(format) { |
| 1245 | 2851 | return VALID_IMAGE_FORMATS.includes(format); |
| 1246 | 2852 | } |
| 1247 | - function getInterlacedSetting(outputMimeType, settings) { | |
| 1248 | - switch (outputMimeType) { | |
| 1249 | - case "image/jpeg": | |
| 1250 | - return settings.jpegInterlaced ?? false; | |
| 1251 | - case "image/png": | |
| 1252 | - return settings.pngInterlaced ?? false; | |
| 1253 | - case "image/gif": | |
| 1254 | - return settings.gifInterlaced ?? false; | |
| 1255 | - default: | |
| 1256 | - return false; | |
| 1257 | - } | |
| 1258 | - } | |
| 1259 | - async function getTranscodeImageOperation(file, outputMimeType, settings) { | |
| 2853 | + async function getTranscodeImageOperation(file, outputMimeType, interlaced = false, quality = DEFAULT_OUTPUT_QUALITY) { | |
| 1260 | 2854 | if (file.type === "image/png" && outputMimeType === "image/jpeg") { |
| 1261 | 2855 | const blobUrl = (0, import_blob.createBlobURL)(file); |
| 1262 | 2856 | try { |
| 1263 | 2857 | const hasAlpha = await vipsHasTransparency(blobUrl); |
| @@ -1277,10 +2871,10 @@ | ||
| 1277 | 2871 | return [ |
| 1278 | 2872 | OperationType.TranscodeImage, |
| 1279 | 2873 | { |
| 1280 | 2874 | outputFormat: formatPart, |
| 1281 | - outputQuality: DEFAULT_OUTPUT_QUALITY, | |
| 1282 | - interlaced: getInterlacedSetting(outputMimeType, settings) | |
| 2875 | + outputQuality: quality, | |
| 2876 | + interlaced | |
| 1283 | 2877 | } |
| 1284 | 2878 | ]; |
| 1285 | 2879 | } |
| 1286 | 2880 | function prepareItem(id) { |
| @@ -1291,24 +2885,88 @@ | ||
| 1291 | 2885 | } |
| 1292 | 2886 | const { file } = item; |
| 1293 | 2887 | const operations = []; |
| 1294 | 2888 | const settings = select2.getSettings(); |
| 1295 | - const isImage = file.type.startsWith("image/"); | |
| 1296 | - const isVipsSupported = CLIENT_SIDE_SUPPORTED_MIME_TYPES.includes( | |
| 1297 | - file.type | |
| 1298 | - ); | |
| 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 | + } | |
| 2924 | + let heicJpeg = null; | |
| 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; | |
| 1299 | 2934 | if (isImage && isVipsSupported) { |
| 1300 | - const { imageOutputFormats } = settings; | |
| 1301 | - const outputMimeType = imageOutputFormats?.[file.type]; | |
| 1302 | - if (outputMimeType && outputMimeType !== file.type) { | |
| 1303 | - const transcodeOperation = await getTranscodeImageOperation( | |
| 1304 | - file, | |
| 1305 | - outputMimeType, | |
| 1306 | - settings | |
| 2935 | + const dimensions = await getImageDimensions(file); | |
| 2936 | + if (dimensions && exceedsClientProcessingMemory(dimensions)) { | |
| 2937 | + tooLargeForClient = true; | |
| 2938 | + } | |
| 2939 | + } | |
| 2940 | + if (file.type === "image/jpeg" && !isHeic && !tooLargeForClient) { | |
| 2941 | + operations.push(OperationType.DetectUltraHdr); | |
| 2942 | + } | |
| 2943 | + if (isImage && isVipsSupported && !tooLargeForClient) { | |
| 2944 | + operations.push( | |
| 2945 | + OperationType.Upload, | |
| 2946 | + OperationType.ThumbnailGeneration, | |
| 2947 | + OperationType.Finalize | |
| 2948 | + ); | |
| 2949 | + } else if (isImage && isHeic) { | |
| 2950 | + try { | |
| 2951 | + heicJpeg = await canvasConvertToJpeg( | |
| 2952 | + heicFile, | |
| 2953 | + settings.imageQuality ?? DEFAULT_OUTPUT_QUALITY | |
| 1307 | 2954 | ); |
| 1308 | - if (transcodeOperation) { | |
| 1309 | - operations.push(transcodeOperation); | |
| 1310 | - } | |
| 2955 | + } catch (error) { | |
| 2956 | + const unsupported = error instanceof HeicUnsupportedError; | |
| 2957 | + dispatch.cancelItem( | |
| 2958 | + id, | |
| 2959 | + new UploadError({ | |
| 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 | |
| 2966 | + }) | |
| 2967 | + ); | |
| 2968 | + return; | |
| 1311 | 2969 | } |
| 1312 | 2970 | operations.push( |
| 1313 | 2971 | OperationType.Upload, |
| 1314 | 2972 | OperationType.ThumbnailGeneration, |
| @@ -1321,18 +2979,58 @@ | ||
| 1321 | 2979 | type: Type.AddOperations, |
| 1322 | 2980 | id, |
| 1323 | 2981 | operations |
| 1324 | 2982 | }); |
| 1325 | - const updates = !isVipsSupported || !isImage ? { | |
| 1326 | - additionalData: { | |
| 1327 | - ...item.additionalData, | |
| 1328 | - generate_sub_sizes: true, | |
| 1329 | - convert_format: true | |
| 1330 | - } | |
| 1331 | - } : {}; | |
| 2983 | + let updates; | |
| 2984 | + if (isHeic && heicJpeg) { | |
| 2985 | + const vipsAvailable = isClientSideMediaSupported(); | |
| 2986 | + updates = { | |
| 2987 | + file: heicJpeg, | |
| 2988 | + sourceFile: heicJpeg, | |
| 2989 | + originalHeicFile: heicFile, | |
| 2990 | + additionalData: { | |
| 2991 | + ...item.additionalData, | |
| 2992 | + generate_sub_sizes: !vipsAvailable, | |
| 2993 | + convert_format: true | |
| 2994 | + } | |
| 2995 | + }; | |
| 2996 | + } else if (!isVipsSupported || !isImage || tooLargeForClient) { | |
| 2997 | + updates = { | |
| 2998 | + additionalData: { | |
| 2999 | + ...item.additionalData, | |
| 3000 | + generate_sub_sizes: true, | |
| 3001 | + convert_format: true | |
| 3002 | + } | |
| 3003 | + }; | |
| 3004 | + } else { | |
| 3005 | + updates = { | |
| 3006 | + additionalData: { | |
| 3007 | + ...item.additionalData, | |
| 3008 | + generate_sub_sizes: false | |
| 3009 | + } | |
| 3010 | + }; | |
| 3011 | + } | |
| 1332 | 3012 | dispatch.finishOperation(id, updates); |
| 1333 | 3013 | }; |
| 1334 | 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 | + } | |
| 1335 | 3033 | function uploadItem(id) { |
| 1336 | 3034 | return async ({ select: select2, dispatch }) => { |
| 1337 | 3035 | const item = select2.getItem(id); |
| 1338 | 3036 | if (!item) { |
| @@ -1337,23 +3035,42 @@ | ||
| 1337 | 3035 | const item = select2.getItem(id); |
| 1338 | 3036 | if (!item) { |
| 1339 | 3037 | return; |
| 1340 | 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 | + }; | |
| 1341 | 3057 | select2.getSettings().mediaUpload({ |
| 1342 | 3058 | filesList: [item.file], |
| 1343 | 3059 | additionalData: item.additionalData, |
| 1344 | 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, | |
| 1345 | 3066 | onFileChange: ([attachment]) => { |
| 1346 | 3067 | if (attachment && !(0, import_blob.isBlobURL)(attachment.url)) { |
| 1347 | - dispatch.finishOperation(id, { | |
| 1348 | - attachment | |
| 1349 | - }); | |
| 3068 | + finishUpload(attachment); | |
| 1350 | 3069 | } |
| 1351 | 3070 | }, |
| 1352 | 3071 | onSuccess: ([attachment]) => { |
| 1353 | - dispatch.finishOperation(id, { | |
| 1354 | - attachment | |
| 1355 | - }); | |
| 3072 | + finishUpload(attachment); | |
| 1356 | 3073 | }, |
| 1357 | 3074 | onError: (error) => { |
| 1358 | 3075 | dispatch.cancelItem(id, error); |
| 1359 | 3076 | } |
| @@ -1371,20 +3088,35 @@ | ||
| 1371 | 3088 | if (!mediaSideload) { |
| 1372 | 3089 | dispatch.finishOperation(id, {}); |
| 1373 | 3090 | return; |
| 1374 | 3091 | } |
| 3092 | + const startTime = performance.now(); | |
| 1375 | 3093 | mediaSideload({ |
| 1376 | 3094 | file: item.file, |
| 1377 | 3095 | attachmentId: post, |
| 1378 | 3096 | additionalData, |
| 1379 | 3097 | signal: item.abortController?.signal, |
| 1380 | - onFileChange: ([attachment]) => { | |
| 1381 | - dispatch.finishOperation(id, { attachment }); | |
| 1382 | - dispatch.resumeItemByPostId(post); | |
| 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 | + }); | |
| 3108 | + if (item.parentId) { | |
| 3109 | + dispatch({ | |
| 3110 | + type: Type.AccumulateSubSize, | |
| 3111 | + id: item.parentId, | |
| 3112 | + subSize | |
| 3113 | + }); | |
| 3114 | + } | |
| 3115 | + dispatch.finishOperation(id, {}); | |
| 1383 | 3116 | }, |
| 1384 | 3117 | onError: (error) => { |
| 1385 | 3118 | dispatch.cancelItem(id, error); |
| 1386 | - dispatch.resumeItemByPostId(post); | |
| 1387 | 3119 | } |
| 1388 | 3120 | }); |
| 1389 | 3121 | }; |
| 1390 | 3122 | } |
| @@ -1399,21 +3131,36 @@ | ||
| 1399 | 3131 | file: item.file |
| 1400 | 3132 | }); |
| 1401 | 3133 | return; |
| 1402 | 3134 | } |
| 3135 | + const startTime = performance.now(); | |
| 1403 | 3136 | const addSuffix = Boolean(item.parentId); |
| 1404 | 3137 | const scaledSuffix = Boolean(args.isThresholdResize); |
| 3138 | + const { imageStripMeta, imageMaxBitDepth } = select2.getSettings(); | |
| 1405 | 3139 | try { |
| 1406 | 3140 | const file = await vipsResizeImage( |
| 1407 | 3141 | item.id, |
| 1408 | 3142 | item.file, |
| 1409 | 3143 | args.resize, |
| 1410 | - false, | |
| 1411 | - // smartCrop | |
| 1412 | - addSuffix, | |
| 1413 | - item.abortController?.signal, | |
| 1414 | - 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 | + } | |
| 1415 | 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 | + }); | |
| 1416 | 3163 | const blobUrl = (0, import_blob.createBlobURL)(file); |
| 1417 | 3164 | dispatch({ |
| 1418 | 3165 | type: Type.CacheBlobUrl, |
| 1419 | 3166 | id, |
| @@ -1428,10 +3175,12 @@ | ||
| 1428 | 3175 | } catch (error) { |
| 1429 | 3176 | dispatch.cancelItem( |
| 1430 | 3177 | id, |
| 1431 | 3178 | new UploadError({ |
| 1432 | - code: "IMAGE_TRANSCODING_ERROR", | |
| 1433 | - 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 | + ), | |
| 1434 | 3183 | file: item.file, |
| 1435 | 3184 | cause: error instanceof Error ? error : void 0 |
| 1436 | 3185 | }) |
| 1437 | 3186 | ); |
| @@ -1449,8 +3198,9 @@ | ||
| 1449 | 3198 | file: item.file |
| 1450 | 3199 | }); |
| 1451 | 3200 | return; |
| 1452 | 3201 | } |
| 3202 | + const startTime = performance.now(); | |
| 1453 | 3203 | try { |
| 1454 | 3204 | const file = await vipsRotateImage( |
| 1455 | 3205 | item.id, |
| 1456 | 3206 | item.file, |
| @@ -1456,8 +3206,17 @@ | ||
| 1456 | 3206 | item.file, |
| 1457 | 3207 | args.orientation, |
| 1458 | 3208 | item.abortController?.signal |
| 1459 | 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 | + }); | |
| 1460 | 3219 | const blobUrl = (0, import_blob.createBlobURL)(file); |
| 1461 | 3220 | dispatch({ |
| 1462 | 3221 | type: Type.CacheBlobUrl, |
| 1463 | 3222 | id, |
| @@ -1472,10 +3231,12 @@ | ||
| 1472 | 3231 | } catch (error) { |
| 1473 | 3232 | dispatch.cancelItem( |
| 1474 | 3233 | id, |
| 1475 | 3234 | new UploadError({ |
| 1476 | - code: "IMAGE_ROTATION_ERROR", | |
| 1477 | - 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 | + ), | |
| 1478 | 3239 | file: item.file, |
| 1479 | 3240 | cause: error instanceof Error ? error : void 0 |
| 1480 | 3241 | }) |
| 1481 | 3242 | ); |
| @@ -1493,19 +3254,34 @@ | ||
| 1493 | 3254 | file: item.file |
| 1494 | 3255 | }); |
| 1495 | 3256 | return; |
| 1496 | 3257 | } |
| 3258 | + const startTime = performance.now(); | |
| 1497 | 3259 | const outputMimeType = `image/${args.outputFormat}`; |
| 1498 | 3260 | const quality = args.outputQuality ?? DEFAULT_OUTPUT_QUALITY; |
| 1499 | 3261 | const interlaced = args.interlaced ?? false; |
| 3262 | + const { imageStripMeta, imageMaxBitDepth } = select2.getSettings(); | |
| 1500 | 3263 | try { |
| 1501 | 3264 | const file = await vipsConvertImageFormat( |
| 1502 | 3265 | item.id, |
| 1503 | 3266 | item.file, |
| 1504 | 3267 | outputMimeType, |
| 1505 | - quality, | |
| 1506 | - interlaced | |
| 3268 | + { | |
| 3269 | + quality, | |
| 3270 | + interlaced, | |
| 3271 | + stripMeta: imageStripMeta, | |
| 3272 | + maxBitdepth: imageMaxBitDepth | |
| 3273 | + } | |
| 1507 | 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 | + }); | |
| 1508 | 3284 | const blobUrl = (0, import_blob.createBlobURL)(file); |
| 1509 | 3285 | dispatch({ |
| 1510 | 3286 | type: Type.CacheBlobUrl, |
| 1511 | 3287 | id, |
| @@ -1520,9 +3296,9 @@ | ||
| 1520 | 3296 | } catch (error) { |
| 1521 | 3297 | dispatch.cancelItem( |
| 1522 | 3298 | id, |
| 1523 | 3299 | new UploadError({ |
| 1524 | - code: "MEDIA_TRANSCODING_ERROR", | |
| 3300 | + code: ErrorCode.MEDIA_TRANSCODING_ERROR, | |
| 1525 | 3301 | message: "Image could not be transcoded to the target format", |
| 1526 | 3302 | file: item.file, |
| 1527 | 3303 | cause: error instanceof Error ? error : void 0 |
| 1528 | 3304 | }) |
| @@ -1529,8 +3305,91 @@ | ||
| 1529 | 3305 | ); |
| 1530 | 3306 | } |
| 1531 | 3307 | }; |
| 1532 | 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 | + } | |
| 1533 | 3392 | function generateThumbnails(id) { |
| 1534 | 3393 | return async ({ select: select2, dispatch }) => { |
| 1535 | 3394 | const item = select2.getItem(id); |
| 1536 | 3395 | if (!item) { |
| @@ -1540,19 +3399,74 @@ | ||
| 1540 | 3399 | dispatch.finishOperation(id, {}); |
| 1541 | 3400 | return; |
| 1542 | 3401 | } |
| 1543 | 3402 | const attachment = item.attachment; |
| 1544 | - const needsRotation = attachment.exif_orientation && attachment.exif_orientation !== 1 && !item.file.name.includes("-scaled"); | |
| 1545 | - if (needsRotation && attachment.id) { | |
| 1546 | - try { | |
| 1547 | - const rotatedFile = await vipsRotateImage( | |
| 1548 | - item.id, | |
| 1549 | - item.sourceFile, | |
| 1550 | - attachment.exif_orientation, | |
| 1551 | - item.abortController?.signal | |
| 1552 | - ); | |
| 3403 | + const settings = select2.getSettings(); | |
| 3404 | + if (item.originalHeicFile && attachment.id) { | |
| 3405 | + dispatch.addSideloadItem({ | |
| 3406 | + file: item.originalHeicFile, | |
| 3407 | + batchId: v4_default(), | |
| 3408 | + parentId: item.id, | |
| 3409 | + additionalData: { | |
| 3410 | + post: attachment.id, | |
| 3411 | + image_size: "source_original", | |
| 3412 | + convert_format: false | |
| 3413 | + }, | |
| 3414 | + operations: [OperationType.Upload] | |
| 3415 | + }); | |
| 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; | |
| 3450 | + { | |
| 3451 | + const needsRotation = exifOrientation !== 1 && !item.file.name.includes("-scaled"); | |
| 3452 | + if ((needsRotation || needsClientRotation) && attachment.id) { | |
| 3453 | + try { | |
| 3454 | + rotatedSource = await vipsRotateImage( | |
| 3455 | + item.id, | |
| 3456 | + item.sourceFile, | |
| 3457 | + exifOrientation, | |
| 3458 | + item.abortController?.signal | |
| 3459 | + ); | |
| 3460 | + } catch { | |
| 3461 | + console.warn( | |
| 3462 | + "Failed to rotate image, continuing with thumbnails" | |
| 3463 | + ); | |
| 3464 | + } | |
| 3465 | + } | |
| 3466 | + if (needsRotation && rotatedSource && attachment.id) { | |
| 1553 | 3467 | dispatch.addSideloadItem({ |
| 1554 | - file: rotatedFile, | |
| 3468 | + file: rotatedSource, | |
| 1555 | 3469 | batchId: v4_default(), |
| 1556 | 3470 | parentId: item.id, |
| 1557 | 3471 | additionalData: { |
| 1558 | 3472 | post: attachment.id, |
| @@ -1560,34 +3474,39 @@ | ||
| 1560 | 3474 | convert_format: false |
| 1561 | 3475 | }, |
| 1562 | 3476 | operations: [OperationType.Upload] |
| 1563 | 3477 | }); |
| 1564 | - } catch { | |
| 1565 | - console.warn( | |
| 1566 | - "Failed to rotate image, continuing with thumbnails" | |
| 1567 | - ); | |
| 1568 | 3478 | } |
| 1569 | 3479 | } |
| 1570 | 3480 | if (!item.parentId && attachment.missing_image_sizes && attachment.missing_image_sizes.length > 0) { |
| 1571 | - const settings = select2.getSettings(); | |
| 1572 | 3481 | const allImageSizes = settings.allImageSizes || {}; |
| 1573 | 3482 | const sizesToGenerate = attachment.missing_image_sizes; |
| 1574 | - const file = attachment.filename ? renameFile(item.sourceFile, attachment.filename) : item.sourceFile; | |
| 3483 | + const thumbnailSource = needsClientRotation && rotatedSource ? rotatedSource : item.sourceFile; | |
| 3484 | + const file = attachment.filename ? renameFile(thumbnailSource, attachment.filename) : thumbnailSource; | |
| 1575 | 3485 | const batchId = v4_default(); |
| 1576 | - const { imageOutputFormats } = settings; | |
| 1577 | - const sourceType = item.sourceFile.type; | |
| 1578 | - const outputMimeType = imageOutputFormats?.[sourceType]; | |
| 3486 | + const isUltraHdr = ultraHdrItems.has(item.id); | |
| 3487 | + const outputMimeType = attachment.image_output_format; | |
| 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 | + }; | |
| 1579 | 3499 | let thumbnailTranscodeOperation = null; |
| 1580 | - if (outputMimeType && outputMimeType !== sourceType) { | |
| 3500 | + if (!isUltraHdr && outputMimeType) { | |
| 1581 | 3501 | thumbnailTranscodeOperation = await getTranscodeImageOperation( |
| 1582 | - item.sourceFile, | |
| 3502 | + thumbnailSource, | |
| 1583 | 3503 | outputMimeType, |
| 1584 | - settings | |
| 3504 | + interlaced, | |
| 3505 | + defaultQuality | |
| 1585 | 3506 | ); |
| 1586 | 3507 | } |
| 1587 | - const thumbnailOutputType = thumbnailTranscodeOperation ? `image/${thumbnailTranscodeOperation[1].outputFormat}` : sourceType; | |
| 1588 | - const quality = thumbnailTranscodeOperation ? thumbnailTranscodeOperation[1].outputQuality ?? DEFAULT_OUTPUT_QUALITY : DEFAULT_OUTPUT_QUALITY; | |
| 1589 | - const batchConfigs = []; | |
| 3508 | + const dimensionGroups = /* @__PURE__ */ new Map(); | |
| 1590 | 3509 | for (const name of sizesToGenerate) { |
| 1591 | 3510 | const imageSize = allImageSizes[name]; |
| 1592 | 3511 | if (!imageSize) { |
| 1593 | 3512 | console.warn( |
| @@ -1594,134 +3513,90 @@ | ||
| 1594 | 3513 | `Image size "${name}" not found in configuration` |
| 1595 | 3514 | ); |
| 1596 | 3515 | continue; |
| 1597 | 3516 | } |
| 1598 | - batchConfigs.push({ | |
| 1599 | - name, | |
| 1600 | - resize: imageSize, | |
| 1601 | - quality | |
| 1602 | - }); | |
| 1603 | - } | |
| 1604 | - const { bigImageSizeThreshold } = settings; | |
| 1605 | - let needsScaling = false; | |
| 1606 | - if (bigImageSizeThreshold && attachment.id) { | |
| 1607 | - const bitmap = await createImageBitmap(item.sourceFile); | |
| 1608 | - needsScaling = bitmap.width > bigImageSizeThreshold || bitmap.height > bigImageSizeThreshold; | |
| 1609 | - bitmap.close(); | |
| 1610 | - if (needsScaling) { | |
| 1611 | - batchConfigs.push({ | |
| 1612 | - name: "scaled", | |
| 1613 | - resize: { | |
| 1614 | - width: bigImageSizeThreshold, | |
| 1615 | - height: bigImageSizeThreshold | |
| 1616 | - }, | |
| 1617 | - quality, | |
| 1618 | - scaledSuffix: true | |
| 1619 | - }); | |
| 3517 | + const key = `${imageSize.width}x${imageSize.height}x${imageSize.crop}`; | |
| 3518 | + const group = dimensionGroups.get(key); | |
| 3519 | + if (group) { | |
| 3520 | + group.push(name); | |
| 3521 | + } else { | |
| 3522 | + dimensionGroups.set(key, [name]); | |
| 1620 | 3523 | } |
| 1621 | 3524 | } |
| 1622 | - let batchResults = null; | |
| 1623 | - if (batchConfigs.length > 0) { | |
| 1624 | - try { | |
| 1625 | - batchResults = await vipsBatchResizeImage( | |
| 1626 | - item.id, | |
| 1627 | - file, | |
| 1628 | - thumbnailOutputType, | |
| 1629 | - batchConfigs, | |
| 1630 | - false | |
| 1631 | - ); | |
| 1632 | - } catch { | |
| 1633 | - console.warn( | |
| 1634 | - "Batch resize failed, falling back to per-thumbnail processing" | |
| 1635 | - ); | |
| 3525 | + for (const [, names] of dimensionGroups) { | |
| 3526 | + const imageSize = allImageSizes[names[0]]; | |
| 3527 | + const sizeQuality = qualityForSize(names[0]); | |
| 3528 | + const thumbnailOperations = [ | |
| 3529 | + [ | |
| 3530 | + OperationType.ResizeCrop, | |
| 3531 | + { resize: imageSize, quality: sizeQuality } | |
| 3532 | + ] | |
| 3533 | + ]; | |
| 3534 | + if (!isUltraHdr && thumbnailTranscodeOperation) { | |
| 3535 | + thumbnailOperations.push([ | |
| 3536 | + thumbnailTranscodeOperation[0], | |
| 3537 | + { | |
| 3538 | + ...thumbnailTranscodeOperation[1], | |
| 3539 | + outputQuality: sizeQuality | |
| 3540 | + } | |
| 3541 | + ]); | |
| 1636 | 3542 | } |
| 3543 | + thumbnailOperations.push(OperationType.Upload); | |
| 3544 | + const imageSizeParam = names.length === 1 ? names[0] : names; | |
| 3545 | + dispatch.addSideloadItem({ | |
| 3546 | + file, | |
| 3547 | + batchId, | |
| 3548 | + parentId: item.id, | |
| 3549 | + additionalData: { | |
| 3550 | + // Sideloading does not use the parent post ID but the | |
| 3551 | + // attachment ID as the image sizes need to be added to it. | |
| 3552 | + post: attachment.id, | |
| 3553 | + image_size: imageSizeParam, | |
| 3554 | + convert_format: false | |
| 3555 | + }, | |
| 3556 | + operations: thumbnailOperations | |
| 3557 | + }); | |
| 1637 | 3558 | } |
| 1638 | - if (batchResults) { | |
| 1639 | - for (const result of batchResults) { | |
| 1640 | - dispatch.addSideloadItem({ | |
| 1641 | - file: result.file, | |
| 1642 | - onChange: ([updatedAttachment]) => { | |
| 1643 | - if ((0, import_blob.isBlobURL)(updatedAttachment.url)) { | |
| 1644 | - return; | |
| 1645 | - } | |
| 1646 | - item.onChange?.([updatedAttachment]); | |
| 1647 | - }, | |
| 1648 | - batchId, | |
| 1649 | - parentId: item.id, | |
| 1650 | - additionalData: { | |
| 1651 | - post: attachment.id, | |
| 1652 | - image_size: result.name, | |
| 1653 | - convert_format: false | |
| 1654 | - }, | |
| 1655 | - operations: [OperationType.Upload] | |
| 1656 | - }); | |
| 1657 | - } | |
| 1658 | - } else { | |
| 1659 | - for (const name of sizesToGenerate) { | |
| 1660 | - const imageSize = allImageSizes[name]; | |
| 1661 | - if (!imageSize) { | |
| 1662 | - continue; | |
| 3559 | + { | |
| 3560 | + const { bigImageSizeThreshold } = settings; | |
| 3561 | + if (bigImageSizeThreshold && attachment.id) { | |
| 3562 | + const bitmap = await createImageBitmap(thumbnailSource); | |
| 3563 | + const needsScaling = bitmap.width > bigImageSizeThreshold || bitmap.height > bigImageSizeThreshold; | |
| 3564 | + bitmap.close(); | |
| 3565 | + if (needsScaling) { | |
| 3566 | + const sourceForScaled = attachment.filename ? renameFile(thumbnailSource, attachment.filename) : thumbnailSource; | |
| 3567 | + const scaledOperations = [ | |
| 3568 | + [ | |
| 3569 | + OperationType.ResizeCrop, | |
| 3570 | + { | |
| 3571 | + resize: { | |
| 3572 | + width: bigImageSizeThreshold, | |
| 3573 | + height: bigImageSizeThreshold | |
| 3574 | + }, | |
| 3575 | + isThresholdResize: true, | |
| 3576 | + quality: defaultQuality | |
| 3577 | + } | |
| 3578 | + ] | |
| 3579 | + ]; | |
| 3580 | + if (!isUltraHdr && thumbnailTranscodeOperation) { | |
| 3581 | + scaledOperations.push( | |
| 3582 | + thumbnailTranscodeOperation | |
| 3583 | + ); | |
| 3584 | + } | |
| 3585 | + scaledOperations.push(OperationType.Upload); | |
| 3586 | + dispatch.addSideloadItem({ | |
| 3587 | + file: sourceForScaled, | |
| 3588 | + batchId, | |
| 3589 | + parentId: item.id, | |
| 3590 | + additionalData: { | |
| 3591 | + post: attachment.id, | |
| 3592 | + image_size: "scaled", | |
| 3593 | + convert_format: false | |
| 3594 | + }, | |
| 3595 | + operations: scaledOperations | |
| 3596 | + }); | |
| 1663 | 3597 | } |
| 1664 | - const thumbnailOperations = [ | |
| 1665 | - [OperationType.ResizeCrop, { resize: imageSize }] | |
| 1666 | - ]; | |
| 1667 | - if (thumbnailTranscodeOperation) { | |
| 1668 | - thumbnailOperations.push(thumbnailTranscodeOperation); | |
| 1669 | - } | |
| 1670 | - thumbnailOperations.push(OperationType.Upload); | |
| 1671 | - dispatch.addSideloadItem({ | |
| 1672 | - file, | |
| 1673 | - onChange: ([updatedAttachment]) => { | |
| 1674 | - if ((0, import_blob.isBlobURL)(updatedAttachment.url)) { | |
| 1675 | - return; | |
| 1676 | - } | |
| 1677 | - item.onChange?.([updatedAttachment]); | |
| 1678 | - }, | |
| 1679 | - batchId, | |
| 1680 | - parentId: item.id, | |
| 1681 | - additionalData: { | |
| 1682 | - post: attachment.id, | |
| 1683 | - image_size: name, | |
| 1684 | - convert_format: false | |
| 1685 | - }, | |
| 1686 | - operations: thumbnailOperations | |
| 1687 | - }); | |
| 1688 | 3598 | } |
| 1689 | - if (needsScaling && bigImageSizeThreshold && attachment.id) { | |
| 1690 | - const scaledOperations = [ | |
| 1691 | - [ | |
| 1692 | - OperationType.ResizeCrop, | |
| 1693 | - { | |
| 1694 | - resize: { | |
| 1695 | - width: bigImageSizeThreshold, | |
| 1696 | - height: bigImageSizeThreshold | |
| 1697 | - }, | |
| 1698 | - isThresholdResize: true | |
| 1699 | - } | |
| 1700 | - ] | |
| 1701 | - ]; | |
| 1702 | - if (thumbnailTranscodeOperation) { | |
| 1703 | - scaledOperations.push(thumbnailTranscodeOperation); | |
| 1704 | - } | |
| 1705 | - scaledOperations.push(OperationType.Upload); | |
| 1706 | - dispatch.addSideloadItem({ | |
| 1707 | - file, | |
| 1708 | - onChange: ([updatedAttachment]) => { | |
| 1709 | - if ((0, import_blob.isBlobURL)(updatedAttachment.url)) { | |
| 1710 | - return; | |
| 1711 | - } | |
| 1712 | - item.onChange?.([updatedAttachment]); | |
| 1713 | - }, | |
| 1714 | - batchId, | |
| 1715 | - parentId: item.id, | |
| 1716 | - additionalData: { | |
| 1717 | - post: attachment.id, | |
| 1718 | - image_size: "scaled", | |
| 1719 | - convert_format: false | |
| 1720 | - }, | |
| 1721 | - operations: scaledOperations | |
| 1722 | - }); | |
| 1723 | - } | |
| 1724 | 3599 | } |
| 1725 | 3600 | } |
| 1726 | 3601 | dispatch.finishOperation(id, {}); |
| 1727 | 3602 | }; |
| @@ -1733,16 +3608,32 @@ | ||
| 1733 | 3608 | return; |
| 1734 | 3609 | } |
| 1735 | 3610 | const attachment = item.attachment; |
| 1736 | 3611 | const { mediaFinalize } = select2.getSettings(); |
| 3612 | + const updates = {}; | |
| 1737 | 3613 | if (attachment?.id && mediaFinalize) { |
| 1738 | 3614 | try { |
| 1739 | - await mediaFinalize(attachment.id); | |
| 3615 | + const updatedAttachment = await mediaFinalize( | |
| 3616 | + attachment.id, | |
| 3617 | + item.subSizes || [] | |
| 3618 | + ); | |
| 3619 | + if (updatedAttachment) { | |
| 3620 | + updates.attachment = updatedAttachment; | |
| 3621 | + } | |
| 1740 | 3622 | } catch (error) { |
| 1741 | 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; | |
| 1742 | 3633 | } |
| 1743 | 3634 | } |
| 1744 | - dispatch.finishOperation(id, {}); | |
| 3635 | + dispatch.finishOperation(id, updates); | |
| 1745 | 3636 | }; |
| 1746 | 3637 | } |
| 1747 | 3638 | function revokeBlobUrls(id) { |
| 1748 | 3639 | return async ({ select: select2, dispatch }) => { |
| @@ -1846,96 +3737,114 @@ | ||
| 1846 | 3737 | return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children }); |
| 1847 | 3738 | }); |
| 1848 | 3739 | var provider_default = MediaUploadProvider; |
| 1849 | 3740 | |
| 1850 | - // packages/upload-media/build-module/feature-detection.mjs | |
| 1851 | - var cachedResult = null; | |
| 1852 | - function detectClientSideMediaSupport() { | |
| 1853 | - if (cachedResult !== null) { | |
| 1854 | - return cachedResult; | |
| 1855 | - } | |
| 1856 | - if (typeof WebAssembly === "undefined") { | |
| 1857 | - cachedResult = { | |
| 1858 | - supported: false, | |
| 1859 | - reason: "WebAssembly is not supported in this browser." | |
| 1860 | - }; | |
| 1861 | - return cachedResult; | |
| 1862 | - } | |
| 1863 | - if (typeof SharedArrayBuffer === "undefined") { | |
| 1864 | - cachedResult = { | |
| 1865 | - supported: false, | |
| 1866 | - reason: "SharedArrayBuffer is not available. This may be due to missing cross-origin isolation headers." | |
| 1867 | - }; | |
| 1868 | - return cachedResult; | |
| 1869 | - } | |
| 1870 | - if (typeof Worker === "undefined") { | |
| 1871 | - cachedResult = { | |
| 1872 | - supported: false, | |
| 1873 | - reason: "Web Workers are not supported in this browser." | |
| 1874 | - }; | |
| 1875 | - return cachedResult; | |
| 1876 | - } | |
| 1877 | - if (typeof navigator !== "undefined" && "deviceMemory" in navigator && navigator.deviceMemory <= 2) { | |
| 1878 | - cachedResult = { | |
| 1879 | - supported: false, | |
| 1880 | - reason: "Device has insufficient memory for client-side media processing." | |
| 1881 | - }; | |
| 1882 | - return cachedResult; | |
| 1883 | - } | |
| 1884 | - if (typeof navigator !== "undefined" && "hardwareConcurrency" in navigator && navigator.hardwareConcurrency < 2) { | |
| 1885 | - cachedResult = { | |
| 1886 | - supported: false, | |
| 1887 | - reason: "Device has insufficient CPU cores for client-side media processing." | |
| 1888 | - }; | |
| 1889 | - return cachedResult; | |
| 1890 | - } | |
| 1891 | - if (typeof navigator !== "undefined") { | |
| 1892 | - const connection = navigator.connection; | |
| 1893 | - if (connection) { | |
| 1894 | - if (connection.saveData) { | |
| 1895 | - cachedResult = { | |
| 1896 | - supported: false, | |
| 1897 | - reason: "Data saver mode is enabled." | |
| 1898 | - }; | |
| 1899 | - return cachedResult; | |
| 1900 | - } | |
| 1901 | - if (connection.effectiveType === "slow-2g" || connection.effectiveType === "2g") { | |
| 1902 | - cachedResult = { | |
| 1903 | - supported: false, | |
| 1904 | - reason: "Network connection is too slow for client-side media processing." | |
| 1905 | - }; | |
| 1906 | - return cachedResult; | |
| 1907 | - } | |
| 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.") | |
| 1908 | 3834 | } |
| 1909 | - } | |
| 1910 | - if (typeof window !== "undefined") { | |
| 1911 | - try { | |
| 1912 | - const testBlob = new Blob([""], { | |
| 1913 | - type: "application/javascript" | |
| 1914 | - }); | |
| 1915 | - const testUrl = URL.createObjectURL(testBlob); | |
| 1916 | - try { | |
| 1917 | - const testWorker = new Worker(testUrl); | |
| 1918 | - testWorker.terminate(); | |
| 1919 | - } finally { | |
| 1920 | - URL.revokeObjectURL(testUrl); | |
| 1921 | - } | |
| 1922 | - } catch { | |
| 1923 | - cachedResult = { | |
| 1924 | - supported: false, | |
| 1925 | - reason: "The site's Content Security Policy (CSP) does not allow blob: workers. The worker-src directive must include blob: to enable client-side media processing." | |
| 1926 | - }; | |
| 1927 | - return cachedResult; | |
| 1928 | - } | |
| 1929 | - } | |
| 1930 | - cachedResult = { supported: true }; | |
| 1931 | - return cachedResult; | |
| 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 | + }; | |
| 1932 | 3845 | } |
| 1933 | - function isClientSideMediaSupported() { | |
| 1934 | - return detectClientSideMediaSupport().supported; | |
| 1935 | - } | |
| 1936 | - function clearFeatureDetectionCache() { | |
| 1937 | - cachedResult = null; | |
| 1938 | - } | |
| 1939 | 3846 | return __toCommonJS(index_exports); |
| 3847 | +})(); | |
| 3848 | +(window.wp ||= {}).uploadMedia = wp.uploadMedia; | |
| 1940 | 3849 | })(); |
| 1941 | 3850 | //# sourceMappingURL=index.js.map |