Using ApyHub for Image Moderation

Yoram Kornatzky
2 min readMay 21, 2024

--

In Auctibles, we use ApyHub to moderate images of items for auction. Auctibles is built using the Laravel PHP framework.

Uploading Image Files

Videos are effortlessly uploaded using a straightforward Livewire component and an HTML input element with type=file.

<input id="photos" name="photos" type="file" class="sr-only" wire:model.live="photos" accept="image/png,image/jpg,image/gif">

The component has a public property:

public $photos = [];

Upon clicking a submit button, we apply validation to the field,

'photos' => 'nullable|array|max:3', // array
'photos.*' => [
'required',
'image',
'max:10240', // 10MB
new ExplicitImage(),
],

ExplicitImage is a validation rule.

Temporary Files

We upload temporary files to a Minio bucket which resides on the server. The bucket is defined as an uploads bucket for Laravel. This is done in config/filesystems.php.

ApyHub Results for Image Content

We use curl to call ApyHub. The response looks like this:

{
"data": {
"apyhub": {
"adult": {
"adultScore": 0.0025164163671433926,
"goreScore": 0.0014069777680560946,
"isAdultContent": false,
"isGoryContent": false,
"isRacyContent": false,
"racyScore": 0.0032450903672724962
},
"metadata": {
"format": "Png",
"height": 1024,
"width": 1024
}
}
}
}

The Validation Rule

<?php

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use CURLFile;
use CURLStringFile;
use Illuminate\Support\Facades\Storage;

class ExplicitImage implements ValidationRule
{
/**
* Run the validation rule.
*
* @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
// path to temporary uploaded file in uploads disk
$path = config('livewire')['temporary_file_upload']['directory'] . DIRECTORY_SEPARATOR . $value->getFilename();

$ch = curl_init();

curl_setopt(
$ch,
CURLOPT_URL,
"https://api.apyhub.com/ai/image/detect/explicit-content/file"
);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'apy-token: ' . config('app.APYHUB_TOKEN'),
'content-type: multipart/form-data'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, [
'file' => new CURLStringFile(Storage::disk('uploads')->get($path), $value->getFilename(), $value->getMimeType()),
'requested_service' => 'apyhub',
]);

$response = curl_exec($ch);

curl_close($ch);

if ($response === false) {
$error = curl_error($ch);
logger()->warning("ApyHub image moderation CURL Error: $error");
return;
} else {
// Process the successful CURL call here.
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

if ($statusCode == 200) {
// The request was successful
$data = json_decode($response, true); //

$response = $data['data']['apyhub']['adult'];

if ($response['isAdultContent'] || $response['isGoryContent'] || $response['isRacyContent']) {
$fail('validation.' . 'explicit_image')->translate();
}

} else {
// The server responded with an error
logger()->warning("ApyHub image moderation HTTP Error: $statusCode");
return;
}
}

}
}

--

--

Yoram Kornatzky

Entrepreneur, Auctibles: https://auctibles.com, 25 years of development experience, Ph.D. Computer Science