> For the complete documentation index, see [llms.txt](https://docs.apryse.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.apryse.com/web/video/video-redaction.md).

# Video Redaction

Elevate your video redaction process with WebViewer Video. Discover how to integrate ffmpeg for seamless redaction of video frames using web. Follow our step-by-step guide for setting up a server and

{% hint style="info" %}
New licenses for WebViewer Video are no longer offered.
{% endhint %}

WebViewer Video uses [ffmpeg](https://ffmpeg.org/) in the background to allow for redaction of video frames. In order to utilize ffmpeg, a server must be setup to allow for it. We have created a sample server for you to use and deploy. We recommend hosting the server on AWS.

Before you begin, make sure your development environment includes [Node.js](https://nodejs.org/en/), [npm](https://www.npmjs.com/get-npm/) and [ffmpeg](https://ffmpeg.org/download.html).

## Sample redaction page

The WebViewer-Video github [sample](https://github.com/ApryseSDK/webviewer-video-sample/) has a redaction sample ready to go. It will initialize and run the client and server components. We recommend running that sample directly. To do so, first clone repository and then run the following commands:

{% tabs %}
{% tab title="Shell" %}
{% code lineNumbers="true" %}

```sh
# command line
npm i
npm run start-samples
```

{% endcode %}
{% endtab %}
{% endtabs %}

The steps below will recreate the sample. Use them to integrate redaction into you application.

## Integration

WebViewer Video uses [ffmpeg](https://ffmpeg.org/) in the background to allow for redaction of video frames. In order to utilize ffmpeg, a server must be setup on the user side to allow for it. Find a [sample server on Github](https://github.com/ApryseSDK/media-sample-server/). Please follow installation steps there before continuing. The example code below shows how to integrate the server within your application:

### Initial setup

Before you begin, make sure your development environment includes [Node.js](https://nodejs.org/en/), [npm](https://www.npmjs.com/get-npm/) and [ffmpeg](https://ffmpeg.org/download.html).

### How to use

Below is an example of how to integrate the server with WebViewer Video. In this example, we used [`update element`](https://sdk.apryse.com/api/video/UI.html#.updateElement__anchor) to overload the click event of the `Redact All` button. This click event will now make a call to our custom server where the redaction of the video will take place with ffmpeg.

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-9c459b290917b6b13859d3941ab491e071f7ca1e%2Fc2c355dc6b446f3b4ad4b7777533f48290b8b230-938x700.png?alt=media)

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
import WebViewer from '@pdftron/webviewer';
import { initializeVideoViewer } from '@pdftron/webviewer-video';
WebViewer({
    path: '/webviewer/lib',
    enableRedaction: true,
  },
  viewer.current,
).then(async instance => {
  // Extends WebViewer to allow loading HTML5 videos (.mp4, ogg, webm).
  const {
    UI,
    loadVideo,
  } = await initializeVideoViewer(
    instance,
    {
      license: '---- Insert commercial license key here after purchase ----',
      enableRedaction: true,
    }
  );
  // Load a video at a specific url.
  // Can be a local or public link
  const videoUrl = 'https://pdftron.s3.amazonaws.com/downloads/pl/video/bunny-short.mp4';
  loadVideo(videoUrl);

  // Overloading the onclick function of redacting video button with custom code
  // Function must be async and must return data when function is finished to remove loading spinner.
  // enableRedaction must be set to true when initalizing WebViewer Video:
  // https://sdk.apryse.com/api/video/module-@pdftron_webviewer-video.html#.initializeVideoViewer__anchor
  UI.updateElement('redactApplyButton', {
    onClick: async redactAnnotations => {
      const response = await fetch('http://localhost:3001/video/redact', {
        method: 'POST',
        body: JSON.stringify({
          intervals: redactAnnotations.map(annotation => ({
            start: annotation.getStartTime(),
            end: annotation.getEndTime(),
            shouldRedactAudio: annotation.shouldRedactAudio || annotation.redactionType === 'audioRedaction',
            shouldRedactVideo: annotation.redactionType !== 'audioRedaction',
          })),
          url: videoUrl,
        }),
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
      });

      const videoBuffer = await response.arrayBuffer();

      const newVideoBlob = new Blob([videoBuffer], { type: 'video/mp4' });
      loadVideo(URL.createObjectURL(newVideoBlob));
      return videoBuffer;
    }
  });
});
```

{% endcode %}
{% endtab %}
{% endtabs %}

[WebViewerInstance](https://sdk.apryse.com/api/web/WebViewerInstance.html) [initializeVideoViewer](https://sdk.apryse.com/api/video/module-@pdftron_webviewer-video.html#~initializeVideoViewer__anchor)

### How to persist changes on your server to client

Currently our server returns the video file by sending back a buffer to the client. You will find that code, in this [file](https://github.com/ApryseSDK/media-sample-server/blob/b1dcc064246b385c7ea8b709cc0a283fd944c606/routes/ffmpeg/ffmpeg.js#L52).

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
fs.readFile(`./tmp/${uuid}.mp4`, (err, data) => {
  resolve(data);
});
```

{% endcode %}
{% endtab %}
{% endtabs %}

The issue here is that when redactions are applied to this buffer, we cannot send that local file back to the server so it must be persisted on the server through a variable or by uploading to the cloud in order to have further redactions applied to a previously edited video.

Here is an example of replacing the server code above with an upload to s3:

New server code:

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
return new Promise((resolve) => {
  fs.readFile(`./tmp/${uuid}.mp4`, async (err, data) => {
    await s3.upload({
      Bucket: 'pdftron-media-demo-files',
      Key: `${uuid}.mp4`,
      Body: data
    });

    let s3URL = await s3.getSignedUrl({
      Bucket: 'pdftron-media-demo-files',
      Key: `${uuid}.mp4`,
    });

    resolve(s3URL);
  });
});
```

{% endcode %}
{% endtab %}
{% endtabs %}

New client code:

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
import WebViewer from '@pdftron/webviewer';
import { initializeVideoViewer } from '@pdftron/webviewer-video';
WebViewer({
    path: '/webviewer/lib',
    enableRedaction: true,
  },
  viewer.current,
).then(async instance => {
  // Extends WebViewer to allow loading HTML5 videos (.mp4, ogg, webm).
  const {
    UI,
    loadVideo,
  } = await initializeVideoViewer(
    instance,
    {
      license: '---- Insert commercial license key here after purchase ----',
      enableRedaction: true,
    }
  );
  // Load a video at a specific url.
  // Can be a local or public link
  let currentVideoUrl = 'https://pdftron.s3.amazonaws.com/downloads/pl/video/bunny-short.mp4';
  loadVideo(currentVideoUrl);
  // Overloading the onclick function of redacting video button with custom code
  // Function must be async and must return data when function is finished to remove loading spinner.
  // enableRedaction must be set to true when initalizing WebViewer Video:
  // https://sdk.apryse.com/api/video/module-@pdftron_webviewer-video.html#.initializeVideoViewer__anchor
  UI.updateElement('redactApplyButton', {
    onClick: async redactAnnotations => {
      const response = await fetch('http://localhost:3001/video/redact', {
        method: 'POST',
        body: JSON.stringify({
          intervals: redactAnnotations.map(annotation => ({
            start: annotation.getStartTime(),
            end: annotation.getEndTime(),
            shouldRedactAudio: annotation.shouldRedactAudio || annotation.redactionType === 'audioRedaction',
            shouldRedactVideo: annotation.redactionType !== 'audioRedaction',
          })),
          url: currentVideoUrl,
        }),
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
      });
      currentVideoUrl = await response.text();
      loadVideo(currentVideoUrl);
      return currentVideoUrl;
    }
  });
});
```

{% endcode %}
{% endtab %}
{% endtabs %}

[WebViewerInstance](https://sdk.apryse.com/api/web/WebViewerInstance.html) [initializeVideoViewer](https://sdk.apryse.com/api/video/module-@pdftron_webviewer-video.html#.initializeVideoViewer__anchor)

Now when using the endpoint `/video/redact`, your redacted video will be uploaded to s3 and changes will be persisted on the client.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.apryse.com/web/video/video-redaction.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
