Page 1 of 1

Get image dimensions with a plugin

Posted: Fri Apr 03, 2020 1:38 am
by webform
I've used getimagesize($remoteimagefile) in a template and i works fine. But i've noticed that if i do have more than a couple of images the page slows down a lot.

I then tried to upload a plugin to "lib/plugins" but i can't seem to figure out how to access the output - I only get "Array"!

I have:

Code: Select all

{$imagesize = "{getjpegsize url=$my_url}"}
<pre>{$imagesize}</pre>
But it's only outputting "Array"!

I've also tried {$imagesize[0]} but then i only get "A".

So i'm clearly missing something but what?

My plugin file:

Code: Select all

// Retrieve JPEG width and height without downloading/reading entire image.
function smarty_cms_function_getjpegsize($params, &$smarty) {
    $img_loc = $params['url'];
    $handle = fopen($img_loc, "rb") or die("Invalid file stream.");
    $new_block = NULL;
    if(!feof($handle)) {
        $new_block = fread($handle, 32);
        $i = 0;
        if($new_block[$i]=="\xFF" && $new_block[$i+1]=="\xD8" && $new_block[$i+2]=="\xFF" && $new_block[$i+3]=="\xE0") {
            $i += 4;
            if($new_block[$i+2]=="\x4A" && $new_block[$i+3]=="\x46" && $new_block[$i+4]=="\x49" && $new_block[$i+5]=="\x46" && $new_block[$i+6]=="\x00") {
                // Read block size and skip ahead to begin cycling through blocks in search of SOF marker
                $block_size = unpack("H*", $new_block[$i] . $new_block[$i+1]);
                $block_size = hexdec($block_size[1]);
                while(!feof($handle)) {
                    $i += $block_size;
                    $new_block .= fread($handle, $block_size);
                    if($new_block[$i]=="\xFF") {
                        // New block detected, check for SOF marker
                        $sof_marker = array("\xC0", "\xC1", "\xC2", "\xC3", "\xC5", "\xC6", "\xC7", "\xC8", "\xC9", "\xCA", "\xCB", "\xCD", "\xCE", "\xCF");
                        if(in_array($new_block[$i+1], $sof_marker)) {
                            // SOF marker detected. Width and height information is contained in bytes 4-7 after this byte.
                            $size_data = $new_block[$i+2] . $new_block[$i+3] . $new_block[$i+4] . $new_block[$i+5] . $new_block[$i+6] . $new_block[$i+7] . $new_block[$i+8];
                            $unpacked = unpack("H*", $size_data);
                            $unpacked = $unpacked[1];
                            $height = hexdec($unpacked[6] . $unpacked[7] . $unpacked[8] . $unpacked[9]);
                            $width = hexdec($unpacked[10] . $unpacked[11] . $unpacked[12] . $unpacked[13]);
                            return array($width, $height);
                        } else {
                            // Skip block marker and read block size
                            $i += 2;
                            $block_size = unpack("H*", $new_block[$i] . $new_block[$i+1]);
                            $block_size = hexdec($block_size[1]);
                        }
                    } else {
                        return FALSE;
                    }
                }
            }
        }
    }
    return FALSE;
}

Re: Get image dimensions with a plugin

Posted: Fri Apr 03, 2020 9:29 am
by Rolf

Re: Get image dimensions with a plugin

Posted: Fri Apr 03, 2020 9:37 am
by webform
Thanks Rolf!

I've already tried that one but i'm fetching remote images and using getimagesize() seems to slow down the page considerably when fetching more than a handfull images.

So i found the above function that only get the image width and height from the remote image. But i'm stuck at getting the width value from the output.

If i do this:

Code: Select all

 {getjpegsize|print_r url=$my_url}
i get

Code: Select all

Array ( [0] => 1280 [1] => 720 ) 1
But if i do

Code: Select all

{$imagesize = "{getjpegsize url=$my_url}"}
{$imagesize[0]}
i get this output

Code: Select all

A
So how do i access the first value?

Re: Get image dimensions with a plugin

Posted: Fri Apr 03, 2020 12:41 pm
by Rolf
I use this method i.e. in the PhotoSwipe template for the Gallery module:
http://dev.cmsmadesimple.org/project/fi ... ckage-1341

