Skip to content

Embedded Redactor with Multi-Video Projects

This example shows how to embed Redactor into your web application using multi-video "projects." If you want to run this example on your computer, contact sales@sighthound.com to obtain the sighthound-redactor-embedded-example.zip file and a valid Redactor serial number. The rest of this document assumes you're running the example code (using localhost), but you can modify it to fit your deployment.

Note

The following example uses features that are only available in Redactor v5.2.1-beta-240415. Please contact us if you need access.

Concepts and Definitions

  • Project: A group of one or more videos that appear in the embedded editor for a user. The editor will display one Project at a time. A projectId is specified by the developer in the initial API request body to group multiple videos into a Project. The same projectId is used for each video that should appear for the Project.
  • sessionId: A developer-provided string that uniquely identifies a specific video. The sessionId will be embedded in events and emitted for particular actions, such as when a user clicks the Render and Export button. The sessionId is immutable and must be set on the first MEDIA_PREPARE API call. If a sessionId is not provided, one will be auto-generated by Redactor.

Project Files and Setup

The example project uses Docker Compose to run two containers: an NGINX container that will act as your web application where the Redactor editor will be embedded and a Redactor container. The NGINX service is comprised of a simple web server (serving HTML, CSS, and JS files) and a reverse proxy for the ./proxy/redactor/ path to the backend Redactor server. Redactor uses session cookies to manage auth, so browsers may require a proxy to allow the auth credentials to be passed. Please see the file at ./config/nginx/confg.d/default.conf for the proxy configuration details. Self-signed SSL certificates are used in the example files, so accept any warnings that appear in your browser during development.

To start the example services, extract sighthound-redactor-embedded-example.zip your computer, cd into the newly created sighthound-redactor-embedded-example folder, and type docker compose up.

If this is your first time running this instance of Redactor (particularly with this Docker Compose), you will need to set up your initial admin user and add a license: http://localhost:9000/admin

Docker Compose will bind mount a folder named ./volumes/redactor into the Redactor container to store all of the data and application logs files. If you're encountering into any issues, you can review the log file at ./volumes/redactors/logs/main.log or send it to support@sighthound.com for assistance.

Upload Videos into a Project

You load a video into Redactor by providing a path to the input video (inputUri) and an optional path where all of the Redaction state data for the video will be stored (outputUri). If an outputUri is not provided, all of the files will be stored in Redactor's internal storage and can be moved to a different location (e.g., AWS S3, Azure Blob Storage, local filesystem, etc.) in subsequent API calls when you're ready.

In this example, we will have two videos for a user to redact in the browser. As we load these into Redactor, we'll use the same projectId for all videos that Redactor should display. Each video will need a unique sessionId.

Let's load the two videos into Redactor using my-project-id as their project name:

API Request

POST /api/v1/videos:process

Video 1:

curl --location --request POST 'http://localhost:9000/api/v1/videos:process' \
    --header 'Content-Type: application/json' \
    --data-raw '{
        "inputUri": "https://sh-io-downloads.s3.amazonaws.com/videos/garage-top.mp4",
        "features": ["MEDIA_PREPARE"],
        "videoContext": {
            "prepareConfig": {
                "sessionId": "media-id-240403-1"
            }
        },
        "projectId": "my-project-id"
    }'
{
    "inputUri": "https://sh-io-downloads.s3.amazonaws.com/videos/garage-top.mp4",
    "features": ["MEDIA_PREPARE"],
    "videoContext": {
        "prepareConfig": {
            "sessionId": "media-id-240403-1"
        }
    },
    "projectId": "my-project-id"
}

Video 2:

