DEV Community

Cover image for Using ApyHub for Image Moderation
Yoram Kornatzky
Yoram Kornatzky

Posted on

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">
Enter fullscreen mode Exit fullscreen mode

The component has a public property:

public $photos = [];
Enter fullscreen mode Exit fullscreen mode

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(),
],  
Enter fullscreen mode Exit fullscreen mode

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
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
          }
        }

    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)