> 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/annotate-videos.md).

# Annotate videos

Cretae video annotation and attach them to specific timeframe with Webviewer. In this we will show how to add annotaion through UI as well as programatically.

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

WebViewer Video allows you to create annotations in a video file and attach them to a specific timeframe. It leverages some of the familiar WebViewer annotation tools like *Note*, *Free Text*, *Rectangle* and *Free Hand*. Video annotations comes out of the box with zero configuration.

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-6b793d74f4f5ea3c6f79e1b6e1fe92cb59c1b953%2F8d183a364373a1168fc4cd0033a689e0c685e37d-554x74.jpg?alt=media)

It is also possible to control the amount of time said annotation will be displayed on video playback. After the annotation is created, click on the desired annotation on top of the timeline reel and *use the directional arrows* to expand or contract the amount of time it will be displayed.

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-6cc77dd7c6c598705c40b83b38aea709d28c6a55%2Fb94df6a1629eaaed306ebd1230ae6f35214b2c08-752x286.gif?alt=media)

Annotations can also have comments attached to it, initially ordered by timeframe (bottom being the earliest to show on video). Those are searchable and can be further sorted by position, time, status, author, type and timecode.

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-576f3f56bf384955cfbfdf2a9479c43a1bb452f7%2F1994dde0b765213768315b45633bfacde903275a-2554x1341.gif?alt=media)

A common use case is to save and retrieve the annotations. Just like on WebViewer, this data is stored in the format of a XFDF string that can be exported using the [AnnotationsManager's exportAnnotations API](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#exportAnnotations__anchor).

One example is to have a custom save button added to the WebViewer's header using the [setHeaderItems](https://sdk.apryse.com/api/web/UI.html#.setHeaderItems) API that triggers a call to [exportAnnotations](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#exportAnnotations__anchor) and sending a POST request to your server to store it.

Assuming that there is a previously setup server running and a POST endpoint that accepts a body containing the XFDF string, you can achieve with the following code snippet:

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

```js
// setHeaderItems is a function that can be extracted from a WebViewer instance
setHeaderItems((header) => {
  header.push({
    type: "actionButton",
    img: "<svg>...</svg>",
    onClick: async () => {
      // Save annotations when button is clicked
      // widgets and links will remain in the document without changing so it isn't necessary to export them

      // Make a POST request with XFDF string
      const saveXfdfString = (documentId, xfdfString) => {
        return new Promise((resolve) => {
          fetch(`/server/annotationHandler.js?documentId=${documentId}`, {
            method: "POST",
            body: xfdfString,
          }).then((response) => {
            if (response.status === 200) {
              resolve();
            }
          });
        });
      };

      // Step 1: Get a list of annotations
      const annotations = docViewer.getAnnotationManager().getAnnotationsList();

      // Step 2: Export to a string
      var xfdfString = await annotManager.exportAnnotations({
        links: false,
        widgets: false,
        annotList: annotations,
      });

      // Step 3: Call your endpoint and save it
      await saveXfdfString(DOCUMENT_ID, xfdfString);
      alert("Annotations saved successfully.");
    },
  });
});
```

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

To load the annotations, the process is quite similar. It involves making a GET request to your server to retrieve the XFDF string, use the [AnnotationManager's importAnnotations](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#importAnnotations) and finally call `video.updateAnnotationsToTime(0)` to apply the annotations to the specific timeframes that they were created.

These calls need to be done *after* the video has been loaded. You can safely do these steps inside [docViewer's videoElementLoaded event](https://sdk.apryse.com/api/video/module-@pdftron_webviewer-video.html#~event:videoElementLoaded__anchor) like the following code snippet example:

{% tabs %}
{% tab title="JavaScript (SDK v8.0+)" %}
{% code lineNumbers="true" %}

```js
docViewer.addEventListener("videoElementLoaded", () => {
  const video = docViewer.getDocument().getVideo();

  // Make a GET request to get XFDF string
  const loadXfdfString = (documentId) => {
    return new Promise((resolve) => {
      fetch(`/server/annotationHandler.js?documentId=${documentId}`, {
        method: "GET",
      }).then((response) => {
        if (response.status === 200) {
          response.text().then((xfdfString) => {
            resolve(xfdfString);
          });
        }
      });
    });
  };

  // Step 1: Make a request to your server to get the XFDF string
  loadXfdfString(DOCUMENT_ID)
    .then((xfdfString) => {
      // Step 2: Call importAnnotations to save annotations to AnnotationManager
      const annotManager = docViewer.getAnnotationManager();
      return annotManager.importAnnotations(xfdfString);
    })
    .then(() => {
      // Step 3: Apply the annotations to the video timeframes
      video.updateAnnotationsToTime(0);
    });
});
```

{% endcode %}
{% endtab %}

{% tab title="JavaScript (SDK v6.0+)" %}
{% code lineNumbers="true" %}

```js
docViewer.on("videoElementLoaded", () => {
  const video = docViewer.getDocument().getVideo();

  // Make a GET request to get XFDF string
  const loadXfdfString = (documentId) => {
    return new Promise((resolve) => {
      fetch(`/server/annotationHandler.js?documentId=${documentId}`, {
        method: "GET",
      }).then((response) => {
        if (response.status === 200) {
          response.text().then((xfdfString) => {
            resolve(xfdfString);
          });
        }
      });
    });
  };

  // Step 1: Make a request to your server to get the XFDF string
  loadXfdfString(DOCUMENT_ID)
    .then((xfdfString) => {
      // Step 2: Call importAnnotations to save annotations to AnnotationManager
      const annotManager = docViewer.getAnnotationManager();
      return annotManager.importAnnotations(xfdfString);
    })
    .then(() => {
      // Step 3: Apply the annotations to the video timeframes
      video.updateAnnotationsToTime(0);
    });
});
```

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

## Programmatically adding Annotations

Annotations can also be added programmatically.

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

```js
docViewer.on("videoElementLoaded", () => {
  const annot = new instance.Core.Annotations.RectangleAnnotation({
    Author: 'Test Author',
    Subject: 'Rectangle',
    ToolName: 'AnnotationCreateRectangle',
    Width: 231.5,
    Height: 182.5,
    X: 321,
    Y: 40,
    Listable: true,
    Color: new instance.Core.Annotations.Color(78, 125, 233, 1),
    StrokeColor: new instance.Core.Annotations.Color(78, 125, 233, 1),
    StrokeThickness: 5,
    Opacity: 1,
  });

  // Annot time is in seconds
  annot.setStartTime(0);
  annot.setEndTime(100);

  const video = getVideo();
  annotManager.addAnnotations([ annot ]);

  // Apply the annotations to the video timeframes
  video.updateAnnotationsToTime(0);
});
```

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

For a complete working sample, you can check out the [official WebViewer Video Sample](https://github.com/ApryseSDK/webviewer-video-sample/).


---

# 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 by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

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

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
