Display ASS/SSA subtitles on html video

Installation

pnpm add ass-html5

v0.6.0

Minor Changes

  • e8c46a1: Implementig most of the SSA features

Full Changelog: https://github.com/luxluth/ass-html5/compare/v0.5.4…v0.6.0

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.

Examples

Native Player

Basic native player example. In fullscreen mode, the subtitles will not be visible.

code
<!doctype html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<script src="https://cdn.jsdelivr.net/npm/ass-html5@0.6.0/dist/ass.min.js" defer></script>
		<meta name="viewport" content="width=device-width, initial-scale=1" />
		<title>Native player</title>
	</head>
	<body>
		<video id="video" controls preload="auto">
			<source src="https://v.animethemes.moe/Kaijuu8Gou-ED1.webm" type="video/webm" />
		</video>
	</body>

	<script>
		var fonts = [
			{
				family: 'Trebuchet MS',
				url: '/fonts/trebuc_0.ttf',
				descriptors: { style: 'normal' }
			}
		];
		document.addEventListener('DOMContentLoaded', async () => {
			let res = await fetch('/examples/kaiju.ass');
			let assSubs = await res.text();

			const ass = new ASS.default({
				assText: assSubs,
				video: document.getElementById('video'),
				fonts: fonts
			});
			await ass.render();
		});
	</script>
</html>

<style>
	* {
		box-sizing: border-box;
		padding: 0;
		margin: 0;
	}

	html {
		background-color: #17181c;
		height: 100%;
		width: 100%;
	}

	body {
		height: 100%;
		width: 100%;
		display: flex;
		align-items: center;
		justify-content: center;
	}

	video {
		width: 100%;
		height: 100%;
	}
</style>
subtitles
[Script Info]
; Script generated by Aegisub 3.3.3
; http://www.aegisub.org/
ScriptType: v4.00+
PlayResX: 3840
PlayResY: 2160
ScaledBorderAndShadow: yes
YCbCr Matrix: TV.709

[Aegisub Project Garbage]

[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Trebuchet MS,120,&H0000BAFF,&H00000000,&H00000000,&H00000000,-1,-1,0,0,133,100,0,0,1,3,3.6,8,100,100,75,1
Style: Bright,Trebuchet MS,120,&H00FFFFFF,&H00000000,&H0000BAFF,&H00000000,-1,-1,0,0,133,100,0,0,1,3,3.6,8,100,100,75,1

[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:00.63,0:00:03.57,Default,,0,0,0,,Nobody got you the way I do
Dialogue: 0,0:00:04.00,0:00:07.05,Default,,0,0,0,,Whatever demons u fighting through
Dialogue: 0,0:00:07.45,0:00:10.52,Default,,0,0,0,,When you need somebody to turn to
Dialogue: 0,0:00:10.94,0:00:13.86,Default,,0,0,0,,Nobody got you the way I do
Dialogue: 0,0:00:14.89,0:00:17.46,Bright,,0,0,0,,I’d take the fall
Dialogue: 0,0:00:17.76,0:00:20.91,Bright,,0,0,0,,If ever you feel like there’s no one at all
Dialogue: 0,0:00:21.20,0:00:21.70,Bright,,0,0,0,,Ah yea and
Dialogue: 0,0:00:21.70,0:00:24.35,Bright,,0,0,0,,I’d stay through the night
Dialogue: 0,0:00:24.65,0:00:27.74,Bright,,0,0,0,,When you got monsters tryna take you alive
Dialogue: 0,0:00:28.09,0:00:31.17,Bright,,0,0,0,,There ain’t no people or lines
Dialogue: 0,0:00:31.65,0:00:34.59,Bright,,0,0,0,,That i wouldn’t cross if u need me to
Dialogue: 0,0:00:34.89,0:00:38.27,Bright,,0,0,0,,I’m out here steppin on mines
Dialogue: 0,0:00:38.59,0:00:41.50,Bright,,0,0,0,,And I think it’s finally time that you knew
Dialogue: 0,0:00:41.79,0:00:45.10,Bright,,0,0,0,,Nobody got you the way I do
Dialogue: 0,0:00:45.21,0:00:48.26,Default,,0,0,0,,Whatever demons u fightin through
Dialogue: 0,0:00:48.61,0:00:51.59,Default,,0,0,0,,When you need somebody to turn to
Dialogue: 0,0:00:52.06,0:00:54.06,Default,,0,0,0,,Nobody got you the way I do
Dialogue: 0,0:00:55.27,0:00:58.61,Bright,,0,0,0,,Nobody got u without no shade
Dialogue: 0,0:00:58.89,0:01:02.04,Bright,,0,0,0,,I’ll take the good and the bad all day
Dialogue: 0,0:01:02.20,0:01:05.45,Bright,,0,0,0,,When you need somebody to turn to
Dialogue: 0,0:01:05.56,0:01:08.95,Bright,,0,0,0,,Nobody got you the way I do
Dialogue: 0,0:01:09.62,0:01:12.11,Bright,,0,0,0,,Nobody Nobody Nobody
Dialogue: 0,0:01:13.03,0:01:15.67,Bright,,0,0,0,,Got you the way I do
Dialogue: 0,0:01:16.89,0:01:18.89,Bright,,0,0,0,,Nobody Nobody Nobody
Dialogue: 0,0:01:19.47,0:01:22.12,Bright,,0,0,0,,Nobody got you the way I do
video

VideoJs

An example that uses the well known videojs

code
<!doctype html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<script src="https://cdn.jsdelivr.net/npm/ass-html5@0.6.0/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" />
		<meta name="viewport" content="width=device-width, initial-scale=1" />
		<title>Videojs player player</title>
	</head>
	<body>
		<video id="video" class="video-js" controls preload="auto" data-setup="{}">
			<source src="https://v.animethemes.moe/Kaijuu8Gou-ED1.webm" type="video/webm" />
		</video>
	</body>

	<script>
		var fonts = [
			{
				family: 'Trebuchet MS',
				url: '/fonts/trebuc_0.ttf',
				descriptors: { style: 'normal' }
			}
		];

		document.addEventListener('DOMContentLoaded', async () => {
			let res = await fetch('/examples/kaiju.ass');
			let assSubs = await res.text();

			var player = videojs('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,
					fonts: fonts
				});
				await ass.render();
			});
		});
	</script>
