move ssb-patchwork-api and ssb-patchwork-ui into this repo

This commit is contained in:
Paul Frazee
2015-09-22 13:44:28 -05:00
parent ca4830cec8
commit 0f60126e9d
1094 changed files with 22959 additions and 8 deletions

633
ui/vendor/behave.js vendored Normal file
View File

@@ -0,0 +1,633 @@
/*
* Behave.js
*
* Copyright 2013, Jacob Kelley - http://jakiestfu.com/
* Released under the MIT Licence
* http://opensource.org/licenses/MIT
*
* Github: http://github.com/jakiestfu/Behave.js/
* Version: 1.5
*/
(function(undefined){
'use strict';
var BehaveHooks = BehaveHooks || (function(){
var hooks = {};
return {
add: function(hookName, fn){
if(typeof hookName == "object"){
var i;
for(i=0; i<hookName.length; i++){
var theHook = hookName[i];
if(!hooks[theHook]){
hooks[theHook] = [];
}
hooks[theHook].push(fn);
}
} else {
if(!hooks[hookName]){
hooks[hookName] = [];
}
hooks[hookName].push(fn);
}
},
get: function(hookName){
if(hooks[hookName]){
return hooks[hookName];
}
}
};
})(),
Behave = Behave || function (userOpts) {
if (typeof String.prototype.repeat !== 'function') {
String.prototype.repeat = function(times) {
if(times < 1){
return '';
}
if(times % 2){
return this.repeat(times - 1) + this;
}
var half = this.repeat(times / 2);
return half + half;
};
}
if (typeof Array.prototype.filter !== 'function') {
Array.prototype.filter = function(func /*, thisp */) {
if (this === null) {
throw new TypeError();
}
var t = Object(this),
len = t.length >>> 0;
if (typeof func != "function"){
throw new TypeError();
}
var res = [],
thisp = arguments[1];
for (var i = 0; i < len; i++) {
if (i in t) {
var val = t[i];
if (func.call(thisp, val, i, t)) {
res.push(val);
}
}
}
return res;
};
}
var defaults = {
textarea: null,
replaceTab: true,
softTabs: true,
tabSize: 4,
autoOpen: true,
overwrite: true,
autoStrip: true,
autoIndent: true,
fence: false
},
tab,
newLine,
charSettings = {
keyMap: [
{ open: "\"", close: "\"", canBreak: false },
{ open: "'", close: "'", canBreak: false },
{ open: "(", close: ")", canBreak: false },
{ open: "[", close: "]", canBreak: true },
{ open: "{", close: "}", canBreak: true }
]
},
utils = {
_callHook: function(hookName, passData){
var hooks = BehaveHooks.get(hookName);
passData = typeof passData=="boolean" && passData === false ? false : true;
if(hooks){
if(passData){
var theEditor = defaults.textarea,
textVal = theEditor.value,
caretPos = utils.cursor.get(),
i;
for(i=0; i<hooks.length; i++){
hooks[i].call(undefined, {
editor: {
element: theEditor,
text: textVal,
levelsDeep: utils.levelsDeep()
},
caret: {
pos: caretPos
},
lines: {
current: utils.cursor.getLine(textVal, caretPos),
total: utils.editor.getLines(textVal)
}
});
}
} else {
for(i=0; i<hooks.length; i++){
hooks[i].call(undefined);
}
}
}
},
defineNewLine: function(){
var ta = document.createElement('textarea');
ta.value = "\n";
if(ta.value.length==2){
newLine = "\r\n";
} else {
newLine = "\n";
}
},
defineTabSize: function(tabSize){
if(typeof defaults.textarea.style.OTabSize != "undefined"){
defaults.textarea.style.OTabSize = tabSize; return;
}
if(typeof defaults.textarea.style.MozTabSize != "undefined"){
defaults.textarea.style.MozTabSize = tabSize; return;
}
if(typeof defaults.textarea.style.tabSize != "undefined"){
defaults.textarea.style.tabSize = tabSize; return;
}
},
cursor: {
getLine: function(textVal, pos){
return ((textVal.substring(0,pos)).split("\n")).length;
},
get: function() {
if (typeof document.createElement('textarea').selectionStart==="number") {
return defaults.textarea.selectionStart;
} else if (document.selection) {
var caretPos = 0,
range = defaults.textarea.createTextRange(),
rangeDupe = document.selection.createRange().duplicate(),
rangeDupeBookmark = rangeDupe.getBookmark();
range.moveToBookmark(rangeDupeBookmark);
while (range.moveStart('character' , -1) !== 0) {
caretPos++;
}
return caretPos;
}
},
set: function (start, end) {
if(!end){
end = start;
}
if (defaults.textarea.setSelectionRange) {
defaults.textarea.focus();
defaults.textarea.setSelectionRange(start, end);
} else if (defaults.textarea.createTextRange) {
var range = defaults.textarea.createTextRange();
range.collapse(true);
range.moveEnd('character', end);
range.moveStart('character', start);
range.select();
}
},
selection: function(){
var textAreaElement = defaults.textarea,
start = 0,
end = 0,
normalizedValue,
range,
textInputRange,
len,
endRange;
if (typeof textAreaElement.selectionStart == "number" && typeof textAreaElement.selectionEnd == "number") {
start = textAreaElement.selectionStart;
end = textAreaElement.selectionEnd;
} else {
range = document.selection.createRange();
if (range && range.parentElement() == textAreaElement) {
normalizedValue = utils.editor.get();
len = normalizedValue.length;
textInputRange = textAreaElement.createTextRange();
textInputRange.moveToBookmark(range.getBookmark());
endRange = textAreaElement.createTextRange();
endRange.collapse(false);
if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
start = end = len;
} else {
start = -textInputRange.moveStart("character", -len);
start += normalizedValue.slice(0, start).split(newLine).length - 1;
if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
end = len;
} else {
end = -textInputRange.moveEnd("character", -len);
end += normalizedValue.slice(0, end).split(newLine).length - 1;
}
}
}
}
return start==end ? false : {
start: start,
end: end
};
}
},
editor: {
getLines: function(textVal){
return (textVal).split("\n").length;
},
get: function(){
return defaults.textarea.value.replace(/\r/g,'');
},
set: function(data){
defaults.textarea.value = data;
}
},
fenceRange: function(){
if(typeof defaults.fence == "string"){
var data = utils.editor.get(),
pos = utils.cursor.get(),
hacked = 0,
matchedFence = data.indexOf(defaults.fence),
matchCase = 0;
while(matchedFence>=0){
matchCase++;
if( pos < (matchedFence+hacked) ){
break;
}
hacked += matchedFence+defaults.fence.length;
data = data.substring(matchedFence+defaults.fence.length);
matchedFence = data.indexOf(defaults.fence);
}
if( (hacked) < pos && ( (matchedFence+hacked) > pos ) && matchCase%2===0){
return true;
}
return false;
} else {
return true;
}
},
isEven: function(_this,i){
return i%2;
},
levelsDeep: function(){
var pos = utils.cursor.get(),
val = utils.editor.get();
var left = val.substring(0, pos),
levels = 0,
i, j;
for(i=0; i<left.length; i++){
for (j=0; j<charSettings.keyMap.length; j++) {
if(charSettings.keyMap[j].canBreak){
if(charSettings.keyMap[j].open == left.charAt(i)){
levels++;
}
if(charSettings.keyMap[j].close == left.charAt(i)){
levels--;
}
}
}
}
var toDecrement = 0,
quoteMap = ["'", "\""];
for(i=0; i<charSettings.keyMap.length; i++) {
if(charSettings.keyMap[i].canBreak){
for(j in quoteMap){
toDecrement += left.split(quoteMap[j]).filter(utils.isEven).join('').split(charSettings.keyMap[i].open).length - 1;
}
}
}
var finalLevels = levels - toDecrement;
return finalLevels >=0 ? finalLevels : 0;
},
deepExtend: function(destination, source) {
for (var property in source) {
if (source[property] && source[property].constructor &&
source[property].constructor === Object) {
destination[property] = destination[property] || {};
utils.deepExtend(destination[property], source[property]);
} else {
destination[property] = source[property];
}
}
return destination;
},
addEvent: function addEvent(element, eventName, func) {
if (element.addEventListener){
element.addEventListener(eventName,func,false);
} else if (element.attachEvent) {
element.attachEvent("on"+eventName, func);
}
},
removeEvent: function addEvent(element, eventName, func){
if (element.addEventListener){
element.removeEventListener(eventName,func,false);
} else if (element.attachEvent) {
element.detachEvent("on"+eventName, func);
}
},
preventDefaultEvent: function(e){
if(e.preventDefault){
e.preventDefault();
} else {
e.returnValue = false;
}
}
},
intercept = {
tabKey: function (e) {
if(!utils.fenceRange()){ return; }
if (e.keyCode == 9) {
utils.preventDefaultEvent(e);
var toReturn = true;
utils._callHook('tab:before');
var selection = utils.cursor.selection(),
pos = utils.cursor.get(),
val = utils.editor.get();
if(selection){
var tempStart = selection.start;
while(tempStart--){
if(val.charAt(tempStart)=="\n"){
selection.start = tempStart + 1;
break;
}
}
var toIndent = val.substring(selection.start, selection.end),
lines = toIndent.split("\n"),
i;
if(e.shiftKey){
for(i = 0; i<lines.length; i++){
if(lines[i].substring(0,tab.length) == tab){
lines[i] = lines[i].substring(tab.length);
}
}
toIndent = lines.join("\n");
utils.editor.set( val.substring(0,selection.start) + toIndent + val.substring(selection.end) );
utils.cursor.set(selection.start, selection.start+toIndent.length);
} else {
for(i in lines){
lines[i] = tab + lines[i];
}
toIndent = lines.join("\n");
utils.editor.set( val.substring(0,selection.start) + toIndent + val.substring(selection.end) );
utils.cursor.set(selection.start, selection.start+toIndent.length);
}
} else {
var left = val.substring(0, pos),
right = val.substring(pos),
edited = left + tab + right;
if(e.shiftKey){
if(val.substring(pos-tab.length, pos) == tab){
edited = val.substring(0, pos-tab.length) + right;
utils.editor.set(edited);
utils.cursor.set(pos-tab.length);
}
} else {
utils.editor.set(edited);
utils.cursor.set(pos + tab.length);
toReturn = false;
}
}
utils._callHook('tab:after');
}
return toReturn;
},
enterKey: function (e) {
if(!utils.fenceRange()){ return; }
if (e.keyCode == 13) {
utils.preventDefaultEvent(e);
utils._callHook('enter:before');
var pos = utils.cursor.get(),
val = utils.editor.get(),
left = val.substring(0, pos),
right = val.substring(pos),
leftChar = left.charAt(left.length - 1),
rightChar = right.charAt(0),
numTabs = utils.levelsDeep(),
ourIndent = "",
closingBreak = "",
finalCursorPos,
i;
if(!numTabs){
finalCursorPos = 1;
} else {
while(numTabs--){
ourIndent+=tab;
}
ourIndent = ourIndent;
finalCursorPos = ourIndent.length + 1;
for(i=0; i<charSettings.keyMap.length; i++) {
if (charSettings.keyMap[i].open == leftChar && charSettings.keyMap[i].close == rightChar){
closingBreak = newLine;
}
}
}
var edited = left + newLine + ourIndent + closingBreak + (ourIndent.substring(0, ourIndent.length-tab.length) ) + right;
utils.editor.set(edited);
utils.cursor.set(pos + finalCursorPos);
utils._callHook('enter:after');
}
},
deleteKey: function (e) {
if(!utils.fenceRange()){ return; }
if(e.keyCode == 8){
utils.preventDefaultEvent(e);
utils._callHook('delete:before');
var pos = utils.cursor.get(),
val = utils.editor.get(),
left = val.substring(0, pos),
right = val.substring(pos),
leftChar = left.charAt(left.length - 1),
rightChar = right.charAt(0),
i;
if( utils.cursor.selection() === false ){
for(i=0; i<charSettings.keyMap.length; i++) {
if (charSettings.keyMap[i].open == leftChar && charSettings.keyMap[i].close == rightChar) {
var edited = val.substring(0,pos-1) + val.substring(pos+1);
utils.editor.set(edited);
utils.cursor.set(pos - 1);
return;
}
}
var edited = val.substring(0,pos-1) + val.substring(pos);
utils.editor.set(edited);
utils.cursor.set(pos - 1);
} else {
var sel = utils.cursor.selection(),
edited = val.substring(0,sel.start) + val.substring(sel.end);
utils.editor.set(edited);
utils.cursor.set(pos);
}
utils._callHook('delete:after');
}
}
},
charFuncs = {
openedChar: function (_char, e) {
utils.preventDefaultEvent(e);
utils._callHook('openChar:before');
var pos = utils.cursor.get(),
val = utils.editor.get(),
left = val.substring(0, pos),
right = val.substring(pos),
edited = left + _char.open + _char.close + right;
defaults.textarea.value = edited;
utils.cursor.set(pos + 1);
utils._callHook('openChar:after');
},
closedChar: function (_char, e) {
var pos = utils.cursor.get(),
val = utils.editor.get(),
toOverwrite = val.substring(pos, pos + 1);
if (toOverwrite == _char.close) {
utils.preventDefaultEvent(e);
utils._callHook('closeChar:before');
utils.cursor.set(utils.cursor.get() + 1);
utils._callHook('closeChar:after');
return true;
}
return false;
}
},
action = {
filter: function (e) {
if(!utils.fenceRange()){ return; }
var theCode = e.which || e.keyCode;
if(theCode == 39 || theCode == 40 && e.which===0){ return; }
var _char = String.fromCharCode(theCode),
i;
for(i=0; i<charSettings.keyMap.length; i++) {
if (charSettings.keyMap[i].close == _char) {
var didClose = defaults.overwrite && charFuncs.closedChar(charSettings.keyMap[i], e);
if (!didClose && charSettings.keyMap[i].open == _char && defaults.autoOpen) {
charFuncs.openedChar(charSettings.keyMap[i], e);
}
} else if (charSettings.keyMap[i].open == _char && defaults.autoOpen) {
charFuncs.openedChar(charSettings.keyMap[i], e);
}
}
},
listen: function () {
if(defaults.replaceTab){ utils.addEvent(defaults.textarea, 'keydown', intercept.tabKey); }
if(defaults.autoIndent){ utils.addEvent(defaults.textarea, 'keydown', intercept.enterKey); }
if(defaults.autoStrip){ utils.addEvent(defaults.textarea, 'keydown', intercept.deleteKey); }
utils.addEvent(defaults.textarea, 'keypress', action.filter);
utils.addEvent(defaults.textarea, 'keydown', function(){ utils._callHook('keydown'); });
utils.addEvent(defaults.textarea, 'keyup', function(){ utils._callHook('keyup'); });
}
},
init = function (opts) {
if(opts.textarea){
utils._callHook('init:before', false);
utils.deepExtend(defaults, opts);
utils.defineNewLine();
if (defaults.softTabs) {
tab = " ".repeat(defaults.tabSize);
} else {
tab = "\t";
utils.defineTabSize(defaults.tabSize);
}
action.listen();
utils._callHook('init:after', false);
}
};
this.destroy = function(){
utils.removeEvent(defaults.textarea, 'keydown', intercept.tabKey);
utils.removeEvent(defaults.textarea, 'keydown', intercept.enterKey);
utils.removeEvent(defaults.textarea, 'keydown', intercept.deleteKey);
utils.removeEvent(defaults.textarea, 'keypress', action.filter);
};
init(userOpts);
};
if (typeof module !== 'undefined' && module.exports) {
module.exports = Behave;
}
if (typeof ender === 'undefined') {
this.Behave = Behave;
this.BehaveHooks = BehaveHooks;
}
if (typeof define === "function" && define.amd) {
define("behave", [], function () {
return Behave;
});
}
}).call(this);