Example with more than 100 photos in this template at:
https://music4allharen.nl/fotoalbum/202 ... smaking/67

Re: Get image dimensions with a plugin

Posted: Fri Apr 03, 2020 1:21 pm
by webform
But is the difference not in local and external image files?

I have 6 external image files and load time is ok. But do i increase the gallery to 12 images, then the page takes a long time to load (>10 sec.).

I'm trying to have a portfolio of YouTube videos, where i fetch the YouTube Poster Image depending which size is available (Either Maxresdefault or HQdefault).

And as far i can read on my searches around the web, is that getimagesize() is very slow with external image files, as it has to download the full image data to get width and height.

But maybe it's just in my template there is something wrong to increase load time so much?

My Portfolio template (In LISE):

Code: Select all

{if $items|@count > 0}{strip}
<!-- Portfolio -->
<div id="portfolio" class="grid-layout portfolio-3-columns" data-margin="0">
{foreach from=$items item=item name=portfolio}
    <!-- portfolio item -->
    <div class="portfolio-item{if $smarty.foreach.portfolio.first || $smarty.foreach.portfolio.iteration is div by 8} large-width{/if} img-zoom">
        <div class="portfolio-item-wrap">
            <div class="portfolio-image">
                <a data-lightbox="iframe" href="https://www.youtube.com/watch?v={$item->fielddefs.video_id.value}">
	                {if $item->fielddefs.poster_image.value}
	                <img src="{CGSmartImage src1=$item->fielddefs.poster_image->GetImagePath(true) src2=$item->fielddefs.poster_image->value filter_croptofit='800,450,c' noauto=1}" alt="">
		            {else}
		            {capture assign=maxresdefault}https://img.youtube.com/vi/{$item->fielddefs.video_id.value}/maxresdefault.jpg{/capture}
		            {capture assign=hqdefault}https://img.youtube.com/vi/{$item->fielddefs.video_id.value}/hqdefault.jpg{/capture}
		            {$imagesize = getimagesize($maxresdefault)}
		            {if $imagesize[0]}
		            {CGSmartImage src=$maxresdefault filter_croptofit='800,450,c' noauto=1}
		            {else}
		            {CGSmartImage src=$hqdefault filter_croptofit='480,270,c' noauto=1}
		            {/if}
		            {/if}
		         </a>
            </div>
            <div class="portfolio-description">
                <a class="btn btn-light btn-rounded" title="{$item->title} ({$imagesize[0]})" data-lightbox="iframe" href="https://www.youtube.com/watch?v={$item->fielddefs.video_id.value}">{$item->title}</a>
            </div>
        </div>
    </div>
    <!-- end: portfolio item -->  
{/foreach}
</div>
<!-- end: Portfolio -->
{/strip}{/if}

Re: Get image dimensions with a plugin

Posted: Fri Apr 03, 2020 2:28 pm
by Jo Morg
webform wrote:{$imagesize = "{getjpegsize url=$my_url}"}
<pre>{$imagesize}</pre>

But it's only outputting "Array"!
Looking at the source code it easy to get what is wrong: return array($width, $height);
The typical use of a smarty plugin expects the return to be printable and an array is not printable.
There are several options to get around this, one being imploding the var before returning it, other being assigning the return values to one or two smarty variables and then handling the vars on the template.

Also a note: try NOT to use capture on the templates as that is time expensive and probably one of the issues you are experiencing.
Instead of:

Code: Select all

{capture assign=maxresdefault}https://img.youtube.com/vi/{$item->fielddefs.video_id.value}/maxresdefault.jpg{/capture}
try using

Code: Select all

{$maxresdefault="https://img.youtube.com/vi/{$item->fielddefs.video_id.value}/maxresdefault.jpg"}
HTH

Re: Get image dimensions with a plugin

Posted: Fri Apr 03, 2020 3:06 pm
by DIGI3
Not an answer to your question, but I think the youtube api provides a method for obtaining the thumbnail dimensions. Might be more efficient, if so.

Re: Get image dimensions with a plugin

