Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PDF thumbnails to WEBP/AVIF #1766

Open
1ucay opened this issue Dec 20, 2024 · 7 comments · May be fixed by #1868
Open

PDF thumbnails to WEBP/AVIF #1766

1ucay opened this issue Dec 20, 2024 · 7 comments · May be fixed by #1868
Labels
[Plugin] Modern Image Formats Issues for the Modern Image Formats plugin (formerly WebP Uploads) [Type] Enhancement A suggestion for improvement of an existing feature

Comments

@1ucay
Copy link

1ucay commented Dec 20, 2024

Dear all,

if you upload PDF, in metadata sizes we have array with jpgs.

I tried fix with

add_filter( 'webp_uploads_upload_image_mime_transforms', 'core_webp_uploads_upload_image_mime_transforms', 9, 2 );
function core_webp_uploads_upload_image_mime_transforms( $default_transforms ) {

    $mimes = wp_get_mime_types();

    $output_format = webp_uploads_mime_type_supported( 'image/avif' ) ? webp_uploads_get_image_output_format() : 'webp';
    $transforms    = webp_uploads_is_fallback_enabled() ? array( 'image/jpeg', 'image/' . $output_format ) : array( 'image/' . $output_format );

    foreach( $mimes as $k => $v ) {
        if ( ! isset( $default_transforms[ $v ] ) )
            $default_transforms[ $v ] = $transforms;
    }

    return $default_transforms;
}

If you upload PSD with mime image/vnd.adobe.photoshop and genereate sizes with some custom code (imagemagick support PSD) like PDF does

add_filter( 'wp_generate_attachment_metadata', 'core_wp_generate_attachment_metadata', 9, 2 );

it working ok. I have nice jpg and avif in metadata.

But, different situation is with PDF mime application/pdf. It is blocked by helper.php line 145

$image_path = wp_get_original_image_path( $attachment_id );

