Piwigo Automatic Thumbnail Creation

Piwigo Automatic Thumbnail Creation

April 10, 2021

Piwigo

Piwigo is a great application for storing your online photo galleries and sharing them with friends and family. With a bit of setup work it can be a great replacement for Google Photos that is actually much better in many important ways. Such as you actually retain ownership of your own photos! Yet, one feature has been missing from Piwigo for some time now: the ability to automatically generate the thumbnails for new pictures as they are uploaded to the website. There are numerous people asking for this and a few suggested options appear here and there online but the proposed solutions always had some problem which made think “there must be a better way”.

The Problem

Like I said, Piwigo is great and I am not even using all of the capabilities. I have new photos from my mobile phone automatically sent to Piwigo where they are automatically added to an album of my choice. However, the first time I visit this album from the browser, each of the 15 pictures (the default page size) would take 4-5 seconds to generate the thumbnails and would peg the CPU of my very modest VPS to 100% for at least 1 to 2 minutes. I can’t expect a user to wait that long before they click something else and send more page requests to the server until it turned into a death spiral as the poor little VM tried to keep up.

Design Goals

My goals where this:

  1. As I upload new pictures to the VM I want to also generate the thumbnails so that the website performance would not bog down. The thumbnails would be generated offline, while no one is waiting and distributed over time as new pictures are added.
  2. I also want to minimize the chance of the solution breaking whenever I updated Piwigo, so I can continue to install the latest security updates without breaking my solution.
  3. Finally, I want to make sure the exact same Piwigo code is run to create the thumbnails as when I manually visit each page so I could be assured that there would be no difference between the thumbnails I generate and those that would be generated through the slow, painful way. This way they would be guaranteed to look the same and would not change the look of the website.
  4. I need this feature to be available as an API so that I can call it after I have just called the API to add a new picture to the website.

How Piwigo Works

Some things I noticed about Piwigo from poking around in the code that make this fairly easy to do.

  1. When Piwigo pulls an image (and even a thumbnail) it calls a program name i.php. So all the hyperlinks to the images in the galleries are references to this PHP script.
  2. If the thumbnail exists then i.php simply returns the preexisting image, otherwise it generates it before returning the image.
  3. There is no password or authentication around calling this program directly. Instead each picture’s filename is modified by appending a hash to the filename so it would be extremely difficult to determine any filename directly.
  4. Thumbnails all match the original photo’s filename with a suffix added to indicate which of several thumbnail sizes to return.
  5. So to ‘trick’ Piwigo into generating the thumbnail for us, we really just need to call this PHP script via an HTTP GET request and ask for each of the thumbnails we want to generate and Piwigo will oblige.
  6. When you upload a new photo to Piwigo you will get the image_id of the newly added photo. This is the primary key of the new row added to the image table which will tell us the relative path to the new photo on the remote server, including the hash appended to the filename.

The Design

As I call the built-in Piwigo API (from Python) to add a new photo I just received from my mobile phone, I will retain the image_id of the newly added photo. I will then use that image_id to call a new PHP script on the Piwigo server that will: 1) determine the path of the new file, and 2) for each thumbnail size I want pre-built it will call i.php and ask for that particular thumbnail size using the filename prefixes that Piwigo uses. The end result is a new PHP script I call pwg_generate_thumbnails.php that I put in my Piwigo home directory. And for convenience with Python I will return JSON to tell me if it was successful or not. So to generate a thumbnail I simply call:

https://{piwigo.domain.com}/pwg_generate_thumbnails.php?id={image_id}

The code for this new PHP script is below.

The Code

<?php
/*
	A script to resize an image into prebuilt thumbnails.
*/

// The thumb sizes to auto generate
$thumbs = [ '2s', 'me', 'sm', 'sq', 'th' ];


// Call Piwigo for the image URL to be generated
function resize_img_url( $url )
{
    $message = NULL;

    $ch = curl_init( $url );
    curl_setopt( $ch, CURLOPT_HEADER, false );
    curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
    curl_exec( $ch );

    if ( curl_error( $ch )) {
        $message = curl_error( $ch );
    } 
    curl_close( $ch );

    return( $message );
}


function return_json( $stat, $message )
{
    $response = [ 'stat' => $stat ];

    if ($message != NULL)
    {
        $response['message'] = $message;
    }

    header('Content-Type: application/json');
    die( json_encode($response) );
}


// An image id is required to run this script
if (!isset($_REQUEST['id']))
{
    return_json( 'error', 'Missing id' );
}


// Determine the domain name. Assumes Piwigo is located here.
$domain = $_SERVER['SERVER_NAME'];

// Connect to the database
define('PHPWG_ROOT_PATH','./');
include_once( PHPWG_ROOT_PATH.'include/common.inc.php' );
include(PHPWG_ROOT_PATH.'include/section_init.inc.php');

// Retrieve path to the image from the database
$id = $_REQUEST['id'];
$query = 'select path from piwigo_images where id=' . urlencode($id) . ';';
$row = pwg_db_fetch_assoc( pwg_query( $query ) );
$path = $row['path'];

if ($path == '')
{
    return_json( 'error', 'Id not found' );
}

// Remove leading dot
$path = ltrim( $path, '.' );
$parts = explode( '.', $path );

// Generate thumbs
$url_prefix = 'https://' . $domain . '/i.php?' . $parts[0] . '-';
$url_suffix = '.' . $parts[1];
$rc = true;

foreach ($thumbs as $thumb_size)
{
    $url = $url_prefix . $thumb_size . $url_suffix;
    $message = resize_img_url(  $url );
    if ( $message != NULL )
    {
        return_json( 'error', $message );
    }
}

return_json( 'ok', NULL );

?>