├── .dockerignore
├── .github
└── workflows
│ └── emscripten.yml
├── .gitignore
├── .gitmodules
├── .vscode
└── settings.json
├── Dockerfile
├── LICENSE
├── Makefile
├── Makefile_licence
├── README.md
├── build
├── license_defaults
├── license_extract.sh
├── license_fullnotice
├── license_lint.awk
└── patches
│ ├── brotli
│ ├── .gitkeep
│ └── 0001-upstream_Enable-install-with-emscripten.patch
│ ├── fontconfig
│ ├── .gitkeep
│ └── 0001-disable-pthreads.patch
│ ├── freetype
│ ├── .gitkeep
│ └── 0002-disable-pthread.patch
│ └── harfbuzz
│ ├── .gitkeep
│ └── 0001-force_disable_pthread.patch
├── dist
├── COPYRIGHT
├── default.woff2
├── jassub-worker-modern.wasm
├── jassub-worker.js
├── jassub-worker.wasm
├── jassub-worker.wasm.js
├── jassub.es.js
└── jassub.umd.js
├── functions.mk
├── index.d.ts
├── package-lock.json
├── package.json
├── pnpm-lock.yaml
├── run-buildah-build.sh
├── run-common.sh
├── run-docker-build.sh
├── src
├── JASSUB.cpp
├── jassub.js
├── pre-worker.js
└── worker.js
└── vite.build.js
/.dockerignore:
--------------------------------------------------------------------------------
1 | # Ignore everything for build image step, /code is passed via a volume
2 | *
3 |
--------------------------------------------------------------------------------
/.github/workflows/emscripten.yml:
--------------------------------------------------------------------------------
1 | name: Emscripten
2 |
3 | on:
4 | push:
5 | branches: [main, ci]
6 | pull_request:
7 | workflow_dispatch:
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Cancel previous runs
14 | uses: styfle/cancel-workflow-action@0.9.1
15 | with:
16 | access_token: ${{ github.token }}
17 | all_but_latest: true
18 |
19 | - name: Checkout Base Repo
20 | uses: actions/checkout@v4
21 |
22 | - name: Checkout Submodules
23 | run: |
24 | git submodule sync
25 | git submodule update --init --recursive --force
26 |
27 | - name: Build Docker Image
28 | run: |
29 | docker build -t libass/jso .
30 |
31 | - name: Build Binaries
32 | run: |
33 | docker run --rm -v "${PWD}":/code libass/jso:latest
34 |
35 | - name: Upload Nightly Build
36 | uses: actions/upload-artifact@v4
37 | if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
38 | with:
39 | name: js
40 | path: dist
41 |
42 |
43 | update_gh-pages:
44 | if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main'
45 | needs: build
46 | runs-on: ubuntu-latest
47 | steps:
48 | - name: Checkout
49 | uses: actions/checkout@v4
50 | with:
51 | ref: gh-pages
52 |
53 | - name: Download nightly build
54 | uses: actions/download-artifact@v4
55 | with:
56 | name: js
57 | path: jassub/assets
58 |
59 | - name: Push new version
60 | run: |
61 | git config --global user.email "actions@noreply.github.com"
62 | git config --global user.name "GitHub Action"
63 | git add jassub/assets
64 | git commit -m "$(printf \
65 | "Update binaries to latest nightly\n\nFrom %s" "${{ github.sha }}")"
66 | git push origin gh-pages
67 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | a.out.js
2 | a.out.wasm
3 | a.out.js.mem
4 | a.out
5 | a.wasm
6 | src/parser.out
7 | lib/expat/xmlwf/xmlwf
8 | .libs/
9 | *.bc
10 | *.lo
11 | *.la
12 | *.o
13 | *.pyc
14 | *.tgz
15 | venv/
16 | node_modules/
17 | build/lib/*
18 | dist/js
19 | dist/libraries
20 | dist/license
21 | test
22 | .pnpm-store
23 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "lib/libass"]
2 | path = lib/libass
3 | url = https://github.com/libass/libass.git
4 | ignore = dirty
5 | [submodule "lib/fribidi"]
6 | path = lib/fribidi
7 | url = https://github.com/fribidi/fribidi.git
8 | ignore = dirty
9 | [submodule "lib/fontconfig"]
10 | path = lib/fontconfig
11 | url = https://gitlab.freedesktop.org/fontconfig/fontconfig.git
12 | ignore = dirty
13 | [submodule "lib/freetype"]
14 | path = lib/freetype
15 | url = https://gitlab.freedesktop.org/freetype/freetype.git
16 | ignore = dirty
17 | [submodule "lib/expat"]
18 | path = lib/expat
19 | url = https://github.com/libexpat/libexpat.git
20 | ignore = dirty
21 | [submodule "lib/harfbuzz"]
22 | path = lib/harfbuzz
23 | url = https://github.com/harfbuzz/harfbuzz.git
24 | ignore = dirty
25 | [submodule "lib/brotli"]
26 | path = lib/brotli
27 | url = https://github.com/google/brotli.git
28 | ignore = dirty
29 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.associations": {
3 | "array": "cpp",
4 | "string": "cpp",
5 | "string_view": "cpp"
6 | }
7 | }
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM docker.io/emscripten/emsdk:3.1.40
2 |
3 | RUN apt-get update && \
4 | apt-get install curl -y --no-install-recommends && \
5 | curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash - &&\
6 | apt-get install -y --no-install-recommends \
7 | build-essential \
8 | cmake \
9 | git \
10 | ragel \
11 | patch \
12 | libtool \
13 | itstool \
14 | pkg-config \
15 | python3 \
16 | gettext \
17 | autopoint \
18 | automake \
19 | autoconf \
20 | m4 \
21 | gperf \
22 | licensecheck \
23 | nodejs \
24 | && rm -rf /var/lib/apt/lists/*
25 |
26 | WORKDIR /code
27 | CMD ["bash", "-c", "sudo rm -rf dist/libraries; sudo rm -rf build/lib; make; sudo rm -rf dist/libraries; sudo rm -rf build/lib; env MODERN=1 make; sudo npm i; sudo node vite.build.js"]
28 |
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021-2022 JASSUB contributors
4 | Copyright (c) 2017-2021 JavascriptSubtitlesOctopus contributors
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # JASSUB.js - Makefile
2 |
3 | # make - Build Dependencies and the JASSUB.js
4 | BASE_DIR:=$(dir $(realpath $(firstword $(MAKEFILE_LIST))))
5 | DIST_DIR:=$(BASE_DIR)dist/libraries
6 |
7 | export CFLAGS = -O3 -flto -s USE_PTHREADS=0 -fno-rtti -fno-exceptions
8 | export CXXFLAGS = $(CFLAGS)
9 | export PKG_CONFIG_PATH = $(DIST_DIR)/lib/pkgconfig
10 | export EM_PKG_CONFIG_PATH = $(PKG_CONFIG_PATH)
11 |
12 | SIMD_ARGS = \
13 | -msimd128 \
14 | -msse \
15 | -msse2 \
16 | -msse3 \
17 | -mssse3 \
18 | -msse4 \
19 | -msse4.1 \
20 | -msse4.2 \
21 | -mavx \
22 | -mavx2 \
23 | -matomics \
24 | -mnontrapping-fptoint
25 |
26 | ifeq (${MODERN},1)
27 | WORKER_NAME = jassub-worker-modern
28 | WORKER_ARGS = \
29 | -s WASM=1 \
30 | $(SIMD_ARGS)
31 |
32 | override CFLAGS += $(SIMD_ARGS)
33 | override CXXFLAGS += $(SIMD_ARGS)
34 |
35 | else
36 | WORKER_NAME = jassub-worker
37 | WORKER_ARGS = \
38 | -s WASM=2
39 |
40 | endif
41 |
42 | all: jassub
43 | jassub: dist
44 |
45 | .PHONY: all jassub dist
46 |
47 | include functions.mk
48 |
49 | # FriBidi
50 | build/lib/fribidi/configure: lib/fribidi $(wildcard $(BASE_DIR)build/patches/fribidi/*.patch)
51 | $(call PREPARE_SRC_PATCHED,fribidi)
52 | cd build/lib/fribidi && $(RECONF_AUTO)
53 |
54 | $(DIST_DIR)/lib/libfribidi.a: build/lib/fribidi/configure
55 | cd build/lib/fribidi && \
56 | $(call CONFIGURE_AUTO) && \
57 | $(JSO_MAKE) -C lib/ fribidi-unicode-version.h && \
58 | $(JSO_MAKE) -C lib/ install && \
59 | $(JSO_MAKE) install-pkgconfigDATA
60 |
61 | # Expat
62 | build/lib/expat/configured: lib/expat
63 | $(call PREPARE_SRC_VPATH,expat)
64 | touch build/lib/expat/configured
65 |
66 | $(DIST_DIR)/lib/libexpat.a: build/lib/expat/configured
67 | cd build/lib/expat && \
68 | $(call CONFIGURE_CMAKE,$(BASE_DIR)lib/expat/expat) \
69 | -DEXPAT_BUILD_DOCS=off \
70 | -DEXPAT_SHARED_LIBS=off \
71 | -DEXPAT_BUILD_EXAMPLES=off \
72 | -DEXPAT_BUILD_FUZZERS=off \
73 | -DEXPAT_BUILD_TESTS=off \
74 | -DEXPAT_BUILD_TOOLS=off \
75 | && \
76 | $(JSO_MAKE) install
77 |
78 | # Brotli
79 | build/lib/brotli/configured: lib/brotli $(wildcard $(BASE_DIR)build/patches/brotli/*.patch)
80 | $(call PREPARE_SRC_PATCHED,brotli)
81 | touch build/lib/brotli/configured
82 |
83 | $(DIST_DIR)/lib/libbrotlidec.a: $(DIST_DIR)/lib/libbrotlicommon.a
84 | $(DIST_DIR)/lib/libbrotlicommon.a: build/lib/brotli/configured
85 | cd build/lib/brotli && \
86 | $(call CONFIGURE_CMAKE) && \
87 | $(JSO_MAKE) install
88 | # Normalise static lib names
89 | cd $(DIST_DIR)/lib/ && \
90 | for lib in *-static.a ; do mv "$$lib" "$${lib%-static.a}.a" ; done
91 |
92 |
93 | # Freetype without Harfbuzz
94 | build/lib/freetype/configure: lib/freetype $(wildcard $(BASE_DIR)build/patches/freetype/*.patch)
95 | $(call PREPARE_SRC_PATCHED,freetype)
96 | cd build/lib/freetype && $(RECONF_AUTO)
97 |
98 | build/lib/freetype/build_hb/dist_hb/lib/libfreetype.a: $(DIST_DIR)/lib/libbrotlidec.a build/lib/freetype/configure
99 | cd build/lib/freetype && \
100 | mkdir -p build_hb && \
101 | cd build_hb && \
102 | $(call CONFIGURE_AUTO,..) \
103 | --prefix="$$(pwd)/dist_hb" \
104 | --with-brotli=yes \
105 | --without-harfbuzz \
106 | && \
107 | $(JSO_MAKE) install
108 |
109 | # Harfbuzz
110 | build/lib/harfbuzz/configure: lib/harfbuzz $(wildcard $(BASE_DIR)build/patches/harfbuzz/*.patch)
111 | $(call PREPARE_SRC_PATCHED,harfbuzz)
112 | cd build/lib/harfbuzz && $(RECONF_AUTO)
113 |
114 | $(DIST_DIR)/lib/libharfbuzz.a: build/lib/freetype/build_hb/dist_hb/lib/libfreetype.a build/lib/harfbuzz/configure
115 | cd build/lib/harfbuzz && \
116 | EM_PKG_CONFIG_PATH=$(PKG_CONFIG_PATH):$(BASE_DIR)build/lib/freetype/build_hb/dist_hb/lib/pkgconfig \
117 | CFLAGS="-DHB_NO_MT $(CFLAGS)" \
118 | CXXFLAGS="-DHB_NO_MT $(CFLAGS)" \
119 | $(call CONFIGURE_AUTO) \
120 | --with-freetype \
121 | && \
122 | cd src && \
123 | $(JSO_MAKE) install-libLTLIBRARIES install-pkgincludeHEADERS install-pkgconfigDATA
124 |
125 | # Freetype with Harfbuzz
126 | $(DIST_DIR)/lib/libfreetype.a: $(DIST_DIR)/lib/libharfbuzz.a $(DIST_DIR)/lib/libbrotlidec.a
127 | cd build/lib/freetype && \
128 | EM_PKG_CONFIG_PATH=$(PKG_CONFIG_PATH):$(BASE_DIR)build/lib/freetype/build_hb/dist_hb/lib/pkgconfig \
129 | $(call CONFIGURE_AUTO) \
130 | --with-brotli=yes \
131 | --with-harfbuzz \
132 | && \
133 | $(JSO_MAKE) install
134 |
135 | # Fontconfig
136 | build/lib/fontconfig/configure: lib/fontconfig $(wildcard $(BASE_DIR)build/patches/fontconfig/*.patch)
137 | $(call PREPARE_SRC_PATCHED,fontconfig)
138 | cd build/lib/fontconfig && $(RECONF_AUTO)
139 |
140 | $(DIST_DIR)/lib/libfontconfig.a: $(DIST_DIR)/lib/libharfbuzz.a $(DIST_DIR)/lib/libexpat.a $(DIST_DIR)/lib/libfribidi.a $(DIST_DIR)/lib/libfreetype.a build/lib/fontconfig/configure
141 | cd build/lib/fontconfig && \
142 | $(call CONFIGURE_AUTO) \
143 | --disable-docs \
144 | --with-default-fonts=/fonts \
145 | && \
146 | $(JSO_MAKE) -C src/ install && \
147 | $(JSO_MAKE) -C fontconfig/ install && \
148 | $(JSO_MAKE) install-pkgconfigDATA
149 |
150 |
151 | # libass
152 | build/lib/libass/configured: lib/libass
153 | cd lib/libass && $(RECONF_AUTO)
154 | $(call PREPARE_SRC_VPATH,libass)
155 | touch build/lib/libass/configured
156 |
157 | $(DIST_DIR)/lib/libass.a: $(DIST_DIR)/lib/libfontconfig.a $(DIST_DIR)/lib/libharfbuzz.a $(DIST_DIR)/lib/libexpat.a $(DIST_DIR)/lib/libfribidi.a $(DIST_DIR)/lib/libfreetype.a $(DIST_DIR)/lib/libbrotlidec.a build/lib/libass/configured
158 | cd build/lib/libass && \
159 | $(call CONFIGURE_AUTO,../../../lib/libass) \
160 | --enable-large-tiles \
161 | --enable-fontconfig \
162 | && \
163 | $(JSO_MAKE) install
164 |
165 | LIBASS_DEPS = \
166 | $(DIST_DIR)/lib/libfribidi.a \
167 | $(DIST_DIR)/lib/libbrotlicommon.a \
168 | $(DIST_DIR)/lib/libbrotlidec.a \
169 | $(DIST_DIR)/lib/libfreetype.a \
170 | $(DIST_DIR)/lib/libexpat.a \
171 | $(DIST_DIR)/lib/libharfbuzz.a \
172 | $(DIST_DIR)/lib/libfontconfig.a \
173 | $(DIST_DIR)/lib/libass.a
174 |
175 |
176 | dist: $(LIBASS_DEPS) dist/js/$(WORKER_NAME).js dist/js/jassub.js
177 |
178 | # Dist Files https://github.com/emscripten-core/emscripten/blob/3.1.38/src/settings.js
179 |
180 | # args for increasing performance
181 | # https://github.com/emscripten-core/emscripten/issues/13899
182 | PERFORMANCE_ARGS = \
183 | -s BINARYEN_EXTRA_PASSES=--one-caller-inline-max-function-size=19306 \
184 | -s INVOKE_RUN=0 \
185 | -s DISABLE_EXCEPTION_CATCHING=1 \
186 | -s TEXTDECODER=1 \
187 | -s MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION=1 \
188 | --no-heap-copy \
189 | -flto \
190 | -fno-exceptions \
191 | -O3
192 |
193 | # args for reducing size
194 | SIZE_ARGS = \
195 | -s POLYFILL=0 \
196 | -s FILESYSTEM=0 \
197 | -s AUTO_JS_LIBRARIES=0 \
198 | -s AUTO_NATIVE_LIBRARIES=0 \
199 | -s HTML5_SUPPORT_DEFERRING_USER_SENSITIVE_REQUESTS=0 \
200 | -s INCOMING_MODULE_JS_API="[]" \
201 | -s USE_SDL=0 \
202 | -s MINIMAL_RUNTIME=1
203 |
204 | # args that are required for this to even work at all
205 | COMPAT_ARGS = \
206 | -s EXPORTED_FUNCTIONS="['_malloc']" \
207 | -s EXPORT_KEEPALIVE=1 \
208 | -s EXPORTED_RUNTIME_METHODS="['getTempRet0', 'setTempRet0']" \
209 | -s IMPORTED_MEMORY=1 \
210 | -s MIN_CHROME_VERSION=27 \
211 | -s MIN_SAFARI_VERSION=60005 \
212 | -mbulk-memory \
213 | --memory-init-file 0
214 |
215 | dist/js/$(WORKER_NAME).js: src/JASSUB.cpp src/worker.js src/pre-worker.js
216 | mkdir -p dist/js
217 | emcc src/JASSUB.cpp $(LIBASS_DEPS) \
218 | $(WORKER_ARGS) \
219 | $(PERFORMANCE_ARGS) \
220 | $(SIZE_ARGS) \
221 | $(COMPAT_ARGS) \
222 | --pre-js src/pre-worker.js \
223 | -s ENVIRONMENT=worker \
224 | -s EXIT_RUNTIME=0 \
225 | -s ALLOW_MEMORY_GROWTH=1 \
226 | -s MODULARIZE=1 \
227 | -s EXPORT_ES6=1 \
228 | -lembind \
229 | -o $@
230 |
231 | dist/js/jassub.js: src/jassub.js
232 | mkdir -p dist/js
233 | cp src/jassub.js $@
234 |
235 | # dist/license/all:
236 | # @#FIXME: allow -j in toplevel Makefile and reintegrate licence extraction into this file
237 | # make -j "$$(nproc)" -f Makefile_licence all
238 |
239 | # dist/js/COPYRIGHT: dist/license/all
240 | # cp "$<" "$@"
241 |
242 | # Clean Tasks
243 |
244 | clean: clean-dist clean-libs clean-jassub
245 |
246 | clean-dist:
247 | rm -frv dist/libraries/*
248 | rm -frv dist/js/*
249 | rm -frv dist/license/*
250 | clean-libs:
251 | rm -frv dist/libraries build/lib
252 | clean-jassub:
253 | cd src && git clean -fdX
254 |
255 | git-checkout:
256 | git submodule sync --recursive && \
257 | git submodule update --init --recursive
258 |
259 | SUBMODULES := brotli expat fontconfig freetype fribidi harfbuzz libass
260 | git-smreset: $(addprefix git-, $(SUBMODULES))
261 |
262 | $(foreach subm, $(SUBMODULES), $(eval $(call TR_GIT_SM_RESET,$(subm))))
263 |
264 | server: # Node http server npm i -g http-server
265 | http-server
266 |
267 | .PHONY: clean clean-dist clean-libs clean-jassub git-checkout git-smreset server
268 |
--------------------------------------------------------------------------------
/Makefile_licence:
--------------------------------------------------------------------------------
1 | # FIXME: temporarily split Makefile to parallelise licence info extraction
2 | # once -j can be passed to the toplevel Makefile this should be moved
3 | # back into the main Makefile
4 |
5 | all: dist/license/all
6 |
7 | LIB_LICENSES := brotli expat freetype fribidi fontconfig harfbuzz libass
8 | LIB_LICENSES_FINDOPT_brotli := -path ./research -prune -false -o
9 | LIB_LICENSES_FINDOPT_expat := -path ./expat/fuzz -prune -false -o
10 | LIB_LICENSES_FINDOPT_freetype := -path ./src/tools -prune -false -o
11 | LIB_LICENSES_FINDOPT_fribidi := -path ./bin -prune -false -o
12 | LIB_LICENSES_FINDOPT_harfbuzz := -path ./test -prune -false -o
13 |
14 | $(addprefix dist/license/, $(LIB_LICENSES)): dist/license/%: .git/modules/lib/%/HEAD build/license_extract.sh build/license_defaults
15 | @mkdir -p dist/license
16 | (cd "lib/$*" && FINDOPTS="$(LIB_LICENSES_FINDOPT_$*)" \
17 | ../../build/license_extract.sh ../../build/license_defaults "$*" .) > $@
18 |
19 | dist/license/jassub: .git/HEAD build/license_extract.sh
20 | @mkdir -p dist/license
21 | build/license_extract.sh build/license_defaults jassub src > dist/license/jassub
22 |
23 | dist/license/all: dist/license/jassub $(addprefix dist/license/, $(LIB_LICENSES)) build/license_fullnotice build/license_lint.awk
24 | @echo "# The following lists all copyright notices and licenses for the" > dist/license/all
25 | @echo "# work contained in JASSUB per project." >> dist/license/all
26 | @echo "" >> dist/license/all
27 |
28 | @echo "Concatenate extracted license info..."
29 | @$(foreach LIB_PROJ, jassub $(LIB_LICENSES), \
30 | echo "# Project: $(LIB_PROJ)" >> dist/license/all && \
31 | cat dist/license/$(LIB_PROJ) >> dist/license/all && \
32 | ) :
33 |
34 | mv dist/license/all dist/license/all.tmp
35 | build/license_lint.awk dist/license/all.tmp build/license_fullnotice
36 | cat dist/license/all.tmp build/license_fullnotice > dist/license/all
37 |
38 | dist/js/COPYRIGHT: dist/license/all
39 | cp "$<" "$@"
40 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | JASSUB
3 |
4 |
5 | JavaScript SSA/ASS Subtitle Renderer.
6 |
7 | JASSUB is a JS wrapper for libass , which renders SSA/ASS subtitles directly in your browser. It uses Emscripten to compile libass' C++ code to WASM.
8 |
9 |
10 | Online Demos
11 |
12 |
13 | ## Features
14 | - Supports most SSA/ASS features (everything libass supports)
15 | - Supports all OpenType, TrueType and WOFF fonts, as well as embedded fonts
16 | - Supports anamorphic videos [(on browsers which support it)](https://caniuse.com/mdn-api_htmlvideoelement_requestvideoframecallback)
17 | - Supports different video color spaces [(on browsers which support it)](https://caniuse.com/mdn-api_videocolorspace)
18 | - Capable of using local fonts [(on browsers which support it)](https://caniuse.com/mdn-api_window_querylocalfonts)
19 | - Works fast (all the heavy lifting is done by WebAssembly)
20 | - Is fully threaded (on browsers which support it, it's capable of working fully on a separate thread)
21 | - Is asynchronous (renders when available, not in order of execution)
22 | - Benefits from hardware acceleration (uses hardware accelerated canvas API's)
23 | - Doesn't manipulate the DOM to render subtitles
24 | - Easy to use - just connect it to video element
25 |
26 | ## Isn't this just the same thing as JavascriptSubtitlesOctopus?
27 | No. See this comparison.
28 |
29 | ## Usage
30 | By default all you need to do is copy the files from the `dist/` folder of the repository into the same folder as where your JS runs, then do:
31 | ```js
32 | import JASSUB from './jassub.es.js'
33 |
34 | const renderer = new JASSUB({
35 | video: document.querySelector('video'),
36 | subUrl: './tracks/sub.ass'
37 | })
38 | ```
39 | `Note:` while the `dist/` folder includes a UMD dist it still uses modern syntax. If you want backwards compatibility with older browsers I recommend you run it tru babel.
40 |
41 | If you use a bundler like Vite, you can instead do:
42 | ```shell
43 | npm i jassub
44 | ```
45 |
46 | ```js
47 | import JASSUB from 'jassub'
48 | import workerUrl from 'jassub/dist/jassub-worker.js?url'
49 | import wasmUrl from 'jassub/dist/jassub-worker.wasm?url'
50 |
51 | const renderer = new JASSUB({
52 | video: document.querySelector('video'),
53 | subContent: subtitleString,
54 | workerUrl, // you can also use: `new URL('jassub/dist/jassub-worker.js', import.meta.url)` instead of importing it as an url
55 | wasmUrl
56 | })
57 | ```
58 | ## Using only with canvas
59 | You're also able to use it without any video. However, that requires you to set the time the subtitles should render at yourself:
60 | ```js
61 | import JASSUB from './jassub.es.js'
62 |
63 | const renderer = new JASSUB({
64 | canvas: document.querySelector('canvas'),
65 | subUrl: './tracks/sub.ass'
66 | })
67 |
68 | renderer.setCurrentTime(15)
69 | ```
70 | ## Changing subtitles
71 | You're not limited to only display the subtitle file you referenced in your options. You're able to dynamically change subtitles on the fly. There's three methods that you can use for this specifically:
72 |
73 | - `setTrackByUrl(url):` works the same as the `subUrl` option. It will set the subtitle to display by its URL.
74 | - `setTrack(content):` works the same as the `subContent` option. It will set the subtitle to dispaly by its content.
75 | - `freeTrack():` this simply removes the subtitles. You can use the two methods above to set a new subtitle file to be displayed.
76 | ```js
77 | renderer.setTrackByUrl('/newsub.ass')
78 | ```
79 | ## Cleaning up the object
80 | After you're finished with rendering the subtitles. You need to call the `destroy()` method to correctly destroy the object.
81 | ```js
82 | const renderer = new JASSUB(options)
83 | // After you've finished using it...
84 | renderer.destroy()
85 | ```
86 | ## Options
87 | The default options are best, and automatically fallback to the next fastest options in line, when the API's they use are unsupported. You can however forcefully change this behavior by specifying options. These options are included in the JSDoc of the object, so if your editor supports JSDoc IntelliSense you will see these exact descriptions when calling methods and specifying options.
88 |
89 | - `{Object} options` Settings object.
90 | - `{HTMLVideoElement} options.video` Video to use as target for rendering and event listeners. Optional if canvas is specified instead.
91 | - `{HTMLCanvasElement} options.canvas` { Optional } Canvas to use for manual handling. Not required if video is specified.
92 | - `{'js'|'wasm'} options.blendMode` { Optional = 'js' } Which image blending mode to use. WASM will perform better on lower end devices, JS will perform better if the device and browser supports hardware acceleration.
93 | - `{Boolean} options.asyncRender` { Optional = true } Whether or not to use async rendering, which offloads the CPU by creating image bitmaps on the GPU.
94 | - `{Boolean} options.offscreenRender` { Optional = true } Whether or not to render things fully on the worker, greatly reduces CPU usage.
95 | - `{Boolean} options.onDemandRender` { Optional = true } Whether or not to render subtitles as the video player renders frames, rather than predicting which frame the player is on using events.
96 | - `{Number} options.targetFps` { Optional = true } Target FPS to render subtitles at. Ignored when onDemandRender is enabled.
97 | - `{Number} options.timeOffset` { Optional = 0 } Subtitle time offset in seconds.
98 | - `{Boolean} options.debug` { Optional = false } Whether or not to print debug information.
99 | - `{Number} options.prescaleFactor` { Optional = 1.0 } Scale down (< 1.0) the subtitles canvas to improve performance at the expense of quality, or scale it up (> 1.0).
100 | - `{Number} options.prescaleHeightLimit` { Optional = 1080 } The height in pixels beyond which the subtitles canvas won't be prescaled.
101 | - `{Number} options.maxRenderHeight` { Optional = 0 } The maximum rendering height in pixels of the subtitles canvas. Beyond this subtitles will be upscaled by the browser.
102 | - `{Boolean} options.dropAllAnimations` { Optional = false } Attempt to discard all animated tags. Enabling this may severly mangle complex subtitles and should only be considered as an last ditch effort of uncertain success for hardware otherwise incapable of displaing anything. Will not reliably work with manually edited or allocated events.
103 | - `{Boolean} options.dropAllBlur` { Optional = false } The holy grail of performance gains. If heavy TS lags a lot, disabling this will make it ~x10 faster. This drops blur from all added subtitle tracks making most text and backgrounds look sharper, this is way less intrusive than dropping all animations, while still offering major performance gains.
104 | - `{String} options.workerUrl` { Optional = 'jassub-worker.js' } The URL of the worker.
105 | - `{String} options.wasmUrl` { Optional = 'jassub-worker.wasm' } The URL of the worker WASM.
106 | - `{String} options.legacyWasmUrl` { Optional = 'jassub-worker.wasm.js' } The URL of the legacy worker WASM. Only loaded if the browser doesn't support WASM.
107 | - `{String} options.modernWasmUrl` { Optional } The URL of the modern worker WASM. This includes faster ASM instructions, but is only supported by newer browsers, disabled if the URL isn't defined.
108 | - `{String} [options.subUrl=options.subContent]` The URL of the subtitle file to play.
109 | - `{String} [options.subContent=options.subUrl]` The content of the subtitle file to play.
110 | - `{String[]|Uint8Array[]} options.fonts` { Optional } An array of links or Uint8Arrays to the fonts used in the subtitle. If Uint8Array is used the array is copied, not referenced. This forces all the fonts in this array to be loaded by the renderer, regardless of if they are used.
111 | - `{Object} options.availableFonts` { Optional = {'liberation sans': './default.woff2'}} Object with all available fonts - Key is font family in lower case, value is link or Uint8Array: { arial: '/font1.ttf' }. These fonts are selectively loaded if detected as used in the current subtitle track.
112 | - `{String} options.fallbackFont` { Optional = 'liberation sans' } The font family key of the fallback font in availableFonts to use if the other font for the style is missing special glyphs or unicode.
113 | - `{Boolean} options.useLocalFonts` { Optional = false } If the Local Font Access API is enabled [chrome://flags/#font-access], the library will query for permissions to use local fonts and use them if any are missing. The permission can be queried beforehand using navigator.permissions.request({ name: 'local-fonts' }).
114 | - `{Number} options.libassMemoryLimit` { Optional } libass bitmap cache memory limit in MiB (approximate).
115 | - `{Number} options.libassGlyphLimit` { Optional } libass glyph cache memory limit in MiB (approximate).
116 |
117 | ## Methods and properties
118 | This library has a lot of methods and properties, however many aren't made for manual use or have no effect when changing, those are usually prefixed with `_`. Most of these never need to be called by the user.
119 |
120 | ### List of properties:
121 | - `debug` - -||-
122 | - `prescaleFactor` - -||-
123 | - `prescaleHeightLimit` - -||-
124 | - `maxRenderHeight` - -||-
125 | - `busy` - Boolean which specifies if the renderer is currently busy.
126 | - `timeOffset` - -||-
127 | ### List of methods:
128 | - `resize(width = 0, height = 0, top = 0, left = 0, force)` - Resize the canvas to given parameters. Auto-generated if values are ommited.
129 | - {Number} [width=0]
130 | - {Number} [height=0]
131 | - {Number} [top=0]
132 | - {Number} [left=0]
133 | - {Boolean} force
134 | - `setVideo(video)` - Change the video to use as target for event listeners.
135 | - {HTMLVideoElement} video
136 | - `setTrackByUrl(url)` - Overwrites the current subtitle content.
137 | - {String} url URL to load subtitles from.
138 | - `setTrack(content)` - Overwrites the current subtitle content.
139 | - {String} content Content of the ASS file.
140 | - `freeTrack()` - Free currently used subtitle track.
141 | - `setIsPaused(isPaused)` - Sets the playback state of the media.
142 | - {Boolean} isPaused Pause/Play subtitle playback.
143 | - `setRate(rate)` - Sets the playback rate of the media [speed multiplier].
144 | - {Number} rate Playback rate.
145 | - `setCurrentTime(isPaused, currentTime, rate)` - Sets the current time, playback state and rate of the subtitles.
146 | - {Boolean} [isPaused] Pause/Play subtitle playback.
147 | - {Number} [currentTime] Time in seconds.
148 | - {Number} [rate] Playback rate.
149 | - `destroy(err)` - Destroy the object, worker, listeners and all data.
150 | - {String} [err] Error to throw when destroying.
151 | - `sendMessage(target, data = {}, transferable)` - Send data and execute function in the worker.
152 | - {String} target Target function.
153 | - {Object} [data] Data for function.
154 | - {Transferable[]} [transferable] Array of transferables.
155 | - `createEvent(event)` - Create a new ASS event directly.
156 | - {ASS_Event} event
157 | - `setEvent(event, index)` - Overwrite the data of the event with the specified index.
158 | - {ASS_Event} event
159 | - {Number} index
160 | - `removeEvent(index)` - Remove the event with the specified index.
161 | - {Number} index
162 | - `getEvents(callback)` - Get all ASS events.
163 | - {function(Error|null, ASS_Event)} callback Function to callback when worker returns the events.
164 | - `createStyle(style)` - Create a new ASS style directly.
165 | - {ASS_Style} event
166 | - `setStyle (event, index)` - Overwrite the data of the style with the specified index.
167 | - {ASS_Style} event
168 | - {Number} index
169 | - `removeStyle (index)` - Remove the style with the specified index.
170 | - {Number} index
171 | - `getStyles (callback)` - Get all ASS styles.
172 | - {function(Error|null, ASS_Style)} callback Function to callback when worker returns the styles.
173 | - `addfont (font)` - Adds a font to the renderer.
174 | - {String|Uint8Array} font Font to add.
175 |
176 | ### ASS_Event object properties
177 | - `{Number} Start` - Start Time of the Event, in 0:00:00:00 format ie. Hrs:Mins:Secs:hundredths. This is the time elapsed during script playback at which the text will appear onscreen. Note that there is a single digit for the hours!
178 | - `{Number} Duration` - End Time of the Event, in 0:00:00:00 format ie. Hrs:Mins:Secs:hundredths. This is the time elapsed during script playback at which the text will disappear offscreen. Note that there is a single digit for the hours!
179 | - `{String} Style` - Style name. If it is "Default", then your own *Default style will be subtituted.
180 | - `{String} Name` - Character name. This is the name of the character who speaks the dialogue. It is for information only, to make the script is easier to follow when editing/timing.
181 | - `{Number} MarginL` - 4-figure Left Margin override. The values are in pixels. All zeroes means the default margins defined by the style are used.
182 | - `{Number} MarginR` - 4-figure Right Margin override. The values are in pixels. All zeroes means the default margins defined by the style are used.
183 | - `{Number} MarginV` - 4-figure Bottom Margin override. The values are in pixels. All zeroes means the default margins defined by the style are used.
184 | - `{String} Effect` - Transition Effect. This is either empty, or contains information for one of the three transition effects implemented in SSA v4.x
185 | - `{String} Text` - Subtitle Text. This is the actual text which will be displayed as a subtitle onscreen. Everything after the 9th comma is treated as the subtitle text, so it can include commas.
186 | - `{Number} ReadOrder` - Number in order of which to read this event.
187 | - `{Number} Layer` - Z-index overlap in which to render this event.
188 | - `{Number} _index` - (Internal) index of the event.
189 |
190 | ### ASS_Style object properties
191 | - `{String} Name` The name of the Style. Case sensitive. Cannot include commas.
192 | - `{String} FontName` The fontname as used by Windows. Case-sensitive.
193 | - `{Number} FontSize` Font size.
194 | - `{Number} PrimaryColour` A long integer BGR (blue-green-red) value. ie. the byte order in the hexadecimal equivelent of this number is BBGGRR
195 | - `{Number} SecondaryColour` A long integer BGR (blue-green-red) value. ie. the byte order in the hexadecimal equivelent of this number is BBGGRR
196 | - `{Number} OutlineColour` A long integer BGR (blue-green-red) value. ie. the byte order in the hexadecimal equivelent of this number is BBGGRR
197 | - `{Number} BackColour` This is the colour of the subtitle outline or shadow, if these are used. A long integer BGR (blue-green-red) value. ie. the byte order in the hexadecimal equivelent of this number is BBGGRR.
198 | - `{Number} Bold` This defines whether text is bold (true) or not (false). -1 is True, 0 is False. This is independant of the Italic attribute - you can have have text which is both bold and italic.
199 | - `{Number} Italic` Italic. This defines whether text is italic (true) or not (false). -1 is True, 0 is False. This is independant of the bold attribute - you can have have text which is both bold and italic.
200 | - `{Number} Underline` -1 or 0
201 | - `{Number} StrikeOut` -1 or 0
202 | - `{Number} ScaleX` Modifies the width of the font. [percent]
203 | - `{Number} ScaleY` Modifies the height of the font. [percent]
204 | - `{Number} Spacing` Extra space between characters. [pixels]
205 | - `{Number} Angle` The origin of the rotation is defined by the alignment. Can be a floating point number. [degrees]
206 | - `{Number} BorderStyle` 1=Outline + drop shadow, 3=Opaque box
207 | - `{Number} Outline` If BorderStyle is 1, then this specifies the width of the outline around the text, in pixels. Values may be 0, 1, 2, 3 or 4.
208 | - `{Number} Shadow` If BorderStyle is 1, then this specifies the depth of the drop shadow behind the text, in pixels. Values may be 0, 1, 2, 3 or 4. Drop shadow is always used in addition to an outline - SSA will force an outline of 1 pixel if no outline width is given.
209 | - `{Number} Alignment` This sets how text is "justified" within the Left/Right onscreen margins, and also the vertical placing. Values may be 1=Left, 2=Centered, 3=Right. Add 4 to the value for a "Toptitle". Add 8 to the value for a "Midtitle". eg. 5 = left-justified toptitle
210 | - `{Number} MarginL` This defines the Left Margin in pixels. It is the distance from the left-hand edge of the screen.The three onscreen margins (MarginL, MarginR, MarginV) define areas in which the subtitle text will be displayed.
211 | - `{Number} MarginR` This defines the Right Margin in pixels. It is the distance from the right-hand edge of the screen. The three onscreen margins (MarginL, MarginR, MarginV) define areas in which the subtitle text will be displayed.
212 | - `{Number} MarginV` This defines the vertical Left Margin in pixels. For a subtitle, it is the distance from the bottom of the screen. For a toptitle, it is the distance from the top of the screen. For a midtitle, the value is ignored - the text will be vertically centred.
213 | - `{Number} Encoding` This specifies the font character set or encoding and on multi-lingual Windows installations it provides access to characters used in multiple than one languages. It is usually 0 (zero) for English (Western, ANSI) Windows.
214 | - `{Number} treat_fontname_as_pattern`
215 | - `{Number} Blur`
216 | - `{Number} Justify`
217 |
218 | # How to build?
219 | ## Dependencies
220 | - git
221 | - emscripten (Configure the enviroment)
222 | - make
223 | - python3
224 | - cmake
225 | - pkgconfig
226 | - patch
227 | - libtool
228 | - autotools (autoconf, automake, autopoint)
229 | - gettext
230 | - ragel - Required by Harfbuzz
231 | - itstool - Required by Fontconfig
232 | - gperf - Required by Fontconfig
233 | - licensecheck
234 |
235 | ## Get the Source
236 | Run git clone --recursive https://github.com/ThaUnknown/jassub.git
237 |
238 | ## Build inside a Container
239 | ### Docker
240 | 1. Install Docker
241 | 2. ./run-docker-build.sh
242 | 3. Artifacts are in /dist/js
243 | ### Buildah
244 | 1. Install Buildah and a suitable backend for buildah run like crun or runc
245 | 2. ./run-buildah-build.sh
246 | 3. Artifacts are in /dist/js
247 | ## Build without Containers
248 | 1. Install the dependency packages listed above
249 | 2. make
250 | - If on macOS with libtool from brew, LIBTOOLIZE=glibtoolize make
251 | 3. Artifacts are in /dist/js
252 |
--------------------------------------------------------------------------------
/build/license_defaults:
--------------------------------------------------------------------------------
1 | # Tab seperated list of default license
2 | # and if given copyrightholder per project
3 | jassub Expat 2022-2022 JASSUB contributors
4 | subtitlesoctopus Expat 2017-2021 JavascriptSubtitlesOctopus contributors
5 |
6 | brotli Expat 2009, 2010, 2013-2016 by the Brotli Authors
7 | expat Expat 2000-2017 Expat development team / 1997-2000 Thai Open Source Software Center Ltd
8 | libass ISC 2006-2016 libass contributors
9 | fontconfig NTP~disclaimer 2000-2007 Keith Packard / 2005 Patrick Lam / 2009 Roozbeh Pournader / 2008,2009 Red Hat, Inc. / 2008 Danilo Šegan / 2012 Google, Inc.
10 |
11 | fribidi LGPL-2.1+
12 | harfbuzz MIT~old
13 | freetype FTL
14 |
--------------------------------------------------------------------------------
/build/license_extract.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Copyright 2021 Oneric
4 | # SPDX-License-Identifier: ISC
5 |
6 | # Postprocesses the output of licensecheck to automatically
7 | # generate our distribution notice.
8 | # licensecheck is packaged in Debian and its derivatives
9 | # and can be obtained from CPAN everywhere.
10 |
11 | usage() {
12 | echo "$0 " >&2
13 | exit 1
14 | }
15 |
16 | set -eu
17 |
18 | FINDOPTS="${FINDOPTS:-}"
19 | tabulator="$(printf '\t')"
20 |
21 | if [ "$#" -ne 3 ] || [ ! -e "$3" ] \
22 | || [ ! -e "$1" ] || [ -d "$1" ] ; then
23 | usage
24 | fi
25 |
26 | def_license="$(awk 'BEGIN{FS="\t+"} /^'"$2"'/ {print $2" | "$3}' "$1")"
27 | def_copy="${def_license#* | }"
28 | def_license="${def_license%% | *}"
29 | base_dir="$3"
30 |
31 | if [ -z "$def_license" ] || [ "$def_license" = "$def_copy" ] ; then
32 | echo "The map file appears to be borked or entry is missing!" >&2
33 | exit 1
34 | fi
35 |
36 | if command -v licensecheck >/dev/null ; then
37 | :
38 | else
39 | echo "Could not find licensecheck!" >&2
40 | exit 2
41 | fi
42 |
43 |
44 | find "$base_dir" $FINDOPTS -type f -regextype egrep -regex '.*\.(c|h|cpp|hpp|js)$' -exec \
45 | licensecheck --machine --copyright --deb-fmt '{}' \; \
46 | | awk -F"$tabulator" -v base_dir="$base_dir" \
47 | -v def_license="$def_license" -v def_copy="$def_copy" '
48 | BEGIN {
49 | split("", lcfiles) # Clear array with only pre-Issue 8 POSIX
50 | SUBSEP = sprintf("\n") # Seperator for pseudo-multidim arrays
51 |
52 | # Add default
53 | if(def_copy && def_license)
54 | ++lcfiles[def_license, def_copy]
55 | }
56 |
57 | 1 {
58 | if ($2 == "UNKNOWN")
59 | if (def_license) {
60 | $2 = def_license
61 | }else {
62 | print "ERROR: Unlicensed file "$1" matched!" | "cat>&2"
63 | print " If there is no default license, then" | "cat>&2"
64 | print " reporting this upstream might be a good idea." | "cat>&2"
65 | exit 1
66 | }
67 | if ($3 == "*No copyright*") {
68 | if (def_copy) {
69 | $3 = def_copy
70 | } else if (def_license != $2) {
71 | print "ERROR: Orphaned file "$1" and no default attribution!" | "cat>&2"
72 | exit 1
73 | } else {
74 | # Appears eg in freetype ; hope default copyright holder is correct
75 | next
76 | }
77 | }
78 |
79 | split($3, copyh, " / ")
80 | for(i in copyh)
81 | ++lcfiles[$2, copyh[i]];
82 | }
83 |
84 | END {
85 | # Group copyright holders per license
86 | # The second pass in END is required to only add each (license, copy) pair once
87 | # using pure POSIX-only awk.
88 | split("", tmp)
89 | for(lc in lcfiles) {
90 | split(lc, lico, SUBSEP)
91 | if(lico[1] in tmp)
92 | tmp[lico[1]] = tmp[lico[1]]""SUBSEP" "lico[2]
93 | else
94 | tmp[lico[1]] = lico[2]
95 | }
96 |
97 | for(license in tmp) {
98 | printf "License: %s\n", license
99 | printf "Copyright: %s\n", tmp[license]
100 | printf "\n"
101 | }
102 | }
103 | '
104 |
--------------------------------------------------------------------------------
/build/license_fullnotice:
--------------------------------------------------------------------------------
1 | # The following section contains the full license text or
2 | # where permitted a shorter notice for all works contained
3 | # in JASSUB listed above
4 | #
5 | # Note that the shortnames used here are identical to those used by Debian
6 | # and will in some cases differ from SPDX identifiers. See below.
7 | #
8 | # Debian-name | SPDX-name
9 | # ---------------+-------------------
10 | # MIT~old | MIT-Modern-Variant
11 | # Expat | MIT
12 | # NTP~disclaimer | (None; it is NTP with a more verbose warranty disclaimer)
13 |
14 | License: Expat
15 | Permission is hereby granted, free of charge, to any person obtaining
16 | a copy of this software and associated documentation files (the
17 | "Software"), to deal in the Software without restriction, including
18 | without limitation the rights to use, copy, modify, merge, publish,
19 | distribute, sublicense, and/or sell copies of the Software, and to permit
20 | persons to whom the Software is furnished to do so, subject to the
21 | following conditions:
22 | .
23 | The above copyright notice and this permission notice shall be included
24 | in all copies or substantial portions of the Software.
25 | .
26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
29 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
30 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
31 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
32 | USE OR OTHER DEALINGS IN THE SOFTWARE.
33 |
34 | License: FTL
35 | Portions of this software are copyright © 2021 The FreeType
36 | Project (www.freetype.org). All rights reserved.
37 | A copy of the FreeType License can be obtained at
38 | https://gitlab.freedesktop.org/freetype/freetype/-/blob/master/docs/FTL.TXT
39 |
40 | License: ISC
41 | Permission to use, copy, modify, and/or distribute this software for any
42 | purpose with or without fee is hereby granted, provided that the above
43 | copyright notice and this permission notice appear in all copies.
44 | .
45 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
46 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
47 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
48 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
49 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
50 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
51 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
52 |
53 | License: MIT~old
54 | Permission is hereby granted, without written agreement and without
55 | license or royalty fees, to use, copy, modify, and distribute this
56 | software and its documentation for any purpose, provided that the
57 | above copyright notice and the following two paragraphs appear in
58 | all copies of this software.
59 | .
60 | IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
61 | DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
62 | ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
63 | IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
64 | DAMAGE.
65 | .
66 | THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
67 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
68 | FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
69 | ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
70 | PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS
71 |
72 | License: NTP
73 | Permission to use, copy, modify, and distribute this
74 | software and its documentation for any purpose and without
75 | fee is hereby granted, provided that the above copyright
76 | notice appear in all copies. The University of California
77 | makes no representations about the suitability of this
78 | software for any purpose. It is provided "as is" without
79 | express or implied warranty.
80 |
81 | License: NTP~disclaimer
82 | Permission to use, copy, modify, distribute, and sell this software and its
83 | documentation for any purpose is hereby granted without fee, provided that
84 | the above copyright notice appear in all copies and that both that
85 | copyright notice and this permission notice appear in supporting
86 | documentation, and that the name of the author(s) not be used in
87 | advertising or publicity pertaining to distribution of the software without
88 | specific, written prior permission. The authors make no
89 | representations about the suitability of this software for any purpose. It
90 | is provided "as is" without express or implied warranty.
91 | .
92 | THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
93 | INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
94 | EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
95 | CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
96 | DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
97 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
98 | PERFORMANCE OF THIS SOFTWARE.
99 |
100 | License: Zlib
101 | This software is provided 'as-is', without any express or implied
102 | warranty. In no event will the authors be held liable for any damages
103 | arising from the use of this software.
104 | .
105 | Permission is granted to anyone to use this software for any purpose,
106 | including commercial applications, and to alter it and redistribute it
107 | freely, subject to the following restrictions:
108 | .
109 | 1. The origin of this software must not be misrepresented; you must not
110 | claim that you wrote the original software. If you use this software
111 | in a product, an acknowledgment in the product documentation would be
112 | appreciated but is not required.
113 | 2. Altered source versions must be plainly marked as such, and must not be
114 | misrepresented as being the original software.
115 | 3. This notice may not be removed or altered from any source distribution.
116 |
117 | License: BSL-1
118 | Boost Software License - Version 1.0 - August 17th, 2003
119 | .
120 | Permission is hereby granted, free of charge, to any person or organization
121 | obtaining a copy of the software and accompanying documentation covered by
122 | this license (the "Software") to use, reproduce, display, distribute,
123 | execute, and transmit the Software, and to prepare derivative works of the
124 | Software, and to permit third-parties to whom the Software is furnished to
125 | do so, all subject to the following:
126 | .
127 | The copyright notices in the Software and this entire statement, including
128 | the above license grant, this restriction and the following disclaimer,
129 | must be included in all copies of the Software, in whole or in part, and
130 | all derivative works of the Software, unless such copies or derivative
131 | works are solely in the form of machine-executable object code generated by
132 | a source language processor.
133 | .
134 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
135 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
136 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
137 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
138 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
139 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
140 | DEALINGS IN THE SOFTWARE.
141 |
142 | License: LGPL-2.1
143 | GNU LESSER GENERAL PUBLIC LICENSE
144 | Version 2.1, February 1999
145 | .
146 | Copyright (C) 1991, 1999 Free Software Foundation, Inc.
147 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
148 | Everyone is permitted to copy and distribute verbatim copies
149 | of this license document, but changing it is not allowed.
150 | .
151 | [This is the first released version of the Lesser GPL. It also counts
152 | as the successor of the GNU Library Public License, version 2, hence
153 | the version number 2.1.]
154 | .
155 | Preamble
156 | .
157 | The licenses for most software are designed to take away your
158 | freedom to share and change it. By contrast, the GNU General Public
159 | Licenses are intended to guarantee your freedom to share and change
160 | free software--to make sure the software is free for all its users.
161 | .
162 | This license, the Lesser General Public License, applies to some
163 | specially designated software packages--typically libraries--of the
164 | Free Software Foundation and other authors who decide to use it. You
165 | can use it too, but we suggest you first think carefully about whether
166 | this license or the ordinary General Public License is the better
167 | strategy to use in any particular case, based on the explanations below.
168 | .
169 | When we speak of free software, we are referring to freedom of use,
170 | not price. Our General Public Licenses are designed to make sure that
171 | you have the freedom to distribute copies of free software (and charge
172 | for this service if you wish); that you receive source code or can get
173 | it if you want it; that you can change the software and use pieces of
174 | it in new free programs; and that you are informed that you can do
175 | these things.
176 | .
177 | To protect your rights, we need to make restrictions that forbid
178 | distributors to deny you these rights or to ask you to surrender these
179 | rights. These restrictions translate to certain responsibilities for
180 | you if you distribute copies of the library or if you modify it.
181 | .
182 | For example, if you distribute copies of the library, whether gratis
183 | or for a fee, you must give the recipients all the rights that we gave
184 | you. You must make sure that they, too, receive or can get the source
185 | code. If you link other code with the library, you must provide
186 | complete object files to the recipients, so that they can relink them
187 | with the library after making changes to the library and recompiling
188 | it. And you must show them these terms so they know their rights.
189 | .
190 | We protect your rights with a two-step method: (1) we copyright the
191 | library, and (2) we offer you this license, which gives you legal
192 | permission to copy, distribute and/or modify the library.
193 | .
194 | To protect each distributor, we want to make it very clear that
195 | there is no warranty for the free library. Also, if the library is
196 | modified by someone else and passed on, the recipients should know
197 | that what they have is not the original version, so that the original
198 | author's reputation will not be affected by problems that might be
199 | introduced by others.
200 | .
201 | Finally, software patents pose a constant threat to the existence of
202 | any free program. We wish to make sure that a company cannot
203 | effectively restrict the users of a free program by obtaining a
204 | restrictive license from a patent holder. Therefore, we insist that
205 | any patent license obtained for a version of the library must be
206 | consistent with the full freedom of use specified in this license.
207 | .
208 | Most GNU software, including some libraries, is covered by the
209 | ordinary GNU General Public License. This license, the GNU Lesser
210 | General Public License, applies to certain designated libraries, and
211 | is quite different from the ordinary General Public License. We use
212 | this license for certain libraries in order to permit linking those
213 | libraries into non-free programs.
214 | .
215 | When a program is linked with a library, whether statically or using
216 | a shared library, the combination of the two is legally speaking a
217 | combined work, a derivative of the original library. The ordinary
218 | General Public License therefore permits such linking only if the
219 | entire combination fits its criteria of freedom. The Lesser General
220 | Public License permits more lax criteria for linking other code with
221 | the library.
222 | .
223 | We call this license the "Lesser" General Public License because it
224 | does Less to protect the user's freedom than the ordinary General
225 | Public License. It also provides other free software developers Less
226 | of an advantage over competing non-free programs. These disadvantages
227 | are the reason we use the ordinary General Public License for many
228 | libraries. However, the Lesser license provides advantages in certain
229 | special circumstances.
230 | .
231 | For example, on rare occasions, there may be a special need to
232 | encourage the widest possible use of a certain library, so that it becomes
233 | a de-facto standard. To achieve this, non-free programs must be
234 | allowed to use the library. A more frequent case is that a free
235 | library does the same job as widely used non-free libraries. In this
236 | case, there is little to gain by limiting the free library to free
237 | software only, so we use the Lesser General Public License.
238 | .
239 | In other cases, permission to use a particular library in non-free
240 | programs enables a greater number of people to use a large body of
241 | free software. For example, permission to use the GNU C Library in
242 | non-free programs enables many more people to use the whole GNU
243 | operating system, as well as its variant, the GNU/Linux operating
244 | system.
245 | .
246 | Although the Lesser General Public License is Less protective of the
247 | users' freedom, it does ensure that the user of a program that is
248 | linked with the Library has the freedom and the wherewithal to run
249 | that program using a modified version of the Library.
250 | .
251 | The precise terms and conditions for copying, distribution and
252 | modification follow. Pay close attention to the difference between a
253 | "work based on the library" and a "work that uses the library". The
254 | former contains code derived from the library, whereas the latter must
255 | be combined with the library in order to run.
256 | .
257 | GNU LESSER GENERAL PUBLIC LICENSE
258 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
259 | .
260 | 0. This License Agreement applies to any software library or other
261 | program which contains a notice placed by the copyright holder or
262 | other authorized party saying it may be distributed under the terms of
263 | this Lesser General Public License (also called "this License").
264 | Each licensee is addressed as "you".
265 | .
266 | A "library" means a collection of software functions and/or data
267 | prepared so as to be conveniently linked with application programs
268 | (which use some of those functions and data) to form executables.
269 | .
270 | The "Library", below, refers to any such software library or work
271 | which has been distributed under these terms. A "work based on the
272 | Library" means either the Library or any derivative work under
273 | copyright law: that is to say, a work containing the Library or a
274 | portion of it, either verbatim or with modifications and/or translated
275 | straightforwardly into another language. (Hereinafter, translation is
276 | included without limitation in the term "modification".)
277 | .
278 | "Source code" for a work means the preferred form of the work for
279 | making modifications to it. For a library, complete source code means
280 | all the source code for all modules it contains, plus any associated
281 | interface definition files, plus the scripts used to control compilation
282 | and installation of the library.
283 | .
284 | Activities other than copying, distribution and modification are not
285 | covered by this License; they are outside its scope. The act of
286 | running a program using the Library is not restricted, and output from
287 | such a program is covered only if its contents constitute a work based
288 | on the Library (independent of the use of the Library in a tool for
289 | writing it). Whether that is true depends on what the Library does
290 | and what the program that uses the Library does.
291 |
292 | 1. You may copy and distribute verbatim copies of the Library's
293 | complete source code as you receive it, in any medium, provided that
294 | you conspicuously and appropriately publish on each copy an
295 | appropriate copyright notice and disclaimer of warranty; keep intact
296 | all the notices that refer to this License and to the absence of any
297 | warranty; and distribute a copy of this License along with the
298 | Library.
299 | .
300 | You may charge a fee for the physical act of transferring a copy,
301 | and you may at your option offer warranty protection in exchange for a
302 | fee.
303 | .
304 | 2. You may modify your copy or copies of the Library or any portion
305 | of it, thus forming a work based on the Library, and copy and
306 | distribute such modifications or work under the terms of Section 1
307 | above, provided that you also meet all of these conditions:
308 | .
309 | a) The modified work must itself be a software library.
310 | .
311 | b) You must cause the files modified to carry prominent notices
312 | stating that you changed the files and the date of any change.
313 | .
314 | c) You must cause the whole of the work to be licensed at no
315 | charge to all third parties under the terms of this License.
316 | .
317 | d) If a facility in the modified Library refers to a function or a
318 | table of data to be supplied by an application program that uses
319 | the facility, other than as an argument passed when the facility
320 | is invoked, then you must make a good faith effort to ensure that,
321 | in the event an application does not supply such function or
322 | table, the facility still operates, and performs whatever part of
323 | its purpose remains meaningful.
324 | .
325 | (For example, a function in a library to compute square roots has
326 | a purpose that is entirely well-defined independent of the
327 | application. Therefore, Subsection 2d requires that any
328 | application-supplied function or table used by this function must
329 | be optional: if the application does not supply it, the square
330 | root function must still compute square roots.)
331 | .
332 | These requirements apply to the modified work as a whole. If
333 | identifiable sections of that work are not derived from the Library,
334 | and can be reasonably considered independent and separate works in
335 | themselves, then this License, and its terms, do not apply to those
336 | sections when you distribute them as separate works. But when you
337 | distribute the same sections as part of a whole which is a work based
338 | on the Library, the distribution of the whole must be on the terms of
339 | this License, whose permissions for other licensees extend to the
340 | entire whole, and thus to each and every part regardless of who wrote
341 | it.
342 | .
343 | Thus, it is not the intent of this section to claim rights or contest
344 | your rights to work written entirely by you; rather, the intent is to
345 | exercise the right to control the distribution of derivative or
346 | collective works based on the Library.
347 | .
348 | In addition, mere aggregation of another work not based on the Library
349 | with the Library (or with a work based on the Library) on a volume of
350 | a storage or distribution medium does not bring the other work under
351 | the scope of this License.
352 | .
353 | 3. You may opt to apply the terms of the ordinary GNU General Public
354 | License instead of this License to a given copy of the Library. To do
355 | this, you must alter all the notices that refer to this License, so
356 | that they refer to the ordinary GNU General Public License, version 2,
357 | instead of to this License. (If a newer version than version 2 of the
358 | ordinary GNU General Public License has appeared, then you can specify
359 | that version instead if you wish.) Do not make any other change in
360 | these notices.
361 | .
362 | Once this change is made in a given copy, it is irreversible for
363 | that copy, so the ordinary GNU General Public License applies to all
364 | subsequent copies and derivative works made from that copy.
365 | .
366 | This option is useful when you wish to copy part of the code of
367 | the Library into a program that is not a library.
368 | .
369 | 4. You may copy and distribute the Library (or a portion or
370 | derivative of it, under Section 2) in object code or executable form
371 | under the terms of Sections 1 and 2 above provided that you accompany
372 | it with the complete corresponding machine-readable source code, which
373 | must be distributed under the terms of Sections 1 and 2 above on a
374 | medium customarily used for software interchange.
375 | .
376 | If distribution of object code is made by offering access to copy
377 | from a designated place, then offering equivalent access to copy the
378 | source code from the same place satisfies the requirement to
379 | distribute the source code, even though third parties are not
380 | compelled to copy the source along with the object code.
381 | .
382 | 5. A program that contains no derivative of any portion of the
383 | Library, but is designed to work with the Library by being compiled or
384 | linked with it, is called a "work that uses the Library". Such a
385 | work, in isolation, is not a derivative work of the Library, and
386 | therefore falls outside the scope of this License.
387 | .
388 | However, linking a "work that uses the Library" with the Library
389 | creates an executable that is a derivative of the Library (because it
390 | contains portions of the Library), rather than a "work that uses the
391 | library". The executable is therefore covered by this License.
392 | Section 6 states terms for distribution of such executables.
393 | .
394 | When a "work that uses the Library" uses material from a header file
395 | that is part of the Library, the object code for the work may be a
396 | derivative work of the Library even though the source code is not.
397 | Whether this is true is especially significant if the work can be
398 | linked without the Library, or if the work is itself a library. The
399 | threshold for this to be true is not precisely defined by law.
400 | .
401 | If such an object file uses only numerical parameters, data
402 | structure layouts and accessors, and small macros and small inline
403 | functions (ten lines or less in length), then the use of the object
404 | file is unrestricted, regardless of whether it is legally a derivative
405 | work. (Executables containing this object code plus portions of the
406 | Library will still fall under Section 6.)
407 | .
408 | Otherwise, if the work is a derivative of the Library, you may
409 | distribute the object code for the work under the terms of Section 6.
410 | Any executables containing that work also fall under Section 6,
411 | whether or not they are linked directly with the Library itself.
412 | .
413 | 6. As an exception to the Sections above, you may also combine or
414 | link a "work that uses the Library" with the Library to produce a
415 | work containing portions of the Library, and distribute that work
416 | under terms of your choice, provided that the terms permit
417 | modification of the work for the customer's own use and reverse
418 | engineering for debugging such modifications.
419 | .
420 | You must give prominent notice with each copy of the work that the
421 | Library is used in it and that the Library and its use are covered by
422 | this License. You must supply a copy of this License. If the work
423 | during execution displays copyright notices, you must include the
424 | copyright notice for the Library among them, as well as a reference
425 | directing the user to the copy of this License. Also, you must do one
426 | of these things:
427 | .
428 | a) Accompany the work with the complete corresponding
429 | machine-readable source code for the Library including whatever
430 | changes were used in the work (which must be distributed under
431 | Sections 1 and 2 above); and, if the work is an executable linked
432 | with the Library, with the complete machine-readable "work that
433 | uses the Library", as object code and/or source code, so that the
434 | user can modify the Library and then relink to produce a modified
435 | executable containing the modified Library. (It is understood
436 | that the user who changes the contents of definitions files in the
437 | Library will not necessarily be able to recompile the application
438 | to use the modified definitions.)
439 | .
440 | b) Use a suitable shared library mechanism for linking with the
441 | Library. A suitable mechanism is one that (1) uses at run time a
442 | copy of the library already present on the user's computer system,
443 | rather than copying library functions into the executable, and (2)
444 | will operate properly with a modified version of the library, if
445 | the user installs one, as long as the modified version is
446 | interface-compatible with the version that the work was made with.
447 | .
448 | c) Accompany the work with a written offer, valid for at
449 | least three years, to give the same user the materials
450 | specified in Subsection 6a, above, for a charge no more
451 | than the cost of performing this distribution.
452 | .
453 | d) If distribution of the work is made by offering access to copy
454 | from a designated place, offer equivalent access to copy the above
455 | specified materials from the same place.
456 | .
457 | e) Verify that the user has already received a copy of these
458 | materials or that you have already sent this user a copy.
459 | .
460 | For an executable, the required form of the "work that uses the
461 | Library" must include any data and utility programs needed for
462 | reproducing the executable from it. However, as a special exception,
463 | the materials to be distributed need not include anything that is
464 | normally distributed (in either source or binary form) with the major
465 | components (compiler, kernel, and so on) of the operating system on
466 | which the executable runs, unless that component itself accompanies
467 | the executable.
468 | .
469 | It may happen that this requirement contradicts the license
470 | restrictions of other proprietary libraries that do not normally
471 | accompany the operating system. Such a contradiction means you cannot
472 | use both them and the Library together in an executable that you
473 | distribute.
474 | .
475 | 7. You may place library facilities that are a work based on the
476 | Library side-by-side in a single library together with other library
477 | facilities not covered by this License, and distribute such a combined
478 | library, provided that the separate distribution of the work based on
479 | the Library and of the other library facilities is otherwise
480 | permitted, and provided that you do these two things:
481 | .
482 | a) Accompany the combined library with a copy of the same work
483 | based on the Library, uncombined with any other library
484 | facilities. This must be distributed under the terms of the
485 | Sections above.
486 | .
487 | b) Give prominent notice with the combined library of the fact
488 | that part of it is a work based on the Library, and explaining
489 | where to find the accompanying uncombined form of the same work.
490 | .
491 | 8. You may not copy, modify, sublicense, link with, or distribute
492 | the Library except as expressly provided under this License. Any
493 | attempt otherwise to copy, modify, sublicense, link with, or
494 | distribute the Library is void, and will automatically terminate your
495 | rights under this License. However, parties who have received copies,
496 | or rights, from you under this License will not have their licenses
497 | terminated so long as such parties remain in full compliance.
498 | .
499 | 9. You are not required to accept this License, since you have not
500 | signed it. However, nothing else grants you permission to modify or
501 | distribute the Library or its derivative works. These actions are
502 | prohibited by law if you do not accept this License. Therefore, by
503 | modifying or distributing the Library (or any work based on the
504 | Library), you indicate your acceptance of this License to do so, and
505 | all its terms and conditions for copying, distributing or modifying
506 | the Library or works based on it.
507 | .
508 | 10. Each time you redistribute the Library (or any work based on the
509 | Library), the recipient automatically receives a license from the
510 | original licensor to copy, distribute, link with or modify the Library
511 | subject to these terms and conditions. You may not impose any further
512 | restrictions on the recipients' exercise of the rights granted herein.
513 | You are not responsible for enforcing compliance by third parties with
514 | this License.
515 | .
516 | 11. If, as a consequence of a court judgment or allegation of patent
517 | infringement or for any other reason (not limited to patent issues),
518 | conditions are imposed on you (whether by court order, agreement or
519 | otherwise) that contradict the conditions of this License, they do not
520 | excuse you from the conditions of this License. If you cannot
521 | distribute so as to satisfy simultaneously your obligations under this
522 | License and any other pertinent obligations, then as a consequence you
523 | may not distribute the Library at all. For example, if a patent
524 | license would not permit royalty-free redistribution of the Library by
525 | all those who receive copies directly or indirectly through you, then
526 | the only way you could satisfy both it and this License would be to
527 | refrain entirely from distribution of the Library.
528 | .
529 | If any portion of this section is held invalid or unenforceable under any
530 | particular circumstance, the balance of the section is intended to apply,
531 | and the section as a whole is intended to apply in other circumstances.
532 | .
533 | It is not the purpose of this section to induce you to infringe any
534 | patents or other property right claims or to contest validity of any
535 | such claims; this section has the sole purpose of protecting the
536 | integrity of the free software distribution system which is
537 | implemented by public license practices. Many people have made
538 | generous contributions to the wide range of software distributed
539 | through that system in reliance on consistent application of that
540 | system; it is up to the author/donor to decide if he or she is willing
541 | to distribute software through any other system and a licensee cannot
542 | impose that choice.
543 | .
544 | This section is intended to make thoroughly clear what is believed to
545 | be a consequence of the rest of this License.
546 | .
547 | 12. If the distribution and/or use of the Library is restricted in
548 | certain countries either by patents or by copyrighted interfaces, the
549 | original copyright holder who places the Library under this License may add
550 | an explicit geographical distribution limitation excluding those countries,
551 | so that distribution is permitted only in or among countries not thus
552 | excluded. In such case, this License incorporates the limitation as if
553 | written in the body of this License.
554 | .
555 | 13. The Free Software Foundation may publish revised and/or new
556 | versions of the Lesser General Public License from time to time.
557 | Such new versions will be similar in spirit to the present version,
558 | but may differ in detail to address new problems or concerns.
559 | .
560 | Each version is given a distinguishing version number. If the Library
561 | specifies a version number of this License which applies to it and
562 | "any later version", you have the option of following the terms and
563 | conditions either of that version or of any later version published by
564 | the Free Software Foundation. If the Library does not specify a
565 | license version number, you may choose any version ever published by
566 | the Free Software Foundation.
567 | .
568 | 14. If you wish to incorporate parts of the Library into other free
569 | programs whose distribution conditions are incompatible with these,
570 | write to the author to ask for permission. For software which is
571 | copyrighted by the Free Software Foundation, write to the Free
572 | Software Foundation; we sometimes make exceptions for this. Our
573 | decision will be guided by the two goals of preserving the free status
574 | of all derivatives of our free software and of promoting the sharing
575 | and reuse of software generally.
576 | .
577 | NO WARRANTY
578 | .
579 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
580 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
581 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
582 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
583 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
584 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
585 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
586 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
587 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
588 | .
589 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
590 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
591 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
592 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
593 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
594 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
595 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
596 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
597 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
598 | DAMAGES.
599 | .
600 | END OF TERMS AND CONDITIONS
601 | .
602 | How to Apply These Terms to Your New Libraries
603 | .
604 | If you develop a new library, and you want it to be of the greatest
605 | possible use to the public, we recommend making it free software that
606 | everyone can redistribute and change. You can do so by permitting
607 | redistribution under these terms (or, alternatively, under the terms of the
608 | ordinary General Public License).
609 | .
610 | To apply these terms, attach the following notices to the library. It is
611 | safest to attach them to the start of each source file to most effectively
612 | convey the exclusion of warranty; and each file should have at least the
613 | "copyright" line and a pointer to where the full notice is found.
614 | .
615 |
616 | Copyright (C)
617 | .
618 | This library is free software; you can redistribute it and/or
619 | modify it under the terms of the GNU Lesser General Public
620 | License as published by the Free Software Foundation; either
621 | version 2.1 of the License, or (at your option) any later version.
622 | .
623 | This library is distributed in the hope that it will be useful,
624 | but WITHOUT ANY WARRANTY; without even the implied warranty of
625 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
626 | Lesser General Public License for more details.
627 | .
628 | You should have received a copy of the GNU Lesser General Public
629 | License along with this library; if not, write to the Free Software
630 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
631 | .
632 | Also add information on how to contact you by electronic and paper mail.
633 | .
634 | You should also get your employer (if you work as a programmer) or your
635 | school, if any, to sign a "copyright disclaimer" for the library, if
636 | necessary. Here is a sample; alter the names:
637 | .
638 | Yoyodyne, Inc., hereby disclaims all copyright interest in the
639 | library `Frob' (a library for tweaking knobs) written by James Random Hacker.
640 | .
641 | , 1 April 1990
642 | Ty Coon, President of Vice
643 | .
644 | That's all there is to it!
645 |
--------------------------------------------------------------------------------
/build/license_lint.awk:
--------------------------------------------------------------------------------
1 | #!/usr/bin/awk -f
2 |
3 | # Copyright 2021 Oneric
4 | # SPDX-License-Identifier: ISC
5 |
6 | # Check that we have the full text of all licenses
7 | # being used in the JSO and subprojects
8 | #
9 | # Usage: license_lint.awk
10 |
11 | function canonicalise_name(l)
12 | {
13 | sub(/\++$/, "", l)
14 | sub(/(.0)+$/, "", l)
15 | return l
16 | }
17 |
18 | BEGIN {
19 | FS = ": | and/or "
20 | split("", licenses)
21 | fulltext = 0
22 | errors = 0
23 | }
24 |
25 | FNR == 1 && FILENAME == ARGV[ARGC-1] {
26 | fulltext = 1
27 | }
28 |
29 |
30 | !fulltext && /^License: / {
31 | for(i=2; i<=NF; ++i) {
32 | l = canonicalise_name($2)
33 | ++licenses[l]
34 | }
35 | }
36 |
37 | fulltext && /^License: / {
38 | for(i=2; i<=NF; ++i) {
39 | l = canonicalise_name($2)
40 | if(!(l in licenses)) {
41 | print "Superfluous full text for '"l"'!" | "cat>&2"
42 | ++errors
43 | } else {
44 | delete licenses[l]
45 | }
46 | }
47 | }
48 |
49 | END {
50 | for (l in licenses) {
51 | if(l == "public-domain")
52 | continue # No notice required
53 | print "Missing full text for '"l"'!" | "cat>&2"
54 | ++errors
55 | }
56 |
57 | if (errors) {
58 | exit 1
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/build/patches/brotli/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThaUnknown/jassub/c43b19cd46a0e88685059432c135ddfb5eb3a3bc/build/patches/brotli/.gitkeep
--------------------------------------------------------------------------------
/build/patches/brotli/0001-upstream_Enable-install-with-emscripten.patch:
--------------------------------------------------------------------------------
1 | Backported from: https://github.com/google/brotli/commit/ce222e317e36aa362e83fc50c7a6226d238e03fd
2 | From: Dirk Lemstra
3 | Date: Wed, 23 Jun 2021 10:12:21 +0200
4 | Subject: [PATCH] Enabled install when building with emscripten. (#906)
5 |
6 | * Enabled install when building with emscripten.
7 |
8 | * Also install the pkg-config files.
9 | ---
10 | CMakeLists.txt | 18 ++++++++----------
11 | 1 file changed, 8 insertions(+), 10 deletions(-)
12 |
13 | diff --git a/CMakeLists.txt b/CMakeLists.txt
14 | index 4ff3401..6999292 100644
15 | --- a/CMakeLists.txt
16 | +++ b/CMakeLists.txt
17 | @@ -219,19 +219,20 @@ add_executable(brotli ${BROTLI_CLI_C})
18 | target_link_libraries(brotli ${BROTLI_LIBRARIES_STATIC})
19 |
20 | # Installation
21 | -if(NOT BROTLI_EMSCRIPTEN)
22 | if(NOT BROTLI_BUNDLED_MODE)
23 | install(
24 | TARGETS brotli
25 | RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
26 | )
27 |
28 | - install(
29 | - TARGETS ${BROTLI_LIBRARIES_CORE}
30 | - ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
31 | - LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
32 | - RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
33 | - )
34 | + if(NOT BROTLI_EMSCRIPTEN)
35 | + install(
36 | + TARGETS ${BROTLI_LIBRARIES_CORE}
37 | + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
38 | + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
39 | + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
40 | + )
41 | + endif() # BROTLI_EMSCRIPTEN
42 |
43 | install(
44 | TARGETS ${BROTLI_LIBRARIES_CORE_STATIC}
45 | @@ -245,7 +246,6 @@ if(NOT BROTLI_BUNDLED_MODE)
46 | DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
47 | )
48 | endif() # BROTLI_BUNDLED_MODE
49 | -endif() # BROTLI_EMSCRIPTEN
50 |
51 | # Tests
52 |
53 | @@ -405,7 +405,6 @@ transform_pc_file("scripts/libbrotlidec.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/libb
54 |
55 | transform_pc_file("scripts/libbrotlienc.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/libbrotlienc.pc" "${BROTLI_VERSION}")
56 |
57 | -if(NOT BROTLI_EMSCRIPTEN)
58 | if(NOT BROTLI_BUNDLED_MODE)
59 | install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libbrotlicommon.pc"
60 | DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
61 | @@ -414,7 +413,6 @@ if(NOT BROTLI_BUNDLED_MODE)
62 | install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libbrotlienc.pc"
63 | DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
64 | endif() # BROTLI_BUNDLED_MODE
65 | -endif() # BROTLI_EMSCRIPTEN
66 |
67 | if (ENABLE_COVERAGE STREQUAL "yes")
68 | SETUP_TARGET_FOR_COVERAGE(coverage test coverage)
69 | --
70 | 2.30.2
71 |
72 |
--------------------------------------------------------------------------------
/build/patches/fontconfig/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThaUnknown/jassub/c43b19cd46a0e88685059432c135ddfb5eb3a3bc/build/patches/fontconfig/.gitkeep
--------------------------------------------------------------------------------
/build/patches/fontconfig/0001-disable-pthreads.patch:
--------------------------------------------------------------------------------
1 | diff --git a/configure.ac b/configure.ac
2 | index 26b974a..b73d132 100644
3 | --- a/configure.ac
4 | +++ b/configure.ac
5 | @@ -712,22 +712,8 @@ if $fc_cv_have_solaris_atomic_ops; then
6 | AC_DEFINE(HAVE_SOLARIS_ATOMIC_OPS, 1, [Have Solaris __machine_*_barrier and atomic_* operations])
7 | fi
8 |
9 | -if test "$os_win32" = no && ! $have_pthread; then
10 | - AC_CHECK_HEADERS(sched.h)
11 | - AC_SEARCH_LIBS(sched_yield,rt,AC_DEFINE(HAVE_SCHED_YIELD, 1, [Have sched_yield]))
12 | -fi
13 | -
14 | -have_pthread=false
15 | -if test "$os_win32" = no; then
16 | - AX_PTHREAD([have_pthread=true])
17 | -fi
18 | -if $have_pthread; then
19 | - LIBS="$PTHREAD_LIBS $LIBS"
20 | - CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
21 | - CC="$PTHREAD_CC"
22 | - AC_DEFINE(HAVE_PTHREAD, 1, [Have POSIX threads])
23 | -fi
24 | -AM_CONDITIONAL(HAVE_PTHREAD, $have_pthread)
25 | +AC_DEFINE(HAVE_PTHREAD, 0, [Have POSIX threads])
26 | +AM_CONDITIONAL(HAVE_PTHREAD, false)
27 |
28 |
29 | dnl ===========================================================================
30 |
--------------------------------------------------------------------------------
/build/patches/freetype/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThaUnknown/jassub/c43b19cd46a0e88685059432c135ddfb5eb3a3bc/build/patches/freetype/.gitkeep
--------------------------------------------------------------------------------
/build/patches/freetype/0002-disable-pthread.patch:
--------------------------------------------------------------------------------
1 | diff --git a/builds/unix/configure.raw b/builds/unix/configure.raw
2 | index 56e0a8e2c..0639c10db 100644
3 | --- a/builds/unix/configure.raw
4 | +++ b/builds/unix/configure.raw
5 | @@ -1007,7 +1007,7 @@ esac
6 |
7 | # Check for pthreads
8 |
9 | -AX_PTHREAD([have_pthread=yes], [have_pthread=no])
10 | +have_pthread=no
11 |
12 | # Check for Python and docwriter
13 |
14 |
--------------------------------------------------------------------------------
/build/patches/harfbuzz/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThaUnknown/jassub/c43b19cd46a0e88685059432c135ddfb5eb3a3bc/build/patches/harfbuzz/.gitkeep
--------------------------------------------------------------------------------
/build/patches/harfbuzz/0001-force_disable_pthread.patch:
--------------------------------------------------------------------------------
1 | diff --git a/configure.ac b/configure.ac
2 | index c88cffea..a8f98078 100644
3 | --- a/configure.ac
4 | +++ b/configure.ac
5 | @@ -128,9 +128,9 @@ AC_MSG_RESULT([$hb_os_win32])
6 | AM_CONDITIONAL(OS_WIN32, test "$hb_os_win32" = "yes")
7 |
8 | have_pthread=false
9 | -AX_PTHREAD([have_pthread=true])
10 | +AX_PTHREAD([have_pthread=false])
11 | if $have_pthread; then
12 | - AC_DEFINE(HAVE_PTHREAD, 1, [Have POSIX threads])
13 | + AC_DEFINE(HAVE_PTHREAD, 0, [Have POSIX threads])
14 | fi
15 | AM_CONDITIONAL(HAVE_PTHREAD, $have_pthread)
16 |
17 |
--------------------------------------------------------------------------------
/dist/default.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThaUnknown/jassub/c43b19cd46a0e88685059432c135ddfb5eb3a3bc/dist/default.woff2
--------------------------------------------------------------------------------
/dist/jassub-worker-modern.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThaUnknown/jassub/c43b19cd46a0e88685059432c135ddfb5eb3a3bc/dist/jassub-worker-modern.wasm
--------------------------------------------------------------------------------
/dist/jassub-worker.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThaUnknown/jassub/c43b19cd46a0e88685059432c135ddfb5eb3a3bc/dist/jassub-worker.wasm
--------------------------------------------------------------------------------
/dist/jassub.es.js:
--------------------------------------------------------------------------------
1 | typeof HTMLVideoElement < "u" && !("requestVideoFrameCallback" in HTMLVideoElement.prototype) && "getVideoPlaybackQuality" in HTMLVideoElement.prototype && (HTMLVideoElement.prototype._rvfcpolyfillmap = {}, HTMLVideoElement.prototype.requestVideoFrameCallback = function(c) {
2 | const e = performance.now(), t = this.getVideoPlaybackQuality(), s = this.mozPresentedFrames || this.mozPaintedFrames || t.totalVideoFrames - t.droppedVideoFrames, r = (n, a) => {
3 | const i = this.getVideoPlaybackQuality(), h = this.mozPresentedFrames || this.mozPaintedFrames || i.totalVideoFrames - i.droppedVideoFrames;
4 | if (h > s) {
5 | const d = this.mozFrameDelay || i.totalFrameDelay - t.totalFrameDelay || 0, l = a - n;
6 | c(a, {
7 | presentationTime: a + d * 1e3,
8 | expectedDisplayTime: a + l,
9 | width: this.videoWidth,
10 | height: this.videoHeight,
11 | mediaTime: Math.max(0, this.currentTime || 0) + l / 1e3,
12 | presentedFrames: h,
13 | processingDuration: d
14 | }), delete this._rvfcpolyfillmap[e];
15 | } else
16 | this._rvfcpolyfillmap[e] = requestAnimationFrame((d) => r(a, d));
17 | };
18 | return this._rvfcpolyfillmap[e] = requestAnimationFrame((n) => r(e, n)), e;
19 | }, HTMLVideoElement.prototype.cancelVideoFrameCallback = function(c) {
20 | cancelAnimationFrame(this._rvfcpolyfillmap[c]), delete this._rvfcpolyfillmap[c];
21 | });
22 | const _ = {
23 | bt709: "BT709",
24 | // these might not be exactly correct? oops?
25 | bt470bg: "BT601",
26 | // alias BT.601 PAL... whats the difference?
27 | smpte170m: "BT601"
28 | // alias BT.601 NTSC... whats the difference?
29 | }, f = {
30 | BT601: {
31 | BT709: "1.0863 -0.0723 -0.014 0 0 0.0965 0.8451 0.0584 0 0 -0.0141 -0.0277 1.0418"
32 | },
33 | BT709: {
34 | BT601: "0.9137 0.0784 0.0079 0 0 -0.1049 1.1722 -0.0671 0 0 0.0096 0.0322 0.9582"
35 | },
36 | FCC: {
37 | BT709: "1.0873 -0.0736 -0.0137 0 0 0.0974 0.8494 0.0531 0 0 -0.0127 -0.0251 1.0378",
38 | BT601: "1.001 -0.0008 -0.0002 0 0 0.0009 1.005 -0.006 0 0 0.0013 0.0027 0.996"
39 | },
40 | SMPTE240M: {
41 | BT709: "0.9993 0.0006 0.0001 0 0 -0.0004 0.9812 0.0192 0 0 -0.0034 -0.0114 1.0148",
42 | BT601: "0.913 0.0774 0.0096 0 0 -0.1051 1.1508 -0.0456 0 0 0.0063 0.0207 0.973"
43 | }
44 | };
45 | class o extends EventTarget {
46 | /**
47 | * @param {Object} options Settings object.
48 | * @param {HTMLVideoElement} options.video Video to use as target for rendering and event listeners. Optional if canvas is specified instead.
49 | * @param {HTMLCanvasElement} [options.canvas=HTMLCanvasElement] Canvas to use for manual handling. Not required if video is specified.
50 | * @param {'js'|'wasm'} [options.blendMode='js'] Which image blending mode to use. WASM will perform better on lower end devices, JS will perform better if the device and browser supports hardware acceleration.
51 | * @param {Boolean} [options.asyncRender=true] Whether or not to use async rendering, which offloads the CPU by creating image bitmaps on the GPU.
52 | * @param {Boolean} [options.offscreenRender=true] Whether or not to render things fully on the worker, greatly reduces CPU usage.
53 | * @param {Boolean} [options.onDemandRender=true] Whether or not to render subtitles as the video player decodes renders, rather than predicting which frame the player is on using events.
54 | * @param {Number} [options.targetFps=24] Target FPS to render subtitles at. Ignored when onDemandRender is enabled.
55 | * @param {Number} [options.timeOffset=0] Subtitle time offset in seconds.
56 | * @param {Boolean} [options.debug=false] Whether or not to print debug information.
57 | * @param {Number} [options.prescaleFactor=1.0] Scale down (< 1.0) the subtitles canvas to improve performance at the expense of quality, or scale it up (> 1.0).
58 | * @param {Number} [options.prescaleHeightLimit=1080] The height in pixels beyond which the subtitles canvas won't be prescaled.
59 | * @param {Number} [options.maxRenderHeight=0] The maximum rendering height in pixels of the subtitles canvas. Beyond this subtitles will be upscaled by the browser.
60 | * @param {Boolean} [options.dropAllAnimations=false] Attempt to discard all animated tags. Enabling this may severly mangle complex subtitles and should only be considered as an last ditch effort of uncertain success for hardware otherwise incapable of displaing anything. Will not reliably work with manually edited or allocated events.
61 | * @param {Boolean} [options.dropAllBlur=false] The holy grail of performance gains. If heavy TS lags a lot, disabling this will make it ~x10 faster. This drops blur from all added subtitle tracks making most text and backgrounds look sharper, this is way less intrusive than dropping all animations, while still offering major performance gains.
62 | * @param {String} [options.workerUrl='jassub-worker.js'] The URL of the worker.
63 | * @param {String} [options.wasmUrl='jassub-worker.wasm'] The URL of the worker WASM.
64 | * @param {String} [options.legacyWasmUrl='jassub-worker.wasm.js'] The URL of the worker WASM. Only loaded if the browser doesn't support WASM.
65 | * @param {String} options.modernWasmUrl The URL of the modern worker WASM. This includes faster ASM instructions, but is only supported by newer browsers, disabled if the URL isn't defined.
66 | * @param {String} [options.subUrl=options.subContent] The URL of the subtitle file to play.
67 | * @param {String} [options.subContent=options.subUrl] The content of the subtitle file to play.
68 | * @param {String[]|Uint8Array[]} [options.fonts] An array of links or Uint8Arrays to the fonts used in the subtitle. If Uint8Array is used the array is copied, not referenced. This forces all the fonts in this array to be loaded by the renderer, regardless of if they are used.
69 | * @param {Object} [options.availableFonts={'liberation sans': './default.woff2'}] Object with all available fonts - Key is font family in lower case, value is link or Uint8Array: { arial: '/font1.ttf' }. These fonts are selectively loaded if detected as used in the current subtitle track.
70 | * @param {String} [options.fallbackFont='liberation sans'] The font family key of the fallback font in availableFonts to use if the other font for the style is missing special glyphs or unicode.
71 | * @param {Boolean} [options.useLocalFonts=false] If the Local Font Access API is enabled [chrome://flags/#font-access], the library will query for permissions to use local fonts and use them if any are missing. The permission can be queried beforehand using navigator.permissions.request({ name: 'local-fonts' }).
72 | * @param {Number} [options.libassMemoryLimit] libass bitmap cache memory limit in MiB (approximate).
73 | * @param {Number} [options.libassGlyphLimit] libass glyph cache memory limit in MiB (approximate).
74 | */
75 | constructor(e) {
76 | if (super(), !globalThis.Worker)
77 | throw this.destroy("Worker not supported");
78 | if (!e)
79 | throw this.destroy("No options provided");
80 | this._loaded = /** @type {Promise} */
81 | new Promise((s) => {
82 | this._init = s;
83 | });
84 | const t = o._test();
85 | if (this._onDemandRender = "requestVideoFrameCallback" in HTMLVideoElement.prototype && (e.onDemandRender ?? !0), this._offscreenRender = "transferControlToOffscreen" in HTMLCanvasElement.prototype && !e.canvas && (e.offscreenRender ?? !0), this.timeOffset = e.timeOffset || 0, this._video = e.video, this._videoHeight = 0, this._videoWidth = 0, this._videoColorSpace = null, this._canvas = e.canvas, this._video && !this._canvas)
86 | this._canvasParent = document.createElement("div"), this._canvasParent.className = "JASSUB", this._canvasParent.style.position = "relative", this._canvas = this._createCanvas(), this._video.insertAdjacentElement("afterend", this._canvasParent);
87 | else if (!this._canvas)
88 | throw this.destroy("Don't know where to render: you should give video or canvas in options.");
89 | if (this._bufferCanvas = document.createElement("canvas"), this._bufferCtx = this._bufferCanvas.getContext("2d"), !this._bufferCtx)
90 | throw this.destroy("Canvas rendering not supported");
91 | this._canvasctrl = this._offscreenRender ? this._canvas.transferControlToOffscreen() : this._canvas, this._ctx = !this._offscreenRender && this._canvasctrl.getContext("2d"), this._lastRenderTime = 0, this.debug = !!e.debug, this.prescaleFactor = e.prescaleFactor || 1, this.prescaleHeightLimit = e.prescaleHeightLimit || 1080, this.maxRenderHeight = e.maxRenderHeight || 0, this._boundResize = this.resize.bind(this), this._boundTimeUpdate = this._timeupdate.bind(this), this._boundSetRate = this.setRate.bind(this), this._boundUpdateColorSpace = this._updateColorSpace.bind(this), this._video && this.setVideo(e.video), this._onDemandRender && (this.busy = !1, this._lastDemandTime = null), this._worker = new Worker(e.workerUrl || "jassub-worker.js"), this._worker.onmessage = (s) => this._onmessage(s), this._worker.onerror = (s) => this._error(s), t.then(() => {
92 | this._worker.postMessage({
93 | target: "init",
94 | wasmUrl: o._supportsSIMD && e.modernWasmUrl ? e.modernWasmUrl : e.wasmUrl ?? "jassub-worker.wasm",
95 | legacyWasmUrl: e.legacyWasmUrl ?? "jassub-worker.wasm.js",
96 | asyncRender: typeof createImageBitmap < "u" && (e.asyncRender ?? !0),
97 | onDemandRender: this._onDemandRender,
98 | width: this._canvasctrl.width || 0,
99 | height: this._canvasctrl.height || 0,
100 | blendMode: e.blendMode || "js",
101 | subUrl: e.subUrl,
102 | subContent: e.subContent || null,
103 | fonts: e.fonts || [],
104 | availableFonts: e.availableFonts || { "liberation sans": "./default.woff2" },
105 | fallbackFont: e.fallbackFont || "liberation sans",
106 | debug: this.debug,
107 | targetFps: e.targetFps || 24,
108 | dropAllAnimations: e.dropAllAnimations,
109 | dropAllBlur: e.dropAllBlur,
110 | libassMemoryLimit: e.libassMemoryLimit || 0,
111 | libassGlyphLimit: e.libassGlyphLimit || 0,
112 | // @ts-ignore
113 | useLocalFonts: typeof queryLocalFonts < "u" && (e.useLocalFonts ?? !0),
114 | hasBitmapBug: o._hasBitmapBug
115 | }), this._offscreenRender === !0 && this.sendMessage("offscreenCanvas", null, [this._canvasctrl]);
116 | });
117 | }
118 | _createCanvas() {
119 | return this._canvas = document.createElement("canvas"), this._canvas.style.display = "block", this._canvas.style.position = "absolute", this._canvas.style.pointerEvents = "none", this._canvasParent.appendChild(this._canvas), this._canvas;
120 | }
121 | // test support for WASM, ImageData, alphaBug, but only once, on init so it doesn't run when first running the page
122 | /** @type {boolean|null} */
123 | static _supportsSIMD = null;
124 | /** @type {boolean|null} */
125 | static _hasAlphaBug = null;
126 | /** @type {boolean|null} */
127 | static _hasBitmapBug = null;
128 | static _testSIMD() {
129 | if (o._supportsSIMD === null)
130 | try {
131 | o._supportsSIMD = WebAssembly.validate(Uint8Array.of(0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 123, 3, 2, 1, 0, 10, 10, 1, 8, 0, 65, 0, 253, 15, 253, 98, 11));
132 | } catch {
133 | o._supportsSIMD = !1;
134 | }
135 | }
136 | static async _testImageBugs() {
137 | if (o._hasBitmapBug !== null)
138 | return;
139 | const e = document.createElement("canvas"), t = e.getContext("2d", { willReadFrequently: !0 });
140 | if (!t)
141 | throw new Error("Canvas rendering not supported");
142 | if (typeof ImageData.prototype.constructor == "function")
143 | try {
144 | new ImageData(new Uint8ClampedArray([0, 0, 0, 0]), 1, 1);
145 | } catch {
146 | console.log("Detected that ImageData is not constructable despite browser saying so"), self.ImageData = function(h, d, l) {
147 | const m = t.createImageData(d, l);
148 | return h && m.data.set(h), m;
149 | };
150 | }
151 | const s = document.createElement("canvas"), r = s.getContext("2d", { willReadFrequently: !0 });
152 | if (!r)
153 | throw new Error("Canvas rendering not supported");
154 | e.width = s.width = 1, e.height = s.height = 1, t.clearRect(0, 0, 1, 1), r.clearRect(0, 0, 1, 1);
155 | const n = r.getImageData(0, 0, 1, 1).data;
156 | t.putImageData(new ImageData(new Uint8ClampedArray([0, 255, 0, 0]), 1, 1), 0, 0), r.drawImage(e, 0, 0);
157 | const a = r.getImageData(0, 0, 1, 1).data;
158 | if (o._hasAlphaBug = n[1] !== a[1], o._hasAlphaBug && console.log("Detected a browser having issue with transparent pixels, applying workaround"), typeof createImageBitmap < "u") {
159 | const i = new Uint8ClampedArray([255, 0, 255, 0, 255]).subarray(1, 5);
160 | r.drawImage(await createImageBitmap(new ImageData(i, 1)), 0, 0);
161 | const { data: h } = r.getImageData(0, 0, 1, 1);
162 | o._hasBitmapBug = !1;
163 | for (const [d, l] of h.entries())
164 | if (Math.abs(i[d] - l) > 15) {
165 | o._hasBitmapBug = !0, console.log("Detected a browser having issue with partial bitmaps, applying workaround");
166 | break;
167 | }
168 | } else
169 | o._hasBitmapBug = !1;
170 | e.remove(), s.remove();
171 | }
172 | static async _test() {
173 | o._testSIMD(), await o._testImageBugs();
174 | }
175 | /**
176 | * Resize the canvas to given parameters. Auto-generated if values are ommited.
177 | * @param {Number} [width=0]
178 | * @param {Number} [height=0]
179 | * @param {Number} [top=0]
180 | * @param {Number} [left=0]
181 | * @param {Boolean} [force=false]
182 | */
183 | resize(e = 0, t = 0, s = 0, r = 0, n = this._video?.paused) {
184 | if ((!e || !t) && this._video) {
185 | const a = this._getVideoPosition();
186 | let i = null;
187 | if (this._videoWidth) {
188 | const h = this._video.videoWidth / this._videoWidth, d = this._video.videoHeight / this._videoHeight;
189 | i = this._computeCanvasSize((a.width || 0) / h, (a.height || 0) / d);
190 | } else
191 | i = this._computeCanvasSize(a.width || 0, a.height || 0);
192 | e = i.width, t = i.height, this._canvasParent && (s = a.y - (this._canvasParent.getBoundingClientRect().top - this._video.getBoundingClientRect().top), r = a.x), this._canvas.style.width = a.width + "px", this._canvas.style.height = a.height + "px";
193 | }
194 | this._canvas.style.top = s + "px", this._canvas.style.left = r + "px", n && this.busy === !1 ? this.busy = !0 : n = !1, this.sendMessage("canvas", { width: e, height: t, videoWidth: this._videoWidth || this._video.videoWidth, videoHeight: this._videoHeight || this._video.videoHeight, force: n });
195 | }
196 | _getVideoPosition(e = this._video.videoWidth, t = this._video.videoHeight) {
197 | const s = e / t, { offsetWidth: r, offsetHeight: n } = this._video, a = r / n;
198 | e = r, t = n, a > s ? e = Math.floor(n * s) : t = Math.floor(r / s);
199 | const i = (r - e) / 2, h = (n - t) / 2;
200 | return { width: e, height: t, x: i, y: h };
201 | }
202 | _computeCanvasSize(e = 0, t = 0) {
203 | const s = this.prescaleFactor <= 0 ? 1 : this.prescaleFactor, r = self.devicePixelRatio || 1;
204 | if (t <= 0 || e <= 0)
205 | e = 0, t = 0;
206 | else {
207 | const n = s < 1 ? -1 : 1;
208 | let a = t * r;
209 | n * a * s <= n * this.prescaleHeightLimit ? a *= s : n * a < n * this.prescaleHeightLimit && (a = this.prescaleHeightLimit), this.maxRenderHeight > 0 && a > this.maxRenderHeight && (a = this.maxRenderHeight), e *= a / t, t = a;
210 | }
211 | return { width: e, height: t };
212 | }
213 | _timeupdate({ type: e }) {
214 | const s = {
215 | seeking: !0,
216 | waiting: !0,
217 | playing: !1
218 | }[e];
219 | s != null && (this._playstate = s), this.setCurrentTime(this._video.paused || this._playstate, this._video.currentTime + this.timeOffset);
220 | }
221 | /**
222 | * Change the video to use as target for event listeners.
223 | * @param {HTMLVideoElement} video
224 | */
225 | setVideo(e) {
226 | e instanceof HTMLVideoElement ? (this._removeListeners(), this._video = e, this._onDemandRender ? this._video.requestVideoFrameCallback(this._handleRVFC.bind(this)) : (this._playstate = e.paused, e.addEventListener("timeupdate", this._boundTimeUpdate, !1), e.addEventListener("progress", this._boundTimeUpdate, !1), e.addEventListener("waiting", this._boundTimeUpdate, !1), e.addEventListener("seeking", this._boundTimeUpdate, !1), e.addEventListener("playing", this._boundTimeUpdate, !1), e.addEventListener("ratechange", this._boundSetRate, !1), e.addEventListener("resize", this._boundResize, !1)), "VideoFrame" in window && (e.addEventListener("loadedmetadata", this._boundUpdateColorSpace, !1), e.readyState > 2 && this._updateColorSpace()), e.videoWidth > 0 && this.resize(), typeof ResizeObserver < "u" && (this._ro || (this._ro = new ResizeObserver(() => this.resize())), this._ro.observe(e))) : this._error("Video element invalid!");
227 | }
228 | runBenchmark() {
229 | this.sendMessage("runBenchmark");
230 | }
231 | /**
232 | * Overwrites the current subtitle content.
233 | * @param {String} url URL to load subtitles from.
234 | */
235 | setTrackByUrl(e) {
236 | this.sendMessage("setTrackByUrl", { url: e }), this._reAttachOffscreen(), this._ctx && (this._ctx.filter = "none");
237 | }
238 | /**
239 | * Overwrites the current subtitle content.
240 | * @param {String} content Content of the ASS file.
241 | */
242 | setTrack(e) {
243 | this.sendMessage("setTrack", { content: e }), this._reAttachOffscreen(), this._ctx && (this._ctx.filter = "none");
244 | }
245 | /**
246 | * Free currently used subtitle track.
247 | */
248 | freeTrack() {
249 | this.sendMessage("freeTrack");
250 | }
251 | /**
252 | * Sets the playback state of the media.
253 | * @param {Boolean} isPaused Pause/Play subtitle playback.
254 | */
255 | setIsPaused(e) {
256 | this.sendMessage("video", { isPaused: e });
257 | }
258 | /**
259 | * Sets the playback rate of the media [speed multiplier].
260 | * @param {Number} rate Playback rate.
261 | */
262 | setRate(e) {
263 | this.sendMessage("video", { rate: e });
264 | }
265 | /**
266 | * Sets the current time, playback state and rate of the subtitles.
267 | * @param {Boolean} [isPaused] Pause/Play subtitle playback.
268 | * @param {Number} [currentTime] Time in seconds.
269 | * @param {Number} [rate] Playback rate.
270 | */
271 | setCurrentTime(e, t, s) {
272 | this.sendMessage("video", { isPaused: e, currentTime: t, rate: s, colorSpace: this._videoColorSpace });
273 | }
274 | /**
275 | * @typedef {Object} ASS_Event
276 | * @property {Number} Start Start Time of the Event, in 0:00:00:00 format ie. Hrs:Mins:Secs:hundredths. This is the time elapsed during script playback at which the text will appear onscreen. Note that there is a single digit for the hours!
277 | * @property {Number} Duration End Time of the Event, in 0:00:00:00 format ie. Hrs:Mins:Secs:hundredths. This is the time elapsed during script playback at which the text will disappear offscreen. Note that there is a single digit for the hours!
278 | * @property {String} Style Style name. If it is "Default", then your own *Default style will be subtituted.
279 | * @property {String} Name Character name. This is the name of the character who speaks the dialogue. It is for information only, to make the script is easier to follow when editing/timing.
280 | * @property {Number} MarginL 4-figure Left Margin override. The values are in pixels. All zeroes means the default margins defined by the style are used.
281 | * @property {Number} MarginR 4-figure Right Margin override. The values are in pixels. All zeroes means the default margins defined by the style are used.
282 | * @property {Number} MarginV 4-figure Bottom Margin override. The values are in pixels. All zeroes means the default margins defined by the style are used.
283 | * @property {String} Effect Transition Effect. This is either empty, or contains information for one of the three transition effects implemented in SSA v4.x
284 | * @property {String} Text Subtitle Text. This is the actual text which will be displayed as a subtitle onscreen. Everything after the 9th comma is treated as the subtitle text, so it can include commas.
285 | * @property {Number} ReadOrder Number in order of which to read this event.
286 | * @property {Number} Layer Z-index overlap in which to render this event.
287 | * @property {Number} _index (Internal) index of the event.
288 | */
289 | /**
290 | * Create a new ASS event directly.
291 | * @param {ASS_Event} event
292 | */
293 | createEvent(e) {
294 | this.sendMessage("createEvent", { event: e });
295 | }
296 | /**
297 | * Overwrite the data of the event with the specified index.
298 | * @param {ASS_Event} event
299 | * @param {Number} index
300 | */
301 | setEvent(e, t) {
302 | this.sendMessage("setEvent", { event: e, index: t });
303 | }
304 | /**
305 | * Remove the event with the specified index.
306 | * @param {Number} index
307 | */
308 | removeEvent(e) {
309 | this.sendMessage("removeEvent", { index: e });
310 | }
311 | /**
312 | * Get all ASS events.
313 | * @param {function(Error|null, ASS_Event): void} callback Function to callback when worker returns the events.
314 | */
315 | getEvents(e) {
316 | this._fetchFromWorker({
317 | target: "getEvents"
318 | }, (t, { events: s }) => {
319 | e(t, s);
320 | });
321 | }
322 | /**
323 | * Set a style override.
324 | * @param {ASS_Style} style
325 | */
326 | styleOverride(e) {
327 | this.sendMessage("styleOverride", { style: e });
328 | }
329 | /**
330 | * Disable style override.
331 | */
332 | disableStyleOverride() {
333 | this.sendMessage("disableStyleOverride");
334 | }
335 | /**
336 | * @typedef {Object} ASS_Style
337 | * @property {String} Name The name of the Style. Case sensitive. Cannot include commas.
338 | * @property {String} FontName The fontname as used by Windows. Case-sensitive.
339 | * @property {Number} FontSize Font size.
340 | * @property {Number} PrimaryColour A long integer BGR (blue-green-red) value. ie. the byte order in the hexadecimal equivelent of this number is BBGGRR
341 | * @property {Number} SecondaryColour A long integer BGR (blue-green-red) value. ie. the byte order in the hexadecimal equivelent of this number is BBGGRR
342 | * @property {Number} OutlineColour A long integer BGR (blue-green-red) value. ie. the byte order in the hexadecimal equivelent of this number is BBGGRR
343 | * @property {Number} BackColour This is the colour of the subtitle outline or shadow, if these are used. A long integer BGR (blue-green-red) value. ie. the byte order in the hexadecimal equivelent of this number is BBGGRR.
344 | * @property {Number} Bold This defines whether text is bold (true) or not (false). -1 is True, 0 is False. This is independant of the Italic attribute - you can have have text which is both bold and italic.
345 | * @property {Number} Italic Italic. This defines whether text is italic (true) or not (false). -1 is True, 0 is False. This is independant of the bold attribute - you can have have text which is both bold and italic.
346 | * @property {Number} Underline -1 or 0
347 | * @property {Number} StrikeOut -1 or 0
348 | * @property {Number} ScaleX Modifies the width of the font. [percent]
349 | * @property {Number} ScaleY Modifies the height of the font. [percent]
350 | * @property {Number} Spacing Extra space between characters. [pixels]
351 | * @property {Number} Angle The origin of the rotation is defined by the alignment. Can be a floating point number. [degrees]
352 | * @property {Number} BorderStyle 1=Outline + drop shadow, 3=Opaque box
353 | * @property {Number} Outline If BorderStyle is 1, then this specifies the width of the outline around the text, in pixels. Values may be 0, 1, 2, 3 or 4.
354 | * @property {Number} Shadow If BorderStyle is 1, then this specifies the depth of the drop shadow behind the text, in pixels. Values may be 0, 1, 2, 3 or 4. Drop shadow is always used in addition to an outline - SSA will force an outline of 1 pixel if no outline width is given.
355 | * @property {Number} Alignment This sets how text is "justified" within the Left/Right onscreen margins, and also the vertical placing. Values may be 1=Left, 2=Centered, 3=Right. Add 4 to the value for a "Toptitle". Add 8 to the value for a "Midtitle". eg. 5 = left-justified toptitle
356 | * @property {Number} MarginL This defines the Left Margin in pixels. It is the distance from the left-hand edge of the screen.The three onscreen margins (MarginL, MarginR, MarginV) define areas in which the subtitle text will be displayed.
357 | * @property {Number} MarginR This defines the Right Margin in pixels. It is the distance from the right-hand edge of the screen. The three onscreen margins (MarginL, MarginR, MarginV) define areas in which the subtitle text will be displayed.
358 | * @property {Number} MarginV This defines the vertical Left Margin in pixels. For a subtitle, it is the distance from the bottom of the screen. For a toptitle, it is the distance from the top of the screen. For a midtitle, the value is ignored - the text will be vertically centred.
359 | * @property {Number} Encoding This specifies the font character set or encoding and on multi-lingual Windows installations it provides access to characters used in multiple than one languages. It is usually 0 (zero) for English (Western, ANSI) Windows.
360 | * @property {Number} treat_fontname_as_pattern
361 | * @property {Number} Blur
362 | * @property {Number} Justify
363 | */
364 | /**
365 | * Create a new ASS style directly.
366 | * @param {ASS_Style} style
367 | */
368 | createStyle(e) {
369 | this.sendMessage("createStyle", { style: e });
370 | }
371 | /**
372 | * Overwrite the data of the style with the specified index.
373 | * @param {ASS_Style} style
374 | * @param {Number} index
375 | */
376 | setStyle(e, t) {
377 | this.sendMessage("setStyle", { style: e, index: t });
378 | }
379 | /**
380 | * Remove the style with the specified index.
381 | * @param {Number} index
382 | */
383 | removeStyle(e) {
384 | this.sendMessage("removeStyle", { index: e });
385 | }
386 | /**
387 | * Get all ASS styles.
388 | * @param {function(Error|null, ASS_Style): void} callback Function to callback when worker returns the styles.
389 | */
390 | getStyles(e) {
391 | this._fetchFromWorker({
392 | target: "getStyles"
393 | }, (t, { styles: s }) => {
394 | e(t, s);
395 | });
396 | }
397 | /**
398 | * Adds a font to the renderer.
399 | * @param {String|Uint8Array} font Font to add.
400 | */
401 | addFont(e) {
402 | this.sendMessage("addFont", { font: e });
403 | }
404 | /**
405 | * Changes the font family of the default font, this font needs to be previously added via addFont or fonts array on construction.
406 | * @param {String} font Font family to change to.
407 | */
408 | setDefaultFont(e) {
409 | this.sendMessage("defaultFont", { font: e });
410 | }
411 | _sendLocalFont(e) {
412 | try {
413 | queryLocalFonts().then((t) => {
414 | const s = t?.find((r) => r.fullName.toLowerCase() === e);
415 | s && s.blob().then((r) => {
416 | r.arrayBuffer().then((n) => {
417 | this.addFont(new Uint8Array(n));
418 | });
419 | });
420 | });
421 | } catch (t) {
422 | console.warn("Local fonts API:", t);
423 | }
424 | }
425 | _getLocalFont({ font: e }) {
426 | try {
427 | navigator?.permissions?.query ? navigator.permissions.query({ name: "local-fonts" }).then((t) => {
428 | t.state === "granted" && this._sendLocalFont(e);
429 | }) : this._sendLocalFont(e);
430 | } catch (t) {
431 | console.warn("Local fonts API:", t);
432 | }
433 | }
434 | _unbusy() {
435 | this._lastDemandTime ? this._demandRender(this._lastDemandTime) : this.busy = !1;
436 | }
437 | _handleRVFC(e, { mediaTime: t, width: s, height: r }) {
438 | if (this._destroyed)
439 | return null;
440 | this.busy ? this._lastDemandTime = { mediaTime: t, width: s, height: r } : (this.busy = !0, this._demandRender({ mediaTime: t, width: s, height: r })), this._video.requestVideoFrameCallback(this._handleRVFC.bind(this));
441 | }
442 | _demandRender({ mediaTime: e, width: t, height: s }) {
443 | this._lastDemandTime = null, (t !== this._videoWidth || s !== this._videoHeight) && (this._videoWidth = t, this._videoHeight = s, this.resize()), this.sendMessage("demand", { time: e + this.timeOffset });
444 | }
445 | // if we're using offscreen render, we can't use ctx filters, so we can't use a transfered canvas
446 | _detachOffscreen() {
447 | if (!this._offscreenRender || this._ctx)
448 | return null;
449 | this._canvas.remove(), this._createCanvas(), this._canvasctrl = this._canvas, this._ctx = this._canvasctrl.getContext("2d"), this.sendMessage("detachOffscreen"), this.busy = !1, this.resize(0, 0, 0, 0, !0);
450 | }
451 | // if the video or track changed, we need to re-attach the offscreen canvas
452 | _reAttachOffscreen() {
453 | if (!this._offscreenRender || !this._ctx)
454 | return null;
455 | this._canvas.remove(), this._createCanvas(), this._canvasctrl = this._canvas.transferControlToOffscreen(), this._ctx = !1, this.sendMessage("offscreenCanvas", null, [this._canvasctrl]), this.resize(0, 0, 0, 0, !0);
456 | }
457 | _updateColorSpace() {
458 | this._video.requestVideoFrameCallback(() => {
459 | try {
460 | const e = new VideoFrame(this._video);
461 | this._videoColorSpace = _[e.colorSpace.matrix], e.close(), this.sendMessage("getColorSpace");
462 | } catch (e) {
463 | console.warn(e);
464 | }
465 | });
466 | }
467 | /**
468 | * Veryify the color spaces for subtitles and videos, then apply filters to correct the color of subtitles.
469 | * @param {Object} options
470 | * @param {String} options.subtitleColorSpace Subtitle color space. One of: BT601 BT709 SMPTE240M FCC
471 | * @param {String=} options.videoColorSpace Video color space. One of: BT601 BT709
472 | */
473 | _verifyColorSpace({ subtitleColorSpace: e, videoColorSpace: t = this._videoColorSpace }) {
474 | !e || !t || e !== t && (this._detachOffscreen(), this._ctx.filter = `url("data:image/svg+xml;utf8, #f")`);
475 | }
476 | _render({ images: e, asyncRender: t, times: s, width: r, height: n, colorSpace: a }) {
477 | this._unbusy(), this.debug && (s.IPCTime = Date.now() - s.JSRenderTime), (this._canvasctrl.width !== r || this._canvasctrl.height !== n) && (this._canvasctrl.width = r, this._canvasctrl.height = n, this._verifyColorSpace({ subtitleColorSpace: a })), this._ctx.clearRect(0, 0, this._canvasctrl.width, this._canvasctrl.height);
478 | for (const i of e)
479 | i.image && (t ? (this._ctx.drawImage(i.image, i.x, i.y), i.image.close()) : (this._bufferCanvas.width = i.w, this._bufferCanvas.height = i.h, this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(i.image)), i.w, i.h), 0, 0), this._ctx.drawImage(this._bufferCanvas, i.x, i.y)));
480 | if (this.debug) {
481 | s.JSRenderTime = Date.now() - s.JSRenderTime - s.IPCTime;
482 | let i = 0;
483 | const h = s.bitmaps || e.length;
484 | delete s.bitmaps;
485 | for (const d in s)
486 | i += s[d];
487 | console.log("Bitmaps: " + h + " Total: " + (i | 0) + "ms", s);
488 | }
489 | }
490 | _fixAlpha(e) {
491 | if (o._hasAlphaBug)
492 | for (let t = 3; t < e.length; t += 4)
493 | e[t] = e[t] > 1 ? e[t] : 1;
494 | return e;
495 | }
496 | _ready() {
497 | this._init(), this.dispatchEvent(new CustomEvent("ready"));
498 | }
499 | /**
500 | * Send data and execute function in the worker.
501 | * @param {String} target Target function.
502 | * @param {Object} [data] Data for function.
503 | * @param {Transferable[]} [transferable] Array of transferables.
504 | */
505 | async sendMessage(e, t = {}, s) {
506 | await this._loaded, s ? this._worker.postMessage({
507 | target: e,
508 | transferable: s,
509 | ...t
510 | }, [...s]) : this._worker.postMessage({
511 | target: e,
512 | ...t
513 | });
514 | }
515 | _fetchFromWorker(e, t) {
516 | try {
517 | const s = e.target, r = setTimeout(() => {
518 | a(new Error("Error: Timeout while try to fetch " + s));
519 | }, 5e3), n = ({ data: i }) => {
520 | i.target === s && (t(null, i), this._worker.removeEventListener("message", n), this._worker.removeEventListener("error", a), clearTimeout(r));
521 | }, a = (i) => {
522 | t(i), this._worker.removeEventListener("message", n), this._worker.removeEventListener("error", a), clearTimeout(r);
523 | };
524 | this._worker.addEventListener("message", n), this._worker.addEventListener("error", a), this._worker.postMessage(e);
525 | } catch (s) {
526 | this._error(s);
527 | }
528 | }
529 | _console({ content: e, command: t }) {
530 | console[t].apply(console, JSON.parse(e));
531 | }
532 | _onmessage({ data: e }) {
533 | this["_" + e.target] && this["_" + e.target](e);
534 | }
535 | _error(e) {
536 | const t = e instanceof Error ? e : e instanceof ErrorEvent ? e.error : new Error(e), s = e instanceof Event ? new ErrorEvent(e.type, e) : new ErrorEvent("error", { error: t });
537 | return this.dispatchEvent(s), console.error(t), t;
538 | }
539 | _removeListeners() {
540 | this._video && (this._ro && this._ro.unobserve(this._video), this._ctx && (this._ctx.filter = "none"), this._video.removeEventListener("timeupdate", this._boundTimeUpdate), this._video.removeEventListener("progress", this._boundTimeUpdate), this._video.removeEventListener("waiting", this._boundTimeUpdate), this._video.removeEventListener("seeking", this._boundTimeUpdate), this._video.removeEventListener("playing", this._boundTimeUpdate), this._video.removeEventListener("ratechange", this._boundSetRate), this._video.removeEventListener("resize", this._boundResize), this._video.removeEventListener("loadedmetadata", this._boundUpdateColorSpace));
541 | }
542 | /**
543 | * Destroy the object, worker, listeners and all data.
544 | * @param {String|Error} [err] Error to throw when destroying.
545 | */
546 | destroy(e) {
547 | return e && (e = this._error(e)), this._video && this._canvasParent && this._video.parentNode?.removeChild(this._canvasParent), this._destroyed = !0, this._removeListeners(), this.sendMessage("destroy"), this._worker?.terminate(), e;
548 | }
549 | }
550 | export {
551 | o as default
552 | };
553 |
--------------------------------------------------------------------------------
/dist/jassub.umd.js:
--------------------------------------------------------------------------------
1 | (function(c,m){typeof exports=="object"&&typeof module<"u"?module.exports=m():typeof define=="function"&&define.amd?define(m):(c=typeof globalThis<"u"?globalThis:c||self,c.JASSUB=m())})(this,function(){"use strict";typeof HTMLVideoElement<"u"&&!("requestVideoFrameCallback"in HTMLVideoElement.prototype)&&"getVideoPlaybackQuality"in HTMLVideoElement.prototype&&(HTMLVideoElement.prototype._rvfcpolyfillmap={},HTMLVideoElement.prototype.requestVideoFrameCallback=function(_){const e=performance.now(),t=this.getVideoPlaybackQuality(),s=this.mozPresentedFrames||this.mozPaintedFrames||t.totalVideoFrames-t.droppedVideoFrames,r=(n,a)=>{const i=this.getVideoPlaybackQuality(),h=this.mozPresentedFrames||this.mozPaintedFrames||i.totalVideoFrames-i.droppedVideoFrames;if(h>s){const d=this.mozFrameDelay||i.totalFrameDelay-t.totalFrameDelay||0,l=a-n;_(a,{presentationTime:a+d*1e3,expectedDisplayTime:a+l,width:this.videoWidth,height:this.videoHeight,mediaTime:Math.max(0,this.currentTime||0)+l/1e3,presentedFrames:h,processingDuration:d}),delete this._rvfcpolyfillmap[e]}else this._rvfcpolyfillmap[e]=requestAnimationFrame(d=>r(a,d))};return this._rvfcpolyfillmap[e]=requestAnimationFrame(n=>r(e,n)),e},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(_){cancelAnimationFrame(this._rvfcpolyfillmap[_]),delete this._rvfcpolyfillmap[_]});const c={bt709:"BT709",bt470bg:"BT601",smpte170m:"BT601"},m={BT601:{BT709:"1.0863 -0.0723 -0.014 0 0 0.0965 0.8451 0.0584 0 0 -0.0141 -0.0277 1.0418"},BT709:{BT601:"0.9137 0.0784 0.0079 0 0 -0.1049 1.1722 -0.0671 0 0 0.0096 0.0322 0.9582"},FCC:{BT709:"1.0873 -0.0736 -0.0137 0 0 0.0974 0.8494 0.0531 0 0 -0.0127 -0.0251 1.0378",BT601:"1.001 -0.0008 -0.0002 0 0 0.0009 1.005 -0.006 0 0 0.0013 0.0027 0.996"},SMPTE240M:{BT709:"0.9993 0.0006 0.0001 0 0 -0.0004 0.9812 0.0192 0 0 -0.0034 -0.0114 1.0148",BT601:"0.913 0.0774 0.0096 0 0 -0.1051 1.1508 -0.0456 0 0 0.0063 0.0207 0.973"}};class o extends EventTarget{constructor(e){if(super(),!globalThis.Worker)throw this.destroy("Worker not supported");if(!e)throw this.destroy("No options provided");this._loaded=new Promise(s=>{this._init=s});const t=o._test();if(this._onDemandRender="requestVideoFrameCallback"in HTMLVideoElement.prototype&&(e.onDemandRender??!0),this._offscreenRender="transferControlToOffscreen"in HTMLCanvasElement.prototype&&!e.canvas&&(e.offscreenRender??!0),this.timeOffset=e.timeOffset||0,this._video=e.video,this._videoHeight=0,this._videoWidth=0,this._videoColorSpace=null,this._canvas=e.canvas,this._video&&!this._canvas)this._canvasParent=document.createElement("div"),this._canvasParent.className="JASSUB",this._canvasParent.style.position="relative",this._canvas=this._createCanvas(),this._video.insertAdjacentElement("afterend",this._canvasParent);else if(!this._canvas)throw this.destroy("Don't know where to render: you should give video or canvas in options.");if(this._bufferCanvas=document.createElement("canvas"),this._bufferCtx=this._bufferCanvas.getContext("2d"),!this._bufferCtx)throw this.destroy("Canvas rendering not supported");this._canvasctrl=this._offscreenRender?this._canvas.transferControlToOffscreen():this._canvas,this._ctx=!this._offscreenRender&&this._canvasctrl.getContext("2d"),this._lastRenderTime=0,this.debug=!!e.debug,this.prescaleFactor=e.prescaleFactor||1,this.prescaleHeightLimit=e.prescaleHeightLimit||1080,this.maxRenderHeight=e.maxRenderHeight||0,this._boundResize=this.resize.bind(this),this._boundTimeUpdate=this._timeupdate.bind(this),this._boundSetRate=this.setRate.bind(this),this._boundUpdateColorSpace=this._updateColorSpace.bind(this),this._video&&this.setVideo(e.video),this._onDemandRender&&(this.busy=!1,this._lastDemandTime=null),this._worker=new Worker(e.workerUrl||"jassub-worker.js"),this._worker.onmessage=s=>this._onmessage(s),this._worker.onerror=s=>this._error(s),t.then(()=>{this._worker.postMessage({target:"init",wasmUrl:o._supportsSIMD&&e.modernWasmUrl?e.modernWasmUrl:e.wasmUrl??"jassub-worker.wasm",legacyWasmUrl:e.legacyWasmUrl??"jassub-worker.wasm.js",asyncRender:typeof createImageBitmap<"u"&&(e.asyncRender??!0),onDemandRender:this._onDemandRender,width:this._canvasctrl.width||0,height:this._canvasctrl.height||0,blendMode:e.blendMode||"js",subUrl:e.subUrl,subContent:e.subContent||null,fonts:e.fonts||[],availableFonts:e.availableFonts||{"liberation sans":"./default.woff2"},fallbackFont:e.fallbackFont||"liberation sans",debug:this.debug,targetFps:e.targetFps||24,dropAllAnimations:e.dropAllAnimations,dropAllBlur:e.dropAllBlur,libassMemoryLimit:e.libassMemoryLimit||0,libassGlyphLimit:e.libassGlyphLimit||0,useLocalFonts:typeof queryLocalFonts<"u"&&(e.useLocalFonts??!0),hasBitmapBug:o._hasBitmapBug}),this._offscreenRender===!0&&this.sendMessage("offscreenCanvas",null,[this._canvasctrl])})}_createCanvas(){return this._canvas=document.createElement("canvas"),this._canvas.style.display="block",this._canvas.style.position="absolute",this._canvas.style.pointerEvents="none",this._canvasParent.appendChild(this._canvas),this._canvas}static _supportsSIMD=null;static _hasAlphaBug=null;static _hasBitmapBug=null;static _testSIMD(){if(o._supportsSIMD===null)try{o._supportsSIMD=WebAssembly.validate(Uint8Array.of(0,97,115,109,1,0,0,0,1,5,1,96,0,1,123,3,2,1,0,10,10,1,8,0,65,0,253,15,253,98,11))}catch{o._supportsSIMD=!1}}static async _testImageBugs(){if(o._hasBitmapBug!==null)return;const e=document.createElement("canvas"),t=e.getContext("2d",{willReadFrequently:!0});if(!t)throw new Error("Canvas rendering not supported");if(typeof ImageData.prototype.constructor=="function")try{new ImageData(new Uint8ClampedArray([0,0,0,0]),1,1)}catch{console.log("Detected that ImageData is not constructable despite browser saying so"),self.ImageData=function(h,d,l){const f=t.createImageData(d,l);return h&&f.data.set(h),f}}const s=document.createElement("canvas"),r=s.getContext("2d",{willReadFrequently:!0});if(!r)throw new Error("Canvas rendering not supported");e.width=s.width=1,e.height=s.height=1,t.clearRect(0,0,1,1),r.clearRect(0,0,1,1);const n=r.getImageData(0,0,1,1).data;t.putImageData(new ImageData(new Uint8ClampedArray([0,255,0,0]),1,1),0,0),r.drawImage(e,0,0);const a=r.getImageData(0,0,1,1).data;if(o._hasAlphaBug=n[1]!==a[1],o._hasAlphaBug&&console.log("Detected a browser having issue with transparent pixels, applying workaround"),typeof createImageBitmap<"u"){const i=new Uint8ClampedArray([255,0,255,0,255]).subarray(1,5);r.drawImage(await createImageBitmap(new ImageData(i,1)),0,0);const{data:h}=r.getImageData(0,0,1,1);o._hasBitmapBug=!1;for(const[d,l]of h.entries())if(Math.abs(i[d]-l)>15){o._hasBitmapBug=!0,console.log("Detected a browser having issue with partial bitmaps, applying workaround");break}}else o._hasBitmapBug=!1;e.remove(),s.remove()}static async _test(){o._testSIMD(),await o._testImageBugs()}resize(e=0,t=0,s=0,r=0,n=this._video?.paused){if((!e||!t)&&this._video){const a=this._getVideoPosition();let i=null;if(this._videoWidth){const h=this._video.videoWidth/this._videoWidth,d=this._video.videoHeight/this._videoHeight;i=this._computeCanvasSize((a.width||0)/h,(a.height||0)/d)}else i=this._computeCanvasSize(a.width||0,a.height||0);e=i.width,t=i.height,this._canvasParent&&(s=a.y-(this._canvasParent.getBoundingClientRect().top-this._video.getBoundingClientRect().top),r=a.x),this._canvas.style.width=a.width+"px",this._canvas.style.height=a.height+"px"}this._canvas.style.top=s+"px",this._canvas.style.left=r+"px",n&&this.busy===!1?this.busy=!0:n=!1,this.sendMessage("canvas",{width:e,height:t,videoWidth:this._videoWidth||this._video.videoWidth,videoHeight:this._videoHeight||this._video.videoHeight,force:n})}_getVideoPosition(e=this._video.videoWidth,t=this._video.videoHeight){const s=e/t,{offsetWidth:r,offsetHeight:n}=this._video,a=r/n;e=r,t=n,a>s?e=Math.floor(n*s):t=Math.floor(r/s);const i=(r-e)/2,h=(n-t)/2;return{width:e,height:t,x:i,y:h}}_computeCanvasSize(e=0,t=0){const s=this.prescaleFactor<=0?1:this.prescaleFactor,r=self.devicePixelRatio||1;if(t<=0||e<=0)e=0,t=0;else{const n=s<1?-1:1;let a=t*r;n*a*s<=n*this.prescaleHeightLimit?a*=s:n*a0&&a>this.maxRenderHeight&&(a=this.maxRenderHeight),e*=a/t,t=a}return{width:e,height:t}}_timeupdate({type:e}){const s={seeking:!0,waiting:!0,playing:!1}[e];s!=null&&(this._playstate=s),this.setCurrentTime(this._video.paused||this._playstate,this._video.currentTime+this.timeOffset)}setVideo(e){e instanceof HTMLVideoElement?(this._removeListeners(),this._video=e,this._onDemandRender?this._video.requestVideoFrameCallback(this._handleRVFC.bind(this)):(this._playstate=e.paused,e.addEventListener("timeupdate",this._boundTimeUpdate,!1),e.addEventListener("progress",this._boundTimeUpdate,!1),e.addEventListener("waiting",this._boundTimeUpdate,!1),e.addEventListener("seeking",this._boundTimeUpdate,!1),e.addEventListener("playing",this._boundTimeUpdate,!1),e.addEventListener("ratechange",this._boundSetRate,!1),e.addEventListener("resize",this._boundResize,!1)),"VideoFrame"in window&&(e.addEventListener("loadedmetadata",this._boundUpdateColorSpace,!1),e.readyState>2&&this._updateColorSpace()),e.videoWidth>0&&this.resize(),typeof ResizeObserver<"u"&&(this._ro||(this._ro=new ResizeObserver(()=>this.resize())),this._ro.observe(e))):this._error("Video element invalid!")}runBenchmark(){this.sendMessage("runBenchmark")}setTrackByUrl(e){this.sendMessage("setTrackByUrl",{url:e}),this._reAttachOffscreen(),this._ctx&&(this._ctx.filter="none")}setTrack(e){this.sendMessage("setTrack",{content:e}),this._reAttachOffscreen(),this._ctx&&(this._ctx.filter="none")}freeTrack(){this.sendMessage("freeTrack")}setIsPaused(e){this.sendMessage("video",{isPaused:e})}setRate(e){this.sendMessage("video",{rate:e})}setCurrentTime(e,t,s){this.sendMessage("video",{isPaused:e,currentTime:t,rate:s,colorSpace:this._videoColorSpace})}createEvent(e){this.sendMessage("createEvent",{event:e})}setEvent(e,t){this.sendMessage("setEvent",{event:e,index:t})}removeEvent(e){this.sendMessage("removeEvent",{index:e})}getEvents(e){this._fetchFromWorker({target:"getEvents"},(t,{events:s})=>{e(t,s)})}styleOverride(e){this.sendMessage("styleOverride",{style:e})}disableStyleOverride(){this.sendMessage("disableStyleOverride")}createStyle(e){this.sendMessage("createStyle",{style:e})}setStyle(e,t){this.sendMessage("setStyle",{style:e,index:t})}removeStyle(e){this.sendMessage("removeStyle",{index:e})}getStyles(e){this._fetchFromWorker({target:"getStyles"},(t,{styles:s})=>{e(t,s)})}addFont(e){this.sendMessage("addFont",{font:e})}setDefaultFont(e){this.sendMessage("defaultFont",{font:e})}_sendLocalFont(e){try{queryLocalFonts().then(t=>{const s=t?.find(r=>r.fullName.toLowerCase()===e);s&&s.blob().then(r=>{r.arrayBuffer().then(n=>{this.addFont(new Uint8Array(n))})})})}catch(t){console.warn("Local fonts API:",t)}}_getLocalFont({font:e}){try{navigator?.permissions?.query?navigator.permissions.query({name:"local-fonts"}).then(t=>{t.state==="granted"&&this._sendLocalFont(e)}):this._sendLocalFont(e)}catch(t){console.warn("Local fonts API:",t)}}_unbusy(){this._lastDemandTime?this._demandRender(this._lastDemandTime):this.busy=!1}_handleRVFC(e,{mediaTime:t,width:s,height:r}){if(this._destroyed)return null;this.busy?this._lastDemandTime={mediaTime:t,width:s,height:r}:(this.busy=!0,this._demandRender({mediaTime:t,width:s,height:r})),this._video.requestVideoFrameCallback(this._handleRVFC.bind(this))}_demandRender({mediaTime:e,width:t,height:s}){this._lastDemandTime=null,(t!==this._videoWidth||s!==this._videoHeight)&&(this._videoWidth=t,this._videoHeight=s,this.resize()),this.sendMessage("demand",{time:e+this.timeOffset})}_detachOffscreen(){if(!this._offscreenRender||this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas,this._ctx=this._canvasctrl.getContext("2d"),this.sendMessage("detachOffscreen"),this.busy=!1,this.resize(0,0,0,0,!0)}_reAttachOffscreen(){if(!this._offscreenRender||!this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas.transferControlToOffscreen(),this._ctx=!1,this.sendMessage("offscreenCanvas",null,[this._canvasctrl]),this.resize(0,0,0,0,!0)}_updateColorSpace(){this._video.requestVideoFrameCallback(()=>{try{const e=new VideoFrame(this._video);this._videoColorSpace=c[e.colorSpace.matrix],e.close(),this.sendMessage("getColorSpace")}catch(e){console.warn(e)}})}_verifyColorSpace({subtitleColorSpace:e,videoColorSpace:t=this._videoColorSpace}){!e||!t||e!==t&&(this._detachOffscreen(),this._ctx.filter=`url("data:image/svg+xml;utf8, #f")`)}_render({images:e,asyncRender:t,times:s,width:r,height:n,colorSpace:a}){this._unbusy(),this.debug&&(s.IPCTime=Date.now()-s.JSRenderTime),(this._canvasctrl.width!==r||this._canvasctrl.height!==n)&&(this._canvasctrl.width=r,this._canvasctrl.height=n,this._verifyColorSpace({subtitleColorSpace:a})),this._ctx.clearRect(0,0,this._canvasctrl.width,this._canvasctrl.height);for(const i of e)i.image&&(t?(this._ctx.drawImage(i.image,i.x,i.y),i.image.close()):(this._bufferCanvas.width=i.w,this._bufferCanvas.height=i.h,this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(i.image)),i.w,i.h),0,0),this._ctx.drawImage(this._bufferCanvas,i.x,i.y)));if(this.debug){s.JSRenderTime=Date.now()-s.JSRenderTime-s.IPCTime;let i=0;const h=s.bitmaps||e.length;delete s.bitmaps;for(const d in s)i+=s[d];console.log("Bitmaps: "+h+" Total: "+(i|0)+"ms",s)}}_fixAlpha(e){if(o._hasAlphaBug)for(let t=3;t1?e[t]:1;return e}_ready(){this._init(),this.dispatchEvent(new CustomEvent("ready"))}async sendMessage(e,t={},s){await this._loaded,s?this._worker.postMessage({target:e,transferable:s,...t},[...s]):this._worker.postMessage({target:e,...t})}_fetchFromWorker(e,t){try{const s=e.target,r=setTimeout(()=>{a(new Error("Error: Timeout while try to fetch "+s))},5e3),n=({data:i})=>{i.target===s&&(t(null,i),this._worker.removeEventListener("message",n),this._worker.removeEventListener("error",a),clearTimeout(r))},a=i=>{t(i),this._worker.removeEventListener("message",n),this._worker.removeEventListener("error",a),clearTimeout(r)};this._worker.addEventListener("message",n),this._worker.addEventListener("error",a),this._worker.postMessage(e)}catch(s){this._error(s)}}_console({content:e,command:t}){console[t].apply(console,JSON.parse(e))}_onmessage({data:e}){this["_"+e.target]&&this["_"+e.target](e)}_error(e){const t=e instanceof Error?e:e instanceof ErrorEvent?e.error:new Error(e),s=e instanceof Event?new ErrorEvent(e.type,e):new ErrorEvent("error",{error:t});return this.dispatchEvent(s),console.error(t),t}_removeListeners(){this._video&&(this._ro&&this._ro.unobserve(this._video),this._ctx&&(this._ctx.filter="none"),this._video.removeEventListener("timeupdate",this._boundTimeUpdate),this._video.removeEventListener("progress",this._boundTimeUpdate),this._video.removeEventListener("waiting",this._boundTimeUpdate),this._video.removeEventListener("seeking",this._boundTimeUpdate),this._video.removeEventListener("playing",this._boundTimeUpdate),this._video.removeEventListener("ratechange",this._boundSetRate),this._video.removeEventListener("resize",this._boundResize),this._video.removeEventListener("loadedmetadata",this._boundUpdateColorSpace))}destroy(e){return e&&(e=this._error(e)),this._video&&this._canvasParent&&this._video.parentNode?.removeChild(this._canvasParent),this._destroyed=!0,this._removeListeners(),this.sendMessage("destroy"),this._worker?.terminate(),e}}return o});
2 |
--------------------------------------------------------------------------------
/functions.mk:
--------------------------------------------------------------------------------
1 | # For inclusion in toplevel Makefile
2 | # Defines some useful macros and variables for building etc
3 | # If arguments are expected (macro) it needs to be invoked with $(call ...),
4 | # if no arguments are supported the definition is aregular avariable and can be used as such.
5 | # Special macros of the name TR_... create targets (and always take arguments)
6 | # and thus also need to be $(eval ...)'ed
7 |
8 | ## Build stuff
9 |
10 | # @arg1: name of submodule
11 | define PREPARE_SRC_PATCHED
12 | rm -rf build/lib/$(1)
13 | mkdir -p build/lib
14 | cp -r lib/$(1) build/lib/$(1)
15 | $(foreach file, $(wildcard $(BASE_DIR)build/patches/$(1)/*.patch), \
16 | patch -d "$(BASE_DIR)build/lib/$(1)" -Np1 -i $(file) && \
17 | ) :
18 | endef
19 |
20 | # @arg1: name of submdolue
21 | define PREPARE_SRC_VPATH
22 | rm -rf build/lib/$(1)
23 | mkdir -p build/lib/$(1)
24 | touch build/lib/$(1)/configured
25 | endef
26 |
27 | # All projects we build have autogen.sh, otherwise we could also fallback to `autoreconf -ivf .`
28 | RECONF_AUTO := NOCONFIGURE=1 ./autogen.sh
29 |
30 | CONF_ARGS = --enable-optimize
31 |
32 | ifeq (${MODERN},1)
33 | override CONF_ARGS += --enable-simd
34 | endif
35 |
36 | # @arg1: path to source directory; defaults to current working directory
37 | define CONFIGURE_AUTO
38 | emconfigure $(or $(1),.)/configure \
39 | --prefix="$(DIST_DIR)" \
40 | --host=x86-none-linux \
41 | --build=x86_64 \
42 | --enable-static \
43 | --disable-shared \
44 | --disable-debug \
45 | $(CONF_ARGS)
46 | endef
47 |
48 | # @arg1: path to source directory; defaults to current working directory
49 | define CONFIGURE_CMAKE
50 | emcmake cmake -S "$(or $(1),.)" -DCMAKE_INSTALL_PREFIX="$(DIST_DIR)"
51 | endef
52 |
53 | # FIXME: Propagate jobserver info with $(MAKE) and set up our makefile for fully parallel builds
54 | JSO_MAKE := emmake make -j "$(shell nproc)"
55 |
56 | ## Clean and git related
57 |
58 | # @arg1: submodule name
59 | define TR_GIT_SM_RESET
60 | git-$(1):
61 | cd lib/$(1) && \
62 | git reset --hard && \
63 | git clean -dfx
64 | git submodule update --force lib/$(1)
65 |
66 | .PHONY: git-$(1)
67 | endef
68 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | interface ASS_Event {
2 | Start: number;
3 | Duration: number;
4 | Style: string;
5 | Name: string;
6 | MarginL: number;
7 | MarginR: number;
8 | MarginV: number;
9 | Effect: string;
10 | Text: string;
11 | ReadOrder: number;
12 | Layer: number;
13 | _index: number;
14 | }
15 |
16 | interface ASS_Style {
17 | Name: string;
18 | FontName: string;
19 | FontSize: number;
20 | PrimaryColour: number; // uint32_t RGBA
21 | SecondaryColour: number; // uint32_t RGBA
22 | OutlineColour: number; // uint32_t RGBA
23 | BackColour: number; // uint32_t RGBA
24 | Bold: number;
25 | Italic: number;
26 | Underline: number;
27 | StrikeOut: number;
28 | ScaleX: number;
29 | ScaleY: number;
30 | Spacing: number;
31 | Angle: number;
32 | BorderStyle: number;
33 | Outline: number;
34 | Shadow: number;
35 | Alignment: number;
36 | MarginL: number;
37 | MarginR: number;
38 | MarginV: number;
39 | Encoding: number;
40 | treat_fontname_as_pattern: number;
41 | Blur: number;
42 | Justify: number;
43 | }
44 |
45 | interface JassubOptions {
46 |
47 | video?: HTMLVideoElement;
48 | canvas?: HTMLCanvasElement;
49 |
50 | blendMode?: 'js' | 'wasm';
51 |
52 | asyncRender?: boolean;
53 | offscreenRender?: boolean;
54 | onDemandRender?: boolean;
55 | targetFps?: number;
56 | timeOffset?: number;
57 |
58 | debug?: boolean;
59 | prescaleFactor?: number;
60 | prescaleHeightLimit?: number;
61 | maxRenderHeight?: number;
62 | dropAllAnimations?: boolean;
63 | dropAllBlur?: boolean
64 |
65 | workerUrl?: string;
66 | wasmUrl?: string;
67 | legacyWasmUrl?: string;
68 | modernWasmUrl?: string;
69 |
70 | subUrl?: string;
71 | subContent?: string;
72 |
73 | fonts?: string[] | Uint8Array[];
74 | availableFonts?: Record;
75 | fallbackFont?: string;
76 | useLocalFonts?: boolean;
77 |
78 | libassMemoryLimit?: number;
79 | libassGlyphLimit?: number;
80 | }
81 |
82 | type ASS_EventCallback = (error: Error | null, event: ASS_Event[]) => void;
83 | type ASS_StyleCallback = (error: Error | null, event: ASS_Style[]) => void;
84 |
85 | export default class JASSUB {
86 | constructor (options: JassubOptions);
87 |
88 | resize (width?: number, height?: number, top?: number, left?: number): void;
89 | setVideo (video: HTMLVideoElement): void;
90 | runBenchmark (): void;
91 |
92 | setTrackByUrl (url: string): void;
93 | setTrack (content: string): void;
94 | freeTrack (): void;
95 |
96 | setIsPaused (isPaused: boolean): void;
97 | setRate (rate: number): void;
98 | setCurrentTime (isPaused?: boolean, currentTime?: number, rate?: number): void;
99 |
100 | createEvent (event: ASS_Event): void;
101 | setEvent (event: ASS_Event, index: number): void;
102 | removeEvent (index: number): void;
103 | getEvents (callback: ASS_EventCallback): void;
104 |
105 | createStyle (style: ASS_Style): void;
106 | setStyle (style: ASS_Style, index: number): void;
107 | removeStyle (index: number): void;
108 | getStyles (callback: ASS_StyleCallback): void;
109 | styleOverride (style: ASS_Style): void;
110 | disableStyleOverride(): void;
111 | setDefaultFont(font: string): void;
112 |
113 | addFont (font: string | Uint8Array): void;
114 |
115 | sendMessage (target: string, data?: Record, transferable?: Transferable[]): void;
116 | destroy (err?: string): void;
117 |
118 | static _hasAlphaBug: boolean;
119 | static _hasBitmapBug: boolean;
120 | _ctx: CanvasRenderingContext2D;
121 | _canvas: HTMLCanvasElement;
122 | }
123 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jassub",
3 | "version": "1.8.6",
4 | "description": "libass Subtitle Renderer and Parser library for browsers",
5 | "main": "src/jassub.js",
6 | "type": "module",
7 | "files": [
8 | "dist/COPYRIGHT",
9 | "dist/default.woff2",
10 | "dist/jassub*",
11 | "src/jassub.js",
12 | "index.d.ts"
13 | ],
14 | "scripts": {
15 | "build": "node vite.build.js",
16 | "docker:build": "docker build -t thaunknown/jassub-build .",
17 | "docker:run": "docker run -it --rm -v ${PWD}:/code --name thaunknown_jassub-build thaunknown/jassub-build:latest"
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "git+https://github.com/ThaUnknown/jassub.git"
22 | },
23 | "keywords": [
24 | "libass",
25 | "subtitle",
26 | "wasm",
27 | "emscripten"
28 | ],
29 | "author": "ThaUnknown",
30 | "contributors": [
31 | "SubtitlesOctopus Contributors"
32 | ],
33 | "license": "LGPL-2.1-or-later AND (FTL OR GPL-2.0-or-later) AND MIT AND MIT-Modern-Variant AND ISC AND NTP AND Zlib AND BSL-1.0",
34 | "bugs": {
35 | "url": "https://github.com/ThaUnknown/jassub/issues"
36 | },
37 | "homepage": "https://github.com/ThaUnknown/jassub",
38 | "dependencies": {
39 | "rvfc-polyfill": "^1.0.7"
40 | },
41 | "devDependencies": {
42 | "@types/emscripten": "^1.39.7",
43 | "terser": "^5.19.4",
44 | "vite": "^4.4.9",
45 | "vite-plugin-static-copy": "^0.17.0"
46 | }
47 | }
--------------------------------------------------------------------------------
/pnpm-lock.yaml:
--------------------------------------------------------------------------------
1 | lockfileVersion: '9.0'
2 |
3 | settings:
4 | autoInstallPeers: true
5 | excludeLinksFromLockfile: false
6 |
7 | importers:
8 |
9 | .:
10 | dependencies:
11 | rvfc-polyfill:
12 | specifier: ^1.0.7
13 | version: 1.0.7
14 | devDependencies:
15 | '@types/emscripten':
16 | specifier: ^1.39.7
17 | version: 1.39.7
18 | terser:
19 | specifier: ^5.19.4
20 | version: 5.19.4
21 | vite:
22 | specifier: ^4.4.9
23 | version: 4.4.9(terser@5.19.4)
24 | vite-plugin-static-copy:
25 | specifier: ^0.17.0
26 | version: 0.17.0(vite@4.4.9)
27 |
28 | packages:
29 |
30 | '@esbuild/android-arm64@0.18.20':
31 | resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
32 | engines: {node: '>=12'}
33 | cpu: [arm64]
34 | os: [android]
35 |
36 | '@esbuild/android-arm@0.18.20':
37 | resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==}
38 | engines: {node: '>=12'}
39 | cpu: [arm]
40 | os: [android]
41 |
42 | '@esbuild/android-x64@0.18.20':
43 | resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==}
44 | engines: {node: '>=12'}
45 | cpu: [x64]
46 | os: [android]
47 |
48 | '@esbuild/darwin-arm64@0.18.20':
49 | resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==}
50 | engines: {node: '>=12'}
51 | cpu: [arm64]
52 | os: [darwin]
53 |
54 | '@esbuild/darwin-x64@0.18.20':
55 | resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==}
56 | engines: {node: '>=12'}
57 | cpu: [x64]
58 | os: [darwin]
59 |
60 | '@esbuild/freebsd-arm64@0.18.20':
61 | resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==}
62 | engines: {node: '>=12'}
63 | cpu: [arm64]
64 | os: [freebsd]
65 |
66 | '@esbuild/freebsd-x64@0.18.20':
67 | resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==}
68 | engines: {node: '>=12'}
69 | cpu: [x64]
70 | os: [freebsd]
71 |
72 | '@esbuild/linux-arm64@0.18.20':
73 | resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==}
74 | engines: {node: '>=12'}
75 | cpu: [arm64]
76 | os: [linux]
77 |
78 | '@esbuild/linux-arm@0.18.20':
79 | resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==}
80 | engines: {node: '>=12'}
81 | cpu: [arm]
82 | os: [linux]
83 |
84 | '@esbuild/linux-ia32@0.18.20':
85 | resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==}
86 | engines: {node: '>=12'}
87 | cpu: [ia32]
88 | os: [linux]
89 |
90 | '@esbuild/linux-loong64@0.18.20':
91 | resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==}
92 | engines: {node: '>=12'}
93 | cpu: [loong64]
94 | os: [linux]
95 |
96 | '@esbuild/linux-mips64el@0.18.20':
97 | resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==}
98 | engines: {node: '>=12'}
99 | cpu: [mips64el]
100 | os: [linux]
101 |
102 | '@esbuild/linux-ppc64@0.18.20':
103 | resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==}
104 | engines: {node: '>=12'}
105 | cpu: [ppc64]
106 | os: [linux]
107 |
108 | '@esbuild/linux-riscv64@0.18.20':
109 | resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==}
110 | engines: {node: '>=12'}
111 | cpu: [riscv64]
112 | os: [linux]
113 |
114 | '@esbuild/linux-s390x@0.18.20':
115 | resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==}
116 | engines: {node: '>=12'}
117 | cpu: [s390x]
118 | os: [linux]
119 |
120 | '@esbuild/linux-x64@0.18.20':
121 | resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==}
122 | engines: {node: '>=12'}
123 | cpu: [x64]
124 | os: [linux]
125 |
126 | '@esbuild/netbsd-x64@0.18.20':
127 | resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==}
128 | engines: {node: '>=12'}
129 | cpu: [x64]
130 | os: [netbsd]
131 |
132 | '@esbuild/openbsd-x64@0.18.20':
133 | resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==}
134 | engines: {node: '>=12'}
135 | cpu: [x64]
136 | os: [openbsd]
137 |
138 | '@esbuild/sunos-x64@0.18.20':
139 | resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==}
140 | engines: {node: '>=12'}
141 | cpu: [x64]
142 | os: [sunos]
143 |
144 | '@esbuild/win32-arm64@0.18.20':
145 | resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==}
146 | engines: {node: '>=12'}
147 | cpu: [arm64]
148 | os: [win32]
149 |
150 | '@esbuild/win32-ia32@0.18.20':
151 | resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==}
152 | engines: {node: '>=12'}
153 | cpu: [ia32]
154 | os: [win32]
155 |
156 | '@esbuild/win32-x64@0.18.20':
157 | resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==}
158 | engines: {node: '>=12'}
159 | cpu: [x64]
160 | os: [win32]
161 |
162 | '@jridgewell/gen-mapping@0.3.3':
163 | resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==}
164 | engines: {node: '>=6.0.0'}
165 |
166 | '@jridgewell/resolve-uri@3.1.1':
167 | resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==}
168 | engines: {node: '>=6.0.0'}
169 |
170 | '@jridgewell/set-array@1.1.2':
171 | resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
172 | engines: {node: '>=6.0.0'}
173 |
174 | '@jridgewell/source-map@0.3.5':
175 | resolution: {integrity: sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==}
176 |
177 | '@jridgewell/sourcemap-codec@1.4.15':
178 | resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
179 |
180 | '@jridgewell/trace-mapping@0.3.19':
181 | resolution: {integrity: sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==}
182 |
183 | '@nodelib/fs.scandir@2.1.5':
184 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
185 | engines: {node: '>= 8'}
186 |
187 | '@nodelib/fs.stat@2.0.5':
188 | resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
189 | engines: {node: '>= 8'}
190 |
191 | '@nodelib/fs.walk@1.2.8':
192 | resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
193 | engines: {node: '>= 8'}
194 |
195 | '@types/emscripten@1.39.7':
196 | resolution: {integrity: sha512-tLqYV94vuqDrXh515F/FOGtBcRMTPGvVV1LzLbtYDcQmmhtpf/gLYf+hikBbQk8MzOHNz37wpFfJbYAuSn8HqA==}
197 |
198 | acorn@8.10.0:
199 | resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==}
200 | engines: {node: '>=0.4.0'}
201 | hasBin: true
202 |
203 | anymatch@3.1.3:
204 | resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
205 | engines: {node: '>= 8'}
206 |
207 | binary-extensions@2.2.0:
208 | resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
209 | engines: {node: '>=8'}
210 |
211 | braces@3.0.2:
212 | resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
213 | engines: {node: '>=8'}
214 |
215 | buffer-from@1.1.2:
216 | resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
217 |
218 | chokidar@3.5.3:
219 | resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
220 | engines: {node: '>= 8.10.0'}
221 |
222 | commander@2.20.3:
223 | resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
224 |
225 | esbuild@0.18.20:
226 | resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==}
227 | engines: {node: '>=12'}
228 | hasBin: true
229 |
230 | fast-glob@3.3.1:
231 | resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==}
232 | engines: {node: '>=8.6.0'}
233 |
234 | fastq@1.15.0:
235 | resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==}
236 |
237 | fill-range@7.0.1:
238 | resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
239 | engines: {node: '>=8'}
240 |
241 | fs-extra@11.1.1:
242 | resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==}
243 | engines: {node: '>=14.14'}
244 |
245 | fsevents@2.3.3:
246 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
247 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
248 | os: [darwin]
249 |
250 | glob-parent@5.1.2:
251 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
252 | engines: {node: '>= 6'}
253 |
254 | graceful-fs@4.2.11:
255 | resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
256 |
257 | is-binary-path@2.1.0:
258 | resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
259 | engines: {node: '>=8'}
260 |
261 | is-extglob@2.1.1:
262 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
263 | engines: {node: '>=0.10.0'}
264 |
265 | is-glob@4.0.3:
266 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
267 | engines: {node: '>=0.10.0'}
268 |
269 | is-number@7.0.0:
270 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
271 | engines: {node: '>=0.12.0'}
272 |
273 | jsonfile@6.1.0:
274 | resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
275 |
276 | merge2@1.4.1:
277 | resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
278 | engines: {node: '>= 8'}
279 |
280 | micromatch@4.0.5:
281 | resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
282 | engines: {node: '>=8.6'}
283 |
284 | nanoid@3.3.6:
285 | resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==}
286 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
287 | hasBin: true
288 |
289 | normalize-path@3.0.0:
290 | resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
291 | engines: {node: '>=0.10.0'}
292 |
293 | picocolors@1.0.0:
294 | resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
295 |
296 | picomatch@2.3.1:
297 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
298 | engines: {node: '>=8.6'}
299 |
300 | postcss@8.4.29:
301 | resolution: {integrity: sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==}
302 | engines: {node: ^10 || ^12 || >=14}
303 |
304 | queue-microtask@1.2.3:
305 | resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
306 |
307 | readdirp@3.6.0:
308 | resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
309 | engines: {node: '>=8.10.0'}
310 |
311 | reusify@1.0.4:
312 | resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
313 | engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
314 |
315 | rollup@3.29.2:
316 | resolution: {integrity: sha512-CJouHoZ27v6siztc21eEQGo0kIcE5D1gVPA571ez0mMYb25LGYGKnVNXpEj5MGlepmDWGXNjDB5q7uNiPHC11A==}
317 | engines: {node: '>=14.18.0', npm: '>=8.0.0'}
318 | hasBin: true
319 |
320 | run-parallel@1.2.0:
321 | resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
322 |
323 | rvfc-polyfill@1.0.7:
324 | resolution: {integrity: sha512-seBl7J1J3/k0LuzW2T9fG6JIOpni5AbU+/87LA+zTYKgTVhsfShmS8K/yOo1eeEjGJHnAdkVAUUM+PEjN9Mpkw==}
325 |
326 | source-map-js@1.0.2:
327 | resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
328 | engines: {node: '>=0.10.0'}
329 |
330 | source-map-support@0.5.21:
331 | resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
332 |
333 | source-map@0.6.1:
334 | resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
335 | engines: {node: '>=0.10.0'}
336 |
337 | terser@5.19.4:
338 | resolution: {integrity: sha512-6p1DjHeuluwxDXcuT9VR8p64klWJKo1ILiy19s6C9+0Bh2+NWTX6nD9EPppiER4ICkHDVB1RkVpin/YW2nQn/g==}
339 | engines: {node: '>=10'}
340 | hasBin: true
341 |
342 | to-regex-range@5.0.1:
343 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
344 | engines: {node: '>=8.0'}
345 |
346 | universalify@2.0.0:
347 | resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==}
348 | engines: {node: '>= 10.0.0'}
349 |
350 | vite-plugin-static-copy@0.17.0:
351 | resolution: {integrity: sha512-2HpNbHfDt8SDy393AGXh9llHkc8FJMQkI8s3T5WsH3SWLMO+f5cFIyPErl4yGKU9Uh3Vaqsd4lHZYTf042fQ2A==}
352 | engines: {node: ^14.18.0 || >=16.0.0}
353 | peerDependencies:
354 | vite: ^3.0.0 || ^4.0.0
355 |
356 | vite@4.4.9:
357 | resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==}
358 | engines: {node: ^14.18.0 || >=16.0.0}
359 | hasBin: true
360 | peerDependencies:
361 | '@types/node': '>= 14'
362 | less: '*'
363 | lightningcss: ^1.21.0
364 | sass: '*'
365 | stylus: '*'
366 | sugarss: '*'
367 | terser: ^5.4.0
368 | peerDependenciesMeta:
369 | '@types/node':
370 | optional: true
371 | less:
372 | optional: true
373 | lightningcss:
374 | optional: true
375 | sass:
376 | optional: true
377 | stylus:
378 | optional: true
379 | sugarss:
380 | optional: true
381 | terser:
382 | optional: true
383 |
384 | snapshots:
385 |
386 | '@esbuild/android-arm64@0.18.20':
387 | optional: true
388 |
389 | '@esbuild/android-arm@0.18.20':
390 | optional: true
391 |
392 | '@esbuild/android-x64@0.18.20':
393 | optional: true
394 |
395 | '@esbuild/darwin-arm64@0.18.20':
396 | optional: true
397 |
398 | '@esbuild/darwin-x64@0.18.20':
399 | optional: true
400 |
401 | '@esbuild/freebsd-arm64@0.18.20':
402 | optional: true
403 |
404 | '@esbuild/freebsd-x64@0.18.20':
405 | optional: true
406 |
407 | '@esbuild/linux-arm64@0.18.20':
408 | optional: true
409 |
410 | '@esbuild/linux-arm@0.18.20':
411 | optional: true
412 |
413 | '@esbuild/linux-ia32@0.18.20':
414 | optional: true
415 |
416 | '@esbuild/linux-loong64@0.18.20':
417 | optional: true
418 |
419 | '@esbuild/linux-mips64el@0.18.20':
420 | optional: true
421 |
422 | '@esbuild/linux-ppc64@0.18.20':
423 | optional: true
424 |
425 | '@esbuild/linux-riscv64@0.18.20':
426 | optional: true
427 |
428 | '@esbuild/linux-s390x@0.18.20':
429 | optional: true
430 |
431 | '@esbuild/linux-x64@0.18.20':
432 | optional: true
433 |
434 | '@esbuild/netbsd-x64@0.18.20':
435 | optional: true
436 |
437 | '@esbuild/openbsd-x64@0.18.20':
438 | optional: true
439 |
440 | '@esbuild/sunos-x64@0.18.20':
441 | optional: true
442 |
443 | '@esbuild/win32-arm64@0.18.20':
444 | optional: true
445 |
446 | '@esbuild/win32-ia32@0.18.20':
447 | optional: true
448 |
449 | '@esbuild/win32-x64@0.18.20':
450 | optional: true
451 |
452 | '@jridgewell/gen-mapping@0.3.3':
453 | dependencies:
454 | '@jridgewell/set-array': 1.1.2
455 | '@jridgewell/sourcemap-codec': 1.4.15
456 | '@jridgewell/trace-mapping': 0.3.19
457 |
458 | '@jridgewell/resolve-uri@3.1.1': {}
459 |
460 | '@jridgewell/set-array@1.1.2': {}
461 |
462 | '@jridgewell/source-map@0.3.5':
463 | dependencies:
464 | '@jridgewell/gen-mapping': 0.3.3
465 | '@jridgewell/trace-mapping': 0.3.19
466 |
467 | '@jridgewell/sourcemap-codec@1.4.15': {}
468 |
469 | '@jridgewell/trace-mapping@0.3.19':
470 | dependencies:
471 | '@jridgewell/resolve-uri': 3.1.1
472 | '@jridgewell/sourcemap-codec': 1.4.15
473 |
474 | '@nodelib/fs.scandir@2.1.5':
475 | dependencies:
476 | '@nodelib/fs.stat': 2.0.5
477 | run-parallel: 1.2.0
478 |
479 | '@nodelib/fs.stat@2.0.5': {}
480 |
481 | '@nodelib/fs.walk@1.2.8':
482 | dependencies:
483 | '@nodelib/fs.scandir': 2.1.5
484 | fastq: 1.15.0
485 |
486 | '@types/emscripten@1.39.7': {}
487 |
488 | acorn@8.10.0: {}
489 |
490 | anymatch@3.1.3:
491 | dependencies:
492 | normalize-path: 3.0.0
493 | picomatch: 2.3.1
494 |
495 | binary-extensions@2.2.0: {}
496 |
497 | braces@3.0.2:
498 | dependencies:
499 | fill-range: 7.0.1
500 |
501 | buffer-from@1.1.2: {}
502 |
503 | chokidar@3.5.3:
504 | dependencies:
505 | anymatch: 3.1.3
506 | braces: 3.0.2
507 | glob-parent: 5.1.2
508 | is-binary-path: 2.1.0
509 | is-glob: 4.0.3
510 | normalize-path: 3.0.0
511 | readdirp: 3.6.0
512 | optionalDependencies:
513 | fsevents: 2.3.3
514 |
515 | commander@2.20.3: {}
516 |
517 | esbuild@0.18.20:
518 | optionalDependencies:
519 | '@esbuild/android-arm': 0.18.20
520 | '@esbuild/android-arm64': 0.18.20
521 | '@esbuild/android-x64': 0.18.20
522 | '@esbuild/darwin-arm64': 0.18.20
523 | '@esbuild/darwin-x64': 0.18.20
524 | '@esbuild/freebsd-arm64': 0.18.20
525 | '@esbuild/freebsd-x64': 0.18.20
526 | '@esbuild/linux-arm': 0.18.20
527 | '@esbuild/linux-arm64': 0.18.20
528 | '@esbuild/linux-ia32': 0.18.20
529 | '@esbuild/linux-loong64': 0.18.20
530 | '@esbuild/linux-mips64el': 0.18.20
531 | '@esbuild/linux-ppc64': 0.18.20
532 | '@esbuild/linux-riscv64': 0.18.20
533 | '@esbuild/linux-s390x': 0.18.20
534 | '@esbuild/linux-x64': 0.18.20
535 | '@esbuild/netbsd-x64': 0.18.20
536 | '@esbuild/openbsd-x64': 0.18.20
537 | '@esbuild/sunos-x64': 0.18.20
538 | '@esbuild/win32-arm64': 0.18.20
539 | '@esbuild/win32-ia32': 0.18.20
540 | '@esbuild/win32-x64': 0.18.20
541 |
542 | fast-glob@3.3.1:
543 | dependencies:
544 | '@nodelib/fs.stat': 2.0.5
545 | '@nodelib/fs.walk': 1.2.8
546 | glob-parent: 5.1.2
547 | merge2: 1.4.1
548 | micromatch: 4.0.5
549 |
550 | fastq@1.15.0:
551 | dependencies:
552 | reusify: 1.0.4
553 |
554 | fill-range@7.0.1:
555 | dependencies:
556 | to-regex-range: 5.0.1
557 |
558 | fs-extra@11.1.1:
559 | dependencies:
560 | graceful-fs: 4.2.11
561 | jsonfile: 6.1.0
562 | universalify: 2.0.0
563 |
564 | fsevents@2.3.3:
565 | optional: true
566 |
567 | glob-parent@5.1.2:
568 | dependencies:
569 | is-glob: 4.0.3
570 |
571 | graceful-fs@4.2.11: {}
572 |
573 | is-binary-path@2.1.0:
574 | dependencies:
575 | binary-extensions: 2.2.0
576 |
577 | is-extglob@2.1.1: {}
578 |
579 | is-glob@4.0.3:
580 | dependencies:
581 | is-extglob: 2.1.1
582 |
583 | is-number@7.0.0: {}
584 |
585 | jsonfile@6.1.0:
586 | dependencies:
587 | universalify: 2.0.0
588 | optionalDependencies:
589 | graceful-fs: 4.2.11
590 |
591 | merge2@1.4.1: {}
592 |
593 | micromatch@4.0.5:
594 | dependencies:
595 | braces: 3.0.2
596 | picomatch: 2.3.1
597 |
598 | nanoid@3.3.6: {}
599 |
600 | normalize-path@3.0.0: {}
601 |
602 | picocolors@1.0.0: {}
603 |
604 | picomatch@2.3.1: {}
605 |
606 | postcss@8.4.29:
607 | dependencies:
608 | nanoid: 3.3.6
609 | picocolors: 1.0.0
610 | source-map-js: 1.0.2
611 |
612 | queue-microtask@1.2.3: {}
613 |
614 | readdirp@3.6.0:
615 | dependencies:
616 | picomatch: 2.3.1
617 |
618 | reusify@1.0.4: {}
619 |
620 | rollup@3.29.2:
621 | optionalDependencies:
622 | fsevents: 2.3.3
623 |
624 | run-parallel@1.2.0:
625 | dependencies:
626 | queue-microtask: 1.2.3
627 |
628 | rvfc-polyfill@1.0.7: {}
629 |
630 | source-map-js@1.0.2: {}
631 |
632 | source-map-support@0.5.21:
633 | dependencies:
634 | buffer-from: 1.1.2
635 | source-map: 0.6.1
636 |
637 | source-map@0.6.1: {}
638 |
639 | terser@5.19.4:
640 | dependencies:
641 | '@jridgewell/source-map': 0.3.5
642 | acorn: 8.10.0
643 | commander: 2.20.3
644 | source-map-support: 0.5.21
645 |
646 | to-regex-range@5.0.1:
647 | dependencies:
648 | is-number: 7.0.0
649 |
650 | universalify@2.0.0: {}
651 |
652 | vite-plugin-static-copy@0.17.0(vite@4.4.9):
653 | dependencies:
654 | chokidar: 3.5.3
655 | fast-glob: 3.3.1
656 | fs-extra: 11.1.1
657 | picocolors: 1.0.0
658 | vite: 4.4.9(terser@5.19.4)
659 |
660 | vite@4.4.9(terser@5.19.4):
661 | dependencies:
662 | esbuild: 0.18.20
663 | postcss: 8.4.29
664 | rollup: 3.29.2
665 | terser: 5.19.4
666 | optionalDependencies:
667 | fsevents: 2.3.3
668 |
--------------------------------------------------------------------------------
/run-buildah-build.sh:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 | set -e
3 | cd "$(dirname "$0")"
4 |
5 | . ./run-common.sh
6 |
7 | if [ "$#" -eq 0 ] ; then
8 | cmd="make"
9 | else
10 | cmd=""
11 | fi
12 |
13 | if [ "$FAST" -eq 0 ] ; then
14 | buildah bud -t "$IMAGE" .
15 | buildah rm "$CONTAINER" >/dev/null 2>&1 || :
16 | buildah from --name "$CONTAINER" "$IMAGE":latest
17 | fi
18 | buildah run -t -v "${PWD}":/code "$CONTAINER" $cmd "$@"
19 |
--------------------------------------------------------------------------------
/run-common.sh:
--------------------------------------------------------------------------------
1 | usage() {
2 | echo "$0 [-f] [-c con_name] [-i img_name] [commmand [command_args...]]"
3 | echo " -f: Skip building the container and reuse existing (\"fast\")"
4 | echo " -c: Name of the container to create/use;"
5 | echo " defaults to thaunknown_jassub-build"
6 | echo " -i: Name of the image to buld/use;"
7 | echo " defaults to thaunknown/jassub-build"
8 | echo "If no command is given `make` without arguments will be executed"
9 | exit 2
10 | }
11 |
12 | OPTIND=1
13 | CONTAINER="thaunknown_jassub-build"
14 | IMAGE="thaunknown/jassub-build"
15 | FAST=0
16 | while getopts "fc:s:" opt ; do
17 | case "$opt" in
18 | f) FAST=1 ;;
19 | c) CONTAINER="$OPTARG" ;;
20 | i) IMAGE="$OPTARG" ;;
21 | *) usage ;;
22 | esac
23 | done
24 |
25 | if [ "$OPTIND" -gt 1 ] ; then
26 | shift $(( OPTIND - 1 ))
27 | fi
28 |
--------------------------------------------------------------------------------
/run-docker-build.sh:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 | set -e
3 | cd "$(dirname "$0")"
4 |
5 | . ./run-common.sh
6 |
7 | if [ "$FAST" -eq 0 ] ; then
8 | docker build -t "$IMAGE" .
9 | fi
10 | if [ "$#" -eq 0 ] ; then
11 | docker run -it --rm -v "${PWD}":/code --name "$CONTAINER" "$IMAGE":latest
12 | else
13 | docker run -it --rm -v "${PWD}":/code --name "$CONTAINER" "$IMAGE":latest "$@"
14 | fi
15 |
--------------------------------------------------------------------------------
/src/JASSUB.cpp:
--------------------------------------------------------------------------------
1 | #include "../lib/libass/libass/ass.h"
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include
10 | #include
11 |
12 | int log_level = 3;
13 |
14 | class ReusableBuffer {
15 | private:
16 | void *buffer;
17 | size_t lessen_counter;
18 |
19 | public:
20 | size_t size;
21 | ReusableBuffer() : buffer(NULL), size(0), lessen_counter(0) {
22 | }
23 |
24 | ~ReusableBuffer() {
25 | free(buffer);
26 | }
27 |
28 | void clear() {
29 | free(buffer);
30 | buffer = NULL;
31 | size = 0;
32 | lessen_counter = 0;
33 | }
34 |
35 | void *take(size_t new_size) {
36 | if (size >= new_size) {
37 | if (size >= 1.3 * new_size) {
38 | // big reduction request
39 | lessen_counter++;
40 | } else {
41 | lessen_counter = 0;
42 | }
43 | if (lessen_counter < 10) {
44 | // not reducing the buffer yet
45 | memset(buffer, 0, new_size);
46 | return buffer;
47 | }
48 | }
49 |
50 | free(buffer);
51 | buffer = malloc(new_size);
52 | if (buffer) {
53 | size = new_size;
54 | memset(buffer, 0, size);
55 | } else
56 | size = 0;
57 | lessen_counter = 0;
58 | return buffer;
59 | }
60 | };
61 |
62 | void msg_callback(int level, const char *fmt, va_list va, void *data) {
63 | if (level > log_level) // 6 for verbose
64 | return;
65 |
66 | const int ERR_LEVEL = 1;
67 | FILE *stream = level <= ERR_LEVEL ? stderr : stdout;
68 |
69 | fprintf(stream, "JASSUB: ");
70 | vfprintf(stream, fmt, va);
71 | fprintf(stream, "\n");
72 | }
73 |
74 | const float MIN_UINT8_CAST = 0.9 / 255;
75 | const float MAX_UINT8_CAST = 255.9 / 255;
76 |
77 | #define CLAMP_UINT8(value) ((value > MIN_UINT8_CAST) ? ((value < MAX_UINT8_CAST) ? (int)(value * 255) : 255) : 0)
78 |
79 | typedef struct RenderResult {
80 | public:
81 | int x, y, w, h;
82 | size_t image;
83 | RenderResult *next;
84 | } RenderResult;
85 |
86 | // maximum regions - a grid of 3x3
87 | #define MAX_BLEND_STORAGES (3 * 3)
88 | struct RenderBlendStorage {
89 | RenderResult next;
90 | ReusableBuffer buf;
91 | bool taken;
92 | };
93 |
94 | #define MIN(x, y) (((x) < (y)) ? (x) : (y))
95 | #define MAX(x, y) (((x) > (y)) ? (x) : (y))
96 |
97 | class BoundingBox {
98 | public:
99 | int min_x, max_x, min_y, max_y;
100 |
101 | BoundingBox() : min_x(-1), max_x(-1), min_y(-1), max_y(-1) {
102 | }
103 |
104 | bool empty() const {
105 | return min_x == -1;
106 | }
107 |
108 | void add(int x1, int y1, int w, int h) {
109 | int x2 = x1 + w - 1, y2 = y1 + h - 1;
110 | min_x = (min_x < 0) ? x1 : MIN(min_x, x1);
111 | min_y = (min_y < 0) ? y1 : MIN(min_y, y1);
112 | max_x = (max_x < 0) ? x2 : MAX(max_x, x2);
113 | max_y = (max_y < 0) ? y2 : MAX(max_y, y2);
114 | }
115 |
116 | bool intersets(const BoundingBox &other) const {
117 | return !(other.min_x > max_x || other.max_x < min_x || other.min_y > max_y || other.max_y < min_y);
118 | }
119 |
120 | bool tryMerge(BoundingBox &other) {
121 | if (!intersets(other))
122 | return false;
123 |
124 | min_x = MIN(min_x, other.min_x);
125 | min_y = MIN(min_y, other.min_y);
126 | max_x = MAX(max_x, other.max_x);
127 | max_y = MAX(max_y, other.max_y);
128 | return true;
129 | }
130 |
131 | void clear() {
132 | min_x = max_x = min_y = max_y = -1;
133 | }
134 | };
135 |
136 | /**
137 | * \brief Overwrite tag with whitespace to nullify its effect
138 | * Boundaries are inclusive at both ends.
139 | */
140 | static void _remove_tag(char *begin, char *end) {
141 | if (end < begin)
142 | return;
143 | memset(begin, ' ', end - begin + 1);
144 | }
145 |
146 | /**
147 | * \param begin point to the first character of the tag name (after backslash)
148 | * \param end last character that can be read; at least the name itself
149 | and the following character if any must be included
150 | * \return true if tag may cause animations, false if it will definitely not
151 | */
152 | static bool _is_animated_tag(char *begin, char *end) {
153 | if (end <= begin)
154 | return false;
155 |
156 | size_t length = end - begin + 1;
157 |
158 | #define check_simple_tag(tag) (sizeof(tag) - 1 < length && !strncmp(begin, tag, sizeof(tag) - 1))
159 | #define check_complex_tag(tag) (check_simple_tag(tag) && (begin[sizeof(tag) - 1] == '(' || begin[sizeof(tag) - 1] == ' ' || begin[sizeof(tag) - 1] == '\t'))
160 | switch (begin[0]) {
161 | case 'k': //-fallthrough
162 | case 'K':
163 | // Karaoke: k, kf, ko, K and kt ; no other valid ASS-tag starts with k/K
164 | return true;
165 | case 't':
166 | // Animated transform: no other valid tag begins with t
167 | // non-nested t-tags have to be complex tags even in single argument
168 | // form, but nested t-tags (which act like independent t-tags) are allowed to be
169 | // simple-tags without parentheses due to VSF-parsing quirk.
170 | // Since all valid simple t-tags require the existence of a complex t-tag, we only check for complex tags
171 | // to avoid false positives from invalid simple t-tags. This makes animation-dropping somewhat incorrect
172 | // but as animation detection remains accurate, we consider this to be "good enough"
173 | return check_complex_tag("t");
174 | case 'm':
175 | // Movement: complex tag; again no other valid tag begins with m
176 | // but ensure it's complex just to be sure
177 | return check_complex_tag("move");
178 | case 'f':
179 | // Fade: \fad and Fade (complex): \fade; both complex
180 | // there are several other valid tags beginning with f
181 | return check_complex_tag("fad") || check_complex_tag("fade");
182 | }
183 |
184 | return false;
185 | #undef check_complex_tag
186 | #undef check_simple_tag
187 | }
188 |
189 | /**
190 | * \param start First character after { (optionally spaces can be dropped)
191 | * \param end Last character before } (optionally spaces can be dropped)
192 | * \param drop_animations If true animation tags will be discarded
193 | * \return true if after processing the event may contain animations
194 | (i.e. when dropping animations this is always false)
195 | */
196 | static bool _is_block_animated(char *start, char *end, bool drop_animations) {
197 | char *tag_start = NULL; // points to beginning backslash
198 | for (char *p = start; p <= end; p++) {
199 | if (*p == '\\') {
200 | // It is safe to go one before and beyond unconditionally
201 | // because the text passed in must be surronded by { }
202 | if (tag_start && _is_animated_tag(tag_start + 1, p - 1)) {
203 | if (!drop_animations)
204 | return true;
205 | // For \t transforms this will assume the final state
206 | _remove_tag(tag_start, p - 1);
207 | }
208 | tag_start = p;
209 | }
210 | }
211 |
212 | if (tag_start && _is_animated_tag(tag_start + 1, end)) {
213 | if (!drop_animations)
214 | return true;
215 | _remove_tag(tag_start, end);
216 | }
217 |
218 | return false;
219 | }
220 |
221 | /**
222 | * \param event ASS event to be processed
223 | * \param drop_animations If true animation tags will be discarded
224 | * \return true if after processing the event may contain animations
225 | (i.e. when dropping animations this is always false)
226 | */
227 | static bool _is_event_animated(ASS_Event *event, bool drop_animations) {
228 | // Event is animated if it has an Effect or animated override tags
229 | if (event->Effect && event->Effect[0] != '\0') {
230 | if (!drop_animations)
231 | return 1;
232 | event->Effect[0] = '\0';
233 | }
234 |
235 | // Search for override blocks
236 | // Only closed {...}-blocks are parsed by VSFilters and libass
237 | char *block_start = NULL; // points to opening {
238 | for (char *p = event->Text; *p != '\0'; p++) {
239 | switch (*p) {
240 | case '{':
241 | // Escaping the opening curly bracket to not start an override block is
242 | // a VSFilter-incompatible libass extension. But we only use libass, so...
243 | if (!block_start && (p == event->Text || *(p - 1) != '\\'))
244 | block_start = p;
245 | break;
246 | case '}':
247 | if (block_start && p - block_start > 2 && _is_block_animated(block_start + 1, p - 1, drop_animations))
248 | return true;
249 | block_start = NULL;
250 | break;
251 | default:
252 | break;
253 | }
254 | }
255 |
256 | return false;
257 | }
258 |
259 | static char *copyString(const std::string &str) {
260 | char *result = new char[str.length() + 1];
261 | strcpy(result, str.data());
262 | return result;
263 | }
264 |
265 | class JASSUB {
266 | private:
267 | ReusableBuffer m_buffer;
268 | RenderBlendStorage m_blendParts[MAX_BLEND_STORAGES];
269 | bool drop_animations;
270 | int scanned_events; // next unscanned event index
271 | ASS_Library *ass_library;
272 | ASS_Renderer *ass_renderer;
273 | bool debug;
274 |
275 | int canvas_w;
276 | int canvas_h;
277 |
278 | int status;
279 |
280 | const char *defaultFont;
281 |
282 | public:
283 | ASS_Track *track;
284 |
285 | int trackColorSpace;
286 | int changed = 0;
287 | int count = 0;
288 | int time = 0;
289 | JASSUB(int canvas_w, int canvas_h, const std::string &df, bool debug) {
290 | status = 0;
291 | ass_library = NULL;
292 | ass_renderer = NULL;
293 | track = NULL;
294 | this->canvas_w = canvas_w;
295 | this->canvas_h = canvas_h;
296 | drop_animations = false;
297 | scanned_events = 0;
298 | this->debug = debug;
299 |
300 | defaultFont = copyString(df);
301 | ass_library = ass_library_init();
302 | if (!ass_library) {
303 | fprintf(stderr, "JASSUB: ass_library_init failed!\n");
304 | exit(2);
305 | }
306 |
307 | ass_set_message_cb(ass_library, msg_callback, NULL);
308 |
309 | ass_renderer = ass_renderer_init(ass_library);
310 | if (!ass_renderer) {
311 | fprintf(stderr, "JASSUB: ass_renderer_init failed!\n");
312 | exit(3);
313 | }
314 | ass_set_extract_fonts(ass_library, true);
315 |
316 | resizeCanvas(canvas_w, canvas_h, canvas_w, canvas_h);
317 |
318 | reloadFonts();
319 | m_buffer.clear();
320 | }
321 |
322 | void setLogLevel(int level) {
323 | log_level = level;
324 | }
325 |
326 | void setDropAnimations(int value) {
327 | drop_animations = !!value;
328 | if (drop_animations)
329 | scanAnimations(scanned_events);
330 | }
331 |
332 | /*
333 | * \brief Scan events starting at index i for animations
334 | * and discard animated tags when found.
335 | * Note that once animated tags were dropped they cannot be restored.
336 | * Updates the class member scanned_events to last scanned index.
337 | */
338 | void scanAnimations(int i) {
339 | for (; i < track->n_events; i++) {
340 | _is_event_animated(track->events + i, drop_animations);
341 | }
342 | scanned_events = i;
343 | }
344 |
345 | /* TRACK */
346 | void createTrackMem(std::string buf) {
347 | removeTrack();
348 | track = ass_read_memory(ass_library, buf.data(), buf.size(), NULL);
349 | if (!track) {
350 | fprintf(stderr, "JASSUB: Failed to start a track\n");
351 | exit(4);
352 | }
353 | scanAnimations(0);
354 |
355 | trackColorSpace = track->YCbCrMatrix;
356 | }
357 |
358 | void removeTrack() {
359 | if (track != NULL) {
360 | ass_free_track(track);
361 | track = NULL;
362 | }
363 | }
364 | /* TRACK */
365 |
366 | /* CANVAS */
367 | void resizeCanvas(int canvas_w, int canvas_h, int video_w, int video_h) {
368 | ass_set_storage_size(ass_renderer, video_w, video_h);
369 | ass_set_frame_size(ass_renderer, canvas_w, canvas_h);
370 | this->canvas_h = canvas_h;
371 | this->canvas_w = canvas_w;
372 | }
373 | int getBufferSize(ASS_Image *img) {
374 | int size = 0;
375 | for (ASS_Image *tmp = img; tmp; tmp = tmp->next) {
376 | if (tmp->w == 0 || tmp->h == 0) {
377 | continue;
378 | }
379 | size += sizeof(uint32_t) * tmp->w * tmp->h + sizeof(RenderResult);
380 | }
381 | return size;
382 | }
383 | RenderResult *processImages(ASS_Image *img) {
384 | RenderResult *renderResult = NULL;
385 | char *rawbuffer = (char *)m_buffer.take(getBufferSize(img));
386 | if (rawbuffer == NULL) {
387 | fprintf(stderr, "JASSUB: cannot allocate buffer for rendering\n");
388 | return NULL;
389 | }
390 | for (RenderResult *tmp = renderResult; img; img = img->next) {
391 | int w = img->w, h = img->h;
392 | if (w == 0 || h == 0) continue;
393 |
394 | double alpha = (255 - (img->color & 255)) / 255.0;
395 | if (alpha == 0.0) continue;
396 |
397 | unsigned int datasize = sizeof(uint32_t) * w * h;
398 | size_t *data = (size_t *)rawbuffer;
399 | decodeBitmap(alpha, data, img, w, h);
400 | RenderResult *result = (RenderResult *)(rawbuffer + datasize);
401 | result->w = w;
402 | result->h = h;
403 | result->x = img->dst_x;
404 | result->y = img->dst_y;
405 | result->image = (size_t)data;
406 | result->next = NULL;
407 |
408 | if (tmp) {
409 | tmp->next = result;
410 | } else {
411 | renderResult = result;
412 | }
413 | tmp = result;
414 |
415 | rawbuffer += datasize + sizeof(RenderResult);
416 | ++count;
417 | }
418 | return renderResult;
419 | }
420 |
421 | void decodeBitmap(double alpha, size_t *data, ASS_Image *img, int w, int h) {
422 | uint32_t color = ((img->color << 8) & 0xff0000) | ((img->color >> 8) & 0xff00) | ((img->color >> 24) & 0xff);
423 | uint8_t *pos = img->bitmap;
424 | uint32_t res = 0;
425 | for (uint32_t y = 0; y < h; ++y, pos += img->stride) {
426 | for (uint32_t z = 0; z < w; ++z, ++res) {
427 | uint8_t mask = pos[z];
428 | if (mask != 0)
429 | data[res] = ((uint32_t)(alpha * mask) << 24) | color;
430 | }
431 | }
432 | }
433 |
434 | RenderResult *renderImage(double tm, int force) {
435 | time = 0;
436 | count = 0;
437 |
438 | ASS_Image *imgs = ass_render_frame(ass_renderer, track, (int)(tm * 1000), &changed);
439 | if (imgs == NULL || (changed == 0 && !force)) return NULL;
440 |
441 | if (debug) time = emscripten_get_now();
442 |
443 | return processImages(imgs);
444 | }
445 |
446 | void quitLibrary() {
447 | removeTrack();
448 | ass_renderer_done(ass_renderer);
449 | ass_library_done(ass_library);
450 | m_buffer.clear();
451 | }
452 |
453 | void setDefaultFont(const std::string &name) {
454 | defaultFont = copyString(name);
455 | reloadFonts();
456 | }
457 |
458 | void reloadFonts() {
459 | ass_set_fonts(ass_renderer, NULL, defaultFont, ASS_FONTPROVIDER_NONE, NULL, 1);
460 | }
461 |
462 | void addFont(const std::string &name, int data, unsigned long data_size) {
463 | ass_add_font(ass_library, name.c_str(), (char *)data, (size_t)data_size);
464 | free((char *)data);
465 | }
466 |
467 | void setMargin(int top, int bottom, int left, int right) {
468 | ass_set_margins(ass_renderer, top, bottom, left, right);
469 | }
470 |
471 | int getEventCount() const {
472 | return track->n_events;
473 | }
474 |
475 | int allocEvent() {
476 | return ass_alloc_event(track);
477 | }
478 |
479 | void removeEvent(int eid) {
480 | ass_free_event(track, eid);
481 | }
482 |
483 | int getStyleCount() const {
484 | return track->n_styles;
485 | }
486 |
487 | int allocStyle() {
488 | return ass_alloc_style(track);
489 | }
490 |
491 | void removeStyle(int sid) {
492 | ass_free_event(track, sid);
493 | }
494 |
495 | void removeAllEvents() {
496 | ass_flush_events(track);
497 | }
498 |
499 | void setMemoryLimits(int glyph_limit, int bitmap_cache_limit) {
500 | printf("JASSUB: setting total libass memory limits to: glyph=%d MiB, bitmap cache=%d MiB\n", glyph_limit, bitmap_cache_limit);
501 | ass_set_cache_limits(ass_renderer, glyph_limit, bitmap_cache_limit);
502 | }
503 |
504 | RenderResult *renderBlend(double tm, int force) {
505 | time = 0;
506 | count = 0;
507 |
508 | ASS_Image *img = ass_render_frame(ass_renderer, track, (int)(tm * 1000), &changed);
509 | if (img == NULL || (changed == 0 && !force)) {
510 | return NULL;
511 | }
512 |
513 | if (debug) time = emscripten_get_now();
514 | for (int i = 0; i < MAX_BLEND_STORAGES; i++) {
515 | m_blendParts[i].taken = false;
516 | }
517 |
518 | // split rendering region in 9 pieces (as on 3x3 grid)
519 | int split_x_low = canvas_w / 3, split_x_high = 2 * canvas_w / 3;
520 | int split_y_low = canvas_h / 3, split_y_high = 2 * canvas_h / 3;
521 | BoundingBox boxes[MAX_BLEND_STORAGES];
522 | for (ASS_Image *cur = img; cur != NULL; cur = cur->next) {
523 | if (cur->w == 0 || cur->h == 0)
524 | continue; // skip empty images
525 | int index = 0;
526 | int middle_x = cur->dst_x + (cur->w >> 1), middle_y = cur->dst_y + (cur->h >> 1);
527 | if (middle_y > split_y_high) {
528 | index += 2 * 3;
529 | } else if (middle_y > split_y_low) {
530 | index += 1 * 3;
531 | }
532 | if (middle_x > split_x_high) {
533 | index += 2;
534 | } else if (middle_y > split_x_low) {
535 | index += 1;
536 | }
537 | boxes[index].add(cur->dst_x, cur->dst_y, cur->w, cur->h);
538 | }
539 |
540 | // now merge regions as long as there are intersecting regions
541 | for (;;) {
542 | bool merged = false;
543 | for (int box1 = 0; box1 < MAX_BLEND_STORAGES - 1; box1++) {
544 | if (boxes[box1].empty())
545 | continue;
546 | for (int box2 = box1 + 1; box2 < MAX_BLEND_STORAGES; box2++) {
547 | if (boxes[box2].empty())
548 | continue;
549 | if (boxes[box1].tryMerge(boxes[box2])) {
550 | boxes[box2].clear();
551 | merged = true;
552 | }
553 | }
554 | }
555 | if (!merged)
556 | break;
557 | }
558 |
559 | RenderResult *renderResult = NULL;
560 | for (int box = 0; box < MAX_BLEND_STORAGES; box++) {
561 | if (boxes[box].empty()) {
562 | continue;
563 | }
564 | RenderResult *part = renderBlendPart(boxes[box], img);
565 | if (part == NULL) {
566 | break; // memory allocation error
567 | }
568 | if (renderResult) {
569 | part->next = renderResult->next;
570 | renderResult->next = part;
571 | } else {
572 | renderResult = part;
573 | }
574 |
575 | ++count;
576 | }
577 |
578 | return renderResult;
579 | }
580 |
581 | RenderResult *renderBlendPart(const BoundingBox &rect, ASS_Image *img) {
582 | int width = rect.max_x - rect.min_x + 1, height = rect.max_y - rect.min_y + 1;
583 |
584 | // make float buffer for blending
585 | const size_t buffer_size = width * height * 4 * sizeof(float);
586 | float *buf = (float *)m_buffer.take(buffer_size);
587 | if (buf == NULL) {
588 | fprintf(stderr, "JASSUB: cannot allocate buffer for blending\n");
589 | return NULL;
590 | }
591 |
592 | // blend things in
593 | for (ASS_Image *cur = img; cur != NULL; cur = cur->next) {
594 | if (cur->dst_x < rect.min_x || cur->dst_y < rect.min_y)
595 | continue; // skip images not fully within render region
596 | int curw = cur->w, curh = cur->h;
597 | if (curw == 0 || curh == 0 || cur->dst_x + curw - 1 > rect.max_x || cur->dst_y + curh - 1 > rect.max_y)
598 | continue; // skip empty images or images outside render region
599 | int a = (255 - (cur->color & 0xFF));
600 | if (a == 0)
601 | continue; // skip transparent images
602 |
603 | int curs = (cur->stride >= curw) ? cur->stride : curw;
604 | int curx = cur->dst_x - rect.min_x, cury = cur->dst_y - rect.min_y;
605 |
606 | unsigned char *bitmap = cur->bitmap;
607 | float normalized_a = a / 255.0;
608 | float r = ((cur->color >> 24) & 0xFF) / 255.0;
609 | float g = ((cur->color >> 16) & 0xFF) / 255.0;
610 | float b = ((cur->color >> 8) & 0xFF) / 255.0;
611 |
612 | int buf_line_coord = cury * width;
613 | for (int y = 0, bitmap_offset = 0; y < curh; y++, bitmap_offset += curs, buf_line_coord += width) {
614 | for (int x = 0; x < curw; x++) {
615 | float pix_alpha = bitmap[bitmap_offset + x] * normalized_a / 255.0;
616 | float inv_alpha = 1.0 - pix_alpha;
617 |
618 | int buf_coord = (buf_line_coord + curx + x) << 2;
619 | float *buf_r = buf + buf_coord;
620 | float *buf_g = buf + buf_coord + 1;
621 | float *buf_b = buf + buf_coord + 2;
622 | float *buf_a = buf + buf_coord + 3;
623 |
624 | // do the compositing, pre-multiply image RGB with alpha for current pixel
625 | *buf_a = pix_alpha + *buf_a * inv_alpha;
626 | *buf_r = r * pix_alpha + *buf_r * inv_alpha;
627 | *buf_g = g * pix_alpha + *buf_g * inv_alpha;
628 | *buf_b = b * pix_alpha + *buf_b * inv_alpha;
629 | }
630 | }
631 | }
632 |
633 | // find closest free buffer
634 | size_t needed = sizeof(unsigned int) * width * height;
635 | RenderBlendStorage *storage = m_blendParts, *bigBuffer = NULL, *smallBuffer = NULL;
636 | for (int buffer_index = 0; buffer_index < MAX_BLEND_STORAGES; buffer_index++, storage++) {
637 | if (storage->taken)
638 | continue;
639 | if (storage->buf.size >= needed) {
640 | if (bigBuffer == NULL || bigBuffer->buf.size > storage->buf.size)
641 | bigBuffer = storage;
642 | } else {
643 | if (smallBuffer == NULL || smallBuffer->buf.size > storage->buf.size)
644 | smallBuffer = storage;
645 | }
646 | }
647 |
648 | if (bigBuffer != NULL) {
649 | storage = bigBuffer;
650 | } else if (smallBuffer != NULL) {
651 | storage = smallBuffer;
652 | } else {
653 | printf("JASSUB: cannot get a buffer for rendering part!\n");
654 | return NULL;
655 | }
656 |
657 | unsigned int *result = (unsigned int *)storage->buf.take(needed);
658 | if (result == NULL) {
659 | printf("JASSUB: cannot make a buffer for rendering part!\n");
660 | return NULL;
661 | }
662 | storage->taken = true;
663 |
664 | // now build the result;
665 | for (int y = 0, buf_line_coord = 0; y < height; y++, buf_line_coord += width) {
666 | for (int x = 0; x < width; x++) {
667 | unsigned int pixel = 0;
668 | int buf_coord = (buf_line_coord + x) << 2;
669 | float alpha = buf[buf_coord + 3];
670 | if (alpha > MIN_UINT8_CAST) {
671 | // need to un-multiply the result
672 | float value = buf[buf_coord] / alpha;
673 | pixel |= CLAMP_UINT8(value); // R
674 | value = buf[buf_coord + 1] / alpha;
675 | pixel |= CLAMP_UINT8(value) << 8; // G
676 | value = buf[buf_coord + 2] / alpha;
677 | pixel |= CLAMP_UINT8(value) << 16; // B
678 | pixel |= CLAMP_UINT8(alpha) << 24; // A
679 | }
680 | result[buf_line_coord + x] = pixel;
681 | }
682 | }
683 |
684 | // return the thing
685 | storage->next.x = rect.min_x;
686 | storage->next.y = rect.min_y;
687 | storage->next.w = width;
688 | storage->next.h = height;
689 | storage->next.image = (size_t)result;
690 |
691 | return &storage->next;
692 | }
693 |
694 | // BINDING
695 | ASS_Event *getEvent(int i) {
696 | return &track->events[i];
697 | }
698 |
699 | ASS_Style *getStyle(int i) {
700 | return &track->styles[i];
701 | }
702 |
703 | void styleOverride(ASS_Style style) {
704 | int set_force_flags = ASS_OVERRIDE_BIT_STYLE
705 | | ASS_OVERRIDE_BIT_SELECTIVE_FONT_SCALE;
706 |
707 | ass_set_selective_style_override_enabled(ass_renderer, set_force_flags);
708 | ass_set_selective_style_override(ass_renderer, &style);
709 | ass_set_font_scale(ass_renderer, 0.3);
710 | }
711 |
712 | void disableStyleOverride() {
713 | ass_set_selective_style_override_enabled(ass_renderer, 0);
714 | ass_set_font_scale(ass_renderer, 1);
715 | }
716 | };
717 |
718 | static uint32_t getDuration(const ASS_Event &evt) {
719 | return (uint32_t)evt.Duration;
720 | }
721 |
722 | static void setDuration(ASS_Event &evt, const long ms) {
723 | evt.Duration = ms;
724 | }
725 |
726 | static uint32_t getStart(const ASS_Event &evt) {
727 | return (uint32_t)evt.Start;
728 | }
729 |
730 | static void setStart(ASS_Event &evt, const long ms) {
731 | evt.Start = ms;
732 | }
733 |
734 | static std::string getEventName(const ASS_Event &evt) {
735 | return evt.Name;
736 | }
737 |
738 | static void setEventName(ASS_Event &evt, const std::string &str) {
739 | evt.Name = copyString(str);
740 | }
741 |
742 | static std::string getText(const ASS_Event &evt) {
743 | return evt.Text;
744 | }
745 |
746 | static void setText(ASS_Event &evt, const std::string &str) {
747 | evt.Text = copyString(str);
748 | }
749 |
750 | static std::string getEffect(const ASS_Event &evt) {
751 | return evt.Effect;
752 | }
753 |
754 | static void setEffect(ASS_Event &evt, const std::string &str) {
755 | evt.Effect = copyString(str);
756 | }
757 |
758 | static std::string getStyleName(const ASS_Style &style) {
759 | return style.Name;
760 | }
761 |
762 | static void setStyleName(ASS_Style &style, const std::string &str) {
763 | style.Name = copyString(str);
764 | }
765 |
766 | static std::string getFontName(const ASS_Style &style) {
767 | return style.FontName;
768 | }
769 |
770 | static void setFontName(ASS_Style &style, const std::string &str) {
771 | style.FontName = copyString(str);
772 | }
773 |
774 | static RenderResult getNext(const RenderResult &res) {
775 | return *res.next;
776 | }
777 |
778 | EMSCRIPTEN_BINDINGS(JASSUB) {
779 | emscripten::class_("RenderResult")
780 | .property("x", &RenderResult::x)
781 | .property("y", &RenderResult::y)
782 | .property("w", &RenderResult::w)
783 | .property("h", &RenderResult::h)
784 | .property("next", &getNext)
785 | .property("image", &RenderResult::image);
786 |
787 | emscripten::class_("ASS_Style")
788 | .property("Name", &getStyleName, &setStyleName)
789 | .property("FontName", &getFontName, &setFontName)
790 | .property("FontSize", &ASS_Style::FontSize)
791 | .property("PrimaryColour", &ASS_Style::PrimaryColour)
792 | .property("SecondaryColour", &ASS_Style::SecondaryColour)
793 | .property("OutlineColour", &ASS_Style::OutlineColour)
794 | .property("BackColour", &ASS_Style::BackColour)
795 | .property("Bold", &ASS_Style::Bold)
796 | .property("Italic", &ASS_Style::Italic)
797 | .property("Underline", &ASS_Style::Underline)
798 | .property("StrikeOut", &ASS_Style::StrikeOut)
799 | .property("ScaleX", &ASS_Style::ScaleX)
800 | .property("ScaleY", &ASS_Style::ScaleY)
801 | .property("Spacing", &ASS_Style::Spacing)
802 | .property("Angle", &ASS_Style::Angle)
803 | .property("BorderStyle", &ASS_Style::BorderStyle)
804 | .property("Outline", &ASS_Style::Outline)
805 | .property("Shadow", &ASS_Style::Shadow)
806 | .property("Alignment", &ASS_Style::Alignment)
807 | .property("MarginL", &ASS_Style::MarginL)
808 | .property("MarginR", &ASS_Style::MarginR)
809 | .property("MarginV", &ASS_Style::MarginV)
810 | .property("Encoding", &ASS_Style::Encoding)
811 | .property("treat_fontname_as_pattern", &ASS_Style::treat_fontname_as_pattern)
812 | .property("Blur", &ASS_Style::Blur)
813 | .property("Justify", &ASS_Style::Justify);
814 |
815 | emscripten::class_("ASS_Event")
816 | .property("Start", &getStart, &setStart)
817 | .property("Duration", &getDuration, &setDuration)
818 | .property("Name", &getEventName, &setEventName)
819 | .property("Effect", &getEffect, &setEffect)
820 | .property("Text", &getText, &setText)
821 | .property("ReadOrder", &ASS_Event::ReadOrder)
822 | .property("Layer", &ASS_Event::Layer)
823 | .property("Style", &ASS_Event::Style)
824 | .property("MarginL", &ASS_Event::MarginL)
825 | .property("MarginR", &ASS_Event::MarginR)
826 | .property("MarginV", &ASS_Event::MarginV);
827 |
828 | emscripten::class_("JASSUB")
829 | .constructor()
830 | .function("setLogLevel", &JASSUB::setLogLevel)
831 | .function("setDropAnimations", &JASSUB::setDropAnimations)
832 | .function("createTrackMem", &JASSUB::createTrackMem)
833 | .function("removeTrack", &JASSUB::removeTrack)
834 | .function("resizeCanvas", &JASSUB::resizeCanvas)
835 | .function("quitLibrary", &JASSUB::quitLibrary)
836 | .function("addFont", &JASSUB::addFont)
837 | .function("reloadFonts", &JASSUB::reloadFonts)
838 | .function("setMargin", &JASSUB::setMargin)
839 | .function("getEventCount", &JASSUB::getEventCount)
840 | .function("allocEvent", &JASSUB::allocEvent)
841 | .function("allocStyle", &JASSUB::allocStyle)
842 | .function("removeEvent", &JASSUB::removeEvent)
843 | .function("getStyleCount", &JASSUB::getStyleCount)
844 | .function("removeStyle", &JASSUB::removeStyle)
845 | .function("removeAllEvents", &JASSUB::removeAllEvents)
846 | .function("setMemoryLimits", &JASSUB::setMemoryLimits)
847 | .function("renderBlend", &JASSUB::renderBlend, emscripten::allow_raw_pointers())
848 | .function("renderImage", &JASSUB::renderImage, emscripten::allow_raw_pointers())
849 | .function("getEvent", &JASSUB::getEvent, emscripten::allow_raw_pointers())
850 | .function("getStyle", &JASSUB::getStyle, emscripten::allow_raw_pointers())
851 | .function("styleOverride", &JASSUB::styleOverride, emscripten::allow_raw_pointers())
852 | .function("disableStyleOverride", &JASSUB::disableStyleOverride)
853 | .function("setDefaultFont", &JASSUB::setDefaultFont)
854 | .property("trackColorSpace", &JASSUB::trackColorSpace)
855 | .property("changed", &JASSUB::changed)
856 | .property("count", &JASSUB::count)
857 | .property("time", &JASSUB::time);
858 | }
859 |
--------------------------------------------------------------------------------
/src/pre-worker.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-global-assign, no-unused-vars, prefer-const, no-extend-native */
2 | /* global out, err, updateMemoryViews, wasmMemory */
3 |
4 | function assert (c, m) {
5 | if (!c) throw m
6 | }
7 |
8 | let asm = null
9 |
10 | out = text => {
11 | if (text === 'JASSUB: No usable fontconfig configuration file found, using fallback.') {
12 | console.debug(text)
13 | } else {
14 | console.log(text)
15 | }
16 | }
17 |
18 | err = text => {
19 | if (text === 'Fontconfig error: Cannot load default config file: No such file: (null)') {
20 | console.debug(text)
21 | } else {
22 | console.error(text)
23 | }
24 | }
25 |
26 | // patch EMS function to include Uint8Clamped, but call old function too
27 | updateMemoryViews = (_super => {
28 | return () => {
29 | _super()
30 | self.wasmMemory = wasmMemory
31 | self.HEAPU8C = new Uint8ClampedArray(wasmMemory.buffer)
32 | self.HEAPU8 = new Uint8Array(wasmMemory.buffer)
33 | }
34 | })(updateMemoryViews)
35 |
--------------------------------------------------------------------------------
/src/worker.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-extend-native */
2 | // @ts-ignore
3 | import WASM from 'wasm'
4 |
5 | // polyfills for old or weird engines
6 |
7 | if (!String.prototype.startsWith) {
8 | String.prototype.startsWith = function (s, p = 0) {
9 | return this.substring(p, s.length) === s
10 | }
11 | }
12 |
13 | if (!String.prototype.includes) {
14 | String.prototype.includes = function (s, p) {
15 | return this.indexOf(s, p) !== -1
16 | }
17 | }
18 |
19 | if (!Uint8Array.prototype.slice) {
20 | Uint8Array.prototype.slice = function (b, e) {
21 | return new Uint8Array(this.subarray(b, e))
22 | }
23 | }
24 |
25 | function toAbsoluteIndex (index, length) {
26 | const integer = index >> 0
27 | return integer < 0 ? Math.max(integer + length, 0) : Math.min(integer, length)
28 | }
29 |
30 | if (!Uint8Array.prototype.fill) {
31 | Int8Array.prototype.fill = Int16Array.prototype.fill = Int32Array.prototype.fill = Uint8Array.prototype.fill = Uint16Array.prototype.fill = Uint32Array.prototype.fill = Float32Array.prototype.fill = Float64Array.prototype.fill = Array.prototype.fill = function (value) {
32 | if (this == null) throw new TypeError('this is null or not defined')
33 |
34 | const O = Object(this)
35 |
36 | const length = O.length >>> 0
37 |
38 | const argumentsLength = arguments.length
39 | let index = toAbsoluteIndex(argumentsLength > 1 ? arguments[1] : undefined, length)
40 | const end = argumentsLength > 2 ? arguments[2] : undefined
41 | const endPos = end === undefined ? length : toAbsoluteIndex(end, length)
42 | while (endPos > index) O[index++] = value
43 | return O
44 | }
45 | }
46 |
47 | if (!Uint8Array.prototype.copyWithin) {
48 | Int8Array.prototype.copyWithin = Int16Array.prototype.copyWithin = Int32Array.prototype.copyWithin = Uint8Array.prototype.copyWithin = Uint16Array.prototype.copyWithin = Uint32Array.prototype.copyWithin = Float32Array.prototype.copyWithin = Float64Array.prototype.copyWithin = Array.prototype.copyWithin = function (target, start) {
49 | const O = Object(this)
50 | const len = O.length >>> 0
51 |
52 | let to = toAbsoluteIndex(target, len)
53 | let from = toAbsoluteIndex(start, len)
54 | const end = arguments.length > 2 ? arguments[2] : undefined
55 | let count = Math.min((end === undefined ? len : toAbsoluteIndex(end, len)) - from, len - to)
56 | let inc = 1
57 | if (from < to && to < from + count) {
58 | inc = -1
59 | from += count - 1
60 | to += count - 1
61 | }
62 | while (count-- > 0) {
63 | if (from in O) O[to] = O[from]
64 | else delete O[to]
65 | to += inc
66 | from += inc
67 | } return O
68 | }
69 | }
70 |
71 | if (!Date.now) Date.now = () => new Date().getTime()
72 | // @ts-ignore
73 | if (!('performance' in self)) self.performance = { now: () => Date.now() }
74 |
75 | // implement console methods if they're missing
76 | if (typeof console === 'undefined') {
77 | const msg = (command, a) => {
78 | postMessage({
79 | target: 'console',
80 | command,
81 | content: JSON.stringify(Array.prototype.slice.call(a))
82 | })
83 | }
84 | // @ts-ignore
85 | self.console = {
86 | log: function () {
87 | msg('log', arguments)
88 | },
89 | debug: function () {
90 | msg('debug', arguments)
91 | },
92 | info: function () {
93 | msg('info', arguments)
94 | },
95 | warn: function () {
96 | msg('warn', arguments)
97 | },
98 | error: function () {
99 | msg('error', arguments)
100 | }
101 | }
102 | console.log('Detected lack of console, overridden console')
103 | }
104 |
105 | let promiseSupported = typeof Promise !== 'undefined'
106 |
107 | // some engines report that Promise resolve isn't a function... what?...
108 | if (promiseSupported) {
109 | try {
110 | let res
111 | // eslint-disable-next-line no-new
112 | new Promise(resolve => { res = resolve })
113 | res()
114 | } catch (error) {
115 | promiseSupported = false
116 | }
117 | }
118 |
119 | // very bad promise polyfill, it's absolutely minimal just to make emscripten work
120 | // in engines that don't have promises, Promise should never be used otherwise
121 | if (!promiseSupported) {
122 | // @ts-ignore
123 | self.Promise = function (cb) {
124 | let then = () => {}
125 | cb(a => setTimeout(() => then(a), 0))
126 | return { then: fn => then = fn }
127 | }
128 | }
129 |
130 | const read_ = (url, ab) => {
131 | const xhr = new XMLHttpRequest()
132 | xhr.open('GET', url, false)
133 | xhr.responseType = ab ? 'arraybuffer' : 'text'
134 | xhr.send(null)
135 | return xhr.response
136 | }
137 | const readAsync = (url, load, err) => {
138 | const xhr = new XMLHttpRequest()
139 | xhr.open('GET', url, true)
140 | xhr.responseType = 'arraybuffer'
141 | xhr.onload = () => {
142 | if ((xhr.status === 200 || xhr.status === 0) && xhr.response) {
143 | return load(xhr.response)
144 | }
145 | }
146 | xhr.onerror = err
147 | xhr.send(null)
148 | }
149 |
150 | let lastCurrentTime = 0
151 | const rate = 1
152 | let rafId = null
153 | let nextIsRaf = false
154 | let lastCurrentTimeReceivedAt = Date.now()
155 | let targetFps = 24
156 | let useLocalFonts = false
157 | let blendMode = 'js'
158 | let availableFonts = {}
159 | const fontMap_ = {}
160 | let fontId = 0
161 | let debug
162 |
163 | self.width = 0
164 | self.height = 0
165 |
166 | let asyncRender = false
167 |
168 | self.addFont = ({ font }) => asyncWrite(font)
169 |
170 | const findAvailableFonts = font => {
171 | font = font.trim().toLowerCase()
172 |
173 | if (font.startsWith('@')) font = font.substring(1)
174 |
175 | if (fontMap_[font]) return
176 |
177 | fontMap_[font] = true
178 |
179 | if (!availableFonts[font]) {
180 | if (useLocalFonts) postMessage({ target: 'getLocalFont', font })
181 | } else {
182 | asyncWrite(availableFonts[font])
183 | }
184 | }
185 |
186 | const asyncWrite = font => {
187 | if (typeof font === 'string') {
188 | readAsync(font, fontData => {
189 | allocFont(new Uint8Array(fontData))
190 | }, console.error)
191 | } else {
192 | allocFont(font)
193 | }
194 | }
195 |
196 | // TODO: this should re-draw last frame!
197 | const allocFont = uint8 => {
198 | const ptr = _malloc(uint8.byteLength)
199 | self.HEAPU8.set(uint8, ptr)
200 | jassubObj.addFont('font-' + (fontId++), ptr, uint8.byteLength)
201 | jassubObj.reloadFonts()
202 | }
203 |
204 | const processAvailableFonts = content => {
205 | if (!availableFonts) return
206 |
207 | const sections = parseAss(content)
208 |
209 | for (let i = 0; i < sections.length; i++) {
210 | for (let j = 0; j < sections[i].body.length; j++) {
211 | if (sections[i].body[j].key === 'Style') {
212 | findAvailableFonts(sections[i].body[j].value.Fontname)
213 | }
214 | }
215 | }
216 |
217 | const regex = /\\fn([^\\}]*?)[\\}]/g
218 | let matches
219 | while ((matches = regex.exec(content)) !== null) {
220 | findAvailableFonts(matches[1])
221 | }
222 | }
223 | /**
224 | * Set the subtitle track.
225 | * @param {!string} content the content of the subtitle file.
226 | */
227 | self.setTrack = ({ content }) => {
228 | // Make sure that the fonts are loaded
229 | processAvailableFonts(content)
230 |
231 | if (dropAllBlur) content = dropBlur(content)
232 | // Tell libass to render the new track
233 | jassubObj.createTrackMem(content)
234 |
235 | subtitleColorSpace = libassYCbCrMap[jassubObj.trackColorSpace]
236 | postMessage({ target: 'verifyColorSpace', subtitleColorSpace })
237 | }
238 |
239 | self.getColorSpace = () => postMessage({ target: 'verifyColorSpace', subtitleColorSpace })
240 |
241 | /**
242 | * Remove subtitle track.
243 | */
244 | self.freeTrack = () => {
245 | jassubObj.removeTrack()
246 | }
247 |
248 | /**
249 | * Set the subtitle track.
250 | * @param {!string} url the URL of the subtitle file.
251 | */
252 | self.setTrackByUrl = ({ url }) => {
253 | self.setTrack({ content: read_(url) })
254 | }
255 |
256 | const getCurrentTime = () => {
257 | const diff = (Date.now() - lastCurrentTimeReceivedAt) / 1000
258 | if (_isPaused) {
259 | return lastCurrentTime
260 | } else {
261 | if (diff > 5) {
262 | console.error('Didn\'t received currentTime > 5 seconds. Assuming video was paused.')
263 | setIsPaused(true)
264 | }
265 | return lastCurrentTime + (diff * rate)
266 | }
267 | }
268 | const setCurrentTime = currentTime => {
269 | lastCurrentTime = currentTime
270 | lastCurrentTimeReceivedAt = Date.now()
271 | if (!rafId) {
272 | if (nextIsRaf) {
273 | rafId = requestAnimationFrame(renderLoop)
274 | } else {
275 | renderLoop()
276 |
277 | // Give onmessage chance to receive all queued messages
278 | setTimeout(() => {
279 | nextIsRaf = false
280 | }, 20)
281 | }
282 | }
283 | }
284 |
285 | let _isPaused = true
286 | const setIsPaused = isPaused => {
287 | if (isPaused !== _isPaused) {
288 | _isPaused = isPaused
289 | if (isPaused) {
290 | if (rafId) {
291 | clearTimeout(rafId)
292 | rafId = null
293 | }
294 | } else {
295 | lastCurrentTimeReceivedAt = Date.now()
296 | rafId = requestAnimationFrame(renderLoop)
297 | }
298 | }
299 | }
300 |
301 | const a = 'BT601'
302 | const b = 'BT709'
303 | const c = 'SMPTE240M'
304 | const d = 'FCC'
305 |
306 | const libassYCbCrMap = [null, a, null, a, a, b, b, c, c, d, d]
307 |
308 | const render = (time, force) => {
309 | const times = {}
310 | const renderStartTime = performance.now()
311 | const renderResult = blendMode === 'wasm' ? jassubObj.renderBlend(time, force || 0) : jassubObj.renderImage(time, force || 0)
312 | if (debug) {
313 | const decodeEndTime = performance.now()
314 | const renderEndTime = jassubObj.time
315 | times.WASMRenderTime = renderEndTime - renderStartTime
316 | times.WASMBitmapDecodeTime = decodeEndTime - renderEndTime
317 | // performance.now is relative to the creation of the scope, since this time MIGHT be used to calculate a time difference
318 | // on the main thread, we need absolute time, not relative
319 | times.JSRenderTime = Date.now()
320 | }
321 | if (jassubObj.changed !== 0 || force) {
322 | const images = []
323 | const buffers = []
324 | if (!renderResult) return paintImages({ images, buffers, times })
325 | if (asyncRender) {
326 | const promises = []
327 | for (let result = renderResult, i = 0; i < jassubObj.count; result = result.next, ++i) {
328 | const reassigned = { w: result.w, h: result.h, x: result.x, y: result.y }
329 | const pointer = result.image
330 | const data = hasBitmapBug ? self.HEAPU8C.slice(pointer, pointer + reassigned.w * reassigned.h * 4) : self.HEAPU8C.subarray(pointer, pointer + reassigned.w * reassigned.h * 4)
331 | promises.push(createImageBitmap(new ImageData(data, reassigned.w, reassigned.h)))
332 | images.push(reassigned)
333 | }
334 | // use callback to not rely on async/await
335 | Promise.all(promises).then(bitmaps => {
336 | for (let i = 0; i < images.length; i++) {
337 | images[i].image = bitmaps[i]
338 | }
339 | if (debug) times.JSBitmapGenerationTime = Date.now() - times.JSRenderTime
340 | paintImages({ images, buffers: bitmaps, times })
341 | })
342 | } else {
343 | for (let image = renderResult, i = 0; i < jassubObj.count; image = image.next, ++i) {
344 | const reassigned = { w: image.w, h: image.h, x: image.x, y: image.y, image: image.image }
345 | if (!offCanvasCtx) {
346 | const buf = self.wasmMemory.buffer.slice(image.image, image.image + image.w * image.h * 4)
347 | buffers.push(buf)
348 | reassigned.image = buf
349 | }
350 | images.push(reassigned)
351 | }
352 | paintImages({ images, buffers, times })
353 | }
354 | } else {
355 | postMessage({
356 | target: 'unbusy'
357 | })
358 | }
359 | }
360 |
361 | self.demand = ({ time }) => {
362 | lastCurrentTime = time
363 | render(time)
364 | }
365 |
366 | const renderLoop = force => {
367 | rafId = 0
368 | render(getCurrentTime(), force)
369 | if (!_isPaused) {
370 | rafId = requestAnimationFrame(renderLoop)
371 | }
372 | }
373 |
374 | const paintImages = ({ times, images, buffers }) => {
375 | const resultObject = {
376 | target: 'render',
377 | asyncRender,
378 | images,
379 | times,
380 | width: self.width,
381 | height: self.height,
382 | colorSpace: subtitleColorSpace
383 | }
384 |
385 | if (offscreenRender) {
386 | if (offCanvas.height !== self.height || offCanvas.width !== self.width) {
387 | offCanvas.width = self.width
388 | offCanvas.height = self.height
389 | }
390 | offCanvasCtx.clearRect(0, 0, self.width, self.height)
391 | for (const image of images) {
392 | if (image.image) {
393 | if (asyncRender) {
394 | offCanvasCtx.drawImage(image.image, image.x, image.y)
395 | image.image.close()
396 | } else {
397 | bufferCanvas.width = image.w
398 | bufferCanvas.height = image.h
399 | bufferCtx.putImageData(new ImageData(self.HEAPU8C.subarray(image.image, image.image + image.w * image.h * 4), image.w, image.h), 0, 0)
400 | offCanvasCtx.drawImage(bufferCanvas, image.x, image.y)
401 | }
402 | }
403 | }
404 | if (offscreenRender === 'hybrid') {
405 | if (!images.length) return postMessage(resultObject)
406 | if (debug) times.bitmaps = images.length
407 | try {
408 | const image = offCanvas.transferToImageBitmap()
409 | resultObject.images = [{ image, x: 0, y: 0 }]
410 | resultObject.asyncRender = true
411 | postMessage(resultObject, [image])
412 | } catch (e) {
413 | postMessage({ target: 'unbusy' })
414 | }
415 | } else {
416 | if (debug) {
417 | times.JSRenderTime = Date.now() - times.JSRenderTime - (times.JSBitmapGenerationTime || 0)
418 | let total = 0
419 | for (const key in times) total += times[key]
420 | console.log('Bitmaps: ' + images.length + ' Total: ' + (total | 0) + 'ms', times)
421 | }
422 | postMessage({ target: 'unbusy' })
423 | }
424 | } else {
425 | postMessage(resultObject, buffers)
426 | }
427 | }
428 |
429 | /**
430 | * Parse the content of an .ass file.
431 | * @param {!string} content the content of the file
432 | */
433 | const parseAss = content => {
434 | let m, format, lastPart, parts, key, value, tmp, i, j, body
435 | const sections = []
436 | const lines = content.split(/[\r\n]+/g)
437 | for (i = 0; i < lines.length; i++) {
438 | m = lines[i].match(/^\[(.*)\]$/)
439 | if (m) {
440 | format = null
441 | sections.push({
442 | name: m[1],
443 | body: []
444 | })
445 | } else {
446 | if (/^\s*$/.test(lines[i])) continue
447 | if (sections.length === 0) continue
448 | body = sections[sections.length - 1].body
449 | if (lines[i][0] === ';') {
450 | body.push({
451 | type: 'comment',
452 | value: lines[i].substring(1)
453 | })
454 | } else {
455 | parts = lines[i].split(':')
456 | key = parts[0]
457 | value = parts.slice(1).join(':').trim()
458 | if (format || key === 'Format') {
459 | value = value.split(',')
460 | if (format && value.length > format.length) {
461 | lastPart = value.slice(format.length - 1).join(',')
462 | value = value.slice(0, format.length - 1)
463 | value.push(lastPart)
464 | }
465 | value = value.map(s => {
466 | return s.trim()
467 | })
468 | if (format) {
469 | tmp = {}
470 | for (j = 0; j < value.length; j++) {
471 | tmp[format[j]] = value[j]
472 | }
473 | value = tmp
474 | }
475 | }
476 | if (key === 'Format') {
477 | format = value
478 | }
479 | body.push({
480 | key,
481 | value
482 | })
483 | }
484 | }
485 | }
486 |
487 | return sections
488 | }
489 |
490 | const blurRegex = /\\blur(?:[0-9]+\.)?[0-9]+/gm
491 |
492 | const dropBlur = subContent => {
493 | return subContent.replace(blurRegex, '')
494 | }
495 |
496 | const requestAnimationFrame = (() => {
497 | // similar to Browser.requestAnimationFrame
498 | let nextRAF = 0
499 | return func => {
500 | // try to keep target fps (30fps) between calls to here
501 | const now = Date.now()
502 | if (nextRAF === 0) {
503 | nextRAF = now + 1000 / targetFps
504 | } else {
505 | while (now + 2 >= nextRAF) { // fudge a little, to avoid timer jitter causing us to do lots of delay:0
506 | nextRAF += 1000 / targetFps
507 | }
508 | }
509 | const delay = Math.max(nextRAF - now, 0)
510 | return setTimeout(func, delay)
511 | // return setTimeout(func, 1);
512 | }
513 | })()
514 |
515 | const _applyKeys = (input, output) => {
516 | for (const v of Object.keys(input)) {
517 | output[v] = input[v]
518 | }
519 | }
520 | let offCanvas
521 | let offCanvasCtx
522 | let offscreenRender
523 | let bufferCanvas
524 | let bufferCtx
525 | let jassubObj
526 | let subtitleColorSpace
527 | let dropAllBlur
528 | let _malloc
529 | let hasBitmapBug
530 |
531 | self.init = data => {
532 | hasBitmapBug = data.hasBitmapBug
533 | try {
534 | const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00))
535 | if (!(module instanceof WebAssembly.Module) || !(new WebAssembly.Instance(module) instanceof WebAssembly.Instance)) throw new Error('WASM not supported')
536 | } catch (e) {
537 | console.warn(e)
538 | // load WASM2JS code if WASM is unsupported
539 | // eslint-disable-next-line no-eval
540 | eval(read_(data.legacyWasmUrl))
541 | }
542 | // hack, we want custom WASM URLs
543 | // @ts-ignore
544 | if (WebAssembly.instantiateStreaming) {
545 | const _fetch = self.fetch
546 | self.fetch = _ => _fetch(data.wasmUrl)
547 | }
548 | WASM({ wasm: !WebAssembly.instantiateStreaming && read_(data.wasmUrl, true) }).then((/** @type {EmscriptenModule} */Module) => {
549 | _malloc = Module._malloc
550 | self.width = data.width
551 | self.height = data.height
552 | blendMode = data.blendMode
553 | asyncRender = data.asyncRender
554 | // Force fallback if engine does not support 'lossy' mode.
555 | // We only use createImageBitmap in the worker and historic WebKit versions supported
556 | // the API in the normal but not the worker scope, so we can't check this earlier.
557 | if (asyncRender && typeof createImageBitmap === 'undefined') {
558 | asyncRender = false
559 | console.error("'createImageBitmap' needed for 'asyncRender' unsupported!")
560 | }
561 |
562 | availableFonts = data.availableFonts
563 | debug = data.debug
564 | targetFps = data.targetFps || targetFps
565 | useLocalFonts = data.useLocalFonts
566 | dropAllBlur = data.dropAllBlur
567 |
568 | const fallbackFont = data.fallbackFont.toLowerCase()
569 | jassubObj = new Module.JASSUB(self.width, self.height, fallbackFont || null, debug)
570 |
571 | if (fallbackFont) findAvailableFonts(fallbackFont)
572 |
573 | let subContent = data.subContent
574 | if (!subContent) subContent = read_(data.subUrl)
575 |
576 | processAvailableFonts(subContent)
577 | if (dropAllBlur) subContent = dropBlur(subContent)
578 |
579 | for (const font of data.fonts || []) asyncWrite(font)
580 |
581 | jassubObj.createTrackMem(subContent)
582 |
583 | subtitleColorSpace = libassYCbCrMap[jassubObj.trackColorSpace]
584 |
585 | jassubObj.setDropAnimations(data.dropAllAnimations || 0)
586 |
587 | if (data.libassMemoryLimit > 0 || data.libassGlyphLimit > 0) {
588 | jassubObj.setMemoryLimits(data.libassGlyphLimit || 0, data.libassMemoryLimit || 0)
589 | }
590 |
591 | postMessage({ target: 'ready' })
592 | postMessage({ target: 'verifyColorSpace', subtitleColorSpace })
593 | })
594 | }
595 |
596 | self.offscreenCanvas = ({ transferable }) => {
597 | offCanvas = transferable[0]
598 | offCanvasCtx = offCanvas.getContext('2d')
599 | if (!asyncRender) {
600 | bufferCanvas = new OffscreenCanvas(self.height, self.width)
601 | bufferCtx = bufferCanvas.getContext('2d', { desynchronized: true })
602 | }
603 | offscreenRender = true
604 | }
605 |
606 | self.detachOffscreen = () => {
607 | offCanvas = new OffscreenCanvas(self.height, self.width)
608 | offCanvasCtx = offCanvas.getContext('2d', { desynchronized: true })
609 | offscreenRender = 'hybrid'
610 | }
611 |
612 | self.canvas = ({ width, height, videoWidth, videoHeight, force }) => {
613 | if (width == null) throw new Error('Invalid canvas size specified')
614 | self.width = width
615 | self.height = height
616 | if (jassubObj) jassubObj.resizeCanvas(width, height, videoWidth, videoHeight)
617 | if (force) render(lastCurrentTime, true)
618 | }
619 |
620 | self.video = ({ currentTime, isPaused, rate }) => {
621 | if (currentTime != null) setCurrentTime(currentTime)
622 | if (isPaused != null) setIsPaused(isPaused)
623 | rate = rate || rate
624 | }
625 |
626 | self.destroy = () => {
627 | jassubObj.quitLibrary()
628 | }
629 |
630 | self.createEvent = ({ event }) => {
631 | _applyKeys(event, jassubObj.getEvent(jassubObj.allocEvent()))
632 | }
633 |
634 | self.getEvents = () => {
635 | const events = []
636 | for (let i = 0; i < jassubObj.getEventCount(); i++) {
637 | const { Start, Duration, ReadOrder, Layer, Style, MarginL, MarginR, MarginV, Name, Text, Effect } = jassubObj.getEvent(i)
638 | events.push({ Start, Duration, ReadOrder, Layer, Style, MarginL, MarginR, MarginV, Name, Text, Effect })
639 | }
640 | postMessage({
641 | target: 'getEvents',
642 | events
643 | })
644 | }
645 |
646 | self.setEvent = ({ event, index }) => {
647 | _applyKeys(event, jassubObj.getEvent(index))
648 | }
649 |
650 | self.removeEvent = ({ index }) => {
651 | jassubObj.removeEvent(index)
652 | }
653 |
654 | self.createStyle = ({ style }) => {
655 | const alloc = jassubObj.getStyle(jassubObj.allocStyle())
656 | _applyKeys(style, alloc)
657 | return alloc
658 | }
659 |
660 | self.getStyles = () => {
661 | const styles = []
662 | for (let i = 0; i < jassubObj.getStyleCount(); i++) {
663 | // eslint-disable-next-line camelcase
664 | const { Name, FontName, FontSize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding, treat_fontname_as_pattern, Blur, Justify } = jassubObj.getStyle(i)
665 | // eslint-disable-next-line camelcase
666 | styles.push({ Name, FontName, FontSize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding, treat_fontname_as_pattern, Blur, Justify })
667 | }
668 | postMessage({
669 | target: 'getStyles',
670 | time: Date.now(),
671 | styles
672 | })
673 | }
674 |
675 | self.setStyle = ({ style, index }) => {
676 | _applyKeys(style, jassubObj.getStyle(index))
677 | }
678 |
679 | self.removeStyle = ({ index }) => {
680 | jassubObj.removeStyle(index)
681 | }
682 |
683 | self.styleOverride = (data) => {
684 | jassubObj.styleOverride(self.createStyle(data))
685 | }
686 |
687 | self.disableStyleOverride = () => {
688 | jassubObj.disableStyleOverride()
689 | }
690 |
691 | self.defaultFont = ({ font }) => {
692 | jassubObj.setDefaultFont(font)
693 | }
694 |
695 | onmessage = ({ data }) => {
696 | if (self[data.target]) {
697 | self[data.target](data)
698 | } else {
699 | throw new Error('Unknown event target ' + data.target)
700 | }
701 | }
702 |
--------------------------------------------------------------------------------
/vite.build.js:
--------------------------------------------------------------------------------
1 | // vite.config.js
2 | import { resolve, dirname } from 'path'
3 | import { build } from 'vite'
4 | import { viteStaticCopy } from 'vite-plugin-static-copy'
5 | import { fileURLToPath } from 'url'
6 | import { appendFile } from 'fs/promises'
7 |
8 | const __dirname = dirname(fileURLToPath(import.meta.url))
9 |
10 | await build({
11 | configFile: false,
12 | build: {
13 | target: 'esnext',
14 | emptyOutDir: false,
15 | minify: 'esbuild',
16 | lib: {
17 | entry: resolve(__dirname, 'src/jassub.js'),
18 | name: 'JASSUB',
19 | fileName: (format) => `jassub.${format}.js`
20 | }
21 | }
22 | })
23 |
24 | await build({
25 | configFile: false,
26 | plugins: [
27 | viteStaticCopy({
28 | targets: [
29 | {
30 | src: 'dist/js/jassub-worker.wasm',
31 | dest: './'
32 | },
33 | {
34 | src: 'dist/js/jassub-worker-modern.wasm',
35 | dest: './'
36 | }
37 | ]
38 | })
39 | ],
40 | resolve: {
41 | alias: {
42 | wasm: 'dist/js/jassub-worker-modern.js'
43 | }
44 | },
45 | build: {
46 | target: 'esnext',
47 | outDir: './dist',
48 | minify: 'esbuild',
49 | lib: {
50 | fileName: () => 'jassub-worker.js',
51 | entry: 'src/worker.js',
52 | formats: ['cjs']
53 | },
54 | emptyOutDir: false
55 | }
56 | })
57 |
58 | await build({
59 | configFile: false,
60 | build: {
61 | terserOptions: {
62 | mangle: false,
63 | compress: false,
64 | format: {
65 | comments: false
66 | }
67 | },
68 | target: 'esnext',
69 | outDir: './dist',
70 | minify: 'terser',
71 | rollupOptions: {
72 | treeshake: false,
73 | output: {
74 | exports: 'none',
75 | entryFileNames: '[name].js'
76 | },
77 | input: {
78 | 'jassub-worker.wasm': resolve(__dirname, 'dist/js/jassub-worker.wasm.js')
79 | }
80 | },
81 | emptyOutDir: false
82 | }
83 | })
84 |
85 | await appendFile(resolve(__dirname, 'dist/jassub-worker.wasm.js'), 'self.WebAssembly=WebAssembly')
86 |
--------------------------------------------------------------------------------