Video Moderation / Continuous Moderation

Continuous Video Moderation

Introduction

Continuous Video Moderation is done through a few steps:

  1. Your back-end submits a video and gets a stream ID from Sightengine
  2. Sightengine analyzes the video, and creates alerts each time offending content is found
  3. Your back-end receives callbacks (through webhooks) to inform you of any offending content. Or you can poll Sightengine to ask for status updates and moderation progress and results
  4. Once finished, the full results are sent to your server through a webhook

Submitting a Video to Sightengine

There are two ways to submit a video for Continuous Moderation:

  • Via raw bytes upload (you send the video file to our API). This is usually the best solution if you have a static video file at hand.
  • Via a streaming URL (you send a URL pointing to your video). This is usually the best solution for streaming and live feeds

Moderate by uploading the Video


curl -X POST 'https://api.sightengine.com/1.0/video/check.json' \
  -F 'media=@/path/to/video' \
  -F 'models=nudity' \
  -F 'callback_url=https://yourcallback/path' \
  -F 'api_user={api_user}' \
  -F 'api_secret={api_secret}'


# this example uses requests
import requests
import json

params = {
  # specify the models you want to apply
  'models': 'nudity',
  # specify where you want to receive result callbacks (optional)
  'callback_url': 'https://yourcallback/path',
  'api_user': '{api_user}',
  'api_secret': '{api_secret}'
}
files = {'media': open('/path/to/video', 'rb')}
r = requests.post('https://api.sightengine.com/1.0/video/check.json', files=files, data=params)

output = json.loads(r.text)


$params = array(
  'media' => new CurlFile('/path/to/video'),
  // specify the models you want to apply
  'models' => 'nudity',
  // specify where you want to receive result callbacks (optional)
  'callback_url': 'https://yourcallback/path',
  'api_user' => '{api_user}',
  'api_secret' => '{api_secret}',
);

// this example uses cURL
$ch = curl_init('https://api.sightengine.com/1.0/video/check.json');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
$response = curl_exec($ch);
curl_close($ch);

$output = json_decode($response, true);


// this example uses axios and form-data
const axios = require('axios');
const FormData = require('form-data');
const fs = require('fs');

data = new FormData();
data.append('media', fs.createReadStream('/path/to/video'));
// specify the models you want to apply
data.append('models', 'nudity');
// specify where you want to receive result callbacks (optional)
data.append('callback_url', 'https://yourcallback/path');
data.append('api_user', '{api_user}');
data.append('api_secret', '{api_secret}');

axios({
  method: 'post',
  url:'https://api.sightengine.com/1.0/video/check.json',
  data: data,
  headers: data.getHeaders()
})
.then(function (response) {
  // on success: handle response
  console.log(response.data);
})
.catch(function (error) {
  // handle error
  if (error.response) console.log(error.response.data);
  else console.log(error.message);
});

Moderate by submitting a Video URL


curl -X GET -G 'https://api.sightengine.com/1.0/video/check.json' \
    -d 'stream_url=https://yourvideo/path' \
    -d 'models=nudity' \
    -d 'callback_url=https://yourcallback/path' \
    -d 'api_user={api_user}' \
    -d 'api_secret={api_secret}'


# if you haven't already, install the SDK with 'pip install sightengine'
from sightengine.client import SightengineClient
client = SightengineClient('{api_user}','{api_secret}')
client.check('nudity').video('https://yourvideo/path', 'https://yourcallback/path')


// if you haven't already, install the SDK with 'composer require sightengine/client-php'
use \Sightengine\SightengineClient;
$client = new SightengineClient('{api_user}','{api_secret}');
$client->check(['nudity'])->video('https://yourvideo/path', 'https://yourcallback/path');


// if you haven't already, install the SDK with 'npm install sightengine --save'
var sightengine = require('sightengine')('{api_user}','{api_secret}');
sightengine.check(['nudity']).video('https://yourvideo/path', 'https://yourcallback/path').then(function(result) {
    // The API response (result)
}).catch(function(err) {
    // Handle error
});

