Using ApyHub for Image Moderation
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;
}
}
}
}