Alan M. Alan M. - 3 months ago 34
PHP Question

Using PHP getimagesize and imagecreate with Google App Engine and Google Cloud Storage

I want to use PHP in GAE to upload file image files.

Before storing, I want to convert each file to JPEG and reduce it to thumbnail quality.

Using the following code (which fully works in a normal PHP environment, less the bucket-specific adjustments), I am able to receive the uploads and determine the temporary file name and location, but getimagesize produces an error when attempting to access the CloudStorage.

$bucket = CloudStorageTools::getDefaultGoogleStorageBucketName();
$bucketPath = "gs://" . $bucket . "/" . $_SERVER["REQUEST_ID_HASH"] . "/";
$counter = 0;

foreach($_FILES["file"]["name"] as $idx => $tempFile) {
$counter++;
$sourceFile = $bucketPath . $tempFile;

syslog(LOG_DEBUG, $sourceFile);

$photoInfo = getimagesize($sourceFile);
if ($photoInfo["mime"] == "image/jpeg") {
$photoImage = imagecreatefromjpeg($sourceFile);
$valid = true;
}
elseif ($photoInfo["mime"] == "image/gif") {
$photoImage = imagecreatefromgif($sourceFile);
$valid = true;
}
elseif ($photoInfo["mime"] == "image/png") {
$photoImage = imagecreatefrompng($sourceFile);
$valid = true;
}

if (isset($valid)) {
$date = date("Y-m-d H:i:s");

$photoFolder = rtrim($photoFolder, "/") . "/";
$photoFile = "Test {$counter} {$date}.jpg";

$imageSaved = imagejpeg($photoImage, $photoFolder.$photoFile, 50);

syslog(LOG_DEBUG, "File saved is " . $imageSaved);
}
}


The first syslog entry confirms the file path and name...

gs://[myappid].appspot.com/AC3E3530/IMG_20160701_120144.jpg


The error log shows an error in attempting to open the stream, but I don't know how to address it.

PHP Warning: getimagesize(gs://[myappid].appspot.com/AC3E3530/IMG_20160701_120144.jpg): failed to open stream: "\google\appengine\ext\cloud_storage_streams\CloudStorageStreamWrapper::stream_open" call failed in /base/data/home/apps/s~[myappid]/v1.394746390020376247/code/server.php on line 169





I already have a variation of this functionality working on GAE with photos that my server receives through Twilio (where processPhoto() is a function identical to the code I excerpted above). In this case, I'm using getimagesize and imagecreate with a URL. I just don't know how to do the same with CloudStorage.

if ($fetch && $numMedia > 0) {
for ($x = 0; $x < $numMedia; $x++) {
$sourceFile = $_REQUEST["MediaUrl" . $x];
$sid = $_REQUEST["MessageSid"];
processPhoto("sms", $projectID, $sourceFile, $caption, $sid, $mobile, $message);
}
}

Answer

I think the problem was that the temporary file was removed before I could process it. So, I...

  1. Removed the functionality of processing multiple files (which I didn't need anyhow).
  2. Immediately move the file to another bucket.
  3. Examine the file for its type.
  4. Save it as desired.
  5. Remove the temporary file.

This is the form that I generate in PHP. There's no Submit button because I watch for a file change with jQuery.

<form id='form_uploadPhotos' method='post' enctype='multipart/form-data' action='{$websiteURL}?action=uploadPhotos'>
    <input type='file' id='input_uploadPhoto' name='file'>
    <input type='hidden' name='projectID' value='{$projectID}'>
</form>

This is the uploadPhotos function that's called when the form is submitted:

if ($action == "uploadPhotos") {
    $projectID = preg_replace("/\D/", "", $_REQUEST["projectID"]);

    $bucket = CloudStorageTools::getDefaultGoogleStorageBucketName();
    $bucketPath = "gs://" . $bucket . "/" . $_SERVER["REQUEST_ID_HASH"] . "/";

    $date = date("Y-m-d H:i:s");
    $time = time();

    $photoFile = sprintf("%08d", $projectID) . "." . $date . "." . $time . ".TEMP";
    $sourceFile = $photoFolder.$photoFile; // The default photo folder is defined elsewhere.

    move_uploaded_file($_FILES["file"]["tmp_name"], $sourceFile);
    processPhoto("upload", $projectID, $sourceFile, null, null, null, null);
}

This is the function that processes the photo. It's called by other processes that also receive photos (e.g., SMS attachments via Twilio).

function processPhoto($via, $projectID, $sourceFile, $caption, $twilioMessageID, $smsMobile, $smsMessage) {
    global $photoFolder;

    $photoInfo = getimagesize($sourceFile);
    if ($photoInfo["mime"] == "image/jpeg") {
        $photoImage = imagecreatefromjpeg($sourceFile);
        $valid = true;
    }
    elseif ($photoInfo["mime"] == "image/gif") {
        $photoImage = imagecreatefromgif($sourceFile);
        $valid = true;
    }
    elseif ($photoInfo["mime"] == "image/png") {
        $photoImage = imagecreatefrompng($sourceFile);
        $valid = true;
    }

    if (isset($valid)) {
        $date = date("Y-m-d H:i:s");
        $time = time();
        $photoFile = sprintf("%08d", $projectID) . "." . $date . "." . $time . ".JPEG";

        $photoImage = imagecreatefromjpeg($sourceFile);
        list($width, $height) = getimagesize($sourceFile);
        if (max($width, $height) > 800) {
            $scale = 800/max($width, $height);
            $newWidth = floor($width * $scale);
            $newHeight = floor($height * $scale);
            $saveImage = imagecreatetruecolor($newWidth, $newHeight);
            imagecopyresampled($saveImage, $photoImage, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
        }

        $imageSaved = imagejpeg($photoImage, $photoFolder.$photoFile);
        imagedestroy($photoImage);

        if ($imageSaved) {
            if (isset($twilioMessageID)) {
                $twilioMediaID = substr($sourceFile, strrpos($sourceFile, "/") + 1);
                purgeTwilioMedia($twilioMessageID, $twilioMediaID);
            }
            elseif (substr($sourceFile, strrpos($sourceFile, ".")) == ".TEMP") {
                unlink($sourceFile);
            }

            <Additional processing (e.g., adding entry to database.)
            .
            .
            .
        }
    }
}

Note: The image scaling code between "list($width, $height..." and "imagecopyresampled..." is based on Dano's answer to PHP resize image to fit within a certain area.