Please note that you will have to choose the models you wish to apply to your video, and make sure you have an API user and API secret. You can get those from your dashboard.

Receiving webhook callbacks

Once you have submitted a video stream and received a 200 HTTP status code, our Moderation Engine will start consuming the stream and will assign a moderation score to relevant frames and send moderation results to your callback URL.

To avoid sending too many callback events, the engine will send callbacks only when one of the following happens:

  • the video has been completely read and moderation is done
  • an "interesting event" has been detected. One such event would for instance be the presence of nudity. This is defined by setting threshold values on the nudity probabilities.
  • an error was encountered after the initial request was closed

Server-side code for callback receipt


# callbacks cannot be received through the command line
# please choose a server-side language to receive and process callbacks
# or use an approach that does not require callbacks (such as sequential video moderation)


import json
from django.http import HttpResponse

# Using Django
@csrf_exempt
def my_callback_view(request):
  payload = request.body
  content = json.loads(payload)

  # You now have access to the following
  # the media id => content['media']['id']
  # the moderation data => content['data']
  # the moderation status => content['data']['status']

  # Acknowledge receipt
  return HttpResponse(status=200)


$payload = @file_get_contents('php://input');
$content = json_decode($payload, true)

// You now have access to the following:
// the media id => $content['media']['id']
// the moderation data => $content['data']
// the moderation status => $content['data']['status']

// Acknowledge receipt
http_response_code(200);


// This example uses Express to receive callbacks
const app = require('express')();

// Use body-parser to retrieve the raw body as a buffer
const bodyParser = require('body-parser');

// Match the raw body to content type application/json
app.post('/webhook', bodyParser.raw({type: 'application/json'}), (request, response) => {
  let event;

  content = JSON.parse(request.body);

  // You now have access to the following:
  // the media id => content.media.id
  // the moderation data => content.data
  // the moderation status => content.data.status

  // Acknowledge receipt
  response.json({received: true});
});

app.listen(8000, () => console.log('Running on port 8000'));

Callback recommendations

Acknowledging receipt

To acknowledge receipt of a callback, your endpoint should return a 2xx HTTP status code. Any other information returned in the request headers or request body is ignored. All response codes outside this range, including 3xx codes, will indicate to Sightengine that you did not receive the callback. This does mean that a URL redirection or a "Not Modified" response will be treated as a failure.

If your callback script performs complex logic, or makes network calls, it's possible the script would timeout before Sightengine sees its complete execution. For that reason, you may want to have your callback endpoint immediately acknowledge receipt by returning a 2xx HTTP status code, and then perform the rest of its duties.

Verifying callbacks

As an extra security measure, you should check that the media id value returned in the callback matches the media id returned in the initial API response. This is can be helpful if you have several streams moderated at the same time and need to verify what media each callback refers to.

HTTPS

If you use an HTTPS URL for your callback endopint, please make sure that your server is correctly configured to support HTTPS with a valid server certificate. Sightengine will valide that the connection to your server is secure before sending the data.

Idempotency

Keep in mind that your callback may occasionally receive the same event more than once. We advise you to guard against duplicated event receipts by making your event processing idempotent.

Interacting with video moderations

Getting the list of on-going video moderations

At any time you can request a list of on-going video moderations, meaning video moderations that have been launched but have not reached the stopped or finished status.

This can be useful if you hit your maximum concurrency limit, to see what moderations are on-going, or if you wish to monitor the progress of videos but don't have access to the stream ids.


curl -X GET -G 'https://api.sightengine.com/1.0/video/ongoing.json' \
  -d 'api_user={api_user}' \
  -d 'api_secret={api_secret}'


# this example uses requests
import requests
import json

params = {'api_user': '{api_user}', 'api_secret': '{api_secret}'}
r = requests.get('https://api.sightengine.com/1.0/video/ongoing.json', params=params)

output = json.loads(r.text)


$params = array(
  'api_user' => '{api_user}',
  'api_secret' => '{api_secret}',
);

// this example uses cURL
$ch = curl_init('https://api.sightengine.com/1.0/video/ongoing.json?'.http_build_query($params));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);

$output = json_decode($response, true);


