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
38 changed files with 21638 additions and 9117 deletions
-7
View File
@@ -1,7 +0,0 @@
node_modules/
libjass.js
libjass.min.js
*.map
*.log
-3
View File
@@ -1,3 +0,0 @@
language: node_js
node_js:
- "0.10"
-25
View File
@@ -1,25 +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
npm install
This will install the dependencies - [Jake](https://github.com/mde/jake), [TypeScript](http://www.typescriptlang.org/) and [UglifyJS2](https://github.com/mishoo/UglifyJS2). It will then run Jake to build libjass.js and use UglifyJS2 to minify it into libjass.min.js
1. Set the URLs of the video and the ASS file in index.xhtml
<video id="video" src=" <URL OF VIDEO HERE> " controls="">
<track src=" <URL OF SCRIPT HERE> " kind="metadata" data-format="ass" />
</video>
1. Set the URLs of any fonts you want to make available in fonts.css
@font-face {
font-family: " <NAME OF FONT HERE> ";
src: url(" <URL OF FONT HERE> ");
}
for each font. The name of the font is what it's called in the ASS file.
1. Start your web server and navigate to index.xhtml in a browser.
-30
View File
@@ -1,30 +0,0 @@
desc("Build libjass.js, libjass.min.js and their sourcemaps");
task("default", ["_default:writeCode", "_default:writeSourceMap", "_default:writeMinifiedCode", "_default:writeMinifiedSourceMap"], function () {
console.log("[" + this.fullName + "]");
});
desc("Clean");
task("clean", ["_clean:run"], function () {
console.log("[" + this.fullName + "]");
});
desc("Test");
task("test", ["_test:run[true]"], function () {
console.log("[" + this.fullName + "]");
});
desc("Watch");
task("watch", ["_watch:run"], function () {
console.log("[" + this.fullName + "]");
});
desc("Generate API documentation");
task("doc", [], function (outputFilename) {
console.log("[" + this.fullName + "]");
if (outputFilename === undefined) {
throw new Error("outputFilename not specified.");
}
jake.Task["_doc:writeHtml"].invoke(outputFilename);
});
-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.
-108
View File
@@ -1,108 +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.
### 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 &lt;div&gt; and &lt;span&gt; elements. This allows all the layout and rendering to be handled by the browser instead of requiring complex and costly drawing and animation code. For example, libjass uses CSS3 animations to simulate tags such as \fad. While a canvas-drawing library would have to re-draw such a subtitle on the canvas for every frame of the video, libjass only renders it once and lets the browser render the fade effect.
As a result, libjass is able to render subtitles with very low CPU usage. The downside to libjass's aproach is that it is hard (and potentially impossible) to map all effects possible in ASS (using \t, ASS draw) etc. into DOM elements. As of now, the subset of tags supported by libjass has no such problems.
### What are all these files?
* The .ts files are the source of libjass. They are TypeScript files and must be compiled into JavaScript for the browser using the TypeScript compiler.
* The rest of the files - index.xhtml, index.js, index.css and fonts.css - are a sample implementation of how to use libjass on a web page. They demonstrate the API to call, how to place &lt;div&gt; elements to render the subs, etc.
### I want to use libjass for my website. What do I need to do?
1. You need to build libjass.js using the instructions in BUILD.md
1. You need to load libjass.js on the page with your video.
1. You need to call the libjass API.
Only libjass.js is 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 constructor ASS() takes in the raw ASS string and returns an object representing the script information, the line styles and dialogue lines in it. The example index.js uses XHR to get this data using the URL specified in a track tag.
* index.js initializes a default renderer that libjass ships with, the DefaultRenderer. This renderer uses information from the ASS object to build up a series of div elements around the video tag. There is a wrapper (#libjass-subs) containing div's corresponding to the 9 alignment directions, 9 for each layer in the ASS script. index.css contains styles for these div's to render them at the correct location.
* The renderer starts a timer that ticks ever 41ms. 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 result into the appropriate layer+alignment div.
* The renderer handles resizing the video and subs when the user clicks the browser's native fullscreen-video button. index.js also contains code to change the size of the video based on user input.
* Lastly, the renderer contains an implementation of preloading all the fonts used in the ASS file. It uses a map of font names to URLs provided by index.js - this map is contained in fonts.css in the form of @font-family rules.
### 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 hop by 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, Outline, Alignment, MarginL, MarginR, MarginV
* Tags: \i, \b, \u, \s, \bord, \xbord, \ybord, \blur, \fn, \fs, \fscx, \fscy, \fsp, \frx, \fry, \frz, \fax, \fay, \c, \1c, \3c, \alpha, \1a, \3a, \an, \r, \pos, \fad
* Custom fonts, using CSS web fonts.
## Known bugs
* \an4, \an5, \an6 aren't positioned correctly.
* Unsupported tags: Everything else, notably \t.
* Font sizes aren't pixel perfect.
* \blur uses an approximation instead of Gaussian blur.
* ASS draw is unsupported.
* Subs don't show over a fullscreen video in IE.
## Planned improvements
* Document browser compatibility. Currently libjass is tested on IE11 (Windows 7), Firefox Nightly and Google Chrome (Dev channel).
* Write API documentation. Add more explanatory comments to the code.
* Write more parser tests. Also figure out a way to test layout.
* Evaluate (document, benchmark) the benefits and drawbacks of DOM+CSS-based drawing over canvas.
# License
```
libjass
https://github.com/Arnavion/libjass
Copyright 2013 Arnav Singh
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```
+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);
}
+16 -1
View File
@@ -20,12 +20,27 @@
body {
margin: 0;
padding: 10px;
box-sizing: border-box;
}
.libjass-subs {
z-index: 2147483647;
}
.settings-form > fieldset {
#settings-form > fieldset {
display: inline-block;
}
#console > .warning {
color: orange;
}
#console > .error {
color: red;
}
ul.choices-list {
list-style-type: none;
padding-left: 0;
}
+566
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>
+39 -45
View File
@@ -20,10 +20,7 @@
.libjass-wrapper {
position: relative;
}
.libjass-wrapper.libjass-full-screen {
position: static;
overflow: hidden;
}
.libjass-subs {
@@ -37,74 +34,71 @@
animation-fill-mode: both !important;
}
.libjass-subs > * {
position: absolute;
width: 100%;
}
.libjass-subs.paused * {
-webkit-animation-play-state: paused !important;
animation-play-state: paused !important;
}
.an1, .an2, .an3 {
.libjass-subs .an {
position: absolute;
}
.libjass-subs .an1, .libjass-subs .an2, .libjass-subs .an3 {
bottom: 0;
}
.an4, .an5, .an6 {
.libjass-subs .an4, .libjass-subs .an5, .libjass-subs .an6 {
display: table;
width: 100%;
height: 100%;
}
.an4 > *, .an5 > *, .an6 > * {
.libjass-subs .an4 > *, .libjass-subs .an5 > *, .libjass-subs .an6 > * {
display: table-cell;
top: 50%;
vertical-align: middle;
}
.an7, .an8, .an9 {
.libjass-subs .an7, .libjass-subs .an8, .libjass-subs .an9 {
top: 0;
}
.libjass-subs span {
white-space: pre-wrap;
.libjass-subs .an1, .libjass-subs .an4, .libjass-subs .an7 {
text-align: left;
}
.libjass-subs div {
.libjass-subs .an2, .libjass-subs .an5, .libjass-subs .an8 {
text-align: center;
}
.libjass-subs .an3, .libjass-subs .an6, .libjass-subs .an9 {
text-align: right;
}
.libjass-subs {
line-height: 0;
}
@-webkit-keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
/* Filter wrapper span */
.libjass-subs div[data-dialogue-id] > span {
-webkit-perspective-origin: center;
-webkit-perspective: 400px;
perspective-origin: center;
perspective: 400px;
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
.libjass-font-measure {
position: absolute;
visibility: hidden;
border: 0;
margin: 0;
padding: 0;
line-height: normal;
}
@-webkit-keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
.libjass-filters {
display: block;
}
@keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
.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.
-571
View File
@@ -1,571 +0,0 @@
@font-face {
font-family: "???? Pro";
src: url("/fonts/Yryr%20minc%20R.ttf");
}
@font-face {
font-family: "123 and...";
src: url("/fonts/123-and...-Regular.ttf");
}
@font-face {
font-family: "123and";
src: url("/fonts/123-and...-Regular.ttf");
}
@font-face {
font-family: "Amienne";
src: url("/fonts/Amienne.ttf");
}
@font-face {
font-family: "Arial";
src: url("/fonts/arial.ttf");
}
@font-face {
font-family: "ArialMT";
src: url("/fonts/arial.ttf");
}
@font-face {
font-family: "ArtificeSSK";
src: url("/fonts/ArtificeSSK.ttf");
}
@font-face {
font-family: "Baar Sophia";
src: url("/fonts/BAARS.TTF");
}
@font-face {
font-family: "BaarSophia";
src: url("/fonts/BAARS.TTF");
}
@font-face {
font-family: "Babylon 27";
src: url("/fonts/Babylon-27-Babylon-27.ttf");
}
@font-face {
font-family: "Bee Marker Ink";
src: url("/fonts/BeeMarkerInk.TTF");
}
@font-face {
font-family: "BeeMarkerInk";
src: url("/fonts/BeeMarkerInk.TTF");
}
@font-face {
font-family: "BlueBrush";
src: url("/fonts/BlueBrush.otf");
}
@font-face {
font-family: "bylon 27";
src: url("/fonts/Babylon-27-Babylon-27.ttf");
}
@font-face {
font-family: "CAC Moose";
src: url("/fonts/CAC%20Moose.ttf");
}
@font-face {
font-family: "CACMoose";
src: url("/fonts/CAC%20Moose.ttf");
}
@font-face {
font-family: "CenturyOldStyle-Light";
src: url("/fonts/CENTURYO.TTF"), url("/fonts/CenturyOldStyle-Light.ttf");
}
@font-face {
font-family: "CenturyOldStyle-Light";
src: url("/fonts/CenturyOldStyle-Light%20Bold.ttf");
font-weight: bold;
}
@font-face {
font-family: "CenturyOldStyle-Light-Bold";
src: url("/fonts/CenturyOldStyle-Light%20Bold.ttf");
font-weight: bold;
}
@font-face {
font-family: "Charme";
src: url("/fonts/CHARME.TTF");
}
@font-face {
font-family: "Chinacat";
src: url("/fonts/chinrg.ttf");
}
@font-face {
font-family: "Complete in Him";
src: url("/fonts/Complete%20in%20Him.ttf");
}
@font-face {
font-family: "CompleteinHim";
src: url("/fonts/Complete%20in%20Him.ttf");
}
@font-face {
font-family: "Consolas";
src: url("/fonts/consola.ttf");
}
@font-face {
font-family: "Consolas";
src: url("/fonts/consolab.ttf");
font-weight: bold;
}
@font-face {
font-family: "Consolas-Bold";
src: url("/fonts/consolab.ttf");
font-weight: bold;
}
@font-face {
font-family: "Continuum Bold";
src: url("/fonts/contb.ttf");
}
@font-face {
font-family: "ContinuumBold";
src: url("/fonts/contb.ttf");
}
@font-face {
font-family: "Designosaur";
src: url("/fonts/Designosaur-Regular.ttf");
}
@font-face {
font-family: "Designosaur";
src: url("/fonts/Designosaur-Italic.ttf");
font-style: italic;
}
@font-face {
font-family: "Designosaur-Italic";
src: url("/fonts/Designosaur-Italic.ttf");
font-style: italic;
}
@font-face {
font-family: "Elephant";
src: url("/fonts/ELEPHNT.TTF");
}
@font-face {
font-family: "Elephant-Regular";
src: url("/fonts/ELEPHNT.TTF");
}
@font-face {
font-family: "EngraversGothic BT";
src: url("/fonts/Engrvgot.TTF");
}
@font-face {
font-family: "EngraversGothicBT-Regular";
src: url("/fonts/Engrvgot.TTF");
}
@font-face {
font-family: "Essai";
src: url("/fonts/Essai.ttf");
}
@font-face {
font-family: "Estrangelo Edessa";
src: url("/fonts/estre.ttf");
}
@font-face {
font-family: "EstrangeloEdessa";
src: url("/fonts/estre.ttf");
}
@font-face {
font-family: "Euphemia";
src: url("/fonts/Euphemia_0.ttf");
}
@font-face {
font-family: "EuphemiaCAS";
src: url("/fonts/Euphemia_0.ttf");
}
@font-face {
font-family: "Forte";
src: url("/fonts/FORTE.TTF");
}
@font-face {
font-family: "ForteMT";
src: url("/fonts/FORTE.TTF");
}
@font-face {
font-family: "Franchise";
src: url("/fonts/Franchise-Bold-hinted.ttf");
}
@font-face {
font-family: "Franchise-Bold";
src: url("/fonts/Franchise-Bold-hinted.ttf");
}
@font-face {
font-family: "Gandhi Sans";
src: url("/fonts/GandhiSans-Bold.otf");
font-weight: bold;
}
@font-face {
font-family: "Gandhi Sans";
src: url("/fonts/GandhiSans-BoldItalic.otf");
font-weight: bold;
font-style: italic;
}
@font-face {
font-family: "GandhiSans-Bold";
src: url("/fonts/GandhiSans-Bold.otf");
font-weight: bold;
}
@font-face {
font-family: "GandhiSans-BoldItalic";
src: url("/fonts/GandhiSans-BoldItalic.otf");
font-weight: bold;
font-style: italic;
}
@font-face {
font-family: "HandelGotDBol";
src: url("/fonts/HANDGOTB.TTF");
}
@font-face {
font-family: "HandelGotD-Bold";
src: url("/fonts/HANDGOTB.TTF");
}
@font-face {
font-family: "HandelGothic BT";
src: url("/fonts/HANDGOTN.TTF");
}
@font-face {
font-family: "HandelGothicBT-Regular";
src: url("/fonts/HANDGOTN.TTF");
}
@font-face {
font-family: "HiraginoGyoDS-W4";
src: url("/fonts/HiraginoGyoDS-W4.ttf");
}
@font-face {
font-family: "HZHandwrite";
src: url("/fonts/HZHandwrite.ttf");
}
@font-face {
font-family: "IwaMinPro-Madoka-Lt";
src: url("/fonts/IwaMinPro-Lt-Madoka.ttf");
}
@font-face {
font-family: "IwaOMinPro-Fate-Bd";
src: url("/fonts/IwaOMinPro-Bd-Fate.ttf");
}
@font-face {
font-family: "Iwata Mincho Old Pro-Fate";
src: url("/fonts/IwaOMinPro-Bd-Fate.ttf");
}
@font-face {
font-family: "Iwata Mincho Old Pro-Fate B";
src: url("/fonts/IwaOMinPro-Bd-Fate.ttf");
}
@font-face {
font-family: "Iwata Mincho Pro-Madoka";
src: url("/fonts/IwaMinPro-Lt-Madoka.ttf");
}
@font-face {
font-family: "Iwata Mincho Pro-Madoka L";
src: url("/fonts/IwaMinPro-Lt-Madoka.ttf");
}
@font-face {
font-family: "JanieHmkBold";
src: url("/fonts/JANIHB.TTF");
}
@font-face {
font-family: "KozGoPro-Heavy-Madoka";
src: url("/fonts/KozGoProH-Madoka.ttf");
font-weight: bold;
}
@font-face {
font-family: "Kozuka Gothic Pro";
src: url("/fonts/KozGoProH-Madoka.ttf");
font-weight: bold;
}
@font-face {
font-family: "KozukaGothicProH-Madoka";
src: url("/fonts/KozGoProH-Madoka.ttf");
font-weight: bold;
}
@font-face {
font-family: "LHF Scriptana";
src: url("/fonts/LHF_Scriptana.ttf");
}
@font-face {
font-family: "LHFScriptana-Regular";
src: url("/fonts/LHF_Scriptana.ttf");
}
@font-face {
font-family: "LinotypeFinnegan-Medium";
src: url("/fonts/LT.ttf"), url("/fonts/macron%20finnetier%200.3.ttf");
}
@font-face {
font-family: "LinotypeFinnegan-MediumIt";
src: url("/fonts/LT_3italic.ttf"), url("/fonts/LT_italic.ttf");
font-style: italic;
}
@font-face {
font-family: "LTFinnegan Medium";
src: url("/fonts/LT.ttf"), url("/fonts/macron%20finnetier%200.3.ttf");
}
@font-face {
font-family: "LTFinnegan Medium";
src: url("/fonts/LT_3italic.ttf"), url("/fonts/LT_italic.ttf");
font-style: italic;
}
@font-face {
font-family: "Macron Finnetier Medium";
src: url("/fonts/macron%20finnetier%200.3.ttf");
}
@font-face {
font-family: "MacronFinnetier-Medium";
src: url("/fonts/macron%20finnetier%200.3.ttf");
}
@font-face {
font-family: "Mentone";
src: url("/fonts/mentone-semibol.otf");
font-weight: 600;
}
@font-face {
font-family: "Mentone Lig";
src: url("/fonts/mentone-semibol.otf");
font-weight: 600;
}
@font-face {
font-family: "Mentone-SemiBold";
src: url("/fonts/mentone-semibol.otf");
font-weight: 600;
}
@font-face {
font-family: "Montara";
src: url("/fonts/Montara-Gothic.otf");
}
@font-face {
font-family: "Montara";
src: url("/fonts/Montara-Italic.otf");
font-style: italic;
}
@font-face {
font-family: "Montara Gothic";
src: url("/fonts/Montara-Gothic.otf");
}
@font-face {
font-family: "Montara Gothic";
src: url("/fonts/Montara-Italic.otf");
font-style: italic;
}
@font-face {
font-family: "Montara-Gothic";
src: url("/fonts/Montara-Gothic.otf");
}
@font-face {
font-family: "Montara-Italic";
src: url("/fonts/Montara-Italic.otf");
font-style: italic;
}
@font-face {
font-family: "New";
src: url("/fonts/OBGB.TTF");
}
@font-face {
font-family: "NewBaskerville";
src: url("/fonts/NEWBASK.TTF"), url("/fonts/NewBaskerville.ttf");
}
@font-face {
font-family: "Old Block";
src: url("/fonts/OBGB.TTF");
}
@font-face {
font-family: "Old Block Black";
src: url("/fonts/OBGB.TTF");
}
@font-face {
font-family: "Painfresco";
src: url("/fonts/Painfresco-Regular.ttf");
}
@font-face {
font-family: "Painfresco";
src: url("/fonts/Painfresco-Italic.ttf");
font-style: italic;
}
@font-face {
font-family: "Painfresco-Italic";
src: url("/fonts/Painfresco-Italic.ttf");
font-style: italic;
}
@font-face {
font-family: "Painfresco-Regular";
src: url("/fonts/Painfresco-Regular.ttf");
}
@font-face {
font-family: "PaintyPaint";
src: url("/fonts/PAINP.TTF"), url("/fonts/PAINP_0.TTF");
}
@font-face {
font-family: "PaintyPaint1";
src: url("/fonts/PAINP.TTF"), url("/fonts/PAINP_0.TTF");
}
@font-face {
font-family: "Panefresco800wt-Italic";
src: url("/fonts/Painfresco-Italic.ttf");
font-style: italic;
}
@font-face {
font-family: "Panefresco800wtKilllaKill-Regular";
src: url("/fonts/Painfresco-Regular.ttf");
}
@font-face {
font-family: "Pencilfied";
src: url("/fonts/pencilfied.ttf");
}
@font-face {
font-family: "PencilfiedX";
src: url("/fonts/pencilfied.ttf");
}
@font-face {
font-family: "Profile";
src: url("/fonts/Profile-Medium.otf"), url("/fonts/Profile-Regular.otf");
}
@font-face {
font-family: "Profile-Medium";
src: url("/fonts/Profile-Medium.otf");
}
@font-face {
font-family: "Profile-Regular";
src: url("/fonts/Profile-Regular.otf");
}
@font-face {
font-family: "Prototype";
src: url("/fonts/Prototype.ttf");
}
@font-face {
font-family: "Reswysokr";
src: url("/fonts/Reswysokr.otf");
}
@font-face {
font-family: "Signika";
src: url("/fonts/Signika-Regular.otf");
}
@font-face {
font-family: "Signika-Regular";
src: url("/fonts/Signika-Regular.otf");
}
@font-face {
font-family: "Swis721 Hv BT";
src: url("/fonts/SWZ721H.TTF");
}
@font-face {
font-family: "Swis721 Md BT";
src: url("/fonts/SWZ721M.TTF");
}
@font-face {
font-family: "Swiss721BT-Heavy";
src: url("/fonts/SWZ721H.TTF");
}
@font-face {
font-family: "Swiss721BT-Medium";
src: url("/fonts/SWZ721M.TTF");
}
@font-face {
font-family: "Untitled";
src: url("/fonts/Babylon-27-Babylon-27.ttf");
}
@font-face {
font-family: "Yryr minc R";
src: url("/fonts/Yryr%20minc%20R.ttf");
}
@font-face {
font-family: "YryrmincR";
src: url("/fonts/Yryr%20minc%20R.ttf");
}
-120
View File
@@ -1,120 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
"use strict";
libjass.debugMode = (location.search === "?debug") || (location.search === "?verbose");
libjass.verboseMode = (location.search === "?verbose");
addEventListener("DOMContentLoaded", function () {
var debug = function () {
if (libjass.debugMode) {
console.log.apply(console, arguments);
}
}
var video = document.querySelector("#video");
var videoMetadataLoaded = false;
var ass = null;
var testVideoAndASSLoaded = function () {
if (videoMetadataLoaded && ass) {
var renderer = new libjass.renderers.DefaultRenderer(video, ass, {
preLoadFonts: true,
fontMap: libjass.renderers.DefaultRenderer.makeFontMapFromStyleElement(document.querySelector("#font-map"))
});
renderer.addEventListener("ready", function () {
debug("All fonts have been preloaded. Beginning autoplay.");
video.play();
});
var changeVideoSizeSelection = function (id) {
if (typeof id === "undefined") {
[].some.call(videoSizeSelector.querySelectorAll("input[name='video-size']"), function (option) {
if (option.checked) {
id = option.id;
return true;
}
return false;
});
}
if (id === "video-size-video-radio") {
renderer.resizeVideo(video.videoWidth, video.videoHeight);
}
else if (id === "video-size-script-radio") {
renderer.resizeVideo(ass.resolutionX, ass.resolutionY);
}
};
var videoSizeSelector = document.querySelector("#video-size-selector");
videoSizeSelector.addEventListener("change", function (event) { changeVideoSizeSelection(event.target.id); }, false);
renderer.addEventListener("fullScreenChange", function (newState) {
if (newState === false) {
changeVideoSizeSelection();
}
});
changeVideoSizeSelection();
};
}
if (video.readyState < HTMLMediaElement.HAVE_METADATA) {
video.addEventListener("loadedmetadata", function () {
debug("Video metadata loaded.");
videoMetadataLoaded = true;
document.querySelector("#video-resolution-label-width").appendChild(document.createTextNode(video.videoWidth));
document.querySelector("#video-resolution-label-height").appendChild(document.createTextNode(video.videoHeight));
testVideoAndASSLoaded();
}, false);
}
else {
debug("Video metadata loaded.");
videoMetadataLoaded = true;
testVideoAndASSLoaded();
}
var track = document.querySelector("#video > track[data-format='ass']");
var subsRequest = new XMLHttpRequest();
subsRequest.open("GET", track.src || track.getAttribute("src"), true);
subsRequest.addEventListener("readystatechange", function () {
if (subsRequest.readyState === XMLHttpRequest.DONE) {
debug("ASS script received.");
ass = new libjass.ASS(subsRequest.responseText);
if (libjass.debugMode) {
window.ass = ass;
}
document.querySelector("#script-resolution-label-width").appendChild(document.createTextNode(ass.resolutionX));
document.querySelector("#script-resolution-label-height").appendChild(document.createTextNode(ass.resolutionY));
testVideoAndASSLoaded();
}
}, false);
subsRequest.send(null);
}, false);
+9 -47
View File
@@ -1,54 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
libjass
https://github.com/Arnavion/libjass
Copyright 2013 Arnav Singh
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>&gt;2012 &gt;Streaman Animu</title>
<link rel="stylesheet" href="libjass.css" />
<link rel="stylesheet" href="index.css" />
<link rel="stylesheet" href="fonts.css" id="font-map" />
<!-- Unminified file -->
<script src="libjass.js" />
<!-- Minified file -->
<!--
<script src="libjass.min.js" />
-->
<script src="index.js" />
<title>libjass</title>
</head>
<body>
<h1>Foo</h1>
<h2>Bar</h2>
<video id="video" src="/video/gc-01.webm" controls="">
<track src="/video/gc-01.ass" kind="metadata" data-format="ass" />
</video>
<form class="settings-form">
<fieldset>
<legend>Video size</legend>
<div id="video-size-selector">
<input type="radio" name="video-size" id="video-size-video-radio" checked="checked" /><label for="video-size-video-radio">Video resolution <span id="video-resolution-label-width" />x<span id="video-resolution-label-height" /></label>
<input type="radio" name="video-size" id="video-size-script-radio" /><label for="video-size-script-radio">Script resolution <span id="script-resolution-label-width" />x<span id="script-resolution-label-height" /></label>
</div>
</fieldset>
</form>
<h1>libjass - Renders ASS subs in the browser</h1>
<ul>
<li><a href="https://github.com/Arnavion/libjass">GitHub</a></li>
<li>IRC channel - #libjass on irc.rizon.net</li>
<li><a href="https://arnavion.github.io/libjass/api.xhtml">API documentation</a></li>
<li><a href="https://arnavion.github.io/libjass/demo/">Demo</a></li>
</ul>
</body>
</html>
-18
View File
@@ -1,18 +0,0 @@
var fs = require("fs");
namespace("_clean", function () {
task("run", [], function () {
console.log("[" + this.fullName + "]");
["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;
}
}
});
});
});
-286
View File
@@ -1,286 +0,0 @@
var fs = require("fs");
var path = require("path");
var UglifyJS = require("uglify-js");
namespace("_default", function () {
task("typeScript", ["_typescript:getCompilerFactory"], function () {
console.log("[" + this.fullName + "]");
var Compiler = jake.Task["_typescript:getCompilerFactory"].value;
var compiler = new Compiler();
return compiler.compile(["libjass.ts"]);
});
task("fixup", ["_default:typeScript"], function () {
console.log("[" + this.fullName + "]");
var compiled = jake.Task["_default:typeScript"].value;
UglifyJS.base54.reset();
// Parse
var root = UglifyJS.parse(compiled.code, {
filename: "libjass.js",
toplevel: null
});
root.figure_out_scope();
// Remove some things from the AST
var nodesToRemove;
nodesToRemove = [];
// 1. All but the first top-level "var libjass;"
var firstVarLibjassFound = false;
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (node instanceof UglifyJS.AST_Var && node.definitions[0].name.name === "libjass") {
if (firstVarLibjassFound === false) {
firstVarLibjassFound = true;
}
else {
nodesToRemove.push({ node: node, parent: root.body });
}
}
}));
// Repeat because removing some declarations may make others unreferenced
for (;;) {
// 2. Unreferenced variable and function declarations, and unreferenced terminal function arguments
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (node instanceof UglifyJS.AST_SymbolDeclaration && node.unreferenced()) {
if (node instanceof UglifyJS.AST_SymbolFunarg) {
if (this.parent().argnames.indexOf(node) === this.parent().argnames.length - 1) {
nodesToRemove.push({ node: node, parent: this.parent().argnames });
}
}
else if (node instanceof UglifyJS.AST_SymbolVar) {
nodesToRemove.push({ node: this.parent(), parent: this.parent(1).definitions });
if (this.parent(1).definitions.length === 1) {
nodesToRemove.push({ node: this.parent(1), parent: this.parent(2).body });
}
}
else if (node instanceof UglifyJS.AST_SymbolDefun) {
nodesToRemove.push({ node: this.parent(), parent: this.parent(1).body });
}
}
}));
if (nodesToRemove.length === 0) {
break;
}
nodesToRemove.forEach(function (node) {
node.parent.splice(node.parent.indexOf(node.node), 1);
});
nodesToRemove = [];
root.figure_out_scope();
}
// Output
var firstLicenseHeaderFound = false; // To detect and preserve the first license header
var output = {
source_map: UglifyJS.SourceMap({
file: "libjass.js.map",
orig: compiled.sourceMap,
root: ""
}),
beautify: true,
comments: {
test: function (comment) {
if (comment.indexOf("Copyright") !== -1) {
if (!firstLicenseHeaderFound) {
firstLicenseHeaderFound = true;
return true;
}
else {
return false;
}
}
return true;
}
}
};
var stream = UglifyJS.OutputStream(output);
root.print(stream);
return {
code: stream.toString() + "\n//# sourceMappingURL=libjass.js.map",
sourceMap: output.source_map.toString()
};
});
task("writeCode", ["_default:fixup"], function () {
console.log("[" + this.fullName + "]");
var fixedUp = jake.Task["_default:fixup"].value;
fs.writeFileSync("libjass.js", fixedUp.code);
});
task("writeSourceMap", ["_default:fixup"], function () {
console.log("[" + this.fullName + "]");
var fixedUp = jake.Task["_default:fixup"].value;
fs.writeFileSync("libjass.js.map", fixedUp.sourceMap);
});
task("minify", ["_default:fixup"], function () {
console.log("[" + this.fullName + "]");
var fixedUp = jake.Task["_default:fixup"].value;
UglifyJS.base54.reset();
// Parse
var root = null;
root = UglifyJS.parse(fixedUp.code, {
filename: "libjass.js",
toplevel: root
});
root.figure_out_scope();
// Suppress some warnings
var originalWarn = UglifyJS.AST_Node.warn;
UglifyJS.AST_Node.warn = function (text, properties) {
if (
(text === "Eval is used [{file}:{line},{col}]" && properties.file === "libjass.js" && properties.line === 23)
) {
return;
}
originalWarn.call(UglifyJS.AST_Node, text, properties);
};
// Warnings
root.scope_warnings({
func_arguments: false
});
// Compress
var compressor = UglifyJS.Compressor({
warnings: true
});
root = root.transform(compressor);
// Mangle
root.figure_out_scope();
root.compute_char_frequency();
root.mangle_names();
// 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.indexOf("_") === 0
) {
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: "libjass.min.js.map",
orig: fixedUp.sourceMap,
root: ""
}),
comments: {
test: function (comment) {
if (!firstLicenseHeaderFound && comment.indexOf("Copyright") !== -1) {
firstLicenseHeaderFound = true;
return true;
}
return false;
}
}
};
var stream = UglifyJS.OutputStream(output);
root.print(stream);
return {
code: stream.toString() + "\n//# sourceMappingURL=libjass.min.js.map",
sourceMap: output.source_map.toString()
};
});
task("writeMinifiedCode", ["_default:minify"], function () {
console.log("[" + this.fullName + "]");
var minified = jake.Task["_default:minify"].value;
fs.writeFileSync("libjass.min.js", minified.code);
});
task("writeMinifiedSourceMap", ["_default:minify"], function () {
console.log("[" + this.fullName + "]");
var minified = jake.Task["_default:minify"].value;
fs.writeFileSync("libjass.min.js.map", minified.sourceMap);
});
});
-1079
View File
File diff suppressed because it is too large Load Diff
-58
View File
@@ -1,58 +0,0 @@
var path = require("path");
var Mocha = require("mocha");
namespace("_test", function () {
task("run", ["clean", "_default:writeCode"], function (failTaskOnFailure) {
console.log("[" + this.fullName + "]");
failTaskOnFailure = (failTaskOnFailure === true) || (failTaskOnFailure === "true");
var mocha = new Mocha({
ui: "tdd",
reporter: "spec",
loadInNewContext: !failTaskOnFailure
});
(new jake.FileList("./tests/test-*.js")).toArray().forEach(function (filename) {
mocha.addFile(filename);
});
mocha.run(function (failures) {
if (failTaskOnFailure && failures > 0) {
fail(failures + " test(s) failed.");
}
complete();
});
}, { async: true });
});
Mocha.prototype.loadFiles = function (fn){
var self = this;
var suite = this.suite;
var pending = this.files.length;
this.files.forEach(function(file){
file = path.resolve(file);
suite.emit('pre-require', global, file, self);
var fileExports;
if (!self.options.loadInNewContext) {
fileExports = require(file);
}
else {
var filePathAsJsString = file.replace(/\\/g, "\\\\");
var newContextScript = 'var module = new Module("' + filePathAsJsString + '"); module.load("' + filePathAsJsString + '"); module.exports';
fileExports = require('vm').runInNewContext(newContextScript, {
Module: module.constructor,
process: process
});
}
suite.emit('require', fileExports, file, self);
suite.emit('post-require', global, file, self);
--pending || (fn && fn());
});
};
-165
View File
@@ -1,165 +0,0 @@
var fs = require("fs");
var path = require("path");
var UglifyJS = require("uglify-js");
var typeScriptModulePath = path.resolve("./node_modules/typescript/bin");
var typeScriptJsPath = path.join(typeScriptModulePath, "typescript.js");
var makeCompilerFunction = function (TypeScript) {
var Compiler = function () {
this.mutableSettings = new TypeScript.CompilationSettings();
this.mutableSettings.codeGenTarget = TypeScript.LanguageVersion.EcmaScript5;
this.mutableSettings.mapSourceFiles = true;
this.mutableSettings.noImplicitAny = true;
};
Compiler.prototype.compile = function (inputFilenames) {
console.log("Compiling " + JSON.stringify(inputFilenames) + "...");
this.mutableSettings.outFileOption = "output.js";
try {
var run = new CompilerRun();
var resolutionResults = TypeScript.ReferenceResolver.resolve(inputFilenames, {
resolveRelativePath: function (file, directory) {
return path.resolve(directory, file);
},
fileExists: fs.existsSync.bind(fs),
getScriptSnapshot: run.getScriptSnapshot.bind(run),
getParentDirectory: path.dirname.bind(path)
}, true);
var resolvedFiles = resolutionResults.resolvedFiles;
resolvedFiles.unshift({
path: path.resolve(typeScriptModulePath, "lib.d.ts"),
referencedFiles: [],
importedFiles: []
});
run.addDiagnostics(resolutionResults.diagnostics);
var compiler = new TypeScript.TypeScriptCompiler(new TypeScript.NullLogger(), TypeScript.ImmutableCompilationSettings.fromCompilationSettings(this.mutableSettings));
resolvedFiles.forEach(function (resolvedFile) {
var scriptSnapshot = run.getScriptSnapshot(resolvedFile.path);
compiler.addFile(resolvedFile.path, scriptSnapshot, 0, 0, false, resolvedFile.referencedFiles);
});
var iterator = compiler.compile(path.resolve.bind(path));
var compilerOutput = Object.create(null);
while (iterator.moveNext()) {
var result = iterator.current();
run.addDiagnostics(result.diagnostics);
result.outputFiles.forEach(function (outputFile) {
switch (outputFile.fileType) {
case TypeScript.OutputFileType.JavaScript:
compilerOutput.code = outputFile.text;
break;
case TypeScript.OutputFileType.SourceMap:
compilerOutput.sourceMap = outputFile.text;
break;
}
});
}
if (run.diagnostics.some(function (diagnostic) { return diagnostic.info().category === 1; })) {
throw new Error("There were one or more errors.");
}
console.log("Compile succeeded.");
return compilerOutput;
}
catch (ex) {
if (ex instanceof Error) {
throw ex;
}
else {
throw new Error("Internal compiler error: " + ex.stack + "\n");
}
}
};
var CompilerRun = function () {
this.diagnostics = [];
this._scriptSnapshots = Object.create(null);
};
CompilerRun.prototype.addDiagnostics = function (newDiagnostics) {
newDiagnostics.forEach(function (diagnostic) {
var message = diagnostic.message();
if (diagnostic.fileName()) {
message = diagnostic.fileName() + "(" + (diagnostic.line() + 1) + "," + (diagnostic.character() + 1) + "): " + message;
}
console.error(message);
});
this.diagnostics.push.apply(this.diagnostics, newDiagnostics);
};
CompilerRun.prototype.getScriptSnapshot = function (filename) {
var scriptSnapshot = this._scriptSnapshots[filename];
if (!scriptSnapshot) {
var fileContents;
try {
fileContents = fs.readFileSync(filename, { encoding: "utf8" });
}
catch (ex) {
run.addDiagnostics([new TypeScript.Diagnostic(null, null, 0, 0, TypeScript.DiagnosticCode.Cannot_read_file_0_1, [filename, ex.message])]);
fileContents = "";
}
var scriptSnapshot = TypeScript.ScriptSnapshot.fromString(fileContents);
this._scriptSnapshots[filename] = scriptSnapshot;
}
return scriptSnapshot;
};
return Compiler;
};
namespace("_typescript", function () {
task("require", [], function () {
console.log("[" + this.fullName + "]");
var vm = require("vm");
var data = fs.readFileSync(typeScriptJsPath, { encoding: "utf8" });
data += "module.exports = TypeScript;";
var TypeScript = {};
vm.runInNewContext(data, {
module: Object.defineProperty(Object.create(null), "exports", {
get: function () { return TypeScript; },
set: function (value) { TypeScript = value; }
}),
require: require,
process: process,
__filename: typeScriptJsPath,
__dirname: typeScriptModulePath
});
return TypeScript;
});
task("getCompilerFactory", ["_typescript:require"], function () {
console.log("[" + this.fullName + "]");
var TypeScript = jake.Task["_typescript:require"].value;
return makeCompilerFunction(TypeScript);
});
});
-64
View File
@@ -1,64 +0,0 @@
var childProcess = require("child_process");
var fs = require("fs");
var path = require("path");
namespace("_watch", function () {
task("runTask", ["_test:run[false]", "_default:writeSourceMap"], function () { });
task("run", [], function () {
console.log("[" + this.fullName + "]");
var spawnSubProcess = function () {
var pathToJake = path.resolve("./node_modules/.bin/jake");
var subProcess = childProcess.exec(pathToJake + " _watch:runUntilError --trace");
subProcess.stdout.pipe(process.stdout);
subProcess.stderr.pipe(process.stderr);
subProcess.addListener("exit", function (code, signal) {
spawnSubProcess();
});
};
spawnSubProcess();
}, { async: true });
task("runUntilError", ["_typescript:getCompilerFactory"], function () {
console.log("[" + this.fullName + "]");
var tasksToReEnable = [];
var stack = ["_watch:runTask"];
while (stack.length > 0) {
var next = stack.shift();
if (next.indexOf("[") !== -1) {
next = next.substr(0, next.indexOf("["));
}
if (next !== "_typescript:getCompilerFactory" && tasksToReEnable.indexOf(jake.Task[next]) === -1) {
tasksToReEnable.push(jake.Task[next]);
stack.push.apply(stack, jake.Task[next].prereqs);
}
}
var timeoutId = null;
fs.watch(".", function (event, filename) {
if (filename === null) {
return;
}
var extension = path.extname(filename);
if (extension !== ".ts") {
return;
}
console.log(event, filename, JSON.stringify([].slice.call(arguments, 0)));
clearTimeout(timeoutId);
timeoutId = setTimeout(function () {
tasksToReEnable.forEach(function (task) { task.reenable(); });
jake.Task["_watch:runTask"].invoke();
}, 1000);
});
});
});
-25
View File
@@ -1,25 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
///<reference path="utility.ts" />
///<reference path="parts.ts" />
///<reference path="parser.ts" />
///<reference path="renderer.ts" />
///<reference path="types.ts" />
-32
View File
@@ -1,32 +0,0 @@
{
"name": "libjass",
"version": "0.4.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": "jake clean && jake --trace",
"test": "jake test"
},
"dependencies": {
"jake": "latest",
"mocha": "~1.16.2",
"typescript": "0.9.5",
"uglify-js": "~2"
}
}
-1908
View File
File diff suppressed because it is too large Load Diff
-1246
View File
File diff suppressed because it is too large Load Diff
-1456
View File
File diff suppressed because it is too large Load Diff
-347
View File
@@ -1,347 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.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) {
logger.beginSuite(this);
this.children.forEach(function (child) { child.run(logger); });
logger.endSuite(this);
};
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) {
if (this.body) {
this.passed = false;
try {
this.body();
this.passed = true;
}
catch (testException) {
this.exception = testException;
}
}
logger.writeTest(this);
};
var currentSuite = null;
suite = function (name, body) {
var newSuite = new Suite(name, currentSuite);
currentSuite = newSuite;
body();
currentSuite = newSuite.parent;
return newSuite;
};
test = function (description, body) {
return new Test(description, body, currentSuite);
};
})();
var assert = new function () {
this.deepEqual = function (actual, expected) {
if (expected === undefined) {
throw new Error("Expected should not be undefined");
}
if (actual === undefined) {
throw new Error("Actual should not be 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) {
throw new Error("Expected value of type [" + expected.constructor.name + "] but got value of type [" + actual.constructor.name + "].");
}
switch(typeof expected) {
case "boolean":
case "number":
case "string":
if (expected !== actual) {
throw new Error("Expected [" + expected + "] but got [" + actual + "]");
}
break;
case "object":
if (Array.isArray(expected)) {
if (!Array.isArray(actual)) {
throw new Error("Expected an array but got [" + actual + "]");
}
if (expected.length !== actual.length) {
throw new Error("Expected an array of length " + expected.length + " but got array of length " + actual.length);
}
var i;
for (i = 0; i < expected.length; i++) {
assert.deepEqual(actual[i], expected[i]);
}
}
else {
var expectedProperties = Object.keys(expected);
var actualProperties = Object.keys(actual);
expectedProperties.sort();
actualProperties.sort();
assert.deepEqual(actualProperties, expectedProperties);
expectedProperties.forEach(function (property) {
assert.deepEqual(actual[property], expected[property]);
});
}
break;
default:
throw new Error("Unrecognized type: " + typeof expected);
}
};
};
var Logger = function (outputDiv) {
var testDiv = document.createElement("div");
testDiv.className = "test";
var suiteElement = document.createElement("fieldset");
suiteElement.className = "suite";
suiteElement.appendChild(document.createElement("legend"));
var totalDiv = document.createElement("div");
var currentSuiteElement = outputDiv;
this.beginSuite = function (suite) {
var newSectionElement = suiteElement.cloneNode(true);
currentSuiteElement.appendChild(newSectionElement);
currentSuiteElement = newSectionElement;
var message = "Suite " + suite.number + " - \"" + suite.name + "\"";
console.group(message);
currentSuiteElement.querySelector("legend").appendChild(document.createTextNode(message));
};
this.endSuite = function (suite) {
var numTotal = suite.total;
var numPassed = suite.passed;
var numFailed = suite.failed;
var message = numPassed + " of " + numTotal + " tests passed";
console.log(message);
if (numFailed > 0) {
var message = numFailed + " of " + numTotal + " tests failed";
console.warn(message);
currentSuiteElement.querySelector("legend").appendChild(document.createTextNode(" - " + message));
currentSuiteElement.className += " failed";
}
else {
currentSuiteElement.className += " passed";
}
if (numTotal > numPassed + numFailed) {
var message = (numTotal - (numPassed + numFailed)) + " of " + numTotal + " tests skipped";
console.log(message);
currentSuiteElement.querySelector("legend").appendChild(document.createTextNode(" - " + message));
}
currentSuiteElement = currentSuiteElement.parentElement;
console.groupEnd();
};
this.writeTest = function (test) {
if (test.passed === true) {
var message = "Test " + test.number + " - \"" + test.description + "\" - " + test.customProperties.rule + " [ " + test.customProperties.input + " ] ";
console.log(message);
append(testDiv, message).className += " passed";
}
else if (test.passed === false) {
var message = "Test " + test.number + " - \"" + test.description + "\" - " + test.customProperties.rule + " [ " + test.customProperties.input + " ] " + " : " + ((test.exception !== null) ? test.exception.message : "<No exception>");
console.warn(message);
append(testDiv, message).className += " failed";
}
else {
var message = "Test " + test.number + " - \"" + test.description + "\" - " + test.customProperties.rule + " [ " + test.customProperties.input + " ] " + " : Skipped";
console.log(message);
append(testDiv, message).className += " skipped";
}
};
this.writeTotal = function (suites) {
currentSuiteElement = suiteElement.cloneNode(true);
outputDiv.appendChild(currentSuiteElement);
currentSuiteElement.className = "total";
console.group("Total");
currentSuiteElement.querySelector("legend").appendChild(document.createTextNode("Total"));
var numTotal = suites.map(function (section) { return section.total; }).sum();
var numPassed = suites.map(function (section) { return section.passed; }).sum();
var numFailed = suites.map(function (section) { return section.failed; }).sum();
var message = numPassed + " of " + numTotal + " tests passed.";
console.log(message);
append(totalDiv, message).className = "passed";
if (numFailed > 0) {
var message = numFailed + " of " + numTotal + " tests failed.";
console.warn(message);
append(totalDiv, message).className = "failed";
}
if (numTotal > numPassed + numFailed) {
var message = (numTotal - (numPassed + numFailed)) + " of " + numTotal + " tests skipped.";
console.warn(message);
append(totalDiv, message).className = "skipped";
}
console.groupEnd();
};
var append = function (messageDivType, message) {
var messageDiv = messageDivType.cloneNode();
currentSuiteElement.appendChild(messageDiv);
messageDiv.appendChild(document.createTextNode(message));
return messageDiv;
};
};
addEventListener("DOMContentLoaded", function () {
var logger = new Logger(document.querySelector("#output"));
rootSuites.forEach(function (suite) { suite.run(logger); });
logger.writeTotal(rootSuites);
}, false);
-46
View File
@@ -1,46 +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" />
</head>
<body>
<div id="output" />
</body>
</html>
-70
View File
@@ -1,70 +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 = function (description, input, rule, expected) {
var result = test(description, function () {
var actual = null;
try {
actual = libjass.parser.parse(input, rule);
}
catch (parseException) {
if (expected === null) {
passed = true;
return;
}
else {
throw new Error("Expected parse to succeed but it threw an exception: " + parseException.stack);
}
}
if (expected === null) {
throw new Error("Expected parse to fail.");
}
else if (actual === null) {
throw new Error("Parse failed without throwing an exception.");
}
assert.deepEqual(actual, expected);
});
result.customProperties = Object.create(null);
result.customProperties.input = input;
result.customProperties.rule = rule;
return result;
};
parserTest.skip = function (description, input, rule, expected) {
var result = test(description); // Could use test.skip but Mocha's implementation has a bug - it doesn't return the Test object
result.customProperties = Object.create(null);
result.customProperties.input = input;
result.customProperties.rule = rule;
return result;
};
if (typeof module !== "undefined") {
module.exports = parserTest;
}
-103
View File
@@ -1,103 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var libjass = require("../libjass.js");
var parserTest = require("./parser-test.js");
suite("Miscellaneous", function () {
parserTest("herkz", "{\\pos(311,4)\\blur0.8\\fs40\\bord0\\c&H3F171F&\\t(3820,3820,\\blur6}Chi{\\c&H422CB1&}tose {\\c&H3F171F&}Furu", "dialogueParts", [
new libjass.parts.Position(311, 4),
new libjass.parts.GaussianBlur(0.8),
new libjass.parts.FontSize(40),
new libjass.parts.Border(0),
new libjass.parts.PrimaryColor(new libjass.parts.Color(31, 23, 63, 1)),
new libjass.parts.Transform(3.82, 3.82, null, [new libjass.parts.GaussianBlur(6)]),
new libjass.parts.Text("Chi"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(177, 44, 66, 1)),
new libjass.parts.Text("tose "),
new libjass.parts.PrimaryColor(new libjass.parts.Color(31, 23, 63, 1)),
new libjass.parts.Text("Furu")
]);
parserTest("herkz-fixed", "{\\pos(311,4)\\blur0.8\\fs40\\bord0\\c&H3F171F&\\t(3820,3820,\\blur6)}Chi{\\c&H422CB1&}tose {\\c&H3F171F&}Furu", "dialogueParts", [
new libjass.parts.Position(311, 4),
new libjass.parts.GaussianBlur(0.8),
new libjass.parts.FontSize(40),
new libjass.parts.Border(0),
new libjass.parts.PrimaryColor(new libjass.parts.Color(31, 23, 63, 1)),
new libjass.parts.Transform(3.82, 3.82, null, [new libjass.parts.GaussianBlur(6)]),
new libjass.parts.Text("Chi"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(177, 44, 66, 1)),
new libjass.parts.Text("tose "),
new libjass.parts.PrimaryColor(new libjass.parts.Color(31, 23, 63, 1)),
new libjass.parts.Text("Furu")
]);
parserTest("knk-02", "{\\\\k0}{\\1c&H35C2BD&}s{\\1c&H53A2C0&}e{\\1c&H757FC4&}i{\\1c&H866EC6&}j{\\1c&H995AC8&}a{\\1c&HBC36CC&}k{\\1c&HD420C8&}u{\\1c&HB942A7&} {\\1c&HA25F8A&}w{\\1c&H799356&}a{\\1c&H63B03A&} {\\1c&H54C723&}k{\\1c&H68C126&}i{\\1c&H7BBC29&}r{\\1c&H8CB82C&}i{\\1c&HA2B230&}s{\\1c&HBEAB34&}a{\\1c&HD8A339&}k{\\1c&HB68D3B&}u{\\1c&HA4813C&} {\\1c&H88703E&}y{\\1c&H665A41&}o{\\1c&H444444&}u", "dialogueParts", [
new libjass.parts.Comment("\\"),
new libjass.parts.ColorKaraoke(0),
new libjass.parts.PrimaryColor(new libjass.parts.Color(189, 194, 53, 1)),
new libjass.parts.Text("s"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(192, 162, 83, 1)),
new libjass.parts.Text("e"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(196, 127, 117, 1)),
new libjass.parts.Text("i"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(198, 110, 134, 1)),
new libjass.parts.Text("j"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(200, 90, 153, 1)),
new libjass.parts.Text("a"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(204, 54, 188, 1)),
new libjass.parts.Text("k"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(200, 32, 212, 1)),
new libjass.parts.Text("u"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(167, 66, 185, 1)),
new libjass.parts.Text(" "),
new libjass.parts.PrimaryColor(new libjass.parts.Color(138, 95, 162, 1)),
new libjass.parts.Text("w"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(86, 147, 121, 1)),
new libjass.parts.Text("a"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(58, 176, 99, 1)),
new libjass.parts.Text(" "),
new libjass.parts.PrimaryColor(new libjass.parts.Color(35, 199, 84, 1)),
new libjass.parts.Text("k"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(38, 193, 104, 1)),
new libjass.parts.Text("i"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(41, 188, 123, 1)),
new libjass.parts.Text("r"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(44, 184, 140, 1)),
new libjass.parts.Text("i"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(48, 178, 162, 1)),
new libjass.parts.Text("s"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(52, 171, 190, 1)),
new libjass.parts.Text("a"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(57, 163, 216, 1)),
new libjass.parts.Text("k"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(59, 141, 182, 1)),
new libjass.parts.Text("u"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(60, 129, 164, 1)),
new libjass.parts.Text(" "),
new libjass.parts.PrimaryColor(new libjass.parts.Color(62, 112, 136, 1)),
new libjass.parts.Text("y"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(65, 90, 102, 1)),
new libjass.parts.Text("o"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(68, 68, 68, 1)),
new libjass.parts.Text("u")
]);
});
-68
View File
@@ -1,68 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var libjass = require("../libjass.js");
var parserTest = require("./parser-test.js");
suite("Primitives", function () {
suite("Color", function () {
parserTest("Starts with &H", "&H3F171F&", "color", new libjass.parts.Color(31, 23, 63, 1));
parserTest("Starts with H", "&3F171F&", "color", new libjass.parts.Color(31, 23, 63, 1));
parserTest("Eight digits", "&H3F171F00&", "color", new libjass.parts.Color(31, 23, 63, 1));
parserTest("Eight digits, non-zero alpha", "&H3F171FFF&", "color", null);
});
suite("Alpha", function () {
parserTest("Starts with &H", "&HFF&", "alpha", 0);
parserTest("Starts with &", "&FF&", "alpha", 0);
parserTest("Starts with &H, one digit", "&HF&", "alpha", 0);
parserTest("Starts with &H, one digit", "&H0&", "alpha", 1);
parserTest("Starts with &, one digit", "&F&", "alpha", 0);
parserTest("Starts with &, one digit", "&0&", "alpha", 1);
parserTest("Starts with &H, doesn't end with &", "&HF&", "alpha", 0);
parserTest("Starts with &H, doesn't end with &", "&H0&", "alpha", 1);
parserTest("Starts with &, doesn't end with &", "&F", "alpha", 0);
parserTest("Starts with &, doesn't end with &", "&0", "alpha", 1);
parserTest("Doesn't start with &, doesn't end with &", "0", "alpha", 1);
parserTest("Starts with H", "H0", "alpha", null);
});
suite("ColorWithAlpha", function () {
parserTest("AABBGGRR", "&H00434441", "colorWithAlpha", new libjass.parts.Color(65, 68, 67, 1));
parserTest("AABBGGRR", "&HF0434441", "colorWithAlpha", new libjass.parts.Color(65, 68, 67, (1 - 240 / 255)));
parserTest("AABBGGRR", "&HFF434441", "colorWithAlpha", new libjass.parts.Color(65, 68, 67, 0));
});
});
-78
View File
@@ -1,78 +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("Primary color tag - \\c or \\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("Transform tag - \\t", function () {
parserTest("No tags", "{\\t(100,200)}", "enclosedTags", [new libjass.parts.Comment("\\t(100,200)")]);
});
});
-638
View File
@@ -1,638 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
///<reference path="libjass.ts" />
module libjass {
export interface StyleMap {
[name: string]: Style;
}
/**
* This class represents an ASS script. It contains information about the script, an array of Styles, and an array of Dialogues.
*
* @constructor
* @param {string} rawASS The raw text of the ASS script.
*
* @memberof libjass
*/
export class ASS {
private _resolutionX: number;
private _resolutionY: number;
private _scaleX: number;
private _scaleY: number;
private _styles: StyleMap = Object.create(null);
private _dialogues: Dialogue[] = [];
constructor(rawASS: string) {
rawASS = rawASS.replace(/\r$/gm, "");
var script = parser.parse(rawASS, "script");
// Get the script info template
var infoTemplate: Object = script["Script Info"];
if (libjass.verboseMode) {
console.log("Read script info: " + JSON.stringify(infoTemplate), infoTemplate);
}
// Parse the horizontal script resolution
this._resolutionX = parseInt(infoTemplate["PlayResX"]);
// Parse the vertical script resolution
this._resolutionY = parseInt(infoTemplate["PlayResY"]);
// Get styles from the styles section
script["V4+ Styles"].forEach((line: any) => {
if (line.type === "Style") {
var styleTemplate: Template = line.template;
if (libjass.verboseMode) {
console.log("Read style: " + JSON.stringify(styleTemplate), styleTemplate);
}
// Create the style and add it to the styles map
var newStyle = new Style(styleTemplate);
this._styles[newStyle.name] = newStyle;
}
});
// Get dialogues from the events section
script["Events"].forEach((line: any) => {
if (line.type === "Dialogue") {
var dialogueTemplate: Template = line.template;
if (libjass.verboseMode) {
console.log("Read dialogue: " + JSON.stringify(dialogueTemplate), dialogueTemplate);
}
// Create the dialogue and add it to the dialogues array
this._dialogues.push(new Dialogue(dialogueTemplate, this));
}
});
}
/**
* The horizontal script resolution.
*
* @type {number}
*/
get resolutionX(): number {
return this._resolutionX;
}
/**
* The vertical script resolution.
*
* @type {number}
*/
get resolutionY(): number {
return this._resolutionY;
}
/**
* After calling ASS.scaleTo(), this is the multiplicative factor to scale horizontal script resolution to video resolution.
*
* @type {number}
*/
get scaleX(): number {
return this._scaleX;
}
/**
* After calling ASS.scaleTo(), this is the multiplicative factor to scale vertical script resolution to video resolution.
*
* @type {number}
*/
get scaleY(): number {
return this._scaleY;
}
/**
* The styles in this script.
*
* @type {!Object.<string, !libjass.Style>}
*/
get styles(): StyleMap {
return this._styles;
}
/**
* The dialogues in this script.
*
* @type {!Array.<!libjass.Dialogue>}
*/
get dialogues(): Dialogue[] {
return this._dialogues;
}
/**
* This method takes in the actual video height and width and prepares the scaleX and scaleY
* properties according to the script resolution.
*
* @param {number} videoWidth The width of the video, in pixels
* @param {number} videoHeight The height of the video, in pixels
*/
scaleTo(videoWidth: number, videoHeight: number): void {
this._scaleX = videoWidth / this._resolutionX;
this._scaleY = videoHeight / this._resolutionY;
}
}
/**
* This class represents a single global style declaration in an ASS script. The styles can be obtained via the ASS.styles property.
*
* @constructor
* @param {!Object} template The template object that contains the style's properties. It is a map of the string values read from the ASS file.
* @param {string} template["Name"] The name of the style
* @param {string} template["Italic"] -1 if the style is italicized
* @param {string} template["Bold"] -1 if the style is bold
* @param {string} template["Underline"] -1 if the style is underlined
* @param {string} template["StrikeOut"] -1 if the style is struck-through
* @param {string} template["OutlineWidth"] The outline width
* @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["PrimaryColor"] The primary color
* @param {string} template["OutlineColor"] The outline color
* @param {string} template["Outline"] The outline width
* @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
*
* @memberof libjass
*/
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 _primaryColor: parts.Color;
private _outlineColor: parts.Color;
private _outlineWidth: number;
private _alignment: number;
private _marginLeft: number;
private _marginRight: number;
private _marginVertical: number;
constructor(template: Template) {
this._name = template["Name"];
this._italic = template["Italic"] === "-1";
this._bold = template["Bold"] === "-1";
this._underline = template["Underline"] === "-1";
this._strikeThrough = template["StrikeOut"] === "-1";
this._fontName = template["Fontname"];
this._fontSize = parseFloat(template["Fontsize"]);
this._fontScaleX = parseFloat(template["ScaleX"]) / 100;
this._fontScaleY = parseFloat(template["ScaleY"]) / 100;
this._letterSpacing = parseFloat(template["Spacing"]);
this._primaryColor = <parts.Color>parser.parse(template["PrimaryColour"], "colorWithAlpha");
this._outlineColor = <parts.Color>parser.parse(template["OutlineColour"], "colorWithAlpha");
this._outlineWidth = parseFloat(template["Outline"]);
this._alignment = parseInt(template["Alignment"]);
this._marginLeft = parseFloat(template["MarginL"]);
this._marginRight = parseFloat(template["MarginR"]);
this._marginVertical = parseFloat(template["MarginV"]);
}
/**
* The name of this style.
*
* @type {string}
*/
get name(): string {
return this._name;
}
/**
* Whether this style is italicized or not.
*
* @type {string}
*/
get italic(): boolean {
return this._italic;
}
/**
* Whether this style is bold or not.
*
* @type {boolean}
*/
get bold(): boolean {
return this._bold;
}
/**
* Whether this style is underlined or not.
*
* @type {boolean}
*/
get underline(): boolean {
return this._underline;
}
/**
* Whether this style is struck-through or not.
*
* @type {boolean}
*/
get strikeThrough(): boolean {
return this._strikeThrough;
}
/**
* The name of this style's font.
*
* @type {string}
*/
get fontName(): string {
return this._fontName;
}
/**
* The size of this style's font.
*
* @type {number}
*/
get fontSize(): number {
return this._fontSize;
}
/**
* The horizontal scaling of this style's font.
*
* @type {number}
*/
get fontScaleX(): number {
return this._fontScaleX;
}
/**
* The vertical scaling of this style's font.
*
* @type {number}
*/
get fontScaleY(): number {
return this._fontScaleY;
}
/**
* The letter spacing scaling of this style's font.
*
* @type {number}
*/
get letterSpacing(): number {
return this._letterSpacing;
}
/**
* The color of this style's font.
*
* @type {!libjass.parts.Color}
*/
get primaryColor(): parts.Color {
return this._primaryColor;
}
/**
* The color of this style's outline.
*
* @type {!libjass.parts.Color}
*/
get outlineColor(): parts.Color {
return this._outlineColor;
}
/**
* The width of this style's outline.
*
* @type {number}
*/
get outlineWidth(): number {
return this._outlineWidth;
}
/**
* The alignment of dialogues of this style.
*
* @type {number}
*/
get alignment(): number {
return this._alignment;
}
/**
* The left margin of dialogues of this style.
*
* @type {number}
*/
get marginLeft(): number {
return this._marginLeft;
}
/**
* The right margin of dialogues of this style.
*
* @type {number}
*/
get marginRight(): number {
return this._marginRight;
}
/**
* The vertical margin of dialogues of this style.
*
* @type {number}
*/
get marginVertical(): number {
return this._marginVertical;
}
};
/**
* This class represents a dialogue in an ASS script.
*
* @constructor
* @param {!Object} template The template object that contains the dialogue's properties. It is a map of the string values read from the ASS file.
* @param {string} template["Style"] The name of the default style of this dialogue
* @param {string} template["Start"] The start time
* @param {string} template["End"] The end time
* @param {string} template["Layer"] The layer number
* @param {string} template["Text"] The text of this dialogue
* @param {ASS} ass The ASS object to which this dialogue belongs
*
* @memberof libjass
*/
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 _transformOriginX: number;
private _transformOriginY: number;
private _transformOrigin: string;
private _rawPartsString: string;
private _parts: parts.Part[] = null;
private _sub: HTMLDivElement = null;
constructor(template: Template, ass: ASS) {
this._id = ++Dialogue._lastDialogueId;
this._style = ass.styles[template["Style"]];
this._start = Dialogue._toTime(template["Start"]);
this._end = Dialogue._toTime(template["End"]);
this._layer = Math.max(parseInt(template["Layer"]), 0);
this._alignment = this._style.alignment;
this._rawPartsString = template["Text"];
}
/**
* The unique ID of this dialogue. Auto-generated.
*
* @type {number}
*/
get id(): number {
return this._id;
}
/**
* The start time of this dialogue.
*
* @type {number}
*/
get start(): number {
return this._start;
}
/**
* The end time of this dialogue.
*
* @type {number}
*/
get end(): number {
return this._end;
}
get style(): Style {
return this._style;
}
/**
* The alignment number of this dialogue.
*
* @type {number}
*/
get alignment(): number {
if (this._parts === null) {
this._parsePartsString();
}
return this._alignment;
}
get transformOriginX(): number {
if (this._parts === null) {
this._parsePartsString();
}
return this._transformOriginX;
}
get transformOriginY(): number {
if (this._parts === null) {
this._parsePartsString();
}
return this._transformOriginY;
}
get transformOrigin(): string {
if (this._parts === null) {
this._parsePartsString();
}
return this._transformOrigin;
}
/**
* The layer number of this dialogue.
*
* @type {number}
*/
get layer(): number {
return this._layer;
}
/**
* The parts of this dialogue.
*
* @type {!Array.<!libjass.parts.Tag>}
*/
get parts(): parts.Part[] {
if (this._parts === null) {
this._parsePartsString();
}
return this._parts;
}
/**
* @return {string} A simple representation of this dialogue's properties and tags.
*/
toString(): string {
return "#" + this._id + " [" + this._start.toFixed(3) + "-" + this._end.toFixed(3) + "] " + ((this._parts !== null) ? this._parts.join(", ") : this._rawPartsString);
}
private _parsePartsString(): void {
this._parts = <parts.Part[]>parser.parse(this._rawPartsString, "dialogueParts");
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
);
}
}
});
switch (this._alignment) {
case 1: this._transformOriginX = 0; this._transformOriginY = 100; break;
case 2: this._transformOriginX = 50; this._transformOriginY = 100; break;
case 3: this._transformOriginX = 100; this._transformOriginY = 100; break;
case 4: this._transformOriginX = 0; this._transformOriginY = 50; break;
case 5: this._transformOriginX = 50; this._transformOriginY = 50; break;
case 6: this._transformOriginX = 100; this._transformOriginY = 50; break;
case 7: this._transformOriginX = 0; this._transformOriginY = 0; break;
case 8: this._transformOriginX = 50; this._transformOriginY = 0; break;
case 9: this._transformOriginX = 100; this._transformOriginY = 0; break;
}
this._transformOrigin = this._transformOriginX + "% " + this._transformOriginY + "%";
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} string
* @return {number}
*
* @private
*/
private static _toTime(str: string): number {
return str.split(":").reduce<number>((previousValue, currentValue) => previousValue * 60 + parseFloat(currentValue), 0);
}
}
/**
* A template object. It is a map of string keys and string values.
*/
export interface Template {
[key: string]: string;
}
/**
* Debug mode. When true, libjass logs some debug messages.
*
* @type {boolean}
*/
export var debugMode: boolean = false;
/**
* Verbose debug mode. When true, libjass logs some more debug messages. This setting is independent of debugMode.
*
* @type {boolean}
*/
export var verboseMode: boolean = false;
}
-271
View File
@@ -1,271 +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 CSSStyleDeclaration {
webkitAnimation: string
webkitAnimationDelay: string
webkitFilter: string
webkitTransform: string
webkitTransformOrigin: string
}
interface Document {
fullscreenElement: Element
mozFullScreenElement: Element
webkitFullscreenElement: Element
}
var global: any = (0, eval)("this");
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.
*
* @constructor
* @template T
*
* @private
* @memberof libjass
*/
class SimpleSet<T> implements Set<T> {
private _data: Object;
constructor() {
this.clear();
}
/**
* @param {T} value
* @return {libjass.Set.<T>} This set
*/
add(value: T): Set<T> {
var property = this._toProperty(value);
if (property === null) {
throw new Error("This Set implementation only supports Number and String values.");
}
this._data[property] = value;
return this;
}
clear(): void {
this._data = Object.create(null);
}
/**
* @param {T} value
* @return {boolean}
*/
has(value: T): boolean {
var property = this._toProperty(value);
if (property === null) {
return false;
}
return property in this._data;
}
/**
* @param {function(T, T, libjass.Set.<T>)} callbackfn A function that is called with each value in the set.
*/
forEach(callbackfn: (value: T, index: T, set: Set<T>) => void, thisArg?: any): void {
Object.keys(this._data).map((property: string) => {
var value = this._data[property];
callbackfn.call(thisArg, value, value, this);
});
}
delete(value: T): boolean {
throw new Error("This Set implementation doesn't support delete().");
}
get size(): number {
throw new Error("This Set implementation doesn't support size.");
}
private _toProperty(value: T): string {
if (typeof value == "number") {
return "#" + value;
}
else if (typeof value == "string") {
return "'" + value;
}
return null;
}
}
export var Set: {
new <T>(): Set<T>;
}
// Use this browser's implementation of Set if it has one
if (typeof global.Set !== "undefined" && typeof global.Set.prototype.forEach === "function") {
Set = global.Set;
}
else {
Set = SimpleSet;
}
/**
* Map implementation for browsers that don't support it. Only supports Number and String keys.
*
* Keys and values are stored as properties of an object, with property names derived from the key type.
*
* @constructor
* @template K, V
*
* @private
* @memberof libjass
*/
class SimpleMap<K, V> implements Map<K, V> {
private _keys: { [key: string]: K };
private _values: { [key: string]: V };
constructor() {
this.clear();
}
/**
* @param {K} key
* @return {V}
*/
get(key: K): V {
var property = this._keyToProperty(key);
if (property === null) {
return undefined;
}
return this._values[property];
}
/**
* @param {K} key
* @return {boolean}
*/
has(key: K): boolean {
var property = this._keyToProperty(key);
if (property === null) {
return false;
}
return property in this._keys;
}
/**
* @param {K} key
* @param {V} value
* @return {libjass.Map.<K, V>} This map
*/
set(key: K, value: V): Map<K, V> {
var property = this._keyToProperty(key);
if (property === null) {
throw new Error("This Map implementation only supports Number and String keys.");
}
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];
}
return result;
}
clear(): void {
this._keys = Object.create(null);
this._values = Object.create(null);
}
/**
* @param {function(V, K, libjass.Map.<K, V>)} callbackfn A function that is called with each key and value in the map.
*/
forEach(callbackfn: (value: V, index: K, map: Map<K, V>) => void, thisArg?: any): void {
var keysArray = Object.keys(this._keys);
for (var i = 0; i < keysArray.length; i++) {
var property = keysArray[i];
callbackfn.call(thisArg, this._values[property], this._keys[property], this);
}
}
get size(): number {
throw new Error("This Map implementation doesn't support size.");
}
private _keyToProperty(key: K): string {
if (typeof key == "number") {
return "#" + key;
}
else if (typeof key == "string") {
return "'" + key;
}
return null;
}
}
export var Map: {
new <K, V>(): Map<K, V>;
}
// Use this browser's implementation of Map if it has one
if (typeof global.Map !== "undefined" && typeof global.Map.prototype.forEach === "function") {
Map = global.Map;
}
else {
Map = <any>SimpleMap;
}
}
var module: any;
if (module && module.exports) {
module.exports = libjass;
}