curl --location --request POST 'http://localhost:9000/api/v1/videos:process' \
    --header 'Content-Type: application/json'  \
    --data-raw '{
        "inputUri": "https://sh-io-downloads.s3.amazonaws.com/videos/Dancing.mp4",
        "features": ["MEDIA_PREPARE"],
        "videoContext": {
            "prepareConfig": {
                "sessionId": "media-id-240403-2"
            }
        },
        "projectId": "my-project-id"
    }'
{
    "inputUri": "https://sh-io-downloads.s3.amazonaws.com/videos/Dancing.mp4",
    "features": ["MEDIA_PREPARE"],
    "videoContext": {
        "prepareConfig": {
            "sessionId": "media-id-240403-2"
        }
    },
    "projectId": "my-project-id"
}

Redactor will download the requested videos and prepare them for use in the UI.

API Response

{
   "name": "projects/{project-id}/locations/{location-id}/operations/{operation-id}"
}

Note

API calls made to the /api/v1/videos:process start operations on the Redactor server that may take some time to complete, such as loading large videos or auto-detecting faces. Because of this, Redactor uses an asynchronous API that quickly returns a response containing an operation name that can be used to check the status. The operation status can be monitored by querying the /api/v1/{operation-name} endpoint using the full name returned in the response above or by specifying a webhook endpoint for progress, start, and completion events. Subscribing to COMPLETION events is highly recommended so that you know when the video has been successfully loaded into Redactor or if there were any errors. The bottom of this guide has more detail on the Operation State and Webhook Notifications.

Instantiate the Redactor Editor UI

Once you've received notification that the original MEDIA_PREPARE operation is complete, you can instantiate the editor UI on your website.

Open https://127.0.0.1:3443/ in your browser if you're running the example project code. The reason we're using 127.0.0.1 is to keep the cookies separate from the localhost domain in case you need to access the standard Redactor at http://localhost:9000. You will get a browser/security warning due to the example using self-signed certificates, so select the option to proceed to the site. Redactor's Projects page will be displayed, and the Redactor editor will show the videos and allow you full access to the redaction tools.

The web page contains the HTML below and is meant to demonstrate how to embed the Redactor editor into a web application. It uses the sighthound-redactor.js script in the <head> of the page and is configured to connect to the projectId used in the above API calls.

The example HTML file is provided below for easy access, but it can also be found at ./config/html/index.html for customization. NGINX serves it as part of the Docker Compose example.

<!--
    During development, open your browser's dev tools console to view helpful logs.
