28 Commits
Author SHA1 Message Date
Arnavion b13173112d master 2016-05-04 00:18:56 -07:00
Arnavion 7293203dc2 Also capture window "error" events in page console.
Fixes #78
2016-05-04 00:12:26 -07:00
Arnavion 5f75c60eb0 Added accept attribute to file inputs. 2016-04-09 18:39:51 -07:00
Arnavion 8ec552464c Minor refactoring. 2016-03-20 17:16:44 -07:00
Arnavion 90a16c6350 Use libjass.configure 2016-03-19 23:17:55 -07:00
Arnavion dbe2dddc8b Ask user if their browser supports SVG filter effects, and set enableSvg accordingly.
Fixes #71
2016-03-19 23:16:25 -07:00
Arnavion 4cde2a0b5e Proper main page. 2016-03-10 22:13:28 -08:00
Arnavion 9dc9e3e53c Not just for HTML5 video. 2016-03-10 22:12:52 -08:00
Arnavion 36abcf4b66 v0.11.0 2016-01-24 02:33:06 -08:00
Arnavion 8ef48c7c95 Removed fullscreen handling. 2016-01-23 11:26:04 -08:00
Arnavion aa4e9eb018 Added inputs for arbitrary dummy video width and height. 2016-01-23 11:24:47 -08:00
Arnavion 7b3190956d Select sample video and sample ASS by default. 2016-01-18 00:36:06 -08:00
Arnavion 958da9931f Move inputs inside labels. 2016-01-18 00:34:22 -08:00
Arnavion 85542b38a4 Added a sample video that goes with the sample ASS script. 2016-01-02 00:04:26 -08:00
Arnavion b278de84c8 Preload a sample script. 2016-01-01 20:41:13 -08:00
Arnavion e761cd6444 Implemented dummy video functionality.
See #60
2015-12-31 16:22:32 -08:00
Arnavion 7bf9101b21 Fixed console.warn HTML logger. 2015-12-31 16:22:17 -08:00
Arnavion c97fba6ba8 Set enableSvg to false on IE. 2015-12-27 21:10:13 -08:00
Arnavion 6d2fe37eb2 Demo page improvements.
See #60
2015-12-27 16:34:29 -08:00
Arnavion 36f6600d6c v0.10.0 2015-05-05 21:50:51 -07:00
Arnavion 903e0dd0a7 v0.9.0 2014-11-27 00:22:36 -08:00
Arnavion d74f6e7b87 v0.8.0 2014-08-16 22:02:27 -07:00
Arnavion d7e97098f2 v0.7.0 2014-05-15 15:27:32 -07:00
Arnavion b71ec3c608 v0.6.0 2014-04-04 00:42:49 -07:00
Arnavion 02a70f667c v0.5.0 2014-01-26 11:44:56 -08:00
Arnavion 66969efa30 v0.4.0 2013-12-27 15:14:00 -08:00
Arnavion 19e24deed9 Added autogenerated API documentation. 2013-10-27 17:31:10 -07:00
Arnav Singh 063ab543d9 Create gh-pages branch via GitHub 2013-09-16 23:48:20 -07:00
40 changed files with 21623 additions and 12731 deletions
-7
View File
@@ -1,7 +0,0 @@
node_modules/
libjass.js
libjass.min.js
*.map
*.log
-5
View File
@@ -1,5 +0,0 @@
language: node_js
node_js:
- "0.10"
script:
- "./node_modules/.bin/gulp doc"
-11
View File
@@ -1,11 +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
-92
View File
@@ -1,92 +0,0 @@
### v0.9.0 - 2014/11/27
- BREAKING CHANGE - ASS.fromString() now returns a Promise of an ASS object, not an ASS object directly. The synchronous ASS parser used by ASS.fromString() is no more.
- BACKWARD-COMPATIBLE CHANGE - WebRenderer constructor parameters order has changed from (ass, clock, settings, libjassSubsWrapper) to (ass, clock, libjassSubsWrapper, settings). The constructor will detect the old order and reorder accordingly.
- Added ASS.fromStream and ASS.fromXhr that read a stream and an XMLHttpRequest object's response respectively and return (a Promise of) an ASS object. Both of these parse the script asynchronously.
- Added RendererSettings.enableSvg that can be used to toggle the use of SVG filter effects for outlines and blur.
- libjass.js now has an AMD wrapper so that it can be used with RequireJS, etc.
- Settings parameter is now optional for WebRenderer and DefaultRenderer.
- Added support for clock rates apart from 1 to clocks and renderers.
- Added a parameter to libjass.createWorker to specify the path to libjass.js that will run in the worker.
- Fixed Style and Dialogue constructors not setting defaults for missing properties.
- Fixed color and alpha parser to support more formats.
- Fixed SRT parser to replace all HTML tags it finds, instead of just the first one.
- Fixed font size calculation to use the vertical scale instead of horizontal.
- Fixed line-height on newlines.
- Fixed missing perspective on X and Y rotations.
### v0.8.0 - 2014/08/16
- Added web worker support. libjass.parse can now be offloaded to a web worker.
- Implemented \fs+ and \fs-
- Added ASS.addEvent() to add dialogue lines to an ASS object.
- Renamed ClockEvent.TimeUpdate to ClockEvent.Tick, and added ClockEvent.Stop
- Clock.enable() and .disable() now return a boolean to indicate whether the function had any effect.
- Added Clock.setEnabled() to force the enabled-state to the given value.
- Renamed ManualClock.timeUpdate() to ManualClock.tick()
- Moved WebRenderer.enable(), .disable() and .enabled to NullRenderer
- Fixed not being able to parse tags with default values.
- Fixed font preloader downloading the same font multiple times because it didn't filter for duplicates.
- Fixed min-width value not taking separate left and right margins into account.
- Fixed absolutely positioned subs were always left-aligned even if they had an alignment tag.
- Fixed blur and outlines getting truncated.
### v0.7.0 - 2014/05/15
- Implemented \be
- Split a new renderer, WebRenderer, off DefaultRenderer that doesn't rely on a video element.
- All renderers now require a Clock to generate time events. VideoClock is a Clock backed by a video element, while ManualClock is a clock that can be used to generate arbitrary time events.
### v0.6.0 - 2014/03/24
- All script properties and style properties are now parsed and stored in the ASS and Style objects.
- Basic SRT support, by passing in a libjass.Format argument to ASS.fromString()
- \clip and \iclip now have their drawing instructions parsed as an array of libjass.parts.drawing.Instruction's instead of just a string.
- Added DefaultRenderer.enable(), DefaultRenderer.disable() and DefaultRenderer.toggle() to change whether the renderer is displaying subtitles or not.
- DefaultRenderer.resizeVideo is now called DefaultRenderer.resize. Now it only resizes the subtitle wrapper div, not the video element.
- Replaced the 41ms setInterval-bsed timer with a requestAnimationFrame-based timer to reduce load on minimized or hidden browser tabs.
- DefaultRenderer now renders dialogues in the correct order according to the script.
- Fixed incorrect font sizes.
- Replaced jake with gulp.
### v0.5.0 - 2014/01/26
- Removed preLoadFonts renderer setting. It was redundant with the actual fontMap setting since the presence or absence of that setting is enough to signal whether the user wants to preload fonts or not.
- Multiple renderers can now be used on the same page without conflicting with each other.
- Implemented \shad, \xshad, \yshad
- Fixed ASS draw scale being used incorrectly.
- ASS.resolutionX and ASS.resolutionY are now properties of ASS.properties, a ScriptProperties object.
### v0.4.0 - 2013/12/27
- All parts moved from the libjass.tags namespace to the libjass.parts namespace.
- Replaced PEG.js parser with a hand-written one. This allows for parsing lines that are strictly invalid grammar but are parsed successfully by VSFilter or libass.
- All ASS tags are now supported by the parser.
- Removed the useHighResolutionTimer setting for DefaultRenderer. DefaultRenderer always uses the 41ms timer now.
- Implemented \move
- Implemented ASS draw
- Fixed subs overflowing the video dimensions still being visible.
- SVG filters are now used for outlines and blur.
- Delay parsing of dialogue lines till they need to be pre-rendered. As a side-effect, all fonts in the font map are preloaded now, not just the ones used in the current script.
### v0.3.0 - 2013/10/28
- Moved libjass.DefaultRenderer to libjass.renderers.DefaultRenderer
- Added libjass.renderers.NullRenderer, a renderer that doesn't render anything.
- DefaultRenderer's fontMap setting is now a Map instead of an Object. It now supports more than one URL for each font name.
- DefaultRenderer now generates the subtitle wrapper div itself.
- DefaultRenderer now takes video letterboxing into account when resizing the subtitles.
- DefaultRenderer has a new setting useHighResolutionTimer that makes it use a 41ms timer instead of video.timeUpdate's 250ms timer.
- div IDs and CSS class names are now prefixed with "libjass-" to avoid collisions with other elements on the page.
- All numeric CSS property values are now truncated to three decimal places.
- Added ```jake watch``` that rebuilds and runs tests on changes to the source.
- Added ```jake doc``` that builds API documentation.
- Added Travis CI build.
### v0.2.0 - 2013/09/11
- Added libjass.DefaultRenderer, a class that handles initializing the layer div's, preloading fonts, and drawing Dialogues based on the current video time.
- libjass.js can now be loaded in node. Only the parser can be used.
- Tests can now be run with ```jake test``` or ```npm test``` using Mocha.
### v0.1.0 - 2013/08/29
- First npm release.
-202
View File
@@ -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.
-138
View File
@@ -1,138 +0,0 @@
[![Build Status](https://travis-ci.org/Arnavion/libjass.png?branch=master)](https://travis-ci.org/Arnavion/libjass)
libjass is a JavaScript library written in TypeScript to render ASS subs on HTML5 video in the browser. [Check out the demo.](http://arnavion.github.io/libjass/demo/index.xhtml)
### 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.
* gulpfile.js is the build script. The build command will use this script to build libjass.js
* libjass.css is a CSS file that you'll need to deploy to your website.
### 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.fromUrl()](http://arnavion.github.io/libjass/api.xhtml#libjass.ASS.fromUrl) takes in a URL to an ASS script and returns a promise that resolves to an [ASS](http://arnavion.github.io/libjass/api.xhtml#libjass.ASS) object. This ASS object represents the script properties, the line styles and dialogue lines in it. Alternatively, you can use [ASS.fromString()](http://arnavion.github.io/libjass/api.xhtml#libjass.ASS.fromString) to convert a string of the script contents into an ASS object.
* Next, you initialize a renderer to render the subtitles. libjass ships with an easy-to-use renderer, the [DefaultRenderer](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.DefaultRenderer). It 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 can be told to dynamically change the size of the subtitles based on user input by calling [WebRenderer.resize()](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.WebRenderer.resize) DefaultRenderer also handles resizing the subtitles automatically when the user clicks the browser's native fullscreen-video button.
* Lastly, the renderer contains an implementation of preloading fonts before playing the video. It uses a map of font names to URLs - this map can be conveniently created from a CSS file containing @font-face rules using [RendererSettings.makeFontMapFromStyleElement()](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.RendererSettings.makeFontMapFromStyleElement)
* For an example of using libjass, check out [the demo.](http://arnavion.github.io/libjass/demo/index.xhtml)
### 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.
Entire scripts can be parsed via [ASS.fromString()](http://arnavion.github.io/libjass/api.xhtml#libjass.ASS.fromString)
### 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. See [#21](https://github.com/Arnavion/libjass/issues/21)
* 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.
```
+10596
View File
File diff suppressed because it is too large Load Diff
+204
View File
@@ -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);
}
+26 -9
View File
@@ -18,12 +18,29 @@
* 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" />
body {
margin: 0;
padding: 10px;
box-sizing: border-box;
}
.libjass-subs {
z-index: 2147483647;
}
#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
View File
@@ -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);
});
+227
View File
@@ -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>
+7 -4
View File
@@ -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 * {
@@ -94,8 +92,13 @@
border: 0;
margin: 0;
padding: 0;
line-height: normal;
}
.libjass-filters {
display: block;
}
.libjass-filters * {
color-interpolation-filters: sRGB;
}
+9980
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
-162
View File
@@ -1,162 +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 newComments = [];
if (current instanceof TypeScript.AST.Namespace) {
current.members.forEach(visitor);
}
else if (current instanceof TypeScript.AST.Constructor) {
newComments.push("@constructor");
if (current.baseType !== null) {
newComments.push(
"@extends {" +
current.baseType.type.fullName +
(current.baseType.generics.length > 0 ? ("." + current.baseType.generics.join(", ")) : "") +
"}"
);
}
if (current.parent !== null) {
newComments.push("@memberOf " + current.parent.fullName);
}
current.members.forEach(visitor);
}
else if (current instanceof TypeScript.AST.Interface) {
if (current.baseTypes.length > 0) {
current.baseTypes.forEach(function (baseType) {
newComments.push(
"@extends {" +
baseType.type.fullName +
(baseType.generics.length > 0 ? ("." + baseType.generics.join(", ")) : "") +
"}"
);
});
}
}
else if (current instanceof TypeScript.AST.Enum) {
newComments.push("@enum");
}
if ((current.generics !== undefined) && (current.generics.length > 0)) {
newComments.push("@template " + current.generics.join(", "));
}
if (current.isPrivate) {
newComments.push("@private");
}
if (current.isProtected) {
newComments.push("@protected");
}
if (current.isStatic) {
newComments.push("@static");
}
if (newComments.length > 0) {
current.astNode["gulp-typescript-new-comment"] = newComments;
}
};
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("demo", ["libjass.js"], function () {
return gulp.src(["./libjass.js", "./libjass.js.map", "./libjass.css"]).pipe(gulp.dest("../libjass-gh-pages/demo/"));
});
gulp.task("doc", ["libjass.js"], function () {
return gulp.src("./libjass.ts").pipe(Doc("./api.xhtml")).pipe(gulp.dest("../libjass-gh-pages/"));
});
-692
View File
@@ -1,692 +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;
};
var sorter = (function () {
var visibilitySorter = function (value1, value2) {
if (value1.isPrivate === value2.isPrivate && value1.isProtected === value2.isProtected) {
return 0;
}
if (value1.isPrivate) {
return 1;
}
if (value2.isPrivate) {
return -1;
}
if (value1.isProtected) {
return 1;
}
if (value2.isProtected) {
return -1;
}
return 0;
};
var types = [TypeScript.AST.Variable, TypeScript.AST.Property, TypeScript.AST.Function, TypeScript.AST.Interface, TypeScript.AST.Constructor, TypeScript.AST.Enum];
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 indenter = function (indent) {
return function (line) {
return ((line === "") ? line : (Array(indent + 1).join("\t") + line));
};
};
var sanitize = function (string) {
return string.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
};
var toVariableName = function (item) {
// TODO: Handle non-letters (are both their toLowerCase() and toUpperCase())
var name = item.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;
};
var toUsageName = function (item) {
if (item instanceof TypeScript.AST.Constructor || item instanceof TypeScript.AST.Interface || item instanceof TypeScript.AST.Enum) {
if (item.isPrivate) {
return item.name;
}
return item.fullName;
}
if (item.parent instanceof TypeScript.AST.Namespace) {
if (item.isPrivate) {
return item.name;
}
return item.fullName;
}
if (item.isStatic) {
return toUsageName(item.parent) + '.' + item.name;
}
return toVariableName(item.parent) + '.' + item.name;
};
var toId = function (item) {
return sanitize((item.fullName === undefined) ? item.name : item.fullName);
};
var toLink = function (item) {
return (
'<a href="#' + toId(((item instanceof TypeScript.AST.TypeReference) ? item.type : item)) + '">' +
sanitize(
((item instanceof TypeScript.AST.TypeReference) ? item.type.name : item.name) +
((item.generics !== undefined && item.generics.length > 0) ? ('.<' + item.generics.join(', ') + '>') : '')
) +
'</a>'
);
};
var writeDescription = function (text) {
var result = sanitize(text).replace(/\{@link ([^} ]+)\}/g, function (substring, linkTarget) {
return '<a href="#' + linkTarget + '">' + linkTarget + '</a>';
});
var inCodeBlock = false;
result = result.split("\n").map(function (line) {
if (line.substr(0, " ".length) === " ") {
line = line.substr(" ".length);
if (!inCodeBlock) {
inCodeBlock = true;
line = '<pre class="code"><code>' + line;
}
}
else if (line.length > 0 && inCodeBlock) {
inCodeBlock = false;
line = '</code></pre>' + line;
}
return line;
}).join("\n");
if (inCodeBlock) {
result += '</code></pre>';
}
return result;
};
var writeParameters = function (parameters) {
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">' + writeDescription(parameter.description) + '</dd>'
].concatMany(writeParameters(parameter.subParameters).map(indenter(2)));
})).concat([
' </dl>',
'</dd>'
]);
};
TypeScript.AST.Function.prototype.toHtml = function () {
return [
'<dl id="' + toId(this) + '" class="function' +
(this.isAbstract ? ' abstract' : '') +
(this.isPrivate ? ' private' : '') +
(this.isProtected ? ' protected' : '') +
(this.isStatic ? ' static' : '') +
'">',
' <dt class="name">' + toLink(this) + '</dt>',
' <dd class="description">',
' <p>' + writeDescription(this.description) + '</p>',
' </dd>',
' <dd class="usage"><fieldset><legend /><pre><code>' +
sanitize(
((this.returnType !== null) ? 'var result = ' : '') + toUsageName(this) + '(' +
this.parameters.map(function (parameter) {
return parameter.name;
}).join(', ') + ');'
) +
'</code></pre></fieldset></dd>'
].concat(writeParameters(this.parameters).map(indenter(1))).concat(
(this.returnType === null) ? [] : [
' <dt>Returns</dt>',
' <dd class="return type">' + sanitize(this.returnType.type) + '</dd>',
' <dd class="return description">' + writeDescription(this.returnType.description) + '</dd>'
]
).concat([
'</dl>'
]);
};
TypeScript.AST.Interface.prototype.toHtml = function () {
return [
'<dl id="' + toId(this) + '" class="interface' +
(this.isPrivate ? ' private' : '') +
'">',
' <dt class="name">interface ' + toLink(this) + ((this.baseTypes.length > 0) ? (' extends ' + this.baseTypes.map(function (baseType) { return toLink(baseType); }).join(', ')) : '') + '</dt>',
' <dd class="description">',
' <p>' + writeDescription(this.description) + '</p>',
' </dd>',
' <dd class="members">'
].concatMany(this.members.map(function (member) {
return member.toHtml().map(indenter(2));
})).concat([
' </dd>',
'</dl>'
]);
};
TypeScript.AST.Constructor.prototype.toHtml = function () {
return [
'<dl id="' + toId(this) + '" class="constructor' +
(this.isAbstract ? ' abstract' : '') +
(this.isPrivate ? ' private' : '') +
'">',
' <dt class="name">class ' + toLink(this) + ((this.baseType !== null) ? (' extends ' + toLink(this.baseType)) : '') + '</dt>',
' <dd class="description">',
' <p>' + writeDescription(this.description) + '</p>',
' </dd>',
' <dd class="usage"><fieldset><legend /><pre><code>' +
sanitize(
'var ' + toVariableName(this) + ' = ' + 'new ' + toUsageName(this) + '(' +
this.parameters.map(function (parameter) {
return parameter.name;
}).join(', ') +
');'
) + '</code></pre></fieldset></dd>'
].concat(writeParameters(this.parameters).map(indenter(1))).concat([
' <dd class="members">'
]).concatMany(this.members.map(function (member) {
return member.toHtml().map(indenter(2));
})).concat([
' </dd>',
'</dl>'
]);
};
TypeScript.AST.Enum.prototype.toHtml = function () {
return [
'<dl id="' + toId(this) + '" class="enum' +
(this.isPrivate ? ' private' : '') +
'">',
' <dt class="name">enum <a href="#' + sanitize(this.fullName) + '">' + sanitize(this.name) + '</a></dt>',
' <dd class="description">',
' <p>' + writeDescription(this.description) + '</p>',
' </dd>'
].concat([
' <dd class="members">'
]).concatMany(this.members.map(function (member) {
return [
' <dl id="' + toId(member) + '" class="member">',
' <dt class="name">' + toLink(member) + ' = ' + member.value + '</dt>',
' <dd class="description">',
' <p>' + writeDescription(member.description) + '</p>',
' </dd>',
' </dl>'
];
})).concat([
' </dd>',
'</dl>'
]);
};
TypeScript.AST.Variable.prototype.toHtml = function () {
return [
'<dl id="' + toId(this) + '" class="variable">',
' <dt class="name">' + toLink(this) + '</dt>',
' <dd class="description">',
' <p>' + writeDescription(this.description) + '</p>',
' </dd>',
' <dd class="usage"><fieldset><legend /><pre><code>' +
sanitize('var result = ' + toUsageName(this) + ';\n' +
toUsageName(this) + ' = value;') +
'</code></pre></fieldset></dd>',
' <dd class="return type">' + sanitize(this.type) + '</dd>',
'</dl>'
];
};
TypeScript.AST.Property.prototype.toHtml = function () {
return [
'<dl id="' + toId(this) + '" class="property">',
' <dt class="name">' + toLink(this) + '</dt>'
].concat((this.getter === null) ? [] : [
' <dt class="getter">Getter</dt>',
' <dd class="description">',
' <p>' + writeDescription(this.getter.description) + '</p>',
' </dd>',
' <dd class="usage"><fieldset><legend /><pre><code>' + sanitize('var result = ' + toUsageName(this) + ';') + '</code></pre></fieldset></dd>',
' <dd class="return type">' + sanitize(this.getter.type) + '</dd>'
]).concat((this.setter === null) ? [] : [
' <dt class="setter">Setter</dt>',
' <dd class="description">',
' <p>' + writeDescription(this.setter.description) + '</p>',
' </dd>',
' <dd class="usage"><fieldset><legend /><pre><code>' + sanitize(toUsageName(this) + ' = value;') + '</code></pre></fieldset></dd>'
].concat(writeParameters([new TypeScript.AST.Parameter("value", "", this.setter.type)]).map(indenter(1)))).concat([
'</dl>'
]);
};
module.exports = function (outputFilePath) {
var compiler = new TypeScript.Compiler();
return Transform(function (file, encoding) {
compiler.addFile(file);
}, function () {
// Compile
compiler.compile();
// Walk
var namespaces = TypeScript.AST.walk(compiler);
// Make HTML
var namespaceNames = Object.keys(namespaces).filter(function (namespaceName) {
return namespaceName.substr(0, "libjass".length) === "libjass";
}).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);
});
});
this.push(new Vinyl({
path: outputFilePath,
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, .enum, .property {',
' margin-left: 30px;',
' padding: 10px;',
' }',
'',
' .getter, .setter {',
' font-size: large;',
' }',
'',
' section > .variable:nth-child(2n), section > .function:nth-child(2n), section > .interface:nth-child(2n), section > .constructor:nth-child(2n), section > .enum: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), section > .enum: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 fieldset {',
' min-width: initial;',
' overflow-x: auto;',
' }',
'',
' .usage pre {',
' margin: 0;',
' }',
'',
' .interface .variable, .interface .function, .interface .property,',
' .constructor .variable, .constructor .function, .constructor .property, .enum .member {',
' 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 ";',
' }',
'',
' .protected > .name:before {',
' content: "protected ";',
' }',
'',
' .static > .name:before {',
' content: "static ";',
' }',
'',
' .abstract.private > .name:before {',
' content: "abstract private ";',
' }',
'',
' .private.static > .name:before {',
' content: "static private ";',
' }',
'',
' .abstract.protected > .name:before {',
' content: "abstract protected ";',
' }',
'',
' .protected.static > .name:before {',
' content: "static protected ";',
' }',
'',
' body:not(.show-private) .private, body:not(.show-private) .protected {',
' display: none;',
' }',
'',
' .description .code {',
' margin-left: 30px;',
' }',
' ]]>',
' </style>',
' <script>',
' <![CDATA[',
' addEventListener("DOMContentLoaded", function () {',
' document.querySelector("#show-private").addEventListener("change", function (event) {',
' document.body.className = (event.target.checked ? "show-private" : "");',
' }, false);',
'',
' showPrivateIfNecessary();',
' }, false);',
'',
' function showPrivateIfNecessary() {',
' var jumpToElement = document.querySelector("[id=\\"" + location.hash.substr(1) + "\\"]");',
' if (jumpToElement !== null && jumpToElement.offsetHeight === 0) {',
' document.querySelector("#show-private").click()',
' jumpToElement.scrollIntoView();',
' }',
' }',
'',
' addEventListener("hashchange", showPrivateIfNecessary, false);',
' ]]>',
' </script>',
' </head>',
' <body>',
' <nav class="namespaces">',
' <label><input type="checkbox" id="show-private" />Show private</label>',
' <h2>Namespaces</h2>'
].concat([].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>',
''
]);
}))).concat([
' </nav>',
' <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 enums = namespaces[namespaceName].members.filter(function (value) {
return value instanceof TypeScript.AST.Enum;
});
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 value.toHtml().map(indenter(5));
})).concat([
' </section>',
''
]);
}
if (functions.length > 0) {
result = result.concat([
' <section>',
' <h2>Free functions</h2>'
]).concatMany(functions.map(function (value) {
return value.toHtml().map(indenter(5));
})).concat([
' </section>',
''
]);
}
if (interfaces.length > 0) {
result = result.concat([
' <section>',
' <h2>Interfaces</h2>'
]).concatMany(interfaces.map(function (value) {
return value.toHtml().map(indenter(5));
})).concat([
' </section>',
''
]);
}
if (constructors.length > 0) {
result = result.concat([
' <section>',
' <h2>Classes</h2>'
]).concatMany(constructors.map(function (value) {
return value.toHtml().map(indenter(5));
})).concat([
' </section>',
''
]);
}
if (enums.length > 0) {
result = result.concat([
' <section>',
' <h2>Enums</h2>'
]).concatMany(enums.map(function (value) {
return value.toHtml().map(indenter(5));
})).concat([
' </section>',
''
]);
}
result = result.concat([
' </section>'
]);
return result;
})).concat([
' </div>',
' </body>',
'</html>'
]).map(function (line) {
return new Buffer(line + "\n");
}))
}));
});
};
-92
View File
@@ -1,92 +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) {
if (!(ex instanceof Error)) {
ex = new Error(ex.message + "\nStack:\n" + ex.stack);
}
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;
}
};
};
-41
View File
@@ -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
-396
View File
@@ -1,396 +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 = UglifyJS.parse(
'(function (define) {\n' +
' var global = this;\n' +
' define(["exports"], function (libjass) {\n' +
'"$ORIG";\n' +
' });\n' +
'})((typeof define !== "undefined" && define.amd) ? define : function (/* ujs:unreferenced */ names, body) {\n' +
' body((typeof module !== "undefined") ? module.exports : (this.libjass = Object.create(null)));\n' +
'});'
).transform(new UglifyJS.TreeTransformer(function (node) {
if (node instanceof UglifyJS.AST_Directive && node.value === "$ORIG") {
return UglifyJS.MAP.splice(root.body);
}
}));
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 });
}
if (["BorderStyle", "ClockEvent", "WorkerCommands", "WrappingStyle"].indexOf(node.name) === -1) {
console.warn("Unused variable %s at %s:%s:%s", node.name, node.start.file, node.start.line, node.start.col);
}
}
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);
}
}));
// 5. Fixup anonymous functions to print a space after "function"
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (node instanceof UglifyJS.AST_Lambda && !node.name) {
node.name = Object.create(UglifyJS.AST_Node.prototype);
node.name.print = function () { };
}
}));
// 6. Find the first license header
var licenseHeader = null;
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (licenseHeader !== null) {
return;
}
if (node.start) {
(node.start.comments_before || []).some(function (comment) {
if (comment.value.indexOf("Copyright") !== -1) {
licenseHeader = comment;
node.start.comments_before = [];
return true;
}
return false;
});
}
}));
root.start.comments_before = [licenseHeader];
root.start.nlb = true;
// Output
var output = {
source_map: UglifyJS.SourceMap({
file: path.basename(sourceMapFile.path),
orig: sourceMapFile.contents.toString()
}),
beautify: true,
comments: function (node, comment) {
// If this is the first license header, keep it.
if (comment === licenseHeader) {
return true;
}
// If this is a license header, remove it.
if (comment.value.indexOf("Copyright") !== -1) {
return false;
}
// If this is a TypeScript reference comment, strip it.
if (comment.value.substr(0, "/<reference path=".length) === "/<reference path=") {
return false;
}
// UJS shifts multi-line comments eight spaces left, so shift each line except the first one eight 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: function (node, comment) {
if (!firstLicenseHeaderFound && comment.value.indexOf("Copyright") !== -1) {
firstLicenseHeaderFound = true;
// Align the license header correctly.
var lines = comment.value.split("\n");
lines = [lines[0]].concat(lines.slice(1).map(function (line) { return line.replace(/^ +/g, " "); }));
comment.value = lines.join('\n');
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;
}
if (this instanceof UglifyJS.AST_SymbolCatch) {
return false;
}
return originalSymbolUnreferenced.call(this);
};
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>libjass</title>
</head>
<body>
<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>
-33
View File
@@ -1,33 +0,0 @@
{
"name": "libjass",
"version": "0.9.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": "2.0.x",
"typescript": "1.3.0",
"uglify-js": "2.x >=2.4.14",
"vinyl": "latest"
}
}
-2560
View File
File diff suppressed because it is too large Load Diff
-1441
View File
File diff suppressed because it is too large Load Diff
-531
View File
@@ -1,531 +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.
*/
export class EventSource<T> {
/**
* A map from event type to an array of all the listeners registered for that event type.
*
* @type {!Map.<T, !Array.<Function>>}
*/
_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);
}
/**
* Calls all listeners registered for the given event type.
*
* @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);
});
}
}
}
/**
* The type of clock event.
*/
export enum ClockEvent {
Play,
Tick,
Pause,
Stop,
RateChange,
}
/**
* The clock interface. A clock is used by a renderer as a source of {@link libjass.renderers.ClockEvent}s.
*/
export interface Clock extends EventSource<ClockEvent> {
/**
* @type {number}
*/
currentTime: number;
/**
* @type {boolean}
*/
enabled: boolean;
/**
* Gets the rate of the clock - how fast the clock ticks compared to real time.
*
* @type {number}
*/
rate: number;
/**
* 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
/**
* @param {number} type
* @param {!Function} listener
*/
addEventListener(type: ClockEvent, listener: Function): void;
}
/**
* An implementation of {@link 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;
private _rate: number = 1;
/**
* Trigger a {@link libjass.renderers.ClockEvent.Play}
*/
play(): void {
this._dispatchEvent(ClockEvent.Play, []);
}
/**
* Trigger a {@link libjass.renderers.ClockEvent.Tick}
*
* @param {number} currentTime
*/
tick(currentTime: number): void {
this._currentTime = currentTime;
this._dispatchEvent(ClockEvent.Tick, []);
}
/**
* Trigger a {@link libjass.renderers.ClockEvent.Pause}
*/
pause(): void {
this._dispatchEvent(ClockEvent.Pause, []);
}
/**
* Trigger a {@link libjass.renderers.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;
}
/**
* Gets the rate of the clock - how fast the clock ticks compared to real time.
*
* @type {number}
*/
get rate(): number {
return this._rate;
}
/**
* 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();
}
}
/**
* Sets the rate of the clock - how fast the clock ticks compared to real time.
*
* @param {number} rate The new rate of the clock.
*/
setRate(rate: number): void {
this._rate = rate;
this._dispatchEvent(ClockEvent.RateChange, []);
}
// EventSource members
/**
* @type {!Map.<T, !Array.<Function>>}
*/
_eventListeners: Map<ClockEvent, Function[]> = new Map<ClockEvent, Function[]>();
/**
* @type {function(number, !Function)}
*/
addEventListener: (type: ClockEvent, listener: Function) => void;
/**
* @type {function(number, Array.<*>)}
*/
_dispatchEvent: (type: ClockEvent, args: Object[]) => void;
}
mixin(ManualClock, [EventSource]);
/**
* The state of the video.
*/
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);
this._video.addEventListener("ratechange", () => this._onVideoRateChange(), false);
}
// Clock members
/**
* @type {number}
*/
get currentTime(): number {
return this._currentTime;
}
/**
* @type {boolean}
*/
get enabled(): boolean {
return this._enabled;
}
/**
* Gets the rate of the clock - how fast the clock ticks compared to real time.
*
* @type {number}
*/
get rate(): number {
return this._video.playbackRate;
}
/**
* 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 _onVideoRateChange(): void {
this._dispatchEvent(ClockEvent.RateChange, []);
}
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
/**
* @type {!Map.<T, !Array.<Function>>}
*/
_eventListeners: Map<ClockEvent, Function[]> = new Map<ClockEvent, Function[]>();
/**
* @type {function(number, !Function)}
*/
addEventListener: (type: ClockEvent, listener: Function) => void;
/**
* @type {function(number, Array.<*>)}
*/
_dispatchEvent: (type: ClockEvent, args: Object[]) => void;
}
mixin(VideoClock, [EventSource]);
}
-105
View File
@@ -1,105 +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 {
/**
* @type {Element}
*/
fullscreenElement: Element;
/**
* @type {Element}
*/
mozFullScreenElement: Element;
/**
* @type {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), document.createElement("div"), settings);
this._video.parentElement.replaceChild(this.libjassSubsWrapper, this._video);
this.libjassSubsWrapper.insertBefore(this._video, this.libjassSubsWrapper.firstElementChild);
}
/**
* @deprecated
*
* @param {number} width
* @param {number} height
*/
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);
}
protected _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();
}
/**
* @param {!Element} fullScreenElement
*/
private _onFullScreenChange(fullScreenElement: Element): void {
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]);
}
}
}
}
-325
View File
@@ -1,325 +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());
this._clock.addEventListener(ClockEvent.RateChange, () => this._onClockRateChange());
}
/**
* 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.
*/
protected _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.
*/
protected _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.
*/
protected _onClockPause(): void {
if (libjass.verboseMode) {
console.log("NullRenderer._onClockPause");
}
}
/**
* Runs when the clock is disabled.
*/
protected _onClockStop(): void {
if (libjass.verboseMode) {
console.log("NullRenderer._onClockStop");
}
}
/**
* Runs when the clock changes its rate.
*/
protected _onClockRateChange(): void {
if (libjass.verboseMode) {
console.log("NullRenderer._onClockRateChange");
}
}
}
/**
* 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 {@link libjass.renderers.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;
/**
* Outlines and blur are implemented using SVG filters by default. When false, they will be rendered using alternative means.
*
* IE 11 and below do not support SVG filters on HTML elements so this should be set to false there.
*
* Defaults to true.
*
* @type {boolean}
*/
enableSvg: boolean;
/**
* A convenience method to create a font map from a <style> or <link> element that contains @font-face rules. There should be one @font-face rule for each font name, mapping to a font file URL.
*
* For example:
*
* @font-face {
* font-family: "Helvetica";
* src: url("/fonts/helvetica.ttf");
* }
*
* @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 {@link libjass.renderers.RendererSettings} object.
*
* @param {*} object
* @return {!libjass.renderers.RendererSettings}
*/
static from(object?: any): RendererSettings {
if (object === undefined || object === null) {
object = {};
}
return RendererSettings._from(object.fontMap, object.preRenderTime, object.preciseOutlines, object.enableSvg);
}
/**
* @param {Map.<string, !Array.<string>>=null} fontMap
* @param {number=5} preRenderTime
* @param {boolean=false} preciseOutlines
* @param {boolean=true} enableSvg
* @return {!libjass.renderers.RendererSettings}
*/
private static _from(fontMap: Map<string, string[]> = null, preRenderTime: number = 5, preciseOutlines: boolean = false, enableSvg: boolean = true): RendererSettings {
var result = new RendererSettings();
result.fontMap = fontMap;
result.preRenderTime = preRenderTime;
result.preciseOutlines = preciseOutlines;
result.enableSvg = enableSvg;
return result;
}
/**
* @param {string} str
* @return {string}
*/
private static _stripQuotes(str: string): string {
return str.match(/^["']?(.*?)["']?$/)[1];
}
}
}
-1560
View File
File diff suppressed because it is too large Load Diff
-408
View File
@@ -1,408 +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) {
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) {
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) {
var message;
if (test.passed === true) {
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) {
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 {
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) {
message = numFailed + " of " + numTotal + " tests failed.";
console.warn(message);
append(totalDiv, message).className = "failed";
}
if (numTotal > numPassed + numFailed) {
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);
-47
View File
@@ -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>
-71
View File
@@ -1,71 +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) {
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;
}
-227
View File
@@ -1,227 +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");
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")
]);
test("SRT", function (done) {
this.customProperties = {
rule: "SRT",
input: "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>"
};
libjass.ASS.fromString(this.customProperties.input, libjass.Format.SRT).then(function (ass) {
try {
assert.deepEqual(ass.dialogues.length, 2);
assert.deepEqual(ass.dialogues[0].start, 10.500);
assert.deepEqual(ass.dialogues[0].end, 13.000);
assert.deepEqual(ass.dialogues[0].parts, [
new libjass.parts.Text("Elephant's Dream")
]);
assert.deepEqual(ass.dialogues[1].start, 15.000);
assert.deepEqual(ass.dialogues[1].end, 18.000);
assert.deepEqual(ass.dialogues[1].parts, [
new libjass.parts.Text("<font color=\"cyan\">At the left we can see..."),
new libjass.parts.PrimaryColor(null)
]);
done();
}
catch (ex) {
done(ex);
}
}, function (reason) {
done(reason);
});
});
test("SRT", function (done) {
this.customProperties = {
rule: "SRT",
input: "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"
};
libjass.ASS.fromString(this.customProperties.input, libjass.Format.SRT).then(function (ass) {
try {
assert.deepEqual(ass.dialogues.length, 2);
assert.deepEqual(ass.dialogues[0].start, 1 * 60 + 15.940);
assert.deepEqual(ass.dialogues[0].end, 1 * 60 + 17.280);
assert.deepEqual(ass.dialogues[0].parts, [
new libjass.parts.Text("Have you secured the key?"),
new libjass.parts.NewLine(),
new libjass.parts.Text("Why can't people forgive each other")
]);
assert.deepEqual(ass.dialogues[1].start, 1 * 60 + 17.280);
assert.deepEqual(ass.dialogues[1].end, 1 * 60 + 17.670);
assert.deepEqual(ass.dialogues[1].parts, [
new libjass.parts.Text("Why can't people forgive each other")
]);
done();
}
catch (ex) {
done(ex);
}
}, function (reason) {
done(reason);
});
});
});
-84
View File
@@ -1,84 +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("Less than six digits", "&H71F&", "color", new libjass.parts.Color(31, 7, 0, 1));
parserTest("Eight digits", "&H3F171F00&", "color", new libjass.parts.Color(0, 31, 23, 1));
parserTest("Eight digits", "&H3F171FFF&", "color", new libjass.parts.Color(255, 31, 23, 1));
parserTest("More than eight digits", "&HAAAA3F171F00&", "color", new libjass.parts.Color(255, 255, 255, 1));
parserTest("More than eight digits", "&HAAAA3F171FFF&", "color", new libjass.parts.Color(255, 255, 255, 1));
});
suite("Alpha", function () {
parserTest("Starts with &H", "&HFF&", "alpha", 0);
parserTest("Starts with &", "&FF&", "alpha", 0);
parserTest("Starts with &H, one digit", "&HF&", "alpha", 1 - 15 / 255);
parserTest("Starts with &H, one digit", "&H0&", "alpha", 1);
parserTest("Starts with &, one digit", "&F&", "alpha", 1 - 15 / 255);
parserTest("Starts with &, one digit", "&0&", "alpha", 1);
parserTest("Starts with &H, doesn't end with &", "&HF&", "alpha", 1 - 15 / 255);
parserTest("Starts with &H, doesn't end with &", "&H0&", "alpha", 1);
parserTest("Starts with &, doesn't end with &", "&F", "alpha", 1 - 15 / 255);
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", 1);
});
suite("ColorWithAlpha", function () {
parserTest("Starts with &H", "&H00434441", "colorWithAlpha", new libjass.parts.Color(65, 68, 67, 1));
parserTest("Starts with &H, non-zero alpha", "&HF0434441", "colorWithAlpha", new libjass.parts.Color(65, 68, 67, 1 - 240 / 255));
parserTest("Starts with &H, zero alpha", "&HFF434441", "colorWithAlpha", new libjass.parts.Color(65, 68, 67, 0));
parserTest("Less than six digits", "&H71F", "colorWithAlpha", new libjass.parts.Color(31, 7, 0, 1));
parserTest("Eight digits", "&H3F171F00", "colorWithAlpha", new libjass.parts.Color(0, 31, 23, 1 - 63 / 255));
parserTest("Eight digits", "&H3F171FFF", "colorWithAlpha", new libjass.parts.Color(255, 31, 23, 1 - 63 / 255));
parserTest("More than eight digits", "&HAAAA3F171F00", "colorWithAlpha", new libjass.parts.Color(255, 255, 255, 0));
parserTest("More than eight digits", "&HAAAA3F171FFF", "colorWithAlpha", new libjass.parts.Color(255, 255, 255, 0));
});
});
-173
View File
@@ -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", [
]);
});
});
-90
View File
@@ -1,90 +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 () {
if (libjass.webworker.supported) {
workerChannel = libjass.webworker.createWorker("../libjass.js");
}
});
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 (value) {
try {
assert.deepEqual(value, [
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);
}
}, function (reason) {
done(reason);
});
});
}
else {
test("Parse: Web workers not supported in this environment.");
}
if (libjass.webworker.supported) {
test("Parse failure", function (done) {
this.customProperties = {
rule: "tag_a",
input: "a4"
};
workerChannel.request(libjass.webworker.WorkerCommands.Parse, {
input: this.customProperties.input,
rule: this.customProperties.rule
}).then(function (value) {
done(new Error("Expected parse to fail."));
}, function (reason) {
done();
});
});
}
else {
test("Parse failure: Web workers not supported in this environment.");
}
});
});
-856
View File
@@ -1,856 +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 {
/**
* This class represents an ASS script. It contains the {@link libjass.ScriptProperties}, an array of {@link libjass.Style}s, and an array of {@link libjass.Dialogue}s.
*/
export class ASS {
private _properties: ScriptProperties = new ScriptProperties();
private _styles: Map<string, Style> = new Map<string, Style>();
private _dialogues: Dialogue[] = [];
private _stylesFormatSpecifier: string[] = null;
private _dialoguesFormatSpecifier: string[] = null;
/**
* The properties of this script.
*
* @type {!libjass.ScriptProperties}
*/
get properties(): ScriptProperties {
return this._properties;
}
/**
* The styles in this script.
*
* @type {!Map.<string, !libjass.Style>}
*/
get styles(): Map<string, Style> {
return this._styles;
}
/**
* The dialogues in this script.
*
* @type {!Array.<!libjass.Dialogue>}
*/
get dialogues(): Dialogue[] {
return this._dialogues;
}
/**
* The format specifier for the styles section.
*
* @type {!Array.<string>}
*/
get stylesFormatSpecifier(): string[] {
return this._stylesFormatSpecifier;
}
/**
* The format specifier for the styles section.
*
* @type {!Array.<string>}
*/
get dialoguesFormatSpecifier(): string[] {
return this._dialoguesFormatSpecifier;
}
/**
* The format specifier for the events section.
*
* @type {!Array.<string>}
*/
set stylesFormatSpecifier(value: string[]) {
this._stylesFormatSpecifier = value;
}
/**
* The format specifier for the events section.
*
* @type {!Array.<string>}
*/
set dialoguesFormatSpecifier(value: string[]) {
this._dialoguesFormatSpecifier = value;
}
constructor() {
// Deprecated constructor argument
if (arguments.length === 1) {
throw new Error("Constructor `new ASS(rawASS)` has been deprecated. Use `ASS.fromString(rawASS)` instead.");
}
}
/**
* Add a style to this ASS script.
*
* @param {string} line The line from the script that contains the new style.
*/
addStyle(line: string): void {
var styleLine = parser.parseLineIntoTypedTemplate(line, this._stylesFormatSpecifier);
if (styleLine === null || styleLine.type !== "Style") {
return;
}
var styleTemplate = styleLine.template;
if (libjass.verboseMode) {
var repr = "";
styleTemplate.forEach((value, key) => repr += key + " = " + value + ", ");
console.log("Read style: " + repr);
}
// Create the dialogue and add it to the dialogues array
var style = new Style(styleTemplate);
this._styles.set(style.name, style);
}
/**
* 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 dialogueLine = parser.parseLineIntoTypedTemplate(line, this._dialoguesFormatSpecifier);
if (dialogueLine === null || dialogueLine.type !== "Dialogue") {
return;
}
var dialogueTemplate = dialogueLine.template;
if (libjass.verboseMode) {
var repr = "";
dialogueTemplate.forEach((value, key) => repr += key + " = " + value + ", ");
console.log("Read dialogue: " + repr);
}
// 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 {@link libjass.Format} constants.
* @return {!Promise.<!libjass.ASS>}
*/
static fromString(raw: string, type: Format = Format.ASS): Promise<ASS> {
if ((<{ [index: string]: any }><any>Format)[Format[type]] !== type) {
throw new Error("Illegal value of type: " + type);
}
return ASS.fromStream(new parser.StringStream(raw), type);
}
/**
* Creates an ASS object from the given {@link libjass.parser.Stream}.
*
* @param {!libjass.parser.Stream} stream The stream to parse the script from
* @param {number=0} type The type of the script. One of the {@link libjass.Format} constants.
* @return {!Promise.<!libjass.ASS>} A promise that will be resolved with the ASS object when it has been fully parsed
*/
static fromStream(stream: parser.Stream, type: Format = Format.ASS): Promise<ASS> {
switch (type) {
case Format.ASS:
return new parser.StreamParser(stream).ass;
case Format.SRT:
return new parser.SrtStreamParser(stream).ass;
default:
throw new Error("Illegal value of type: " + type);
}
}
/**
* Creates an ASS object from the given URL.
*
* @param {string} url The URL of the script.
* @param {number=0} type The type of the script. One of the {@link libjass.Format} constants.
* @return {!Promise.<!libjass.ASS>} A promise that will be resolved with the ASS object when it has been fully parsed
*/
static fromUrl(url: string, type: Format = Format.ASS): Promise<ASS> {
if ((<{ [index: string]: any }><any>Format)[Format[type]] !== type) {
throw new Error("Illegal value of type: " + type);
}
var xhr = new XMLHttpRequest();
var result = ASS.fromStream(new parser.XhrStream(xhr), type);
xhr.open("GET", url, true);
xhr.send();
return result;
}
}
/**
* The format of the string passed to {@link libjass.ASS.fromString}
*/
export enum Format {
ASS,
SRT
}
/**
* The wrapping style defined in the {@link libjass.ScriptProperties}
*/
export enum WrappingStyle {
SmartWrappingWithWiderTopLine = 0,
SmartWrappingWithWiderBottomLine = 3,
EndOfLineWrapping = 1,
NoLineWrapping = 2,
}
/**
* This class represents the properties of a {@link libjass.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. One of the {@link libjass.WrappingStyle} constants.
*
* @type {number}
*/
get wrappingStyle(): WrappingStyle {
return this._wrappingStyle;
}
/**
* The wrap style. One of the {@link libjass.WrappingStyle} constants.
*
* @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;
}
}
/**
* The border style defined in the {@link libjass.Style} properties.
*/
export enum BorderStyle {
Outline = 1,
OpaqueBox = 3,
}
/**
* This class represents a single global style declaration in a {@link libjass.ASS} script. The styles can be obtained via the {@link libjass.ASS.styles} property.
*
* @param {!Map.<string, string>} 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: Map<string, string>) {
this._name = template.get("Name");
if (this._name === undefined || this._name === null || this._name.constructor !== String) {
throw new Error("Style doesn't have a name.");
}
this._italic = template.get("Italic") === "-1";
this._bold = template.get("Bold") === "-1";
this._underline = template.get("Underline") === "-1";
this._strikeThrough = template.get("StrikeOut") === "-1";
this._fontName = template.get("Fontname");
this._fontSize = _valueOrDefault(template, "Fontsize", parseFloat, value => !isNaN(value), "50");
this._fontScaleX = _valueOrDefault(template, "ScaleX", parseFloat, value => !isNaN(value), "100") / 100;
this._fontScaleY = _valueOrDefault(template, "ScaleY", parseFloat, value => !isNaN(value), "100") / 100;
this._letterSpacing = _valueOrDefault(template, "Spacing", parseFloat, value => !isNaN(value), "0");
this._rotationZ = _valueOrDefault(template, "Angle", parseFloat, value => !isNaN(value), "0");
this._primaryColor = _valueOrDefault(template, "PrimaryColour", str => <parts.Color>parser.parse(str, "colorWithAlpha"), null, "&H0000FFFF");
this._secondaryColor = _valueOrDefault(template, "SecondaryColour", str => <parts.Color>parser.parse(str, "colorWithAlpha"), null, "&H00000000");
this._outlineColor = _valueOrDefault(template, "OutlineColour", str => <parts.Color>parser.parse(str, "colorWithAlpha"), null, "&H00000000");
this._shadowColor = _valueOrDefault(template, "BackColour", str => <parts.Color>parser.parse(str, "colorWithAlpha"), null, "&H00000000");
this._outlineThickness = _valueOrDefault(template, "Outline", parseFloat, value => !isNaN(value), "1");
this._borderStyle = _valueOrDefault(template, "BorderStyle", parseInt, value => !isNaN(value), "1");
this._shadowDepth = _valueOrDefault(template, "Shadow", parseFloat, value => !isNaN(value), "1");
this._alignment = _valueOrDefault(template, "Alignment", parseInt, value => !isNaN(value), "2");
this._marginLeft = _valueOrDefault(template, "MarginL", parseFloat, value => !isNaN(value), "80");
this._marginRight = _valueOrDefault(template, "MarginR", parseFloat, value => !isNaN(value), "80");
this._marginVertical = _valueOrDefault(template, "MarginV", parseFloat, value => !isNaN(value), "35");
}
/**
* 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 a {@link libjass.ASS} script.
*
* @param {!Map.<string, string>} 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: Map<string, string>, ass: ASS) {
this._id = ++Dialogue._lastDialogueId;
var style = template.get("Style");
this._style = ass.styles.get(style);
if (this._style === undefined) {
throw new Error("Unrecognized style " + style);
}
this._start = Dialogue._toTime(template.get("Start"));
this._end = Dialogue._toTime(template.get("End"));
this._layer = Math.max(_valueOrDefault(template, "Layer", parseInt, value => !isNaN(value), "0"), 0);
this._rawPartsString = template.get("Text");
if (this._rawPartsString === undefined || this._rawPartsString === null || this._rawPartsString.constructor !== String) {
throw new Error("Dialogue doesn't have any 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;
}
/**
* The default style of this dialogue.
*
* @type {!libjass.Style}
*/
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 {@link libjass.parts} of this dialogue.
*
* @type {!Array.<!libjass.parts.Part>}
*/
get parts(): parts.Part[] {
if (this._parts === null) {
this._parsePartsString();
}
return this._parts;
}
/**
* @return {string} A simple representation of this dialogue's properties and parts.
*/
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 property.
*/
export interface Property {
/**
* @type {string}
*/
name: string;
/**
* @type {string}
*/
value: string;
}
/**
* A template object with a particular type.
*/
export interface TypedTemplate {
/**
* @type {string}
*/
type: string;
/**
* @type {!Map.<string, string>}
*/
template: Map<string, string>;
}
/**
* @param {!Map.<string, string>} template
* @param {string} key
* @param {function(string):T} converter
* @param {?function(T):boolean} validator
* @param {T} defaultValue
* @return {T}
*/
function _valueOrDefault<T>(template: Map<string, string>, key: string, converter: (str: string) => T, validator: (value: T) => boolean, defaultValue: string): T {
var value = template.get(key);
if (value === undefined) {
return converter(defaultValue);
}
try {
var numValue = converter(value);
if (validator !== null && !validator(numValue)) {
throw new Error("Validation failed.");
}
return numValue;
}
catch (ex) {
throw new Error("Property " + key + " has invalid value " + value + " - " + ex.stack);
}
}
}
-703
View File
@@ -1,703 +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 Promise<T> {
/**
* @param {function(T):U} fulfilledHandler
* @param {?function(*):U} rejectedHandler
* @return {!Promise.<U>}
*/
then<U>(fulfilledHandler: (value: T) => U, rejectedHandler?: (reason: any) => U): Promise<U>;
/**
* @param {function(T):!Promise.<U>} fulfilledHandler
* @param {?function(*):!Promise.<U>} rejectedHandler
* @return {!Promise.<U>}
*/
then<U>(fulfilledHandler: (value: T) => Promise<U>, rejectedHandler?: (reason: any) => Promise<U>): Promise<U>;
/**
* @param {function(T):U} fulfilledHandler
* @param {?function(*):!Promise.<U>} rejectedHandler
* @return {!Promise.<U>}
*/
then<U>(fulfilledHandler: (value: T) => U, rejectedHandler?: (reason: any) => Promise<U>): Promise<U>;
/**
* @param {function(T):!Promise.<U>} fulfilledHandler
* @param {?function(*):U} rejectedHandler
* @return {!Promise.<U>}
*/
then<U>(fulfilledHandler: (value: T) => Promise<U>, rejectedHandler?: (reason: any) => U): Promise<U>;
}
interface Global {
/**
* @type {function(new:Set.<T>, !Array.<T>=)}
*/
Set: {
new <T>(iterable?: T[]): Set<T>; prototype: Set<any>
};
/**
* @type {function(new:Map.<K, V>, !Array.<!Array.<*>>=)}
*/
Map: {
new <K, V>(iterable?: [K, V][]): Map<K, V>; prototype: Map<any, any>
};
/**
* @type {function(new:Promise.<T>, function(T), function(*)), resolve: function(T):!Promise.<T>, all: function(!Array.<!Promise.<T>>):!Promise.<!Array.<T>>}}
*/
Promise: {
new <T>(resolver: (fulfill: (value: T) => void, reject: (reason: any) => void) => void): Promise<T>;
resolve<T>(value: T): Promise<T>;
all<T>(promises: Promise<T>[]): Promise<T[]>;
};
}
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.
*
* @param {!Array.<T>=} iterable Only an array of values is supported.
*/
class SimpleSet<T> implements Set<T> {
private _elements: { [key: string]: T };
private _size: number;
constructor(iterable?: T[]) {
this.clear();
if (Array.isArray(iterable)) {
iterable.forEach(value => this.add(value));
}
}
/**
* @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.
* @param {*} thisArg
*/
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);
});
}
/**
* Not implemented.
*
* @param {T} value
* @return {boolean}
*/
delete(value: T): boolean {
throw new Error("This Set implementation doesn't support delete().");
}
/**
* @type {number}
*/
get size(): number {
return this._size;
}
/**
* Converts the given value into a property name for the internal map.
*
* @param {T} value
* @return {string}
*/
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 {@link libjass.SimpleSet}
*
* @type {function(new:Set, !Array.<T>=)}
*/
export var Set = global.Set;
if (Set === undefined || typeof Set.prototype.forEach !== "function" || new Set([1, 2]).size !== 2) {
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.
*
* @param {!Array.<!Array.<*>>=} iterable Only an array of elements (where each element is a 2-tuple of key and value) is supported.
*/
class SimpleMap<K, V> implements Map<K, V> {
private _keys: { [key: string]: K };
private _values: { [key: string]: V };
private _size: number;
constructor(iterable?: [K, V][]) {
this.clear();
if (Array.isArray(iterable)) {
iterable.forEach(element => this.set(element[0], element[1]));
}
}
/**
* @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.
* @param {*} thisArg
*/
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;
}
/**
* Converts the given key into a property name for the internal map.
*
* @param {K} key
* @return {string}
*/
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 {@link libjass.SimpleMap}
*
* @type {function(new:Map, !Array.<!Array.<*>>=)}
*/
export var Map = global.Map;
if (Map === undefined || typeof Map.prototype.forEach !== "function" || new Map([[1, "foo"], [2, "bar"]]).size !== 2) {
Map = <any>SimpleMap;
}
/**
* Promise implementation for browsers that don't support it.
*
* @param {function(function(T), function(*))} resolver
*/
class SimplePromise<T> implements Promise<T> {
private _state: SimplePromiseState = SimplePromiseState.PENDING;
private _thens: { propagateFulfilling: (value: T) => void; propagateRejection: (reason: any) => void }[] = [];
private _pendingPropagateTimeout: number = null;
private _alreadyFulfilledValue: T = null;
private _alreadyRejectedReason: any = null;
constructor(private _resolver: (resolve: (value: T) => void, reject: (reason: any) => void) => void) {
try {
_resolver(value => this._resolve(value), reason => this._reject(reason));
}
catch (ex) {
this._reject(ex);
}
}
/**
* @param {function(T):(U|Promise.<U>)} fulfilledHandler
* @param {?function(*):(U|Promise.<U>)} rejectedHandler
* @return {!Promise.<U>}
*/
then<U>(fulfilledHandler: (value: T) => U, rejectedHandler: (reason: any) => U): Promise<U> {
fulfilledHandler = (typeof fulfilledHandler === "function") ? fulfilledHandler : null;
rejectedHandler = (typeof rejectedHandler === "function") ? rejectedHandler : null;
if (fulfilledHandler === null && rejectedHandler === null) {
return <any>this;
}
if (fulfilledHandler === null) {
fulfilledHandler = value => <U><any>value;
}
if (rejectedHandler === null) {
rejectedHandler = (reason): U => { throw reason; };
}
var result = new SimplePromise<U>((resolve, reject) => {
this._thens.push({
propagateFulfilling: value => {
try {
resolve(fulfilledHandler(value));
}
catch (ex) {
reject(ex);
}
}, propagateRejection: reason => {
try {
resolve(rejectedHandler(reason));
}
catch (ex) {
reject(ex);
}
}
});
});
if ((this._state === SimplePromiseState.FULFILLED || this._state === SimplePromiseState.REJECTED) && this._pendingPropagateTimeout === null) {
this._pendingPropagateTimeout = setTimeout(() => this._propagateResolution(), 0);
}
return result;
}
/**
* @return {boolean}
*/
isFulfilled(): boolean {
return (this._state !== SimplePromiseState.FULFILLED);
}
/**
* @return {boolean}
*/
isRejected(): boolean {
return (this._state !== SimplePromiseState.REJECTED);
}
/**
* @return {boolean}
*/
isPending(): boolean {
return (this._state !== SimplePromiseState.PENDING);
}
/**
* @return {T}
*/
value(): T {
if (this._state !== SimplePromiseState.FULFILLED) {
throw new Error("This promise is not in FULFILLED state.");
}
return this._alreadyFulfilledValue;
}
/**
* @return {*}
*/
reason(): any {
if (this._state !== SimplePromiseState.REJECTED) {
throw new Error("This promise is not in FULFILLED state.");
}
return this._alreadyRejectedReason;
}
/**
* @param {T} value
* @return {!Promise.<T>}
*/
static resolve<T>(value: T): Promise<T> {
return new SimplePromise<T>(resolve => resolve(value));
}
/**
* @param {!Array.<!Promise.<T>>} promises
* @return {!Promise.<!Array.<T>>}
*/
static all<T>(promises: Promise<T>[]): Promise<T[]> {
return new Promise<T[]>((resolve, reject) => {
var result: T[] = [];
var numUnresolved = promises.length;
if (numUnresolved === 0) {
resolve(result);
return;
}
promises.forEach((promise, index) => promise.then(value => {
result[index] = value;
numUnresolved--;
if (numUnresolved === 0) {
resolve(result);
}
}), reject);
});
}
/**
* @param {T} value
*/
private _resolve(value: T): void {
var alreadyCalled = false;
try {
if (<any>value === this) {
throw new TypeError("2.3.1");
}
var thenMethod = SimplePromise._getThenMethod<T>(value);
if (thenMethod === null) {
this._fulfill(value);
return;
}
thenMethod.call(
<Promise<T>><any>value,
(value: T) => {
if (alreadyCalled) {
return;
}
alreadyCalled = true;
this._resolve(value);
},
(reason: any) => {
if (alreadyCalled) {
return;
}
alreadyCalled = true;
this._reject(reason);
});
}
catch (ex) {
if (alreadyCalled) {
return;
}
this._reject(ex);
}
}
/**
* @param {T} value
*/
private _fulfill(value: T): void {
if (this._state !== SimplePromiseState.PENDING) {
return;
}
this._state = SimplePromiseState.FULFILLED;
this._alreadyFulfilledValue = value;
if (this._pendingPropagateTimeout === null) {
this._pendingPropagateTimeout = setTimeout(() => this._propagateResolution(), 0);
}
}
/**
* @param {*} reason
*/
private _reject(reason: any): void {
if (this._state !== SimplePromiseState.PENDING) {
return;
}
this._state = SimplePromiseState.REJECTED;
this._alreadyRejectedReason = reason;
if (this._pendingPropagateTimeout === null) {
this._pendingPropagateTimeout = setTimeout(() => this._propagateResolution(), 0);
}
}
/**
* @param {!*} obj
* @return {?function(function(T):T, function(*):T):!Promise.<T>}
*/
private static _getThenMethod<T>(obj: any): (fulfilledHandler: (value: T) => T, rejectedHandler: (reason: any) => T) => Promise<T> {
if (typeof obj !== "object" && typeof obj !== "function") {
return null;
}
if (obj === null || obj === undefined) {
return null;
}
var then: any = obj.then;
if (typeof then !== "function") {
return null;
}
return then;
}
/**
* Propagates the result of the current promise to all its children.
*/
private _propagateResolution(): void {
this._pendingPropagateTimeout = null;
if (this._state === SimplePromiseState.FULFILLED) {
while (this._thens.length > 0) {
var nextThen = this._thens.shift();
nextThen.propagateFulfilling(this._alreadyFulfilledValue);
}
}
else if (this._state === SimplePromiseState.REJECTED) {
while (this._thens.length > 0) {
var nextThen = this._thens.shift();
nextThen.propagateRejection(this._alreadyRejectedReason);
}
}
}
}
/**
* The state of the {@link libjass.SimplePromise}
*/
enum SimplePromiseState {
PENDING = 0,
FULFILLED = 1,
REJECTED = 2,
}
/**
* Set to browser's implementation of Promise if it has one, else set to {@link libjass.SimplePromise}
*
* @type {function(new:Promise)}
*/
export var Promise = global.Promise;
if (Promise === undefined) {
Promise = <any>SimplePromise;
}
/**
* A deferred promise.
*/
export class DeferredPromise<T> {
private _promise: Promise<T>;
private _resolve: (value: T) => void;
private _reject: (reason: any) => void;
constructor() {
this._promise = new Promise<T>((resolve, reject) => {
this._resolve = resolve;
this._reject = reject;
});
}
/**
* @type {!Promise.<T>}
*/
get promise(): Promise<T> {
return this._promise;
}
/**
* @param {T} value
*/
resolve(value: T): void {
this._resolve(value);
}
/**
* @param {*} reason
*/
reject(reason: any): void {
this._reject(reason);
}
}
/**
* Adds properties of the given mixins' prototypes to the given class's prototype.
*
* @param {!*} clazz
* @param {!Array.<*>} mixins
*/
export function mixin(clazz: any, mixins: any[]): void {
mixins.forEach((mixin: any) => {
Object.getOwnPropertyNames(mixin.prototype).forEach(name => {
clazz.prototype[name] = mixin.prototype[name];
});
});
}
/**
* 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 {@link libjass.debugMode}
*
* @type {boolean}
*/
export var verboseMode: boolean = false;
}
-329
View File
@@ -1,329 +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 {
/**
* @type {!HTMLScriptElement}
*/
currentScript: HTMLScriptElement;
}
interface WorkerGlobalScope {
/**
* @param {*} message
*/
postMessage(message: any): void;
/**
* @param {string} type
* @param {function(*)} listener
* @param {boolean} useCapture
*/
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 {@link libjass.webworker.createWorker}
*/
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 {!Promise.<*>} A promise that will get resolved when the other side computes the result
*/
request(command: WorkerCommands, parameters: any): Promise<any>;
}
/**
* Create a new web worker and returns a {@link libjass.webworker.WorkerChannel} to it.
*
* @param {string=} scriptPath The path to libjass.js to be loaded in the web worker. If the browser supports document.currentScript, the parameter is optional and, if not provided,
* the path will be determined from the src attribute of the <script> element that contains the currently running copy of libjass.js
* @return {!libjass.webworker.WorkerChannel} A communication channel to the new web worker.
*/
export function createWorker(scriptPath: string = _scriptNode.src): WorkerChannel {
return new WorkerChannelImpl(new Worker(scriptPath));
}
/**
* 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 command in {@link libjass.webworker.WorkerCommands}
*/
export interface WorkerCommandHandler {
(parameters: any, response: WorkerResultCallback): void;
}
/**
* The signature of a callback called by a {@link libjass.webworker.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. One of the {@link libjass.webworker.WorkerCommands} constants.
* @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): void {
workerCommands.set(command, handler);
}
/**
* Registers a prototype as a deserializable type.
*
* @param {!*} prototype
*/
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>();
/**
* The interface implemented by a communication channel to the other side.
*/
interface WorkerCommunication {
/**
* @param {"message"} type
* @param {function(!MessageEvent): *} listener
* @param {?boolean} useCapture
*/
addEventListener(type: "message", listener: (ev: MessageEvent) => any, useCapture?: boolean): void;
/**
* @param {string} type
* @param {!EventListener} listener
* @param {?boolean} useCapture
*/
addEventListener(type: string, listener: EventListener, useCapture?: boolean): void;
/**
* @param {*} message
*/
postMessage(message: any): void;
}
/**
* The interface implemented by a request sent to the other side of the communication channel.
*/
interface WorkerRequestMessage {
/**
* An internal identifier for this request. Used to connect responses to their corresponding requests.
*
* @type {number}
*/
requestId: number;
/**
* The command type of this request.
*
* @type {number}
*/
command: WorkerCommands;
/**
* Any parameters serialized with this request.
*
* @type {*}
*/
parameters: any;
}
/**
* The interface implemented by a response received from the other side of the communication channel.
*/
interface WorkerResponseMessage {
/**
* An internal identifier for this response. Used to connect responses to their corresponding requests.
*
* @type {number}
*/
requestId: number;
/**
* Set if the computation of this response resulted in an error.
*
* @type {*}
*/
error: any;
/**
* The result of computing this response.
*
* @type {*}
*/
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 static _lastRequestId: number = -1;
private _pendingRequests = new Map<number, DeferredPromise<any>>();
constructor(private _comm: WorkerCommunication) {
this._comm.addEventListener("message", ev => this._onMessage(<string>ev.data), false);
}
/**
* @param {number} command
* @param {*} parameters
* @return {!Promise.<*>}
*/
request(command: WorkerCommands, parameters: any): Promise<any> {
var deferred = new DeferredPromise<any>();
var requestId = ++WorkerChannelImpl._lastRequestId;
this._pendingRequests.set(requestId, deferred);
var requestMessage: WorkerRequestMessage = { requestId: requestId, command: command, parameters: parameters };
this._comm.postMessage(WorkerChannelImpl._toJSON(requestMessage));
return deferred.promise;
}
/**
* @param {number} requestId
*/
cancelRequest(requestId: number): void {
var deferred = this._pendingRequests.get(requestId);
if (deferred === undefined) {
return;
}
this._pendingRequests.delete(requestId);
deferred.reject(new Error("Cancelled."));
}
/**
* @param {!WorkerResponseMessage} message
*/
private _respond(message: WorkerResponseMessage): void {
this._comm.postMessage(WorkerChannelImpl._toJSON({ command: WorkerCommands.Response, requestId: message.requestId, error: message.error, result: message.result }));
}
/**
* @param {string} rawMessage
*/
private _onMessage(rawMessage: string): void {
var message = <{ command: WorkerCommands }>WorkerChannelImpl._fromJSON(rawMessage);
if (message.command === WorkerCommands.Response) {
var responseMessage = <WorkerResponseMessage><any>message;
var deferred = this._pendingRequests.get(responseMessage.requestId);
if (deferred !== undefined) {
this._pendingRequests.delete(responseMessage.requestId);
if (responseMessage.error === null) {
deferred.resolve(responseMessage.result);
}
else {
deferred.reject(responseMessage.error);
}
}
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 }));
}
/**
* @param {*} obj
* @return {string}
*/
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;
});
}
/**
* @param {string} str
* @return {*}
*/
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);
}
}