Skip to content
Draft
3 changes: 3 additions & 0 deletions inc/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,8 @@ function filter_rest_index( WP_REST_Response $response ): WP_REST_Response {
/** This filter is documented in wp-includes/class-wp-image-editor-imagick.php */
$gif_interlaced = apply_filters( 'image_save_progressive', false, 'image/gif' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound

$supports_heic = wp_image_editor_supports( [ 'mime_type' => 'image/heic' ] );

$response->data['image_sizes'] = get_all_image_sizes();
$response->data['image_size_threshold'] = $image_size_threshold;
$response->data['video_size_threshold'] = $video_size_threshold;
Expand All @@ -716,6 +718,7 @@ function filter_rest_index( WP_REST_Response $response ): WP_REST_Response {
$response->data['png_interlaced'] = $png_interlaced;
$response->data['gif_interlaced'] = $gif_interlaced;
$response->data['media_source_terms'] = $media_source_terms;
$response->data['supports_heic'] = $supports_heic;

return $response;
}
Expand Down
2 changes: 2 additions & 0 deletions packages/editor/src/init/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ const siteDataFields: Array< keyof RestBaseRecord > = [
'png_interlaced',
'gif_interlaced',
'image_sizes',
'supports_heic',
];

// Initialize default settings as soon as base data is available.
Expand Down Expand Up @@ -157,6 +158,7 @@ const unsubscribeCoreStore = subscribe( () => {

void dispatch( uploadStore ).updateSettings( {
imageSizes: siteData.image_sizes,
supportsHeic: siteData.supports_heic,
} );

unsubscribeCoreStore();
Expand Down
1 change: 1 addition & 0 deletions packages/editor/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ export type RestBaseRecord = {
gif_interlaced: boolean;
image_sizes: Record< string, ImageSizeCrop >;
media_source_terms: Record< MediaSourceTerm, number >;
supports_heic: boolean;
};

export type BulkOptimizationAttachmentData = {
Expand Down
87 changes: 82 additions & 5 deletions packages/upload-media/src/store/private-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,16 @@ const isSafari = Boolean(
! window.navigator.userAgent.includes( 'Chromium' )
);

/**
* Checks if a file is a HEIC/HEIF image.
*
* @param file File to check.
* @return Whether the file is a HEIC/HEIF image.
*/
function isHeicFile( file: File ): boolean {
return file.type === 'image/heic' || file.type === 'image/heif';
}

type ActionCreators = {
cancelItem: typeof cancelItem;
addItem: typeof addItem;
Expand Down Expand Up @@ -236,7 +246,7 @@ export function addItem( {
abortController,
operations,
}: AddItemArgs ) {
return async ( { dispatch, registry }: ThunkArgs ) => {
return async ( { dispatch, registry, select }: ThunkArgs ) => {
const thumbnailGeneration: ThumbnailGeneration = registry
.select( preferencesStore )
.get( PREFERENCES_NAME, 'thumbnailGeneration' );
Expand All @@ -259,6 +269,12 @@ export function addItem( {
} );
}

// Check if this is a HEIC file that the server can convert.
// If so, we don't want the server to generate thumbnails - we'll do it client-side.
const supportsHeicOnServer = select.getSettings().supportsHeic;
const shouldDisableServerThumbnails =
isHeicFile( file ) && supportsHeicOnServer;

dispatch< AddAction >( {
type: Type.Add,
item: {
Expand All @@ -271,7 +287,9 @@ export function addItem( {
url: blobUrl,
},
additionalData: {
generate_sub_sizes: 'server' === thumbnailGeneration,
generate_sub_sizes: shouldDisableServerThumbnails
? false
: 'server' === thumbnailGeneration,
convert_format: false,
...additionalData,
},
Expand Down Expand Up @@ -876,10 +894,18 @@ export function prepareItem( id: QueueItemId ) {

let uploadOriginalImage = false;

const supportsHeicOnServer = select.getSettings().supportsHeic;
const serverWillConvertHeic =
isHeif && convertUnsafe && supportsHeicOnServer;

if ( convertUnsafe ) {
if ( isHeif ) {
operations.push( OperationType.TranscodeHeif );
uploadOriginalImage = true;
// If server supports HEIC, upload the file directly and let the server handle conversion.
// Otherwise, do client-side conversion.
if ( ! supportsHeicOnServer ) {
operations.push( OperationType.TranscodeHeif );
uploadOriginalImage = true;
}
} else if ( ! isWebSafe ) {
operations.push( [
OperationType.TranscodeImage,
Expand All @@ -894,6 +920,16 @@ export function prepareItem( id: QueueItemId ) {
}
}

// For HEIC files that will be converted server-side,
// upload first, then dynamically add operations to fetch the converted file
// and do all processing on it.
if ( serverWillConvertHeic ) {
operations.push( OperationType.Upload );
// The rest of the operations (FetchRemoteFile, GenerateMetadata, GenerateCaptions, ThumbnailGeneration)
// will be added dynamically in uploadItem() after the upload completes.
break;
}

const imageSizeThreshold: number = registry
.select( preferencesStore )
.get( PREFERENCES_NAME, 'bigImageSizeThreshold' );
Expand Down Expand Up @@ -1894,7 +1930,7 @@ export function resizeCropItem( id: QueueItemId, args?: ResizeCropItemArgs ) {
* @param id Item ID.
*/
export function uploadItem( id: QueueItemId ) {
return async ( { select, dispatch }: ThunkArgs ) => {
return async ( { select, dispatch, registry }: ThunkArgs ) => {
const item = select.getItem( id ) as QueueItem;

const startTime = performance.now();
Expand Down Expand Up @@ -1940,6 +1976,47 @@ export function uploadItem( id: QueueItemId ) {
}
}

// If this was a HEIC file uploaded to a server that supports HEIC conversion,
// we need to fetch the converted JPEG file back and then do all processing on it.
const supportsHeicOnServer = select.getSettings().supportsHeic;

if (
isHeicFile( item.sourceFile ) &&
supportsHeicOnServer &&
attachment.url
) {
// Add operations to fetch the converted JPEG and then process it.
const fileName =
attachment.mexp_filename || item.sourceFile.name;

const operations: Operation[] = [
[
OperationType.FetchRemoteFile,
{
url: attachment.url,
fileName,
},
],
OperationType.GenerateMetadata,
];

const useAi: boolean = registry
.select( preferencesStore )
.get( PREFERENCES_NAME, 'useAi' );

if ( useAi ) {
operations.push( OperationType.GenerateCaptions );
}

operations.push( OperationType.ThumbnailGeneration );

dispatch< AddOperationsAction >( {
type: Type.AddOperations,
id,
operations,
} );
}

dispatch.finishOperation( id, {
attachment,
timings,
Expand Down
1 change: 1 addition & 0 deletions packages/upload-media/src/store/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export interface Settings {
mediaUpload: ( args: UploadMediaArgs ) => void;
mediaSideload: ( args: SideloadMediaArgs ) => void;
imageSizes: Record< string, ImageSizeCrop >;
supportsHeic?: boolean;
}

// Must match the Attachment type from the media-utils package.
Expand Down
Loading
Loading