Add item to media library from blob or dataUrl

I’m trying to build a webcam snapshot recorder. It should take a picture and upload it to the WP media library via ajax / async-upload.

Already managed to add a view to the media library. Recording also works.
I can either store the raw image data in blob or in base 64 encoded string (like data url)

How can I put this data into the upload queue?

Any suggestion / further reading / well explained reasons why this won’t work appreciated.

Thanks a lot, joern

EDIT:
This is my code so far. It

  • adds a panel to WP media library.

  • displays an image with
    src="data:image/

  • Tries to generate a file-like object and put it in the upload queue. (That’s where I’m failing)

JS-Dependency: Needs github repo blueimp/JavaScript-Canvas-to-Blob

<script type="text/javascript">
(function($){
var media = wp.media,
    l10n = media.view.l10n = typeof _wpMediaViewsL10n === 'undefined' ? {} : _wpMediaViewsL10n;

// override router creation
media.view.MediaFrame.Post.prototype.browseRouter = function( view ) {
    view.set({
        upload: {
            text:     l10n.uploadFilesTitle,
            priority: 20
        },
        dataurltest: {
            text:     'DataUrl Test', 
            priority: 30
        },
        browse: {
            text:     l10n.mediaLibraryTitle,
            priority: 40
        }
    });
};

var bindHandlers = media.view.MediaFrame.Post.prototype.bindHandlers,
    dataUrlTest, frame;

media.view.MediaFrame.Post.prototype.bindHandlers = function() {
    // bind parent object handlers
    bindHandlers.apply( this, arguments );
    // bind our create handler.
    this.on( 'content:create:dataurltest', this.dataurltestContent, this );
    frame = this;
};
media.view.MediaFrame.Post.prototype.dataurltestContent = function( content ){
    // generate test content
    var state = this.state();
    this.$el.removeClass('hide-toolbar');
    dataUrlTest = new media.view.dataUrlTest({});
    content.view = dataUrlTest;
}


media.view.dataUrlTest = media.View.extend({
    // our test view
    tagName:   'div',
    className: 'data-url-test',

    initialize: function() {
        _.defaults( this.options, {

        });
        var self = this, 
            blob;


        // add image with data-url to panel content
        var b64Data = 'R0lGODdhUAA8AIABAAAAAP///ywAAAAAUAA8AAACS4SPqcvtD6' +
                'OctNqLs968+w+G4kiW5omm6sq27gvH8kzX9o3n+s73/g8MCofE' +
                'ovGITCqXzKbzCY1Kp9Sq9YrNarfcrvcLDovH5PKsAAA7',
            imageUrl = 'data:image/gif;base64,' + b64Data,
            $img = $('<img src="'+imageUrl+'" />')
                .appendTo(this.$el);
        // add a submit link
        $('<a href="#" class="test-send">Okay</a>')
            .insertAfter($img)
            .on('click',null,function(){
                // creating a blob would go like this:
                //blob = window.dataURLtoBlob && window.dataURLtoBlob($img.get(0).src)

                var file = new plupload.File(null,b64Data);
                file.name = 'test.gif';
                file.id = 'id'+(new Date()).getTime(); 

                var attributes = _.extend({
                    file:      file,
                    uploading: true,
                    date:      new Date(),
                    filename:  'test.gif',
                    menuOrder: 0,
                    uploadedTo: wp.media.model.settings.post.id,
                    type : 'image',
                    subtype : 'gif'
                }, _.pick( file, 'loaded', 'size', 'percent' ) );

                file.attachment = wp.media.model.Attachment.create( attributes );
                wp.Uploader.queue.add(file.attachment);
                frame.uploader.uploader.uploader.refresh();
                frame.uploader.uploader.uploader.start();
            });

    },
});

return;




})(jQuery);
</script>

Solutions Collecting From Web of "Add item to media library from blob or dataUrl"

Finally I found a solution on my own.

There is no direct way to pass pure binary data (like a blob) to the WP upload object. Only File based objects will be processed.
I had to create a separate XmlHttpRequest. (Simply using $.ajax() did not work and resulted in polluted Image data.)

After successfully uploading the image data to async-upload.php I was able to fire a ‘FileUploaded’ event, along with what the server responded. After this the image showd up in the media library.

The code example above could look like this (untested and still sketchy):

