summaryrefslogtreecommitdiff
path: root/js/record.concat.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/record.concat.js')
-rw-r--r--js/record.concat.js720
1 files changed, 720 insertions, 0 deletions
diff --git a/js/record.concat.js b/js/record.concat.js
new file mode 100644
index 0000000..4a03964
--- /dev/null
+++ b/js/record.concat.js
@@ -0,0 +1,720 @@
+/* FileSaver.js
+ * A saveAs() FileSaver implementation.
+ * 2013-10-21
+ *
+ * By Eli Grey, http://eligrey.com
+ * License: X11/MIT
+ * See LICENSE.md
+ */
+
+/*global self */
+/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
+ plusplus: true */
+
+/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
+
+var saveAs = saveAs
+ || (typeof navigator !== 'undefined' && navigator.msSaveOrOpenBlob && navigator.msSaveOrOpenBlob.bind(navigator))
+ || (function(view) {
+ "use strict";
+ var
+ doc = view.document
+ // only get URL when necessary in case BlobBuilder.js hasn't overridden it yet
+ , get_URL = function() {
+ return view.URL || view.webkitURL || view;
+ }
+ , URL = view.URL || view.webkitURL || view
+ , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
+ , can_use_save_link = !view.externalHost && "download" in save_link
+ , click = function(node) {
+ var event = doc.createEvent("MouseEvents");
+ event.initMouseEvent(
+ "click", true, false, view, 0, 0, 0, 0, 0
+ , false, false, false, false, 0, null
+ );
+ node.dispatchEvent(event);
+ }
+ , webkit_req_fs = view.webkitRequestFileSystem
+ , req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem
+ , throw_outside = function (ex) {
+ (view.setImmediate || view.setTimeout)(function() {
+ throw ex;
+ }, 0);
+ }
+ , force_saveable_type = "application/octet-stream"
+ , fs_min_size = 0
+ , deletion_queue = []
+ , process_deletion_queue = function() {
+ var i = deletion_queue.length;
+ while (i--) {
+ var file = deletion_queue[i];
+ if (typeof file === "string") { // file is an object URL
+ URL.revokeObjectURL(file);
+ } else { // file is a File
+ file.remove();
+ }
+ }
+ deletion_queue.length = 0; // clear queue
+ }
+ , dispatch = function(filesaver, event_types, event) {
+ event_types = [].concat(event_types);
+ var i = event_types.length;
+ while (i--) {
+ var listener = filesaver["on" + event_types[i]];
+ if (typeof listener === "function") {
+ try {
+ listener.call(filesaver, event || filesaver);
+ } catch (ex) {
+ throw_outside(ex);
+ }
+ }
+ }
+ }
+ , FileSaver = function(blob, name) {
+ // First try a.download, then web filesystem, then object URLs
+ var
+ filesaver = this
+ , type = blob.type
+ , blob_changed = false
+ , object_url
+ , target_view
+ , get_object_url = function() {
+ var object_url = get_URL().createObjectURL(blob);
+ deletion_queue.push(object_url);
+ return object_url;
+ }
+ , dispatch_all = function() {
+ dispatch(filesaver, "writestart progress write writeend".split(" "));
+ }
+ // on any filesys errors revert to saving with object URLs
+ , fs_error = function() {
+ // don't create more object URLs than needed
+ if (blob_changed || !object_url) {
+ object_url = get_object_url(blob);
+ }
+ if (target_view) {
+ target_view.location.href = object_url;
+ } else {
+ window.open(object_url, "_blank");
+ }
+ filesaver.readyState = filesaver.DONE;
+ dispatch_all();
+ }
+ , abortable = function(func) {
+ return function() {
+ if (filesaver.readyState !== filesaver.DONE) {
+ return func.apply(this, arguments);
+ }
+ };
+ }
+ , create_if_not_found = {create: true, exclusive: false}
+ , slice
+ ;
+ filesaver.readyState = filesaver.INIT;
+ if (!name) {
+ name = "download";
+ }
+ if (can_use_save_link) {
+ object_url = get_object_url(blob);
+ // FF for Android has a nasty garbage collection mechanism
+ // that turns all objects that are not pure javascript into 'deadObject'
+ // this means `doc` and `save_link` are unusable and need to be recreated
+ // `view` is usable though:
+ doc = view.document;
+ save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a");
+ save_link.href = object_url;
+ save_link.download = name;
+ var event = doc.createEvent("MouseEvents");
+ event.initMouseEvent(
+ "click", true, false, view, 0, 0, 0, 0, 0
+ , false, false, false, false, 0, null
+ );
+ save_link.dispatchEvent(event);
+ filesaver.readyState = filesaver.DONE;
+ dispatch_all();
+ return;
+ }
+ // Object and web filesystem URLs have a problem saving in Google Chrome when
+ // viewed in a tab, so I force save with application/octet-stream
+ // http://code.google.com/p/chromium/issues/detail?id=91158
+ if (view.chrome && type && type !== force_saveable_type) {
+ slice = blob.slice || blob.webkitSlice;
+ blob = slice.call(blob, 0, blob.size, force_saveable_type);
+ blob_changed = true;
+ }
+ // Since I can't be sure that the guessed media type will trigger a download
+ // in WebKit, I append .download to the filename.
+ // https://bugs.webkit.org/show_bug.cgi?id=65440
+ if (webkit_req_fs && name !== "download") {
+ name += ".download";
+ }
+ if (type === force_saveable_type || webkit_req_fs) {
+ target_view = view;
+ }
+ if (!req_fs) {
+ fs_error();
+ return;
+ }
+ fs_min_size += blob.size;
+ req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) {
+ fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) {
+ var save = function() {
+ dir.getFile(name, create_if_not_found, abortable(function(file) {
+ file.createWriter(abortable(function(writer) {
+ writer.onwriteend = function(event) {
+ target_view.location.href = file.toURL();
+ deletion_queue.push(file);
+ filesaver.readyState = filesaver.DONE;
+ dispatch(filesaver, "writeend", event);
+ };
+ writer.onerror = function() {
+ var error = writer.error;
+ if (error.code !== error.ABORT_ERR) {
+ fs_error();
+ }
+ };
+ "writestart progress write abort".split(" ").forEach(function(event) {
+ writer["on" + event] = filesaver["on" + event];
+ });
+ writer.write(blob);
+ filesaver.abort = function() {
+ writer.abort();
+ filesaver.readyState = filesaver.DONE;
+ };
+ filesaver.readyState = filesaver.WRITING;
+ }), fs_error);
+ }), fs_error);
+ };
+ dir.getFile(name, {create: false}, abortable(function(file) {
+ // delete file if it already exists
+ file.remove();
+ save();
+ }), abortable(function(ex) {
+ if (ex.code === ex.NOT_FOUND_ERR) {
+ save();
+ } else {
+ fs_error();
+ }
+ }));
+ }), fs_error);
+ }), fs_error);
+ }
+ , FS_proto = FileSaver.prototype
+ , saveAs = function(blob, name) {
+ return new FileSaver(blob, name);
+ }
+ ;
+ FS_proto.abort = function() {
+ var filesaver = this;
+ filesaver.readyState = filesaver.DONE;
+ dispatch(filesaver, "abort");
+ };
+ FS_proto.readyState = FS_proto.INIT = 0;
+ FS_proto.WRITING = 1;
+ FS_proto.DONE = 2;
+
+ FS_proto.error =
+ FS_proto.onwritestart =
+ FS_proto.onprogress =
+ FS_proto.onwrite =
+ FS_proto.onabort =
+ FS_proto.onerror =
+ FS_proto.onwriteend =
+ null;
+
+ view.addEventListener("unload", process_deletion_queue, false);
+ return saveAs;
+}(this.self || this.window || this.content));
+// `self` is undefined in Firefox for Android content script context
+// while `this` is nsIContentFrameMessageManager
+// with an attribute `content` that corresponds to the window
+
+if (typeof module !== 'undefined') module.exports = saveAs;
+;window.dataUriToBlob = (function(){
+/**
+ * Blob constructor.
+ */
+
+var Blob = window.Blob;
+
+/**
+ * ArrayBufferView support.
+ */
+
+var hasArrayBufferView = new Blob([new Uint8Array(100)]).size == 100;
+
+/**
+ * Return a `Blob` for the given data `uri`.
+ *
+ * @param {String} uri
+ * @return {Blob}
+ * @api public
+ */
+
+var dataUriToBlob = function(uri){
+ var data = uri.split(',')[1];
+ var bytes = atob(data);
+ var buf = new ArrayBuffer(bytes.length);
+ var arr = new Uint8Array(buf);
+ for (var i = 0; i < bytes.length; i++) {
+ arr[i] = bytes.charCodeAt(i);
+ }
+
+ if (!hasArrayBufferView) arr = buf;
+ var blob = new Blob([arr], { type: mime(uri) });
+ blob.slice = blob.slice || blob.webkitSlice;
+ return blob;
+};
+
+/**
+ * Return data uri mime type.
+ */
+
+function mime(uri) {
+ return uri.split(';')[0].slice(5);
+}
+
+return dataUriToBlob;
+
+})()
+;// Total frames to record
+var FRAMES_PER_GIF = 36;
+
+// Frames per second to read from the video
+var FPS = 12;
+
+// Per-frame delay in milliseconds
+var DELAY = Math.floor( 1000 / FPS );
+
+// Number of WebWorkers to create
+var WORKERS = 4;
+
+// Number of frames to use to build the gif palette (takes longest)
+var FRAMES_TO_QUANTIZE = 4;
+
+// Upload these gifs when finished??
+var DO_UPLOAD = true;
+
+function GifEncoder(){
+ var base = this;
+ this.working = false;
+ var canvases = [];
+ var contexts = [];
+ var frames = [];
+ var delays = [];
+ var width = 0;
+ var height = 0;
+ var frames_done = 0;
+
+ var initted = Date.now();
+ var started = Date.now();
+ var tube = base.tube = new Tube ()
+
+ var workers = new Factory ();
+
+ var width, height;
+ var neuquant, colortab;
+
+ workers.hire("message", receiveMessage);
+ workers.hire("quantize", receiveQuantize);
+ workers.hire("encode", receiveEncode);
+
+ var reset = this.reset = function(){
+ resetFrames()
+ neuquant = null;
+ colortab = null;
+ base.quantized = false
+ }
+ var resetFrames = this.resetFrames = function(){
+ canvases = [];
+ contexts = [];
+ frames = [];
+ delays = [];
+ width = 0;
+ height = 0;
+ frames_done = 0;
+ }
+
+ this.on = function(){
+ base.tube.on.apply(base.tube, arguments)
+ };
+
+ this.off = function(){
+ base.tube.off.apply(base.tube, arguments)
+ };
+
+ var addFrame = this.addFrame = function(canvas, delay) {
+ var ctx = canvas.getContext('2d');
+ canvases.push(canvas);
+ contexts.push(ctx);
+ delays.push(delay);
+
+ if (canvases.length == 1) {
+ width = canvas.width;
+ height = canvas.height;
+ }
+ }
+
+ var addFrames = this.addFrames = function(canvas_array, delay){
+ for (var i = 0; i < canvas_array.length; i++) {
+ var canvas = canvas_array[i]
+ var ctx = canvas.getContext('2d');
+ canvases.push(canvas);
+ contexts.push(ctx);
+ delays.push(delay);
+ }
+
+ if (canvases.length == canvas_array.length) {
+ width = canvas_array[0].width;
+ height = canvas_array[0].height;
+ }
+ }
+
+ var copyFrame = this.copyFrame = function(canvas, delay) {
+ var newCanvas = document.createElement("canvas");
+ var ctx = newCanvas.getContext('2d');
+
+ ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height);
+
+ canvases.push(newCanvas);
+ contexts.push(ctx);
+ delays.push(delay);
+
+ if (canvases.length == 1) {
+ width = canvas.width;
+ height = canvas.height;
+ }
+ }
+
+ function Factory () {
+ var base = this;
+ var w = 0; // which worker to work next
+ var ww = [];
+ base.init = function(){
+ for (var i = 0; i < WORKERS; i++) {
+ var worker = new Worker('js/vendor/gif-encode/worker.js');
+ worker.onmessage = base.receiveWork;
+ ww.push(worker);
+ }
+ }
+ var tasks = {};
+ base.hire = function(task, cb){
+ tasks[task] = cb;
+ }
+ base.work = function(job){
+ ww[++w % ww.length].postMessage(job);
+ }
+ base.receiveWork = function(e){
+ e.data.task in tasks && tasks[e.data.task](e);
+ }
+ base.init();
+ }
+
+ function receiveMessage(e){
+ console.log("[WORKER]", e.data.message);
+ }
+
+ var neuquant, colortab;
+ var quantize = this.quantize = function () {
+ initted = Date.now();
+ started = Date.now();
+ var spritedata = spriteSheet(FRAMES_TO_QUANTIZE);
+
+ workers.work({
+ task: 'quantize',
+ imageData: spritedata
+ });
+ }
+
+ function receiveQuantize(e) {
+ console.log(Date.now() - started, "quantization done");
+ neuquant = e.data.neuquant;
+ colortab = e.data.colortab;
+ base.quantized = true
+ base.tube("quantized")
+ }
+
+ var encode = this.encode = function (nq, ct) {
+ if (! canvases.length) {
+ throw Error ("No frames to encode")
+ }
+ nq = nq || neuquant
+ ct = ct || colortab
+
+ started = Date.now();
+ frames_done = 0;
+
+ console.log('working .... ');
+ var i = 0;
+
+ function sendWork () {
+ if (i == canvases.length) return doneSending();
+
+ var ctx = contexts[i];
+ var imdata = ctx.getImageData(0, 0, width, height).data;
+ var delay = delays[i];
+
+ workers.work({
+ task: 'encode',
+ frame_index: i,
+ frame_length: contexts.length-1,
+ height: height,
+ width: width,
+ delay: delay,
+ imageData: imdata,
+ neuquant: neuquant,
+ colortab: colortab
+ });
+
+ i++;
+ setTimeout(sendWork, 16);
+ }
+ function doneSending(){
+ base.tube("done_sending")
+ }
+ sendWork();
+ }
+
+ function receiveEncode(e){
+ var frame_index = e.data["frame_index"];
+ var frame_data = e.data["frame_data"];
+
+ frames[frame_index] = frame_data;
+
+ base.tube("encoded-frame", frames.length, canvases.length)
+ for (var j = 0; j < canvases.length; j++) {
+ if (frames[j] == null) {
+ return;
+ }
+ }
+ console.log("FINISHED " + canvases.length);
+ var binary_gif = frames.join('');
+ var base64_gif = window.btoa(binary_gif);
+ var data_url = 'data:image/gif;base64,'+base64_gif;
+
+ base.working = false;
+
+ // photo.setAttribute('src', data_url);
+ // ui.doneEncodingPicture();
+ base.tube("rendered", binary_gif)
+ base.tube("rendered-url", data_url)
+// if (DO_UPLOAD) upload( base64_gif );
+
+ console.log((Date.now() - started), "processed frames");
+ console.log((Date.now() - initted), "done");
+ }
+
+// function upload (base64_gif) {
+// $("#working").html("UPLOADING")
+//
+// console.log("starting upload")
+// var params = {
+// url: base64_gif
+// }
+// $.ajax({
+// 'url': "/photos.json",
+// 'type': 'post',
+// 'data': csrf(params),
+// 'success': function(data){
+//
+// // $("#share").data("href", "/photos/" + data.hash)
+// // $("#share, #make-another").fadeIn(400);
+// console.log(data);
+// console.log((Date.now() - started), "uploaded");
+// // $("#photo").attr("src", data.url);
+// // window.location.href = "/photos/" + data.hash
+// localStorage.setItem('hash', data.hash)
+// window.location.href = "/"
+// // data.hash
+// }
+// });
+// console.log("ok");
+// }
+
+ function spriteSheet (frameCount) {
+ var start = Date.now();
+ frameCount = Math.min(contexts.length, frameCount);
+ var sprites = document.createElement("canvas");
+ var spriteContext = sprites.getContext('2d');
+ sprites.width = width;
+ sprites.height = height * frameCount;
+ var spritedata = spriteContext.getImageData(0, 0, sprites.width, sprites.height)
+ var spritedatadata = spritedata.data
+ var j = 0;
+ var ctxz = sample(contexts, 4);
+ while (frameCount--) {
+ var ctx = ctxz[frameCount];
+ var imdata = ctx.getImageData(0, 0, width, height).data;
+ for (var n = 0; n < imdata.length; j++, n++) {
+ spritedatadata[j] = imdata[n];
+ }
+ }
+ // spriteContext.putImageData(spritedata, 0, 0, 0, 0, sprites.width, sprites.height);
+ // upload( sprites.toDataURL("image/png").split(",")[1]
+ console.log(Date.now() - start, "built spritesheet");
+ return spritedata;
+ }
+
+}
+;(function(){
+ var canvases = document.getElementsByTagName("canvas")
+ if (canvases.length == 0) { alert("no canvas found"); return; }
+
+ var source = canvases[0]
+
+ var encoder = new GifEncoder()
+ encoder.on("encoded-frame", encoded_frame)
+ encoder.on("rendered", rendered_bytes)
+ encoder.on("rendered-url", rendered_url)
+
+ var w, h, x, y;
+ var last_t, frame_t, count, delay, done;
+ var frames = []
+ var curtain, outline
+ var dragging = false
+ var lastGif
+
+ init()
+ function init(){
+ document.body.style.width = "100%"
+ document.body.style.height = "100%"
+ document.body.parentNode.style.width = "100%"
+ document.body.parentNode.style.height = "100%"
+ var template = document.getElementById("template").innerHTML
+ var el = document.createElement("div")
+ el.innerHTML = template
+ document.body.appendChild(el)
+ bind()
+ }
+ function bind(){
+ curtain = document.getElementById("curtain")
+ outline = document.getElementById("outline")
+ controls = document.getElementById("outline")
+ document.getElementById("record").addEventListener("click", record, false)
+ document.getElementById("save").addEventListener("click", save, false)
+ curtain.addEventListener("mousedown", box_start, false)
+ curtain.addEventListener("mousemove", box_size, false)
+ curtain.addEventListener("mouseup", box_end, false)
+ outline.style.display = "none"
+ controls.style.display = "none"
+ }
+ function box_start(e){
+ x = e.pageX
+ y = e.pageY
+ w = 1
+ h = 1
+ dragging = true
+ outline.style.left = px(x-1)
+ outline.style.top = px(y-1)
+ outline.style.width = px(w)
+ outline.style.height = px(h)
+ outline.style.display = "block"
+ }
+ function box_size(e){
+ if (! dragging) return
+ w = e.pageX - x
+ h = e.pageY - y
+ outline.style.width = px(w)
+ outline.style.height = px(h)
+ }
+ function box_end(e){
+ dragging = false
+ controls.style.display = "block"
+ enable("record")
+ document.getElementById("record").focus()
+ }
+ function enable(id){ document.getElementById(id).removeAttribute("disabled") }
+ function disable(id){ document.getElementById(id).setAttribute("disabled","disabled") }
+ function px(n){ return n + "px" }
+ function record(){
+ count = parseInt(document.getElementById("framecount").value)
+ delay = parseFloat(document.getElementById("framedelay").value) * 1000
+ done = 0
+ frame_t = 0
+ build()
+ capture()
+ last_t = +new Date()
+ requestAnimationFrame(recordloop)
+ status("recording")
+ disable("record")
+ }
+ function recordloop(){
+ var canvas = document.createElement("canvas")
+ var t = +new Date()
+ frame_t += t - last_t
+ last_t = t
+ if (frame_t > delay) {
+ frame_t -= delay
+ capture()
+ }
+ if (done == count) {
+ render()
+ }
+ else {
+ requestAnimationFrame(recordloop)
+ }
+ }
+ function build(){
+ frames = new Array(count)
+ for (var i = 0; i < count; i++){
+ frames[i] = document.createElement("canvas")
+ frames[i].height = h
+ frames[i].width = w
+ }
+ }
+ function capture(){
+ var frame = frames[done++]
+ var ctx = frame.getContext('2d')
+ ctx.fillStyle = document.body.backgroundColor
+ ctx.fillRect(0,0,w,h)
+ ctx.drawImage(source, x, y, w, h, 0, 0, w, h)
+ }
+ function render(){
+ encoder.reset()
+ encoder.addFrames(frames, delay)
+ status("encoding")
+ try {
+ encoder.encode()
+ }
+ catch (e) {
+ rendering = false
+ status(e)
+ throw e
+ }
+ }
+ function status(s){
+ document.getElementById("status").innerHTML = s
+ }
+ function encoded_frame(done,count){
+ status("encoded " + done + " / " + count)
+ }
+ function rendered_bytes(bytes){
+ status(filesize(bytes.length))
+ }
+ function rendered_url(url){
+ var image = new Image ()
+ lastGif = image.src = url
+ document.getElementById("preview").innerHTML = ""
+ document.getElementById("preview").appendChild(image)
+ rendering = false
+ enable("record")
+ enable("save")
+ }
+ function save (){
+ if (! lastGif) return;
+ var filename = (window.location.host + window.location.pathname).replace(/[^a-zA-Z0-9]/g,"-").replace(/-+/,"-")
+ var blob = dataUriToBlob(lastGif)
+ saveAs(blob, filename + "-" + (+new Date()) + ".gif");
+ }
+ function filesize(n) {
+ if (n < 1e3) return n + " bytes"
+ if (n < 1e6) return decimalString(n/1e3) + " kb"
+ if (n < 1e9) return decimalString(n/1e6) + " mb"
+ return "WAY TOO BIG DUDE"
+ }
+ function decimalString(n){
+ var m = Math.floor(n);
+ return m + "." + Math.round((n-m)*10)
+ }
+})()
+