and inside function is condition

  if ( ! wp_attachment_is_image( $attachment_id ) ) {

We dont have any filter skip this check, so PDF avif is not working. Best would be add filter to "wp_attachment_is" and override.

Im working on LibreOffice (exec soffice), audiowave (exec https://github.com/bbc/audiowaveform ) , PSD / AI (imagemagick) thumbnailer.
https://core.trac.wordpress.org/ticket/62712

@github-project-automation github-project-automation bot moved this to Not Started/Backlog 📆 in WP Performance 2024 Dec 20, 2024
@westonruter westonruter added the [Plugin] Modern Image Formats Issues for the Modern Image Formats plugin (formerly WebP Uploads) label Dec 20, 2024
@swissspidy swissspidy added the [Type] Enhancement A suggestion for improvement of an existing feature label Jan 14, 2025
@AhmarZaidi
Copy link
Contributor

Hey @1ucay

If I understand correctly, the issue is that PDF uploads should generate previews, but generation is blocked because wp_get_original_image_path() checks wp_attachment_is_image(), which returns false for PDFs.

I did try to replicate the issue and did not get pdf previews but I was unable to confirm if that is due to ! wp_attachment_is_image( $attachment_id ).

If you could revisit the issue once more and provide replication instructions, it would be very helpful.

@1ucay
Copy link
Author

1ucay commented Feb 8, 2025

Dear @AhmarZaidi
yes, exactly, for AVIF previews it is blocked by wp_attachment_is_image, if mime type of file is not image/
Would be possible add some apply_filters?

@1ucay
Copy link
Author

1ucay commented Feb 8, 2025

you can add application/pdf over webp_uploads_upload_image_mime_transforms

@AhmarZaidi
Copy link
Contributor

@1ucay Thanks for the confirmation.

There already exist a filter webp_uploads_upload_image_mime_transforms which we can use to add application/pdf mimetype to transforms like so:

add_filter( 'webp_uploads_upload_image_mime_transforms', function( $transforms ) {
    $transforms['application/pdf'] = array( 'image/jpeg' );
    return $transforms;
});

A possible solution could be to add application/pdf mimetype to the default list and use get_attached_file() function for getting the original pdf file path if mime type is application/pdf to bypass $image_path = wp_get_original_image_path( $attachment_id );


Now, it doesn't work when I have the dev environment running using npm run wp-env start and shows security policy error:

WP_Error Object
(
    [errors] => Array
    (
        [invalid_image] => Array
        (
            [0] => attempt to perform an operation not allowed by the security policy `PDF' @ error/constitute.c/IsCoderAuthorized/426
        )
    )

    [error_data] => Array
    (
        [invalid_image] => /var/www/html/wp-content/uploads/2025/02/test.pdf
    )

    [additional_data:protected] => Array ()
)

This appears to be an issue with ImageMagick policy used in the docker WordPress environment. Solutions suggest changing the ImageMagick policy

Image

However, looking into the core file reveal that it should work so I tested the solution on other setups like localWP (by separately adding Modern Image Formats plugin) and it seems to work fine.

Screen.Recording.2025-02-12.at.8.48.56.AM.mov

Feel free to let me know if I've understood or implemented something incorrectly.

@1ucay
Copy link
Author

1ucay commented Feb 12, 2025

I have some workaround for this. but it so hacky ;) I can upload even PSD file (image/vnd.adobe.photoshop), which is by ImageMagick supported format. It will create thumbnail sizes (jpg,avif), I can even work in WP image editor (cropping etc). But it will be separate plugin.

Best solution for every usecase would be add filter to wp_attachment_is_image. So user can override function.

You have to change post_mime_type in WP Post object after filter "webp_uploads_pre_generate_additional_image_source"

<?php

if ( ! defined( 'ABSPATH' ) ) {
    die( 'Cannot access pages directly.' );
}

function core_get_image_viewable_formats() {
    return array( 'image/jpeg', 'image/png', 'image/apng', 'image/gif', 'image/bmp', 'image/webp', 'image/avif' );
}

// important hook with priority 10!! otherwise not working second image edit
add_filter( 'wp_update_attachment_metadata', 'core_thumbnail_generator_wp_update_attachment_metadata', 10, 2 );
function core_thumbnail_generator_wp_update_attachment_metadata( $meta, $attachment_id ) {

    if ( ! did_action( 'core_thumbnail_generator_alter_post_mime_type' ) )
        return $meta;

    $old_meta = wp_get_attachment_metadata( $attachment_id );

    $meta['sizes']['full'] = array(
        'file'      => wp_basename( apply_filters( 'image_make_intermediate_size', $meta['file'] ) ),
        'width'     => $meta['width'],
        'height'    => $meta['height'],
        'mime-type' => $meta['sizes']['full']['mime-type'],
        'filesize'  => $meta['filesize']
    );

    $meta['width']    = $old_meta['width'];
    $meta['height']   = $old_meta['height'];
    $meta['filesize'] = $old_meta['filesize'];

    return $meta;
}

// fix for AVIF / WEBP for Modern Image Formats
add_filter( 'webp_uploads_upload_image_mime_transforms', function( $transforms ) {
    $transforms['application/pdf'] = array( 'image/jpeg' );
    return $transforms;
});

add_filter( 'webp_uploads_pre_generate_additional_image_source', 'core_thumbnail_generator_webp_uploads_pre_generate_additional_image_source', 10, 5 );
function core_thumbnail_generator_webp_uploads_pre_generate_additional_image_source( $return, $attachment_id, $image_size, $size_data, $mime ) {

    core_thumbnail_generator_alter_post_mime_type( $attachment_id, false );

    return;
}

add_filter( 'wp_update_attachment_metadata', 'core_thumbnail_generator_wp_update_attachment_metadata_webp', 11, 2 );
function core_thumbnail_generator_wp_update_attachment_metadata_webp( $meta, $attachment_id ) {

    if ( did_filter( 'webp_uploads_upload_image_mime_transforms' ) && isset( $meta['sources'] ) && isset( $meta['sizes'] ) && isset( $meta['sizes']['full'] ) ) {

        // revert alter mime
        $attachment = wp_cache_get( $attachment_id, 'posts' );

        if ( isset( $attachment->post_mime_type_backup ) ) {

            $attachment->post_mime_type = $attachment->post_mime_type_backup;
            unset( $attachment->post_mime_type_backup );
            wp_cache_set( $attachment->ID, $attachment, 'posts' );

            if ( isset( $meta['sources'][ $attachment->post_mime_type ] ) ) {

                unset( $meta['sources'][ $attachment->post_mime_type ] );

                $meta['sources'][ $meta['sizes']['full']['mime-type'] ] = array(
                    'file'     => $meta['sizes']['full']['file'],
                    'filesize' => $meta['sizes']['full']['filesize'],
                );

            }

            remove_filter( 'get_attached_file', 'core_thumbnail_generator_get_attached_file', 10, 2 );

        }
    }

    return $meta;
}

function core_thumbnail_generator_update_attached_file( $file, $attachment_id ) {

    add_filter( 'update_post_metadata', function( $return, $object_id, $meta_key, $meta_value, $prev_value ) {

        remove_filter( current_filter(), __FUNCTION__ );

        if ( '_wp_attached_file' === $meta_key )
            return true;

    }, 10, 5 );

    return get_post_meta( $attachment_id, '_wp_attached_file', true );

}

function core_thumbnail_generator_alter_post_mime_type( $attachment_id = null ) {

    $post = get_post( $attachment_id );

    $attachment = wp_cache_get( $attachment_id, 'posts' );

    if ( ! $attachment || is_a( $attachment, 'WP_Post' ) )
        return;

    if ( in_array( $attachment->post_mime_type, core_get_image_viewable_formats() ) )
        return;

    $sizes = image_get_intermediate_size( $attachment->ID, 'full' );
    if ( ! $sizes )
        return;

    $attachment->post_mime_type_backup = $attachment->post_mime_type;
    $attachment->post_mime_type = $sizes['mime-type'];

    wp_cache_set( $attachment->ID, $attachment, 'posts' );

    add_filter( 'get_attached_file', 'core_thumbnail_generator_get_attached_file', 10, 2 );
    
    do_action( 'core_thumbnail_generator_alter_post_mime_type' );
}

function core_thumbnail_generator_get_attached_file( $filepath, $attachment_id ) {

    if ( $full_size = image_get_intermediate_size( $attachment_id, 'full' ) )
        $filepath = path_join( dirname( $filepath ), $full_size['file'] );

    return $filepath;
}


// bug wordpress-develop/src/wp-includes/media.php @4613, psd is image, but cannot be editable
add_filter( 'wp_prepare_attachment_for_js', 'core_thumbnail_generator_wp_prepare_attachment_for_js', 10, 3 );
function core_thumbnail_generator_wp_prepare_attachment_for_js( $response, $attachment, $meta ) {

    if ( ! empty( $meta['sizes'] ) && ( isset( $attachment->post_mime_type_backup ) || ! in_array( $attachment->post_mime_type, core_get_image_viewable_formats() ) ) ) {

        $attachment_url = wp_get_attachment_url( $attachment->ID );
        $base_url       = str_replace( wp_basename( $attachment_url ), '', $attachment_url );

        if ( $full_size = image_get_intermediate_size( $attachment->ID, 'full' ) ) {
            $response['sizes']['full'] = array(
                'url'         => $base_url . $full_size['file'] . ( str_contains( wp_get_referer(), '&mode=edit' ) ? '?' . time() . rand( 100, 999 ) : '' ),
                'height'      => $full_size['height'],
                'width'       => $full_size['width'],
                'orientation' => $full_size['height'] > $full_size['width'] ? 'portrait' : 'landscape',
            );
            $response['editable'] = true;
            return $response;
        }
    }


    return $response;
}

@AhmarZaidi AhmarZaidi linked a pull request Feb 12, 2025 that will close this issue
@AhmarZaidi
Copy link
Contributor

AhmarZaidi commented Feb 12, 2025

I've created a draft PR with the previous approach before noticing there's an update 😅:

A possible solution could be to add application/pdf mimetype to the default list and use get_attached_file() function for getting the original pdf file path if mime type is application/pdf to bypass $image_path = wp_get_original_image_path( $attachment_id );

Will be reviewing and testing the new approach:

Best solution for every usecase would be add filter to wp_attachment_is_image. So user can override function.

@1ucay
Copy link
Author

1ucay commented Feb 12, 2025

Nice, thank you, but it is solution only for PDF :)
What about, if I have thumbnail for different type of document, for example docx, waveform of mp3 etc?

My proposal

$image_path = in_array( get_post_mime_type( $attachment_id ), array( 'image/jpeg', 'image/webp', 'image/avif', 'image/png' ) ) ? wp_get_original_image_path( $attachment_id ) : get_attached_file( $attachment_id );

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Plugin] Modern Image Formats Issues for the Modern Image Formats plugin (formerly WebP Uploads) [Type] Enhancement A suggestion for improvement of an existing feature
Projects
Status: Not Started/Backlog 📆
Development

Successfully merging a pull request may close this issue.

4 participants