<script type="text/javascript">
(function($){
var media = wp.media,
    frame,
    l10n = media.view.l10n = typeof _wpMediaViewsL10n === 'undefined' ? {} : _wpMediaViewsL10n;

// override router creation
media.view.MediaFrame.Post.prototype.browseRouter = function( view ) {
    view.set({
        upload: {
            text:     l10n.uploadFilesTitle,
            priority: 20
        },
        dataurltest: {
            text:     'DataUrl Test', 
            priority: 30
        },
        browse: {
            text:     l10n.mediaLibraryTitle,
            priority: 40
        }
    });
};

var bindHandlers = media.view.MediaFrame.Post.prototype.bindHandlers,
    dataUrlTest, frame;

media.view.MediaFrame.Post.prototype.bindHandlers = function() {
    // bind parent object handlers
    bindHandlers.apply( this, arguments );
    // bind our create handler.
    this.on( 'content:create:dataurltest', this.dataurltestContent, this );
    frame = this;
};
media.view.MediaFrame.Post.prototype.dataurltestContent = function( content ){
    // generate test content
    var state = this.state();
    this.$el.removeClass('hide-toolbar');
    dataUrlTest = new media.view.dataUrlTest({});
    content.view = dataUrlTest;
}


media.view.dataUrlTest = media.View.extend({
    // our test view
    tagName:   'div',
    className: 'data-url-test',

    initialize: function() {
        _.defaults( this.options, {

        });
        var self = this, 
            blob;

        // add image with data-url to panel content
        var b64Data =   'iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAGXRFWHRTb2Z0d2F'+
                        'yZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bX'+
                        'AAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTe'+
                        'k5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8i'+
                        'IHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjA'+
                        'xMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPS'+
                        'JodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gP'+
                        'HJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8v'+
                        'bnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmF'+
                        'kb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG'+
                        '9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yV'+
                        'G9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5z'+
                        'dGFuY2VJRD0ieG1wLmlpZDpGN0NENTMwNzdCNzcxMUUzQTBFQzg3RURFQTJCNTM'+
                        '5QiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpGN0NENTMwODdCNzcxMUUzQT'+
                        'BFQzg3RURFQTJCNTM5QiI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0Y'+
                        'W5jZUlEPSJ4bXAuaWlkOkY3Q0Q1MzA1N0I3NzExRTNBMEVDODdFREVBMkI1MzlC'+
                        'IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkY3Q0Q1MzA2N0I3NzExRTNBMEV'+
                        'DODdFREVBMkI1MzlCIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+ID'+
                        'wveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+wqqcoQAAAAZQTFRFAAAA/'+
                        '///pdmf3QAAABFJREFUeNpiYBgFo4D6ACDAAAJYAAFvc4UPAAAAAElFTkSuQmCC',
            imageUrl = 'data:image/png;base64,' + b64Data,
            $img = $('<img src="'+imageUrl+'" />')
                .appendTo(this.$el);
        // add a submit link
        $('<a href="#" class="test-send">Okay</a>')
            .insertAfter($img)
            .on('click',null,function(){
                // creating a blob would go like this:
                //blob = window.dataURLtoBlob && window.dataURLtoBlob($img.get(0).src)

                // create and add item to upload queue
                var file = {}; 
                var attributes = {
                    file:      file,
                    uploading: true,
                    date:      new Date(),
                    filename:  'test.png',
                    menuOrder: 0,
                    uploadedTo: wp.media.model.settings.post.id,
                    type : 'image',
                    subtype : 'png',
                    loaded : 0,
                    size : 100,
                    percent : 0
                };

                file.attachment = wp.media.model.Attachment.create( attributes );
                wp.Uploader.queue.add(file.attachment);

                var post_data = {
                    action   : wp.Uploader.defaults.multipart_params.action,
                    _wpnonce : wp.Uploader.defaults.multipart_params._wpnonce,
                    post_id  : wp.media.model.settings.post.id
                }

                 function upload_succes( xhr , httpStatus ) {
                    // this will update the uploaded image in the media library
                    frame.uploader.uploader.uploader.trigger('FileUploaded', file, {
                        response : xhr.responseText,
                        status : httpStatus
                    });
                }

                send_b64_data( b64data , attributes.filename , post_data , upload_succes );
            });

    },
});

return;




})(jQuery);
</script>

The function send_b64_data( b64data , filename , post_data , success_callback ) would look like this:

function send_b64_data( b64data , filename , post_data , success_callback ) {

    var boundary, dashdash, crlf, multipart_string,
        file_data_name, name, mime_type , xhr;

    // setup multipart
    boundary            = '----multipart_boundary'+(new Date().getTime().toString(32))+Math.random().toString(32);
    dashdash            = '--';
    crlf                = '\r\n';


    // build request payload
    multipart_string    = '';
    for ( name in post_data ) {
        multipart_string += dashdash + boundary + crlf +
            'Content-Disposition: form-data; name="' + name + '"' + crlf + crlf;
        multipart_string += unescape(encodeURIComponent(send_data[name])) + crlf;
    }

    // add image data
    mime_type       = 'image/png';
    file_data_name  = 'async-upload';
    multipart_string += dashdash + boundary + crlf +
        'Content-Disposition: form-data; name="' + wp.Uploader.defaults.file_data_name + '"; filename="' + filename + '"' + crlf +
        'Content-Type: ' + mime_type + crlf +
            crlf + atob( b64data ) + crlf +
            dashdash + boundary + dashdash + crlf;

    // build and send request
    xhr = new XMLHttpRequest()
    xhr.open("post", wp.Uploader.defaults.url, true);
    xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);
    xhr.onreadystatechange = function() {
        var httpStatus, chunkArgs;
        if (xhr.readyState == 4 ) {
            try {
                httpStatus = xhr.status;
            } catch (ex) {
                httpStatus = 0;
            }
            if (httpStatus == 200) {
                // will load contents to file fake
                success_callback(xhr,httpStatus);
            } else if ( httpStatus >= 400 ) {
                // handle error
            }
        }
    }

    if (xhr.sendAsBinary) { // Gecko
        xhr.sendAsBinary(multipart_string);
    } else { // WebKit with typed arrays support
        var ui8a = new Uint8Array(multipart_string.length);
        for (var i = 0; i < multipart_string.length; i++) {
            ui8a[i] = (multipart_string.charCodeAt(i) & 0xff);
        }
        xhr.send(ui8a.buffer);
    }
}

Working code can be found here: https://github.com/mcguffin/say-cheese
(WP Plugin allowing you to add webcam snapshots and pasted images to the MediaLibrary)