Posted: Fri Apr 03, 2020 4:19 pm
by velden
Also not an answer to your question but can you imagine how inefficient it is to calculate the dimensions of an remote image on every visit of a page?
Especially considering the odds the values change over time.

Further, not sure if I'm right, it seems you're trying to get two remote images for every item. Then get the size of both, if the first fails use the second.
Again the efficiency won't be optimal.

If I were you, I'd consider looking at the LISE api, add some fields if necessary and use a cmsms event - on saving the the LISE item - to trigger your logic and save the results with the LISE item itself.

Re: Get image dimensions with a plugin

Posted: Fri Apr 03, 2020 4:40 pm
by webform
Thanks all for some very valuable tips and insights.

I've tried something different based on your suggestions:

I've created an UDT for testing if the YouTube URL are available:

Code: Select all

$url = isset($params['url']) ? $params['url'] : '';
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, true);   
curl_setopt($ch, CURLOPT_NOBODY, true);    
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.4");
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_TIMEOUT,10);
curl_setopt($ch, CURLOPT_ENCODING, "gzip");
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
$output = curl_exec($ch);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $httpcode;
And my portfolio template i've removed "capture" and moved the fetching of the fallback image $hqdefault so it doesn't get fetched unless there is no $maxresdefault image.
I'm already experiencing a vast improvement in load time (although it probably could get better)

Code: Select all

{if $items|@count > 0}{strip}
<!-- Portfolio -->
<div id="portfolio" class="grid-layout portfolio-3-columns" data-margin="0">
{foreach from=$items item=item name=portfolio}
    <!-- portfolio item -->
    <div class="portfolio-item{if $smarty.foreach.portfolio.first || $smarty.foreach.portfolio.iteration is div by 8} large-width{/if} img-zoom">
        <div class="portfolio-item-wrap">
            <div class="portfolio-image">
                <a data-lightbox="iframe" href="https://www.youtube.com/watch?v={$item->fielddefs.video_id.value}">
	                {if $item->fielddefs.poster_image.value}
	                <img src="{CGSmartImage src1=$item->fielddefs.poster_image->GetImagePath(true) src2=$item->fielddefs.poster_image->value filter_croptofit='800,450,c' noauto=1}" alt="">
		            {else}
		            {$maxresdefault="https://img.youtube.com/vi/{$item->fielddefs.video_id.value}/maxresdefault.jpg"}
		            {$check="{check_404 url=$maxresdefault}"}
		            {if $check == '200'}
		            {CGSmartImage src=$maxresdefault filter_croptofit='800,450,c' noauto=1}
		            {else}
		            {$hqdefault="https://img.youtube.com/vi/{$item->fielddefs.video_id.value}/hqdefault.jpg"}
		            {CGSmartImage src=$hqdefault filter_croptofit='480,270,c' noauto=1}
		            {/if}
		            {/if}
		         </a>
            </div>
            <div class="portfolio-description">
                <a class="btn btn-light btn-rounded" title="{$item->title}" data-lightbox="iframe" href="//www.youtube.com/watch?v={$item->fielddefs.video_id.value}">{$item->title}</a>
            </div>
        </div>
    </div>
    <!-- end: portfolio item -->  
{/foreach}
</div>
<!-- end: Portfolio -->
{/strip}{/if}

Re: Get image dimensions with a plugin

Posted: Fri Apr 03, 2020 4:50 pm
by velden
I think if you do the checks for the two images on the saving of the LISE item you will be good.
Then you only need to check once which file to use.

CGSmartImage will do the caching of the cropped image for you.

If you still want to do the check from within the template, I just found this article about checking the existence of a file without actually downloading it.
https://thisinterestsme.com/check-see-h ... xists-php/

I can't see if you're already using this. But remember, it still has to do the request etc. So better would be to do it once, set and forget.

Re: Get image dimensions with a plugin

Posted: Fri Apr 03, 2020 5:11 pm
by webform
Thanks!

Yes, i'm doing a http header check, to see if the image exist and load the correct image accordingly.

You saying i could do this already at the creation of the LISE item?

Re: Get image dimensions with a plugin

Posted: Fri Apr 03, 2020 5:26 pm
by velden
I think it's possible yes.