95
ui/vendor/canvas-to-blob.js vendored Normal file
View File

@@ -0,0 +1,95 @@
/*
* JavaScript Canvas to Blob 2.0.5
* https://github.com/blueimp/JavaScript-Canvas-to-Blob
*
* Copyright 2012, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*
* Based on stackoverflow user Stoive's code snippet:
* http://stackoverflow.com/q/4998908
*/
/*jslint nomen: true, regexp: true */
/*global window, atob, Blob, ArrayBuffer, Uint8Array, define */
(function (window) {
'use strict';
var CanvasPrototype = window.HTMLCanvasElement &&
window.HTMLCanvasElement.prototype,
hasBlobConstructor = window.Blob && (function () {
try {
return Boolean(new Blob());
} catch (e) {
return false;
}
}()),
hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array &&
(function () {
try {
return new Blob([new Uint8Array(100)]).size === 100;
} catch (e) {
return false;
}
}()),
BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder ||
window.MozBlobBuilder || window.MSBlobBuilder,
dataURLtoBlob = (hasBlobConstructor || BlobBuilder) && window.atob &&
window.ArrayBuffer && window.Uint8Array && function (dataURI) {
var byteString,
arrayBuffer,
intArray,
i,
mimeString,
bb;
if (dataURI.split(',')[0].indexOf('base64') >= 0) {
// Convert base64 to raw binary data held in a string:
byteString = atob(dataURI.split(',')[1]);
} else {
// Convert base64/URLEncoded data component to raw binary data:
byteString = decodeURIComponent(dataURI.split(',')[1]);
}
// Write the bytes of the string to an ArrayBuffer:
arrayBuffer = new ArrayBuffer(byteString.length);
intArray = new Uint8Array(arrayBuffer);
for (i = 0; i < byteString.length; i += 1) {
intArray[i] = byteString.charCodeAt(i);
}
// Separate out the mime component:
mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
// Write the ArrayBuffer (or ArrayBufferView) to a blob:
if (hasBlobConstructor) {
return new Blob(
[hasArrayBufferViewSupport ? intArray : arrayBuffer],
{type: mimeString}
);
}
bb = new BlobBuilder();
bb.append(arrayBuffer);
return bb.getBlob(mimeString);
};
if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) {
if (CanvasPrototype.mozGetAsFile) {
CanvasPrototype.toBlob = function (callback, type, quality) {
if (quality && CanvasPrototype.toDataURL && dataURLtoBlob) {
callback(dataURLtoBlob(this.toDataURL(type, quality)));
} else {
callback(this.mozGetAsFile('blob', type));
}
};
} else if (CanvasPrototype.toDataURL && dataURLtoBlob) {
CanvasPrototype.toBlob = function (callback, type, quality) {
callback(dataURLtoBlob(this.toDataURL(type, quality)));
};
}
}
if (typeof define === 'function' && define.amd) {
define(function () {
return dataURLtoBlob;
});
} else {
window.dataURLtoBlob = dataURLtoBlob;
}
}(this));