</html>

<style>
	* {
		box-sizing: border-box;
		padding: 0;
		margin: 0;
	}

	html {
		background-color: #17181c;
		height: 100%;
		width: 100%;
	}

	body {
		height: 100%;
		width: 100%;
		display: flex;
		align-items: center;
		justify-content: center;
	}

	#video {
		width: 100%;
		height: 100%;
	}
</style>
subtitles
[Script Info]
; Script generated by Aegisub 3.3.3
; http://www.aegisub.org/
ScriptType: v4.00+
PlayResX: 3840
PlayResY: 2160
ScaledBorderAndShadow: yes
YCbCr Matrix: TV.709

[Aegisub Project Garbage]

[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Trebuchet MS,120,&H0000BAFF,&H00000000,&H00000000,&H00000000,-1,-1,0,0,133,100,0,0,1,3,3.6,8,100,100,75,1
Style: Bright,Trebuchet MS,120,&H00FFFFFF,&H00000000,&H0000BAFF,&H00000000,-1,-1,0,0,133,100,0,0,1,3,3.6,8,100,100,75,1

[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:00.63,0:00:03.57,Default,,0,0,0,,Nobody got you the way I do
Dialogue: 0,0:00:04.00,0:00:07.05,Default,,0,0,0,,Whatever demons u fighting through
Dialogue: 0,0:00:07.45,0:00:10.52,Default,,0,0,0,,When you need somebody to turn to
Dialogue: 0,0:00:10.94,0:00:13.86,Default,,0,0,0,,Nobody got you the way I do
Dialogue: 0,0:00:14.89,0:00:17.46,Bright,,0,0,0,,I’d take the fall
Dialogue: 0,0:00:17.76,0:00:20.91,Bright,,0,0,0,,If ever you feel like there’s no one at all
Dialogue: 0,0:00:21.20,0:00:21.70,Bright,,0,0,0,,Ah yea and
Dialogue: 0,0:00:21.70,0:00:24.35,Bright,,0,0,0,,I’d stay through the night
Dialogue: 0,0:00:24.65,0:00:27.74,Bright,,0,0,0,,When you got monsters tryna take you alive
Dialogue: 0,0:00:28.09,0:00:31.17,Bright,,0,0,0,,There ain’t no people or lines
Dialogue: 0,0:00:31.65,0:00:34.59,Bright,,0,0,0,,That i wouldn’t cross if u need me to
Dialogue: 0,0:00:34.89,0:00:38.27,Bright,,0,0,0,,I’m out here steppin on mines
Dialogue: 0,0:00:38.59,0:00:41.50,Bright,,0,0,0,,And I think it’s finally time that you knew
Dialogue: 0,0:00:41.79,0:00:45.10,Bright,,0,0,0,,Nobody got you the way I do
Dialogue: 0,0:00:45.21,0:00:48.26,Default,,0,0,0,,Whatever demons u fightin through
Dialogue: 0,0:00:48.61,0:00:51.59,Default,,0,0,0,,When you need somebody to turn to
Dialogue: 0,0:00:52.06,0:00:54.06,Default,,0,0,0,,Nobody got you the way I do
Dialogue: 0,0:00:55.27,0:00:58.61,Bright,,0,0,0,,Nobody got u without no shade
Dialogue: 0,0:00:58.89,0:01:02.04,Bright,,0,0,0,,I’ll take the good and the bad all day
Dialogue: 0,0:01:02.20,0:01:05.45,Bright,,0,0,0,,When you need somebody to turn to
Dialogue: 0,0:01:05.56,0:01:08.95,Bright,,0,0,0,,Nobody got you the way I do
Dialogue: 0,0:01:09.62,0:01:12.11,Bright,,0,0,0,,Nobody Nobody Nobody
Dialogue: 0,0:01:13.03,0:01:15.67,Bright,,0,0,0,,Got you the way I do
Dialogue: 0,0:01:16.89,0:01:18.89,Bright,,0,0,0,,Nobody Nobody Nobody
Dialogue: 0,0:01:19.47,0:01:22.12,Bright,,0,0,0,,Nobody got you the way I do
video