3730 lines
115 KiB
JavaScript
3730 lines
115 KiB
JavaScript
(function() {
|
|
"use strict";
|
|
var wp;
|
|
(wp ||= {}).uploadMedia = (() => {
|
|
var __create = Object.create;
|
|
var __defProp = Object.defineProperty;
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
var __getProtoOf = Object.getPrototypeOf;
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
}) : x)(function(x) {
|
|
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
});
|
|
var __commonJS = (cb, mod) => function __require2() {
|
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
};
|
|
var __export = (target, all) => {
|
|
for (var name in all)
|
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
};
|
|
var __copyProps = (to, from, except, desc) => {
|
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
for (let key of __getOwnPropNames(from))
|
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
}
|
|
return to;
|
|
};
|
|
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
// If the importer is in node compatibility mode or this is not an ESM
|
|
// file that has been converted to a CommonJS file using a Babel-
|
|
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
mod
|
|
));
|
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
|
|
// package-external:@wordpress/data
|
|
var require_data = __commonJS({
|
|
"package-external:@wordpress/data"(exports, module) {
|
|
module.exports = window.wp.data;
|
|
}
|
|
});
|
|
|
|
// package-external:@wordpress/i18n
|
|
var require_i18n = __commonJS({
|
|
"package-external:@wordpress/i18n"(exports, module) {
|
|
module.exports = window.wp.i18n;
|
|
}
|
|
});
|
|
|
|
// package-external:@wordpress/url
|
|
var require_url = __commonJS({
|
|
"package-external:@wordpress/url"(exports, module) {
|
|
module.exports = window.wp.url;
|
|
}
|
|
});
|
|
|
|
// package-external:@wordpress/blob
|
|
var require_blob = __commonJS({
|
|
"package-external:@wordpress/blob"(exports, module) {
|
|
module.exports = window.wp.blob;
|
|
}
|
|
});
|
|
|
|
// package-external:@wordpress/private-apis
|
|
var require_private_apis = __commonJS({
|
|
"package-external:@wordpress/private-apis"(exports, module) {
|
|
module.exports = window.wp.privateApis;
|
|
}
|
|
});
|
|
|
|
// package-external:@wordpress/element
|
|
var require_element = __commonJS({
|
|
"package-external:@wordpress/element"(exports, module) {
|
|
module.exports = window.wp.element;
|
|
}
|
|
});
|
|
|
|
// package-external:@wordpress/compose
|
|
var require_compose = __commonJS({
|
|
"package-external:@wordpress/compose"(exports, module) {
|
|
module.exports = window.wp.compose;
|
|
}
|
|
});
|
|
|
|
// vendor-external:react/jsx-runtime
|
|
var require_jsx_runtime = __commonJS({
|
|
"vendor-external:react/jsx-runtime"(exports, module) {
|
|
module.exports = window.ReactJSXRuntime;
|
|
}
|
|
});
|
|
|
|
// packages/upload-media/build-module/index.mjs
|
|
var index_exports = {};
|
|
__export(index_exports, {
|
|
ErrorCode: () => ErrorCode,
|
|
MediaUploadProvider: () => provider_default,
|
|
UploadError: () => UploadError,
|
|
clearFeatureDetectionCache: () => clearFeatureDetectionCache,
|
|
detectClientSideMediaSupport: () => detectClientSideMediaSupport,
|
|
getErrorMessage: () => getErrorMessage,
|
|
getHeicConversionAdvice: () => getHeicConversionAdvice,
|
|
getHeicUnsupportedMessage: () => getHeicUnsupportedMessage,
|
|
isClientSideMediaSupported: () => isClientSideMediaSupported,
|
|
isHeicCanvasSupported: () => isHeicCanvasSupported,
|
|
store: () => store
|
|
});
|
|
|
|
// packages/upload-media/build-module/store/index.mjs
|
|
var import_data = __toESM(require_data(), 1);
|
|
|
|
// packages/upload-media/build-module/store/types.mjs
|
|
var Type = /* @__PURE__ */ ((Type2) => {
|
|
Type2["Unknown"] = "REDUX_UNKNOWN";
|
|
Type2["Add"] = "ADD_ITEM";
|
|
Type2["Prepare"] = "PREPARE_ITEM";
|
|
Type2["Cancel"] = "CANCEL_ITEM";
|
|
Type2["Remove"] = "REMOVE_ITEM";
|
|
Type2["RetryItem"] = "RETRY_ITEM";
|
|
Type2["ScheduleRetry"] = "SCHEDULE_RETRY";
|
|
Type2["PauseItem"] = "PAUSE_ITEM";
|
|
Type2["ResumeItem"] = "RESUME_ITEM";
|
|
Type2["PauseQueue"] = "PAUSE_QUEUE";
|
|
Type2["ResumeQueue"] = "RESUME_QUEUE";
|
|
Type2["OperationStart"] = "OPERATION_START";
|
|
Type2["OperationFinish"] = "OPERATION_FINISH";
|
|
Type2["AddOperations"] = "ADD_OPERATIONS";
|
|
Type2["CacheBlobUrl"] = "CACHE_BLOB_URL";
|
|
Type2["RevokeBlobUrls"] = "REVOKE_BLOB_URLS";
|
|
Type2["UpdateProgress"] = "UPDATE_PROGRESS";
|
|
Type2["AccumulateSubSize"] = "ACCUMULATE_SUB_SIZE";
|
|
Type2["UpdateSettings"] = "UPDATE_SETTINGS";
|
|
return Type2;
|
|
})(Type || {});
|
|
var ItemStatus = /* @__PURE__ */ ((ItemStatus2) => {
|
|
ItemStatus2["Queued"] = "QUEUED";
|
|
ItemStatus2["Processing"] = "PROCESSING";
|
|
ItemStatus2["Paused"] = "PAUSED";
|
|
ItemStatus2["PendingRetry"] = "PENDING_RETRY";
|
|
ItemStatus2["Uploaded"] = "UPLOADED";
|
|
ItemStatus2["Error"] = "ERROR";
|
|
return ItemStatus2;
|
|
})(ItemStatus || {});
|
|
var OperationType = /* @__PURE__ */ ((OperationType2) => {
|
|
OperationType2["Prepare"] = "PREPARE";
|
|
OperationType2["Upload"] = "UPLOAD";
|
|
OperationType2["ResizeCrop"] = "RESIZE_CROP";
|
|
OperationType2["Rotate"] = "ROTATE";
|
|
OperationType2["TranscodeImage"] = "TRANSCODE_IMAGE";
|
|
OperationType2["TranscodeGif"] = "TRANSCODE_GIF";
|
|
OperationType2["ThumbnailGeneration"] = "THUMBNAIL_GENERATION";
|
|
OperationType2["Finalize"] = "FINALIZE";
|
|
OperationType2["DetectUltraHdr"] = "DETECT_ULTRAHDR";
|
|
return OperationType2;
|
|
})(OperationType || {});
|
|
|
|
// packages/upload-media/build-module/store/constants.mjs
|
|
var STORE_NAME = "core/upload-media";
|
|
var DEFAULT_MAX_CONCURRENT_UPLOADS = 5;
|
|
var DEFAULT_MAX_CONCURRENT_IMAGE_PROCESSING = 2;
|
|
var DEFAULT_RETRY_SETTINGS = {
|
|
maxRetryAttempts: 3,
|
|
initialRetryDelayMs: 1e3,
|
|
maxRetryDelayMs: 3e4,
|
|
backoffMultiplier: 2,
|
|
retryJitter: 0.1
|
|
};
|
|
var CLIENT_SIDE_SUPPORTED_MIME_TYPES = [
|
|
"image/jpeg",
|
|
"image/png",
|
|
"image/gif",
|
|
"image/webp",
|
|
"image/avif"
|
|
];
|
|
var HEIC_MIME_TYPES = [
|
|
"image/heic",
|
|
"image/heif"
|
|
];
|
|
|
|
// packages/upload-media/build-module/store/reducer.mjs
|
|
var noop = () => {
|
|
};
|
|
var DEFAULT_STATE = {
|
|
queue: [],
|
|
queueStatus: "active",
|
|
blobUrls: {},
|
|
settings: {
|
|
mediaUpload: noop,
|
|
maxConcurrentUploads: DEFAULT_MAX_CONCURRENT_UPLOADS,
|
|
maxConcurrentImageProcessing: DEFAULT_MAX_CONCURRENT_IMAGE_PROCESSING,
|
|
retry: { ...DEFAULT_RETRY_SETTINGS }
|
|
}
|
|
};
|
|
function reducer(state = DEFAULT_STATE, action = { type: Type.Unknown }) {
|
|
switch (action.type) {
|
|
case Type.PauseQueue: {
|
|
return {
|
|
...state,
|
|
queueStatus: "paused"
|
|
};
|
|
}
|
|
case Type.ResumeQueue: {
|
|
return {
|
|
...state,
|
|
queueStatus: "active"
|
|
};
|
|
}
|
|
case Type.PauseItem:
|
|
return {
|
|
...state,
|
|
queue: state.queue.map(
|
|
(item) => item.id === action.id ? {
|
|
...item,
|
|
status: ItemStatus.Paused
|
|
} : item
|
|
)
|
|
};
|
|
case Type.ResumeItem:
|
|
return {
|
|
...state,
|
|
queue: state.queue.map(
|
|
(item) => item.id === action.id ? {
|
|
...item,
|
|
status: ItemStatus.Processing
|
|
} : item
|
|
)
|
|
};
|
|
case Type.Add:
|
|
return {
|
|
...state,
|
|
queue: [...state.queue, action.item]
|
|
};
|
|
case Type.Cancel:
|
|
return {
|
|
...state,
|
|
queue: state.queue.map(
|
|
(item) => item.id === action.id ? {
|
|
...item,
|
|
error: action.error
|
|
} : item
|
|
)
|
|
};
|
|
case Type.RetryItem:
|
|
return {
|
|
...state,
|
|
queue: state.queue.map(
|
|
(item) => item.id === action.id ? {
|
|
...item,
|
|
status: ItemStatus.Processing,
|
|
error: void 0,
|
|
retryCount: (item.retryCount ?? 0) + 1,
|
|
abortController: new AbortController()
|
|
} : item
|
|
)
|
|
};
|
|
case Type.ScheduleRetry:
|
|
return {
|
|
...state,
|
|
queue: state.queue.map(
|
|
(item) => item.id === action.id ? {
|
|
...item,
|
|
status: ItemStatus.PendingRetry,
|
|
error: action.error,
|
|
retryCount: action.retryCount,
|
|
nextRetryTimestamp: action.nextRetryTimestamp
|
|
} : item
|
|
)
|
|
};
|
|
case Type.Remove:
|
|
return {
|
|
...state,
|
|
queue: state.queue.filter((item) => item.id !== action.id)
|
|
};
|
|
case Type.OperationStart: {
|
|
return {
|
|
...state,
|
|
queue: state.queue.map(
|
|
(item) => item.id === action.id ? {
|
|
...item,
|
|
currentOperation: action.operation
|
|
} : item
|
|
)
|
|
};
|
|
}
|
|
case Type.AddOperations:
|
|
return {
|
|
...state,
|
|
queue: state.queue.map((item) => {
|
|
if (item.id !== action.id) {
|
|
return item;
|
|
}
|
|
return {
|
|
...item,
|
|
operations: [
|
|
...item.operations || [],
|
|
...action.operations
|
|
]
|
|
};
|
|
})
|
|
};
|
|
case Type.OperationFinish:
|
|
return {
|
|
...state,
|
|
queue: state.queue.map((item) => {
|
|
if (item.id !== action.id) {
|
|
return item;
|
|
}
|
|
const operations = item.operations ? item.operations.slice(1) : [];
|
|
const attachment = item.attachment || action.item.attachment ? {
|
|
...item.attachment,
|
|
...action.item.attachment
|
|
} : void 0;
|
|
return {
|
|
...item,
|
|
currentOperation: void 0,
|
|
operations,
|
|
...action.item,
|
|
attachment,
|
|
additionalData: {
|
|
...item.additionalData,
|
|
...action.item.additionalData
|
|
}
|
|
};
|
|
})
|
|
};
|
|
case Type.CacheBlobUrl: {
|
|
const blobUrls = state.blobUrls[action.id] || [];
|
|
return {
|
|
...state,
|
|
blobUrls: {
|
|
...state.blobUrls,
|
|
[action.id]: [...blobUrls, action.blobUrl]
|
|
}
|
|
};
|
|
}
|
|
case Type.RevokeBlobUrls: {
|
|
const newBlobUrls = { ...state.blobUrls };
|
|
delete newBlobUrls[action.id];
|
|
return {
|
|
...state,
|
|
blobUrls: newBlobUrls
|
|
};
|
|
}
|
|
case Type.UpdateProgress:
|
|
return {
|
|
...state,
|
|
queue: state.queue.map(
|
|
(item) => item.id === action.id ? {
|
|
...item,
|
|
progress: action.progress
|
|
} : item
|
|
)
|
|
};
|
|
case Type.AccumulateSubSize:
|
|
return {
|
|
...state,
|
|
queue: state.queue.map(
|
|
(item) => item.id === action.id ? {
|
|
...item,
|
|
subSizes: [
|
|
...item.subSizes || [],
|
|
action.subSize
|
|
]
|
|
} : item
|
|
)
|
|
};
|
|
case Type.UpdateSettings: {
|
|
return {
|
|
...state,
|
|
settings: {
|
|
...state.settings,
|
|
...action.settings
|
|
}
|
|
};
|
|
}
|
|
}
|
|
return state;
|
|
}
|
|
var reducer_default = reducer;
|
|
|
|
// packages/upload-media/build-module/store/selectors.mjs
|
|
var selectors_exports = {};
|
|
__export(selectors_exports, {
|
|
getItems: () => getItems,
|
|
getSettings: () => getSettings,
|
|
isUploading: () => isUploading,
|
|
isUploadingById: () => isUploadingById,
|
|
isUploadingByUrl: () => isUploadingByUrl
|
|
});
|
|
function getItems(state) {
|
|
return state.queue;
|
|
}
|
|
function isUploading(state) {
|
|
return state.queue.length >= 1;
|
|
}
|
|
function isUploadingByUrl(state, url) {
|
|
return state.queue.some(
|
|
(item) => item.attachment?.url === url || item.sourceUrl === url
|
|
);
|
|
}
|
|
function isUploadingById(state, attachmentId) {
|
|
return state.queue.some(
|
|
(item) => item.attachment?.id === attachmentId || item.sourceAttachmentId === attachmentId
|
|
);
|
|
}
|
|
function getSettings(state) {
|
|
return state.settings;
|
|
}
|
|
|
|
// packages/upload-media/build-module/store/private-selectors.mjs
|
|
var private_selectors_exports = {};
|
|
__export(private_selectors_exports, {
|
|
getActiveImageProcessingCount: () => getActiveImageProcessingCount,
|
|
getActiveUploadCount: () => getActiveUploadCount,
|
|
getActiveVideoProcessingCount: () => getActiveVideoProcessingCount,
|
|
getAllItems: () => getAllItems,
|
|
getBlobUrls: () => getBlobUrls,
|
|
getFailedItems: () => getFailedItems,
|
|
getItem: () => getItem,
|
|
getItemProgress: () => getItemProgress,
|
|
getPendingImageProcessing: () => getPendingImageProcessing,
|
|
getPendingUploads: () => getPendingUploads,
|
|
getPendingVideoProcessing: () => getPendingVideoProcessing,
|
|
hasPendingItemsByParentId: () => hasPendingItemsByParentId,
|
|
isBatchUploaded: () => isBatchUploaded,
|
|
isPaused: () => isPaused
|
|
});
|
|
function getAllItems(state) {
|
|
return state.queue;
|
|
}
|
|
function getItem(state, id) {
|
|
return state.queue.find((item) => item.id === id);
|
|
}
|
|
function isBatchUploaded(state, batchId) {
|
|
const batchItems = state.queue.filter(
|
|
(item) => batchId === item.batchId
|
|
);
|
|
return batchItems.length === 0;
|
|
}
|
|
function isPaused(state) {
|
|
return state.queueStatus === "paused";
|
|
}
|
|
function getBlobUrls(state, id) {
|
|
return state.blobUrls[id] || [];
|
|
}
|
|
function getActiveUploadCount(state) {
|
|
return state.queue.filter(
|
|
(item) => item.currentOperation === OperationType.Upload
|
|
).length;
|
|
}
|
|
function getPendingUploads(state) {
|
|
return state.queue.filter((item) => {
|
|
const nextOperation = Array.isArray(item.operations?.[0]) ? item.operations[0][0] : item.operations?.[0];
|
|
return nextOperation === OperationType.Upload && item.currentOperation !== OperationType.Upload;
|
|
});
|
|
}
|
|
function getActiveImageProcessingCount(state) {
|
|
return state.queue.filter(
|
|
(item) => item.currentOperation === OperationType.ResizeCrop || item.currentOperation === OperationType.Rotate
|
|
).length;
|
|
}
|
|
function getActiveVideoProcessingCount(state) {
|
|
return state.queue.filter(
|
|
(item) => item.currentOperation === OperationType.TranscodeGif
|
|
).length;
|
|
}
|
|
function getPendingImageProcessing(state) {
|
|
return state.queue.filter((item) => {
|
|
const nextOperation = Array.isArray(item.operations?.[0]) ? item.operations[0][0] : item.operations?.[0];
|
|
return (nextOperation === OperationType.ResizeCrop || nextOperation === OperationType.Rotate) && item.currentOperation !== OperationType.ResizeCrop && item.currentOperation !== OperationType.Rotate;
|
|
});
|
|
}
|
|
function getPendingVideoProcessing(state) {
|
|
return state.queue.filter((item) => {
|
|
const nextOperation = Array.isArray(item.operations?.[0]) ? item.operations[0][0] : item.operations?.[0];
|
|
return nextOperation === OperationType.TranscodeGif && item.currentOperation !== OperationType.TranscodeGif;
|
|
});
|
|
}
|
|
function getFailedItems(state) {
|
|
return state.queue.filter((item) => item.error !== void 0);
|
|
}
|
|
function hasPendingItemsByParentId(state, parentId) {
|
|
return state.queue.some((item) => item.parentId === parentId);
|
|
}
|
|
function getItemProgress(state, id) {
|
|
const item = state.queue.find((i) => i.id === id);
|
|
return item?.progress;
|
|
}
|
|
|
|
// packages/upload-media/build-module/store/actions.mjs
|
|
var actions_exports = {};
|
|
__export(actions_exports, {
|
|
addItems: () => addItems,
|
|
cancelItem: () => cancelItem,
|
|
executeRetry: () => executeRetry,
|
|
retryItem: () => retryItem,
|
|
scheduleRetry: () => scheduleRetry
|
|
});
|
|
|
|
// node_modules/uuid/dist/stringify.js
|
|
var byteToHex = [];
|
|
for (let i = 0; i < 256; ++i) {
|
|
byteToHex.push((i + 256).toString(16).slice(1));
|
|
}
|
|
function unsafeStringify(arr, offset = 0) {
|
|
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();
|
|
}
|
|
|
|
// node_modules/uuid/dist/rng.js
|
|
var rnds8 = new Uint8Array(16);
|
|
function rng() {
|
|
return crypto.getRandomValues(rnds8);
|
|
}
|
|
|
|
// node_modules/uuid/dist/v4.js
|
|
function v4(options, buf, offset) {
|
|
if (!buf && !options && crypto.randomUUID) {
|
|
return crypto.randomUUID();
|
|
}
|
|
return _v4(options, buf, offset);
|
|
}
|
|
function _v4(options, buf, offset) {
|
|
options = options || {};
|
|
const rnds = options.random ?? options.rng?.() ?? rng();
|
|
if (rnds.length < 16) {
|
|
throw new Error("Random bytes length must be >= 16");
|
|
}
|
|
rnds[6] = rnds[6] & 15 | 64;
|
|
rnds[8] = rnds[8] & 63 | 128;
|
|
if (buf) {
|
|
offset = offset || 0;
|
|
if (offset < 0 || offset + 16 > buf.length) {
|
|
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
|
|
}
|
|
for (let i = 0; i < 16; ++i) {
|
|
buf[offset + i] = rnds[i];
|
|
}
|
|
return buf;
|
|
}
|
|
return unsafeStringify(rnds);
|
|
}
|
|
var v4_default = v4;
|
|
|
|
// packages/upload-media/build-module/store/actions.mjs
|
|
var import_i18n5 = __toESM(require_i18n(), 1);
|
|
|
|
// packages/upload-media/build-module/store/utils/retry.mjs
|
|
function calculateRetryDelay(options) {
|
|
const { attempt, initialDelay, maxDelay, multiplier, jitter } = options;
|
|
const exponentialDelay = initialDelay * Math.pow(multiplier, attempt - 1);
|
|
const cappedDelay = Math.min(exponentialDelay, maxDelay);
|
|
const jitterFactor = 1 + (Math.random() * 2 - 1) * jitter;
|
|
return Math.floor(cappedDelay * jitterFactor);
|
|
}
|
|
var RETRYABLE_MESSAGE_PATTERNS = [
|
|
/network/i,
|
|
/timeout/i,
|
|
/ECONNRESET/i,
|
|
/fetch failed/i,
|
|
/connection/i,
|
|
/socket/i,
|
|
/ETIMEDOUT/i,
|
|
/ENOTFOUND/i,
|
|
/Could not get a valid response/i,
|
|
/Failed to fetch/i,
|
|
/Load failed/i
|
|
];
|
|
function shouldRetryError(error, retryCount, maxRetries) {
|
|
if (retryCount >= maxRetries) {
|
|
return false;
|
|
}
|
|
const message = typeof error === "string" ? error : error?.message || "";
|
|
return RETRYABLE_MESSAGE_PATTERNS.some(
|
|
(pattern) => pattern.test(message)
|
|
);
|
|
}
|
|
var retryTimers = /* @__PURE__ */ new Map();
|
|
function clearRetryTimer(id) {
|
|
const pendingTimer = retryTimers.get(id);
|
|
if (pendingTimer !== void 0) {
|
|
clearTimeout(pendingTimer);
|
|
retryTimers.delete(id);
|
|
}
|
|
}
|
|
|
|
// packages/upload-media/build-module/image-file.mjs
|
|
var ImageFile = class extends File {
|
|
width = 0;
|
|
height = 0;
|
|
originalWidth = 0;
|
|
originalHeight = 0;
|
|
get wasResized() {
|
|
return (this.originalWidth || 0) > this.width || (this.originalHeight || 0) > this.height;
|
|
}
|
|
constructor(file, width, height, originalWidth, originalHeight) {
|
|
super([file], file.name, {
|
|
type: file.type,
|
|
lastModified: file.lastModified
|
|
});
|
|
this.width = width;
|
|
this.height = height;
|
|
this.originalWidth = originalWidth;
|
|
this.originalHeight = originalHeight;
|
|
}
|
|
};
|
|
|
|
// packages/upload-media/build-module/utils.mjs
|
|
var import_url = __toESM(require_url(), 1);
|
|
var import_i18n = __toESM(require_i18n(), 1);
|
|
function convertBlobToFile(fileOrBlob) {
|
|
if (fileOrBlob instanceof File) {
|
|
return fileOrBlob;
|
|
}
|
|
if ("name" in fileOrBlob && typeof fileOrBlob.name === "string") {
|
|
return new File([fileOrBlob], fileOrBlob.name, {
|
|
type: fileOrBlob.type,
|
|
lastModified: fileOrBlob.lastModified
|
|
});
|
|
}
|
|
const ext = fileOrBlob.type.split("/")[1];
|
|
const mediaType = "application/pdf" === fileOrBlob.type ? "document" : fileOrBlob.type.split("/")[0];
|
|
return new File([fileOrBlob], `${mediaType}.${ext}`, {
|
|
type: fileOrBlob.type
|
|
});
|
|
}
|
|
function renameFile(file, name) {
|
|
return new File([file], name, {
|
|
type: file.type,
|
|
lastModified: file.lastModified
|
|
});
|
|
}
|
|
function cloneFile(file) {
|
|
return renameFile(file, file.name);
|
|
}
|
|
function getFileBasename(name) {
|
|
return name.includes(".") ? name.split(".").slice(0, -1).join(".") : name;
|
|
}
|
|
function isAnimatedGif(buffer) {
|
|
const view = new Uint8Array(buffer);
|
|
if (view.length < 4 || view[0] !== 71 || view[1] !== 73 || view[2] !== 70 || view[3] !== 56) {
|
|
return false;
|
|
}
|
|
let frameCount = 0;
|
|
for (let i = 0; i < view.length - 2; i++) {
|
|
if (view[i] === 0 && view[i + 1] === 33 && view[i + 2] === 249) {
|
|
frameCount++;
|
|
if (frameCount > 1) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// packages/upload-media/build-module/store/utils/index.mjs
|
|
var vipsModulePromise;
|
|
var vipsModule;
|
|
function loadVipsModule() {
|
|
if (!vipsModulePromise) {
|
|
vipsModulePromise = import("@wordpress/vips/worker").then(
|
|
(mod) => {
|
|
vipsModule = mod;
|
|
return mod;
|
|
}
|
|
);
|
|
}
|
|
return vipsModulePromise;
|
|
}
|
|
async function vipsConvertImageFormat(id, file, type, options = {}) {
|
|
const { vipsConvertImageFormat: convertImageFormat } = await loadVipsModule();
|
|
const buffer = await convertImageFormat(
|
|
id,
|
|
await file.arrayBuffer(),
|
|
file.type,
|
|
type,
|
|
options
|
|
);
|
|
const ext = type.split("/")[1];
|
|
const fileName = `${getFileBasename(file.name)}.${ext}`;
|
|
return new File([new Blob([buffer])], fileName, {
|
|
type
|
|
});
|
|
}
|
|
async function vipsHasTransparency(url) {
|
|
const { vipsHasTransparency: hasTransparency } = await loadVipsModule();
|
|
const response = await fetch(url);
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to fetch image: ${response.status}`);
|
|
}
|
|
return hasTransparency(await response.arrayBuffer());
|
|
}
|
|
async function vipsGetUltraHdrInfo(buffer) {
|
|
const { vipsGetUltraHdrInfo: getUltraHdrInfo } = await loadVipsModule();
|
|
return getUltraHdrInfo(buffer);
|
|
}
|
|
async function vipsResizeImage(id, file, resize, options = {}) {
|
|
const {
|
|
smartCrop = false,
|
|
addSuffix = false,
|
|
signal,
|
|
scaledSuffix,
|
|
quality,
|
|
stripMeta,
|
|
maxBitdepth
|
|
} = options;
|
|
if (signal?.aborted) {
|
|
throw new Error("Operation aborted");
|
|
}
|
|
const { vipsResizeImage: resizeImage } = await loadVipsModule();
|
|
const { buffer, width, height, originalWidth, originalHeight } = await resizeImage(id, await file.arrayBuffer(), file.type, resize, {
|
|
smartCrop,
|
|
quality,
|
|
stripMeta,
|
|
maxBitdepth
|
|
});
|
|
let fileName = file.name;
|
|
const wasResized = originalWidth > width || originalHeight > height;
|
|
if (wasResized) {
|
|
const basename = getFileBasename(file.name);
|
|
if (scaledSuffix) {
|
|
fileName = file.name.replace(basename, `${basename}-scaled`);
|
|
} else if (addSuffix) {
|
|
fileName = file.name.replace(
|
|
basename,
|
|
`${basename}-${width}x${height}`
|
|
);
|
|
}
|
|
}
|
|
const resultFile = new ImageFile(
|
|
new File(
|
|
[new Blob([buffer], { type: file.type })],
|
|
fileName,
|
|
{
|
|
type: file.type
|
|
}
|
|
),
|
|
width,
|
|
height,
|
|
originalWidth,
|
|
originalHeight
|
|
);
|
|
return resultFile;
|
|
}
|
|
async function vipsRotateImage(id, file, orientation, signal) {
|
|
if (signal?.aborted) {
|
|
throw new Error("Operation aborted");
|
|
}
|
|
if (orientation === 1) {
|
|
return file;
|
|
}
|
|
const { vipsRotateImage: rotateImage } = await loadVipsModule();
|
|
const { buffer, width, height } = await rotateImage(
|
|
id,
|
|
await file.arrayBuffer(),
|
|
file.type,
|
|
orientation
|
|
);
|
|
const basename = getFileBasename(file.name);
|
|
const fileName = file.name.replace(basename, `${basename}-rotated`);
|
|
const resultFile = new ImageFile(
|
|
new File(
|
|
[new Blob([buffer], { type: file.type })],
|
|
fileName,
|
|
{
|
|
type: file.type
|
|
}
|
|
),
|
|
width,
|
|
height
|
|
);
|
|
return resultFile;
|
|
}
|
|
async function vipsCancelOperations(id) {
|
|
if (!vipsModule) {
|
|
return false;
|
|
}
|
|
return vipsModule.vipsCancelOperations(id);
|
|
}
|
|
function terminateVipsWorker() {
|
|
if (vipsModule) {
|
|
vipsModule.terminateVipsWorker();
|
|
}
|
|
}
|
|
var completedVipsOperations = 0;
|
|
var MAX_VIPS_OPS_BEFORE_RECYCLE = 50;
|
|
function maybeRecycleVipsWorker(activeImageProcessingCount) {
|
|
completedVipsOperations++;
|
|
if (completedVipsOperations >= MAX_VIPS_OPS_BEFORE_RECYCLE && activeImageProcessingCount === 0) {
|
|
terminateVipsWorker();
|
|
completedVipsOperations = 0;
|
|
}
|
|
}
|
|
|
|
// packages/upload-media/build-module/store/utils/video-conversion.mjs
|
|
var UNSUPPORTED_ERROR_PREFIX = "Unsupported";
|
|
var SIZE_LIMIT_ERROR_PREFIX = `${UNSUPPORTED_ERROR_PREFIX}: GIF exceeds maximum conversion size`;
|
|
var CONVERSION_TIMEOUT_ERROR_PREFIX = "GIF to video conversion timed out";
|
|
var DEFAULT_CONVERSION_TIMEOUT = 3e4;
|
|
function isUnsupportedConversionError(error) {
|
|
return error instanceof Error && error.message.startsWith(UNSUPPORTED_ERROR_PREFIX);
|
|
}
|
|
function isSizeLimitConversionError(error) {
|
|
return error instanceof Error && error.message.startsWith(SIZE_LIMIT_ERROR_PREFIX);
|
|
}
|
|
function isConversionTimeoutError(error) {
|
|
return error instanceof Error && error.message.startsWith(CONVERSION_TIMEOUT_ERROR_PREFIX);
|
|
}
|
|
var videoConversionModulePromise;
|
|
var videoConversionModule;
|
|
function loadVideoConversionModule() {
|
|
if (!videoConversionModulePromise) {
|
|
videoConversionModulePromise = import("@wordpress/video-conversion/worker").then((mod) => {
|
|
videoConversionModule = mod;
|
|
return mod;
|
|
}).catch((error) => {
|
|
videoConversionModulePromise = void 0;
|
|
throw error;
|
|
});
|
|
}
|
|
return videoConversionModulePromise;
|
|
}
|
|
async function convertGifToVideo(id, file, outputMimeType, {
|
|
maxDimensions,
|
|
timeout = DEFAULT_CONVERSION_TIMEOUT,
|
|
maxTotalPixels
|
|
} = {}) {
|
|
const mod = await loadVideoConversionModule();
|
|
const conversion = mod.convertGifToVideo(
|
|
id,
|
|
file,
|
|
outputMimeType,
|
|
maxDimensions,
|
|
maxTotalPixels
|
|
);
|
|
let buffer;
|
|
if (timeout > 0) {
|
|
let timer;
|
|
try {
|
|
buffer = await Promise.race([
|
|
conversion,
|
|
new Promise((_, reject) => {
|
|
timer = setTimeout(() => {
|
|
reject(
|
|
new Error(
|
|
`${CONVERSION_TIMEOUT_ERROR_PREFIX} after ${timeout}ms`
|
|
)
|
|
);
|
|
}, timeout);
|
|
})
|
|
]);
|
|
} catch (error) {
|
|
if (isConversionTimeoutError(error)) {
|
|
conversion.catch(() => {
|
|
});
|
|
mod.cancelGifToVideoOperations(id).catch(() => {
|
|
});
|
|
}
|
|
throw error;
|
|
} finally {
|
|
clearTimeout(timer);
|
|
}
|
|
} else {
|
|
buffer = await conversion;
|
|
}
|
|
const ext = outputMimeType === "video/webm" ? "webm" : "mp4";
|
|
const fileName = `${getFileBasename(file.name)}.${ext}`;
|
|
return new File(
|
|
[new Blob([buffer], { type: outputMimeType })],
|
|
fileName,
|
|
{ type: outputMimeType }
|
|
);
|
|
}
|
|
async function cancelGifToVideoOperations(id) {
|
|
const mod = videoConversionModule ?? (videoConversionModulePromise ? await videoConversionModulePromise.catch(() => void 0) : void 0);
|
|
if (!mod) {
|
|
return false;
|
|
}
|
|
return mod.cancelGifToVideoOperations(id);
|
|
}
|
|
function terminateVideoConversionWorker() {
|
|
if (videoConversionModule) {
|
|
videoConversionModule.terminateVideoConversionWorker();
|
|
return;
|
|
}
|
|
if (videoConversionModulePromise) {
|
|
void videoConversionModulePromise.then((mod) => mod.terminateVideoConversionWorker()).catch(() => {
|
|
});
|
|
}
|
|
}
|
|
|
|
// packages/upload-media/build-module/store/utils/debug-logger.mjs
|
|
function isDebugEnabled() {
|
|
return true;
|
|
}
|
|
function debug(message, ...args) {
|
|
if (!isDebugEnabled()) {
|
|
return;
|
|
}
|
|
console.debug(`[upload-media] ${message}`, ...args);
|
|
}
|
|
function measure(options) {
|
|
if (!isDebugEnabled()) {
|
|
return;
|
|
}
|
|
const {
|
|
measureName,
|
|
startTime,
|
|
endTime = performance.now(),
|
|
tooltipText,
|
|
properties
|
|
} = options;
|
|
const detail = {
|
|
devtools: {
|
|
dataType: "track-entry",
|
|
track: "Upload Media",
|
|
tooltipText,
|
|
properties: properties?.map(([key, value]) => ({
|
|
key,
|
|
value
|
|
}))
|
|
}
|
|
};
|
|
try {
|
|
performance.measure(measureName, {
|
|
start: startTime,
|
|
end: endTime,
|
|
detail
|
|
});
|
|
} catch {
|
|
}
|
|
}
|
|
|
|
// packages/upload-media/build-module/upload-error.mjs
|
|
var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
|
|
ErrorCode2["EMPTY_FILE"] = "EMPTY_FILE";
|
|
ErrorCode2["SIZE_ABOVE_LIMIT"] = "SIZE_ABOVE_LIMIT";
|
|
ErrorCode2["MIME_TYPE_NOT_SUPPORTED"] = "MIME_TYPE_NOT_SUPPORTED";
|
|
ErrorCode2["MIME_TYPE_NOT_ALLOWED_FOR_USER"] = "MIME_TYPE_NOT_ALLOWED_FOR_USER";
|
|
ErrorCode2["HEIC_DECODE_ERROR"] = "HEIC_DECODE_ERROR";
|
|
ErrorCode2["IMAGE_TRANSCODING_ERROR"] = "IMAGE_TRANSCODING_ERROR";
|
|
ErrorCode2["IMAGE_ROTATION_ERROR"] = "IMAGE_ROTATION_ERROR";
|
|
ErrorCode2["MEDIA_TRANSCODING_ERROR"] = "MEDIA_TRANSCODING_ERROR";
|
|
ErrorCode2["GIF_TRANSCODING_ERROR"] = "GIF_TRANSCODING_ERROR";
|
|
ErrorCode2["MEDIA_FINALIZE_ERROR"] = "MEDIA_FINALIZE_ERROR";
|
|
ErrorCode2["GENERAL"] = "GENERAL";
|
|
return ErrorCode2;
|
|
})(ErrorCode || {});
|
|
var UploadError = class extends Error {
|
|
code;
|
|
file;
|
|
constructor({ code, message, file, cause }) {
|
|
super(message, { cause });
|
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
this.code = code;
|
|
this.file = file;
|
|
}
|
|
};
|
|
|
|
// packages/upload-media/build-module/validate-mime-type.mjs
|
|
var import_i18n2 = __toESM(require_i18n(), 1);
|
|
function validateMimeType(file, allowedTypes) {
|
|
if (!allowedTypes) {
|
|
return;
|
|
}
|
|
const isAllowedType = allowedTypes.some((allowedType) => {
|
|
if (allowedType.includes("/")) {
|
|
return allowedType === file.type;
|
|
}
|
|
return file.type.startsWith(`${allowedType}/`);
|
|
});
|
|
if (file.type && !isAllowedType) {
|
|
throw new UploadError({
|
|
code: ErrorCode.MIME_TYPE_NOT_SUPPORTED,
|
|
message: (0, import_i18n2.sprintf)(
|
|
// translators: %s: file name.
|
|
(0, import_i18n2.__)("%s: Sorry, this file type is not supported here."),
|
|
file.name
|
|
),
|
|
file
|
|
});
|
|
}
|
|
}
|
|
|
|
// packages/upload-media/build-module/validate-mime-type-for-user.mjs
|
|
var import_i18n3 = __toESM(require_i18n(), 1);
|
|
|
|
// packages/upload-media/build-module/get-mime-types-array.mjs
|
|
function getMimeTypesArray(wpMimeTypesObject) {
|
|
if (!wpMimeTypesObject) {
|
|
return null;
|
|
}
|
|
return Object.entries(wpMimeTypesObject).flatMap(
|
|
([extensionsString, mime]) => {
|
|
const [type] = mime.split("/");
|
|
const extensions = extensionsString.split("|");
|
|
return [
|
|
mime,
|
|
...extensions.map(
|
|
(extension) => `${type}/${extension}`
|
|
)
|
|
];
|
|
}
|
|
);
|
|
}
|
|
|
|
// packages/upload-media/build-module/validate-mime-type-for-user.mjs
|
|
function validateMimeTypeForUser(file, wpAllowedMimeTypes) {
|
|
const allowedMimeTypesForUser = getMimeTypesArray(wpAllowedMimeTypes);
|
|
if (!allowedMimeTypesForUser) {
|
|
return;
|
|
}
|
|
const isAllowedMimeTypeForUser = allowedMimeTypesForUser.includes(
|
|
file.type
|
|
);
|
|
if (file.type && !isAllowedMimeTypeForUser) {
|
|
throw new UploadError({
|
|
code: ErrorCode.MIME_TYPE_NOT_ALLOWED_FOR_USER,
|
|
message: (0, import_i18n3.sprintf)(
|
|
// translators: %s: file name.
|
|
(0, import_i18n3.__)(
|
|
"%s: Sorry, you are not allowed to upload this file type."
|
|
),
|
|
file.name
|
|
),
|
|
file
|
|
});
|
|
}
|
|
}
|
|
|
|
// packages/upload-media/build-module/validate-file-size.mjs
|
|
var import_i18n4 = __toESM(require_i18n(), 1);
|
|
function validateFileSize(file, maxUploadFileSize) {
|
|
if (file.size <= 0) {
|
|
throw new UploadError({
|
|
code: ErrorCode.EMPTY_FILE,
|
|
message: (0, import_i18n4.sprintf)(
|
|
// translators: %s: file name.
|
|
(0, import_i18n4.__)("%s: This file is empty."),
|
|
file.name
|
|
),
|
|
file
|
|
});
|
|
}
|
|
if (maxUploadFileSize && file.size > maxUploadFileSize) {
|
|
throw new UploadError({
|
|
code: ErrorCode.SIZE_ABOVE_LIMIT,
|
|
message: (0, import_i18n4.sprintf)(
|
|
// translators: %s: file name.
|
|
(0, import_i18n4.__)(
|
|
"%s: This file exceeds the maximum upload size for this site."
|
|
),
|
|
file.name
|
|
),
|
|
file
|
|
});
|
|
}
|
|
}
|
|
|
|
// packages/upload-media/build-module/store/actions.mjs
|
|
function addItems({
|
|
files,
|
|
onChange,
|
|
onSuccess,
|
|
onError,
|
|
onBatchSuccess,
|
|
additionalData,
|
|
allowedTypes
|
|
}) {
|
|
return async ({ select: select2, dispatch }) => {
|
|
const batchId = v4_default();
|
|
for (const file of files) {
|
|
try {
|
|
validateMimeType(file, allowedTypes);
|
|
validateMimeTypeForUser(
|
|
file,
|
|
select2.getSettings().allowedMimeTypes
|
|
);
|
|
} catch (error) {
|
|
onError?.(error);
|
|
continue;
|
|
}
|
|
try {
|
|
validateFileSize(
|
|
file,
|
|
select2.getSettings().maxUploadFileSize
|
|
);
|
|
} catch (error) {
|
|
onError?.(error);
|
|
continue;
|
|
}
|
|
dispatch.addItem({
|
|
file,
|
|
batchId,
|
|
onChange,
|
|
onSuccess,
|
|
onBatchSuccess,
|
|
onError,
|
|
additionalData
|
|
});
|
|
}
|
|
};
|
|
}
|
|
function cancelItem(id, error, silent = false) {
|
|
return async ({ select: select2, dispatch }) => {
|
|
const item = select2.getItem(id);
|
|
if (!item) {
|
|
return;
|
|
}
|
|
clearRetryTimer(id);
|
|
if (!silent && error && !item.parentId && !item.attachment?.id) {
|
|
const settings = select2.getSettings();
|
|
const retrySettings = settings.retry;
|
|
if (retrySettings) {
|
|
const retryCount = item.retryCount ?? 0;
|
|
const maxRetries = retrySettings.maxRetryAttempts;
|
|
if (shouldRetryError(error, retryCount, maxRetries)) {
|
|
dispatch.scheduleRetry(id, error);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
item.abortController?.abort();
|
|
vipsCancelOperations(id).catch(() => {
|
|
});
|
|
cancelGifToVideoOperations(id).catch(() => {
|
|
});
|
|
if (!silent) {
|
|
const { onError } = item;
|
|
onError?.(error ?? new Error("Upload cancelled"));
|
|
if (!onError && error && !item.parentId) {
|
|
console.error("Upload cancelled", error);
|
|
}
|
|
} else {
|
|
debug(
|
|
`Item cancelled: ${item.file.name} (item ${id}): ${error instanceof Error ? error.message : error}`
|
|
);
|
|
}
|
|
const { currentOperation, parentId, batchId } = item;
|
|
dispatch({
|
|
type: Type.Cancel,
|
|
id,
|
|
error
|
|
});
|
|
dispatch.removeItem(id);
|
|
dispatch.revokeBlobUrls(id);
|
|
if (currentOperation === OperationType.ResizeCrop || currentOperation === OperationType.Rotate) {
|
|
for (const pending of select2.getPendingImageProcessing()) {
|
|
dispatch.processItem(pending.id);
|
|
}
|
|
}
|
|
if (currentOperation === OperationType.Upload) {
|
|
for (const pending of select2.getPendingUploads()) {
|
|
dispatch.processItem(pending.id);
|
|
}
|
|
}
|
|
if (currentOperation === OperationType.TranscodeGif) {
|
|
for (const pending of select2.getPendingVideoProcessing()) {
|
|
dispatch.processItem(pending.id);
|
|
}
|
|
}
|
|
if (currentOperation === OperationType.ResizeCrop || currentOperation === OperationType.Rotate || currentOperation === OperationType.TranscodeImage) {
|
|
maybeRecycleVipsWorker(select2.getActiveImageProcessingCount());
|
|
}
|
|
if (parentId) {
|
|
const parentItem = select2.getItem(parentId);
|
|
if (parentItem) {
|
|
const isOptionalCompanion = item.additionalData?.image_size === "animated_video" || item.additionalData?.image_size === "animated_video_poster";
|
|
if (select2.hasPendingItemsByParentId(parentId)) {
|
|
if (parentItem.operations && parentItem.operations.length > 0) {
|
|
dispatch.processItem(parentId);
|
|
}
|
|
} else if (parentItem.subSizes && parentItem.subSizes.length > 0 || isOptionalCompanion) {
|
|
if (parentItem.operations && parentItem.operations.length > 0) {
|
|
dispatch.processItem(parentId);
|
|
}
|
|
} else {
|
|
const parentAttachmentId = parentItem.attachment?.id;
|
|
const { mediaDelete } = select2.getSettings();
|
|
if (parentAttachmentId && mediaDelete) {
|
|
mediaDelete(parentAttachmentId).catch(() => {
|
|
});
|
|
}
|
|
await dispatch.cancelItem(
|
|
parentId,
|
|
new UploadError({
|
|
code: error instanceof UploadError && error.code || ErrorCode.GENERAL,
|
|
message: error?.message || (0, import_i18n5.__)("The image could not be uploaded."),
|
|
file: parentItem.file,
|
|
cause: error instanceof Error ? error : void 0
|
|
})
|
|
);
|
|
}
|
|
}
|
|
}
|
|
if (batchId && select2.isBatchUploaded(batchId)) {
|
|
debug(`Batch completed: ${batchId}`);
|
|
item.onBatchSuccess?.();
|
|
}
|
|
};
|
|
}
|
|
function retryItem(id) {
|
|
return async ({ select: select2, dispatch }) => {
|
|
const item = select2.getItem(id);
|
|
if (!item) {
|
|
return;
|
|
}
|
|
if (!item.error) {
|
|
return;
|
|
}
|
|
dispatch({
|
|
type: Type.RetryItem,
|
|
id
|
|
});
|
|
dispatch.processItem(id);
|
|
};
|
|
}
|
|
function scheduleRetry(id, error) {
|
|
return async ({ select: select2, dispatch }) => {
|
|
const item = select2.getItem(id);
|
|
if (!item) {
|
|
return;
|
|
}
|
|
const settings = select2.getSettings();
|
|
const retrySettings = settings.retry;
|
|
if (!retrySettings) {
|
|
return;
|
|
}
|
|
const currentRetryCount = item.retryCount ?? 0;
|
|
const delay = calculateRetryDelay({
|
|
attempt: currentRetryCount + 1,
|
|
initialDelay: retrySettings.initialRetryDelayMs,
|
|
maxDelay: retrySettings.maxRetryDelayMs,
|
|
multiplier: retrySettings.backoffMultiplier,
|
|
jitter: retrySettings.retryJitter
|
|
});
|
|
const timerId = setTimeout(() => {
|
|
retryTimers.delete(id);
|
|
dispatch.executeRetry(id);
|
|
}, delay);
|
|
retryTimers.set(id, timerId);
|
|
dispatch({
|
|
type: Type.ScheduleRetry,
|
|
id,
|
|
error,
|
|
retryCount: currentRetryCount,
|
|
nextRetryTimestamp: Date.now() + delay
|
|
});
|
|
};
|
|
}
|
|
function executeRetry(id) {
|
|
return async ({ select: select2, dispatch }) => {
|
|
const item = select2.getItem(id);
|
|
if (!item || item.status !== ItemStatus.PendingRetry) {
|
|
return;
|
|
}
|
|
if (select2.isPaused()) {
|
|
return;
|
|
}
|
|
dispatch({
|
|
type: Type.RetryItem,
|
|
id
|
|
});
|
|
dispatch.processItem(id);
|
|
};
|
|
}
|
|
|
|
// packages/upload-media/build-module/store/private-actions.mjs
|
|
var private_actions_exports = {};
|
|
__export(private_actions_exports, {
|
|
addItem: () => addItem,
|
|
addSideloadItem: () => addSideloadItem,
|
|
detectUltraHdr: () => detectUltraHdr,
|
|
finalizeItem: () => finalizeItem,
|
|
finishOperation: () => finishOperation,
|
|
generateThumbnails: () => generateThumbnails,
|
|
getTranscodeImageOperation: () => getTranscodeImageOperation,
|
|
pauseItem: () => pauseItem,
|
|
pauseQueue: () => pauseQueue,
|
|
prepareItem: () => prepareItem,
|
|
processItem: () => processItem,
|
|
removeItem: () => removeItem,
|
|
resizeCropItem: () => resizeCropItem,
|
|
resumeQueue: () => resumeQueue,
|
|
revokeBlobUrls: () => revokeBlobUrls,
|
|
rotateItem: () => rotateItem,
|
|
sideloadItem: () => sideloadItem,
|
|
transcodeGifItem: () => transcodeGifItem,
|
|
transcodeImageItem: () => transcodeImageItem,
|
|
updateItemProgress: () => updateItemProgress,
|
|
updateSettings: () => updateSettings,
|
|
uploadItem: () => uploadItem
|
|
});
|
|
var import_blob = __toESM(require_blob(), 1);
|
|
var import_i18n7 = __toESM(require_i18n(), 1);
|
|
|
|
// packages/upload-media/build-module/heic-parser.mjs
|
|
var Reader = class {
|
|
view;
|
|
buffer;
|
|
pos;
|
|
constructor(buffer, offset = 0) {
|
|
this.buffer = buffer;
|
|
this.view = new DataView(buffer);
|
|
this.pos = offset;
|
|
}
|
|
u8() {
|
|
const v = this.view.getUint8(this.pos);
|
|
this.pos += 1;
|
|
return v;
|
|
}
|
|
u16() {
|
|
const v = this.view.getUint16(this.pos);
|
|
this.pos += 2;
|
|
return v;
|
|
}
|
|
u32() {
|
|
const v = this.view.getUint32(this.pos);
|
|
this.pos += 4;
|
|
return v;
|
|
}
|
|
u64() {
|
|
const hi = this.view.getUint32(this.pos);
|
|
const lo = this.view.getUint32(this.pos + 4);
|
|
this.pos += 8;
|
|
return hi * 4294967296 + lo;
|
|
}
|
|
/**
|
|
* Read a variable-width unsigned integer (0, 4 or 8 bytes).
|
|
*
|
|
* @param size Byte width to read (0, 4, or 8).
|
|
*/
|
|
uN(size) {
|
|
if (size === 0) {
|
|
return 0;
|
|
}
|
|
if (size === 4) {
|
|
return this.u32();
|
|
}
|
|
if (size === 8) {
|
|
return this.u64();
|
|
}
|
|
throw new Error(`Unsupported uint size: ${size}`);
|
|
}
|
|
str(len) {
|
|
let s = "";
|
|
for (let i = 0; i < len; i++) {
|
|
s += String.fromCharCode(this.view.getUint8(this.pos + i));
|
|
}
|
|
this.pos += len;
|
|
return s;
|
|
}
|
|
bytes(len) {
|
|
const b = new Uint8Array(this.buffer, this.pos, len);
|
|
this.pos += len;
|
|
return new Uint8Array(b);
|
|
}
|
|
};
|
|
function readBox(r) {
|
|
if (r.pos + 8 > r.view.byteLength) {
|
|
return null;
|
|
}
|
|
const offset = r.pos;
|
|
let size = r.u32();
|
|
const type = r.str(4);
|
|
let headerSize = 8;
|
|
if (size === 1) {
|
|
size = r.u64();
|
|
headerSize = 16;
|
|
} else if (size === 0) {
|
|
size = r.view.byteLength - offset;
|
|
}
|
|
return { type, offset, size, headerSize };
|
|
}
|
|
function findBoxes(r, start, end) {
|
|
const boxes = [];
|
|
r.pos = start;
|
|
while (r.pos < end) {
|
|
const box = readBox(r);
|
|
if (!box || box.size < 8) {
|
|
break;
|
|
}
|
|
boxes.push(box);
|
|
r.pos = box.offset + box.size;
|
|
}
|
|
return boxes;
|
|
}
|
|
function findBox(r, start, end, type) {
|
|
r.pos = start;
|
|
while (r.pos < end) {
|
|
const box = readBox(r);
|
|
if (!box || box.size < 8) {
|
|
break;
|
|
}
|
|
if (box.type === type) {
|
|
return box;
|
|
}
|
|
r.pos = box.offset + box.size;
|
|
}
|
|
return void 0;
|
|
}
|
|
function parsePitm(r, box) {
|
|
r.pos = box.offset + box.headerSize;
|
|
const version = r.u8();
|
|
r.pos += 3;
|
|
return version === 0 ? r.u16() : r.u32();
|
|
}
|
|
function parseIloc(r, box) {
|
|
r.pos = box.offset + box.headerSize;
|
|
const version = r.u8();
|
|
r.pos += 3;
|
|
const byte1 = r.u8();
|
|
const offsetSize = byte1 >> 4 & 15;
|
|
const lengthSize = byte1 & 15;
|
|
const byte2 = r.u8();
|
|
const baseOffsetSize = byte2 >> 4 & 15;
|
|
const indexSize = version >= 1 ? byte2 & 15 : 0;
|
|
const itemCount = version < 2 ? r.u16() : r.u32();
|
|
const items = /* @__PURE__ */ new Map();
|
|
for (let i = 0; i < itemCount; i++) {
|
|
const itemId = version < 2 ? r.u16() : r.u32();
|
|
let constructionMethod = 0;
|
|
if (version === 1 || version === 2) {
|
|
const cm = r.u16();
|
|
constructionMethod = cm & 15;
|
|
}
|
|
r.u16();
|
|
const baseOffset = r.uN(baseOffsetSize);
|
|
const extentCount = r.u16();
|
|
const extents = [];
|
|
for (let j = 0; j < extentCount; j++) {
|
|
if (version >= 1) {
|
|
r.uN(indexSize);
|
|
}
|
|
const extOffset = r.uN(offsetSize);
|
|
const extLength = r.uN(lengthSize);
|
|
extents.push({
|
|
offset: baseOffset + extOffset,
|
|
length: extLength
|
|
});
|
|
}
|
|
items.set(itemId, { constructionMethod, extents });
|
|
}
|
|
return items;
|
|
}
|
|
function parseIpma(r, box) {
|
|
r.pos = box.offset + box.headerSize;
|
|
const vf = r.u32();
|
|
const version = vf >>> 24;
|
|
const flags = vf & 16777215;
|
|
const largeIndex = (flags & 1) !== 0;
|
|
const entryCount = r.u32();
|
|
const associations = /* @__PURE__ */ new Map();
|
|
for (let i = 0; i < entryCount; i++) {
|
|
const itemId = version < 1 ? r.u16() : r.u32();
|
|
const assocCount = r.u8();
|
|
const indices = [];
|
|
for (let j = 0; j < assocCount; j++) {
|
|
if (largeIndex) {
|
|
indices.push(r.u16() & 32767);
|
|
} else {
|
|
indices.push(r.u8() & 127);
|
|
}
|
|
}
|
|
associations.set(itemId, indices);
|
|
}
|
|
return associations;
|
|
}
|
|
function parseIspe(r, box) {
|
|
r.pos = box.offset + box.headerSize + 4;
|
|
return { width: r.u32(), height: r.u32() };
|
|
}
|
|
function parseIrot(r, box) {
|
|
r.pos = box.offset + box.headerSize;
|
|
return (r.u8() & 3) * 90;
|
|
}
|
|
function parseIinf(r, box) {
|
|
r.pos = box.offset + box.headerSize;
|
|
const version = r.u8();
|
|
r.pos += 3;
|
|
const entryCount = version === 0 ? r.u16() : r.u32();
|
|
const itemTypes = /* @__PURE__ */ new Map();
|
|
const entriesStart = r.pos;
|
|
const boxEnd = box.offset + box.size;
|
|
const infeBoxes = findBoxes(r, entriesStart, boxEnd);
|
|
for (let i = 0; i < Math.min(entryCount, infeBoxes.length); i++) {
|
|
const infe = infeBoxes[i];
|
|
if (infe.type !== "infe") {
|
|
continue;
|
|
}
|
|
r.pos = infe.offset + infe.headerSize;
|
|
const infeVersion = r.u8();
|
|
r.pos += 3;
|
|
if (infeVersion >= 2) {
|
|
const itemId = infeVersion === 2 ? r.u16() : r.u32();
|
|
r.u16();
|
|
const itemType = r.str(4);
|
|
itemTypes.set(itemId, itemType);
|
|
}
|
|
}
|
|
return itemTypes;
|
|
}
|
|
function parseIref(r, box, refType) {
|
|
r.pos = box.offset + box.headerSize;
|
|
const version = r.u8();
|
|
r.pos += 3;
|
|
const refs = /* @__PURE__ */ new Map();
|
|
const boxEnd = box.offset + box.size;
|
|
while (r.pos < boxEnd) {
|
|
const refBox = readBox(r);
|
|
if (!refBox || refBox.size < 8) {
|
|
break;
|
|
}
|
|
r.pos = refBox.offset + refBox.headerSize;
|
|
const fromId = version === 0 ? r.u16() : r.u32();
|
|
const refCount = r.u16();
|
|
const toIds = [];
|
|
for (let i = 0; i < refCount; i++) {
|
|
toIds.push(version === 0 ? r.u16() : r.u32());
|
|
}
|
|
if (refBox.type === refType) {
|
|
refs.set(fromId, toIds);
|
|
}
|
|
r.pos = refBox.offset + refBox.size;
|
|
}
|
|
return refs;
|
|
}
|
|
function reverseBits32(n) {
|
|
n = n >>> 1 & 1431655765 | (n & 1431655765) << 1;
|
|
n = n >>> 2 & 858993459 | (n & 858993459) << 2;
|
|
n = n >>> 4 & 252645135 | (n & 252645135) << 4;
|
|
n = n >>> 8 & 16711935 | (n & 16711935) << 8;
|
|
n = n >>> 16 | n << 16;
|
|
return n >>> 0;
|
|
}
|
|
function buildCodecString(r, recordOffset) {
|
|
r.pos = recordOffset;
|
|
r.u8();
|
|
const byte1 = r.u8();
|
|
const profileSpace = byte1 >> 6 & 3;
|
|
const tierFlag = byte1 >> 5 & 1;
|
|
const profileIdc = byte1 & 31;
|
|
const compatFlags = r.u32();
|
|
const constraintBytes = r.bytes(6);
|
|
const levelIdc = r.u8();
|
|
const spacePrefix = profileSpace > 0 ? String.fromCharCode(64 + profileSpace) : "";
|
|
const compatHex = reverseBits32(compatFlags).toString(16).toUpperCase();
|
|
const tierChar = tierFlag ? "H" : "L";
|
|
let lastNonZero = -1;
|
|
for (let i = 5; i >= 0; i--) {
|
|
if (constraintBytes[i] !== 0) {
|
|
lastNonZero = i;
|
|
break;
|
|
}
|
|
}
|
|
let constraintStr = "";
|
|
if (lastNonZero >= 0) {
|
|
const parts = [];
|
|
for (let i = 0; i <= lastNonZero; i++) {
|
|
parts.push(constraintBytes[i].toString(16).toUpperCase());
|
|
}
|
|
constraintStr = "." + parts.join(".");
|
|
}
|
|
return `hvc1.${spacePrefix}${profileIdc}.${compatHex}.${tierChar}${levelIdc}${constraintStr}`;
|
|
}
|
|
function readItemData(buffer, loc, idatOffset) {
|
|
const baseOffset = loc.constructionMethod === 1 ? idatOffset : 0;
|
|
if (loc.extents.length === 1) {
|
|
const ext = loc.extents[0];
|
|
const start = baseOffset + ext.offset;
|
|
return new Uint8Array(buffer.slice(start, start + ext.length));
|
|
}
|
|
let totalLength = 0;
|
|
for (const ext of loc.extents) {
|
|
totalLength += ext.length;
|
|
}
|
|
const data = new Uint8Array(totalLength);
|
|
let pos = 0;
|
|
for (const ext of loc.extents) {
|
|
const start = baseOffset + ext.offset;
|
|
data.set(
|
|
new Uint8Array(buffer.slice(start, start + ext.length)),
|
|
pos
|
|
);
|
|
pos += ext.length;
|
|
}
|
|
return data;
|
|
}
|
|
function findHvcProperties(propIndices, properties) {
|
|
let hvcCBox;
|
|
let ispeBox;
|
|
let irotBox;
|
|
for (const idx of propIndices) {
|
|
if (idx < 1 || idx > properties.length) {
|
|
continue;
|
|
}
|
|
const prop = properties[idx - 1];
|
|
if (prop.type === "hvcC" && !hvcCBox) {
|
|
hvcCBox = prop;
|
|
}
|
|
if (prop.type === "ispe" && !ispeBox) {
|
|
ispeBox = prop;
|
|
}
|
|
if (prop.type === "irot" && !irotBox) {
|
|
irotBox = prop;
|
|
}
|
|
}
|
|
if (!hvcCBox) {
|
|
throw new Error("No HEVC configuration (hvcC) found");
|
|
}
|
|
if (!ispeBox) {
|
|
throw new Error("No image dimensions (ispe) found");
|
|
}
|
|
return { hvcCBox, ispeBox, irotBox };
|
|
}
|
|
function parseHeic(buffer) {
|
|
const r = new Reader(buffer);
|
|
const fileEnd = buffer.byteLength;
|
|
const metaBox = findBox(r, 0, fileEnd, "meta");
|
|
if (!metaBox) {
|
|
throw new Error("No meta box found in HEIC file");
|
|
}
|
|
const metaChildStart = metaBox.offset + metaBox.headerSize + 4;
|
|
const metaEnd = metaBox.offset + metaBox.size;
|
|
const children = findBoxes(r, metaChildStart, metaEnd);
|
|
const pitmBox = children.find((b) => b.type === "pitm");
|
|
const ilocBox = children.find((b) => b.type === "iloc");
|
|
const iprpBox = children.find((b) => b.type === "iprp");
|
|
const iinfBox = children.find((b) => b.type === "iinf");
|
|
const irefBox = children.find((b) => b.type === "iref");
|
|
const idatBox = children.find((b) => b.type === "idat");
|
|
const idatOffset = idatBox ? idatBox.offset + idatBox.headerSize : 0;
|
|
if (!pitmBox || !ilocBox || !iprpBox) {
|
|
throw new Error("Missing required boxes (pitm, iloc, iprp) in HEIC");
|
|
}
|
|
const primaryId = parsePitm(r, pitmBox);
|
|
const locations = parseIloc(r, ilocBox);
|
|
const iprpStart = iprpBox.offset + iprpBox.headerSize;
|
|
const iprpEnd = iprpBox.offset + iprpBox.size;
|
|
const iprpChildren = findBoxes(r, iprpStart, iprpEnd);
|
|
const ipcoBox = iprpChildren.find((b) => b.type === "ipco");
|
|
const ipmaBox = iprpChildren.find((b) => b.type === "ipma");
|
|
if (!ipcoBox || !ipmaBox) {
|
|
throw new Error("Missing ipco or ipma in HEIC properties");
|
|
}
|
|
const allAssoc = parseIpma(r, ipmaBox);
|
|
const ipcoStart = ipcoBox.offset + ipcoBox.headerSize;
|
|
const ipcoEnd = ipcoBox.offset + ipcoBox.size;
|
|
const properties = findBoxes(r, ipcoStart, ipcoEnd);
|
|
let primaryItemType = "hvc1";
|
|
if (iinfBox) {
|
|
const itemTypes = parseIinf(r, iinfBox);
|
|
const t = itemTypes.get(primaryId);
|
|
if (t) {
|
|
primaryItemType = t;
|
|
}
|
|
}
|
|
if (primaryItemType === "grid") {
|
|
return parseGridImage(
|
|
r,
|
|
buffer,
|
|
primaryId,
|
|
locations,
|
|
allAssoc,
|
|
properties,
|
|
irefBox,
|
|
idatOffset
|
|
);
|
|
}
|
|
const primaryLoc = locations.get(primaryId);
|
|
if (!primaryLoc || primaryLoc.extents.length === 0) {
|
|
throw new Error(`No location data for primary item ${primaryId}`);
|
|
}
|
|
const primaryPropIndices = allAssoc.get(primaryId);
|
|
if (!primaryPropIndices || primaryPropIndices.length === 0) {
|
|
throw new Error("No property associations for primary item");
|
|
}
|
|
const { hvcCBox, ispeBox, irotBox } = findHvcProperties(
|
|
primaryPropIndices,
|
|
properties
|
|
);
|
|
const hvcCDataStart = hvcCBox.offset + hvcCBox.headerSize;
|
|
const hvcCDataSize = hvcCBox.size - hvcCBox.headerSize;
|
|
const description = new Uint8Array(
|
|
buffer.slice(hvcCDataStart, hvcCDataStart + hvcCDataSize)
|
|
);
|
|
const codecString = buildCodecString(r, hvcCDataStart);
|
|
const { width, height } = parseIspe(r, ispeBox);
|
|
const rotation = irotBox ? parseIrot(r, irotBox) : 0;
|
|
return {
|
|
codecString,
|
|
description,
|
|
tiles: [
|
|
{
|
|
data: readItemData(buffer, primaryLoc, idatOffset),
|
|
x: 0,
|
|
y: 0
|
|
}
|
|
],
|
|
tileWidth: width,
|
|
tileHeight: height,
|
|
outputWidth: width,
|
|
outputHeight: height,
|
|
rotation,
|
|
exifOrientation: rotation === 0 ? getUnappliedExifOrientation(buffer) : 1
|
|
};
|
|
}
|
|
function parseGridImage(r, buffer, gridItemId, locations, allAssoc, properties, irefBox, idatOffset) {
|
|
const gridLoc = locations.get(gridItemId);
|
|
if (!gridLoc || gridLoc.extents.length === 0) {
|
|
throw new Error("No location data for grid item");
|
|
}
|
|
const gridData = readItemData(buffer, gridLoc, idatOffset);
|
|
const largeFields = gridData.length > 1 && (gridData[1] & 1) !== 0;
|
|
const minGridSize = largeFields ? 12 : 8;
|
|
if (gridData.length < minGridSize) {
|
|
throw new Error(
|
|
`Grid descriptor too short: ${gridData.length} bytes`
|
|
);
|
|
}
|
|
const rows = gridData[2] + 1;
|
|
const columns = gridData[3] + 1;
|
|
const gv = new DataView(gridData.buffer, gridData.byteOffset);
|
|
let outputWidth;
|
|
let outputHeight;
|
|
if (largeFields) {
|
|
outputWidth = gv.getUint32(4);
|
|
outputHeight = gv.getUint32(8);
|
|
} else {
|
|
outputWidth = gv.getUint16(4);
|
|
outputHeight = gv.getUint16(6);
|
|
}
|
|
if (!irefBox) {
|
|
throw new Error("Grid image requires iref box");
|
|
}
|
|
const dimgRefs = parseIref(r, irefBox, "dimg");
|
|
const tileItemIds = dimgRefs.get(gridItemId);
|
|
if (!tileItemIds || tileItemIds.length === 0) {
|
|
throw new Error("No tile references found for grid item");
|
|
}
|
|
const expectedTiles = rows * columns;
|
|
if (tileItemIds.length < expectedTiles) {
|
|
throw new Error(
|
|
`Grid expects ${expectedTiles} tiles but found ${tileItemIds.length}`
|
|
);
|
|
}
|
|
const firstTileProps = allAssoc.get(tileItemIds[0]);
|
|
if (!firstTileProps || firstTileProps.length === 0) {
|
|
throw new Error("No property associations for tile item");
|
|
}
|
|
const { hvcCBox, ispeBox } = findHvcProperties(
|
|
firstTileProps,
|
|
properties
|
|
);
|
|
const gridProps = allAssoc.get(gridItemId) || [];
|
|
let irotBox;
|
|
for (const idx of gridProps) {
|
|
if (idx >= 1 && idx <= properties.length) {
|
|
const prop = properties[idx - 1];
|
|
if (prop.type === "irot") {
|
|
irotBox = prop;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
const hvcCDataStart = hvcCBox.offset + hvcCBox.headerSize;
|
|
const hvcCDataSize = hvcCBox.size - hvcCBox.headerSize;
|
|
const description = new Uint8Array(
|
|
buffer.slice(hvcCDataStart, hvcCDataStart + hvcCDataSize)
|
|
);
|
|
const codecString = buildCodecString(r, hvcCDataStart);
|
|
const { width: tileWidth, height: tileHeight } = parseIspe(r, ispeBox);
|
|
const tiles = [];
|
|
for (let row = 0; row < rows; row++) {
|
|
for (let col = 0; col < columns; col++) {
|
|
const tileIdx = row * columns + col;
|
|
const tileId = tileItemIds[tileIdx];
|
|
const tileLoc = locations.get(tileId);
|
|
if (!tileLoc || tileLoc.extents.length === 0) {
|
|
throw new Error(`No location data for tile item ${tileId}`);
|
|
}
|
|
tiles.push({
|
|
data: readItemData(buffer, tileLoc, idatOffset),
|
|
x: col * tileWidth,
|
|
y: row * tileHeight
|
|
});
|
|
}
|
|
}
|
|
const rotation = irotBox ? parseIrot(r, irotBox) : 0;
|
|
return {
|
|
codecString,
|
|
description,
|
|
tiles,
|
|
tileWidth,
|
|
tileHeight,
|
|
outputWidth,
|
|
outputHeight,
|
|
rotation,
|
|
exifOrientation: rotation === 0 ? getUnappliedExifOrientation(buffer) : 1
|
|
};
|
|
}
|
|
function readTiffOrientation(payload) {
|
|
if (payload.length < 8) {
|
|
return 1;
|
|
}
|
|
const view = new DataView(
|
|
payload.buffer,
|
|
payload.byteOffset,
|
|
payload.byteLength
|
|
);
|
|
let tiffStart = 0;
|
|
const firstWord = view.getUint16(0);
|
|
if (firstWord !== 18761 && firstWord !== 19789) {
|
|
tiffStart = view.getUint32(0) + 4;
|
|
}
|
|
if (tiffStart + 8 > payload.length) {
|
|
return 1;
|
|
}
|
|
const byteOrder = view.getUint16(tiffStart);
|
|
let little;
|
|
if (byteOrder === 18761) {
|
|
little = true;
|
|
} else if (byteOrder === 19789) {
|
|
little = false;
|
|
} else {
|
|
return 1;
|
|
}
|
|
const ifd0 = tiffStart + view.getUint32(tiffStart + 4, little);
|
|
if (ifd0 + 2 > payload.length) {
|
|
return 1;
|
|
}
|
|
const entryCount = view.getUint16(ifd0, little);
|
|
for (let i = 0; i < entryCount; i++) {
|
|
const entry = ifd0 + 2 + i * 12;
|
|
if (entry + 12 > payload.length) {
|
|
break;
|
|
}
|
|
if (view.getUint16(entry, little) === 274) {
|
|
const value = view.getUint16(entry + 8, little);
|
|
return value >= 1 && value <= 8 ? value : 1;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
function findMeta(r) {
|
|
const metaBox = findBox(r, 0, r.view.byteLength, "meta");
|
|
if (!metaBox) {
|
|
return null;
|
|
}
|
|
const start = metaBox.offset + metaBox.headerSize + 4;
|
|
const end = metaBox.offset + metaBox.size;
|
|
return { box: metaBox, children: findBoxes(r, start, end) };
|
|
}
|
|
function parseExifOrientation(buffer) {
|
|
try {
|
|
const r = new Reader(buffer);
|
|
const meta = findMeta(r);
|
|
if (!meta) {
|
|
return 1;
|
|
}
|
|
const iinfBox = meta.children.find((b) => b.type === "iinf");
|
|
const ilocBox = meta.children.find((b) => b.type === "iloc");
|
|
if (!iinfBox || !ilocBox) {
|
|
return 1;
|
|
}
|
|
const itemTypes = parseIinf(r, iinfBox);
|
|
let exifItemId;
|
|
for (const [id, type] of itemTypes) {
|
|
if (type === "Exif") {
|
|
exifItemId = id;
|
|
break;
|
|
}
|
|
}
|
|
if (exifItemId === void 0) {
|
|
return 1;
|
|
}
|
|
const loc = parseIloc(r, ilocBox).get(exifItemId);
|
|
if (!loc || loc.extents.length === 0) {
|
|
return 1;
|
|
}
|
|
const idatBox = meta.children.find((b) => b.type === "idat");
|
|
const idatOffset = idatBox ? idatBox.offset + idatBox.headerSize : 0;
|
|
return readTiffOrientation(readItemData(buffer, loc, idatOffset));
|
|
} catch {
|
|
return 1;
|
|
}
|
|
}
|
|
function hasNativeTransform(r) {
|
|
const meta = findMeta(r);
|
|
if (!meta) {
|
|
return false;
|
|
}
|
|
const iprp = meta.children.find((b) => b.type === "iprp");
|
|
if (!iprp) {
|
|
return false;
|
|
}
|
|
const ipco = findBox(
|
|
r,
|
|
iprp.offset + iprp.headerSize,
|
|
iprp.offset + iprp.size,
|
|
"ipco"
|
|
);
|
|
if (!ipco) {
|
|
return false;
|
|
}
|
|
const start = ipco.offset + ipco.headerSize;
|
|
const end = ipco.offset + ipco.size;
|
|
return Boolean(
|
|
findBox(r, start, end, "irot") || findBox(r, start, end, "imir")
|
|
);
|
|
}
|
|
function getUnappliedExifOrientation(buffer) {
|
|
try {
|
|
if (hasNativeTransform(new Reader(buffer))) {
|
|
return 1;
|
|
}
|
|
} catch {
|
|
return 1;
|
|
}
|
|
return parseExifOrientation(buffer);
|
|
}
|
|
|
|
// packages/upload-media/build-module/heic-support.mjs
|
|
var import_i18n6 = __toESM(require_i18n(), 1);
|
|
function detectBrowserFamily() {
|
|
if (typeof navigator === "undefined") {
|
|
return null;
|
|
}
|
|
const brands = navigator.userAgentData?.brands;
|
|
if (brands?.length) {
|
|
if (brands.some(({ brand }) => /Chromium/i.test(brand))) {
|
|
return "chromium";
|
|
}
|
|
}
|
|
const userAgent = navigator.userAgent || "";
|
|
if (/Firefox|FxiOS/i.test(userAgent)) {
|
|
return "firefox";
|
|
}
|
|
if (/Chrome|Chromium|CriOS|Edg|OPR/i.test(userAgent)) {
|
|
return "chromium";
|
|
}
|
|
if (/Safari/i.test(userAgent)) {
|
|
return "safari";
|
|
}
|
|
return null;
|
|
}
|
|
function detectPlatform() {
|
|
if (typeof navigator === "undefined") {
|
|
return "other";
|
|
}
|
|
const platform = navigator.userAgentData?.platform || navigator.userAgent || "";
|
|
if (/iPhone|iPad|iPod|iOS/i.test(platform)) {
|
|
return "other";
|
|
}
|
|
if (/macOS|Mac OS X|Macintosh/i.test(platform)) {
|
|
return "macos";
|
|
}
|
|
if (/Windows/i.test(platform)) {
|
|
return "windows";
|
|
}
|
|
if (/Android|CrOS|Chrome OS/i.test(platform)) {
|
|
return "other";
|
|
}
|
|
if (/Linux|X11/i.test(platform)) {
|
|
return "linux";
|
|
}
|
|
return "other";
|
|
}
|
|
function getBrowserName() {
|
|
if (detectBrowserFamily() === "chromium") {
|
|
const brands = navigator.userAgentData?.brands ?? [];
|
|
const userAgent = navigator.userAgent || "";
|
|
if (brands.some(({ brand }) => /Edge/i.test(brand)) || /Edg\//i.test(userAgent)) {
|
|
return "Edge";
|
|
}
|
|
if (brands.some(({ brand }) => /Opera/i.test(brand)) || /OPR\//i.test(userAgent)) {
|
|
return "Opera";
|
|
}
|
|
return "Chrome";
|
|
}
|
|
switch (detectBrowserFamily()) {
|
|
case "firefox":
|
|
return "Firefox";
|
|
case "safari":
|
|
return "Safari";
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
function getHeicConversionAdvice() {
|
|
const platform = detectPlatform();
|
|
const family = detectBrowserFamily();
|
|
if (platform === "macos") {
|
|
if (family === "chromium") {
|
|
return (0, import_i18n6.__)(
|
|
"Safari usually can, or you can upload a JPEG instead."
|
|
);
|
|
}
|
|
if (family !== "safari") {
|
|
return (0, import_i18n6.__)(
|
|
"Safari or Chrome usually can, or you can upload a JPEG instead."
|
|
);
|
|
}
|
|
}
|
|
if (platform === "windows" && family === "firefox") {
|
|
return (0, import_i18n6.__)("Chrome or Edge might, or you can upload a JPEG instead.");
|
|
}
|
|
return (0, import_i18n6.__)("You can upload a JPEG instead.");
|
|
}
|
|
function getHeicUnsupportedMessage() {
|
|
const platform = detectPlatform();
|
|
const family = detectBrowserFamily();
|
|
const browserName = getBrowserName();
|
|
let cause;
|
|
if (platform === "linux") {
|
|
cause = (0, import_i18n6.__)(
|
|
"HEIC photos can't be decoded on Linux, so unfortunately we couldn't convert this one."
|
|
);
|
|
} else if (family === "firefox") {
|
|
cause = (0, import_i18n6.__)(
|
|
"Firefox can't read HEIC photos, so we couldn't convert this one."
|
|
);
|
|
} else if (browserName && platform === "windows") {
|
|
cause = (0, import_i18n6.sprintf)(
|
|
/* translators: %s: browser name, e.g. "Chrome". */
|
|
(0, import_i18n6.__)(
|
|
"%s couldn't decode HEIC on this PC, so we couldn't convert this one."
|
|
),
|
|
browserName
|
|
);
|
|
} else if (browserName && platform === "macos") {
|
|
cause = (0, import_i18n6.sprintf)(
|
|
/* translators: %s: browser name, e.g. "Safari". */
|
|
(0, import_i18n6.__)(
|
|
"%s couldn't decode HEIC on this Mac, so we couldn't convert this one."
|
|
),
|
|
browserName
|
|
);
|
|
} else if (browserName) {
|
|
cause = (0, import_i18n6.sprintf)(
|
|
/* translators: %s: browser name, e.g. "Chrome". */
|
|
(0, import_i18n6.__)(
|
|
"%s couldn't decode HEIC on this device, so we couldn't convert this one."
|
|
),
|
|
browserName
|
|
);
|
|
} else {
|
|
cause = (0, import_i18n6.__)(
|
|
"This browser can't read HEIC photos, so we couldn't convert this one."
|
|
);
|
|
}
|
|
return `${cause} ${getHeicConversionAdvice()}`;
|
|
}
|
|
|
|
// packages/upload-media/build-module/canvas-utils.mjs
|
|
async function canvasConvertToJpeg(file, quality = 0.82) {
|
|
const baseName = getFileBasename(file.name);
|
|
try {
|
|
const bitmap = await createImageBitmap(file);
|
|
try {
|
|
const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
|
|
const ctx = canvas.getContext("2d");
|
|
if (!ctx) {
|
|
throw new Error("Could not get canvas 2d context");
|
|
}
|
|
ctx.drawImage(bitmap, 0, 0);
|
|
const jpegBlob = await canvas.convertToBlob({
|
|
type: "image/jpeg",
|
|
quality
|
|
});
|
|
return new File([jpegBlob], `${baseName}.jpg`, {
|
|
type: "image/jpeg"
|
|
});
|
|
} finally {
|
|
bitmap.close();
|
|
}
|
|
} catch {
|
|
}
|
|
if (typeof ImageDecoder !== "undefined") {
|
|
const supported = await ImageDecoder.isTypeSupported(file.type);
|
|
if (supported) {
|
|
const decoder = new ImageDecoder({
|
|
type: file.type,
|
|
data: file.stream()
|
|
});
|
|
try {
|
|
const { image: videoFrame } = await decoder.decode();
|
|
try {
|
|
const canvas = new OffscreenCanvas(
|
|
videoFrame.displayWidth,
|
|
videoFrame.displayHeight
|
|
);
|
|
const ctx = canvas.getContext("2d");
|
|
if (!ctx) {
|
|
throw new Error("Could not get canvas 2d context");
|
|
}
|
|
ctx.drawImage(videoFrame, 0, 0);
|
|
const jpegBlob = await canvas.convertToBlob({
|
|
type: "image/jpeg",
|
|
quality
|
|
});
|
|
return new File([jpegBlob], `${baseName}.jpg`, {
|
|
type: "image/jpeg"
|
|
});
|
|
} finally {
|
|
videoFrame.close();
|
|
}
|
|
} finally {
|
|
decoder.close();
|
|
}
|
|
}
|
|
}
|
|
if (typeof VideoDecoder !== "undefined") {
|
|
try {
|
|
const heicData = parseHeic(await file.arrayBuffer());
|
|
const support = await VideoDecoder.isConfigSupported({
|
|
codec: heicData.codecString
|
|
});
|
|
if (support.supported) {
|
|
const canvas = new OffscreenCanvas(
|
|
heicData.outputWidth,
|
|
heicData.outputHeight
|
|
);
|
|
const ctx = canvas.getContext("2d");
|
|
if (!ctx) {
|
|
throw new Error("Could not get canvas 2d context");
|
|
}
|
|
for (const tile of heicData.tiles) {
|
|
const frame = await decodeHevcFrame(
|
|
heicData.codecString,
|
|
heicData.description,
|
|
heicData.tileWidth,
|
|
heicData.tileHeight,
|
|
tile.data
|
|
);
|
|
try {
|
|
ctx.drawImage(frame, tile.x, tile.y);
|
|
} finally {
|
|
frame.close();
|
|
}
|
|
}
|
|
const outputCanvas = heicData.rotation !== 0 ? applyRotation(canvas, heicData.rotation) : applyExifOrientation(
|
|
canvas,
|
|
heicData.exifOrientation
|
|
);
|
|
const jpegBlob = await outputCanvas.convertToBlob({
|
|
type: "image/jpeg",
|
|
quality
|
|
});
|
|
return new File([jpegBlob], `${baseName}.jpg`, {
|
|
type: "image/jpeg"
|
|
});
|
|
}
|
|
} catch {
|
|
}
|
|
}
|
|
throw new Error(getHeicUnsupportedMessage());
|
|
}
|
|
function applyRotation(source, rotation) {
|
|
if (rotation === 0) {
|
|
return source;
|
|
}
|
|
const swap = rotation === 90 || rotation === 270;
|
|
const w = swap ? source.height : source.width;
|
|
const h = swap ? source.width : source.height;
|
|
const rotated = new OffscreenCanvas(w, h);
|
|
const ctx = rotated.getContext("2d");
|
|
if (!ctx) {
|
|
return source;
|
|
}
|
|
ctx.translate(w / 2, h / 2);
|
|
ctx.rotate(-rotation * Math.PI / 180);
|
|
ctx.drawImage(source, -source.width / 2, -source.height / 2);
|
|
return rotated;
|
|
}
|
|
function applyExifOrientation(source, orientation) {
|
|
if (orientation <= 1 || orientation > 8) {
|
|
return source;
|
|
}
|
|
const { width: sw, height: sh } = source;
|
|
const swap = orientation >= 5;
|
|
const out = new OffscreenCanvas(swap ? sh : sw, swap ? sw : sh);
|
|
const ctx = out.getContext("2d");
|
|
if (!ctx) {
|
|
return source;
|
|
}
|
|
switch (orientation) {
|
|
case 2:
|
|
ctx.transform(-1, 0, 0, 1, sw, 0);
|
|
break;
|
|
case 3:
|
|
ctx.transform(-1, 0, 0, -1, sw, sh);
|
|
break;
|
|
case 4:
|
|
ctx.transform(1, 0, 0, -1, 0, sh);
|
|
break;
|
|
case 5:
|
|
ctx.transform(0, 1, 1, 0, 0, 0);
|
|
break;
|
|
case 6:
|
|
ctx.transform(0, 1, -1, 0, sh, 0);
|
|
break;
|
|
case 7:
|
|
ctx.transform(0, -1, -1, 0, sh, sw);
|
|
break;
|
|
case 8:
|
|
ctx.transform(0, -1, 1, 0, 0, sw);
|
|
break;
|
|
}
|
|
ctx.drawImage(source, 0, 0);
|
|
return out;
|
|
}
|
|
function decodeHevcFrame(codec, description, width, height, data) {
|
|
return new Promise((resolve, reject) => {
|
|
const decoder = new VideoDecoder({
|
|
output: (frame) => {
|
|
decoder.close();
|
|
resolve(frame);
|
|
},
|
|
error: (e) => {
|
|
if (decoder.state !== "closed") {
|
|
decoder.close();
|
|
}
|
|
reject(e);
|
|
}
|
|
});
|
|
decoder.configure({
|
|
codec,
|
|
codedWidth: width,
|
|
codedHeight: height,
|
|
description
|
|
});
|
|
decoder.decode(
|
|
new EncodedVideoChunk({
|
|
type: "key",
|
|
timestamp: 0,
|
|
data
|
|
})
|
|
);
|
|
decoder.flush().catch((e) => {
|
|
if (decoder.state !== "closed") {
|
|
decoder.close();
|
|
}
|
|
reject(e);
|
|
});
|
|
});
|
|
}
|
|
|
|
// packages/upload-media/build-module/feature-detection.mjs
|
|
var cachedResult = null;
|
|
function detectClientSideMediaSupport() {
|
|
if (cachedResult !== null) {
|
|
return cachedResult;
|
|
}
|
|
if (typeof WebAssembly === "undefined") {
|
|
cachedResult = {
|
|
supported: false,
|
|
reason: "WebAssembly is not supported in this browser."
|
|
};
|
|
return cachedResult;
|
|
}
|
|
if (typeof SharedArrayBuffer === "undefined") {
|
|
cachedResult = {
|
|
supported: false,
|
|
reason: "SharedArrayBuffer is not available. This may be due to missing cross-origin isolation headers."
|
|
};
|
|
return cachedResult;
|
|
}
|
|
if (typeof Worker === "undefined") {
|
|
cachedResult = {
|
|
supported: false,
|
|
reason: "Web Workers are not supported in this browser."
|
|
};
|
|
return cachedResult;
|
|
}
|
|
if (typeof navigator !== "undefined" && "deviceMemory" in navigator && navigator.deviceMemory <= 2) {
|
|
cachedResult = {
|
|
supported: false,
|
|
reason: "Device has insufficient memory for client-side media processing."
|
|
};
|
|
return cachedResult;
|
|
}
|
|
if (typeof navigator !== "undefined" && "hardwareConcurrency" in navigator && navigator.hardwareConcurrency < 2) {
|
|
cachedResult = {
|
|
supported: false,
|
|
reason: "Device has insufficient CPU cores for client-side media processing."
|
|
};
|
|
return cachedResult;
|
|
}
|
|
if (typeof navigator !== "undefined") {
|
|
const connection = navigator.connection;
|
|
if (connection) {
|
|
if (connection.saveData) {
|
|
cachedResult = {
|
|
supported: false,
|
|
reason: "Data saver mode is enabled."
|
|
};
|
|
return cachedResult;
|
|
}
|
|
if (connection.effectiveType === "slow-2g" || connection.effectiveType === "2g") {
|
|
cachedResult = {
|
|
supported: false,
|
|
reason: "Network connection is too slow for client-side media processing."
|
|
};
|
|
return cachedResult;
|
|
}
|
|
}
|
|
}
|
|
if (typeof window !== "undefined") {
|
|
try {
|
|
const testBlob = new Blob([""], {
|
|
type: "application/javascript"
|
|
});
|
|
const testUrl = URL.createObjectURL(testBlob);
|
|
try {
|
|
const testWorker = new Worker(testUrl);
|
|
testWorker.terminate();
|
|
} finally {
|
|
URL.revokeObjectURL(testUrl);
|
|
}
|
|
} catch {
|
|
cachedResult = {
|
|
supported: false,
|
|
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."
|
|
};
|
|
return cachedResult;
|
|
}
|
|
}
|
|
cachedResult = { supported: true };
|
|
return cachedResult;
|
|
}
|
|
function isClientSideMediaSupported() {
|
|
return detectClientSideMediaSupport().supported;
|
|
}
|
|
function isHeicCanvasSupported() {
|
|
return typeof createImageBitmap !== "undefined" && typeof OffscreenCanvas !== "undefined";
|
|
}
|
|
function clearFeatureDetectionCache() {
|
|
cachedResult = null;
|
|
}
|
|
var BYTES_PER_PIXEL = 4;
|
|
var INTERLACED_MEMORY_BUDGET = 0.5 * 1024 * 1024 * 1024;
|
|
var BASELINE_MEMORY_BUDGET = 0.9 * 1024 * 1024 * 1024;
|
|
function exceedsClientProcessingMemory(dimensions) {
|
|
const { width, height, interlaced } = dimensions;
|
|
const estimatedBytes = width * height * BYTES_PER_PIXEL;
|
|
const budget = interlaced ? INTERLACED_MEMORY_BUDGET : BASELINE_MEMORY_BUDGET;
|
|
return estimatedBytes > budget;
|
|
}
|
|
|
|
// packages/upload-media/build-module/get-image-dimensions.mjs
|
|
var MAX_HEADER_BYTES = 512 * 1024;
|
|
function parseJpeg(view) {
|
|
let offset = 2;
|
|
while (offset < view.byteLength) {
|
|
if (view.getUint8(offset) !== 255) {
|
|
return null;
|
|
}
|
|
while (offset < view.byteLength && view.getUint8(offset) === 255) {
|
|
offset++;
|
|
}
|
|
if (offset >= view.byteLength) {
|
|
return null;
|
|
}
|
|
const marker = view.getUint8(offset);
|
|
offset++;
|
|
if (marker === 1 || marker >= 208 && marker <= 217) {
|
|
continue;
|
|
}
|
|
if (marker === 218) {
|
|
return null;
|
|
}
|
|
if (offset + 2 > view.byteLength) {
|
|
return null;
|
|
}
|
|
const segmentLength = view.getUint16(offset);
|
|
if (segmentLength < 2) {
|
|
return null;
|
|
}
|
|
const isStartOfFrame = marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204;
|
|
if (isStartOfFrame) {
|
|
if (offset + 7 > view.byteLength) {
|
|
return null;
|
|
}
|
|
const height = view.getUint16(offset + 3);
|
|
const width = view.getUint16(offset + 5);
|
|
const interlaced = marker === 194 || marker === 198 || marker === 202 || marker === 206;
|
|
return { width, height, interlaced };
|
|
}
|
|
offset += segmentLength;
|
|
}
|
|
return null;
|
|
}
|
|
function parsePng(view) {
|
|
if (view.byteLength < 29) {
|
|
return null;
|
|
}
|
|
const isIhdr = view.getUint8(12) === 73 && // I
|
|
view.getUint8(13) === 72 && // H
|
|
view.getUint8(14) === 68 && // D
|
|
view.getUint8(15) === 82;
|
|
if (!isIhdr) {
|
|
return null;
|
|
}
|
|
const width = view.getUint32(16);
|
|
const height = view.getUint32(20);
|
|
const interlaceMethod = view.getUint8(28);
|
|
return { width, height, interlaced: interlaceMethod !== 0 };
|
|
}
|
|
async function getImageDimensions(file) {
|
|
try {
|
|
const headerBytes = Math.min(file.size, MAX_HEADER_BYTES);
|
|
const buffer = await file.slice(0, headerBytes).arrayBuffer();
|
|
const view = new DataView(buffer);
|
|
if (view.byteLength >= 3 && view.getUint16(0) === 65496) {
|
|
return parseJpeg(view);
|
|
}
|
|
if (view.byteLength >= 8 && view.getUint32(0) === 2303741511 && view.getUint32(4) === 218765834) {
|
|
return parsePng(view);
|
|
}
|
|
return null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// packages/upload-media/build-module/stub-file.mjs
|
|
var StubFile = class extends File {
|
|
constructor(fileName = "stub-file") {
|
|
super([], fileName);
|
|
}
|
|
};
|
|
|
|
// packages/upload-media/build-module/store/private-actions.mjs
|
|
var DEFAULT_OUTPUT_QUALITY = 0.82;
|
|
var ultraHdrItems = /* @__PURE__ */ new Set();
|
|
function addItem({
|
|
file: fileOrBlob,
|
|
batchId,
|
|
onChange,
|
|
onSuccess,
|
|
onBatchSuccess,
|
|
onError,
|
|
additionalData = {},
|
|
sourceUrl,
|
|
sourceAttachmentId,
|
|
abortController,
|
|
operations
|
|
}) {
|
|
return async ({ dispatch }) => {
|
|
const itemId = v4_default();
|
|
const file = convertBlobToFile(fileOrBlob);
|
|
let blobUrl;
|
|
if (!(file instanceof StubFile)) {
|
|
blobUrl = (0, import_blob.createBlobURL)(file);
|
|
dispatch({
|
|
type: Type.CacheBlobUrl,
|
|
id: itemId,
|
|
blobUrl
|
|
});
|
|
}
|
|
dispatch({
|
|
type: Type.Add,
|
|
item: {
|
|
id: itemId,
|
|
batchId,
|
|
status: ItemStatus.Processing,
|
|
sourceFile: cloneFile(file),
|
|
file,
|
|
attachment: {
|
|
url: blobUrl
|
|
},
|
|
additionalData: {
|
|
generate_sub_sizes: false,
|
|
...additionalData
|
|
},
|
|
onChange,
|
|
onSuccess,
|
|
onBatchSuccess,
|
|
onError,
|
|
sourceUrl,
|
|
sourceAttachmentId,
|
|
abortController: abortController || new AbortController(),
|
|
operations: Array.isArray(operations) ? operations : [OperationType.Prepare]
|
|
}
|
|
});
|
|
dispatch.processItem(itemId);
|
|
};
|
|
}
|
|
function addSideloadItem({
|
|
file,
|
|
onChange,
|
|
additionalData,
|
|
operations,
|
|
batchId,
|
|
parentId
|
|
}) {
|
|
return ({ dispatch }) => {
|
|
const itemId = v4_default();
|
|
dispatch({
|
|
type: Type.Add,
|
|
item: {
|
|
id: itemId,
|
|
batchId,
|
|
status: ItemStatus.Processing,
|
|
sourceFile: cloneFile(file),
|
|
file,
|
|
onChange,
|
|
additionalData: {
|
|
...additionalData
|
|
},
|
|
parentId,
|
|
operations: Array.isArray(operations) ? operations : [OperationType.Prepare],
|
|
abortController: new AbortController()
|
|
}
|
|
});
|
|
dispatch.processItem(itemId);
|
|
};
|
|
}
|
|
function processItem(id) {
|
|
return async ({ select: select2, dispatch }) => {
|
|
if (select2.isPaused()) {
|
|
return;
|
|
}
|
|
const item = select2.getItem(id);
|
|
if (!item) {
|
|
return;
|
|
}
|
|
const {
|
|
attachment,
|
|
onChange,
|
|
onSuccess,
|
|
onBatchSuccess,
|
|
batchId,
|
|
parentId
|
|
} = item;
|
|
const operation = Array.isArray(item.operations?.[0]) ? item.operations[0][0] : item.operations?.[0];
|
|
const operationArgs = Array.isArray(item.operations?.[0]) ? item.operations[0][1] : void 0;
|
|
if (operation === OperationType.Upload) {
|
|
const settings = select2.getSettings();
|
|
const activeCount = select2.getActiveUploadCount();
|
|
if (activeCount >= settings.maxConcurrentUploads) {
|
|
return;
|
|
}
|
|
}
|
|
if (operation === OperationType.ResizeCrop || operation === OperationType.Rotate) {
|
|
const settings = select2.getSettings();
|
|
const activeCount = select2.getActiveImageProcessingCount();
|
|
if (activeCount >= settings.maxConcurrentImageProcessing) {
|
|
return;
|
|
}
|
|
}
|
|
if (operation === OperationType.TranscodeGif) {
|
|
const activeCount = select2.getActiveVideoProcessingCount();
|
|
if (activeCount >= 1) {
|
|
return;
|
|
}
|
|
}
|
|
if (attachment) {
|
|
const isHeicUrl = attachment.url && /\.hei[cf]$/i.test(attachment.url);
|
|
if (!isHeicUrl) {
|
|
onChange?.([attachment]);
|
|
}
|
|
}
|
|
if (!operation) {
|
|
if (parentId || !parentId && !select2.hasPendingItemsByParentId(id)) {
|
|
if (attachment) {
|
|
onSuccess?.([attachment]);
|
|
}
|
|
dispatch.removeItem(id);
|
|
dispatch.revokeBlobUrls(id);
|
|
if (batchId && select2.isBatchUploaded(batchId)) {
|
|
onBatchSuccess?.();
|
|
}
|
|
}
|
|
if (parentId && batchId && select2.isBatchUploaded(batchId)) {
|
|
const parentItem = select2.getItem(parentId);
|
|
if (!parentItem) {
|
|
return;
|
|
}
|
|
if (parentItem.operations && parentItem.operations.length > 0) {
|
|
dispatch.processItem(parentId);
|
|
return;
|
|
}
|
|
if (attachment) {
|
|
parentItem.onSuccess?.([attachment]);
|
|
}
|
|
dispatch.removeItem(parentId);
|
|
dispatch.revokeBlobUrls(parentId);
|
|
if (parentItem.batchId && select2.isBatchUploaded(parentItem.batchId)) {
|
|
parentItem.onBatchSuccess?.();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
if (operation === OperationType.Finalize && select2.hasPendingItemsByParentId(id)) {
|
|
return;
|
|
}
|
|
dispatch({
|
|
type: Type.OperationStart,
|
|
id,
|
|
operation
|
|
});
|
|
debug(
|
|
`Starting operation ${operation} for ${item.file.name} (item ${item.id})`
|
|
);
|
|
switch (operation) {
|
|
case OperationType.Prepare:
|
|
dispatch.prepareItem(item.id);
|
|
break;
|
|
case OperationType.ResizeCrop:
|
|
dispatch.resizeCropItem(
|
|
item.id,
|
|
operationArgs
|
|
);
|
|
break;
|
|
case OperationType.Rotate:
|
|
dispatch.rotateItem(
|
|
item.id,
|
|
operationArgs
|
|
);
|
|
break;
|
|
case OperationType.TranscodeImage:
|
|
dispatch.transcodeImageItem(
|
|
item.id,
|
|
operationArgs
|
|
);
|
|
break;
|
|
case OperationType.TranscodeGif:
|
|
dispatch.transcodeGifItem(
|
|
item.id,
|
|
operationArgs
|
|
);
|
|
break;
|
|
case OperationType.Upload:
|
|
if (item.parentId) {
|
|
dispatch.sideloadItem(id);
|
|
} else {
|
|
dispatch.uploadItem(id);
|
|
}
|
|
break;
|
|
case OperationType.ThumbnailGeneration:
|
|
dispatch.generateThumbnails(id);
|
|
break;
|
|
case OperationType.Finalize:
|
|
dispatch.finalizeItem(id);
|
|
break;
|
|
case OperationType.DetectUltraHdr:
|
|
dispatch.detectUltraHdr(id);
|
|
break;
|
|
}
|
|
};
|
|
}
|
|
function pauseQueue() {
|
|
return {
|
|
type: Type.PauseQueue
|
|
};
|
|
}
|
|
function resumeQueue() {
|
|
return async ({ select: select2, dispatch }) => {
|
|
dispatch({
|
|
type: Type.ResumeQueue
|
|
});
|
|
for (const item of select2.getAllItems()) {
|
|
if (item.status === ItemStatus.PendingRetry) {
|
|
dispatch.executeRetry(item.id);
|
|
} else {
|
|
dispatch.processItem(item.id);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
function pauseItem(id) {
|
|
return async ({ dispatch }) => {
|
|
dispatch({
|
|
type: Type.PauseItem,
|
|
id
|
|
});
|
|
};
|
|
}
|
|
function removeItem(id) {
|
|
return async ({ select: select2, dispatch }) => {
|
|
const item = select2.getItem(id);
|
|
if (!item) {
|
|
return;
|
|
}
|
|
ultraHdrItems.delete(id);
|
|
clearRetryTimer(id);
|
|
dispatch({
|
|
type: Type.Remove,
|
|
id
|
|
});
|
|
if (select2.getAllItems().length === 0) {
|
|
terminateVipsWorker();
|
|
terminateVideoConversionWorker();
|
|
}
|
|
};
|
|
}
|
|
function finishOperation(id, updates) {
|
|
return async ({ select: select2, dispatch }) => {
|
|
const item = select2.getItem(id);
|
|
const previousOperation = item?.currentOperation;
|
|
dispatch({
|
|
type: Type.OperationFinish,
|
|
id,
|
|
item: updates
|
|
});
|
|
dispatch.processItem(id);
|
|
if (previousOperation === OperationType.Upload) {
|
|
const pendingUploads = select2.getPendingUploads();
|
|
for (const pendingItem of pendingUploads) {
|
|
dispatch.processItem(pendingItem.id);
|
|
}
|
|
}
|
|
if (previousOperation === OperationType.ResizeCrop || previousOperation === OperationType.Rotate) {
|
|
const pendingItems = select2.getPendingImageProcessing();
|
|
for (const pendingItem of pendingItems) {
|
|
dispatch.processItem(pendingItem.id);
|
|
}
|
|
}
|
|
if (previousOperation === OperationType.TranscodeGif) {
|
|
const pendingItems = select2.getPendingVideoProcessing();
|
|
for (const pendingItem of pendingItems) {
|
|
dispatch.processItem(pendingItem.id);
|
|
}
|
|
}
|
|
if (previousOperation === OperationType.ResizeCrop || previousOperation === OperationType.Rotate || previousOperation === OperationType.TranscodeImage) {
|
|
maybeRecycleVipsWorker(select2.getActiveImageProcessingCount());
|
|
}
|
|
};
|
|
}
|
|
var VALID_IMAGE_FORMATS = ["jpeg", "webp", "avif", "png", "gif"];
|
|
function isValidImageFormat(format) {
|
|
return VALID_IMAGE_FORMATS.includes(format);
|
|
}
|
|
async function getTranscodeImageOperation(file, outputMimeType, interlaced = false, quality = DEFAULT_OUTPUT_QUALITY) {
|
|
if (file.type === "image/png" && outputMimeType === "image/jpeg") {
|
|
const blobUrl = (0, import_blob.createBlobURL)(file);
|
|
try {
|
|
const hasAlpha = await vipsHasTransparency(blobUrl);
|
|
if (hasAlpha) {
|
|
return null;
|
|
}
|
|
} catch {
|
|
return null;
|
|
} finally {
|
|
(0, import_blob.revokeBlobURL)(blobUrl);
|
|
}
|
|
}
|
|
const formatPart = outputMimeType.split("/")[1];
|
|
if (!isValidImageFormat(formatPart)) {
|
|
return null;
|
|
}
|
|
return [
|
|
OperationType.TranscodeImage,
|
|
{
|
|
outputFormat: formatPart,
|
|
outputQuality: quality,
|
|
interlaced
|
|
}
|
|
];
|
|
}
|
|
function prepareItem(id) {
|
|
return async ({ select: select2, dispatch }) => {
|
|
const item = select2.getItem(id);
|
|
if (!item) {
|
|
return;
|
|
}
|
|
const { file } = item;
|
|
const operations = [];
|
|
const settings = select2.getSettings();
|
|
if (file.type === "image/gif" && settings.gifConvert !== false && typeof ImageDecoder !== "undefined" && typeof VideoEncoder !== "undefined") {
|
|
let isAnimated = false;
|
|
try {
|
|
isAnimated = isAnimatedGif(await file.arrayBuffer());
|
|
} catch {
|
|
isAnimated = false;
|
|
}
|
|
if (isAnimated) {
|
|
let hasTransparency = false;
|
|
const blobUrl = (0, import_blob.createBlobURL)(file);
|
|
try {
|
|
hasTransparency = await vipsHasTransparency(blobUrl);
|
|
} catch {
|
|
hasTransparency = true;
|
|
} finally {
|
|
(0, import_blob.revokeBlobURL)(blobUrl);
|
|
}
|
|
if (!hasTransparency) {
|
|
operations.push(
|
|
OperationType.Upload,
|
|
OperationType.ThumbnailGeneration,
|
|
OperationType.Finalize
|
|
);
|
|
dispatch({
|
|
type: Type.AddOperations,
|
|
id,
|
|
operations
|
|
});
|
|
dispatch.finishOperation(id, {
|
|
animatedGifFile: item.file
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
let heicJpeg = null;
|
|
const isImage = file.type.startsWith("image/");
|
|
const isVipsSupported = CLIENT_SIDE_SUPPORTED_MIME_TYPES.includes(
|
|
file.type
|
|
);
|
|
const isHeic = HEIC_MIME_TYPES.includes(file.type);
|
|
let tooLargeForClient = false;
|
|
if (isImage && isVipsSupported) {
|
|
const dimensions = await getImageDimensions(file);
|
|
if (dimensions && exceedsClientProcessingMemory(dimensions)) {
|
|
tooLargeForClient = true;
|
|
}
|
|
}
|
|
if (file.type === "image/jpeg" && !tooLargeForClient) {
|
|
operations.push(OperationType.DetectUltraHdr);
|
|
}
|
|
if (isImage && isVipsSupported && !tooLargeForClient) {
|
|
operations.push(
|
|
OperationType.Upload,
|
|
OperationType.ThumbnailGeneration,
|
|
OperationType.Finalize
|
|
);
|
|
} else if (isImage && isHeic) {
|
|
try {
|
|
heicJpeg = await canvasConvertToJpeg(
|
|
file,
|
|
settings.imageQuality ?? DEFAULT_OUTPUT_QUALITY
|
|
);
|
|
} catch {
|
|
dispatch.cancelItem(
|
|
id,
|
|
new UploadError({
|
|
code: ErrorCode.HEIC_DECODE_ERROR,
|
|
message: getHeicUnsupportedMessage(),
|
|
file
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
operations.push(
|
|
OperationType.Upload,
|
|
OperationType.ThumbnailGeneration,
|
|
OperationType.Finalize
|
|
);
|
|
} else {
|
|
operations.push(OperationType.Upload);
|
|
}
|
|
dispatch({
|
|
type: Type.AddOperations,
|
|
id,
|
|
operations
|
|
});
|
|
let updates;
|
|
if (isHeic && heicJpeg) {
|
|
const vipsAvailable = isClientSideMediaSupported();
|
|
updates = {
|
|
file: heicJpeg,
|
|
sourceFile: heicJpeg,
|
|
originalHeicFile: item.file,
|
|
additionalData: {
|
|
...item.additionalData,
|
|
generate_sub_sizes: !vipsAvailable,
|
|
convert_format: true
|
|
}
|
|
};
|
|
} else if (!isVipsSupported || !isImage || tooLargeForClient) {
|
|
updates = {
|
|
additionalData: {
|
|
...item.additionalData,
|
|
generate_sub_sizes: true,
|
|
convert_format: true
|
|
}
|
|
};
|
|
} else {
|
|
updates = {
|
|
additionalData: {
|
|
...item.additionalData,
|
|
generate_sub_sizes: false
|
|
}
|
|
};
|
|
}
|
|
dispatch.finishOperation(id, updates);
|
|
};
|
|
}
|
|
function detectUltraHdr(id) {
|
|
return async ({ select: select2, dispatch }) => {
|
|
const item = select2.getItem(id);
|
|
if (!item) {
|
|
return;
|
|
}
|
|
let info;
|
|
try {
|
|
const buffer = await item.file.arrayBuffer();
|
|
info = await vipsGetUltraHdrInfo(buffer);
|
|
} catch {
|
|
}
|
|
if (info) {
|
|
ultraHdrItems.add(id);
|
|
}
|
|
dispatch.finishOperation(id, {});
|
|
};
|
|
}
|
|
function uploadItem(id) {
|
|
return async ({ select: select2, dispatch }) => {
|
|
const item = select2.getItem(id);
|
|
if (!item) {
|
|
return;
|
|
}
|
|
const startTime = performance.now();
|
|
let finished = false;
|
|
const finishUpload = (attachment) => {
|
|
if (finished) {
|
|
return;
|
|
}
|
|
finished = true;
|
|
measure({
|
|
measureName: `Upload ${item.file.name}`,
|
|
startTime,
|
|
tooltipText: item.file.name,
|
|
properties: [
|
|
["Item ID", item.id],
|
|
["File name", item.file.name]
|
|
]
|
|
});
|
|
dispatch.finishOperation(id, { attachment });
|
|
};
|
|
select2.getSettings().mediaUpload({
|
|
filesList: [item.file],
|
|
additionalData: item.additionalData,
|
|
signal: item.abortController?.signal,
|
|
// The queue's own items drive upload progress UI and save
|
|
// locking; without this, consumers that track uploads themselves
|
|
// (e.g. the editor's progress snackbar) would count this file a
|
|
// second time.
|
|
isTransportOnly: true,
|
|
onFileChange: ([attachment]) => {
|
|
if (attachment && !(0, import_blob.isBlobURL)(attachment.url)) {
|
|
finishUpload(attachment);
|
|
}
|
|
},
|
|
onSuccess: ([attachment]) => {
|
|
finishUpload(attachment);
|
|
},
|
|
onError: (error) => {
|
|
dispatch.cancelItem(id, error);
|
|
}
|
|
});
|
|
};
|
|
}
|
|
function sideloadItem(id) {
|
|
return async ({ select: select2, dispatch }) => {
|
|
const item = select2.getItem(id);
|
|
if (!item) {
|
|
return;
|
|
}
|
|
const { post, ...additionalData } = item.additionalData;
|
|
const mediaSideload = select2.getSettings().mediaSideload;
|
|
if (!mediaSideload) {
|
|
dispatch.finishOperation(id, {});
|
|
return;
|
|
}
|
|
const startTime = performance.now();
|
|
mediaSideload({
|
|
file: item.file,
|
|
attachmentId: post,
|
|
additionalData,
|
|
signal: item.abortController?.signal,
|
|
onSuccess: (subSize) => {
|
|
measure({
|
|
measureName: `Sideload ${item.file.name}`,
|
|
startTime,
|
|
tooltipText: item.file.name,
|
|
properties: [
|
|
["Item ID", item.id],
|
|
["File name", item.file.name]
|
|
]
|
|
});
|
|
if (item.parentId) {
|
|
dispatch({
|
|
type: Type.AccumulateSubSize,
|
|
id: item.parentId,
|
|
subSize
|
|
});
|
|
}
|
|
dispatch.finishOperation(id, {});
|
|
},
|
|
onError: (error) => {
|
|
dispatch.cancelItem(id, error);
|
|
}
|
|
});
|
|
};
|
|
}
|
|
function resizeCropItem(id, args) {
|
|
return async ({ select: select2, dispatch }) => {
|
|
const item = select2.getItem(id);
|
|
if (!item) {
|
|
return;
|
|
}
|
|
if (!args?.resize) {
|
|
dispatch.finishOperation(id, {
|
|
file: item.file
|
|
});
|
|
return;
|
|
}
|
|
const startTime = performance.now();
|
|
const addSuffix = Boolean(item.parentId);
|
|
const scaledSuffix = Boolean(args.isThresholdResize);
|
|
const { imageStripMeta, imageMaxBitDepth } = select2.getSettings();
|
|
try {
|
|
const file = await vipsResizeImage(
|
|
item.id,
|
|
item.file,
|
|
args.resize,
|
|
{
|
|
smartCrop: false,
|
|
addSuffix,
|
|
signal: item.abortController?.signal,
|
|
scaledSuffix,
|
|
quality: args.quality,
|
|
stripMeta: imageStripMeta,
|
|
maxBitdepth: imageMaxBitDepth
|
|
}
|
|
);
|
|
measure({
|
|
measureName: `ResizeCrop ${item.file.name}`,
|
|
startTime,
|
|
tooltipText: item.file.name,
|
|
properties: [
|
|
["Item ID", item.id],
|
|
["File name", item.file.name]
|
|
]
|
|
});
|
|
const blobUrl = (0, import_blob.createBlobURL)(file);
|
|
dispatch({
|
|
type: Type.CacheBlobUrl,
|
|
id,
|
|
blobUrl
|
|
});
|
|
dispatch.finishOperation(id, {
|
|
file,
|
|
attachment: {
|
|
url: blobUrl
|
|
}
|
|
});
|
|
} catch (error) {
|
|
dispatch.cancelItem(
|
|
id,
|
|
new UploadError({
|
|
code: ErrorCode.IMAGE_TRANSCODING_ERROR,
|
|
message: (0, import_i18n7.__)(
|
|
"The web server cannot generate responsive image sizes for this image. Convert it to JPEG or PNG before uploading."
|
|
),
|
|
file: item.file,
|
|
cause: error instanceof Error ? error : void 0
|
|
})
|
|
);
|
|
}
|
|
};
|
|
}
|
|
function rotateItem(id, args) {
|
|
return async ({ select: select2, dispatch }) => {
|
|
const item = select2.getItem(id);
|
|
if (!item) {
|
|
return;
|
|
}
|
|
if (!args?.orientation || args.orientation === 1) {
|
|
dispatch.finishOperation(id, {
|
|
file: item.file
|
|
});
|
|
return;
|
|
}
|
|
const startTime = performance.now();
|
|
try {
|
|
const file = await vipsRotateImage(
|
|
item.id,
|
|
item.file,
|
|
args.orientation,
|
|
item.abortController?.signal
|
|
);
|
|
measure({
|
|
measureName: `Rotate ${item.file.name}`,
|
|
startTime,
|
|
tooltipText: item.file.name,
|
|
properties: [
|
|
["Item ID", item.id],
|
|
["File name", item.file.name]
|
|
]
|
|
});
|
|
const blobUrl = (0, import_blob.createBlobURL)(file);
|
|
dispatch({
|
|
type: Type.CacheBlobUrl,
|
|
id,
|
|
blobUrl
|
|
});
|
|
dispatch.finishOperation(id, {
|
|
file,
|
|
attachment: {
|
|
url: blobUrl
|
|
}
|
|
});
|
|
} catch (error) {
|
|
dispatch.cancelItem(
|
|
id,
|
|
new UploadError({
|
|
code: ErrorCode.IMAGE_ROTATION_ERROR,
|
|
message: (0, import_i18n7.__)(
|
|
"The web server cannot generate responsive image sizes for this image. Convert it to JPEG or PNG before uploading."
|
|
),
|
|
file: item.file,
|
|
cause: error instanceof Error ? error : void 0
|
|
})
|
|
);
|
|
}
|
|
};
|
|
}
|
|
function transcodeImageItem(id, args) {
|
|
return async ({ select: select2, dispatch }) => {
|
|
const item = select2.getItem(id);
|
|
if (!item) {
|
|
return;
|
|
}
|
|
if (!args?.outputFormat) {
|
|
dispatch.finishOperation(id, {
|
|
file: item.file
|
|
});
|
|
return;
|
|
}
|
|
const startTime = performance.now();
|
|
const outputMimeType = `image/${args.outputFormat}`;
|
|
const quality = args.outputQuality ?? DEFAULT_OUTPUT_QUALITY;
|
|
const interlaced = args.interlaced ?? false;
|
|
const { imageStripMeta, imageMaxBitDepth } = select2.getSettings();
|
|
try {
|
|
const file = await vipsConvertImageFormat(
|
|
item.id,
|
|
item.file,
|
|
outputMimeType,
|
|
{
|
|
quality,
|
|
interlaced,
|
|
stripMeta: imageStripMeta,
|
|
maxBitdepth: imageMaxBitDepth
|
|
}
|
|
);
|
|
measure({
|
|
measureName: `Transcode ${item.file.name}`,
|
|
startTime,
|
|
tooltipText: item.file.name,
|
|
properties: [
|
|
["Item ID", item.id],
|
|
["File name", item.file.name]
|
|
]
|
|
});
|
|
const blobUrl = (0, import_blob.createBlobURL)(file);
|
|
dispatch({
|
|
type: Type.CacheBlobUrl,
|
|
id,
|
|
blobUrl
|
|
});
|
|
dispatch.finishOperation(id, {
|
|
file,
|
|
attachment: {
|
|
url: blobUrl
|
|
}
|
|
});
|
|
} catch (error) {
|
|
dispatch.cancelItem(
|
|
id,
|
|
new UploadError({
|
|
code: ErrorCode.MEDIA_TRANSCODING_ERROR,
|
|
message: "Image could not be transcoded to the target format",
|
|
file: item.file,
|
|
cause: error instanceof Error ? error : void 0
|
|
})
|
|
);
|
|
}
|
|
};
|
|
}
|
|
function transcodeGifItem(id, args) {
|
|
return async ({ select: select2, dispatch }) => {
|
|
const item = select2.getItem(id);
|
|
if (!item) {
|
|
return;
|
|
}
|
|
const outputFormat = args?.outputFormat ?? "mp4";
|
|
const outputMimeType = `video/${outputFormat}`;
|
|
const gifFile = item.file;
|
|
try {
|
|
const file = await convertGifToVideo(
|
|
item.id,
|
|
gifFile,
|
|
outputMimeType,
|
|
{
|
|
timeout: args?.timeout,
|
|
maxTotalPixels: args?.maxTotalPixels
|
|
}
|
|
);
|
|
dispatch.finishOperation(id, { file });
|
|
dispatch.addSideloadItem({
|
|
file: gifFile,
|
|
batchId: v4_default(),
|
|
parentId: item.parentId,
|
|
additionalData: {
|
|
post: item.additionalData?.post,
|
|
image_size: "animated_video_poster",
|
|
convert_format: false
|
|
},
|
|
operations: [
|
|
[
|
|
OperationType.TranscodeImage,
|
|
{
|
|
outputFormat: "jpeg",
|
|
outputQuality: DEFAULT_OUTPUT_QUALITY,
|
|
interlaced: false
|
|
}
|
|
],
|
|
OperationType.Upload
|
|
]
|
|
});
|
|
} catch (error) {
|
|
if (isUnsupportedConversionError(error)) {
|
|
if (isSizeLimitConversionError(error)) {
|
|
debug(
|
|
`Skipping GIF to video conversion: ${error instanceof Error ? error.message : error}`
|
|
);
|
|
}
|
|
dispatch.cancelItem(
|
|
id,
|
|
new Error("Animated GIF conversion unsupported"),
|
|
true
|
|
);
|
|
return;
|
|
}
|
|
if (isConversionTimeoutError(error)) {
|
|
debug(
|
|
`GIF to video conversion timed out; keeping the original GIF only: ${error instanceof Error ? error.message : error}`
|
|
);
|
|
dispatch.cancelItem(
|
|
id,
|
|
new Error("Animated GIF conversion timed out"),
|
|
true
|
|
);
|
|
return;
|
|
}
|
|
console.error(
|
|
"[video-conversion] GIF to video conversion failed:",
|
|
error
|
|
);
|
|
dispatch.cancelItem(
|
|
id,
|
|
new UploadError({
|
|
code: ErrorCode.GIF_TRANSCODING_ERROR,
|
|
message: "Animated GIF could not be converted to video",
|
|
file: item.file,
|
|
cause: error instanceof Error ? error : void 0
|
|
}),
|
|
true
|
|
);
|
|
}
|
|
};
|
|
}
|
|
function generateThumbnails(id) {
|
|
return async ({ select: select2, dispatch }) => {
|
|
const item = select2.getItem(id);
|
|
if (!item) {
|
|
return;
|
|
}
|
|
if (!item.attachment) {
|
|
dispatch.finishOperation(id, {});
|
|
return;
|
|
}
|
|
const attachment = item.attachment;
|
|
const settings = select2.getSettings();
|
|
if (item.originalHeicFile && attachment.id) {
|
|
dispatch.addSideloadItem({
|
|
file: item.originalHeicFile,
|
|
batchId: v4_default(),
|
|
parentId: item.id,
|
|
additionalData: {
|
|
post: attachment.id,
|
|
image_size: "source_original",
|
|
convert_format: false
|
|
},
|
|
operations: [OperationType.Upload]
|
|
});
|
|
}
|
|
if (item.animatedGifFile && attachment.id) {
|
|
const outputFormat = settings.videoOutputFormat === "video/webm" ? "webm" : "mp4";
|
|
dispatch.addSideloadItem({
|
|
file: item.animatedGifFile,
|
|
batchId: v4_default(),
|
|
parentId: item.id,
|
|
additionalData: {
|
|
post: attachment.id,
|
|
image_size: "animated_video",
|
|
convert_format: false
|
|
},
|
|
operations: [
|
|
[
|
|
OperationType.TranscodeGif,
|
|
{
|
|
outputFormat
|
|
}
|
|
],
|
|
OperationType.Upload
|
|
]
|
|
});
|
|
}
|
|
let exifOrientation = attachment.exif_orientation || 1;
|
|
const sourceType = item.sourceFile.type;
|
|
const isHeifFamily = sourceType === "image/avif" || sourceType === "image/heif";
|
|
let needsClientRotation = false;
|
|
if (isHeifFamily) {
|
|
exifOrientation = getUnappliedExifOrientation(
|
|
await item.sourceFile.arrayBuffer()
|
|
);
|
|
needsClientRotation = exifOrientation !== 1;
|
|
}
|
|
let rotatedSource;
|
|
{
|
|
const needsRotation = exifOrientation !== 1 && !item.file.name.includes("-scaled");
|
|
if ((needsRotation || needsClientRotation) && attachment.id) {
|
|
try {
|
|
rotatedSource = await vipsRotateImage(
|
|
item.id,
|
|
item.sourceFile,
|
|
exifOrientation,
|
|
item.abortController?.signal
|
|
);
|
|
} catch {
|
|
console.warn(
|
|
"Failed to rotate image, continuing with thumbnails"
|
|
);
|
|
}
|
|
}
|
|
if (needsRotation && rotatedSource && attachment.id) {
|
|
dispatch.addSideloadItem({
|
|
file: rotatedSource,
|
|
batchId: v4_default(),
|
|
parentId: item.id,
|
|
additionalData: {
|
|
post: attachment.id,
|
|
image_size: "original",
|
|
convert_format: false
|
|
},
|
|
operations: [OperationType.Upload]
|
|
});
|
|
}
|
|
}
|
|
if (!item.parentId && attachment.missing_image_sizes && attachment.missing_image_sizes.length > 0) {
|
|
const allImageSizes = settings.allImageSizes || {};
|
|
const sizesToGenerate = attachment.missing_image_sizes;
|
|
const thumbnailSource = needsClientRotation && rotatedSource ? rotatedSource : item.sourceFile;
|
|
const file = attachment.filename ? renameFile(thumbnailSource, attachment.filename) : thumbnailSource;
|
|
const batchId = v4_default();
|
|
const isUltraHdr = ultraHdrItems.has(item.id);
|
|
const outputMimeType = attachment.image_output_format;
|
|
const interlaced = attachment.image_save_progressive ?? false;
|
|
const imageQuality = attachment.image_quality;
|
|
const fallbackQuality = settings.imageQuality ?? DEFAULT_OUTPUT_QUALITY;
|
|
const defaultQuality = typeof imageQuality?.default === "number" ? imageQuality.default / 100 : fallbackQuality;
|
|
const qualityForSize = (sizeName) => {
|
|
const sized = imageQuality?.sizes?.[sizeName];
|
|
if (typeof sized === "number") {
|
|
return sized / 100;
|
|
}
|
|
return defaultQuality;
|
|
};
|
|
let thumbnailTranscodeOperation = null;
|
|
if (!isUltraHdr && outputMimeType) {
|
|
thumbnailTranscodeOperation = await getTranscodeImageOperation(
|
|
thumbnailSource,
|
|
outputMimeType,
|
|
interlaced,
|
|
defaultQuality
|
|
);
|
|
}
|
|
const dimensionGroups = /* @__PURE__ */ new Map();
|
|
for (const name of sizesToGenerate) {
|
|
const imageSize = allImageSizes[name];
|
|
if (!imageSize) {
|
|
console.warn(
|
|
`Image size "${name}" not found in configuration`
|
|
);
|
|
continue;
|
|
}
|
|
const key = `${imageSize.width}x${imageSize.height}x${imageSize.crop}`;
|
|
const group = dimensionGroups.get(key);
|
|
if (group) {
|
|
group.push(name);
|
|
} else {
|
|
dimensionGroups.set(key, [name]);
|
|
}
|
|
}
|
|
for (const [, names] of dimensionGroups) {
|
|
const imageSize = allImageSizes[names[0]];
|
|
const sizeQuality = qualityForSize(names[0]);
|
|
const thumbnailOperations = [
|
|
[
|
|
OperationType.ResizeCrop,
|
|
{ resize: imageSize, quality: sizeQuality }
|
|
]
|
|
];
|
|
if (!isUltraHdr && thumbnailTranscodeOperation) {
|
|
thumbnailOperations.push([
|
|
thumbnailTranscodeOperation[0],
|
|
{
|
|
...thumbnailTranscodeOperation[1],
|
|
outputQuality: sizeQuality
|
|
}
|
|
]);
|
|
}
|
|
thumbnailOperations.push(OperationType.Upload);
|
|
const imageSizeParam = names.length === 1 ? names[0] : names;
|
|
dispatch.addSideloadItem({
|
|
file,
|
|
batchId,
|
|
parentId: item.id,
|
|
additionalData: {
|
|
// Sideloading does not use the parent post ID but the
|
|
// attachment ID as the image sizes need to be added to it.
|
|
post: attachment.id,
|
|
image_size: imageSizeParam,
|
|
convert_format: false
|
|
},
|
|
operations: thumbnailOperations
|
|
});
|
|
}
|
|
{
|
|
const { bigImageSizeThreshold } = settings;
|
|
if (bigImageSizeThreshold && attachment.id) {
|
|
const bitmap = await createImageBitmap(thumbnailSource);
|
|
const needsScaling = bitmap.width > bigImageSizeThreshold || bitmap.height > bigImageSizeThreshold;
|
|
bitmap.close();
|
|
if (needsScaling) {
|
|
const sourceForScaled = attachment.filename ? renameFile(thumbnailSource, attachment.filename) : thumbnailSource;
|
|
const scaledOperations = [
|
|
[
|
|
OperationType.ResizeCrop,
|
|
{
|
|
resize: {
|
|
width: bigImageSizeThreshold,
|
|
height: bigImageSizeThreshold
|
|
},
|
|
isThresholdResize: true,
|
|
quality: defaultQuality
|
|
}
|
|
]
|
|
];
|
|
if (!isUltraHdr && thumbnailTranscodeOperation) {
|
|
scaledOperations.push(
|
|
thumbnailTranscodeOperation
|
|
);
|
|
}
|
|
scaledOperations.push(OperationType.Upload);
|
|
dispatch.addSideloadItem({
|
|
file: sourceForScaled,
|
|
batchId,
|
|
parentId: item.id,
|
|
additionalData: {
|
|
post: attachment.id,
|
|
image_size: "scaled",
|
|
convert_format: false
|
|
},
|
|
operations: scaledOperations
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
dispatch.finishOperation(id, {});
|
|
};
|
|
}
|
|
function finalizeItem(id) {
|
|
return async ({ select: select2, dispatch }) => {
|
|
const item = select2.getItem(id);
|
|
if (!item) {
|
|
return;
|
|
}
|
|
const attachment = item.attachment;
|
|
const { mediaFinalize } = select2.getSettings();
|
|
const updates = {};
|
|
if (attachment?.id && mediaFinalize) {
|
|
try {
|
|
const updatedAttachment = await mediaFinalize(
|
|
attachment.id,
|
|
item.subSizes || []
|
|
);
|
|
if (updatedAttachment) {
|
|
updates.attachment = updatedAttachment;
|
|
}
|
|
} catch (error) {
|
|
console.warn("Media finalization failed:", error);
|
|
dispatch.cancelItem(
|
|
id,
|
|
new UploadError({
|
|
code: ErrorCode.MEDIA_FINALIZE_ERROR,
|
|
message: (0, import_i18n7.__)("Could not finalize the upload."),
|
|
file: item.file
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
dispatch.finishOperation(id, updates);
|
|
};
|
|
}
|
|
function revokeBlobUrls(id) {
|
|
return async ({ select: select2, dispatch }) => {
|
|
const blobUrls = select2.getBlobUrls(id);
|
|
for (const blobUrl of blobUrls) {
|
|
(0, import_blob.revokeBlobURL)(blobUrl);
|
|
}
|
|
dispatch({
|
|
type: Type.RevokeBlobUrls,
|
|
id
|
|
});
|
|
};
|
|
}
|
|
function updateItemProgress(id, progress) {
|
|
return async ({ dispatch }) => {
|
|
dispatch({
|
|
type: Type.UpdateProgress,
|
|
id,
|
|
progress
|
|
});
|
|
};
|
|
}
|
|
function updateSettings(settings) {
|
|
return {
|
|
type: Type.UpdateSettings,
|
|
settings
|
|
};
|
|
}
|
|
|
|
// packages/upload-media/build-module/lock-unlock.mjs
|
|
var import_private_apis = __toESM(require_private_apis(), 1);
|
|
var { lock, unlock } = (0, import_private_apis.__dangerousOptInToUnstableAPIsOnlyForCoreModules)(
|
|
"I acknowledge private features are not for use in themes or plugins and doing so will break in the next version of WordPress.",
|
|
"@wordpress/upload-media"
|
|
);
|
|
|
|
// packages/upload-media/build-module/store/index.mjs
|
|
var storeConfig = {
|
|
reducer: reducer_default,
|
|
selectors: selectors_exports,
|
|
actions: actions_exports
|
|
};
|
|
var store = (0, import_data.createReduxStore)(STORE_NAME, {
|
|
reducer: reducer_default,
|
|
selectors: selectors_exports,
|
|
actions: actions_exports
|
|
});
|
|
if (!(0, import_data.select)(store)) {
|
|
(0, import_data.register)(store);
|
|
}
|
|
unlock(store).registerPrivateActions(private_actions_exports);
|
|
unlock(store).registerPrivateSelectors(private_selectors_exports);
|
|
|
|
// packages/upload-media/build-module/components/provider/index.mjs
|
|
var import_element2 = __toESM(require_element(), 1);
|
|
var import_data3 = __toESM(require_data(), 1);
|
|
|
|
// packages/upload-media/build-module/components/provider/with-registry-provider.mjs
|
|
var import_element = __toESM(require_element(), 1);
|
|
var import_data2 = __toESM(require_data(), 1);
|
|
var import_compose = __toESM(require_compose(), 1);
|
|
var import_jsx_runtime = __toESM(require_jsx_runtime(), 1);
|
|
function getSubRegistry(subRegistries, registry, useSubRegistry) {
|
|
if (!useSubRegistry) {
|
|
return registry;
|
|
}
|
|
let subRegistry = subRegistries.get(registry);
|
|
if (!subRegistry) {
|
|
subRegistry = (0, import_data2.createRegistry)({}, registry);
|
|
subRegistry.registerStore(STORE_NAME, storeConfig);
|
|
subRegistries.set(registry, subRegistry);
|
|
}
|
|
return subRegistry;
|
|
}
|
|
var withRegistryProvider = (0, import_compose.createHigherOrderComponent)(
|
|
(WrappedComponent) => ({ useSubRegistry = true, ...props }) => {
|
|
const registry = (0, import_data2.useRegistry)();
|
|
const [subRegistries] = (0, import_element.useState)(() => /* @__PURE__ */ new WeakMap());
|
|
const subRegistry = getSubRegistry(
|
|
subRegistries,
|
|
registry,
|
|
useSubRegistry
|
|
);
|
|
if (subRegistry === registry) {
|
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WrappedComponent, { registry, ...props });
|
|
}
|
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_data2.RegistryProvider, { value: subRegistry, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WrappedComponent, { registry: subRegistry, ...props }) });
|
|
},
|
|
"withRegistryProvider"
|
|
);
|
|
var with_registry_provider_default = withRegistryProvider;
|
|
|
|
// packages/upload-media/build-module/components/provider/index.mjs
|
|
var import_jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
|
|
var MediaUploadProvider = with_registry_provider_default((props) => {
|
|
const { children, settings } = props;
|
|
const { updateSettings: updateSettings2 } = unlock((0, import_data3.useDispatch)(store));
|
|
(0, import_element2.useEffect)(() => {
|
|
updateSettings2(settings);
|
|
}, [settings, updateSettings2]);
|
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
|
|
});
|
|
var provider_default = MediaUploadProvider;
|
|
|
|
// packages/upload-media/build-module/error-messages.mjs
|
|
var import_i18n8 = __toESM(require_i18n(), 1);
|
|
function getErrorMessage(code, fileName) {
|
|
const messages = {
|
|
[ErrorCode.EMPTY_FILE]: {
|
|
title: (0, import_i18n8.__)("Empty file"),
|
|
description: (0, import_i18n8.sprintf)(
|
|
/* translators: %s: file name */
|
|
(0, import_i18n8.__)('"%s" is empty.'),
|
|
fileName
|
|
),
|
|
action: (0, import_i18n8.__)("Please choose a different file.")
|
|
},
|
|
[ErrorCode.SIZE_ABOVE_LIMIT]: {
|
|
title: (0, import_i18n8.__)("File too large"),
|
|
description: (0, import_i18n8.sprintf)(
|
|
/* translators: %s: file name */
|
|
(0, import_i18n8.__)('"%s" exceeds the maximum upload size.'),
|
|
fileName
|
|
),
|
|
action: (0, import_i18n8.__)("Please reduce the file size and try again.")
|
|
},
|
|
[ErrorCode.MIME_TYPE_NOT_SUPPORTED]: {
|
|
title: (0, import_i18n8.__)("Unsupported file type"),
|
|
description: (0, import_i18n8.sprintf)(
|
|
/* translators: %s: file name */
|
|
(0, import_i18n8.__)('"%s" is not a supported file type.'),
|
|
fileName
|
|
),
|
|
action: (0, import_i18n8.__)("Please upload a different file format.")
|
|
},
|
|
[ErrorCode.MIME_TYPE_NOT_ALLOWED_FOR_USER]: {
|
|
title: (0, import_i18n8.__)("File type not allowed"),
|
|
description: (0, import_i18n8.sprintf)(
|
|
/* translators: %s: file name */
|
|
(0, import_i18n8.__)('You are not allowed to upload "%s".'),
|
|
fileName
|
|
),
|
|
action: (0, import_i18n8.__)("Please contact your site administrator.")
|
|
},
|
|
[ErrorCode.HEIC_DECODE_ERROR]: {
|
|
title: (0, import_i18n8.__)("HEIC decode failed"),
|
|
description: (0, import_i18n8.sprintf)(
|
|
/* translators: %s: file name */
|
|
(0, import_i18n8.__)('Failed to decode HEIC file "%s".'),
|
|
fileName
|
|
),
|
|
action: getHeicConversionAdvice()
|
|
},
|
|
[ErrorCode.IMAGE_TRANSCODING_ERROR]: {
|
|
title: (0, import_i18n8.__)("Image processing failed"),
|
|
description: (0, import_i18n8.sprintf)(
|
|
/* translators: %s: file name */
|
|
(0, import_i18n8.__)('Failed to process "%s".'),
|
|
fileName
|
|
),
|
|
action: (0, import_i18n8.__)("The image may be corrupted. Try a different file.")
|
|
},
|
|
[ErrorCode.IMAGE_ROTATION_ERROR]: {
|
|
title: (0, import_i18n8.__)("Image rotation failed"),
|
|
description: (0, import_i18n8.sprintf)(
|
|
/* translators: %s: file name */
|
|
(0, import_i18n8.__)('Failed to rotate "%s".'),
|
|
fileName
|
|
),
|
|
action: (0, import_i18n8.__)("The image may be corrupted. Try a different file.")
|
|
},
|
|
[ErrorCode.MEDIA_TRANSCODING_ERROR]: {
|
|
title: (0, import_i18n8.__)("Media processing failed"),
|
|
description: (0, import_i18n8.sprintf)(
|
|
/* translators: %s: file name */
|
|
(0, import_i18n8.__)('Failed to convert "%s" to the target format.'),
|
|
fileName
|
|
),
|
|
action: (0, import_i18n8.__)("The file may be corrupted. Try a different file.")
|
|
},
|
|
[ErrorCode.MEDIA_FINALIZE_ERROR]: {
|
|
title: (0, import_i18n8.__)("Upload failed"),
|
|
description: (0, import_i18n8.sprintf)(
|
|
/* translators: %s: file name */
|
|
(0, import_i18n8.__)('Could not finalize the upload of "%s".'),
|
|
fileName
|
|
),
|
|
action: (0, import_i18n8.__)("Please try again.")
|
|
},
|
|
[ErrorCode.GENERAL]: {
|
|
title: (0, import_i18n8.__)("Upload failed"),
|
|
description: (0, import_i18n8.sprintf)(
|
|
/* translators: %s: file name */
|
|
(0, import_i18n8.__)('Failed to upload "%s".'),
|
|
fileName
|
|
),
|
|
action: (0, import_i18n8.__)("Please try again.")
|
|
}
|
|
};
|
|
return messages[code] || {
|
|
title: (0, import_i18n8.__)("Upload failed"),
|
|
description: (0, import_i18n8.sprintf)(
|
|
/* translators: %s: file name */
|
|
(0, import_i18n8.__)('Failed to upload "%s".'),
|
|
fileName
|
|
),
|
|
action: (0, import_i18n8.__)("Please try again.")
|
|
};
|
|
}
|
|
return __toCommonJS(index_exports);
|
|
})();
|
|
(window.wp ||= {}).uploadMedia = wp.uploadMedia;
|
|
})();
|