// this example uses axios
const axios = require('axios');

axios.get('https://api.sightengine.com/1.0/video/ongoing.json', {
  params: {
    'api_user': '{api_user}',
    'api_secret': '{api_secret}',
  }
})
.then(function (response) {
  // on success: handle response
  console.log(response.data);
})
.catch(function (error) {
  // handle error
  if (error.response) console.log(error.response.data);
  else console.log(error.message);
});

Getting the status and results of an on-going video moderation


# {media_id} is the id of the media that is being moderated
# this id is given to you by the API when you perform a moderation request
curl -X GET -G 'https://api.sightengine.com/1.0/video/byid.json' \
  -d 'id={media_id}' \
  -d 'api_user={api_user}' \
  -d 'api_secret={api_secret}'


# this example uses requests
import requests
import json

params = {
  # {media_id} is the id of the media that is being moderated
  # this id is given to you by the API when you perform a moderation request
  'id': '{media_id}',
  'api_user': '{api_user}',
  'api_secret': '{api_secret}'
}
r = requests.get('https://api.sightengine.com/1.0/video/byid.json', params=params)

output = json.loads(r.text)


$params = array(
  // {media_id} is the id of the media that is being moderated
  // this id is given to you by the API when you perform a moderation request
  'id' => '{media_id}',
  'api_user' => '{api_user}',
  'api_secret' => '{api_secret}',
);

// this example uses cURL
$ch = curl_init('https://api.sightengine.com/1.0/video/byid.json?'.http_build_query($params));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);

$output = json_decode($response, true);


// this example uses axios
const axios = require('axios');

axios.get('https://api.sightengine.com/1.0/video/byid.json', {
  params: {
    'id': '{media_id}',
    'api_user': '{api_user}',
    'api_secret': '{api_secret}',
  }
})
.then(function (response) {
  // on success: handle response
  console.log(response);
})
.catch(function (error) {
  // handle error
  if (error.response) console.log(error.response.data);
  else console.log(error.message);
});

Early stopping of a video moderation

In general you should not worry about having to stop Video Moderation. Moderation actions automatically finish once the video ends or once the video timeout has been reached. Early Stopping is useful either if you have a long running video and realize you don't want/need to run it through moderation until the end, or if you made a mistake and need to stop Moderation immediately.

To stop a video, simply perform the following command, providing the video's media id.


# {media_id} is the id of the media that is being moderated
# this id is given to you by the API when you perform a moderation request
curl -X DELETE 'https://api.sightengine.com/1.0/video/byid.json' \
  -d 'id={media_id}' \
  -d 'api_user={api_user}' \
  -d 'api_secret={api_secret}'


# this example uses requests
import requests
import json

data = {
  # {media_id} is the id of the media that is being moderated
  # this id is given to you by the API when you perform a moderation request
  'id': '{media_id}',
  'api_user': '{api_user}',
  'api_secret': '{api_secret}'
}
r = requests.delete('https://api.sightengine.com/1.0/video/byid.json', data=data)

output = json.loads(r.text)


$params = array(
  // {media_id} is the id of the media that is being moderated
  // this id is given to you by the API when you perform a moderation request
  'id' => '{media_id}',
  'api_user' => '{api_user}',
  'api_secret' => '{api_secret}',
);

// this example uses cURL
$ch = curl_init('https://api.sightengine.com/1.0/video/byid.json');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
$response = curl_exec($ch);
curl_close($ch);

$output = json_decode($response, true);


// this example uses axios and querystring
const axios = require('axios');
const qs = require('querystring');

data = {
  'id': '{media_id}',
  'api_user': '{api_user}',
  'api_secret': '{api_secret}',
}

axios.delete('https://api.sightengine.com/1.0/video/byid.json', {data: qs.stringify(data)})
.then(function (response) {
  // on success: handle response
  console.log(response.data);
})
.catch(function (error) {
  // handle error
  if (error.response) console.log(error.response.data);
  else console.log(error.message);
});

Did you find this page helpful?

We're always looking for advice to help improve our documentation!

Let us know what you think

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more

OK