<div align="center">

# ass-html5

Display ASS/SSA subtitles on html5 videos

![CI workflow](https://github.com/luxluth/ass-html5/actions/workflows/main.yml/badge.svg)
![Publish workflow](https://github.com/luxluth/ass-html5/actions/workflows/publish.yml/badge.svg)
![License](https://img.shields.io/github/license/luxluth/ass-html5?color=blue)
![npm bundle size](https://img.shields.io/bundlephobia/min/ass-html5)
![npm](https://img.shields.io/npm/v/ass-html5?logo=npm&color=white&link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Fass-html5)

</div>

## Table of Contents

- [ass-html5](#ass-html5)
  - [Table of Contents](#table-of-contents)
  - [Installation](#installation)
  - [Features](#features)
  - [Usage](#usage)
    - [Options](#options)
    - [Simple HTML](#simple-html)
    - [Svelte and Plry](#svelte-and-plry)
    - [videojs](#videojs)
- [Credits](#credits)

## Installation

```bash
pnpm add ass-html5
```

## Features

- **Karaoke Support**: Full support for `\k`, `\kf`, `\ko`, and `\K` tags.
- **Vector Drawings**: High-performance rendering of ASS vector paths (`\p`) using `Path2D`.
- **Clipping**: Support for rectangular and vector clipping/masking (`\clip`, `\iclip`).
- **Advanced Transformations**:
  - Rotation around all axes (`\frz`, `\frx`, `\fry`).
  - Scaling (`\fscx`, `\fscy`) and Shearing (`\fax`, `\fay`).
  - Custom rotation origin (`\org`).
- **Smart Wrapping**: Implementation of `\q` styles for optimal line breaking.
- **Collision Handling**: Automatic vertical shifting to prevent overlapping subtitles.
- **Blur Effects**: Edge blur (`\be`) and Gaussian blur (`\blur`) using Canvas filters.
- **Performance Optimized**: Built-in layer caching to minimize redundant redraws.

## Usage

### Options

| option  |                                                 description                                                 | required |             type              |        default        |
| :-----: | :---------------------------------------------------------------------------------------------------------: | :------: | :---------------------------: | :-------------------: |
| assText |                                             The ass text string                                             |    🚫    |           `string`            |      `undefined`      |
| subUrl  |                                      URL to fetch the ASS content from                                      |    🚫    |           `string`            |      `undefined`      |
|  video  | The video to display the subtile on. Can be either an `HTMLVideoElement` or `string` (html query selector ) |    ✅    | `HTMLVideoElement` / `string` |      `undefined`      |
|  fonts  |                                            Custom fonts to load                                             |    🚫    |  [`Font[]`](src/ass.ts#L78)   |      `undefined`      |
| zIndex  |                                        zIndex of the rendering frame                                        |    🚫    |           `number`            | Drawn after the video |
| onReady |              A Callback that is invoked when the preprocess of the ass text by render is done               |    🚫    |         `() => void`          |      `undefined`      |
| logging |                                               Type of logging                                               |    🚫    |  [`LOGTYPE`](src/ass.ts#L50)  |   `LOGTYPE.DISABLE`   |

> One of `assText` or `subUrl` is required.

### Simple HTML

> [!NOTE]
> The simple `video` tag element, in fullscreen mode, the position of the video is absolutely on top of any element.
> No other element can go on top of it.
>
> It's therefore recommended to use a third party player rather than the native one. You can see an example with [plry](https://github.com/sampotts/plyr) [here](#svelte-and-plry).

```html
<script src="https://cdn.jsdelivr.net/npm/ass-html5@0.6.0/dist/ass.min.js"></script>
```

```html
<video src="/assets/video.mp4" id="video" controls></video>
```

```html
<script>
  document.addEventListener('DOMContentLoaded', async () => {
    // Using subUrl to fetch subtitle automatically
    const ass = new ASS.default({
      subUrl: '/assets/video.ass',
      video: document.getElementById('video')
    });
    await ass.render();

    // You can switch subtitles dynamically:
    // await ass.setSubUrl('/assets/other.ass');
    // OR
    // await ass.setAssText('[Script Info]...');
  });
</script>
```

### [Svelte](https://github.com/sveltejs/svelte) and [Plry](https://github.com/sampotts/plyr)

```svelte
<script lang="ts">
    import video from '$lib/assets/video.mp4'
    import cc from '$lib/assets/cc.ass?url'
    import ASS from 'ass-html5'
    import { onMount } from 'svelte';
    import Plyr from 'plyr'

    const ass = new ASS({
        subUrl: cc,
        video: "#video-test"
    })

    let vidElement: HTMLVideoElement
    let player: Plyr
    onMount(async () => {
        player = new Plyr(vidElement)
        await ass.render()
    })

</script>

<div class="video-container">
    <!-- svelte-ignore a11y-media-has-caption -->
    <video
        preload="metadata"
        src="{video}"
        id="video-test"
        controls
        autoplay
        class="vid"
        bind:this={vidElement}
    ></video>
</div>
```

### [videojs](https://github.com/videojs/video.js)

In the `head` :

```html
<script src="https://cdn.jsdelivr.net/npm/ass-html5@0.6.2/dist/ass.min.js" defer></script>
<script src="https://vjs.zencdn.net/8.3.0/video.min.js" defer></script>
<link href="https://vjs.zencdn.net/8.3.0/video-js.css" rel="stylesheet" />
```

In the `body` :

```html
<video
  id="my-video"
  class="video-js"
  controls
  preload="auto"
  width="1280"
  height="720"
  data-setup="{}"
>
  <source src="assets/video.mp4" type="video/mp4" />
</video>
```

In the `script` tag :

```html
<script>
  document.addEventListener('DOMContentLoaded', async () => {
    let res = await fetch('/assets/video.ass');
    let assSubs = await res.text();

    var player = videojs('my-video');

    player.ready(async () => {
      // Get the video element from the player
      var videoElement = player.el().getElementsByTagName('video')[0];
      const ass = new ASS.default({
        assText: assSubs,
        video: videoElement
      });
      await ass.render();
    });
  });
</script>
```

---

# Credits

Thanks to the [ass-compiler](https://github.com/weizhenye/ass-compiler/) by weizhenye.