-->
<!DOCTYPE html>
<html>
    <head>
        <title>Embedded Redaction UI Demo</title>
        <script src="https://127.0.0.1:3443/proxy/redactor/public/sighthound-redactor.js"></script>
    </head>
    <body>
        <!-- Example buttons showing how to run certain methods. -->
        <div style="position: absolute; top: 10px; left: 10px; z-index: 100">
            <button id="closeBtn" onClick="onClose()" style="color: darkblue">Close Media</button>
            <button id="destroyBtn" onClick="onDestroy()" style="color: darkred" >Destroy UI</button>
            <button id="exportBtn" onClick="onExport()" style="color: darkgreen" >Show Export Dialog</button>
        </div>

        <!--
            Redactor will render into the following div. The ID can be renamed and will be used in the JS below.
            Important: Redactor will expand to fit the dimensions of this div, so be sure to specify a suitable
            width and height for your specific layout.
        -->
        <div id="redactor-editor" style="width: 100vw; height: 100vh"></div>

        <script>
            const redactorElement = document.getElementById("redactor-editor");
            const redactorServer = "https://127.0.0.1:3443/proxy/redactor";
            const redactorWhereTo = {projectId: "my-project-id"}; // This needs to match the projectId you used in your MEDIA_PREPARE API call(s).
            const redactorEditor = new redactor.default(
                redactorElement,
                redactorServer,
                redactorWhereTo,
                {
                    shadowed: true, // Set to true to enable Shadow DOM (recommended), false to disable it.

                    // If using Shadow DOM, fonts and CSS dependencies must be explictly declared in cssHref below.
                    // This example uses the NGINX proxy path to point to the main css file on the Redactor server. Update as needed.
                    // It also includes an optional CSS override file hosted in the NGINX site's "public" folder to show how you can
                    // override Redactor's UI using your own externally hosted CSS file. Both relative and absolute paths are supported.
                    cssHref: {
                        fonts: [
                            "/proxy/redactor/public/sighthound-redactor-fonts.css",
                        ],
                        main: [
                            "/proxy/redactor/public/sighthound-redactor.css",

                            // Optional CSS override file to customize the editor UI
                            "/public/sighthound-redactor-override.css",
                        ],
                    },

                    // Customization options for the editor UI.
                    customize: {
                        export: {
                            download: "show", // The download button behavior: show, hide, or eventOnly
                            save: "hide"  // Set to "show" to display a custom button next to the download button which only emits a click event
                        },
                    },

                    // You can change the wording for any text items in Redactor by overlaying its built-in translations file.
                    // Both relative and absolute paths to remote resources are supported.
                    translationOverwritesUrl: "/public/translations.overlay.hjson",

                }
            );

            // Render editor after MEDIA_PREPARE is done
            redactorEditor.renderEditor((err) => {
                if (err) {
                    console.error("EDITOR RENDER FAILED:", err);
                } else {
                    console.log("editor rendered successfully");
                }
            });

            // Events emitted by the editor when a video is opened or closed.
            // Event detail contains the sessionId of the opened/closed video.
            const editorEventNames = [
                "redactor.editor.enter",
                "redactor.editor.exit",
            ];

            // Events emitted by the export dialog.
            // Event detail contains the sessionId and, if export submit button is pressed, the renderConfig with selected export options.
            const exportEventNames = [
                "redactor.export.cancel",   // Export dialog closed/cancelled.
                "redactor.export.download", // Download button pressed
                "redactor.export.save",     // Save button pressed
                "redactor.export.submit",   // Submit button pressed
            ];

            // Attach event listeners for all event names and log to console for development.
            // You can prevent Redactor from automatically starting the render/export of a video when a user submits the export dialog
            // by uncommenting evt.preventDefault below. In that case, you will receive all of the export setting they chose and will need
            // to make a MEDIA_RENDERING API call from your backend service to start the export.
            [
                ...editorEventNames,
                ...exportEventNames,
            ].forEach(evtName => redactorElement.addEventListener(evtName, evt => {
                console.log("EVENT -", evt.type, "-", JSON.stringify(evt && evt.detail, null, 4));
                // evt.preventDefault();
            }));

            // Log editor active state to console.
            // If `redactorEditor.active()` is true, the editor has a video open. If false, the project page is visible.
            function logActive() {
                console.log("EDITOR ACTIVE:", redactorEditor.active());
            };

            // For development purposes, log editor active state to console on editor enter/exit events.
            editorEventNames.forEach(evtName => redactorElement.addEventListener(evtName, logActive));

            // Closes the currently open video/session. This must be done before making any other API calls to the redaction server.
            function onClose() {
                redactorEditor.cleanup();
                // document.getElementById("closeBtn").style.display = "none"; // hide button
            }

            // Destroys and cleans up the redaction UI
            function onDestroy() {
                redactorEditor.destroy();
                logActive();
            }

            // Opens the export dialog for the currently open video/session.
            function onExport() {
                if (!redactorEditor.sendCommand("export")) {
                    console.warn("Export command ignored. The editor must be open/active.");
                }
            }

        </script>
    </body>
</html>

Exporting the Redacted Video

To trigger a render and export, we need to tell Redactor which session we want to export and where to put the files when done. The inputUri will use the sessionId we defined for the video in our initial API call, and the outputUri will point to a path on the local filesystem. For Video 1, we'll output only the redacted video, and Video 2 will output all of the Redaction state. Redactor determines whether to output only the redacted video or all of the redaction state if the outpuUri ends in a trailing slash. If so, it will output all of the redaction state, including the redacted video named redacted.mp4. If there is no trailing slash, Redactor will name the redacted video with your provided filename.