4
ui/vendor/sigma.min.js vendored Executable file

File diff suppressed because one or more lines are too long

538
ui/vendor/whammy.js vendored Normal file
View File

@@ -0,0 +1,538 @@
// https://github.com/antimatter15/whammy/tree/4effe219137e48787f1e82c8bbc64fbf7b4cfeeb
/*
var vid = new Whammy.Video();
vid.add(canvas or data url)
vid.compile()
*/
window.Whammy = (function(){
// in this case, frames has a very specific meaning, which will be
// detailed once i finish writing the code
function toWebM(frames, outputAsArray){
var info = checkFrames(frames);
//max duration by cluster in milliseconds
var CLUSTER_MAX_DURATION = 30000;
var EBML = [
{
"id": 0x1a45dfa3, // EBML
"data": [
{
"data": 1,
"id": 0x4286 // EBMLVersion
},
{
"data": 1,
"id": 0x42f7 // EBMLReadVersion
},
{
"data": 4,
"id": 0x42f2 // EBMLMaxIDLength
},
{
"data": 8,
"id": 0x42f3 // EBMLMaxSizeLength
},
{
"data": "webm",
"id": 0x4282 // DocType
},
{
"data": 2,
"id": 0x4287 // DocTypeVersion
},
{
"data": 2,
"id": 0x4285 // DocTypeReadVersion
}
]
},
{
"id": 0x18538067, // Segment
"data": [
{
"id": 0x1549a966, // Info
"data": [
{
"data": 1e6, //do things in millisecs (num of nanosecs for duration scale)
"id": 0x2ad7b1 // TimecodeScale
},
{
"data": "whammy",
"id": 0x4d80 // MuxingApp
},
{
"data": "whammy",
"id": 0x5741 // WritingApp
},
{
"data": doubleToString(info.duration),
"id": 0x4489 // Duration
}
]
},
{
"id": 0x1654ae6b, // Tracks
"data": [
{
"id": 0xae, // TrackEntry
"data": [
{
"data": 1,
"id": 0xd7 // TrackNumber
},
{
"data": 1,
"id": 0x73c5 // TrackUID
},
{
"data": 0,
"id": 0x9c // FlagLacing
},
{
"data": "und",
"id": 0x22b59c // Language
},
{
"data": "V_VP8",
"id": 0x86 // CodecID
},
{
"data": "VP8",
"id": 0x258688 // CodecName
},
{
"data": 1,
"id": 0x83 // TrackType
},
{
"id": 0xe0, // Video
"data": [
{
"data": info.width,
"id": 0xb0 // PixelWidth
},
{
"data": info.height,
"id": 0xba // PixelHeight
}
]
}
]
}
]
},
{
"id": 0x1c53bb6b, // Cues
"data": [
//cue insertion point
]
}
//cluster insertion point
]
}
];
var segment = EBML[1];
var cues = segment.data[2];
//Generate clusters (max duration)
var frameNumber = 0;
var clusterTimecode = 0;
while(frameNumber < frames.length){
var cuePoint = {
"id": 0xbb, // CuePoint
"data": [
{
"data": Math.round(clusterTimecode),
"id": 0xb3 // CueTime
},
{
"id": 0xb7, // CueTrackPositions
"data": [
{
"data": 1,
"id": 0xf7 // CueTrack
},
{
"data": 0, // to be filled in when we know it
"size": 8,
"id": 0xf1 // CueClusterPosition
}
]
}
]
};
cues.data.push(cuePoint);
var clusterFrames = [];
var clusterDuration = 0;
do {
clusterFrames.push(frames[frameNumber]);
clusterDuration += frames[frameNumber].duration;
frameNumber++;
}while(frameNumber < frames.length && clusterDuration < CLUSTER_MAX_DURATION);
var clusterCounter = 0;
var cluster = {
"id": 0x1f43b675, // Cluster
"data": [
{
"data": Math.round(clusterTimecode),
"id": 0xe7 // Timecode
}
].concat(clusterFrames.map(function(webp){
var block = makeSimpleBlock({
discardable: 0,
frame: webp.data.slice(4),
invisible: 0,
keyframe: 1,
lacing: 0,
trackNum: 1,
timecode: Math.round(clusterCounter)
});
clusterCounter += webp.duration;
return {
data: block,
id: 0xa3
};
}))
}
//Add cluster to segment
segment.data.push(cluster);
clusterTimecode += clusterDuration;
}
//First pass to compute cluster positions
var position = 0;
for(var i = 0; i < segment.data.length; i++){
if (i >= 3) {
cues.data[i-3].data[1].data[1].data = position;
}
var data = generateEBML([segment.data[i]], outputAsArray);
position += data.size || data.byteLength || data.length;
if (i != 2) { // not cues
//Save results to avoid having to encode everything twice
segment.data[i] = data;
}
}
return generateEBML(EBML, outputAsArray)
}
// sums the lengths of all the frames and gets the duration, woo
function checkFrames(frames){
var width = frames[0].width,
height = frames[0].height,
duration = frames[0].duration;
for(var i = 1; i < frames.length; i++){
if(frames[i].width != width) throw "Frame " + (i + 1) + " has a different width";
if(frames[i].height != height) throw "Frame " + (i + 1) + " has a different height";
if(frames[i].duration < 0 || frames[i].duration > 0x7fff) throw "Frame " + (i + 1) + " has a weird duration (must be between 0 and 32767)";
duration += frames[i].duration;
}
return {
duration: duration,
width: width,
height: height
};
}
function numToBuffer(num){
var parts = [];
while(num > 0){
parts.push(num & 0xff)
num = num >> 8
}
return new Uint8Array(parts.reverse());
}
function numToFixedBuffer(num, size){
var parts = new Uint8Array(size);
for(var i = size - 1; i >= 0; i--){
parts[i] = num & 0xff;
num = num >> 8;
}
return parts;
}
function strToBuffer(str){
// return new Blob([str]);
var arr = new Uint8Array(str.length);
for(var i = 0; i < str.length; i++){
arr[i] = str.charCodeAt(i)
}
return arr;
// this is slower
// return new Uint8Array(str.split('').map(function(e){
// return e.charCodeAt(0)
// }))
}
//sorry this is ugly, and sort of hard to understand exactly why this was done
// at all really, but the reason is that there's some code below that i dont really
// feel like understanding, and this is easier than using my brain.
function bitsToBuffer(bits){
var data = [];
var pad = (bits.length % 8) ? (new Array(1 + 8 - (bits.length % 8))).join('0') : '';
bits = pad + bits;
for(var i = 0; i < bits.length; i+= 8){
data.push(parseInt(bits.substr(i,8),2))
}
return new Uint8Array(data);
}
function generateEBML(json, outputAsArray){
var ebml = [];
for(var i = 0; i < json.length; i++){
if (!('id' in json[i])){
//already encoded blob or byteArray
ebml.push(json[i]);
continue;
}
var data = json[i].data;
if(typeof data == 'object') data = generateEBML(data, outputAsArray);
if(typeof data == 'number') data = ('size' in json[i]) ? numToFixedBuffer(data, json[i].size) : bitsToBuffer(data.toString(2));
if(typeof data == 'string') data = strToBuffer(data);
if(data.length){
var z = z;
}
var len = data.size || data.byteLength || data.length;
var zeroes = Math.ceil(Math.ceil(Math.log(len)/Math.log(2))/8);
var size_str = len.toString(2);
var padded = (new Array((zeroes * 7 + 7 + 1) - size_str.length)).join('0') + size_str;
var size = (new Array(zeroes)).join('0') + '1' + padded;
//i actually dont quite understand what went on up there, so I'm not really
//going to fix this, i'm probably just going to write some hacky thing which
//converts that string into a buffer-esque thing
ebml.push(numToBuffer(json[i].id));
ebml.push(bitsToBuffer(size));
ebml.push(data)
}
//output as blob or byteArray
if(outputAsArray){
//convert ebml to an array
var buffer = toFlatArray(ebml)
return new Uint8Array(buffer);
}else{
return new Blob(ebml, {type: "video/webm"});
}
}
function toFlatArray(arr, outBuffer){
if(outBuffer == null){
outBuffer = [];
}
for(var i = 0; i < arr.length; i++){
if(typeof arr[i] == 'object'){
//an array
toFlatArray(arr[i], outBuffer)
}else{
//a simple element
outBuffer.push(arr[i]);
}
}
return outBuffer;
}
//OKAY, so the following two functions are the string-based old stuff, the reason they're
//still sort of in here, is that they're actually faster than the new blob stuff because
//getAsFile isn't widely implemented, or at least, it doesn't work in chrome, which is the
// only browser which supports get as webp
//Converting between a string of 0010101001's and binary back and forth is probably inefficient
//TODO: get rid of this function
function toBinStr_old(bits){
var data = '';
var pad = (bits.length % 8) ? (new Array(1 + 8 - (bits.length % 8))).join('0') : '';
bits = pad + bits;
for(var i = 0; i < bits.length; i+= 8){
data += String.fromCharCode(parseInt(bits.substr(i,8),2))
}
return data;
}
function generateEBML_old(json){
var ebml = '';
for(var i = 0; i < json.length; i++){
var data = json[i].data;
if(typeof data == 'object') data = generateEBML_old(data);
if(typeof data == 'number') data = toBinStr_old(data.toString(2));
var len = data.length;
var zeroes = Math.ceil(Math.ceil(Math.log(len)/Math.log(2))/8);
var size_str = len.toString(2);
var padded = (new Array((zeroes * 7 + 7 + 1) - size_str.length)).join('0') + size_str;
var size = (new Array(zeroes)).join('0') + '1' + padded;
ebml += toBinStr_old(json[i].id.toString(2)) + toBinStr_old(size) + data;
}
return ebml;
}
//woot, a function that's actually written for this project!
//this parses some json markup and makes it into that binary magic
//which can then get shoved into the matroska comtainer (peaceably)
function makeSimpleBlock(data){
var flags = 0;
if (data.keyframe) flags |= 128;
if (data.invisible) flags |= 8;
if (data.lacing) flags |= (data.lacing << 1);
if (data.discardable) flags |= 1;
if (data.trackNum > 127) {
throw "TrackNumber > 127 not supported";
}
var out = [data.trackNum | 0x80, data.timecode >> 8, data.timecode & 0xff, flags].map(function(e){
return String.fromCharCode(e)
}).join('') + data.frame;
return out;
}
// here's something else taken verbatim from weppy, awesome rite?
function parseWebP(riff){
var VP8 = riff.RIFF[0].WEBP[0];
var frame_start = VP8.indexOf('\x9d\x01\x2a'); //A VP8 keyframe starts with the 0x9d012a header
for(var i = 0, c = []; i < 4; i++) c[i] = VP8.charCodeAt(frame_start + 3 + i);
var width, horizontal_scale, height, vertical_scale, tmp;
//the code below is literally copied verbatim from the bitstream spec
tmp = (c[1] << 8) | c[0];
width = tmp & 0x3FFF;
horizontal_scale = tmp >> 14;
tmp = (c[3] << 8) | c[2];
height = tmp & 0x3FFF;
vertical_scale = tmp >> 14;
return {
width: width,
height: height,
data: VP8,
riff: riff
}
}
// i think i'm going off on a riff by pretending this is some known
// idiom which i'm making a casual and brilliant pun about, but since
// i can't find anything on google which conforms to this idiomatic
// usage, I'm assuming this is just a consequence of some psychotic
// break which makes me make up puns. well, enough riff-raff (aha a
// rescue of sorts), this function was ripped wholesale from weppy
function parseRIFF(string){
var offset = 0;
var chunks = {};
while (offset < string.length) {
var id = string.substr(offset, 4);
chunks[id] = chunks[id] || [];
if (id == 'RIFF' || id == 'LIST') {
var len = parseInt(string.substr(offset + 4, 4).split('').map(function(i){
var unpadded = i.charCodeAt(0).toString(2);
return (new Array(8 - unpadded.length + 1)).join('0') + unpadded
}).join(''),2);
var data = string.substr(offset + 4 + 4, len);
offset += 4 + 4 + len;
chunks[id].push(parseRIFF(data));
} else if (id == 'WEBP') {
// Use (offset + 8) to skip past "VP8 "/"VP8L"/"VP8X" field after "WEBP"
chunks[id].push(string.substr(offset + 8));
offset = string.length;
} else {
// Unknown chunk type; push entire payload
chunks[id].push(string.substr(offset + 4));
offset = string.length;
}
}
return chunks;
}
// here's a little utility function that acts as a utility for other functions
// basically, the only purpose is for encoding "Duration", which is encoded as
// a double (considerably more difficult to encode than an integer)
function doubleToString(num){
return [].slice.call(
new Uint8Array(
(
new Float64Array([num]) //create a float64 array
).buffer) //extract the array buffer
, 0) // convert the Uint8Array into a regular array
.map(function(e){ //since it's a regular array, we can now use map
return String.fromCharCode(e) // encode all the bytes individually
})
.reverse() //correct the byte endianness (assume it's little endian for now)
.join('') // join the bytes in holy matrimony as a string
}
function WhammyVideo(speed, quality){ // a more abstract-ish API
this.frames = [];
this.duration = 1000 / speed;
this.quality = quality || 0.8;
}
WhammyVideo.prototype.add = function(frame, duration){
if(typeof duration != 'undefined' && this.duration) throw "you can't pass a duration if the fps is set";
if(typeof duration == 'undefined' && !this.duration) throw "if you don't have the fps set, you ned to have durations here.";
if(frame.canvas){ //CanvasRenderingContext2D
frame = frame.canvas;
}
if(frame.toDataURL){
frame = frame.toDataURL('image/webp', this.quality)
}else if(typeof frame != "string"){
throw "frame must be a a HTMLCanvasElement, a CanvasRenderingContext2D or a DataURI formatted string"
}
if (!(/^data:image\/webp;base64,/ig).test(frame)) {
throw "Input must be formatted properly as a base64 encoded DataURI of type image/webp";
}
this.frames.push({
image: frame,
duration: duration || this.duration
})
}
WhammyVideo.prototype.compile = function(outputAsArray){
return new toWebM(this.frames.map(function(frame){
var webp = parseWebP(parseRIFF(atob(frame.image.slice(23))));
webp.duration = frame.duration;
return webp;
}), outputAsArray)
}
return {
Video: WhammyVideo,
fromImageArray: function(images, fps, outputAsArray){
return toWebM(images.map(function(image){
var webp = parseWebP(parseRIFF(atob(image.slice(23))))
webp.duration = 1000 / fps;
return webp;
}), outputAsArray)
},
toWebM: toWebM
// expose methods of madness
}
})()