Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b13173112d | ||
|
|
7293203dc2 | ||
|
|
5f75c60eb0 | ||
|
|
8ec552464c | ||
|
|
90a16c6350 | ||
|
|
dbe2dddc8b | ||
|
|
4cde2a0b5e | ||
|
|
9dc9e3e53c | ||
|
|
36abcf4b66 | ||
|
|
8ef48c7c95 | ||
|
|
aa4e9eb018 | ||
|
|
7b3190956d | ||
|
|
958da9931f | ||
|
|
85542b38a4 | ||
|
|
b278de84c8 | ||
|
|
e761cd6444 | ||
|
|
7bf9101b21 | ||
|
|
c97fba6ba8 | ||
|
|
6d2fe37eb2 | ||
|
|
36f6600d6c | ||
|
|
903e0dd0a7 | ||
|
|
d74f6e7b87 | ||
|
|
d7e97098f2 | ||
|
|
b71ec3c608 | ||
|
|
02a70f667c | ||
|
|
66969efa30 | ||
|
|
19e24deed9 | ||
|
|
063ab543d9 |
@@ -1,7 +0,0 @@
|
||||
node_modules/
|
||||
|
||||
libjass.js
|
||||
libjass.min.js
|
||||
*.map
|
||||
|
||||
*.log
|
||||
@@ -1,3 +0,0 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
@@ -1,31 +0,0 @@
|
||||
1. Install node.js from http://nodejs.org/ or via your package manager
|
||||
|
||||
1. Change to the directory where you cloned this repository.
|
||||
|
||||
1. Run the following command
|
||||
|
||||
```shell
|
||||
npm install
|
||||
```
|
||||
|
||||
This will install the dependencies - [Gulp](http://gulpjs.com/), [Mocha](http://visionmedia.github.io/mocha/), [TypeScript](http://www.typescriptlang.org/), [UglifyJS2](https://github.com/mishoo/UglifyJS2) and [Vinyl](https://github.com/wearefractal/vinyl). It will then run gulp to build libjass.js and minify it into libjass.min.js
|
||||
|
||||
1. Set the URLs of the video and the ASS file in index.xhtml
|
||||
|
||||
```html
|
||||
<video id="video" src="<URL OF VIDEO HERE>" controls="">
|
||||
<track src="<URL OF SCRIPT HERE>" kind="metadata" data-format="ass" />
|
||||
</video>
|
||||
```
|
||||
|
||||
1. Set the URLs of any fonts you want to make available in fonts.css
|
||||
|
||||
```css
|
||||
@font-face {
|
||||
font-family: "<NAME OF FONT HERE>";
|
||||
src: url("<URL OF FONT HERE>");
|
||||
}
|
||||
```
|
||||
for each font. The name of the font is what it's called in the ASS file.
|
||||
|
||||
1. Start your web server and navigate to index.xhtml in a browser.
|
||||
@@ -1,202 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,132 +0,0 @@
|
||||
[](https://travis-ci.org/Arnavion/libjass)
|
||||
|
||||
libjass is a JavaScript library written in TypeScript to render ASS subs on HTML5 video in the browser.
|
||||
|
||||
|
||||
### What's special about libjass?
|
||||
|
||||
* libjass requires no tweaks to the ASS file from the original video.
|
||||
|
||||
* It's easy to deploy. There is no server-side support required. A static hosting is all that's needed.
|
||||
|
||||
* libjass uses the browser's native CSS engine by converting the components of each line in the ASS script into a series of styled <div> and <span> elements. This allows all the layout and rendering to be handled by the browser instead of requiring complex and costly drawing and animation code. For example, libjass uses CSS3 animations to simulate tags such as \fad. While a canvas-drawing library would have to re-draw such a subtitle on the canvas for every frame of the video, libjass only renders it once and lets the browser render the fade effect.
|
||||
|
||||
As a result, libjass is able to render subtitles with very low CPU usage. The downside to libjass's aproach is that it is hard (and potentially impossible) to map all effects possible in ASS (using \t, ASS draw) etc. into DOM elements. As of now, the subset of tags supported by libjass has no such problems.
|
||||
|
||||
|
||||
### What are all these files?
|
||||
|
||||
* The .ts files are the source of libjass. They are TypeScript files and must be compiled into JavaScript for the browser using the TypeScript compiler.
|
||||
|
||||
* The rest of the files - index.xhtml, index.js, index.css and fonts.css - are a sample implementation of how to use libjass on a web page. They demonstrate the API to call, how to place <div> elements to render the subs, etc.
|
||||
|
||||
|
||||
### I want to use libjass for my website. What do I need to do?
|
||||
|
||||
1. You need to build libjass.js using the instructions in BUILD.md
|
||||
1. You need to load libjass.js and libjass.css on the page with your video.
|
||||
1. You need to call the libjass API.
|
||||
|
||||
Only libjass.js and libjass.css are needed to use libjass on your website. The other files are only used during the build process and you don't need to deploy them to your website.
|
||||
|
||||
|
||||
### Where's the API documentation? What API do I need to call to use libjass?
|
||||
|
||||
The API documentation is linked in the Links section below. Here's an overview:
|
||||
|
||||
* The method [ASS.fromString()](http://arnavion.github.io/libjass/api.xhtml#libjass.ASS.fromString) takes in the raw ASS string and returns an [ASS](http://arnavion.github.io/libjass/api.xhtml#libjass.ASS) object representing the script properties, the line styles and dialogue lines in it. The example index.js uses XHR to get this data using the URL specified in a track tag.
|
||||
|
||||
* index.js initializes a default renderer that libjass ships with, the [DefaultRenderer](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.DefaultRenderer). This renderer uses information from the ASS object to build up a series of div elements around the video tag. There is a wrapper (.libjass-subs) containing div's corresponding to the layers in the ASS script, and each layer has div's corresponding to the 9 alignment directions. libjass.css contains styles for these div's to render them at the correct location.
|
||||
|
||||
* The renderer uses [window.requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame) as a source of timer ticks. In each tick, it determines the set of dialogues to be shown at the current video time, renders each of them as a div, and appendChild's the div into the appropriate layer+alignment div.
|
||||
|
||||
* The renderer handles resizing the subtitles when the user clicks the browser's native fullscreen-video button. index.js also contains code to change the size of the subtitles based on user input.
|
||||
|
||||
* Lastly, the renderer contains an implementation of preloading fonts before playing the video. It uses a map of font names to URLs - index.js creates this map from the @font-face rules in fonts.css.
|
||||
|
||||
|
||||
### Can I use libjass in node?
|
||||
|
||||
libjass's parser works in node.
|
||||
|
||||
```javascript
|
||||
> var libjass = require("./libjass.js")
|
||||
undefined
|
||||
> var parts = libjass.parser.parse("{\\an8}Are {\\i1}you{\\i0} the one who stole the clock?!", "dialogueParts")
|
||||
undefined
|
||||
> parts.join(" ")
|
||||
'Alignment { value: 8 } Text { value: Are } Italic { value: true } Text { value: you } Italic { value: false } Text { value: the one who stole the clock?! }'
|
||||
> parts.length
|
||||
6
|
||||
> parts[0].toString()
|
||||
'Alignment { value: 8 }'
|
||||
> parts[0] instanceof libjass.parts.Alignment
|
||||
true
|
||||
> parts[0].value
|
||||
8
|
||||
```
|
||||
|
||||
[libjass.parser.parse](http://arnavion.github.io/libjass/api.xhtml#libjass.parser.parse) parses the first parameter using the second parameter as the rule name. The rule names are derived from the methods on the [ParserRun class](http://arnavion.github.io/libjass/api.xhtml#libjass.parser.ParserRun). For example, [dialogueParts](http://arnavion.github.io/libjass/api.xhtml#libjass.parser.ParserRun.parse_dialogueParts) can be used to get an array of [libjass.parts](http://arnavion.github.io/libjass/api.xhtml#libjass.parts) objects that represent the parts of an ASS dialogue line.
|
||||
|
||||
|
||||
### Can I contribute?
|
||||
|
||||
Yes! Feature requests, suggestions, bug reports and pull requests are welcome! I'm especially looking for details and edge-cases of the ASS syntax that libjass doesn't support.
|
||||
|
||||
You can also join the IRC channel below and ask any questions.
|
||||
|
||||
|
||||
### Links
|
||||
|
||||
* [GitHub](https://github.com/Arnavion/libjass/)
|
||||
* IRC channel - #libjass on irc.rizon.net
|
||||
* [API documentation](http://arnavion.github.io/libjass/api.xhtml)
|
||||
* [Aegisub's documentation on ASS](http://docs.aegisub.org/3.0/ASS_Tags/)
|
||||
|
||||
|
||||
### Supported features
|
||||
|
||||
* Styles: Italic, Bold, Underline, StrikeOut, FontName, FontSize, ScaleX, ScaleY, Spacing, PrimaryColor, OutlineColor, BackColor, Outline, Shadow, Alignment, MarginL, MarginR, MarginV
|
||||
* Tags: \i, \b, \u, \s, \bord, \xbord, \ybord, \shad, \xshad, \yshad, \blur, \fn, \fs, \fscx, \fscy, \fsp, \frx, \fry, \frz, \fr, \fax, \fay, \c, \1c, \3c, \4c, \alpha, \1a, \3a, \4a, \an, \a, \r, \pos, \move, \fad, \fade, \p
|
||||
* Custom fonts, using CSS web fonts.
|
||||
|
||||
|
||||
### Known issues
|
||||
|
||||
* Unsupported tags: \be, \fe, \2c, \2a, \k, \K, \kf, \ko, \q, \org, \t, \clip, \iclip
|
||||
* \an4, \an5, \an6 aren't positioned correctly.
|
||||
* Lines scaled using {\fscx} and {\fscy} have their text clip into itself in some situations, and aren't always positioned correctly.
|
||||
* All lines are rendered as wrapping style 1 (end-of-line wrapping).
|
||||
* Lines with multiple rotations aren't rotated the same as VSFilter or libass.
|
||||
* Subtitles aren't styled in IE11 because [it doesn't support SVG filter effects on HTML](http://caniuse.com/svg-html).
|
||||
|
||||
|
||||
### Planned improvements
|
||||
|
||||
* Document browser compatibility. Currently libjass is tested on IE11 (Windows 7), Firefox Nightly and Google Chrome (Dev channel).
|
||||
* Write API documentation. Add more explanatory comments to the code.
|
||||
* Write more parser tests. Also figure out a way to test layout.
|
||||
* Evaluate (document, benchmark) the benefits and drawbacks of DOM+CSS-based drawing over canvas.
|
||||
|
||||
|
||||
### License
|
||||
|
||||
```
|
||||
libjass
|
||||
|
||||
https://github.com/Arnavion/libjass
|
||||
|
||||
Copyright 2013 Arnav Singh
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
```
|
||||
@@ -0,0 +1,204 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Creates a video of the given color, dimensions and duration, and prepares the given video element to play it.
|
||||
*/
|
||||
function makeDummyVideo(video, width, height, color, duration) {
|
||||
return new libjass.Promise(function (resolve, reject) {
|
||||
video.width = width;
|
||||
video.height = height;
|
||||
|
||||
var canvas = document.createElement("canvas");
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
var context = canvas.getContext("2d");
|
||||
context.fillStyle = color;
|
||||
context.fillRect(0, 0, width, height);
|
||||
|
||||
var stream = canvas.captureStream(0);
|
||||
var recorder = new MediaRecorder(stream);
|
||||
|
||||
recorder.start(1); // Get as many events as possible to have a chance at getting the smallest possible chunk.
|
||||
|
||||
var blob = null;
|
||||
|
||||
recorder.addEventListener("dataavailable", function (event) {
|
||||
if (recorder.state === "inactive") {
|
||||
// Being called after recorder.stop(). Do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.data.size === 0) {
|
||||
console.warn("No new data.");
|
||||
return;
|
||||
}
|
||||
|
||||
recorder.pause(); // Don't get flooded with new blobs while parsing the current blob.
|
||||
|
||||
if (blob === null) {
|
||||
blob = event.data;
|
||||
if (!MediaSource.isTypeSupported(blob.type)) {
|
||||
/* MediaRecorder may record a format that MediaSource doesn't support. As of Nightly 46, this is true, since MediaRecorder
|
||||
* records webm which MediaSource doesn't play unless media.mediasource.webm.enabled is true in about:config
|
||||
*/
|
||||
|
||||
recorder.stop();
|
||||
reject(new Error("MediaRecorder is recording video in " + blob.type + " but MediaSource doesn't support it. Make sure media.mediasource.webm.enabled is on in about:config"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
blob = new Blob([blob, event.data], { type: blob.type });
|
||||
}
|
||||
|
||||
// Data is available but may not contain any frames. Test for that.
|
||||
libjass.Promise.all([newMediaSourceAndBuffer(video, blob.type), blobToArrayBuffer(blob)]).then(function (results) {
|
||||
var mediaSource = results[0][0];
|
||||
var sourceBuffer = results[0][1];
|
||||
var buffer = results[1];
|
||||
|
||||
return appendBuffer(sourceBuffer, buffer).then(function () {
|
||||
console.log("Got enough data for " + getEndTime(sourceBuffer) + " seconds.");
|
||||
|
||||
return [mediaSource, sourceBuffer, buffer];
|
||||
});
|
||||
}).then(function (result) {
|
||||
resolve(result);
|
||||
|
||||
recorder.stop();
|
||||
}, function (reason) {
|
||||
console.warn(reason);
|
||||
console.warn("Waiting for more data...");
|
||||
|
||||
recorder.resume();
|
||||
});
|
||||
});
|
||||
}).then(function (results) {
|
||||
var mediaSource = results[0];
|
||||
var sourceBuffer = results[1];
|
||||
var buffer = results[2];
|
||||
|
||||
return appendBufferUntil(sourceBuffer, buffer, duration).then(function () {
|
||||
return mediaSource.endOfStream();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the given `video` to use a new MediaSource, and appends a new SourceBuffer of the given `type`.
|
||||
*/
|
||||
function newMediaSourceAndBuffer(video, type) {
|
||||
return new libjass.Promise(function (resolve, reject) {
|
||||
var mediaSource = new MediaSource();
|
||||
|
||||
function onSourceOpen() {
|
||||
mediaSource.removeEventListener("sourceopen", onSourceOpen, false);
|
||||
|
||||
try {
|
||||
var sourceBuffer = mediaSource.addSourceBuffer(type);
|
||||
|
||||
resolve([mediaSource, sourceBuffer]);
|
||||
}
|
||||
catch (ex) {
|
||||
reject(ex);
|
||||
}
|
||||
}
|
||||
|
||||
mediaSource.addEventListener("sourceopen", onSourceOpen, false);
|
||||
|
||||
video.src = URL.createObjectURL(mediaSource);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Blob to an ArrayBuffer
|
||||
*/
|
||||
function blobToArrayBuffer(blob) {
|
||||
return new libjass.Promise(function (resolve, reject) {
|
||||
var fileReader = new FileReader();
|
||||
|
||||
fileReader.addEventListener("load", function () {
|
||||
resolve(fileReader.result);
|
||||
}, false);
|
||||
fileReader.addEventListener("error", function (event) {
|
||||
reject(event);
|
||||
});
|
||||
|
||||
fileReader.readAsArrayBuffer(blob);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the given video data `buffer` to the given `sourceBuffer`.
|
||||
*/
|
||||
function appendBuffer(sourceBuffer, buffer) {
|
||||
return new libjass.Promise(function (resolve, reject) {
|
||||
var currentEndTime = getEndTime(sourceBuffer);
|
||||
|
||||
function onUpdateEnd() {
|
||||
sourceBuffer.removeEventListener("updateend", onUpdateEnd, false);
|
||||
|
||||
if (sourceBuffer.buffered.length === 0) {
|
||||
reject(new Error("buffer of length " + buffer.byteLength + " could not be appended to sourceBuffer. It's probably too small and doesn't contain any frames."));
|
||||
return;
|
||||
}
|
||||
|
||||
var newEndTime = getEndTime(sourceBuffer);
|
||||
if (newEndTime === currentEndTime) {
|
||||
reject(new Error("sourceBuffer is not increasing in size. Perhaps buffer is too small?"));
|
||||
return;
|
||||
}
|
||||
|
||||
resolve();
|
||||
}
|
||||
|
||||
sourceBuffer.addEventListener("updateend", onUpdateEnd, false);
|
||||
|
||||
sourceBuffer.timestampOffset = currentEndTime;
|
||||
sourceBuffer.appendBuffer(buffer);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Repeatedly appends the given video data `buffer` to the given `sourceBuffer` until it is of `duration` length.
|
||||
*/
|
||||
function appendBufferUntil(sourceBuffer, buffer, duration) {
|
||||
var currentEndTime = getEndTime(sourceBuffer);
|
||||
if (currentEndTime < duration) {
|
||||
return appendBuffer(sourceBuffer, buffer).then(function () {
|
||||
return appendBufferUntil(sourceBuffer, buffer, duration);
|
||||
});
|
||||
}
|
||||
else {
|
||||
return libjass.Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the end time of a SourceBuffer.
|
||||
*/
|
||||
function getEndTime(sourceBuffer) {
|
||||
return (sourceBuffer.buffered.length === 0) ? 0 : sourceBuffer.buffered.end(0);
|
||||
}
|
||||
@@ -20,12 +20,27 @@
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.libjass-subs {
|
||||
z-index: 2147483647;
|
||||
}
|
||||
|
||||
.settings-form > fieldset {
|
||||
#settings-form > fieldset {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#console > .warning {
|
||||
color: orange;
|
||||
}
|
||||
|
||||
#console > .error {
|
||||
color: red;
|
||||
}
|
||||
|
||||
ul.choices-list {
|
||||
list-style-type: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
+566
@@ -0,0 +1,566 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/* This page demonstrates how to use libjass. It allows you to choose a video that will be played in a <video> element, and an ASS script.
|
||||
*
|
||||
*
|
||||
* Below you will find the basics of using libjass - the ASS.fromUrl() and ASS.fromString() functions, and the DefaultRenderer class - and some advanced concepts:
|
||||
* - handling resizable video
|
||||
* - autoplaying video
|
||||
* - toggling subs on user input
|
||||
*
|
||||
* The advanced concepts are optional and marked with "(Advanced)"
|
||||
*
|
||||
*
|
||||
* Some more advanced uses not demonstrated here include:
|
||||
* - renderer settings, such as using custom fonts or controlling the pre-render time
|
||||
* - using WebRenderer instead of DefaultRenderer for control over the placement of the subs <div>, enabling fullscreen video, etc.
|
||||
* - using ASS.fromString() instead of ASS.fromUrl() when you have the ASS string already, such as in an online subs editor
|
||||
* - using libjass.parser.StreamParser and its minimalAss promise directly, such as for dynamically generated scripts
|
||||
* - working with SRT subs by specifying libjass.Format.SRT to the ASS.from*() functions
|
||||
*
|
||||
*
|
||||
* API documentation is available at http://arnavion.github.io/libjass/api.xhtml
|
||||
*/
|
||||
|
||||
|
||||
"use strict";
|
||||
|
||||
|
||||
/* This section sets up the default look of the page - a bunch of input controls for the video and ASS script, and the
|
||||
* "Go" button that will create a <video> element and start rendering subs over it. If you already have URLs for a video and script on your website,
|
||||
* you would just generate the <video> element and start using libjass directly.
|
||||
*/
|
||||
addEventListener("DOMContentLoaded", function () {
|
||||
var htmlConsole = document.querySelector("#console");
|
||||
htmlConsoleLog = htmlConsoleLog(htmlConsole);
|
||||
htmlConsoleWarn = htmlConsoleWarn(htmlConsole);
|
||||
htmlConsoleError = htmlConsoleError(htmlConsole);
|
||||
var originalConsoleLog = console.log.bind(console);
|
||||
var originalConsoleWarn = console.warn.bind(console);
|
||||
var originalConsoleError = console.log.bind(console);
|
||||
console.log = function () {
|
||||
htmlConsoleLog([].slice.call(arguments, 0));
|
||||
originalConsoleLog.apply(null, arguments);
|
||||
};
|
||||
console.warn = function () {
|
||||
htmlConsoleWarn([].slice.call(arguments, 0));
|
||||
originalConsoleWarn.apply(null, arguments);
|
||||
};
|
||||
console.error = function () {
|
||||
htmlConsoleError([].slice.call(arguments, 0));
|
||||
originalConsoleError.apply(null, arguments);
|
||||
};
|
||||
|
||||
var content = document.querySelector("#content");
|
||||
|
||||
var videoChoiceLocalFileInput = document.querySelector("#video-choice-local-file");
|
||||
var videoInputLocalFile = document.querySelector("#video-input-local-file");
|
||||
|
||||
var videoChoiceUrlInput = document.querySelector("#video-choice-url");
|
||||
var videoInputUrl = document.querySelector("#video-input-url");
|
||||
|
||||
var videoChoiceSampleInput = document.querySelector("#video-choice-sample");
|
||||
|
||||
var videoChoiceDummyInput = document.querySelector("#video-choice-dummy");
|
||||
var videoInputDummyResolution = document.querySelector("#video-input-dummy-resolution");
|
||||
var videoInputDummyWidth = document.querySelector("#video-input-dummy-width");
|
||||
var videoInputDummyHeight = document.querySelector("#video-input-dummy-height");
|
||||
var videoInputDummyColor = document.querySelector("#video-input-dummy-color");
|
||||
var videoInputDummyDuration = document.querySelector("#video-input-dummy-duration");
|
||||
|
||||
var assChoiceLocalFileInput = document.querySelector("#ass-choice-local-file");
|
||||
var assInputLocalFile = document.querySelector("#ass-input-local-file");
|
||||
|
||||
var assChoiceUrlInput = document.querySelector("#ass-choice-url");
|
||||
var assInputUrl = document.querySelector("#ass-input-url");
|
||||
|
||||
var assChoiceTextInput = document.querySelector("#ass-choice-text");
|
||||
var assInputText = document.querySelector("#ass-input-text");
|
||||
|
||||
var enableSvgChoiceInputYes = document.querySelector("#enable-svg-yes");
|
||||
var enableSvgChoiceInputNo = document.querySelector("#enable-svg-no");
|
||||
|
||||
// Local file input requires URL.createObjectURL, so disable those inputs if the function doesn't exist.
|
||||
if (typeof URL === "undefined" || typeof URL.createObjectURL !== "function") {
|
||||
[
|
||||
videoChoiceLocalFileInput,
|
||||
assChoiceLocalFileInput
|
||||
].forEach(function (input) {
|
||||
input.disabled = true;
|
||||
input.parentElement.appendChild(document.createTextNode(" (This browser doesn't support URL.createObjectURL)"));
|
||||
});
|
||||
|
||||
[
|
||||
videoInputLocalFile,
|
||||
assInputLocalFile
|
||||
].forEach(function (input) {
|
||||
input.disabled = true;
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
typeof HTMLCanvasElement.prototype.captureStream !== "function" ||
|
||||
typeof MediaRecorder === "undefined" ||
|
||||
typeof MediaSource === "undefined" ||
|
||||
typeof MediaSource.isTypeSupported !== "function"/* ||
|
||||
!MediaSource.isTypeSupported("video/webm")*/
|
||||
) {
|
||||
[
|
||||
videoChoiceDummyInput,
|
||||
videoInputDummyResolution,
|
||||
videoInputDummyWidth,
|
||||
videoInputDummyHeight,
|
||||
videoInputDummyColor,
|
||||
videoInputDummyDuration
|
||||
].forEach(function (input) {
|
||||
input.disabled = true;
|
||||
});
|
||||
|
||||
videoChoiceDummyInput.parentElement.appendChild(document.createTextNode(
|
||||
" (This browser doesn't support generating dummy video. Consider using Firefox 46 or newer and enabling media.mediasource.webm.enabled in about:config)"
|
||||
));
|
||||
}
|
||||
|
||||
// Update dummy video width and height inputs when the dropdown selection changes
|
||||
function updateDummyWidthAndHeight() {
|
||||
var resolution = videoInputDummyResolution.value.split("x");
|
||||
videoInputDummyWidth.value = resolution[0];
|
||||
videoInputDummyHeight.value = resolution[1];
|
||||
}
|
||||
videoInputDummyResolution.addEventListener("change", updateDummyWidthAndHeight, false);
|
||||
updateDummyWidthAndHeight();
|
||||
|
||||
// Register event handlers to enable the Go button if all inputs are valid.
|
||||
[
|
||||
videoChoiceLocalFileInput,
|
||||
videoChoiceUrlInput,
|
||||
videoChoiceSampleInput,
|
||||
videoChoiceDummyInput,
|
||||
assChoiceLocalFileInput,
|
||||
assChoiceUrlInput,
|
||||
assChoiceTextInput
|
||||
].forEach(function (input) {
|
||||
input.addEventListener("change", updateGoButton, false);
|
||||
});
|
||||
|
||||
[
|
||||
videoInputLocalFile,
|
||||
assInputLocalFile,
|
||||
].forEach(function (input) {
|
||||
input.addEventListener("change", updateGoButton, false);
|
||||
});
|
||||
|
||||
[
|
||||
videoInputUrl,
|
||||
videoInputDummyDuration,
|
||||
assInputUrl,
|
||||
assInputText
|
||||
].forEach(function (input) {
|
||||
input.addEventListener("input", updateGoButton, false);
|
||||
});
|
||||
|
||||
|
||||
// libjass.debugMode and libjass.verboseMode are two properties that can be set to true to have libjass print some debug information.
|
||||
var debugModeCheckbox = document.querySelector("#debug-mode");
|
||||
debugModeCheckbox.addEventListener("change", function () {
|
||||
console.log((debugModeCheckbox.checked ? "Enabling" : "Disabling") + " debug mode.");
|
||||
libjass.configure({ debugMode: debugModeCheckbox.checked });
|
||||
}, false);
|
||||
var verboseModeCheckbox = document.querySelector("#verbose-mode");
|
||||
verboseModeCheckbox.addEventListener("change", function () {
|
||||
console.log((debugModeCheckbox.checked ? "Enabling" : "Disabling") + " verbose mode.");
|
||||
libjass.configure({ verboseMode: verboseModeCheckbox.checked });
|
||||
}, false);
|
||||
|
||||
|
||||
var goButton = document.querySelector("#go-button");
|
||||
|
||||
function updateGoButton() {
|
||||
var videoOk = false;
|
||||
var assOk = false;
|
||||
|
||||
var videoChoice = document.querySelector('input[name="video-choice"]:checked');
|
||||
switch (videoChoice) {
|
||||
case videoChoiceLocalFileInput:
|
||||
videoOk = videoInputLocalFile.files.length === 1;
|
||||
break;
|
||||
case videoChoiceUrlInput:
|
||||
videoOk = document.querySelector("#video-input-url:invalid") === null && videoInputUrl.value.length > 0;
|
||||
break;
|
||||
case videoChoiceSampleInput:
|
||||
videoOk = true;
|
||||
case videoChoiceDummyInput:
|
||||
videoOk =
|
||||
parseInt(videoInputDummyWidth.value) > 0 &&
|
||||
parseInt(videoInputDummyHeight.value) > 0 &&
|
||||
videoInputDummyDuration.value.length > 0 &&
|
||||
parseInt(videoInputDummyDuration.value) > 0;
|
||||
break;
|
||||
}
|
||||
|
||||
var assChoice = document.querySelector('input[name="ass-choice"]:checked');
|
||||
switch (assChoice) {
|
||||
case assChoiceLocalFileInput:
|
||||
assOk = assInputLocalFile.files.length === 1;
|
||||
break;
|
||||
case assChoiceUrlInput:
|
||||
assOk = document.querySelector("#ass-input-url:invalid") === null && assInputUrl.value.length > 0;
|
||||
break;
|
||||
case assChoiceTextInput:
|
||||
assOk = document.querySelector("#ass-input-choice:invalid") === null && assInputText.value.length > 0;
|
||||
break;
|
||||
}
|
||||
|
||||
goButton.disabled = !videoOk || !assOk;
|
||||
}
|
||||
|
||||
goButton.addEventListener("click", function () {
|
||||
updateGoButton();
|
||||
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
var videoChoice = document.querySelector('input[name="video-choice"]:checked');
|
||||
var assChoice = document.querySelector('input[name="ass-choice"]:checked');
|
||||
var enableSvgChoice = document.querySelector('input[name="enable-svg"]:checked');
|
||||
|
||||
while (content.firstChild) {
|
||||
content.removeChild(content.firstChild);
|
||||
}
|
||||
|
||||
var template = document.querySelector("#template").cloneNode(true);
|
||||
|
||||
[].slice.call(template.querySelectorAll("[data-id]")).forEach(function (element) {
|
||||
element.id = element.dataset.id;
|
||||
});
|
||||
|
||||
[].slice.call(template.children).forEach(function (element) {
|
||||
content.appendChild(element);
|
||||
});
|
||||
|
||||
var videoPromise = null;
|
||||
|
||||
switch (videoChoice) {
|
||||
case videoChoiceLocalFileInput:
|
||||
// Video is a local file. Convert it into a blob URL.
|
||||
videoPromise = prepareVideo(0 /* URL */, URL.createObjectURL(videoInputLocalFile.files[0]));
|
||||
break;
|
||||
case videoChoiceUrlInput:
|
||||
videoPromise = prepareVideo(0 /* URL */, videoInputUrl.value);
|
||||
break;
|
||||
case videoChoiceSampleInput:
|
||||
videoPromise = prepareVideo(1 /* sample */);
|
||||
break;
|
||||
case videoChoiceDummyInput:
|
||||
var width = parseInt(videoInputDummyWidth.value);
|
||||
var height = parseInt(videoInputDummyHeight.value);
|
||||
var color = videoInputDummyColor.value;
|
||||
var duration = parseInt(videoInputDummyDuration.value) * 60;
|
||||
videoPromise = prepareVideo(2 /* dummy */, width, height, color, duration);
|
||||
break;
|
||||
}
|
||||
|
||||
var assPromise = null;
|
||||
|
||||
switch (assChoice) {
|
||||
case assChoiceLocalFileInput:
|
||||
assPromise = libjass.ASS.fromUrl(URL.createObjectURL(assInputLocalFile.files[0]));
|
||||
break;
|
||||
case assChoiceUrlInput:
|
||||
assPromise = libjass.ASS.fromUrl(assInputUrl.value);
|
||||
break;
|
||||
case assChoiceTextInput:
|
||||
assPromise = libjass.ASS.fromString(assInputText.value);
|
||||
break;
|
||||
}
|
||||
|
||||
var enableSvg = null;
|
||||
switch (enableSvgChoice) {
|
||||
case enableSvgChoiceInputYes:
|
||||
enableSvg = true;
|
||||
break;
|
||||
case enableSvgChoiceInputNo:
|
||||
enableSvg = false;
|
||||
break;
|
||||
}
|
||||
|
||||
go(videoPromise, assPromise, enableSvg);
|
||||
});
|
||||
|
||||
updateGoButton();
|
||||
}, false);
|
||||
|
||||
|
||||
function prepareVideo(videoType /*, ...parameters */) {
|
||||
var video = document.querySelector("#video");
|
||||
|
||||
var videoMetadataLoadedPromise = null;
|
||||
|
||||
if (videoType === 0 /* URL */ || videoType === 1 /* sample */) {
|
||||
if (videoType === 0 /* URL */) {
|
||||
var videoUrl = arguments[1];
|
||||
|
||||
/* Set the <video> element's src to the given URL
|
||||
*/
|
||||
video.src = videoUrl;
|
||||
}
|
||||
else {
|
||||
/* Add <source> elements for the two sample videos.
|
||||
*/
|
||||
var webmSource = document.createElement("source");
|
||||
video.appendChild(webmSource);
|
||||
webmSource.type = "video/webm";
|
||||
webmSource.src = "sample.webm";
|
||||
|
||||
var mp4Source = document.createElement("source");
|
||||
video.appendChild(mp4Source);
|
||||
mp4Source.type = "video/mp4";
|
||||
mp4Source.src = "sample.mp4";
|
||||
}
|
||||
|
||||
/* (Advanced)
|
||||
*
|
||||
* This demo lets you resize the video to its original resolution or the subs resolution. If you have control over the video, you
|
||||
* might already know the resolution of the video, and any alternative resolutions you want to provide, so you don't need to do this.
|
||||
*
|
||||
* We'll get the original video resolution from the video metadata. We can see if the video has metadata already by comparing
|
||||
* video.readyState to HTMLMediaElement.HAVE_METADATA. If not already loaded, we can wait for the loadedmetadata event.
|
||||
*
|
||||
* This code creates a promise that will be resolved when the video metadata is available.
|
||||
*/
|
||||
videoMetadataLoadedPromise = new libjass.Promise(function (resolve, reject) {
|
||||
if (video.readyState < HTMLMediaElement.HAVE_METADATA) {
|
||||
// Video metadata isn't available yet. Register an event handler for it.
|
||||
video.addEventListener("loadedmetadata", resolve, false);
|
||||
video.addEventListener("error", function (event) { reject(video.error); }, false);
|
||||
}
|
||||
else {
|
||||
// Video metadata is already available.
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (videoType === 2 /* dummy */) {
|
||||
var width = arguments[1];
|
||||
var height = arguments[2];
|
||||
var color = arguments[3];
|
||||
var duration = arguments[4];
|
||||
|
||||
videoMetadataLoadedPromise = makeDummyVideo(video, width, height, color, duration);
|
||||
}
|
||||
|
||||
return videoMetadataLoadedPromise.then(function () {
|
||||
console.log("Video metadata loaded.");
|
||||
|
||||
// Prepare the "Video resolution" option label
|
||||
document.querySelector("#video-resolution-label-width").appendChild(document.createTextNode(video.videoWidth));
|
||||
document.querySelector("#video-resolution-label-height").appendChild(document.createTextNode(video.videoHeight));
|
||||
}).catch(function (reason) {
|
||||
var errorCode = (reason.code !== undefined) ? [null, "MEDIA_ERR_ABORTED", "MEDIA_ERR_NETWORK", "MEDIA_ERR_DECODE", "MEDIA_ERR_SRC_NOT_SUPPORTED"][reason.code] : "";
|
||||
console.error("Video could not be loaded: %o %o", errorCode, reason);
|
||||
|
||||
throw reason;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/* This is a function that sets up libjass to render the subs.
|
||||
*/
|
||||
function go(videoPromise, assPromise, enableSvg) {
|
||||
var video = document.querySelector("#video");
|
||||
|
||||
|
||||
/* Now we need to fetch the ASS script at the given URL and convert it into a libjass.ASS object. We use the libjass.ASS.fromUrl()
|
||||
* function for this. It fetches the ASS script asynchronously and returns a promise that will be resolved when the script is fully
|
||||
* parsed.
|
||||
*/
|
||||
var assLoadedPromise = assPromise.then(function (ass) {
|
||||
console.log("Script received.");
|
||||
|
||||
// Export the ASS object for debugging
|
||||
window.ass = ass;
|
||||
|
||||
// Prepare the "Script resolution" option label
|
||||
document.querySelector("#script-resolution-label-width").appendChild(document.createTextNode(ass.properties.resolutionX));
|
||||
document.querySelector("#script-resolution-label-height").appendChild(document.createTextNode(ass.properties.resolutionY));
|
||||
|
||||
return ass;
|
||||
}).catch(function (reason) {
|
||||
console.error("ASS could not be loaded: %o", reason);
|
||||
throw reason;
|
||||
});
|
||||
|
||||
|
||||
/* Next, we wait for both the video and the libjass.ASS object to be available, i.e., for their respective promises to be
|
||||
* resolved. Once they have, we can create the libjass.renderers.DefaultRenderer object. This is the object that will display subs
|
||||
* on the <video> element.
|
||||
*/
|
||||
libjass.Promise.all([videoPromise, assLoadedPromise]).then(function (results) {
|
||||
var ass = results[1];
|
||||
|
||||
var rendererSettings = { };
|
||||
if (enableSvg !== null) {
|
||||
rendererSettings.enableSvg = enableSvg;
|
||||
}
|
||||
// else unset, which means libjass will try to auto-detect it.
|
||||
|
||||
// Create a DefaultRenderer using the video element and the ASS object
|
||||
var renderer = new libjass.renderers.DefaultRenderer(video, ass, rendererSettings);
|
||||
|
||||
// Export the renderer for debugging
|
||||
window.renderer = renderer;
|
||||
|
||||
|
||||
/* (Advanced)
|
||||
*
|
||||
* The renderer sets some internal things up, and then fires a "ready" event when it's done. If you want to have autoplaying video,
|
||||
* but want to wait for the renderer to set up first, you should only play the video when the "ready" event fires.
|
||||
*/
|
||||
renderer.addEventListener("ready", function () {
|
||||
console.log("Beginning autoplay.");
|
||||
|
||||
video.play();
|
||||
});
|
||||
|
||||
|
||||
/* (Advanced)
|
||||
*
|
||||
* This demo page also has a checkbox that can be used to turn the subs off. We'll use the checkbox's value with renderer.setEnabled()
|
||||
* to do this.
|
||||
*/
|
||||
document.querySelector("#enable-disable-subs").addEventListener("change", function (event) {
|
||||
renderer.setEnabled(event.target.checked);
|
||||
}, false);
|
||||
|
||||
|
||||
/* (Advanced)
|
||||
*
|
||||
* As mentioned above, this demo page allows the user to resize the video with two presets - the original video resolution and the subs
|
||||
* resolution. DefaultRenderer needs to be told of changes to the size of the <video> element because it needs the size information to
|
||||
* calculate the positions and sizes of the subs.
|
||||
*
|
||||
* This next function is called whenever the user chooses a different preset size for the video.
|
||||
*/
|
||||
var applyVideoSizeSelection = function () {
|
||||
// Find which option is selected
|
||||
|
||||
var id = videoSizeSelector.querySelector("input[name='video-size']:checked").id;
|
||||
|
||||
if (id === "video-size-video-radio") {
|
||||
// Resize to video resolution
|
||||
|
||||
video.style.width = video.videoWidth + "px";
|
||||
video.style.height = video.videoHeight + "px";
|
||||
renderer.resize();
|
||||
}
|
||||
|
||||
else if (id === "video-size-script-radio") {
|
||||
// Resize to script resolution
|
||||
|
||||
video.style.width = ass.properties.resolutionX + "px";
|
||||
video.style.height = ass.properties.resolutionY + "px";
|
||||
renderer.resize();
|
||||
}
|
||||
};
|
||||
|
||||
var videoSizeSelector = document.querySelector("#video-size-selector");
|
||||
videoSizeSelector.addEventListener("change", function (event) { applyVideoSizeSelection(); }, false);
|
||||
|
||||
// Set the resolution to whatever's selected by default
|
||||
applyVideoSizeSelection();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var htmlConsoleLog = function (htmlConsole) {
|
||||
return function (items) {
|
||||
var message = document.createElement("div");
|
||||
htmlConsole.appendChild(message);
|
||||
message.className = "log";
|
||||
|
||||
var text = new Date().toString() + ": ";
|
||||
items.forEach(function (item) {
|
||||
switch (typeof item) {
|
||||
case "boolean":
|
||||
case "number":
|
||||
case "string":
|
||||
text += item + " ";
|
||||
break;
|
||||
default:
|
||||
text += item + "[Check browser console for more details.] ";
|
||||
break;
|
||||
}
|
||||
});
|
||||
message.appendChild(document.createTextNode(text));
|
||||
};
|
||||
};
|
||||
|
||||
var htmlConsoleWarn = function (htmlConsole) {
|
||||
return function (items) {
|
||||
var message = document.createElement("div");
|
||||
htmlConsole.appendChild(message);
|
||||
message.className = "warning";
|
||||
|
||||
var text = new Date().toString() + ": ";
|
||||
items.forEach(function (item) {
|
||||
switch (typeof item) {
|
||||
case "boolean":
|
||||
case "number":
|
||||
case "string":
|
||||
text += item + " ";
|
||||
break;
|
||||
default:
|
||||
text += item + "[Check browser console for more details.] ";
|
||||
break;
|
||||
}
|
||||
});
|
||||
message.appendChild(document.createTextNode(text));
|
||||
};
|
||||
};
|
||||
|
||||
var htmlConsoleError = function (htmlConsole) {
|
||||
return function (items) {
|
||||
var message = document.createElement("div");
|
||||
htmlConsole.appendChild(message);
|
||||
message.className = "error";
|
||||
|
||||
var text = new Date().toString() + ": ";
|
||||
items.forEach(function (item) {
|
||||
switch (typeof item) {
|
||||
case "boolean":
|
||||
case "number":
|
||||
case "string":
|
||||
text += item + " ";
|
||||
break;
|
||||
default:
|
||||
text += item + "[Check browser console for more details.] ";
|
||||
break;
|
||||
}
|
||||
});
|
||||
message.appendChild(document.createTextNode(text));
|
||||
};
|
||||
};
|
||||
|
||||
addEventListener("error", function (event) {
|
||||
console.error(event.message, event.error);
|
||||
});
|
||||
@@ -0,0 +1,227 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
libjass
|
||||
|
||||
https://github.com/Arnavion/libjass
|
||||
|
||||
Copyright 2013 Arnav Singh
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
|
||||
<head>
|
||||
<title>libjass demo</title>
|
||||
<link rel="stylesheet" href="libjass.css" />
|
||||
<link rel="stylesheet" href="index.css" />
|
||||
<script src="libjass.js" />
|
||||
<script src="index.js" />
|
||||
<script src="dummy-video.js" />
|
||||
</head>
|
||||
<body>
|
||||
<p>This is a demo page for the libjass library - a library for displaying ASS subtitles in the browser. See the source of index.js for an explanation of how to use the library.</p>
|
||||
|
||||
<!-- Default look of the page - input controls to let the user choose a video and an ASS script. -->
|
||||
<div id="content">
|
||||
<fieldset>
|
||||
<legend>Choose a video</legend>
|
||||
<ul class="choices-list">
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio" id="video-choice-local-file" name="video-choice" />
|
||||
Local file (The file won't be uploaded. It will be used directly within your browser.)
|
||||
</label>
|
||||
<input type="file" id="video-input-local-file" accept="video/*" />
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio" id="video-choice-url" name="video-choice" />
|
||||
Direct video URL (webm / MP4)
|
||||
</label>
|
||||
<input type="url" id="video-input-url" />
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio" id="video-choice-sample" name="video-choice" checked="checked" />
|
||||
Sample video (75s long 1280x720, meant to be used with the default "Text" ASS option below)
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio" id="video-choice-dummy" name="video-choice" />
|
||||
Dummy video
|
||||
</label>
|
||||
<select id="video-input-dummy-resolution">
|
||||
<option value="640x480">640 x 480 (SD fullscreen)</option>
|
||||
<option value="704x480">704 x 480 (SD anamorphic)</option>
|
||||
<option value="640x360">640 x 360 (SD widescreen)</option>
|
||||
<option value="704x396">704 x 396 (SD widescreen)</option>
|
||||
<option value="640x352">640 x 352 (SD widescreen MOD16)</option>
|
||||
<option value="704x400">704 x 400 (SD widescreen MOD16)</option>
|
||||
<option value="1280x720" selected="selected">1280 x 720 (HD 720p)</option>
|
||||
<option value="1920x1080">1920 x 1080 (HD 1080p)</option>
|
||||
<option value="1024x576">1024 x 576 (SuperPAL widescreen)</option>
|
||||
</select>
|
||||
<input type="number" id="video-input-dummy-width" />
|
||||
<input type="number" id="video-input-dummy-height" />
|
||||
<input type="color" id="video-input-dummy-color" value="#2fa3fe"></input>
|
||||
<label><input type="number" id="video-input-dummy-duration" value="25"></input> mins</label>
|
||||
</li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Choose an ASS script</legend>
|
||||
<ul class="choices-list">
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio" id="ass-choice-local-file" name="ass-choice" />
|
||||
Local file (The file won't be uploaded. It will be used directly within your browser.)
|
||||
</label>
|
||||
<input type="file" id="ass-input-local-file" accept=".ass" />
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio" id="ass-choice-url" name="ass-choice" />
|
||||
Direct script URL (must be accessible via CORS)
|
||||
</label>
|
||||
<input type="url" id="ass-input-url" />
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio" id="ass-choice-text" name="ass-choice" checked="checked" />
|
||||
Text
|
||||
</label>
|
||||
<textarea id="ass-input-text"><![CDATA[[Script Info]
|
||||
; Script generated by Aegisub 2.1.8
|
||||
; http://www.aegisub.org/
|
||||
Title: Default Aegisub file
|
||||
ScriptType: v4.00+
|
||||
WrapStyle: 0
|
||||
PlayResX: 1280
|
||||
PlayResY: 720
|
||||
ScaledBorderAndShadow: yes
|
||||
Collisions: Normal
|
||||
Video Aspect Ratio: 0
|
||||
Video Zoom: 6
|
||||
Video Position: 0
|
||||
|
||||
[V4+ Styles]
|
||||
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
||||
Style: Default,Arial,50,&H00F1F4F9,&H000000FF,&H000F1115,&H96000000,0,0,0,0,100,100,0,0,1,1.5,0,2,80,80,35,1
|
||||
|
||||
[Events]
|
||||
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
||||
Dialogue: 0,0:00:00.00,0:01:15.00,Default,,0000,0000,0000,,{\an8}This is a sample ASS file\Nthat showcases some of libjass's capabilities.
|
||||
Dialogue: 0,0:00:05.00,0:00:10.00,Default,,0000,0000,0000,,{\c&H0000FF&}Red text with \c
|
||||
Dialogue: 0,0:00:10.00,0:00:15.00,Default,,0000,0000,0000,,{\c&H0000FF&\3c&H00FF00&}Red text with green outline with \c and \3c
|
||||
Dialogue: 0,0:00:15.00,0:00:20.00,Default,,0000,0000,0000,,{\alpha&H80&}Translucent text with \alpha
|
||||
Dialogue: 0,0:00:20.00,0:00:25.00,Default,,0000,0000,0000,,{\fad(2000,2000)}Fade-in and out text with \fad
|
||||
Dialogue: 0,0:00:25.00,0:00:30.00,Default,,0000,0000,0000,,{\move(0,0,1280,720)}Moving text with \mov
|
||||
Dialogue: 0,0:00:30.00,0:00:35.00,Default,,0000,0000,0000,,{\pos(640,360)}Positioned text with \pos
|
||||
Dialogue: 0,0:00:35.00,0:00:40.00,Default,,0000,0000,0000,,{\i1}Italic,{\i} {\u1}underlined{\u} and {\s1}strikeout{\s} text with \i, \u and \s
|
||||
Dialogue: 0,0:00:40.00,0:00:45.00,Default,,0000,0000,0000,,{\bord10}Borders with \bord
|
||||
Dialogue: 0,0:00:45.00,0:00:50.00,Default,,0000,0000,0000,,{\shad10}Shadows with \shad
|
||||
Dialogue: 0,0:00:50.00,0:00:55.00,Default,,0000,0000,0000,,{\fs20}Tiny text{\fs} and {\fs100}large text{\fs} with \fs
|
||||
Dialogue: 0,0:00:55.00,0:01:00.00,Default,,0000,0000,0000,,{\fscx200}Wide text{\fscx} and {\fscy200}tall text{\fscy} with \fscx and \fscy
|
||||
Dialogue: 0,0:01:00.00,0:01:05.00,Default,,0000,0000,0000,,{\pos(640,360)\frz60}Rotated text with \frz
|
||||
Dialogue: 0,0:01:05.00,0:01:10.00,Default,,0000,0000,0000,,{\pos(640,360)\t(\frz720)}You spin me right round baby right round
|
||||
Dialogue: 0,0:01:05.00,0:01:10.00,Default,,0000,0000,0000,,Spinning text with \t and \frz
|
||||
Dialogue: 0,0:01:10.00,0:01:15.00,Default,,0000,0000,0000,,The End
|
||||
]]></textarea>
|
||||
</li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Other options</legend>
|
||||
<ul>
|
||||
<li>
|
||||
Does the M at the end of this question appear red or black?
|
||||
<span style="color: black; -webkit-filter: url('#redtext'); filter: url('#redtext');">M</span>
|
||||
<label><input type="radio" id="enable-svg-yes" name="enable-svg" /> Red</label>
|
||||
<label><input type="radio" id="enable-svg-no" name="enable-svg" /> Black</label>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="0" height="0">
|
||||
<defs>
|
||||
<filter id="redtext" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feComponentTransfer in="SourceAlpha">
|
||||
<feFuncR type="linear" slope="0" intercept="1" />
|
||||
<feFuncG type="linear" slope="0" intercept="0" />
|
||||
<feFuncB type="linear" slope="0" intercept="0" />
|
||||
<feFuncA type="linear" slope="1" intercept="0" />
|
||||
</feComponentTransfer>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
</li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
|
||||
<button type="button" id="go-button" disabled="disabled">Go</button>
|
||||
</div>
|
||||
|
||||
<fieldset id="console">
|
||||
<legend>
|
||||
Console output
|
||||
<label>
|
||||
<input type="checkbox" id="debug-mode" />
|
||||
Enable debug mode
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" id="verbose-mode" />
|
||||
Enable verbose mode
|
||||
</label>
|
||||
</legend>
|
||||
</fieldset>
|
||||
|
||||
<div>
|
||||
<p>Found a bug? Please check if there's already a similar issue already reported at <a href="https://github.com/Arnavion/libjass/issues">https://github.com/Arnavion/libjass/issues</a> If there isn't, please open a new issue. You can also report it in the #libjass channel on the Rizon IRC network.</p>
|
||||
<p>Please include the following information in your bug report:
|
||||
<ul>
|
||||
<li>Your OS and browser versions. Eg: "Chrome 49 on Windows 7"</li>
|
||||
<li>A description of the bug. What did you expect to see? What happened instead? Eg: "All the subtitles are visible except the one at 00:00:05 'Was it you who broke the clock?'" or "The subtitle at 00:00:05 should be red but instead it's blue."</li>
|
||||
<li>If possible, a URL to the video and script that I can access for testing.</li>
|
||||
<li>Any text from the "Console output" section above.</li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script type="text/template" id="template">
|
||||
<!-- The video element that will be generated -->
|
||||
<video data-id="video" controls="" />
|
||||
|
||||
<!-- These controls will be placed next to the video to allow changing its size, and for turning the subs off and on. -->
|
||||
<form data-id="settings-form">
|
||||
<fieldset>
|
||||
<legend>Video size</legend>
|
||||
<div data-id="video-size-selector">
|
||||
<label>
|
||||
<input type="radio" name="video-size" data-id="video-size-video-radio" checked="checked" />
|
||||
Video resolution <span data-id="video-resolution-label-width" />x<span data-id="video-resolution-label-height" />
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="video-size" data-id="video-size-script-radio" />
|
||||
Script resolution <span data-id="script-resolution-label-width" />x<span data-id="script-resolution-label-height" />
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Subtitles</legend>
|
||||
<label>
|
||||
<input type="checkbox" data-id="enable-disable-subs" checked="checked" />
|
||||
Subtitles
|
||||
</label>
|
||||
</fieldset>
|
||||
</form>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -23,10 +23,6 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.libjass-wrapper.libjass-full-screen {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.libjass-subs {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
@@ -34,6 +30,8 @@
|
||||
|
||||
.libjass-subs, .libjass-subs * {
|
||||
pointer-events: none;
|
||||
-webkit-animation-fill-mode: both !important;
|
||||
animation-fill-mode: both !important;
|
||||
}
|
||||
|
||||
.libjass-subs.paused * {
|
||||
@@ -83,9 +81,9 @@
|
||||
/* Filter wrapper span */
|
||||
.libjass-subs div[data-dialogue-id] > span {
|
||||
-webkit-perspective-origin: center;
|
||||
-webkit-perspective: 200px;
|
||||
-webkit-perspective: 400px;
|
||||
perspective-origin: center;
|
||||
perspective: 200px;
|
||||
perspective: 400px;
|
||||
}
|
||||
|
||||
.libjass-font-measure {
|
||||
@@ -94,8 +92,13 @@
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.libjass-filters {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.libjass-filters * {
|
||||
color-interpolation-filters: sRGB;
|
||||
}
|
||||
+9980
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
@@ -1,794 +0,0 @@
|
||||
@font-face {
|
||||
font-family: "???? Pro";
|
||||
src: url("/fonts/Yryr%20minc%20R.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "123 and...";
|
||||
src: url("/fonts/123-and...-Regular.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "123and";
|
||||
src: url("/fonts/123-and...-Regular.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "AmbersHand-Plain";
|
||||
src: url("/fonts/LEHN011.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Amienne";
|
||||
src: url("/fonts/Amienne.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "A-OTF Futo Min A101 Pro";
|
||||
src: url("/fonts/A-OTF-FutoMinA101Pro-Bold.otf");
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "A-OTF Futo Min A101 Pro Bold";
|
||||
src: url("/fonts/A-OTF-FutoMinA101Pro-Bold.otf");
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Arial";
|
||||
src: url("/fonts/arial.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "ArialMT";
|
||||
src: url("/fonts/arial.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "ArtificeSSK";
|
||||
src: url("/fonts/ArtificeSSK.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Baar Sophia";
|
||||
src: url("/fonts/BAARS.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "BaarSophia";
|
||||
src: url("/fonts/BAARS.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Babylon 27";
|
||||
src: url("/fonts/Babylon-27-Babylon-27.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Balzano";
|
||||
src: url("/fonts/BALZANO.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Bee Marker Ink";
|
||||
src: url("/fonts/BeeMarkerInk.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "BeeMarkerInk";
|
||||
src: url("/fonts/BeeMarkerInk.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Berlin Sans FB Demi";
|
||||
src: url("/fonts/BRLNSDB.TTF"), url("/fonts/BRLNSDB_0.TTF");
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "BerlinSansFBDemi-Bold";
|
||||
src: url("/fonts/BRLNSDB.TTF"), url("/fonts/BRLNSDB_0.TTF");
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "BlueBrush";
|
||||
src: url("/fonts/BlueBrush.otf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Brianne's hand";
|
||||
src: url("/fonts/Brianne_s_hand.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Brianne'shand";
|
||||
src: url("/fonts/Brianne_s_hand.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "bylon 27";
|
||||
src: url("/fonts/Babylon-27-Babylon-27.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "CAC Moose";
|
||||
src: url("/fonts/CAC%20Moose.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "CACMoose";
|
||||
src: url("/fonts/CAC%20Moose.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "CenturyOldStyle-Light";
|
||||
src: url("/fonts/CENTURYO.TTF"), url("/fonts/CenturyOldStyle-Light.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "CenturyOldStyle-Light";
|
||||
src: url("/fonts/CenturyOldStyle-Light%20Bold.ttf");
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "CenturyOldStyle-Light-Bold";
|
||||
src: url("/fonts/CenturyOldStyle-Light%20Bold.ttf");
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Charme";
|
||||
src: url("/fonts/CHARME.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Chinacat";
|
||||
src: url("/fonts/chinrg.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Complete in Him";
|
||||
src: url("/fonts/Complete%20in%20Him.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "CompleteinHim";
|
||||
src: url("/fonts/Complete%20in%20Him.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Consolas";
|
||||
src: url("/fonts/consola.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Consolas";
|
||||
src: url("/fonts/consolab.ttf");
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Consolas-Bold";
|
||||
src: url("/fonts/consolab.ttf");
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Continuum Bold";
|
||||
src: url("/fonts/contb.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "ContinuumBold";
|
||||
src: url("/fonts/contb.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Designosaur";
|
||||
src: url("/fonts/Designosaur-Regular.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Designosaur";
|
||||
src: url("/fonts/Designosaur-Italic.ttf");
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Designosaur-Italic";
|
||||
src: url("/fonts/Designosaur-Italic.ttf");
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Dutch766 BT";
|
||||
src: url("/fonts/Du766Bd.ttf");
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Dutch766BT-BoldA";
|
||||
src: url("/fonts/Du766Bd.ttf");
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "EFN Impresja";
|
||||
src: url("/fonts/impresja.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "EFNImpresja";
|
||||
src: url("/fonts/impresja.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Elephant";
|
||||
src: url("/fonts/ELEPHNT.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Elephant-Regular";
|
||||
src: url("/fonts/ELEPHNT.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "EmilysHand-Plain";
|
||||
src: url("/fonts/LEHN084.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "EngraversGothic BT";
|
||||
src: url("/fonts/Engrvgot.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "EngraversGothicBT-Regular";
|
||||
src: url("/fonts/Engrvgot.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Essai";
|
||||
src: url("/fonts/Essai.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Estrangelo Edessa";
|
||||
src: url("/fonts/estre.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "EstrangeloEdessa";
|
||||
src: url("/fonts/estre.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Euphemia";
|
||||
src: url("/fonts/Euphemia_0.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "EuphemiaCAS";
|
||||
src: url("/fonts/Euphemia_0.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "FarrahsHand-Plain";
|
||||
src: url("/fonts/LEHN095.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Fontin Sans";
|
||||
src: url("/fonts/FONTIN_SANS.OTF");
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Fontin Sans";
|
||||
src: url("/fonts/FONTIN_SANS_BI.OTF");
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Fontin Sans Rg";
|
||||
src: url("/fonts/FONTIN_SANS.OTF");
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Fontin Sans Rg";
|
||||
src: url("/fonts/FONTIN_SANS_BI.OTF");
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "FontinSans-Bold";
|
||||
src: url("/fonts/FONTIN_SANS.OTF");
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "FontinSans-BoldItalic";
|
||||
src: url("/fonts/FONTIN_SANS_BI.OTF");
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Forte";
|
||||
src: url("/fonts/FORTE.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "ForteMT";
|
||||
src: url("/fonts/FORTE.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Franchise";
|
||||
src: url("/fonts/Franchise-Bold-hinted.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Franchise-Bold";
|
||||
src: url("/fonts/Franchise-Bold-hinted.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "FutoMinA101Pro-Bold";
|
||||
src: url("/fonts/A-OTF-FutoMinA101Pro-Bold.otf");
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Gandhi Sans";
|
||||
src: url("/fonts/GandhiSans-Bold.otf");
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Gandhi Sans";
|
||||
src: url("/fonts/GandhiSans-BoldItalic.otf");
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "GandhiSans-Bold";
|
||||
src: url("/fonts/GandhiSans-Bold.otf");
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "GandhiSans-BoldItalic";
|
||||
src: url("/fonts/GandhiSans-BoldItalic.otf");
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "HandelGotDBol";
|
||||
src: url("/fonts/HANDGOTB.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "HandelGotD-Bold";
|
||||
src: url("/fonts/HANDGOTB.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "HandelGothic BT";
|
||||
src: url("/fonts/HANDGOTN.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "HandelGothicBT-Regular";
|
||||
src: url("/fonts/HANDGOTN.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "HandVetica";
|
||||
src: url("/fonts/HandVetica.ttf");
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "HandVetica-Bold";
|
||||
src: url("/fonts/HandVetica.ttf");
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "HiraginoGyoDS-W4";
|
||||
src: url("/fonts/HiraginoGyoDS-W4.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "HZHandwrite";
|
||||
src: url("/fonts/HZHandwrite.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Impressum Std";
|
||||
src: url("/fonts/ImpressumStd-Roman.otf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "ImpressumStd-Roman";
|
||||
src: url("/fonts/ImpressumStd-Roman.otf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "ITC Avant Garde Gothic Std";
|
||||
src: url("/fonts/ITCAvantGardeStd-Md.otf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "ITC Avant Garde Std Md";
|
||||
src: url("/fonts/ITCAvantGardeStd-Md.otf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "ITCAvantGardeStd-Md";
|
||||
src: url("/fonts/ITCAvantGardeStd-Md.otf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "IwaMinPro-Madoka-Lt";
|
||||
src: url("/fonts/IwaMinPro-Lt-Madoka.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "IwaOMinPro-Fate-Bd";
|
||||
src: url("/fonts/IwaOMinPro-Bd-Fate.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Iwata Mincho Old Pro-Fate";
|
||||
src: url("/fonts/IwaOMinPro-Bd-Fate.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Iwata Mincho Old Pro-Fate B";
|
||||
src: url("/fonts/IwaOMinPro-Bd-Fate.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Iwata Mincho Pro-Madoka";
|
||||
src: url("/fonts/IwaMinPro-Lt-Madoka.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Iwata Mincho Pro-Madoka L";
|
||||
src: url("/fonts/IwaMinPro-Lt-Madoka.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "JanieHmkBold";
|
||||
src: url("/fonts/JANIHB.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Kabel LT Std";
|
||||
src: url("/fonts/KabelLTStd-Book.otf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Kabel LT Std Book";
|
||||
src: url("/fonts/KabelLTStd-Book.otf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "KabelLTStd-Book";
|
||||
src: url("/fonts/KabelLTStd-Book.otf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "KozGoPro-Heavy-Madoka";
|
||||
src: url("/fonts/KozGoProH-Madoka.ttf");
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Kozuka Gothic Pro";
|
||||
src: url("/fonts/KozGoProH-Madoka.ttf");
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "KozukaGothicProH-Madoka";
|
||||
src: url("/fonts/KozGoProH-Madoka.ttf");
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "LEHN011";
|
||||
src: url("/fonts/LEHN011.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "LEHN084";
|
||||
src: url("/fonts/LEHN084.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "LEHN095";
|
||||
src: url("/fonts/LEHN095.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "LHF Scriptana";
|
||||
src: url("/fonts/LHF_Scriptana.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "LHFScriptana-Regular";
|
||||
src: url("/fonts/LHF_Scriptana.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "LinotypeFinnegan-Medium";
|
||||
src: url("/fonts/LT.ttf"), url("/fonts/LT_3.ttf"), url("/fonts/LT_4.ttf"), url("/fonts/macron%20finnetier%200.3.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "LinotypeFinnegan-MediumIt";
|
||||
src: url("/fonts/LT_3italic.ttf"), url("/fonts/LT_italic.ttf");
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "LLRubberGrotesqueOTF";
|
||||
src: url("/fonts/LLRUG.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "LTFinnegan Medium";
|
||||
src: url("/fonts/LT.ttf"), url("/fonts/LT_3.ttf"), url("/fonts/LT_4.ttf"), url("/fonts/macron%20finnetier%200.3.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "LTFinnegan Medium";
|
||||
src: url("/fonts/LT_3italic.ttf"), url("/fonts/LT_italic.ttf");
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Macron Finnetier Medium";
|
||||
src: url("/fonts/macron%20finnetier%200.3.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "MacronFinnetier-Medium";
|
||||
src: url("/fonts/macron%20finnetier%200.3.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Mentone";
|
||||
src: url("/fonts/mentone-semibol.otf");
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Mentone Lig";
|
||||
src: url("/fonts/mentone-semibol.otf");
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Mentone-SemiBold";
|
||||
src: url("/fonts/mentone-semibol.otf");
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Montara";
|
||||
src: url("/fonts/Montara-Gothic.otf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Montara";
|
||||
src: url("/fonts/Montara-Italic.otf");
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Montara Gothic";
|
||||
src: url("/fonts/Montara-Gothic.otf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Montara Gothic";
|
||||
src: url("/fonts/Montara-Italic.otf");
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Montara-Gothic";
|
||||
src: url("/fonts/Montara-Gothic.otf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Montara-Italic";
|
||||
src: url("/fonts/Montara-Italic.otf");
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "New";
|
||||
src: url("/fonts/OBGB.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "NewBaskerville";
|
||||
src: url("/fonts/NEWBASK.TTF"), url("/fonts/NewBaskerville.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Old Block";
|
||||
src: url("/fonts/OBGB.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Old Block Black";
|
||||
src: url("/fonts/OBGB.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Old newspaper font";
|
||||
src: url("/fonts/Old_Newspaper_Font.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Oldnewspaperfont";
|
||||
src: url("/fonts/Old_Newspaper_Font.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Painfresco";
|
||||
src: url("/fonts/Painfresco-Regular.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Painfresco";
|
||||
src: url("/fonts/Painfresco-Italic.ttf");
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Painfresco-Italic";
|
||||
src: url("/fonts/Painfresco-Italic.ttf");
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Painfresco-Regular";
|
||||
src: url("/fonts/Painfresco-Regular.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "PaintyPaint";
|
||||
src: url("/fonts/PAINP.TTF"), url("/fonts/PAINP_0.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "PaintyPaint1";
|
||||
src: url("/fonts/PAINP.TTF"), url("/fonts/PAINP_0.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Panefresco800wt-Italic";
|
||||
src: url("/fonts/Painfresco-Italic.ttf");
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Panefresco800wtKilllaKill-Regular";
|
||||
src: url("/fonts/Painfresco-Regular.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Pencilfied";
|
||||
src: url("/fonts/pencilfied.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "PencilfiedX";
|
||||
src: url("/fonts/pencilfied.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Profile";
|
||||
src: url("/fonts/Profile-Medium.otf"), url("/fonts/Profile-Regular.otf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Profile-Medium";
|
||||
src: url("/fonts/Profile-Medium.otf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Profile-Regular";
|
||||
src: url("/fonts/Profile-Regular.otf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Prototype";
|
||||
src: url("/fonts/Prototype.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Reswysokr";
|
||||
src: url("/fonts/Reswysokr.otf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Signika";
|
||||
src: url("/fonts/Signika-Regular.otf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Signika-Regular";
|
||||
src: url("/fonts/Signika-Regular.otf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Spectrum MT Std";
|
||||
src: url("/fonts/SpectrumMTStd.otf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "SpectrumMTStd";
|
||||
src: url("/fonts/SpectrumMTStd.otf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Swis721 Hv BT";
|
||||
src: url("/fonts/SWZ721H.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Swis721 Md BT";
|
||||
src: url("/fonts/SWZ721M.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Swiss721BT-Heavy";
|
||||
src: url("/fonts/SWZ721H.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Swiss721BT-Medium";
|
||||
src: url("/fonts/SWZ721M.TTF");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Unisketchlightlimited";
|
||||
src: url("/fonts/Unisketch-light_limited.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Unisketch-lightlimitedLight";
|
||||
src: url("/fonts/Unisketch-light_limited.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Untitled";
|
||||
src: url("/fonts/Babylon-27-Babylon-27.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Yryr minc R";
|
||||
src: url("/fonts/Yryr%20minc%20R.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "YryrmincR";
|
||||
src: url("/fonts/Yryr%20minc%20R.ttf");
|
||||
}
|
||||
|
||||
-130
@@ -1,130 +0,0 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
|
||||
var gulp = require("gulp");
|
||||
var Vinyl = require("vinyl");
|
||||
|
||||
var helpers = require("./gulplib/helpers.js");
|
||||
var Transform = helpers.Transform;
|
||||
var SingletonChildProcess = helpers.SingletonChildProcess;
|
||||
|
||||
Object.defineProperties(global, {
|
||||
Doc: { get: function () { return require("./gulplib/doc.js"); } },
|
||||
TypeScript: { get: function () { return require("./gulplib/typescript.js"); } },
|
||||
UglifyJS: { get: function () { return require("./gulplib/uglify.js"); } },
|
||||
});
|
||||
|
||||
gulp.task("default", ["libjass.js", "libjass.min.js"]);
|
||||
|
||||
function ASTModifer(namespaces) {
|
||||
var visitor = function (current) {
|
||||
var newText = [];
|
||||
|
||||
if (current instanceof TypeScript.AST.Namespace) {
|
||||
current.members.forEach(visitor);
|
||||
}
|
||||
else if (current instanceof TypeScript.AST.Constructor) {
|
||||
newText.push("@constructor");
|
||||
|
||||
if (current.baseType !== null) {
|
||||
newText.push("@extends {" + current.baseType.fullName + "}");
|
||||
}
|
||||
|
||||
if (current.parent !== null) {
|
||||
newText.push("@memberOf " + current.parent.fullName);
|
||||
}
|
||||
|
||||
current.members.forEach(visitor);
|
||||
}
|
||||
|
||||
if (current.isPrivate) {
|
||||
newText.push("@private");
|
||||
}
|
||||
|
||||
if (current.isStatic) {
|
||||
newText.push("@static");
|
||||
}
|
||||
|
||||
if (newText.length > 0) {
|
||||
current.astNode["gulp-typescript-new-comment"] = newText;
|
||||
}
|
||||
};
|
||||
|
||||
Object.keys(namespaces).forEach(function (namespaceName) { visitor(namespaces[namespaceName]); });
|
||||
}
|
||||
|
||||
gulp.task("libjass.js", function (callback) {
|
||||
if (fs.existsSync("./libjass.js")) {
|
||||
return callback(null);
|
||||
}
|
||||
|
||||
return gulp.src("./libjass.ts", { read: false })
|
||||
.pipe(TypeScript.gulp("/libjass.js", "/libjass.js.map", ASTModifer))
|
||||
.pipe(UglifyJS.fixup())
|
||||
.pipe(gulp.dest("."));
|
||||
});
|
||||
|
||||
gulp.task("libjass.min.js", ["libjass.js"], function (callback) {
|
||||
if (fs.existsSync("./libjass.min.js")) {
|
||||
return callback(null);
|
||||
}
|
||||
|
||||
var input = Object.create(null);
|
||||
|
||||
return gulp.src(["./libjass.js", "./libjass.js.map"]).pipe(UglifyJS.minify()).pipe(gulp.dest("."));
|
||||
});
|
||||
|
||||
gulp.task("clean", function () {
|
||||
["libjass.js", "libjass.js.map", "libjass.min.js", "libjass.min.js.map"].forEach(function (file) {
|
||||
try {
|
||||
fs.unlinkSync(file);
|
||||
}
|
||||
catch (ex) {
|
||||
if (ex.code !== "ENOENT") {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task("watch", ["clean"], function (callback) {
|
||||
var gulpTestProcess = SingletonChildProcess(path.resolve("./gulplib/test-runner.js"));
|
||||
|
||||
gulp.src("./libjass.ts", { read: false })
|
||||
.pipe(TypeScript.watch("/libjass.js", "/libjass.js.map"))
|
||||
.pipe(UglifyJS.fixup())
|
||||
.pipe(gulp.dest("."))
|
||||
.pipe(Transform(function (file) {
|
||||
if (path.basename(file.path) === "libjass.js") {
|
||||
gulpTestProcess();
|
||||
}
|
||||
}));
|
||||
|
||||
gulp.watch(["./tests/test-*.js"], { debounceDelay: 1 }, function () {
|
||||
gulpTestProcess();
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task("doc", ["libjass.js"], function () {
|
||||
return gulp.src("./libjass.ts").pipe(Doc("/api.xhtml")).pipe(gulp.dest("../libjass-gh-pages/"));
|
||||
});
|
||||
-678
@@ -1,678 +0,0 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
var vm = require("vm");
|
||||
|
||||
var Vinyl = require("vinyl");
|
||||
|
||||
var Transform = require("./helpers.js").Transform;
|
||||
var TypeScript = require("./typescript.js");
|
||||
|
||||
Array.prototype.concatMany = function (arr) {
|
||||
var result = this;
|
||||
|
||||
arr.forEach(function (a) {
|
||||
result = result.concat(a);
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
module.exports = function () {
|
||||
var compiler = new TypeScript.Compiler();
|
||||
|
||||
var filenames = [];
|
||||
var allFiles = [];
|
||||
|
||||
return Transform(function (file, encoding) {
|
||||
filenames.push(file.path);
|
||||
allFiles.push.apply(allFiles, compiler.addFile(file));
|
||||
}, function () {
|
||||
// Walk
|
||||
var namespaces = TypeScript.AST.walk(compiler, allFiles);
|
||||
|
||||
// Make HTML
|
||||
var sorter = (function () {
|
||||
var visibilitySorter = function (value1, value2) {
|
||||
if (value1.isPrivate === value2.isPrivate) {
|
||||
return 0;
|
||||
}
|
||||
if (value1.isPrivate) {
|
||||
return 1;
|
||||
}
|
||||
if (value2.isPrivate) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
var types = [TypeScript.AST.Variable, TypeScript.AST.Property, TypeScript.AST.Function, TypeScript.AST.Interface, TypeScript.AST.Constructor];
|
||||
var typeSorter = function (value1, value2) {
|
||||
var type1Index = -1;
|
||||
var type2Index = -1;
|
||||
|
||||
types.every(function (type, index) {
|
||||
if (value1 instanceof type) {
|
||||
type1Index = index;
|
||||
}
|
||||
if (value2 instanceof type) {
|
||||
type2Index = index;
|
||||
}
|
||||
return (type1Index === -1) || (type2Index === -1);
|
||||
});
|
||||
|
||||
return type1Index - type2Index;
|
||||
};
|
||||
|
||||
var nameSorter = function (value1, value2) {
|
||||
return value1.name.localeCompare(value2.name);
|
||||
};
|
||||
|
||||
var sorters = [visibilitySorter, typeSorter, nameSorter];
|
||||
|
||||
return function (value1, value2) {
|
||||
for (var i = 0; i < sorters.length; i++) {
|
||||
var result = sorters[i](value1, value2);
|
||||
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
})();
|
||||
|
||||
var namespaceNames = Object.keys(namespaces).sort(function (ns1, ns2) {
|
||||
return ns1.localeCompare(ns2);
|
||||
});
|
||||
|
||||
namespaceNames.forEach(function (namespaceName) {
|
||||
namespaces[namespaceName].members.sort(sorter);
|
||||
|
||||
namespaces[namespaceName].members.filter(function (value) {
|
||||
return value instanceof TypeScript.AST.Interface || value instanceof TypeScript.AST.Constructor;
|
||||
}).forEach(function (interfaceOrConstructor) {
|
||||
interfaceOrConstructor.members.sort(sorter);
|
||||
});
|
||||
});
|
||||
|
||||
var sanitize = function (string) {
|
||||
return string.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
};
|
||||
|
||||
var indenter = function (indent) {
|
||||
return function (line) {
|
||||
return ((line === "") ? line : (Array(indent + 1).join("\t") + line));
|
||||
};
|
||||
};
|
||||
|
||||
var writeOverview = function (indent) {
|
||||
return [].concatMany(namespaceNames.map(function (namespaceName) {
|
||||
var namespace = namespaces[namespaceName];
|
||||
|
||||
return [
|
||||
'<span class="namespace"><a href="#' + sanitize(namespaceName) + '">' + sanitize(namespaceName) + '</a></span>',
|
||||
'<ul class="namespace-elements">'
|
||||
].concatMany(namespace.members.map(function (value) {
|
||||
return (
|
||||
' <li' +
|
||||
((value.isPrivate === true) ? ' class="private"' : '') +
|
||||
'><a href="#' + sanitize(value.fullName) + '">' + sanitize(value.name) + '</a></li>'
|
||||
);
|
||||
})).concat([
|
||||
'</ul>',
|
||||
''
|
||||
]);
|
||||
})).map(indenter(indent));
|
||||
};
|
||||
|
||||
var writeFunction = function (func, indent) {
|
||||
return [
|
||||
'<dl class="function' +
|
||||
(func.isAbstract ? ' abstract' : '') +
|
||||
(func.isPrivate ? ' private' : '') +
|
||||
(func.isStatic ? ' static' : '') +
|
||||
'" id="' + sanitize(func.fullName) +
|
||||
'">',
|
||||
' <dt class="name">' + writeFunctionName(func) + '</dt>',
|
||||
' <dd class="description">',
|
||||
' <p>' + sanitize(func.description) + '</p>',
|
||||
' </dd>',
|
||||
' <dd class="usage"><fieldset><legend />' + writeFunctionUsage(func) + '</fieldset></dd>'
|
||||
].concat(writeParameters(func, 1)).concat(writeReturns(func, 1)).concat([
|
||||
'</dl>'
|
||||
]).map(indenter(indent));
|
||||
};
|
||||
|
||||
var writeInterface = function (interface, indent) {
|
||||
return [
|
||||
'<dl class="interface' +
|
||||
(interface.isPrivate ? ' private' : '') +
|
||||
'" id="' +
|
||||
sanitize(interface.fullName) +
|
||||
'">',
|
||||
' <dt class="name">' + writeInterfaceName(interface) + '</dt>',
|
||||
' <dd class="description">',
|
||||
' <p>' + sanitize(interface.description) + '</p>',
|
||||
' </dd>'
|
||||
].concat([
|
||||
' <dd class="members">'
|
||||
]).concatMany(interface.members.map(function (member) {
|
||||
if (member instanceof TypeScript.AST.Variable) {
|
||||
return writeVariable(member, 2);
|
||||
}
|
||||
if (member instanceof TypeScript.AST.Function) {
|
||||
return writeFunction(member, 2);
|
||||
}
|
||||
if (member instanceof TypeScript.AST.Property) {
|
||||
return writeProperty(member, 2);
|
||||
}
|
||||
})).concat([
|
||||
' </dd>',
|
||||
'</dl>'
|
||||
]).map(indenter(indent));
|
||||
};
|
||||
|
||||
var writeConstructor = function (constructor, indent) {
|
||||
return [
|
||||
'<dl class="constructor' +
|
||||
(constructor.isAbstract ? ' abstract' : '') +
|
||||
(constructor.isPrivate ? ' private' : '') +
|
||||
'" id="' +
|
||||
sanitize(constructor.fullName) +
|
||||
'">',
|
||||
' <dt class="name">' + writeConstructorName(constructor) + '</dt>',
|
||||
' <dd class="description">',
|
||||
' <p>' + sanitize(constructor.description) + '</p>',
|
||||
' </dd>',
|
||||
' <dd class="usage"><fieldset><legend />' + writeConstructorUsage(constructor) + '</fieldset></dd>'
|
||||
].concat(writeParameters(constructor, 1)).concat([
|
||||
' <dd class="members">'
|
||||
]).concatMany(constructor.members.map(function (member) {
|
||||
if (member instanceof TypeScript.AST.Variable) {
|
||||
return writeVariable(member, 2);
|
||||
}
|
||||
if (member instanceof TypeScript.AST.Function) {
|
||||
return writeFunction(member, 2);
|
||||
}
|
||||
if (member instanceof TypeScript.AST.Property) {
|
||||
return writeProperty(member, 2);
|
||||
}
|
||||
})).concat([
|
||||
' </dd>',
|
||||
'</dl>'
|
||||
]).map(indenter(indent));
|
||||
};
|
||||
|
||||
var writeVariable = function (variable, indent) {
|
||||
return [
|
||||
'<dl class="variable" id="' + sanitize(variable.fullName) + '">',
|
||||
' <dt class="name">' + writePropertyName(variable) + '</dt>',
|
||||
' <dd class="description">',
|
||||
' <p>' + sanitize(variable.description) + '</p>',
|
||||
' </dd>',
|
||||
' <dd class="usage"><fieldset><legend />' + writeVariableUsage(variable) + '</fieldset></dd>'
|
||||
].concat([
|
||||
' <dd class="return type">' + sanitize(variable.type) + '</dd>',
|
||||
]).concat([
|
||||
'</dl>'
|
||||
]).map(indenter(indent));
|
||||
};
|
||||
|
||||
var writeCallableName = function (callable) {
|
||||
return (
|
||||
'<a href="#' + sanitize(callable.fullName) + '">' +
|
||||
sanitize(
|
||||
callable.name +
|
||||
((callable.generics.length > 0) ? ('.<' + callable.generics.join(', ') + '>') : '')
|
||||
) +
|
||||
'</a>'
|
||||
);
|
||||
};
|
||||
|
||||
var writeFunctionName = function (func) {
|
||||
return writeCallableName(func);
|
||||
};
|
||||
|
||||
var writeInterfaceName = function (interface) {
|
||||
return (
|
||||
'interface ' +
|
||||
writeCallableName(interface) +
|
||||
((interface.baseType !== null) ? (' extends <a href="#' + sanitize(interface.baseType.fullName) + '">' + sanitize(interface.baseType.name) + '</a>') : '')
|
||||
);
|
||||
};
|
||||
|
||||
var writeConstructorName = function (constructor) {
|
||||
return (
|
||||
'class ' +
|
||||
writeCallableName(constructor) +
|
||||
((constructor.baseType !== null) ? (' extends <a href="#' + sanitize(constructor.baseType.fullName) + '">' + sanitize(constructor.baseType.name) + '</a>') : '')
|
||||
);
|
||||
};
|
||||
|
||||
var writeVariableUsage = function (variable) {
|
||||
return (
|
||||
'<pre><code>' +
|
||||
sanitize('var result = ' + toVariableName(variable.parent) + '.' + variable.name + ';' +
|
||||
'\n' +
|
||||
toVariableName(variable.parent) + '.' + variable.name + ' = value;') +
|
||||
'</code></pre>'
|
||||
);
|
||||
};
|
||||
|
||||
var writeFunctionUsage = function (func) {
|
||||
return (
|
||||
'<pre><code>' +
|
||||
sanitize(
|
||||
((func.returnType !== null) ? 'var result = ' : '') +
|
||||
((func.parent !== null) ? ((func.isStatic ? func.parent.name : toVariableName(func.parent)) + '.') : '') +
|
||||
func.name + '(' +
|
||||
func.parameters.map(function (parameter) {
|
||||
return parameter.name;
|
||||
}).join(', ') + ');') +
|
||||
'</code></pre>'
|
||||
);
|
||||
};
|
||||
|
||||
var writeConstructorUsage = function (constructor) {
|
||||
return (
|
||||
'<pre><code>' +
|
||||
sanitize(
|
||||
'var ' + toVariableName(constructor) + ' = ' +
|
||||
'new ' + constructor.name + '(' +
|
||||
constructor.parameters.map(function (parameter) {
|
||||
return parameter.name;
|
||||
}).join(', ') + ');') +
|
||||
'</code></pre>'
|
||||
);
|
||||
};
|
||||
|
||||
var writeParameters = function (callable, indent) {
|
||||
return _writeParameters(callable.parameters, indent);
|
||||
};
|
||||
|
||||
var _writeParameters = function (parameters, indent) {
|
||||
if (parameters.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'<dd class="parameters">',
|
||||
' <dl>'
|
||||
].concatMany(parameters.map(function (parameter) {
|
||||
return [
|
||||
' <dt class="parameter name">' + sanitize(parameter.name) + '</dt>',
|
||||
' <dd class="parameter type">' + sanitize(parameter.type) + '</dd>',
|
||||
' <dd class="parameter description">' + sanitize(parameter.description) + '</dd>'
|
||||
].concatMany(_writeParameters(parameter.subParameters, indent + 1));
|
||||
})).concat([
|
||||
' </dl>',
|
||||
'</dd>'
|
||||
]).map(indenter(indent));
|
||||
};
|
||||
|
||||
var writeReturns = function (func, indent) {
|
||||
if (func.returnType === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'<dt>Returns</dt>',
|
||||
'<dd class="return type">' + sanitize(func.returnType.type) + '</dd>',
|
||||
'<dd class="return description">' + sanitize(func.returnType.description) + '</dd>'
|
||||
].map(indenter(indent));
|
||||
};
|
||||
|
||||
var writeProperty = function (property, indent) {
|
||||
var result = [];
|
||||
|
||||
if (property.getter !== null) {
|
||||
result = result.concat(writeGetter(property, indent));
|
||||
}
|
||||
|
||||
if (property.setter !== null) {
|
||||
result = result.concat(writeSetter(property, indent));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
var writeGetter = function (property, indent) {
|
||||
var getter = property.getter;
|
||||
|
||||
return [
|
||||
'<dl class="getter" id="' + sanitize(property.fullName) + '">',
|
||||
' <dt class="name">' + writePropertyName(property) + '</dt>',
|
||||
' <dd class="description">',
|
||||
' <p>' + sanitize(getter.description) + '</p>',
|
||||
' </dd>',
|
||||
' <dd class="usage"><fieldset><legend />' + writeGetterUsage(property) + '</fieldset></dd>'
|
||||
].concat([
|
||||
' <dd class="return type">' + sanitize(getter.type) + '</dd>',
|
||||
]).concat([
|
||||
'</dl>'
|
||||
]).map(indenter(indent));
|
||||
};
|
||||
|
||||
var writeSetter = function (property, indent) {
|
||||
var setter = property.setter;
|
||||
|
||||
return [
|
||||
'<dl class="setter" id="' + sanitize(property.fullName) + '">',
|
||||
' <dt class="name">' + writePropertyName(property) + '</dt>',
|
||||
' <dd class="description">',
|
||||
' <p>' + sanitize(setter.description) + '</p>',
|
||||
' </dd>',
|
||||
' <dd class="usage"><fieldset><legend />' + writeSetterUsage(property) + '</fieldset></dd>'
|
||||
].concat(writeParameters(setter, 1)).concat([
|
||||
'</dl>'
|
||||
]).map(indenter(indent));
|
||||
};
|
||||
|
||||
var writePropertyName = function (property) {
|
||||
return (
|
||||
'<a href="#' + sanitize(property.fullName) + '">' +
|
||||
sanitize(property.name) +
|
||||
'</a>'
|
||||
);
|
||||
};
|
||||
|
||||
var writeGetterUsage = function (property) {
|
||||
return (
|
||||
'<pre><code>' +
|
||||
sanitize(
|
||||
'var result = ' +
|
||||
toVariableName(property.parent) + '.' + property.name +
|
||||
';') +
|
||||
'</code></pre>'
|
||||
);
|
||||
};
|
||||
|
||||
var writeSetterUsage = function (property) {
|
||||
return (
|
||||
'<pre><code>' +
|
||||
sanitize(
|
||||
toVariableName(property.parent) + '.' + property.name +
|
||||
' = ' +
|
||||
property.setter.parameters[0].name +
|
||||
';') +
|
||||
'</code></pre>'
|
||||
);
|
||||
};
|
||||
|
||||
var toVariableName = function (constructor) {
|
||||
// TODO: Handle non-letters (are both their toLowerCase() and toUpperCase())
|
||||
|
||||
var name = constructor.name;
|
||||
var result = "";
|
||||
|
||||
for (var i = 0; i < name.length; i++) {
|
||||
if (name[i] === name[i].toLowerCase()) {
|
||||
// This is lower case. Write it as lower case.
|
||||
result += name[i];
|
||||
}
|
||||
|
||||
else {
|
||||
// This is upper case.
|
||||
|
||||
if (i === 0) {
|
||||
// This is the first character. Write it as lower case.
|
||||
result += name[i].toLowerCase();
|
||||
}
|
||||
|
||||
else if (name[i - 1] === name[i - 1].toUpperCase()) {
|
||||
// The previous character was upper case.
|
||||
|
||||
if (i === name.length - 1) {
|
||||
// This is the last character. Write it as lower case.
|
||||
result += name[i].toLowerCase();
|
||||
}
|
||||
else if (name[i + 1] === name[i + 1].toLowerCase()) {
|
||||
// The next character is lower case so this is the start of a new word. Write this one as upper case.
|
||||
result += name[i];
|
||||
}
|
||||
else {
|
||||
// The next character is upper case. Write this one as lower case.
|
||||
result += name[i].toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
// Previous character was lower case so this is the start of a new word. Write this one as upper case.
|
||||
result += name[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
this.push(new Vinyl({
|
||||
path: "api.xhtml",
|
||||
contents: Buffer.concat([
|
||||
'<?xml version="1.0" encoding="utf-8" ?>',
|
||||
'<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">',
|
||||
' <head>',
|
||||
' <title>libjass API Documentation</title>',
|
||||
' <style type="text/css">',
|
||||
' <![CDATA[',
|
||||
' html, body, .namespaces, .content {',
|
||||
' height: 100%;',
|
||||
' margin: 0;',
|
||||
' }',
|
||||
'',
|
||||
' .namespaces, .content {',
|
||||
' overflow-y: scroll;',
|
||||
' }',
|
||||
'',
|
||||
' .namespaces {',
|
||||
' float: left;',
|
||||
' background-color: white;',
|
||||
' padding: 0 20px;',
|
||||
' margin-right: 20px;',
|
||||
' }',
|
||||
'',
|
||||
' .content > section:not(:last-child) {',
|
||||
' border-bottom: 1px solid black;',
|
||||
' }',
|
||||
'',
|
||||
' .variable, .function, .interface, .constructor, .getter, .setter {',
|
||||
' margin-left: 30px;',
|
||||
' padding: 10px;',
|
||||
' }',
|
||||
'',
|
||||
' section > .variable:nth-child(2n), section > .function:nth-child(2n), section > .interface:nth-child(2n), section > .constructor:nth-child(2n) {',
|
||||
' background-color: rgb(221, 250, 238);',
|
||||
' }',
|
||||
'',
|
||||
' section > .variable:nth-child(2n + 1), section > .function:nth-child(2n + 1), section > .interface:nth-child(2n + 1), section > .constructor:nth-child(2n + 1) {',
|
||||
' background-color: rgb(244, 250, 221);',
|
||||
' }',
|
||||
'',
|
||||
' .name {',
|
||||
' font-size: x-large;',
|
||||
' }',
|
||||
'',
|
||||
' .usage {',
|
||||
' font-size: large;',
|
||||
' font-style: italic;',
|
||||
' }',
|
||||
'',
|
||||
' .usage legend:before {',
|
||||
' content: "Usage";',
|
||||
' }',
|
||||
'',
|
||||
' .usage pre {',
|
||||
' margin: 0;',
|
||||
' }',
|
||||
'',
|
||||
' .interface .variable, .interface .function, .interface .getter, .interface .setter,',
|
||||
' .constructor .variable, .constructor .function, .constructor .getter, .constructor .setter {',
|
||||
' background-color: rgb(250, 241, 221);',
|
||||
' }',
|
||||
'',
|
||||
' .parameter.name {',
|
||||
' font-size: large;',
|
||||
' }',
|
||||
'',
|
||||
' .type {',
|
||||
' font-style: italic;',
|
||||
' }',
|
||||
'',
|
||||
' .type:before {',
|
||||
' content: "Type: ";',
|
||||
' }',
|
||||
'',
|
||||
' .abstract > .name:before {',
|
||||
' content: "abstract ";',
|
||||
' }',
|
||||
'',
|
||||
' .private > .name:before {',
|
||||
' content: "private ";',
|
||||
' }',
|
||||
'',
|
||||
' .static > .name:before {',
|
||||
' content: "static ";',
|
||||
' }',
|
||||
'',
|
||||
' .abstract.private > .name:before {',
|
||||
' content: "abstract private ";',
|
||||
' }',
|
||||
'',
|
||||
' .private.static > .name:before {',
|
||||
' content: "static private ";',
|
||||
' }',
|
||||
'',
|
||||
' body:not(.show-private) .private {',
|
||||
' display: none;',
|
||||
' }',
|
||||
' ]]>',
|
||||
' </style>',
|
||||
' <script>',
|
||||
' <![CDATA[',
|
||||
' addEventListener("DOMContentLoaded", function () {',
|
||||
' document.querySelector("#show-private").addEventListener("change", function (event) {',
|
||||
' document.body.className = (event.target.checked ? "show-private" : "");',
|
||||
' }, false);',
|
||||
'',
|
||||
' if (document.querySelector("[href=\\"" + location.hash + "\\"]").offsetHeight === 0) {',
|
||||
' document.querySelector("#show-private").click()',
|
||||
' }',
|
||||
' }, false);',
|
||||
' ]]>',
|
||||
' </script>',
|
||||
' </head>',
|
||||
' <body>',
|
||||
' <section class="namespaces">',
|
||||
' <label><input type="checkbox" id="show-private" />Show private</label>',
|
||||
' <h2>Namespaces</h2>'
|
||||
].concat(writeOverview(3)).concat([
|
||||
' </section>',
|
||||
' <div class="content">',
|
||||
''
|
||||
]).concatMany(namespaceNames.map(function (namespaceName) {
|
||||
var variables = namespaces[namespaceName].members.filter(function (value) {
|
||||
return value instanceof TypeScript.AST.Variable;
|
||||
});
|
||||
|
||||
var functions = namespaces[namespaceName].members.filter(function (value) {
|
||||
return value instanceof TypeScript.AST.Function;
|
||||
});
|
||||
|
||||
var interfaces = namespaces[namespaceName].members.filter(function (value) {
|
||||
return value instanceof TypeScript.AST.Interface;
|
||||
});
|
||||
|
||||
var constructors = namespaces[namespaceName].members.filter(function (value) {
|
||||
return value instanceof TypeScript.AST.Constructor;
|
||||
});
|
||||
|
||||
var result = [
|
||||
' <section>',
|
||||
' <h1 id="' + sanitize(namespaceName) + '">' + sanitize(namespaceName) + '</h1>',
|
||||
''
|
||||
];
|
||||
|
||||
if (variables.length > 0) {
|
||||
result = result.concat([
|
||||
' <section>',
|
||||
' <h2>Variables</h2>'
|
||||
]).concatMany(variables.map(function (value) {
|
||||
return writeVariable(value, 5);
|
||||
})).concat([
|
||||
' </section>',
|
||||
''
|
||||
]);
|
||||
}
|
||||
|
||||
if (functions.length > 0) {
|
||||
result = result.concat([
|
||||
' <section>',
|
||||
' <h2>Free functions</h2>'
|
||||
]).concatMany(functions.map(function (value) {
|
||||
return writeFunction(value, 5);
|
||||
})).concat([
|
||||
' </section>',
|
||||
''
|
||||
]);
|
||||
}
|
||||
|
||||
if (interfaces.length > 0) {
|
||||
result = result.concat([
|
||||
' <section>',
|
||||
' <h2>Interfaces</h2>'
|
||||
]).concatMany(interfaces.map(function (value) {
|
||||
return writeInterface(value, 5);
|
||||
})).concat([
|
||||
' </section>',
|
||||
''
|
||||
]);
|
||||
}
|
||||
|
||||
if (constructors.length > 0) {
|
||||
result = result.concat([
|
||||
' <section>',
|
||||
' <h2>Classes</h2>'
|
||||
]).concatMany(constructors.map(function (value) {
|
||||
return writeConstructor(value, 5);
|
||||
})).concat([
|
||||
' </section>',
|
||||
''
|
||||
]);
|
||||
}
|
||||
|
||||
result = result.concat([
|
||||
' </section>'
|
||||
]);
|
||||
|
||||
return result;
|
||||
})).concat([
|
||||
' </div>',
|
||||
' </body>',
|
||||
'</html>'
|
||||
]).map(function (line) {
|
||||
return new Buffer(line + "\n");
|
||||
}))
|
||||
}));
|
||||
});
|
||||
};
|
||||
@@ -1,88 +0,0 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var childProcess = require("child_process");
|
||||
var stream = require("stream");
|
||||
var util = require("util");
|
||||
|
||||
var Transform = function (transform, flush) {
|
||||
if (!(this instanceof Transform)) {
|
||||
return new Transform(transform, flush);
|
||||
}
|
||||
|
||||
stream.Transform.call(this, { objectMode: true });
|
||||
|
||||
transform = transform || function (chunk, encoding, callback) { callback(); };
|
||||
|
||||
if (transform.length < 3) {
|
||||
this._transform = function (chunk, encoding, callback) {
|
||||
try {
|
||||
transform.call(this, chunk, encoding);
|
||||
callback();
|
||||
}
|
||||
catch (ex) {
|
||||
callback(ex);
|
||||
}
|
||||
};
|
||||
}
|
||||
else {
|
||||
this._transform = transform.bind(this);
|
||||
}
|
||||
|
||||
flush = flush || function (callback) { callback(); };
|
||||
if (flush.length < 1) {
|
||||
this._flush = function (callback) { try { flush.call(this); callback(); } catch (ex) { callback(ex); } };
|
||||
}
|
||||
else {
|
||||
this._flush = flush.bind(this);
|
||||
}
|
||||
};
|
||||
|
||||
util.inherits(Transform, stream.Transform);
|
||||
|
||||
exports.Transform = Transform;
|
||||
|
||||
exports.SingletonChildProcess = function (filename) {
|
||||
var subProcess = null;
|
||||
var rerun = false;
|
||||
|
||||
var spawnSubProcess = function () {
|
||||
subProcess = childProcess.fork(filename);
|
||||
|
||||
subProcess.addListener("exit", function (code, signal) {
|
||||
subProcess = null;
|
||||
|
||||
if (rerun) {
|
||||
spawnSubProcess();
|
||||
}
|
||||
|
||||
rerun = false;
|
||||
});
|
||||
};
|
||||
|
||||
return function () {
|
||||
if (subProcess === null) {
|
||||
spawnSubProcess();
|
||||
}
|
||||
else {
|
||||
rerun = true;
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
|
||||
var Mocha = require("mocha");
|
||||
|
||||
console.log("Running tests...");
|
||||
|
||||
var mocha = new Mocha({
|
||||
ui: "tdd",
|
||||
reporter: "spec"
|
||||
});
|
||||
|
||||
fs.readdirSync("./tests/")
|
||||
.filter(function (filename) { return filename.match(/^test-.*\.js$/) !== null; })
|
||||
.forEach(function (filename) { mocha.addFile(path.resolve("./tests", filename)); });
|
||||
|
||||
mocha.run(function (failures) {
|
||||
if (failures > 0) {
|
||||
throw new Error(failures + " test(s) failed.");
|
||||
}
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,347 +0,0 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
|
||||
var UglifyJS = require("uglify-js");
|
||||
|
||||
var Transform = require("./helpers.js").Transform;
|
||||
|
||||
module.exports = {
|
||||
fixup: function () {
|
||||
var codeFile = null;
|
||||
var sourceMapFile = null;
|
||||
|
||||
return Transform(function (file) {
|
||||
switch (path.extname(file.path)) {
|
||||
case ".js":
|
||||
codeFile = file;
|
||||
break;
|
||||
case ".map":
|
||||
sourceMapFile = file;
|
||||
break;
|
||||
}
|
||||
|
||||
if (codeFile !== null && sourceMapFile !== null) {
|
||||
UglifyJS.base54.reset();
|
||||
|
||||
|
||||
// Parse
|
||||
var root = UglifyJS.parse(codeFile.contents.toString(), {
|
||||
filename: path.basename(codeFile.path),
|
||||
toplevel: null
|
||||
});
|
||||
|
||||
root = root.wrap_enclose(['(typeof module !== "undefined") ? module.exports : (this.libjass = {}):libjass', '(typeof global !== "undefined") ? global : this:global']);
|
||||
|
||||
root.figure_out_scope({ screw_ie8: true });
|
||||
|
||||
|
||||
// Remove some things from the AST
|
||||
var nodesToRemove;
|
||||
|
||||
nodesToRemove = [];
|
||||
|
||||
// 1. All the top-level "var libjass;"
|
||||
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (node instanceof UglifyJS.AST_Var && node.definitions[0].name.name === "libjass") {
|
||||
nodesToRemove.push({ node: node, parent: this.parent().body });
|
||||
}
|
||||
}));
|
||||
|
||||
// 2. All but the first top-level "var __extends = ..."
|
||||
var firstVarExtendsFound = false;
|
||||
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (node instanceof UglifyJS.AST_Var && node.definitions[0].name.name === "__extends") {
|
||||
if (firstVarExtendsFound === false) {
|
||||
firstVarExtendsFound = true;
|
||||
// Remove the check for this.__extends
|
||||
node.definitions[0].value = node.definitions[0].value.right;
|
||||
}
|
||||
else {
|
||||
nodesToRemove.push({ node: node, parent: this.parent().body });
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Repeat because removing some declarations may make others unreferenced
|
||||
for (;;) {
|
||||
// 3. Unreferenced variable and function declarations, and unreferenced terminal function arguments
|
||||
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (node instanceof UglifyJS.AST_SymbolDeclaration && node.unreferenced()) {
|
||||
if (node instanceof UglifyJS.AST_SymbolFunarg) {
|
||||
if (this.parent().argnames.indexOf(node) === this.parent().argnames.length - 1) {
|
||||
nodesToRemove.push({ node: node, parent: this.parent().argnames });
|
||||
}
|
||||
}
|
||||
else if (node instanceof UglifyJS.AST_SymbolVar) {
|
||||
nodesToRemove.push({ node: this.parent(), parent: this.parent(1).definitions });
|
||||
if (this.parent(1).definitions.length === 1) {
|
||||
nodesToRemove.push({ node: this.parent(1), parent: this.parent(2).body });
|
||||
}
|
||||
}
|
||||
else if (node instanceof UglifyJS.AST_SymbolDefun) {
|
||||
nodesToRemove.push({ node: this.parent(), parent: this.parent(1).body });
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
if (nodesToRemove.length === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
nodesToRemove.forEach(function (tuple) {
|
||||
tuple.parent.splice(tuple.parent.indexOf(tuple.node), 1);
|
||||
});
|
||||
|
||||
nodesToRemove = [];
|
||||
|
||||
root.figure_out_scope({ screw_ie8: true });
|
||||
}
|
||||
|
||||
// 4. Rename all function arguments that begin with _ to not have the _.
|
||||
// This converts the TypeScript syntax of declaring private members in the constructor declaration `function Color(private _red: number, ...)` to `function Color(red, ...)`
|
||||
// so that it matches the JSDoc (and looks nicer).
|
||||
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (
|
||||
node instanceof UglifyJS.AST_SymbolFunarg &&
|
||||
node.thedef.name[0] === "_" &&
|
||||
node.thedef.name !== "_super" // Don't rename _super (used in TypeScript's inheritance shim) to super. super is a reserved word.
|
||||
) {
|
||||
node.thedef.name = node.thedef.name.slice(1);
|
||||
}
|
||||
}));
|
||||
|
||||
// Output
|
||||
var firstLicenseHeaderFound = false; // To detect and preserve the first license header
|
||||
|
||||
var output = {
|
||||
source_map: UglifyJS.SourceMap({
|
||||
file: path.basename(sourceMapFile.path),
|
||||
orig: sourceMapFile.contents.toString()
|
||||
}),
|
||||
beautify: true,
|
||||
comments: function (node, comment) {
|
||||
// If this is a license header
|
||||
if (comment.value.indexOf("Copyright") !== -1) {
|
||||
if (!firstLicenseHeaderFound) {
|
||||
firstLicenseHeaderFound = true;
|
||||
}
|
||||
else {
|
||||
// This isn't the first license header. Strip it.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a TypeScript reference comment, strip it.
|
||||
if (comment.value.substr(0, "/<reference path=".length) === "/<reference path=") {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Align multi-line comments correctly.
|
||||
* TypeScript shifts them one space left, and UJS shifts them four spaces left,
|
||||
* so shift each line except the first five spaces right.
|
||||
*/
|
||||
if (comment.type === "comment2") {
|
||||
var lines = comment.value.split("\n");
|
||||
lines = [lines[0]].concat(lines.slice(1).map(function (line) { return ' ' + line; }));
|
||||
comment.value = lines.join('\n');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
var stream = UglifyJS.OutputStream(output);
|
||||
root.print(stream);
|
||||
|
||||
codeFile.contents = Buffer.concat([new Buffer(stream.toString()), new Buffer("\n//# sourceMappingURL="), new Buffer(sourceMapFile.relative)]);
|
||||
this.push(codeFile);
|
||||
|
||||
output.source_map.get()._sources.toArray().forEach(function (filename) {
|
||||
output.source_map.get().setSourceContent(filename, fs.readFileSync(filename, { encoding: "utf8"}));
|
||||
});
|
||||
|
||||
sourceMapFile.contents = new Buffer(output.source_map.toString());
|
||||
this.push(sourceMapFile);
|
||||
|
||||
codeFile = null;
|
||||
sourceMapFile = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
minify: function () {
|
||||
var codeFile = null;
|
||||
var sourceMapFile = null;
|
||||
|
||||
return Transform(function (file) {
|
||||
switch (path.extname(file.path)) {
|
||||
case ".js":
|
||||
codeFile = file;
|
||||
break;
|
||||
case ".map":
|
||||
sourceMapFile = file;
|
||||
break;
|
||||
}
|
||||
|
||||
if (codeFile !== null && sourceMapFile !== null) {
|
||||
UglifyJS.base54.reset();
|
||||
|
||||
|
||||
// Parse
|
||||
var root = null;
|
||||
root = UglifyJS.parse(codeFile.contents.toString(), {
|
||||
filename: path.basename(codeFile.path),
|
||||
toplevel: root
|
||||
});
|
||||
|
||||
root.figure_out_scope({ screw_ie8: true });
|
||||
|
||||
|
||||
// Warnings
|
||||
root.scope_warnings({
|
||||
func_arguments: false
|
||||
});
|
||||
|
||||
|
||||
// Compress
|
||||
var compressor = UglifyJS.Compressor({
|
||||
warnings: true,
|
||||
screw_ie8: true
|
||||
});
|
||||
root = root.transform(compressor);
|
||||
|
||||
|
||||
// Mangle
|
||||
root.figure_out_scope({ screw_ie8: true });
|
||||
root.compute_char_frequency();
|
||||
root.mangle_names({ screw_ie8: true });
|
||||
|
||||
|
||||
// Mangle private members
|
||||
var occurrences = Object.create(null);
|
||||
|
||||
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (
|
||||
node instanceof UglifyJS.AST_PropAccess &&
|
||||
typeof node.property === "string" &&
|
||||
node.property[0] === "_" &&
|
||||
node.property[1] !== "_" && // Doesn't start with two leading underscores
|
||||
node.property !== "_classTag" // webworker serializer uses this property by name, so it shouldn't be changed.
|
||||
) {
|
||||
var occurrence = occurrences[node.property];
|
||||
if (occurrence === undefined) {
|
||||
occurrences[node.property] = 1;
|
||||
}
|
||||
else {
|
||||
occurrences[node.property]++;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
var identifiers = Object.keys(occurrences);
|
||||
identifiers.sort(function (first, second) { return occurrences[second] - occurrences[first]; });
|
||||
|
||||
var generatedIdentifiers = occurrences;
|
||||
|
||||
var validIdentifierCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_0123456789";
|
||||
var toIdentifier = function (index) {
|
||||
var result = validIdentifierCharacters[(index % validIdentifierCharacters.length)];
|
||||
index = (index / validIdentifierCharacters.length) | 0;
|
||||
|
||||
while (index > 0) {
|
||||
index--;
|
||||
result = validIdentifierCharacters[index % validIdentifierCharacters.length] + result;
|
||||
index = (index / validIdentifierCharacters.length) | 0;
|
||||
}
|
||||
|
||||
return "_" + result;
|
||||
};
|
||||
|
||||
identifiers.forEach(function (identifier, index) {
|
||||
generatedIdentifiers[identifier] = toIdentifier(index);
|
||||
});
|
||||
|
||||
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (
|
||||
node instanceof UglifyJS.AST_PropAccess &&
|
||||
typeof node.property === "string" &&
|
||||
node.property in generatedIdentifiers
|
||||
) {
|
||||
node.property = generatedIdentifiers[node.property];
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
// Output
|
||||
var firstLicenseHeaderFound = false; // To detect and preserve the first license header
|
||||
|
||||
var output = {
|
||||
source_map: UglifyJS.SourceMap({
|
||||
file: path.basename(sourceMapFile.path),
|
||||
orig: sourceMapFile.contents.toString()
|
||||
}),
|
||||
comments: {
|
||||
test: function (comment) {
|
||||
if (!firstLicenseHeaderFound && comment.indexOf("Copyright") !== -1) {
|
||||
firstLicenseHeaderFound = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
screw_ie8: true
|
||||
};
|
||||
|
||||
var stream = UglifyJS.OutputStream(output);
|
||||
root.print(stream);
|
||||
|
||||
codeFile.path = codeFile.path.replace(/\.js$/, ".min.js");
|
||||
sourceMapFile.path = sourceMapFile.path.replace(/\.js\.map$/, ".min.js.map");
|
||||
|
||||
codeFile.contents = Buffer.concat([new Buffer(stream.toString()), new Buffer("\n//# sourceMappingURL="), new Buffer(sourceMapFile.relative)]);
|
||||
this.push(codeFile);
|
||||
|
||||
output.source_map.get()._sources.toArray().forEach(function (filename) {
|
||||
output.source_map.get().setSourceContent(filename, fs.readFileSync(filename, { encoding: "utf8"}));
|
||||
});
|
||||
|
||||
sourceMapFile.contents = new Buffer(output.source_map.toString());
|
||||
this.push(sourceMapFile);
|
||||
|
||||
codeFile = null;
|
||||
sourceMapFile = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var originalSymbolUnreferenced = UglifyJS.AST_Symbol.prototype.unreferenced;
|
||||
|
||||
UglifyJS.AST_Symbol.prototype.unreferenced = function () {
|
||||
if (this.start.comments_before.length > 0 && this.start.comments_before[0].value.trim() === "ujs:unreferenced") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return originalSymbolUnreferenced.call(this);
|
||||
};
|
||||
@@ -1,164 +0,0 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
libjass.debugMode = (location.search === "?debug") || (location.search === "?verbose");
|
||||
libjass.verboseMode = (location.search === "?verbose");
|
||||
|
||||
addEventListener("DOMContentLoaded", function () {
|
||||
var debug = function () {
|
||||
if (libjass.debugMode) {
|
||||
console.log.apply(console, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
var video = document.querySelector("#video");
|
||||
|
||||
var videoMetadataLoaded = false;
|
||||
var ass = null;
|
||||
|
||||
var testVideoAndASSLoaded = function () {
|
||||
if (videoMetadataLoaded === false || ass === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Both video metadata and ASS are loaded
|
||||
|
||||
// Create a DefaultRenderer using the video element, the ASS object, and the font map
|
||||
var renderer = new libjass.renderers.DefaultRenderer(video, ass, {
|
||||
fontMap: libjass.renderers.RendererSettings.makeFontMapFromStyleElement(document.querySelector("#font-map"))
|
||||
});
|
||||
|
||||
if (libjass.debugMode) {
|
||||
// Export the renderer for debugging
|
||||
window.renderer = renderer;
|
||||
}
|
||||
|
||||
// Autoplay the video when the renderer is ready
|
||||
renderer.addEventListener("ready", function () {
|
||||
debug("All fonts have been preloaded. Beginning autoplay.");
|
||||
|
||||
video.play();
|
||||
});
|
||||
|
||||
document.querySelector("#enable-disable-subs").addEventListener("change", function (event) {
|
||||
renderer.setEnabled(event.target.checked);
|
||||
}, false);
|
||||
|
||||
// Run when the video/script resolution needs to be changed, either because the user clicked one of the resolution options,
|
||||
// or because they exited full screen and their selection needs to be restored
|
||||
var changeVideoSizeSelection = function (id) {
|
||||
if (typeof id === "undefined") {
|
||||
// Find which option is selected
|
||||
|
||||
[].slice.call(videoSizeSelector.querySelectorAll("input[name='video-size']")).some(function (option) {
|
||||
if (option.checked) {
|
||||
id = option.id;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
if (id === "video-size-video-radio") {
|
||||
// Resize to video resolution
|
||||
|
||||
video.style.width = video.videoWidth + "px";
|
||||
video.style.height = video.videoHeight + "px";
|
||||
renderer.resize(video.videoWidth, video.videoHeight);
|
||||
}
|
||||
|
||||
else if (id === "video-size-script-radio") {
|
||||
// Resize to script resolution
|
||||
|
||||
video.style.width = ass.properties.resolutionX + "px";
|
||||
video.style.height = ass.properties.resolutionY + "px";
|
||||
renderer.resize(ass.properties.resolutionX, ass.properties.resolutionY);
|
||||
}
|
||||
};
|
||||
|
||||
var videoSizeSelector = document.querySelector("#video-size-selector");
|
||||
videoSizeSelector.addEventListener("change", function (event) { changeVideoSizeSelection(event.target.id); }, false);
|
||||
|
||||
// When video exits full screen, restore the size according to the resolution option that's selected
|
||||
renderer.addEventListener("fullScreenChange", function (newState) {
|
||||
if (newState === false) {
|
||||
changeVideoSizeSelection();
|
||||
}
|
||||
});
|
||||
|
||||
// Set the resolution to whatever's selected by default
|
||||
changeVideoSizeSelection();
|
||||
};
|
||||
|
||||
(function (onVideoMetadataLoaded) {
|
||||
if (video.readyState < HTMLMediaElement.HAVE_METADATA) {
|
||||
// Video metadata isn't available yet. Register an event handler for it.
|
||||
video.addEventListener("loadedmetadata", onVideoMetadataLoaded, false);
|
||||
}
|
||||
else {
|
||||
// Video metadata is already available.
|
||||
onVideoMetadataLoaded();
|
||||
}
|
||||
})(function () {
|
||||
debug("Video metadata loaded.");
|
||||
|
||||
videoMetadataLoaded = true;
|
||||
|
||||
// Prepare the "Video resolution" option label
|
||||
document.querySelector("#video-resolution-label-width").appendChild(document.createTextNode(video.videoWidth));
|
||||
document.querySelector("#video-resolution-label-height").appendChild(document.createTextNode(video.videoHeight));
|
||||
|
||||
// Test if everything is loaded
|
||||
testVideoAndASSLoaded();
|
||||
});
|
||||
|
||||
// Find the ASS or SRT track and load it
|
||||
var track = document.querySelector("#video > track[data-format='ass'], #video > track[data-format='srt']");
|
||||
var subsRequest = new XMLHttpRequest();
|
||||
subsRequest.open("GET", track.src || track.getAttribute("src"), true);
|
||||
subsRequest.addEventListener("load", function () {
|
||||
debug("Script received.");
|
||||
|
||||
if (libjass.debugMode) {
|
||||
console.time("Parsing ASS took");
|
||||
}
|
||||
|
||||
// Parse the response string into an ASS object
|
||||
ass = libjass.ASS.fromString(subsRequest.responseText, libjass.Format[track.getAttribute("data-format").toUpperCase()]);
|
||||
|
||||
if (libjass.debugMode) {
|
||||
console.timeEnd("Parsing ASS took");
|
||||
|
||||
// Export the ASS object for debugging
|
||||
window.ass = ass;
|
||||
}
|
||||
|
||||
// Prepare the "Script resolution" option label
|
||||
document.querySelector("#script-resolution-label-width").appendChild(document.createTextNode(ass.properties.resolutionX));
|
||||
document.querySelector("#script-resolution-label-height").appendChild(document.createTextNode(ass.properties.resolutionY));
|
||||
|
||||
// Test if everything is loaded
|
||||
testVideoAndASSLoaded();
|
||||
}, false);
|
||||
subsRequest.send(null);
|
||||
}, false);
|
||||
+9
-51
@@ -1,58 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
libjass
|
||||
|
||||
https://github.com/Arnavion/libjass
|
||||
|
||||
Copyright 2013 Arnav Singh
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
|
||||
<head>
|
||||
<title>>2012 >Streaman Animu</title>
|
||||
<link rel="stylesheet" href="libjass.css" />
|
||||
<link rel="stylesheet" href="index.css" />
|
||||
<link rel="stylesheet" href="fonts.css" id="font-map" />
|
||||
|
||||
<!-- Unminified file -->
|
||||
<script src="libjass.js" />
|
||||
|
||||
<!-- Minified file -->
|
||||
<!--
|
||||
<script src="libjass.min.js" />
|
||||
-->
|
||||
|
||||
<script src="index.js" />
|
||||
<title>libjass</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Foo</h1>
|
||||
<h2>Bar</h2>
|
||||
<video id="video" src="/video/gc-01.webm" controls="">
|
||||
<track src="/video/gc-01.ass" kind="metadata" data-format="ass" />
|
||||
</video>
|
||||
<form class="settings-form">
|
||||
<fieldset>
|
||||
<legend>Video size</legend>
|
||||
<div id="video-size-selector">
|
||||
<input type="radio" name="video-size" id="video-size-video-radio" checked="checked" /><label for="video-size-video-radio">Video resolution <span id="video-resolution-label-width" />x<span id="video-resolution-label-height" /></label>
|
||||
<input type="radio" name="video-size" id="video-size-script-radio" /><label for="video-size-script-radio">Script resolution <span id="script-resolution-label-width" />x<span id="script-resolution-label-height" /></label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Subtitles</legend>
|
||||
<input type="checkbox" id="enable-disable-subs" checked="checked" /><label for="enable-disable-subs">Subtitles</label>
|
||||
</fieldset>
|
||||
</form>
|
||||
<h1>libjass - Renders ASS subs in the browser</h1>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://github.com/Arnavion/libjass">GitHub</a></li>
|
||||
<li>IRC channel - #libjass on irc.rizon.net</li>
|
||||
<li><a href="https://arnavion.github.io/libjass/api.xhtml">API documentation</a></li>
|
||||
<li><a href="https://arnavion.github.io/libjass/demo/">Demo</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
///<reference path="utility.ts" />
|
||||
///<reference path="web-worker.ts" />
|
||||
///<reference path="parts.ts" />
|
||||
///<reference path="parser.ts" />
|
||||
///<reference path="renderers-clocks.ts" />
|
||||
///<reference path="renderers-null.ts" />
|
||||
///<reference path="renderers-web.ts" />
|
||||
///<reference path="renderers-default.ts" />
|
||||
///<reference path="types.ts" />
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"name": "libjass",
|
||||
"version": "0.8.0",
|
||||
"description": "A library to render ASS subtitles on HTML5 video in the browser.",
|
||||
"keywords": ["browser", "html5", "subtitles"],
|
||||
"homepage": "https://github.com/Arnavion/libjass",
|
||||
"bugs": {
|
||||
"url": "https://github.com/Arnavion/libjass/issues"
|
||||
},
|
||||
"licenses": [{
|
||||
"type": "Apache-2.0",
|
||||
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
|
||||
}],
|
||||
"maintainers": [{
|
||||
"name": "Arnav Singh",
|
||||
"email": "arnavion@gmail.com"
|
||||
}],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Arnavion/libjass"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "gulp clean default",
|
||||
"test": "node ./gulplib/test-runner.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"gulp": "latest",
|
||||
"mocha": "1.21.x",
|
||||
"typescript": "1.0.1",
|
||||
"uglify-js": "2.x >=2.4.14",
|
||||
"vinyl": "latest"
|
||||
}
|
||||
}
|
||||
@@ -1,445 +0,0 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
///<reference path="libjass.ts" />
|
||||
|
||||
module libjass.renderers {
|
||||
/**
|
||||
* A mixin class that represents an event source.
|
||||
*
|
||||
* @template T
|
||||
*/
|
||||
export class EventSource<T> {
|
||||
_eventListeners: Map<T, Function[]>;
|
||||
|
||||
/**
|
||||
* Add a listener for the given event.
|
||||
*
|
||||
* @param {!T} type The type of event to attach the listener for
|
||||
* @param {!Function} listener The listener
|
||||
*/
|
||||
addEventListener(type: T, listener: Function): void {
|
||||
var listeners = this._eventListeners.get(type);
|
||||
|
||||
if (listeners === undefined) {
|
||||
this._eventListeners.set(type, listeners = []);
|
||||
}
|
||||
|
||||
listeners.push(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!T} type The type of event to dispatch
|
||||
* @param {!Array.<*>} args Arguments for the listeners of the event
|
||||
*/
|
||||
_dispatchEvent(type: T, args: Object[]): void {
|
||||
var listeners = this._eventListeners.get(type);
|
||||
if (listeners !== undefined) {
|
||||
listeners.forEach((listener: Function) => {
|
||||
listener.apply(this, args);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export enum ClockEvent {
|
||||
Play,
|
||||
Tick,
|
||||
Pause,
|
||||
Stop,
|
||||
}
|
||||
|
||||
export interface Clock extends EventSource<ClockEvent> {
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
currentTime: number;
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
enabled: boolean;
|
||||
|
||||
/**
|
||||
* Enable the clock.
|
||||
*
|
||||
* @return {boolean} True if the clock is now enabled, false if it was already enabled.
|
||||
*/
|
||||
enable(): boolean;
|
||||
|
||||
/**
|
||||
* Disable the clock.
|
||||
*
|
||||
* @return {boolean} True if the clock is now disabled, false if it was already disabled.
|
||||
*/
|
||||
disable(): boolean;
|
||||
|
||||
/**
|
||||
* Toggle the clock.
|
||||
*/
|
||||
toggle(): void;
|
||||
|
||||
/**
|
||||
* Enable or disable the clock.
|
||||
*
|
||||
* @param {boolean} enabled If true, the clock is enabled, otherwise it's disabled.
|
||||
* @return {boolean} True if the clock is now in the given state, false if it was already in that state.
|
||||
*/
|
||||
setEnabled(enabled: boolean): boolean;
|
||||
|
||||
// EventSource members
|
||||
addEventListener: (type: ClockEvent, listener: Function) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of libjass.renderers.Clock that allows user script to manually trigger play, pause and timeUpdate events.
|
||||
*/
|
||||
export class ManualClock implements Clock {
|
||||
private _currentTime: number = -1;
|
||||
private _enabled: boolean = true;
|
||||
|
||||
/**
|
||||
* Trigger a ClockEvent.Play
|
||||
*/
|
||||
play(): void {
|
||||
this._dispatchEvent(ClockEvent.Play, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a ClockEvent.Tick
|
||||
*
|
||||
* @param {number} currentTime
|
||||
*/
|
||||
tick(currentTime: number): void {
|
||||
this._currentTime = currentTime;
|
||||
this._dispatchEvent(ClockEvent.Tick, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a ClockEvent.Pause
|
||||
*/
|
||||
pause(): void {
|
||||
this._dispatchEvent(ClockEvent.Pause, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a ClockEvent.Stop
|
||||
*/
|
||||
stop(): void {
|
||||
this._dispatchEvent(ClockEvent.Stop, []);
|
||||
}
|
||||
|
||||
// Clock members
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
get currentTime(): number {
|
||||
return this._currentTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
get enabled(): boolean {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the clock.
|
||||
*
|
||||
* @return {boolean} True if the clock is now enabled, false if it was already enabled.
|
||||
*/
|
||||
enable(): boolean {
|
||||
if (this._enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._enabled = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the clock.
|
||||
*
|
||||
* @return {boolean} True if the clock is now disabled, false if it was already disabled.
|
||||
*/
|
||||
disable(): boolean {
|
||||
if (!this._enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._enabled = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the clock.
|
||||
*/
|
||||
toggle(): void {
|
||||
if (this._enabled) {
|
||||
this.disable();
|
||||
}
|
||||
else {
|
||||
this.enable();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable the clock.
|
||||
*
|
||||
* @param {boolean} enabled If true, the clock is enabled, otherwise it's disabled.
|
||||
* @return {boolean} True if the clock is now in the given state, false if it was already in that state.
|
||||
*/
|
||||
setEnabled(enabled: boolean): boolean {
|
||||
if (enabled) {
|
||||
return this.enable();
|
||||
}
|
||||
else {
|
||||
return this.disable();
|
||||
}
|
||||
}
|
||||
|
||||
// EventSource members
|
||||
_eventListeners: Map<ClockEvent, Function[]> = new Map<ClockEvent, Function[]>();
|
||||
addEventListener: (type: ClockEvent, listener: Function) => void;
|
||||
_dispatchEvent: (type: ClockEvent, args: Object[]) => void;
|
||||
}
|
||||
mixin(ManualClock, [EventSource]);
|
||||
|
||||
enum VideoState {
|
||||
Playing = 0,
|
||||
Paused = 1,
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of libjass.renderers.Clock that generates play, pause and timeUpdate events according to the state of a <video> element.
|
||||
*
|
||||
* @param {!HTMLVideoElement} video
|
||||
*/
|
||||
export class VideoClock implements Clock {
|
||||
private _currentTime: number = -1;
|
||||
|
||||
private _enabled: boolean = true;
|
||||
|
||||
private _videoState: VideoState;
|
||||
|
||||
private _nextAnimationFrameRequestId: number = null;
|
||||
|
||||
constructor(private _video: HTMLVideoElement) {
|
||||
this._video.addEventListener("playing", () => this._onVideoPlaying(), false);
|
||||
this._video.addEventListener("pause", () => this._onVideoPause(), false);
|
||||
this._video.addEventListener("seeking", () => this._onVideoSeeking(), false);
|
||||
}
|
||||
|
||||
// Clock members
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
get currentTime(): number {
|
||||
return this._currentTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
get enabled(): boolean {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the clock.
|
||||
*
|
||||
* @return {boolean} True if the clock is now enabled, false if it was already enabled.
|
||||
*/
|
||||
enable(): boolean {
|
||||
if (this._enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._videoState !== VideoState.Paused) {
|
||||
if (libjass.debugMode) {
|
||||
console.warn("VideoClock.enable: Abnormal state detected. VideoClock._videoState should have been VideoState.Paused");
|
||||
}
|
||||
}
|
||||
|
||||
this._enabled = true;
|
||||
|
||||
this._startTicking();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the clock.
|
||||
*
|
||||
* @return {boolean} True if the clock is now disabled, false if it was already disabled.
|
||||
*/
|
||||
disable(): boolean {
|
||||
if (!this._enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._videoState === VideoState.Playing) {
|
||||
this._videoState = VideoState.Paused;
|
||||
this._dispatchEvent(ClockEvent.Pause, []);
|
||||
}
|
||||
|
||||
this._enabled = false;
|
||||
|
||||
this._dispatchEvent(ClockEvent.Stop, []);
|
||||
|
||||
this._stopTicking();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the clock.
|
||||
*/
|
||||
toggle(): void {
|
||||
if (this._enabled) {
|
||||
this.disable();
|
||||
}
|
||||
else {
|
||||
this.enable();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable the clock.
|
||||
*
|
||||
* @param {boolean} enabled If true, the clock is enabled, otherwise it's disabled.
|
||||
* @return {boolean} True if the clock is now in the given state, false if it was already in that state.
|
||||
*/
|
||||
setEnabled(enabled: boolean): boolean {
|
||||
if (enabled) {
|
||||
return this.enable();
|
||||
}
|
||||
else {
|
||||
return this.disable();
|
||||
}
|
||||
}
|
||||
|
||||
private _onVideoPlaying(): void {
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._videoState === VideoState.Playing) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._startTicking();
|
||||
}
|
||||
|
||||
private _onVideoPause(): void {
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._videoState === VideoState.Paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._videoState = VideoState.Paused;
|
||||
|
||||
this._dispatchEvent(ClockEvent.Pause, []);
|
||||
|
||||
if (this._nextAnimationFrameRequestId === null) {
|
||||
if (libjass.debugMode) {
|
||||
console.warn("VideoClock._onVideoPause: Abnormal state detected. VideoClock._nextAnimationFrameRequestId should not have been null");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this._stopTicking();
|
||||
}
|
||||
|
||||
private _onVideoSeeking(): void {
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._videoState === VideoState.Playing) {
|
||||
this._videoState = VideoState.Paused;
|
||||
this._dispatchEvent(ClockEvent.Pause, []);
|
||||
}
|
||||
|
||||
if (this._currentTime === this._video.currentTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._currentTime = this._video.currentTime;
|
||||
|
||||
this._dispatchEvent(ClockEvent.Tick, []);
|
||||
}
|
||||
|
||||
private _onTimerTick(): void {
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._videoState === VideoState.Playing) {
|
||||
if (this._currentTime !== this._video.currentTime) {
|
||||
this._currentTime = this._video.currentTime;
|
||||
this._dispatchEvent(ClockEvent.Tick, []);
|
||||
}
|
||||
else {
|
||||
this._videoState = VideoState.Paused;
|
||||
this._dispatchEvent(ClockEvent.Pause, []);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this._currentTime !== this._video.currentTime) {
|
||||
this._currentTime = this._video.currentTime;
|
||||
this._videoState = VideoState.Playing;
|
||||
this._dispatchEvent(ClockEvent.Play, []);
|
||||
this._dispatchEvent(ClockEvent.Tick, []);
|
||||
}
|
||||
}
|
||||
|
||||
this._nextAnimationFrameRequestId = requestAnimationFrame(() => this._onTimerTick());
|
||||
}
|
||||
|
||||
private _startTicking(): void {
|
||||
if (this._nextAnimationFrameRequestId === null) {
|
||||
this._nextAnimationFrameRequestId = requestAnimationFrame(() => this._onTimerTick());
|
||||
}
|
||||
}
|
||||
|
||||
private _stopTicking(): void {
|
||||
if (this._nextAnimationFrameRequestId !== null) {
|
||||
cancelAnimationFrame(this._nextAnimationFrameRequestId);
|
||||
this._nextAnimationFrameRequestId = null;
|
||||
}
|
||||
}
|
||||
|
||||
// EventSource members
|
||||
_eventListeners: Map<ClockEvent, Function[]> = new Map<ClockEvent, Function[]>();
|
||||
addEventListener: (type: ClockEvent, listener: Function) => void;
|
||||
_dispatchEvent: (type: ClockEvent, args: Object[]) => void;
|
||||
}
|
||||
mixin(VideoClock, [EventSource]);
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
///<reference path="libjass.ts" />
|
||||
|
||||
interface Document {
|
||||
fullscreenElement: Element;
|
||||
mozFullScreenElement: Element;
|
||||
webkitFullscreenElement: Element;
|
||||
}
|
||||
|
||||
module libjass.renderers {
|
||||
/**
|
||||
* A default renderer implementation.
|
||||
*
|
||||
* @param {!HTMLVideoElement} video
|
||||
* @param {!libjass.ASS} ass
|
||||
* @param {!libjass.renderers.RendererSettings} settings
|
||||
*/
|
||||
export class DefaultRenderer extends WebRenderer {
|
||||
private _videoIsFullScreen: boolean = false;
|
||||
|
||||
constructor(private _video: HTMLVideoElement, ass: ASS, settings: RendererSettings) {
|
||||
super(ass, new VideoClock(_video), settings, document.createElement("div"));
|
||||
|
||||
this._video.parentElement.replaceChild(this.libjassSubsWrapper, this._video);
|
||||
this.libjassSubsWrapper.insertBefore(this._video, this.libjassSubsWrapper.firstElementChild);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
resizeVideo(width: number, height: number): void {
|
||||
console.warn("`DefaultRenderer.resizeVideo(width, height)` has been deprecated. Use `DefaultRenderer.resize(width, height)` instead.");
|
||||
this.resize(width, height);
|
||||
}
|
||||
|
||||
_ready(): void {
|
||||
document.addEventListener("mozfullscreenchange", event => this._onFullScreenChange(document.mozFullScreenElement), false);
|
||||
document.addEventListener("webkitfullscreenchange", event => this._onFullScreenChange(document.webkitFullscreenElement), false);
|
||||
document.addEventListener("fullscreenchange", event => this._onFullScreenChange(document.fullscreenElement), false);
|
||||
|
||||
this.resize(this._video.offsetWidth, this._video.offsetHeight);
|
||||
|
||||
super._ready();
|
||||
}
|
||||
|
||||
private _onFullScreenChange(fullScreenElement: Element) {
|
||||
if (fullScreenElement === undefined) {
|
||||
fullScreenElement = document.msFullscreenElement;
|
||||
}
|
||||
|
||||
if (fullScreenElement === this._video) {
|
||||
this.libjassSubsWrapper.classList.add("libjass-full-screen");
|
||||
|
||||
this.resize(screen.width, screen.height);
|
||||
|
||||
this._videoIsFullScreen = true;
|
||||
|
||||
this._dispatchEvent("fullScreenChange", [this._videoIsFullScreen]);
|
||||
}
|
||||
else if (fullScreenElement === null && this._videoIsFullScreen) {
|
||||
this.libjassSubsWrapper.classList.remove("libjass-full-screen");
|
||||
|
||||
this._videoIsFullScreen = false;
|
||||
|
||||
this._dispatchEvent("fullScreenChange", [this._videoIsFullScreen]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,280 +0,0 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
///<reference path="libjass.ts" />
|
||||
|
||||
module libjass.renderers {
|
||||
/**
|
||||
* A renderer implementation that doesn't output anything.
|
||||
*
|
||||
* @param {!libjass.ASS} ass
|
||||
* @param {!libjass.renderers.Clock} clock
|
||||
* @param {!libjass.renderers.RendererSettings} settings
|
||||
*/
|
||||
export class NullRenderer {
|
||||
private static _lastRendererId = -1;
|
||||
|
||||
private _id: number;
|
||||
|
||||
private _settings: RendererSettings;
|
||||
|
||||
constructor(private _ass: ASS, private _clock: Clock, settings: RendererSettings) {
|
||||
this._id = ++NullRenderer._lastRendererId;
|
||||
|
||||
this._settings = RendererSettings.from(settings);
|
||||
|
||||
this._clock.addEventListener(ClockEvent.Play, () => this._onClockPlay());
|
||||
this._clock.addEventListener(ClockEvent.Tick, () => this._onClockTick());
|
||||
this._clock.addEventListener(ClockEvent.Pause, () => this._onClockPause());
|
||||
this._clock.addEventListener(ClockEvent.Stop, () => this._onClockStop());
|
||||
}
|
||||
|
||||
/**
|
||||
* The unique ID of this renderer. Auto-generated.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get id(): number {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {!libjass.ASS}
|
||||
*/
|
||||
get ass(): ASS {
|
||||
return this._ass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {!libjass.renderers.Clock}
|
||||
*/
|
||||
get clock(): Clock {
|
||||
return this._clock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {!libjass.renderers.RendererSettings}
|
||||
*/
|
||||
get settings(): RendererSettings {
|
||||
return this._settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-render a dialogue. This is a no-op for this type.
|
||||
*
|
||||
* @param {!libjass.Dialogue} dialogue
|
||||
*/
|
||||
preRender(dialogue: Dialogue): void { }
|
||||
|
||||
/**
|
||||
* Draw a dialogue. This is a no-op for this type.
|
||||
*
|
||||
* @param {!libjass.Dialogue} dialogue
|
||||
*/
|
||||
draw(dialogue: Dialogue): void { }
|
||||
|
||||
/**
|
||||
* Enable the renderer.
|
||||
*
|
||||
* @return {boolean} True if the renderer is now enabled, false if it was already enabled.
|
||||
*/
|
||||
enable(): boolean {
|
||||
return this._clock.enable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the renderer.
|
||||
*
|
||||
* @return {boolean} True if the renderer is now disabled, false if it was already disabled.
|
||||
*/
|
||||
disable(): boolean {
|
||||
return this._clock.disable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the renderer.
|
||||
*/
|
||||
toggle(): void {
|
||||
this._clock.toggle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable the renderer.
|
||||
*
|
||||
* @param {boolean} enabled If true, the renderer is enabled, otherwise it's disabled.
|
||||
* @return {boolean} True if the renderer is now in the given state, false if it was already in that state.
|
||||
*/
|
||||
setEnabled(enabled: boolean): boolean {
|
||||
return this._clock.setEnabled(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
get enabled(): boolean {
|
||||
return this._clock.enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs when the clock is enabled, or starts playing, or is resumed from pause.
|
||||
*/
|
||||
_onClockPlay(): void {
|
||||
if (libjass.verboseMode) {
|
||||
console.log("NullRenderer._onClockPlay");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs when the clock's current time changed. This might be a result of either regular playback or seeking.
|
||||
*/
|
||||
_onClockTick(): void {
|
||||
var currentTime = this._clock.currentTime;
|
||||
|
||||
if (libjass.verboseMode) {
|
||||
console.log("NullRenderer._onClockTick: currentTime = " + currentTime);
|
||||
}
|
||||
|
||||
for (var i = 0; i < this._ass.dialogues.length; i++) {
|
||||
var dialogue = this._ass.dialogues[i];
|
||||
|
||||
if (dialogue.end > currentTime) {
|
||||
if (dialogue.start <= currentTime) {
|
||||
// This dialogue is visible right now. Draw it.
|
||||
this.draw(dialogue);
|
||||
}
|
||||
else if (dialogue.start <= (currentTime + this._settings.preRenderTime)) {
|
||||
// This dialogue will be visible soon. Pre-render it.
|
||||
this.preRender(dialogue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs when the clock is paused.
|
||||
*/
|
||||
_onClockPause(): void {
|
||||
if (libjass.verboseMode) {
|
||||
console.log("NullRenderer._onClockPause");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs when the clock is disabled.
|
||||
*/
|
||||
_onClockStop(): void {
|
||||
if (libjass.verboseMode) {
|
||||
console.log("NullRenderer._onClockStop");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings for the renderer.
|
||||
*/
|
||||
export class RendererSettings {
|
||||
/**
|
||||
* A map of font name to one or more URLs of that font. If provided, the fonts in this map are pre-loaded by the WebRenderer when it's created.
|
||||
*
|
||||
* If you have a <style> or <link> element on the page containing @font-face rules, you can use the RendererSettings.makeFontMapFromStyleElement() convenience method to create a font map.
|
||||
*
|
||||
* @type {!Map.<string, !Array.<string>>}
|
||||
*/
|
||||
fontMap: Map<string, string[]>;
|
||||
|
||||
/**
|
||||
* Subtitles will be pre-rendered for this amount of time (seconds).
|
||||
*
|
||||
* Defaults to 5.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
preRenderTime: number;
|
||||
|
||||
/**
|
||||
* Subtitle outlines will be rendered in full detail. When false, the value of blur is used to draw less outlines for better performance and (hopefully) similar output.
|
||||
*
|
||||
* Defaults to false.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
preciseOutlines: boolean;
|
||||
|
||||
/**
|
||||
* A convenience method to create a font map from a <style> or <link> element that contains @font-face rules.
|
||||
*
|
||||
* @param {!LinkStyle} linkStyle
|
||||
* @return {!Map.<string, !Array.<string>>}
|
||||
*/
|
||||
static makeFontMapFromStyleElement(linkStyle: LinkStyle): Map<string, string[]> {
|
||||
var map = new Map<string, string[]>();
|
||||
|
||||
var styleSheet = <CSSStyleSheet>linkStyle.sheet;
|
||||
var rules: CSSFontFaceRule[] = Array.prototype.filter.call(styleSheet.cssRules, (rule: CSSRule) => rule.type === CSSRule.FONT_FACE_RULE);
|
||||
rules.forEach((rule: CSSFontFaceRule) => {
|
||||
var src = rule.style.getPropertyValue("src");
|
||||
var urls: string[] = [];
|
||||
|
||||
if (!src) {
|
||||
src = rule.cssText.split("\n")
|
||||
.map((line: string) => line.match(/src: ([^;]+);/))
|
||||
.filter((matches: string[]) => matches !== null)
|
||||
.map((matches: string[]) => matches[1])[0];
|
||||
}
|
||||
|
||||
urls = src.split(/,\s*/).map((url: string) => url.match(/^url\((.+)\)$/)[1]);
|
||||
|
||||
if (urls.length > 0) {
|
||||
var name = RendererSettings._stripQuotes(rule.style.getPropertyValue("font-family"));
|
||||
var existingList = map.get(name);
|
||||
if (existingList === undefined) {
|
||||
existingList = [];
|
||||
map.set(name, existingList);
|
||||
}
|
||||
existingList.unshift.apply(existingList, urls.map(RendererSettings._stripQuotes));
|
||||
}
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an arbitrary object into a RendererSettings object.
|
||||
*
|
||||
* @param {!*} object
|
||||
* @return {!libjass.renderers.RendererSettings}
|
||||
*/
|
||||
static from(object: any): RendererSettings {
|
||||
return RendererSettings._from(object.fontMap, object.preRenderTime, object.preciseOutlines);
|
||||
}
|
||||
|
||||
private static _from(fontMap: Map<string, string[]> = null, preRenderTime: number = 5, preciseOutlines: boolean = false): RendererSettings {
|
||||
var result = new RendererSettings();
|
||||
result.fontMap = fontMap;
|
||||
result.preRenderTime = preRenderTime;
|
||||
result.preciseOutlines = preciseOutlines;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static _stripQuotes(str: string): string {
|
||||
return str.match(/^["']?(.*?)["']?$/)[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
-1459
File diff suppressed because it is too large
Load Diff
-406
@@ -1,406 +0,0 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
Array.prototype.sum = function () {
|
||||
return this.reduce(function (previous, current) {
|
||||
return previous + current;
|
||||
}, 0);
|
||||
};
|
||||
|
||||
var rootSuites = [];
|
||||
var suite = null;
|
||||
var setup = null;
|
||||
var test = null;
|
||||
|
||||
var require = function (name) {
|
||||
if (name === "../libjass.js") {
|
||||
return libjass;
|
||||
}
|
||||
else if (name === "assert") {
|
||||
return assert;
|
||||
}
|
||||
else if (name === "./parser-test.js") {
|
||||
return parserTest;
|
||||
}
|
||||
else {
|
||||
throw new Error("Cannot require unmocked module: " + name);
|
||||
}
|
||||
};
|
||||
|
||||
(function () {
|
||||
var Suite = function (name, parent) {
|
||||
this.name = name;
|
||||
|
||||
this.setup = function () { };
|
||||
|
||||
this.children = [];
|
||||
|
||||
this.parent = parent;
|
||||
|
||||
if (this.parent !== null) {
|
||||
this.parent.children.push(this);
|
||||
this.number = this.parent.number + "." + this.parent.children.length;
|
||||
}
|
||||
|
||||
else {
|
||||
rootSuites.push(this);
|
||||
this.number = String(rootSuites.length);
|
||||
}
|
||||
};
|
||||
|
||||
Object.defineProperties(Suite.prototype, {
|
||||
total: { get: function () {
|
||||
return this.children.reduce(function (previous, current) {
|
||||
if (current instanceof Suite) {
|
||||
return previous + current.total;
|
||||
}
|
||||
else {
|
||||
return previous + 1;
|
||||
}
|
||||
}, 0);
|
||||
}, enumerable: true },
|
||||
|
||||
passed: { get: function () {
|
||||
return this.children.reduce(function (previous, current) {
|
||||
if (current instanceof Suite) {
|
||||
return previous + current.passed;
|
||||
}
|
||||
else {
|
||||
return previous + ((current.passed === true) ? 1 : 0);
|
||||
}
|
||||
}, 0);
|
||||
}, enumerable: true },
|
||||
|
||||
failed: { get: function () {
|
||||
return this.children.reduce(function (previous, current) {
|
||||
if (current instanceof Suite) {
|
||||
return previous + current.failed;
|
||||
}
|
||||
else {
|
||||
return previous + ((current.passed === false) ? 1 : 0);
|
||||
}
|
||||
}, 0);
|
||||
}, enumerable: true },
|
||||
});
|
||||
|
||||
Suite.prototype.run = function (logger, done) {
|
||||
var _this = this;
|
||||
|
||||
logger.beginSuite(this);
|
||||
|
||||
this.setup();
|
||||
|
||||
var remainingChildren = this.children.slice();
|
||||
var runChild = function () {
|
||||
if (remainingChildren.length > 0) {
|
||||
var nextChild = remainingChildren.shift();
|
||||
nextChild.run(logger, runChild);
|
||||
}
|
||||
else {
|
||||
logger.endSuite(_this);
|
||||
done();
|
||||
}
|
||||
};
|
||||
runChild();
|
||||
};
|
||||
|
||||
var Test = function (description, body, parent) {
|
||||
this.description = description;
|
||||
this.body = body;
|
||||
|
||||
parent.children.push(this);
|
||||
|
||||
this.number = parent.number + "." + parent.children.length;
|
||||
|
||||
this.passed = null;
|
||||
this.exception = null;
|
||||
};
|
||||
|
||||
Test.prototype.run = function (logger, done) {
|
||||
if (this.body) {
|
||||
this.passed = false;
|
||||
|
||||
if (this.body.length === 0) {
|
||||
try {
|
||||
this.body();
|
||||
|
||||
this.passed = true;
|
||||
}
|
||||
catch (testException) {
|
||||
this.exception = testException;
|
||||
}
|
||||
|
||||
logger.writeTest(this);
|
||||
|
||||
done();
|
||||
}
|
||||
else {
|
||||
try {
|
||||
var _this = this;
|
||||
this.body(function (testException) {
|
||||
if (testException !== undefined && testException !== null) {
|
||||
_this.exception = testException;
|
||||
}
|
||||
else {
|
||||
_this.passed = true;
|
||||
}
|
||||
|
||||
logger.writeTest(_this);
|
||||
|
||||
done();
|
||||
});
|
||||
}
|
||||
catch (testException) {
|
||||
this.exception = testException;
|
||||
|
||||
logger.writeTest(this);
|
||||
|
||||
done();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var currentSuite = null;
|
||||
|
||||
suite = function (name, body) {
|
||||
var newSuite = new Suite(name, currentSuite);
|
||||
|
||||
currentSuite = newSuite;
|
||||
body();
|
||||
currentSuite = newSuite.parent;
|
||||
|
||||
return newSuite;
|
||||
};
|
||||
|
||||
setup = function (body) {
|
||||
currentSuite.setup = body;
|
||||
}
|
||||
|
||||
test = function (description, body) {
|
||||
return new Test(description, body, currentSuite);
|
||||
};
|
||||
})();
|
||||
|
||||
var assert = new function () {
|
||||
this.deepEqual = function (actual, expected) {
|
||||
if (expected === undefined && actual === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (expected === undefined) {
|
||||
throw new Error("Expected undefined but got [" + actual + "]");
|
||||
}
|
||||
|
||||
if (actual === undefined) {
|
||||
throw new Error("Expected [" + expected + "] but got undefined")
|
||||
}
|
||||
|
||||
if (expected === null && actual === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (expected === null) {
|
||||
throw new Error("Expected null but got [" + actual + "]");
|
||||
}
|
||||
|
||||
if (actual === null) {
|
||||
throw new Error("Expected [" + expected + "] but got null")
|
||||
}
|
||||
|
||||
if (expected.constructor !== actual.constructor && actual.constructor !== undefined) {
|
||||
throw new Error("Expected value of type [" + expected.constructor.name + "] but got value of type [" + actual.constructor.name + "].");
|
||||
}
|
||||
|
||||
switch(typeof expected) {
|
||||
case "boolean":
|
||||
case "number":
|
||||
case "string":
|
||||
if (expected !== actual) {
|
||||
throw new Error("Expected [" + expected + "] but got [" + actual + "]");
|
||||
}
|
||||
break;
|
||||
|
||||
case "object":
|
||||
if (Array.isArray(expected)) {
|
||||
if (!Array.isArray(actual)) {
|
||||
throw new Error("Expected an array but got [" + actual + "]");
|
||||
}
|
||||
|
||||
if (expected.length !== actual.length) {
|
||||
throw new Error("Expected an array of length " + expected.length + " but got array of length " + actual.length);
|
||||
}
|
||||
|
||||
var i;
|
||||
for (i = 0; i < expected.length; i++) {
|
||||
assert.deepEqual(actual[i], expected[i]);
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
var expectedProperties = Object.keys(expected);
|
||||
var actualProperties = Object.keys(actual);
|
||||
expectedProperties.sort();
|
||||
actualProperties.sort();
|
||||
|
||||
assert.deepEqual(actualProperties, expectedProperties);
|
||||
|
||||
expectedProperties.forEach(function (property) {
|
||||
assert.deepEqual(actual[property], expected[property]);
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error("Unrecognized type: " + typeof expected);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var Logger = function (outputDiv) {
|
||||
var testDiv = document.createElement("div");
|
||||
testDiv.className = "test";
|
||||
|
||||
var suiteElement = document.createElement("fieldset");
|
||||
suiteElement.className = "suite";
|
||||
suiteElement.appendChild(document.createElement("legend"));
|
||||
|
||||
var totalDiv = document.createElement("div");
|
||||
|
||||
var currentSuiteElement = outputDiv;
|
||||
|
||||
this.beginSuite = function (suite) {
|
||||
var newSectionElement = suiteElement.cloneNode(true);
|
||||
currentSuiteElement.appendChild(newSectionElement);
|
||||
currentSuiteElement = newSectionElement;
|
||||
|
||||
var message = "Suite " + suite.number + " - \"" + suite.name + "\"";
|
||||
console.group(message);
|
||||
currentSuiteElement.querySelector("legend").appendChild(document.createTextNode(message));
|
||||
};
|
||||
|
||||
this.endSuite = function (suite) {
|
||||
var numTotal = suite.total;
|
||||
var numPassed = suite.passed;
|
||||
var numFailed = suite.failed;
|
||||
|
||||
var message = numPassed + " of " + numTotal + " tests passed";
|
||||
|
||||
console.log(message);
|
||||
|
||||
if (numFailed > 0) {
|
||||
var message = numFailed + " of " + numTotal + " tests failed";
|
||||
|
||||
console.warn(message);
|
||||
|
||||
currentSuiteElement.querySelector("legend").appendChild(document.createTextNode(" - " + message));
|
||||
currentSuiteElement.className += " failed";
|
||||
}
|
||||
else {
|
||||
currentSuiteElement.className += " passed";
|
||||
}
|
||||
|
||||
if (numTotal > numPassed + numFailed) {
|
||||
var message = (numTotal - (numPassed + numFailed)) + " of " + numTotal + " tests skipped";
|
||||
|
||||
console.log(message);
|
||||
|
||||
currentSuiteElement.querySelector("legend").appendChild(document.createTextNode(" - " + message));
|
||||
}
|
||||
|
||||
currentSuiteElement = currentSuiteElement.parentElement;
|
||||
console.groupEnd();
|
||||
};
|
||||
|
||||
this.writeTest = function (test) {
|
||||
if (test.passed === true) {
|
||||
var message = "Test " + test.number + " - \"" + test.description + "\" - " + test.customProperties.rule + " [ " + test.customProperties.input + " ]";
|
||||
console.log(message);
|
||||
append(testDiv, message).className += " passed";
|
||||
}
|
||||
else if (test.passed === false) {
|
||||
var message = "Test " + test.number + " - \"" + test.description + "\" - " + test.customProperties.rule + " [ " + test.customProperties.input + " ] " + " : " + ((test.exception !== null) ? test.exception.message : "<No exception>");
|
||||
console.warn(message);
|
||||
append(testDiv, message).className += " failed";
|
||||
}
|
||||
else {
|
||||
var message = "Test " + test.number + " - \"" + test.description + "\" - " + test.customProperties.rule + " [ " + test.customProperties.input + " ] " + " : Skipped";
|
||||
console.log(message);
|
||||
append(testDiv, message).className += " skipped";
|
||||
}
|
||||
};
|
||||
|
||||
this.writeTotal = function (suites) {
|
||||
currentSuiteElement = suiteElement.cloneNode(true);
|
||||
outputDiv.appendChild(currentSuiteElement);
|
||||
currentSuiteElement.className = "total";
|
||||
|
||||
console.group("Total");
|
||||
currentSuiteElement.querySelector("legend").appendChild(document.createTextNode("Total"));
|
||||
|
||||
var numTotal = suites.map(function (section) { return section.total; }).sum();
|
||||
var numPassed = suites.map(function (section) { return section.passed; }).sum();
|
||||
var numFailed = suites.map(function (section) { return section.failed; }).sum();
|
||||
|
||||
var message = numPassed + " of " + numTotal + " tests passed.";
|
||||
console.log(message);
|
||||
append(totalDiv, message).className = "passed";
|
||||
|
||||
if (numFailed > 0) {
|
||||
var message = numFailed + " of " + numTotal + " tests failed.";
|
||||
console.warn(message);
|
||||
append(totalDiv, message).className = "failed";
|
||||
}
|
||||
|
||||
if (numTotal > numPassed + numFailed) {
|
||||
var message = (numTotal - (numPassed + numFailed)) + " of " + numTotal + " tests skipped.";
|
||||
console.warn(message);
|
||||
append(totalDiv, message).className = "skipped";
|
||||
}
|
||||
|
||||
console.groupEnd();
|
||||
};
|
||||
|
||||
var append = function (messageDivType, message) {
|
||||
var messageDiv = messageDivType.cloneNode();
|
||||
|
||||
currentSuiteElement.appendChild(messageDiv);
|
||||
|
||||
messageDiv.appendChild(document.createTextNode(message));
|
||||
|
||||
return messageDiv;
|
||||
};
|
||||
};
|
||||
|
||||
addEventListener("DOMContentLoaded", function () {
|
||||
var logger = new Logger(document.querySelector("#output"));
|
||||
|
||||
var remainingSuites = rootSuites.slice();
|
||||
var runSuite = function () {
|
||||
if (remainingSuites.length > 0) {
|
||||
var nextSuite = remainingSuites.shift();
|
||||
nextSuite.run(logger, runSuite);
|
||||
}
|
||||
else {
|
||||
logger.writeTotal(rootSuites);
|
||||
}
|
||||
};
|
||||
runSuite();
|
||||
}, false);
|
||||
@@ -1,47 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
libjass
|
||||
|
||||
https://github.com/Arnavion/libjass
|
||||
|
||||
Copyright 2013 Arnav Singh
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
|
||||
<head>
|
||||
<title>Parser tests</title>
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
.test.passed, .suite.passed >legend, .total > .passed {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.test.failed, .suite.failed >legend, .total > .failed {
|
||||
color: red;
|
||||
}
|
||||
]]>
|
||||
</style>
|
||||
<script src="../libjass.js" />
|
||||
<script src="index.js" />
|
||||
<script src="parser-test.js" />
|
||||
|
||||
<script src="test-primitives.js" />
|
||||
<script src="test-tags.js" />
|
||||
<script src="test-miscellaneous.js" />
|
||||
<script src="test-webworker.js" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="output" />
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,72 +0,0 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var libjass = require("../libjass.js");
|
||||
var assert = require("assert");
|
||||
|
||||
libjass.debugMode = true;
|
||||
|
||||
var parserTest = function (description, input, rule, expected) {
|
||||
var result = test(description, function () {
|
||||
var actual = null;
|
||||
|
||||
try {
|
||||
actual = libjass.parser.parse(input, rule);
|
||||
}
|
||||
catch (parseException) {
|
||||
if (expected === null) {
|
||||
passed = true;
|
||||
return;
|
||||
}
|
||||
else {
|
||||
throw new Error("Expected parse to succeed but it threw an exception: " + parseException.stack);
|
||||
}
|
||||
}
|
||||
|
||||
if (expected === null) {
|
||||
throw new Error("Expected parse to fail.");
|
||||
}
|
||||
else if (actual === null) {
|
||||
throw new Error("Parse failed without throwing an exception.");
|
||||
}
|
||||
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
|
||||
result.customProperties = Object.create(null);
|
||||
result.customProperties.input = input;
|
||||
result.customProperties.rule = rule;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
parserTest.skip = function (description, input, rule, expected) {
|
||||
var result = test(description); // Could use test.skip but Mocha's implementation has a bug - it doesn't return the Test object
|
||||
|
||||
result.customProperties = Object.create(null);
|
||||
result.customProperties.input = input;
|
||||
result.customProperties.rule = rule;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = parserTest;
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var libjass = require("../libjass.js");
|
||||
var parserTest = require("./parser-test.js");
|
||||
|
||||
suite("Miscellaneous", function () {
|
||||
parserTest("herkz", "{\\pos(311,4)\\blur0.8\\fs40\\bord0\\c&H3F171F&\\t(3820,3820,\\blur6}Chi{\\c&H422CB1&}tose {\\c&H3F171F&}Furu", "dialogueParts", [
|
||||
new libjass.parts.Position(311, 4),
|
||||
new libjass.parts.GaussianBlur(0.8),
|
||||
new libjass.parts.FontSize(40),
|
||||
new libjass.parts.Border(0),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(31, 23, 63, 1)),
|
||||
new libjass.parts.Transform(3.82, 3.82, null, [new libjass.parts.GaussianBlur(6)]),
|
||||
new libjass.parts.Text("Chi"),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(177, 44, 66, 1)),
|
||||
new libjass.parts.Text("tose "),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(31, 23, 63, 1)),
|
||||
new libjass.parts.Text("Furu")
|
||||
]);
|
||||
|
||||
parserTest("herkz-fixed", "{\\pos(311,4)\\blur0.8\\fs40\\bord0\\c&H3F171F&\\t(3820,3820,\\blur6)}Chi{\\c&H422CB1&}tose {\\c&H3F171F&}Furu", "dialogueParts", [
|
||||
new libjass.parts.Position(311, 4),
|
||||
new libjass.parts.GaussianBlur(0.8),
|
||||
new libjass.parts.FontSize(40),
|
||||
new libjass.parts.Border(0),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(31, 23, 63, 1)),
|
||||
new libjass.parts.Transform(3.82, 3.82, null, [new libjass.parts.GaussianBlur(6)]),
|
||||
new libjass.parts.Text("Chi"),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(177, 44, 66, 1)),
|
||||
new libjass.parts.Text("tose "),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(31, 23, 63, 1)),
|
||||
new libjass.parts.Text("Furu")
|
||||
]);
|
||||
|
||||
parserTest("knk-02", "{\\\\k0}{\\1c&H35C2BD&}s{\\1c&H53A2C0&}e{\\1c&H757FC4&}i{\\1c&H866EC6&}j{\\1c&H995AC8&}a{\\1c&HBC36CC&}k{\\1c&HD420C8&}u{\\1c&HB942A7&} {\\1c&HA25F8A&}w{\\1c&H799356&}a{\\1c&H63B03A&} {\\1c&H54C723&}k{\\1c&H68C126&}i{\\1c&H7BBC29&}r{\\1c&H8CB82C&}i{\\1c&HA2B230&}s{\\1c&HBEAB34&}a{\\1c&HD8A339&}k{\\1c&HB68D3B&}u{\\1c&HA4813C&} {\\1c&H88703E&}y{\\1c&H665A41&}o{\\1c&H444444&}u", "dialogueParts", [
|
||||
new libjass.parts.Comment("\\"),
|
||||
new libjass.parts.ColorKaraoke(0),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(189, 194, 53, 1)),
|
||||
new libjass.parts.Text("s"),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(192, 162, 83, 1)),
|
||||
new libjass.parts.Text("e"),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(196, 127, 117, 1)),
|
||||
new libjass.parts.Text("i"),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(198, 110, 134, 1)),
|
||||
new libjass.parts.Text("j"),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(200, 90, 153, 1)),
|
||||
new libjass.parts.Text("a"),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(204, 54, 188, 1)),
|
||||
new libjass.parts.Text("k"),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(200, 32, 212, 1)),
|
||||
new libjass.parts.Text("u"),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(167, 66, 185, 1)),
|
||||
new libjass.parts.Text(" "),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(138, 95, 162, 1)),
|
||||
new libjass.parts.Text("w"),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(86, 147, 121, 1)),
|
||||
new libjass.parts.Text("a"),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(58, 176, 99, 1)),
|
||||
new libjass.parts.Text(" "),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(35, 199, 84, 1)),
|
||||
new libjass.parts.Text("k"),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(38, 193, 104, 1)),
|
||||
new libjass.parts.Text("i"),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(41, 188, 123, 1)),
|
||||
new libjass.parts.Text("r"),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(44, 184, 140, 1)),
|
||||
new libjass.parts.Text("i"),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(48, 178, 162, 1)),
|
||||
new libjass.parts.Text("s"),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(52, 171, 190, 1)),
|
||||
new libjass.parts.Text("a"),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(57, 163, 216, 1)),
|
||||
new libjass.parts.Text("k"),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(59, 141, 182, 1)),
|
||||
new libjass.parts.Text("u"),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(60, 129, 164, 1)),
|
||||
new libjass.parts.Text(" "),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(62, 112, 136, 1)),
|
||||
new libjass.parts.Text("y"),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(65, 90, 102, 1)),
|
||||
new libjass.parts.Text("o"),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(68, 68, 68, 1)),
|
||||
new libjass.parts.Text("u")
|
||||
]);
|
||||
|
||||
parserTest("klk-01", "{\\an5\\bord0\\blur1\\p1\\c&H97B9D6&\\pos(143.39,358.77)\\frz334.3\\fscx39\\fscy47\\iclip(m 129 338 l 121 381 110 372 110 331)}m 0 0 l 100 0 100 100 0 100", "dialogueParts", [
|
||||
new libjass.parts.Alignment(5),
|
||||
new libjass.parts.Border(0),
|
||||
new libjass.parts.GaussianBlur(1),
|
||||
new libjass.parts.DrawingMode(1),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(214, 185, 151, 1)),
|
||||
new libjass.parts.Position(143.39, 358.77),
|
||||
new libjass.parts.RotateZ(334.3),
|
||||
new libjass.parts.FontScaleX(0.39),
|
||||
new libjass.parts.FontScaleY(0.47),
|
||||
new libjass.parts.VectorClip(1, [
|
||||
new libjass.parts.drawing.MoveInstruction(129, 338),
|
||||
new libjass.parts.drawing.LineInstruction(121, 381),
|
||||
new libjass.parts.drawing.LineInstruction(110, 372),
|
||||
new libjass.parts.drawing.LineInstruction(110, 331)
|
||||
], false),
|
||||
new libjass.parts.DrawingInstructions([
|
||||
new libjass.parts.drawing.MoveInstruction(0, 0),
|
||||
new libjass.parts.drawing.LineInstruction(100, 0),
|
||||
new libjass.parts.drawing.LineInstruction(100, 100),
|
||||
new libjass.parts.drawing.LineInstruction(0, 100)
|
||||
])
|
||||
]);
|
||||
|
||||
parserTest("chihaya-16", "{\\an7\\pos(0,0)\\bord0\\blur0.5\\p1\\c&HFAFAFA&}m 88 644 l 87 681 l 255 681 l 256 643 m 311 643 l 310 681 l 482 681 l 483 641 m 562 641 l 561 679 l 730 676 l 731 640 m 800 638 l 798 677 l 968 676 l 970 639 m 1031 638 l 1028 675 l 1200 674 l 1200 640 {p0}", "dialogueParts", [
|
||||
new libjass.parts.Alignment(7),
|
||||
new libjass.parts.Position(0, 0),
|
||||
new libjass.parts.Border(0),
|
||||
new libjass.parts.GaussianBlur(0.5),
|
||||
new libjass.parts.DrawingMode(1),
|
||||
new libjass.parts.PrimaryColor(new libjass.parts.Color(250, 250, 250)),
|
||||
new libjass.parts.DrawingInstructions([
|
||||
new libjass.parts.drawing.MoveInstruction(88, 644),
|
||||
new libjass.parts.drawing.LineInstruction(87, 681),
|
||||
new libjass.parts.drawing.LineInstruction(255, 681),
|
||||
new libjass.parts.drawing.LineInstruction(256, 643),
|
||||
new libjass.parts.drawing.MoveInstruction(311, 643),
|
||||
new libjass.parts.drawing.LineInstruction(310, 681),
|
||||
new libjass.parts.drawing.LineInstruction(482, 681),
|
||||
new libjass.parts.drawing.LineInstruction(483, 641),
|
||||
new libjass.parts.drawing.MoveInstruction(562, 641),
|
||||
new libjass.parts.drawing.LineInstruction(561, 679),
|
||||
new libjass.parts.drawing.LineInstruction(730, 676),
|
||||
new libjass.parts.drawing.LineInstruction(731, 640),
|
||||
new libjass.parts.drawing.MoveInstruction(800, 638),
|
||||
new libjass.parts.drawing.LineInstruction(798, 677),
|
||||
new libjass.parts.drawing.LineInstruction(968, 676),
|
||||
new libjass.parts.drawing.LineInstruction(970, 639),
|
||||
new libjass.parts.drawing.MoveInstruction(1031, 638),
|
||||
new libjass.parts.drawing.LineInstruction(1028, 675),
|
||||
new libjass.parts.drawing.LineInstruction(1200, 674),
|
||||
new libjass.parts.drawing.LineInstruction(1200, 640)
|
||||
]),
|
||||
new libjass.parts.Comment("p0")
|
||||
]);
|
||||
|
||||
parserTest("SRT", "1\n00:00:10,500 --> 00:00:13,000\nElephant's Dream\n\n2\n00:00:15,000 --> 00:00:18,000 X1:52 X2:303 Y1:438 Y2:453\n<font color=\"cyan\">At the left we can see...</font>", "srtScript", [{
|
||||
number: 1,
|
||||
start: "00:00:10.500", end: "00:00:13.000",
|
||||
bounds: { x1: null, y1: null, x2: null, y2: null },
|
||||
text: "Elephant's Dream"
|
||||
}, {
|
||||
number: 2,
|
||||
start: "00:00:15.000", end: "00:00:18.000",
|
||||
bounds: "X1:52 X2:303 Y1:438 Y2:453",
|
||||
text: "<font color=\"cyan\">At the left we can see...</font>"
|
||||
}
|
||||
]);
|
||||
|
||||
parserTest("SRT multiple lines", "1\n00:01:15,940 --> 00:01:17,280\nHave you secured the key?\nWhy can't people forgive each other\n\n2\n00:01:17,280 --> 00:01:17,670\nWhy can't people forgive each other", "srtScript", [{
|
||||
number: 1,
|
||||
start: "00:01:15.940", end: "00:01:17.280",
|
||||
bounds: { x1: null, y1: null, x2: null, y2: null },
|
||||
text: "Have you secured the key?\nWhy can't people forgive each other"
|
||||
}, {
|
||||
number: 2,
|
||||
start: "00:01:17.280", end: "00:01:17.670",
|
||||
bounds: { x1: null, y1: null, x2: null, y2: null },
|
||||
text: "Why can't people forgive each other"
|
||||
}
|
||||
]);
|
||||
});
|
||||
@@ -1,68 +0,0 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var libjass = require("../libjass.js");
|
||||
var parserTest = require("./parser-test.js");
|
||||
|
||||
suite("Primitives", function () {
|
||||
suite("Color", function () {
|
||||
parserTest("Starts with &H", "&H3F171F&", "color", new libjass.parts.Color(31, 23, 63, 1));
|
||||
|
||||
parserTest("Starts with H", "&3F171F&", "color", new libjass.parts.Color(31, 23, 63, 1));
|
||||
|
||||
parserTest("Eight digits", "&H3F171F00&", "color", new libjass.parts.Color(31, 23, 63, 1));
|
||||
|
||||
parserTest("Eight digits, non-zero alpha", "&H3F171FFF&", "color", null);
|
||||
});
|
||||
|
||||
suite("Alpha", function () {
|
||||
parserTest("Starts with &H", "&HFF&", "alpha", 0);
|
||||
|
||||
parserTest("Starts with &", "&FF&", "alpha", 0);
|
||||
|
||||
parserTest("Starts with &H, one digit", "&HF&", "alpha", 0);
|
||||
|
||||
parserTest("Starts with &H, one digit", "&H0&", "alpha", 1);
|
||||
|
||||
parserTest("Starts with &, one digit", "&F&", "alpha", 0);
|
||||
|
||||
parserTest("Starts with &, one digit", "&0&", "alpha", 1);
|
||||
|
||||
parserTest("Starts with &H, doesn't end with &", "&HF&", "alpha", 0);
|
||||
|
||||
parserTest("Starts with &H, doesn't end with &", "&H0&", "alpha", 1);
|
||||
|
||||
parserTest("Starts with &, doesn't end with &", "&F", "alpha", 0);
|
||||
|
||||
parserTest("Starts with &, doesn't end with &", "&0", "alpha", 1);
|
||||
|
||||
parserTest("Doesn't start with &, doesn't end with &", "0", "alpha", 1);
|
||||
|
||||
parserTest("Starts with H", "H0", "alpha", null);
|
||||
});
|
||||
|
||||
suite("ColorWithAlpha", function () {
|
||||
parserTest("AABBGGRR", "&H00434441", "colorWithAlpha", new libjass.parts.Color(65, 68, 67, 1));
|
||||
|
||||
parserTest("AABBGGRR", "&HF0434441", "colorWithAlpha", new libjass.parts.Color(65, 68, 67, (1 - 240 / 255)));
|
||||
|
||||
parserTest("AABBGGRR", "&HFF434441", "colorWithAlpha", new libjass.parts.Color(65, 68, 67, 0));
|
||||
});
|
||||
});
|
||||
@@ -1,173 +0,0 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var libjass = require("../libjass.js");
|
||||
var parserTest = require("./parser-test.js");
|
||||
|
||||
suite("Tags", function () {
|
||||
suite("Bold tag - \\b", function () {
|
||||
parserTest("True", "b1", "tag_b", new libjass.parts.Bold(true));
|
||||
|
||||
parserTest("False", "b0", "tag_b", new libjass.parts.Bold(false));
|
||||
|
||||
parserTest("100", "b100", "tag_b", new libjass.parts.Bold(100));
|
||||
|
||||
parserTest("900", "b900", "tag_b", new libjass.parts.Bold(900));
|
||||
|
||||
parserTest("null", "b", "tag_b", new libjass.parts.Bold(null));
|
||||
|
||||
parserTest("2", "b2", "tag_b", null);
|
||||
|
||||
parserTest("10", "b10", "tag_b", null);
|
||||
|
||||
parserTest("150", "b150", "tag_b", null);
|
||||
|
||||
parserTest("Enclosed tag", "{\\b1}", "enclosedTags", [new libjass.parts.Bold(true)]);
|
||||
|
||||
parserTest("Enclosed tag", "{\\b0}", "enclosedTags", [new libjass.parts.Bold(false)]);
|
||||
|
||||
parserTest("Enclosed tag", "{\\b100}", "enclosedTags", [new libjass.parts.Bold(100)]);
|
||||
|
||||
parserTest("Enclosed tag", "{\\b900}", "enclosedTags", [new libjass.parts.Bold(900)]);
|
||||
|
||||
parserTest("Enclosed tag", "{\\b}", "enclosedTags", [new libjass.parts.Bold(null)]);
|
||||
|
||||
parserTest("Enclosed tag", "{\\b2}", "enclosedTags", [new libjass.parts.Bold(null), new libjass.parts.Comment("2")]);
|
||||
|
||||
parserTest("Enclosed tag", "{\\b10}", "enclosedTags", [new libjass.parts.Bold(true), new libjass.parts.Comment("0")]);
|
||||
|
||||
parserTest("Enclosed tag", "{\\b150}", "enclosedTags", [new libjass.parts.Bold(true), new libjass.parts.Comment("50")]);
|
||||
});
|
||||
|
||||
suite("Italics tag - \\i", function () {
|
||||
parserTest("True", "i1", "tag_i", new libjass.parts.Italic(true));
|
||||
|
||||
parserTest("False", "i0", "tag_i", new libjass.parts.Italic(false));
|
||||
|
||||
parserTest("null", "i", "tag_i", new libjass.parts.Italic(null));
|
||||
});
|
||||
|
||||
suite("Primary color tags - \\c and \\1c", function () {
|
||||
parserTest("Just the tag", "1c&H3F171F&", "tag_1c", new libjass.parts.PrimaryColor(new libjass.parts.Color(31, 23, 63, 1)));
|
||||
|
||||
parserTest("Just the tag", "c&H3F171F&", "tag_c", new libjass.parts.PrimaryColor(new libjass.parts.Color(31, 23, 63, 1)));
|
||||
|
||||
parserTest("Enclosed tag", "{\\c&H3F171F&}", "enclosedTags", [new libjass.parts.PrimaryColor(new libjass.parts.Color(31, 23, 63, 1))]);
|
||||
|
||||
parserTest("Dialogue", "{\\c&H3F171F&}", "dialogueParts", [new libjass.parts.PrimaryColor(new libjass.parts.Color(31, 23, 63, 1))]);
|
||||
});
|
||||
|
||||
suite("Alpha tag - \\alpha", function () {
|
||||
parserTest("Just the tag", "alpha&H00&", "tag_alpha", new libjass.parts.Alpha(1));
|
||||
|
||||
parserTest("Just the tag", "alpha00", "tag_alpha", new libjass.parts.Alpha(1));
|
||||
});
|
||||
|
||||
suite("Font size tag - \\fs", function () {
|
||||
parserTest("5", "fs5", "tag_fs", new libjass.parts.FontSize(5));
|
||||
|
||||
parserTest("null", "fs", "tag_fs", new libjass.parts.FontSize(null));
|
||||
|
||||
parserTest("Plus 5", "fs+5", "tag_fsplus", new libjass.parts.FontSizePlus(5));
|
||||
|
||||
parserTest("Plus null", "fs+", "tag_fsplus", null);
|
||||
|
||||
parserTest("Minus 5", "fs-5", "tag_fsminus", new libjass.parts.FontSizeMinus(5));
|
||||
|
||||
parserTest("Minus null", "fs-", "tag_fsminus", null);
|
||||
});
|
||||
|
||||
suite("Transform tag - \\t", function () {
|
||||
parserTest("No tags", "{\\t(100,200)}", "enclosedTags", [new libjass.parts.Comment("\\t(100,200)")]);
|
||||
});
|
||||
|
||||
suite("Clip tags - \\clip and \\iclip", function () {
|
||||
parserTest("Rectangular clip", "{\\clip(100,200,300,400)}", "enclosedTags", [new libjass.parts.RectangularClip(100, 200, 300, 400, true)]);
|
||||
|
||||
parserTest("Vector clip", "{\\clip(m 129 338 l 121 381 110 372 110 331)}", "enclosedTags", [new libjass.parts.VectorClip(1, [
|
||||
new libjass.parts.drawing.MoveInstruction(129, 338),
|
||||
new libjass.parts.drawing.LineInstruction(121, 381),
|
||||
new libjass.parts.drawing.LineInstruction(110, 372),
|
||||
new libjass.parts.drawing.LineInstruction(110, 331)
|
||||
], true)]);
|
||||
|
||||
parserTest("Vector clip with scale", "{\\clip(1, m 129 338 l 121 381 110 372 110 331)}", "enclosedTags", [new libjass.parts.VectorClip(1, [
|
||||
new libjass.parts.drawing.MoveInstruction(129, 338),
|
||||
new libjass.parts.drawing.LineInstruction(121, 381),
|
||||
new libjass.parts.drawing.LineInstruction(110, 372),
|
||||
new libjass.parts.drawing.LineInstruction(110, 331)
|
||||
], true)]);
|
||||
|
||||
parserTest("Inverted rectangular clip", "{\\iclip(100,200,300,400)}", "enclosedTags", [new libjass.parts.RectangularClip(100, 200, 300, 400, false)]);
|
||||
|
||||
parserTest("Inverted vector clip", "{\\iclip(m 129 338 l 121 381 110 372 110 331)}", "enclosedTags", [new libjass.parts.VectorClip(1, [
|
||||
new libjass.parts.drawing.MoveInstruction(129, 338),
|
||||
new libjass.parts.drawing.LineInstruction(121, 381),
|
||||
new libjass.parts.drawing.LineInstruction(110, 372),
|
||||
new libjass.parts.drawing.LineInstruction(110, 331)
|
||||
], false)]);
|
||||
|
||||
parserTest("Inverted vector clip with scale", "{\\iclip(1, m 129 338 l 121 381 110 372 110 331)}", "enclosedTags", [new libjass.parts.VectorClip(1, [
|
||||
new libjass.parts.drawing.MoveInstruction(129, 338),
|
||||
new libjass.parts.drawing.LineInstruction(121, 381),
|
||||
new libjass.parts.drawing.LineInstruction(110, 372),
|
||||
new libjass.parts.drawing.LineInstruction(110, 331)
|
||||
], false)]);
|
||||
});
|
||||
|
||||
suite("Drawing instructions", function () {
|
||||
parserTest("Normal", "m 984 425 l 985 445 l 985 542 l 973 544 b 983 524 987 496 984 459", "drawingInstructions", [
|
||||
new libjass.parts.drawing.MoveInstruction(984, 425),
|
||||
new libjass.parts.drawing.LineInstruction(985, 445),
|
||||
new libjass.parts.drawing.LineInstruction(985, 542),
|
||||
new libjass.parts.drawing.LineInstruction(973, 544),
|
||||
new libjass.parts.drawing.CubicBezierCurveInstruction(983, 524, 987, 496, 984, 459)
|
||||
]);
|
||||
|
||||
parserTest("Re-use previous instruction", "m 984 425 l 985 445 985 542 973 544 b 983 524 987 496 984 459", "drawingInstructions", [
|
||||
new libjass.parts.drawing.MoveInstruction(984, 425),
|
||||
new libjass.parts.drawing.LineInstruction(985, 445),
|
||||
new libjass.parts.drawing.LineInstruction(985, 542),
|
||||
new libjass.parts.drawing.LineInstruction(973, 544),
|
||||
new libjass.parts.drawing.CubicBezierCurveInstruction(983, 524, 987, 496, 984, 459)
|
||||
]);
|
||||
|
||||
parserTest("Leading space", " m 984 425", "drawingInstructions", [
|
||||
new libjass.parts.drawing.MoveInstruction(984, 425)
|
||||
]);
|
||||
|
||||
parserTest("Terminal space", "m 984 425 ", "drawingInstructions", [
|
||||
new libjass.parts.drawing.MoveInstruction(984, 425)
|
||||
]);
|
||||
|
||||
parserTest("Space on both ends", " m 984 425 ", "drawingInstructions", [
|
||||
new libjass.parts.drawing.MoveInstruction(984, 425)
|
||||
]);
|
||||
|
||||
parserTest("Empty", "", "drawingInstructions", [
|
||||
]);
|
||||
|
||||
parserTest("One space", " ", "drawingInstructions", [
|
||||
]);
|
||||
|
||||
parserTest("Two spaces", " ", "drawingInstructions", [
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1,65 +0,0 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var libjass = require("../libjass.js");
|
||||
var assert = require("assert");
|
||||
|
||||
suite("Web worker", function () {
|
||||
var workerChannel = null;
|
||||
|
||||
setup(function () {
|
||||
workerChannel = libjass.webworker.createWorker();
|
||||
});
|
||||
|
||||
suite("Parse", function () {
|
||||
if (libjass.webworker.supported) {
|
||||
test("Parse", function (done) {
|
||||
this.customProperties = {
|
||||
rule: "dialogueParts",
|
||||
input: "{\\an8}Are {\\i1}you{\\i0} the one who stole the clock?!"
|
||||
};
|
||||
|
||||
workerChannel.request(libjass.webworker.WorkerCommands.Parse, {
|
||||
input: this.customProperties.input,
|
||||
rule: this.customProperties.rule
|
||||
}).then(function (promise) {
|
||||
try {
|
||||
assert.deepEqual(promise.result, [
|
||||
new libjass.parts.Alignment(8),
|
||||
new libjass.parts.Text("Are "),
|
||||
new libjass.parts.Italic(true),
|
||||
new libjass.parts.Text("you"),
|
||||
new libjass.parts.Italic(false),
|
||||
new libjass.parts.Text(" the one who stole the clock?!")
|
||||
]);
|
||||
|
||||
done();
|
||||
}
|
||||
catch (ex) {
|
||||
done(ex);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
test("Parse: Web workers not supported in this environment.");
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,810 +0,0 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
///<reference path="libjass.ts" />
|
||||
|
||||
module libjass {
|
||||
export interface StyleMap {
|
||||
[name: string]: Style;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class represents an ASS script. It contains the script properties, an array of Styles, and an array of Dialogues.
|
||||
*/
|
||||
export class ASS {
|
||||
private _properties: ScriptProperties = new ScriptProperties();
|
||||
private _styles: StyleMap = Object.create(null);
|
||||
private _dialogues: Dialogue[] = [];
|
||||
private _dialoguesFormatSpecifier: string[];
|
||||
|
||||
/**
|
||||
* The properties of this script.
|
||||
*
|
||||
* @type {!libjass.ScriptProperties}
|
||||
*/
|
||||
get properties(): ScriptProperties {
|
||||
return this._properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* The styles in this script.
|
||||
*
|
||||
* @type {!Object.<string, !libjass.Style>}
|
||||
*/
|
||||
get styles(): StyleMap {
|
||||
return this._styles;
|
||||
}
|
||||
|
||||
/**
|
||||
* The dialogues in this script.
|
||||
*
|
||||
* @type {!Array.<!libjass.Dialogue>}
|
||||
*/
|
||||
get dialogues(): Dialogue[] {
|
||||
return this._dialogues;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
// Deprecated constructor argument
|
||||
if (arguments.length === 1) {
|
||||
throw new Error("Constructor `new ASS(rawASS)` has been deprecated. Use `ASS.fromString(rawASS)` instead.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an event to this ASS script.
|
||||
*
|
||||
* @param {string} line The line from the script that contains the new event.
|
||||
*/
|
||||
addEvent(line: string): void {
|
||||
var typedTemplateArray = <TypedTemplateArray>[];
|
||||
typedTemplateArray.formatSpecifier = this._dialoguesFormatSpecifier;
|
||||
var typedTemplate = <TypedTemplate>parser.parse(line, "assScriptProperty", new parser.ParseNode(null, { contents: typedTemplateArray }));
|
||||
|
||||
this._addEvent(typedTemplate);
|
||||
}
|
||||
|
||||
private _addEvent(line: TypedTemplate) {
|
||||
if (line.type === "Dialogue") {
|
||||
var dialogueTemplate = line.template;
|
||||
|
||||
if (libjass.verboseMode) {
|
||||
console.log("Read dialogue: " + JSON.stringify(dialogueTemplate), dialogueTemplate);
|
||||
}
|
||||
|
||||
// Create the dialogue and add it to the dialogues array
|
||||
this.dialogues.push(new Dialogue(dialogueTemplate, this));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ASS object from the raw text of an ASS script.
|
||||
*
|
||||
* @param {string} raw The raw text of the script.
|
||||
* @param {number=0} type The type of the script. One of the libjass.Format constants.
|
||||
* @return {!libjass.ASS}
|
||||
*/
|
||||
static fromString(raw: string, type: Format = Format.ASS): ASS {
|
||||
raw = raw.replace(/\r$/gm, "");
|
||||
|
||||
switch (type) {
|
||||
case Format.ASS:
|
||||
return ASS._fromASSString(raw);
|
||||
case Format.SRT:
|
||||
return ASS._fromSRTString(raw);
|
||||
default:
|
||||
throw new Error("Illegal value of type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
private static _fromASSString(rawASS: string): ASS {
|
||||
var script = parser.parse(rawASS, "assScript");
|
||||
|
||||
var result = new ASS();
|
||||
|
||||
// Get the script info template
|
||||
var infoTemplate: Template = script["Script Info"];
|
||||
|
||||
if (libjass.verboseMode) {
|
||||
console.log("Read script info: " + JSON.stringify(infoTemplate), infoTemplate);
|
||||
}
|
||||
|
||||
// Parse the script properties
|
||||
result.properties.resolutionX = parseInt(infoTemplate["PlayResX"]);
|
||||
result.properties.resolutionY = parseInt(infoTemplate["PlayResY"]);
|
||||
result.properties.wrappingStyle = parseInt(infoTemplate["WrapStyle"]);
|
||||
result.properties.scaleBorderAndShadow = (infoTemplate["ScaledBorderAndShadow"] === "yes");
|
||||
|
||||
// Get styles from the styles section
|
||||
(<TypedTemplateArray>script["V4+ Styles"]).forEach(line => {
|
||||
if (line.type === "Style") {
|
||||
var styleTemplate = line.template;
|
||||
|
||||
if (libjass.verboseMode) {
|
||||
console.log("Read style: " + JSON.stringify(styleTemplate), styleTemplate);
|
||||
}
|
||||
|
||||
// Create the style and add it to the styles map
|
||||
var newStyle = new Style(styleTemplate);
|
||||
result.styles[newStyle.name] = newStyle;
|
||||
}
|
||||
});
|
||||
|
||||
// Get dialogues from the events section
|
||||
var events = <TypedTemplateArray>script["Events"];
|
||||
result._dialoguesFormatSpecifier = events.formatSpecifier;
|
||||
events.forEach(line => result._addEvent(line));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static _fromSRTString(rawSRT: string): ASS {
|
||||
var script: any[] = parser.parse(rawSRT, "srtScript");
|
||||
|
||||
var result = new ASS();
|
||||
|
||||
// Parse the script properties
|
||||
result.properties.resolutionX = 1280;
|
||||
result.properties.resolutionY = 720;
|
||||
result.properties.wrappingStyle = 1;
|
||||
result.properties.scaleBorderAndShadow = true;
|
||||
|
||||
var newStyle = new Style({
|
||||
"Name": "Default",
|
||||
"Italic": "0", "Bold": "0", "Underline": "0", "StrikeOut": "0",
|
||||
"Fontname": "", "Fontsize": "50",
|
||||
"ScaleX": "100", "ScaleY": "100",
|
||||
"Spacing": "0",
|
||||
"Angle": "0",
|
||||
"PrimaryColour": "&H0000FFFF", "SecondaryColour": "&H00000000", "OutlineColour": "&H00000000", "BackColour": "&H00000000",
|
||||
"Outline": "1", "BorderStyle": "1",
|
||||
"Shadow": "1",
|
||||
"Alignment": "2",
|
||||
"MarginL": "80", "MarginR": "80", "MarginV": "35"
|
||||
});
|
||||
result.styles[newStyle.name] = newStyle;
|
||||
|
||||
script.sort((line1, line2) => line1["number"] - line2["number"]).forEach(line => {
|
||||
result.dialogues.push(new Dialogue({
|
||||
"Style": "Default",
|
||||
"Start": line.start, "End": line.end,
|
||||
"Layer": "0",
|
||||
"Text":
|
||||
(<string>line.text)
|
||||
.replace("<b>", "{\\b1}").replace("</b>", "{\\b0}")
|
||||
.replace("<i>", "{\\i1}").replace("</i>", "{\\i0}")
|
||||
.replace("<u>", "{\\u1}").replace("</u>", "{\\u0}")
|
||||
.replace(
|
||||
/<font color="#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})">/g,
|
||||
(/* ujs:unreferenced */ substring: string, red: string, green: string, blue: string) => "{\c&H" + blue + green + red + "&}"
|
||||
).replace("</font>", "{\\c}")
|
||||
}, result));
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export enum Format {
|
||||
ASS,
|
||||
SRT
|
||||
}
|
||||
|
||||
export enum WrappingStyle {
|
||||
SmartWrappingWithWiderTopLine = 0,
|
||||
SmartWrappingWithWiderBottomLine = 3,
|
||||
EndOfLineWrapping = 1,
|
||||
NoLineWrapping = 2,
|
||||
}
|
||||
|
||||
/**
|
||||
* This class represents the properties of an ASS script.
|
||||
*/
|
||||
export class ScriptProperties {
|
||||
private _resolutionX: number;
|
||||
private _resolutionY: number;
|
||||
private _wrappingStyle: WrappingStyle;
|
||||
private _scaleBorderAndShadow: boolean;
|
||||
|
||||
/**
|
||||
* The horizontal script resolution.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get resolutionX(): number {
|
||||
return this._resolutionX;
|
||||
}
|
||||
|
||||
/**
|
||||
* The horizontal script resolution.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
set resolutionX(value: number) {
|
||||
this._resolutionX = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The vertical script resolution.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get resolutionY(): number {
|
||||
return this._resolutionY;
|
||||
}
|
||||
|
||||
/**
|
||||
* The vertical script resolution.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
set resolutionY(value: number) {
|
||||
this._resolutionY = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The wrap style.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get wrappingStyle(): WrappingStyle {
|
||||
return this._wrappingStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* The wrap style.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
set wrappingStyle(value: WrappingStyle) {
|
||||
this._wrappingStyle = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to scale outline widths and shadow depths from script resolution to video resolution or not. If true, widths and depths are scaled.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
get scaleBorderAndShadow(): boolean {
|
||||
return this._scaleBorderAndShadow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to scale outline widths and shadow depths from script resolution to video resolution or not. If true, widths and depths are scaled.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
set scaleBorderAndShadow(value: boolean) {
|
||||
this._scaleBorderAndShadow = value;
|
||||
}
|
||||
}
|
||||
|
||||
export enum BorderStyle {
|
||||
Outline = 1,
|
||||
OpaqueBox = 3,
|
||||
}
|
||||
|
||||
/**
|
||||
* This class represents a single global style declaration in an ASS script. The styles can be obtained via the ASS.styles property.
|
||||
*
|
||||
* @param {!Object} template The template object that contains the style's properties. It is a map of the string values read from the ASS file.
|
||||
* @param {string} template["Name"] The name of the style
|
||||
* @param {string} template["Italic"] -1 if the style is italicized
|
||||
* @param {string} template["Bold"] -1 if the style is bold
|
||||
* @param {string} template["Underline"] -1 if the style is underlined
|
||||
* @param {string} template["StrikeOut"] -1 if the style is struck-through
|
||||
* @param {string} template["Fontname"] The name of the font
|
||||
* @param {string} template["Fontsize"] The size of the font
|
||||
* @param {string} template["ScaleX"] The horizontal scaling of the font
|
||||
* @param {string} template["ScaleY"] The vertical scaling of the font
|
||||
* @param {string} template["Spacing"] The letter spacing of the font
|
||||
* @param {string} template["PrimaryColour"] The primary color
|
||||
* @param {string} template["OutlineColour"] The outline color
|
||||
* @param {string} template["BackColour"] The shadow color
|
||||
* @param {string} template["Outline"] The outline thickness
|
||||
* @param {string} template["Shadow"] The shadow depth
|
||||
* @param {string} template["Alignment"] The alignment number
|
||||
* @param {string} template["MarginL"] The left margin
|
||||
* @param {string} template["MarginR"] The right margin
|
||||
* @param {string} template["MarginV"] The vertical margin
|
||||
*/
|
||||
export class Style {
|
||||
private _name: string;
|
||||
|
||||
private _italic: boolean;
|
||||
private _bold: boolean;
|
||||
private _underline: boolean;
|
||||
private _strikeThrough: boolean;
|
||||
|
||||
private _fontName: string;
|
||||
private _fontSize: number;
|
||||
|
||||
private _fontScaleX: number;
|
||||
private _fontScaleY: number;
|
||||
|
||||
private _letterSpacing: number;
|
||||
|
||||
private _rotationZ: number;
|
||||
|
||||
private _primaryColor: parts.Color;
|
||||
private _secondaryColor: parts.Color;
|
||||
private _outlineColor: parts.Color;
|
||||
private _shadowColor: parts.Color;
|
||||
|
||||
private _outlineThickness: number;
|
||||
private _borderStyle: BorderStyle;
|
||||
|
||||
private _shadowDepth: number;
|
||||
|
||||
private _alignment: number;
|
||||
|
||||
private _marginLeft: number;
|
||||
private _marginRight: number;
|
||||
private _marginVertical: number;
|
||||
|
||||
constructor(template: Template) {
|
||||
this._name = template["Name"];
|
||||
|
||||
this._italic = template["Italic"] === "-1";
|
||||
this._bold = template["Bold"] === "-1";
|
||||
this._underline = template["Underline"] === "-1";
|
||||
this._strikeThrough = template["StrikeOut"] === "-1";
|
||||
|
||||
this._fontName = template["Fontname"];
|
||||
this._fontSize = parseFloat(template["Fontsize"]);
|
||||
|
||||
this._fontScaleX = parseFloat(template["ScaleX"]) / 100;
|
||||
this._fontScaleY = parseFloat(template["ScaleY"]) / 100;
|
||||
|
||||
this._letterSpacing = parseFloat(template["Spacing"]);
|
||||
|
||||
this._rotationZ = parseFloat(template["Angle"]);
|
||||
|
||||
this._primaryColor = <parts.Color>parser.parse(template["PrimaryColour"], "colorWithAlpha");
|
||||
this._secondaryColor = <parts.Color>parser.parse(template["SecondaryColour"], "colorWithAlpha");
|
||||
this._outlineColor = <parts.Color>parser.parse(template["OutlineColour"], "colorWithAlpha");
|
||||
this._shadowColor = <parts.Color>parser.parse(template["BackColour"], "colorWithAlpha");
|
||||
|
||||
this._outlineThickness = parseFloat(template["Outline"]);
|
||||
this._borderStyle = parseInt(template["BorderStyle"]);
|
||||
|
||||
this._shadowDepth = parseFloat(template["Shadow"]);
|
||||
|
||||
this._alignment = parseInt(template["Alignment"]);
|
||||
|
||||
this._marginLeft = parseFloat(template["MarginL"]);
|
||||
this._marginRight = parseFloat(template["MarginR"]);
|
||||
this._marginVertical = parseFloat(template["MarginV"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of this style.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
get name(): string {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this style is italicized or not.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
get italic(): boolean {
|
||||
return this._italic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this style is bold or not.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
get bold(): boolean {
|
||||
return this._bold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this style is underlined or not.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
get underline(): boolean {
|
||||
return this._underline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this style is struck-through or not.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
get strikeThrough(): boolean {
|
||||
return this._strikeThrough;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of this style's font.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
get fontName(): string {
|
||||
return this._fontName;
|
||||
}
|
||||
|
||||
/**
|
||||
* The size of this style's font.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get fontSize(): number {
|
||||
return this._fontSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* The horizontal scaling of this style's font.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get fontScaleX(): number {
|
||||
return this._fontScaleX;
|
||||
}
|
||||
|
||||
/**
|
||||
* The vertical scaling of this style's font.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get fontScaleY(): number {
|
||||
return this._fontScaleY;
|
||||
}
|
||||
|
||||
/**
|
||||
* The letter spacing scaling of this style's font.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get letterSpacing(): number {
|
||||
return this._letterSpacing;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default Z-rotation of this style.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get rotationZ(): number {
|
||||
return this._rotationZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* The color of this style's font.
|
||||
*
|
||||
* @type {!libjass.parts.Color}
|
||||
*/
|
||||
get primaryColor(): parts.Color {
|
||||
return this._primaryColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* The alternate color of this style's font, used in karaoke.
|
||||
*
|
||||
* @type {!libjass.parts.Color}
|
||||
*/
|
||||
get secondaryColor(): parts.Color {
|
||||
return this._secondaryColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* The color of this style's outline.
|
||||
*
|
||||
* @type {!libjass.parts.Color}
|
||||
*/
|
||||
get outlineColor(): parts.Color {
|
||||
return this._outlineColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* The color of this style's shadow.
|
||||
*
|
||||
* @type {!libjass.parts.Color}
|
||||
*/
|
||||
get shadowColor(): parts.Color {
|
||||
return this._shadowColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* The thickness of this style's outline.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get outlineThickness(): number {
|
||||
return this._outlineThickness;
|
||||
}
|
||||
|
||||
/**
|
||||
* The border style of this style.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get borderStyle(): BorderStyle {
|
||||
return this._borderStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* The depth of this style's shadow.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get shadowDepth(): number {
|
||||
return this._shadowDepth;
|
||||
}
|
||||
|
||||
/**
|
||||
* The alignment of dialogues of this style.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get alignment(): number {
|
||||
return this._alignment;
|
||||
}
|
||||
|
||||
/**
|
||||
* The left margin of dialogues of this style.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get marginLeft(): number {
|
||||
return this._marginLeft;
|
||||
}
|
||||
|
||||
/**
|
||||
* The right margin of dialogues of this style.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get marginRight(): number {
|
||||
return this._marginRight;
|
||||
}
|
||||
|
||||
/**
|
||||
* The vertical margin of dialogues of this style.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get marginVertical(): number {
|
||||
return this._marginVertical;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This class represents a dialogue in an ASS script.
|
||||
*
|
||||
* @param {!Object} template The template object that contains the dialogue's properties. It is a map of the string values read from the ASS file.
|
||||
* @param {string} template["Style"] The name of the default style of this dialogue
|
||||
* @param {string} template["Start"] The start time
|
||||
* @param {string} template["End"] The end time
|
||||
* @param {string} template["Layer"] The layer number
|
||||
* @param {string} template["Text"] The text of this dialogue
|
||||
* @param {ASS} ass The ASS object to which this dialogue belongs
|
||||
*/
|
||||
export class Dialogue {
|
||||
private static _lastDialogueId = -1;
|
||||
|
||||
private _id: number;
|
||||
|
||||
private _style: Style;
|
||||
|
||||
private _start: number;
|
||||
private _end: number;
|
||||
|
||||
private _layer: number;
|
||||
private _alignment: number;
|
||||
|
||||
private _rawPartsString: string;
|
||||
private _parts: parts.Part[] = null;
|
||||
|
||||
private _sub: HTMLDivElement = null;
|
||||
|
||||
constructor(template: Template, ass: ASS) {
|
||||
this._id = ++Dialogue._lastDialogueId;
|
||||
|
||||
this._style = ass.styles[template["Style"]];
|
||||
|
||||
this._start = Dialogue._toTime(template["Start"]);
|
||||
this._end = Dialogue._toTime(template["End"]);
|
||||
|
||||
this._layer = Math.max(parseInt(template["Layer"]), 0);
|
||||
|
||||
this._rawPartsString = template["Text"];
|
||||
}
|
||||
|
||||
/**
|
||||
* The unique ID of this dialogue. Auto-generated.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get id(): number {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The start time of this dialogue.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get start(): number {
|
||||
return this._start;
|
||||
}
|
||||
|
||||
/**
|
||||
* The end time of this dialogue.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get end(): number {
|
||||
return this._end;
|
||||
}
|
||||
|
||||
get style(): Style {
|
||||
return this._style;
|
||||
}
|
||||
|
||||
/**
|
||||
* The alignment number of this dialogue.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get alignment(): number {
|
||||
if (this._parts === null) {
|
||||
this._parsePartsString();
|
||||
}
|
||||
|
||||
return this._alignment;
|
||||
}
|
||||
|
||||
/**
|
||||
* The layer number of this dialogue.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get layer(): number {
|
||||
return this._layer;
|
||||
}
|
||||
|
||||
/**
|
||||
* The parts of this dialogue.
|
||||
*
|
||||
* @type {!Array.<!libjass.parts.Tag>}
|
||||
*/
|
||||
get parts(): parts.Part[] {
|
||||
if (this._parts === null) {
|
||||
this._parsePartsString();
|
||||
}
|
||||
|
||||
return this._parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string} A simple representation of this dialogue's properties and tags.
|
||||
*/
|
||||
toString(): string {
|
||||
return "#" + this._id + " [" + this._start.toFixed(3) + "-" + this._end.toFixed(3) + "] " + ((this._parts !== null) ? this._parts.join(", ") : this._rawPartsString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses this dialogue's parts from the raw parts string.
|
||||
*/
|
||||
private _parsePartsString(): void {
|
||||
this._parts = <parts.Part[]>parser.parse(this._rawPartsString, "dialogueParts");
|
||||
|
||||
this._alignment = this._style.alignment;
|
||||
|
||||
this._parts.forEach((part, index) => {
|
||||
if (part instanceof parts.Alignment) {
|
||||
this._alignment = (<parts.Alignment>part).value;
|
||||
}
|
||||
else if (part instanceof parts.Move) {
|
||||
var movePart = <parts.Move>part;
|
||||
|
||||
if (movePart.t1 === null || movePart.t2 === null) {
|
||||
this._parts[index] =
|
||||
new parts.Move(
|
||||
movePart.x1, movePart.y1, movePart.x2, movePart.y2,
|
||||
0, this._end - this._start
|
||||
);
|
||||
}
|
||||
}
|
||||
else if (part instanceof parts.Transform) {
|
||||
var transformPart = <parts.Transform>part;
|
||||
|
||||
if (transformPart.start === null || transformPart.end === null || transformPart.accel === null) {
|
||||
this._parts[index] =
|
||||
new parts.Transform(
|
||||
(transformPart.start === null) ? 0 : transformPart.start,
|
||||
(transformPart.end === null) ? (this._end - this._start) : transformPart.end,
|
||||
(transformPart.accel === null) ? 1 : transformPart.accel,
|
||||
transformPart.tags
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (libjass.debugMode) {
|
||||
var possiblyIncorrectParses = this._parts.filter(part => part instanceof parts.Comment && (<parts.Comment>part).value.indexOf("\\") !== -1);
|
||||
if (possiblyIncorrectParses.length > 0) {
|
||||
console.warn(
|
||||
"Possible incorrect parse:\n" +
|
||||
this._rawPartsString + "\n" +
|
||||
"was parsed as\n" +
|
||||
this.toString() + "\n" +
|
||||
"The possibly incorrect parses are:\n" +
|
||||
possiblyIncorrectParses.join("\n")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this string into the number of seconds it represents. This string must be in the form of hh:mm:ss.MMM
|
||||
*
|
||||
* @param {string} str
|
||||
* @return {number}
|
||||
*/
|
||||
private static _toTime(str: string): number {
|
||||
return str.split(":").reduce<number>((previousValue, currentValue) => previousValue * 60 + parseFloat(currentValue), 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A template object. It is a map of string keys and string values.
|
||||
*/
|
||||
export interface Template {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A template object with a particular type.
|
||||
*/
|
||||
export interface TypedTemplate {
|
||||
type: string;
|
||||
template: Template;
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of TypedTemplate objects with the format specifier that was used to parse them.
|
||||
*/
|
||||
export interface TypedTemplateArray extends Array<TypedTemplate> {
|
||||
formatSpecifier: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug mode. When true, libjass logs some debug messages.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
export var debugMode: boolean = false;
|
||||
|
||||
/**
|
||||
* Verbose debug mode. When true, libjass logs some more debug messages. This setting is independent of debugMode.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
export var verboseMode: boolean = false;
|
||||
}
|
||||
-281
@@ -1,281 +0,0 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
///<reference path="libjass.ts" />
|
||||
|
||||
"use strict";
|
||||
|
||||
interface Global {
|
||||
Set: { new <T>(): Set<T>; prototype: Set<any> }
|
||||
Map: { new <K, V>(): Map<K, V>; prototype: Map<any, any> }
|
||||
}
|
||||
|
||||
declare var global: Global; // Defined as a parameter of the anonymous function wrapper
|
||||
|
||||
module libjass {
|
||||
/**
|
||||
* Set implementation for browsers that don't support it. Only supports Number and String elements.
|
||||
*
|
||||
* Elements are stored as properties of an object, with names derived from their type.
|
||||
*
|
||||
* @template T
|
||||
*/
|
||||
class SimpleSet<T> implements Set<T> {
|
||||
private _elements: { [key: string]: T };
|
||||
private _size: number;
|
||||
|
||||
constructor() {
|
||||
this.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {T} value
|
||||
* @return {libjass.Set.<T>} This set
|
||||
*/
|
||||
add(value: T): Set<T> {
|
||||
var property = this._toProperty(value);
|
||||
|
||||
if (property === null) {
|
||||
throw new Error("This Set implementation only supports Number and String values.");
|
||||
}
|
||||
|
||||
if (!(property in this._elements)) {
|
||||
this._size++;
|
||||
}
|
||||
|
||||
this._elements[property] = value;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
clear(): void {
|
||||
this._elements = Object.create(null);
|
||||
this._size = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {T} value
|
||||
* @return {boolean}
|
||||
*/
|
||||
has(value: T): boolean {
|
||||
var property = this._toProperty(value);
|
||||
|
||||
if (property === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return property in this._elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {function(T, T, libjass.Set.<T>)} callbackfn A function that is called with each value in the set.
|
||||
*/
|
||||
forEach(callbackfn: (value: T, index: T, set: Set<T>) => void, thisArg?: any): void {
|
||||
Object.keys(this._elements).map((property: string) => {
|
||||
var element = this._elements[property];
|
||||
callbackfn.call(thisArg, element, element, this);
|
||||
});
|
||||
}
|
||||
|
||||
delete(value: T): boolean {
|
||||
throw new Error("This Set implementation doesn't support delete().");
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
get size(): number {
|
||||
return this._size;
|
||||
}
|
||||
|
||||
private _toProperty(value: T): string {
|
||||
if (typeof value === "number") {
|
||||
return "#" + value;
|
||||
}
|
||||
|
||||
if (typeof value === "string") {
|
||||
return "'" + value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to browser's implementation of Set if it has one, else set to libjass.SimpleSet
|
||||
*
|
||||
* @type {function(new:Set)}
|
||||
*/
|
||||
export var Set = global.Set;
|
||||
if (Set === undefined || typeof Set.prototype.forEach !== "function") {
|
||||
Set = <any>SimpleSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map implementation for browsers that don't support it. Only supports keys which are of Number or String type, or which have a property called "id".
|
||||
*
|
||||
* Keys and values are stored as properties of an object, with property names derived from the key type.
|
||||
*
|
||||
* @template K, V
|
||||
*/
|
||||
class SimpleMap<K, V> implements Map<K, V> {
|
||||
private _keys: { [key: string]: K };
|
||||
private _values: { [key: string]: V };
|
||||
private _size: number;
|
||||
|
||||
constructor() {
|
||||
this.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {K} key
|
||||
* @return {V}
|
||||
*/
|
||||
get(key: K): V {
|
||||
var property = this._keyToProperty(key);
|
||||
|
||||
if (property === null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this._values[property];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {K} key
|
||||
* @return {boolean}
|
||||
*/
|
||||
has(key: K): boolean {
|
||||
var property = this._keyToProperty(key);
|
||||
|
||||
if (property === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return property in this._keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {K} key
|
||||
* @param {V} value
|
||||
* @return {libjass.Map.<K, V>} This map
|
||||
*/
|
||||
set(key: K, value: V): Map<K, V> {
|
||||
var property = this._keyToProperty(key);
|
||||
|
||||
if (property === null) {
|
||||
throw new Error("This Map implementation only supports Number and String keys, or keys with an id property.");
|
||||
}
|
||||
|
||||
if (!(property in this._keys)) {
|
||||
this._size++;
|
||||
}
|
||||
|
||||
this._keys[property] = key;
|
||||
this._values[property] = value;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {K} key
|
||||
* @return {boolean} true if the key was present before being deleted, false otherwise
|
||||
*/
|
||||
delete(key: K): boolean {
|
||||
var property = this._keyToProperty(key);
|
||||
|
||||
if (property === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = property in this._keys;
|
||||
|
||||
if (result) {
|
||||
delete this._keys[property];
|
||||
delete this._values[property];
|
||||
this._size--;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
clear(): void {
|
||||
this._keys = Object.create(null);
|
||||
this._values = Object.create(null);
|
||||
this._size = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {function(V, K, libjass.Map.<K, V>)} callbackfn A function that is called with each key and value in the map.
|
||||
*/
|
||||
forEach(callbackfn: (value: V, index: K, map: Map<K, V>) => void, thisArg?: any): void {
|
||||
var keysArray = Object.keys(this._keys);
|
||||
for (var i = 0; i < keysArray.length; i++) {
|
||||
var property = keysArray[i];
|
||||
callbackfn.call(thisArg, this._values[property], this._keys[property], this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
get size(): number {
|
||||
return this._size;
|
||||
}
|
||||
|
||||
private _keyToProperty(key: K): string {
|
||||
if (typeof key === "number") {
|
||||
return "#" + key;
|
||||
}
|
||||
|
||||
if (typeof key === "string") {
|
||||
return "'" + key;
|
||||
}
|
||||
|
||||
if ((<any>key).id !== undefined) {
|
||||
return "!" + (<any>key).id;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to browser's implementation of Map if it has one, else set to libjass.SimpleMap
|
||||
*
|
||||
* @type {function(new:Map)}
|
||||
*/
|
||||
export var Map = global.Map;
|
||||
if (Map === undefined || typeof Map.prototype.forEach !== "function") {
|
||||
Map = <any>SimpleMap;
|
||||
}
|
||||
|
||||
export function mixin(derived: any, mixins: any[]) {
|
||||
mixins.forEach((mixin: any) => {
|
||||
Object.getOwnPropertyNames(mixin.prototype).forEach(name => {
|
||||
derived.prototype[name] = mixin.prototype[name];
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
-298
@@ -1,298 +0,0 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
///<reference path="libjass.ts" />
|
||||
|
||||
interface Document {
|
||||
currentScript: HTMLScriptElement;
|
||||
}
|
||||
|
||||
interface WorkerGlobalScope {
|
||||
postMessage(message: any): void;
|
||||
addEventListener(type: string, listener: (message: any) => void, useCapture: boolean): void;
|
||||
}
|
||||
declare var WorkerGlobalScope: {
|
||||
prototype: WorkerGlobalScope;
|
||||
new (): WorkerGlobalScope;
|
||||
};
|
||||
|
||||
module libjass.webworker {
|
||||
Object.defineProperty(webworker, "supported", {
|
||||
value: typeof Worker !== "undefined",
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
});
|
||||
|
||||
/**
|
||||
* Represents a communication channel between the host and the web worker. An instance of this class is created by calling libjass.webworker.setup()
|
||||
*/
|
||||
export interface WorkerChannel {
|
||||
/**
|
||||
* Sends a request to the other side to execute the given command with the given parameters.
|
||||
*
|
||||
* @param {number} command
|
||||
* @param {*} parameters
|
||||
* @return {!libjass.webworker.WorkerPromise} A promise that will get resolved when the other side computes the result
|
||||
*/
|
||||
request(command: WorkerCommands, parameters: any): WorkerPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new web worker.
|
||||
*
|
||||
* @return {!libjass.webworker.WorkerChannel} A communication channel to the new web worker.
|
||||
*/
|
||||
export function createWorker(): WorkerChannel {
|
||||
return new WorkerChannelImpl(new Worker(_scriptNode.src));
|
||||
}
|
||||
|
||||
/**
|
||||
* A promise returned by libjass.webworker.WorkerChannel.request()
|
||||
*/
|
||||
export interface WorkerPromise {
|
||||
resolved: boolean;
|
||||
result: any;
|
||||
|
||||
/**
|
||||
* Registers a callback to run when this promise is resolved. If the promise is already resolved, the callback is called immediately.
|
||||
*
|
||||
* @param {function(*) } callback A function of the form (promise: *): void
|
||||
*/
|
||||
then(callback: WorkerPromiseCallback): void;
|
||||
|
||||
cancel(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The signature of a callback called by WorkerPromise when it is resolved.
|
||||
*/
|
||||
export interface WorkerPromiseCallback {
|
||||
(promise: WorkerPromise): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The commands that can be sent to or from a web worker.
|
||||
*/
|
||||
export enum WorkerCommands {
|
||||
Response = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* The signature of a handler registered to handle a particular WorkerCommand.
|
||||
*/
|
||||
export interface WorkerCommandHandler {
|
||||
(parameters: any, response: WorkerResultCallback): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The signature of a callback called by a WorkerCommandHandler to report its result back to the caller.
|
||||
*/
|
||||
export interface WorkerResultCallback {
|
||||
(error: any, result: any): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a handler for the given worker command.
|
||||
*
|
||||
* @param {number} command The command that this handler will handle. Valid values are the values of libjass.webworker.WorkerCommands
|
||||
* @param {function(*, function(*, *))} handler The handler. A function of the form (parameters: *, response: function(error: *, result: *): void): void
|
||||
*/
|
||||
export function _registerWorkerCommand(command: WorkerCommands, handler: WorkerCommandHandler) {
|
||||
workerCommands.set(command, handler);
|
||||
}
|
||||
|
||||
export function _registerClassPrototype(prototype: any): void {
|
||||
prototype._classTag = classPrototypes.size;
|
||||
classPrototypes.set(prototype._classTag, prototype);
|
||||
}
|
||||
|
||||
var _scriptNode: HTMLScriptElement = null;
|
||||
if (typeof document !== "undefined" && document.currentScript !== undefined) {
|
||||
_scriptNode = document.currentScript;
|
||||
}
|
||||
|
||||
var workerCommands = new Map<WorkerCommands, WorkerCommandHandler>();
|
||||
|
||||
var classPrototypes = new Map<number, any>();
|
||||
|
||||
interface WorkerCommunication {
|
||||
addEventListener(type: "message", listener: (ev: MessageEvent) => any, useCapture?: boolean): void;
|
||||
addEventListener(type: string, listener: EventListener, useCapture?: boolean): void;
|
||||
postMessage(message: any): void;
|
||||
}
|
||||
|
||||
interface WorkerRequestMessage {
|
||||
requestId: number;
|
||||
command: WorkerCommands;
|
||||
parameters: any;
|
||||
}
|
||||
|
||||
interface WorkerResponseMessage {
|
||||
requestId: number;
|
||||
error: any;
|
||||
result: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal implementation of libjass.webworker.WorkerChannel
|
||||
*
|
||||
* @param {!*} comm The other side of the channel. When created by the host, this is the web worker. When created by the web worker, this is its global object.
|
||||
*/
|
||||
class WorkerChannelImpl implements WorkerChannel {
|
||||
private _pendingRequests = new Map<number, WorkerPromiseImpl>();
|
||||
|
||||
constructor(private _comm: WorkerCommunication) {
|
||||
this._comm.addEventListener("message", ev => this._onMessage(<string>ev.data), false);
|
||||
}
|
||||
|
||||
request(command: WorkerCommands, parameters: any): WorkerPromise {
|
||||
var promise = new WorkerPromiseImpl(this);
|
||||
var requestId = promise.id;
|
||||
this._pendingRequests.set(requestId, promise);
|
||||
|
||||
var requestMessage: WorkerRequestMessage = { requestId: requestId, command: command, parameters: parameters };
|
||||
this._comm.postMessage(WorkerChannelImpl._toJSON(requestMessage));
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
cancelRequest(requestId: number): void {
|
||||
this._pendingRequests.delete(requestId);
|
||||
}
|
||||
|
||||
private _respond(message: WorkerResponseMessage): void {
|
||||
this._comm.postMessage(WorkerChannelImpl._toJSON({ command: WorkerCommands.Response, requestId: message.requestId, error: message.error, result: message.result }));
|
||||
}
|
||||
|
||||
private _onMessage(rawMessage: string): void {
|
||||
var message = <{ command: WorkerCommands }>WorkerChannelImpl._fromJSON(rawMessage);
|
||||
|
||||
if (message.command === WorkerCommands.Response) {
|
||||
var responseMessage = <WorkerResponseMessage><any>message;
|
||||
|
||||
var promise = this._pendingRequests.get(responseMessage.requestId);
|
||||
if (promise !== undefined) {
|
||||
this._pendingRequests.delete(responseMessage.requestId);
|
||||
promise.resolve(responseMessage.error, responseMessage.result);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var requestMessage = <WorkerRequestMessage>message;
|
||||
|
||||
var commandCallback = workerCommands.get(requestMessage.command);
|
||||
if (commandCallback === undefined) {
|
||||
this._respond({ requestId: requestMessage.requestId, error: new Error("Unrecognized command: " + requestMessage.command), result: null });
|
||||
return;
|
||||
}
|
||||
|
||||
commandCallback(requestMessage.parameters, (error: any, result: any) => this._respond({ requestId: requestMessage.requestId, error: error, result: result }));
|
||||
}
|
||||
|
||||
private static _toJSON(obj: any): string {
|
||||
return JSON.stringify(obj, (/* ujs:unreferenced */ key: string, value: any) => {
|
||||
if (value && value._classTag !== undefined) {
|
||||
value._classTag = value._classTag;
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
private static _fromJSON(str: string): any {
|
||||
return JSON.parse(str, (/* ujs:unreferenced */ key: string, value: any) => {
|
||||
if (value && value._classTag !== undefined) {
|
||||
var hydratedValue = Object.create(classPrototypes.get(value._classTag));
|
||||
Object.keys(value).forEach(key => {
|
||||
if (key !== "_classTag") {
|
||||
hydratedValue[key] = value[key];
|
||||
}
|
||||
});
|
||||
value = hydratedValue;
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var inWorker = (typeof WorkerGlobalScope !== "undefined" && global instanceof WorkerGlobalScope);
|
||||
if (inWorker) {
|
||||
new WorkerChannelImpl(<WorkerGlobalScope><any>global);
|
||||
}
|
||||
|
||||
class WorkerPromiseImpl implements WorkerPromise {
|
||||
private static _lastPromiseId: number = -1;
|
||||
|
||||
private _id: number = ++WorkerPromiseImpl._lastPromiseId;
|
||||
private _resolved: boolean = false;
|
||||
private _result: any = null;
|
||||
private _error: any = null;
|
||||
private _callback: WorkerPromiseCallback = null;
|
||||
|
||||
constructor(private _channel: WorkerChannelImpl) { }
|
||||
|
||||
get resolved(): boolean {
|
||||
return this._resolved;
|
||||
}
|
||||
|
||||
get result(): any {
|
||||
if (!this._resolved) {
|
||||
throw new Error("Unresolved promise.");
|
||||
}
|
||||
|
||||
if (this._error !== null) {
|
||||
throw this._error;
|
||||
}
|
||||
|
||||
return this._result;
|
||||
}
|
||||
|
||||
then(callback: WorkerPromiseCallback): void {
|
||||
this._callback = callback;
|
||||
if (this._resolved) {
|
||||
setTimeout(() => this._callback(this), 0);
|
||||
}
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
if (this._resolved) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._channel.cancelRequest(this._id);
|
||||
}
|
||||
|
||||
get id(): number {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
resolve(error: any, result: any): void {
|
||||
this._resolved = true;
|
||||
this._error = error;
|
||||
this._result = result;
|
||||
|
||||
if (this._callback !== null) {
|
||||
this._callback(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user