It's worth noting that, by default, Redactor's UI displays an Export Video button, which the user can click to render and download the redacted video directly to their computer. If you prefer to control that process instead, you can hide the Export Video button by setting customize.export.download="hide" in the Redactor instantiation script in the previous section above, and then manually trigger an export by making an API call with the MEDIA_RENDER feature to the Redactor server.

API REQUEST

POST /api/v1/videos:process

Video 1 (Export only the redacted video):

curl --location --request POST 'http://localhost:9000/api/v1/videos:process' \
--header 'Content-Type: application/json' \
--data-raw '{
    "inputUri": "session://media-id-240403-1",
    "features": ["MEDIA_RENDER"],
    "outputUri": "file:///root/Sighthound Redactor/api-test/media-id-240403-1-output/redacted.mp4"
}'
{
    "inputUri": "session://media-id-240403-1",
    "features": ["MEDIA_RENDER"],
    "outputUri": "file:///root/Sighthound Redactor/api-test/media-id-240403-1-output/redacted.mp4"
}

Video 2 (Export all redaction state and redacted.mp4):

curl --location --request POST 'http://localhost:9000/api/v1/videos:process' \
--header 'Content-Type: application/json' \
--data-raw '{
    "inputUri": "session://media-id-240403-2",
    "features": ["MEDIA_RENDER"],
    "outputUri": "file:///root/Sighthound Redactor/api-test/media-id-240403-2-output/"
}'
{
    "inputUri": "session://media-id-240403-2",
    "features": ["MEDIA_RENDER"],
    "outputUri": "file:///root/Sighthound Redactor/api-test/media-id-240403-2-output/"
}

API RESPONSE

{
   "name":"projects/{project-id}/locations/{location-id}/operations/{operation-id}"
}

Operation Status

All requests sent to the /api/v1/videos:process endpoint are asynchronous, with each returning an operation name like:

{
   "name":"projects/{project-id}/locations/{location-id}/operations/{operation-id}"
}

The name value is used to identify a specific Redactor project and inspect the status of the operation.

API REQUEST

GET /api/v1/{operation-name}

Example:

GET /api/v1/projects/{project-id}/locations/{location-id}/operations/{operation-id}

API RESPONSE

{
   "name": "projects/{project-id}/locations/{location-id}/operations/{operation-id}",
   "metadata": {
       "@type": "sighthound.cloud.v1.Progress",
       "progress": {
           "inputUri": "http://example.com/path/to/video1.mp4",
           "progressPercent": 100,
           "startTime": "2020-04-01T22:13:17.978847Z",
           "updateTime": "2020-04-01T22:13:29.576004Z"
       }
   },
   "done": true,
   "response": {
       "@type": "sighthound.cloud.v1.videos.ProcessResponse",
       "processResults": {
           "outputUri": "https://{your-azure-account}.blob.core.windows.net/{container-name}/{video-2-data}/"
       },
       "tag": {
        "your": "custom string, object, or array"
       }
   }
}

When the done value is true, the operation is complete, and either a response or error property will exist to indicate success or failure.

Webhook Notifications

Webhook notifications can be enabled using the notifications property as part of your API request. It's recommended that COMPLETION events are used, but START and PROGRESS are available if they also fit your use case. The Redactor server will POST messages that contain the same content as Get Operation Status above to the uri you provide.

Here is an example of what a request body may look like with webhooks:

{
   "features": ["MEDIA_PREPARE"],
   "inputUri": "https://example.com/path/to/video1.mp4",
   "notifications": [{
      "method": "HTTP_POST",
      "uri": "https://example.com/mywebhook/endpoint",
      "events": ["START", "PROGRESS", "COMPLETION"]
   }],
   "videoContext": {
        "prepareConfig": {
            "sessionId": "my-media-id1"
        }
   },
   "projectId": "my-project-id"
}