LISE has a useful API and you can connect a UDT which does the checks to a LISE event (Extensions > Event Manager).

You will need some php knowledge and some knowledge of the LISE api.

But check the help of the LISE PreItemSave event

Code: Select all

//use for pre-save item event

$item = $params['item_object'];

...do your logic here---

$item->YourFieldAliasHere = 'YourValueHere';
Think that shoud do it.

Re: Get image dimensions with a plugin

Posted: Fri Apr 03, 2020 5:29 pm
by webform
Thanks! I'll look into it as it maybe also could help some more on the load speed of the page.

Re: Get image dimensions with a plugin

Posted: Sat Apr 04, 2020 8:04 pm
by webform
Thanks for all the suggestions, feedback and help.

I've got a working solution now with only a 1/3 of previous load time.

Here is what i did (if it's to any use of others):

My UDT (remote_youtube_poster_image):

Code: Select all

$item = $params['item_object'];

// Remote image URL
$url = 'https://img.youtube.com/vi/'.$item->video_id.'/maxresdefault.jpg';
// Remote fallback image URL
$fallbackurl = 'https://img.youtube.com/vi/'.$item->video_id.'/hqdefault.jpg';

// Save path to uploads folder
$path = $_SERVER['DOCUMENT_ROOT'].'/uploads/images/portfolio/img-'.$item->video_id.'.jpg';

// Image path to LISE field
$img = '/uploads/images/portfolio/img-'.$item->video_id.'.jpg';

//Check Availability
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, true);   
curl_setopt($ch, CURLOPT_NOBODY, true);    
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.4");
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_TIMEOUT,10);
curl_setopt($ch, CURLOPT_ENCODING, "gzip");
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
$output = curl_exec($ch);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($httpcode == '200') {

// Save maxresdefault image
$ch = curl_init($url);
$fp = fopen($path, 'wb');
curl_setopt($ch, CURLOPT_FILE, $fp);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch);
curl_close($ch);
fclose($fp);

} else {

// Save hqdefault image
$ch = curl_init($fallbackurl);
$fp = fopen($path, 'wb');
curl_setopt($ch, CURLOPT_FILE, $fp);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch);
curl_close($ch);
fclose($fp);

}

$item->poster_image_youtube = $img;
The UDT i connected to my LISE PreItemSave in Event Manager.

My LISE Summary Template:

Code: Select all

{if $items|@count > 0}{strip}
<!-- Portfolio -->
<div id="portfolio" class="grid-layout portfolio-3-columns" data-margin="0">
{foreach from=$items item=item name=portfolio}
    <!-- portfolio item -->
    <div class="portfolio-item{if $smarty.foreach.portfolio.first || $smarty.foreach.portfolio.iteration is div by 8} large-width{/if} img-zoom">
        <div class="portfolio-item-wrap">
            <div class="portfolio-image">
                <a data-lightbox="iframe" href="https://www.youtube.com/watch?v={$item->fielddefs.video_id.value}">
	                {if $item->fielddefs.poster_image.value}
	                <img src="{CGSmartImage src1=$item->fielddefs.poster_image->GetImagePath(true) src2=$item->fielddefs.poster_image->value filter_croptofit='800,450,c' noauto=1}" alt="">
		            {else}
		            {$posterimg = $item->fielddefs.poster_image_youtube.value}
		            {$imagesize = getimagesize("{root_url}{$posterimg}")}
		            {if $imagesize[0] > '720'}
		            {CGSmartImage src=$posterimg filter_croptofit='800,450,c' noauto=1}
		            {else}
		            {CGSmartImage src=$posterimg filter_croptofit='480,270,c' noauto=1}
		            {/if}
		            {/if}
		         </a>
            </div>
            <div class="portfolio-description">
                <a class="btn btn-light btn-rounded" title="{$item->title}" data-lightbox="iframe" href="//www.youtube.com/watch?v={$item->fielddefs.video_id.value}">{$item->title}</a>
            </div>
        </div>
    </div>
    <!-- end: portfolio item -->  
{/foreach}
</div>
<!-- end: Portfolio -->
{/strip}{/if}