working with images in meteor

2,401 views
Skip to first unread message

Zak Stone

unread,
Sep 30, 2012, 2:08:34 PM9/30/12
to meteo...@googlegroups.com
Hello everyone,

I'm excited that discussions are underway about managing binary data
with Meteor, and I'd like to share a use-case and some of the issues
we encountered in our initial attempts to use Meteor to work with
image data.

Our goal was to use the recent support for getUserMedia in Chrome to
capture images from the webcam (with approval, of course) and upload
them to our server to run some computer vision algorithms. The most
straightforward approach seemed to be to write the base64-encoded
dataURL string we received from a canvas to minimongo and let Meteor
handle the synchronization with the server.

We encountered a variety of strange behavior while trying to make this
work, but one of the biggest roadblocks was the "duplicate key error"
below that seemed to be outside of our control since we weren't
generating the _id values ourselves:

----------------------------------------------
Exception while invoking method '/photos/insert' MongoError: E11000
duplicate key error index: meteor.photos.$_id_ dup key: { :
"f2bf9d54-509c-4286-af13-1eeb6d88100f" }
at Db.wrap (/usr/local/meteor/lib/node_modules/mongodb/lib/mongodb/db.js:1671:11)
at [object Object].<anonymous>
(/usr/local/meteor/lib/node_modules/mongodb/lib/mongodb/collection.js:301:26)
at [object Object].g (events.js:156:14)
at [object Object].emit (events.js:88:20)
at Db._callHandler
(/usr/local/meteor/lib/node_modules/mongodb/lib/mongodb/db.js:1292:25)
at /usr/local/meteor/lib/node_modules/mongodb/lib/mongodb/connection/server.js:352:30
at [object Object].parseBody
(/usr/local/meteor/lib/node_modules/mongodb/lib/mongodb/responses/mongo_reply.js:118:5)
at [object Object].<anonymous>
(/usr/local/meteor/lib/node_modules/mongodb/lib/mongodb/connection/server.js:343:22)
at [object Object].emit (events.js:67:17)
at [object Object].<anonymous>
(/usr/local/meteor/lib/node_modules/mongodb/lib/mongodb/connection/connection_pool.js:147:13)
----------------------------------------------

Another issue arose when we wanted to display a feed of
recently-captured images in the browser. Meteor's reactivity would be
perfect for updating the feed automatically, but we found that our
image feed would disappear and only briefly flash visible again every
two seconds or so once we had more than a few dozen images saved. This
problem persisted across page reloads, and we had to reset the
database to avoid it.

We've since adapted our code to post images directly to S3 and manage
references to those images with Meteor, but it would be fantastic if
Meteor could handle use-cases like this natively in the future.

Thanks,
Zak

Charlie Magee

unread,
Oct 25, 2012, 12:21:08 AM10/25/12
to meteo...@googlegroups.com
Hey Zack,

Would you mind posting your solution for S3? I'm working on an app for cognitively challenged people, many of whom can't read and have to navigate via images and icons.

Thanks,

Charlie Magee


On Sunday, September 30, 2012 11:08:36 AM UTC-7, Zak wrote:
Hello everyone,

I'm excited that discussions are underway about managing binary data
with Meteor, and I'd like to share a use-case and some of the issues
we encountered in our initial attempts to use Meteor to work with
image data.

------------

Sean Kean

unread,
Oct 25, 2012, 1:43:30 PM10/25/12
to meteo...@googlegroups.com
I'm very much in need of learning how to handle image uploading and management in meteor for the application i'm building - if anyone could share some code or write up a tutorial i think the whole community would really benefit. thanks! 

sean


On Sunday, September 30, 2012 11:08:36 AM UTC-7, Zak wrote:

Darío Javier Cravero

unread,
Oct 26, 2012, 9:36:44 AM10/26/12
to meteo...@googlegroups.com
Hey Sean, here's an implementation I did a couple of days ago using the File API. Meteor is bringing in http endpoints on the server which would pretty much solve your problem, since you can then apply regular image uploading or use something like xhr2, etc. Cheers, Darío

Scott Mankowitz

unread,
Oct 28, 2012, 12:38:01 AM10/28/12
to meteo...@googlegroups.com
wow. that looks great. any idea how to put the file right into mongo?

Darío Javier Cravero

unread,
Oct 29, 2012, 8:24:52 AM10/29/12
to meteo...@googlegroups.com
Well, I reckon you could always dump the content of file.srcElement.result into a mongo object. There're a few things you need to take into consideration beforehand. Good luck with that!..
Darío


--
You received this message because you are subscribed to the Google Groups "meteor-talk" group.
To view this discussion on the web visit https://groups.google.com/d/msg/meteor-talk/-/TmaMqUa-FfAJ.

To post to this group, send email to meteo...@googlegroups.com.
To unsubscribe from this group, send email to meteor-talk...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/meteor-talk?hl=en.

Zak Stone

unread,
Oct 29, 2012, 5:42:19 PM10/29/12
to meteo...@googlegroups.com
> Would you mind posting your solution for S3?

We took advantage of S3's recently-announced support for Cross-Origin
Resource Sharing (CORS):

http://aws.typepad.com/aws/2012/08/amazon-s3-cross-origin-resource-sharing.html

Once you add an appropriate CORS configuration to one of your S3
buckets, it becomes possible to post images directly from JavaScript
to S3 without an intermediate proxy server.

I started with the following walk-throughs:

http://bencoe.tumblr.com/post/30685403088/browser-side-amazon-s3-uploads-using-cors
http://stackoverflow.com/questions/11240127/uploading-image-to-amazon-s3-with-html-javascript-jquery-with-ajax-request-n

Neither of these quite covered my use-case of uploading webcam images
from Chrome directly to S3, though, so I'll provide some additional
details in hopes that someone will find them helpful.

For reference, the policy I sent with each POST request ended up
looking something like this:

{"expiration": "2012-12-31T00:00:00Z",
"conditions": [
{"bucket": "<CORS_ENABLED_S3_BUCKET>"},
{"acl": "public-read"},
{"Content-Type": "image/png"},
["starts-with", "$key", "<DESIRED_KEY_PREFIX>/"],
["content-length-range", 0, <MAX_LENGTH>]
]
}

I found it necessary to use a lower-case bucket name to get this policy to work.

I ran something like the Python script included in the article below
locally to encode the policy above and generate a signature for it
each time I edited it:

http://aws.amazon.com/articles/1434

I then copied the encoded policy and signature (which are safe to
distribute) into my JavaScript code.

Since the images I wanted to POST were being captured by a webcam,
saved to a canvas, and exported in a base64-encoding via toDataURL, I
needed a few additional tricks to convert them back to actual image
files on their way to S3.

A sidenote: a friend helped me discover the necessity of setting the
width and height of the canvas explicitly immediately before exporting
an image with toDataURL. This issue only appeared when we upgraded
from Chrome 21 to Chrome 22, and I'm still not sure exactly when and
why Chrome began resetting our canvas size to zero, but the explicit
reassignment before attempting to capture a video frame was a
sufficient workaround.

I included the following patch in my JavaScript to add a sendAsBinary
method to the XMLHttpRequest implementation in the versions of Chrome
I was using:

http://code.google.com/p/chromium/issues/detail?id=35705#c39

Before each upload, I first stripped the type prefix from the dataURL:

data = dataURL.replace('data:' + 'image/png' + ';base64,', '');

I wasn't able to get the FormData approach of the walkthroughs above
to convert my base64-encoded image back to binary, so I ended up
building my POST request myself roughly as follows:

--------------------------------------------------------------------
var aws_base_url = 'https://<CORS_BUCKET>.s3.amazonaws.com/';

var xhr = new XMLHttpRequest();
xhr.upload.addEventListener("progress", uploadProgress, false);
xhr.addEventListener("load", function (e) {uploadComplete(e [,
<OTHER_PARAMS…>]);}, false);
xhr.open('POST', aws_base_url, true);
var boundary = 'WebKitFormBoundary<EXTRA_RANDOM_STRING>';
xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary='
+ boundary);

upload_data = [
'--' + boundary,
'Content-Disposition: form-data; name="key"',
'',
key,
'--' + boundary,
'Content-Disposition: form-data; name="acl"',
'',
'public-read',
'--' + boundary,
'Content-Disposition: form-data; name="Content-Type"',
'',
type,
'--' + boundary,
'Content-Disposition: form-data; name="AWSAccessKeyId"',
'',
'<AWS_ACCESS_KEY_ID>',
'--' + boundary,
'Content-Disposition: form-data; name="policy"',
'',
'<ENCODED_POLICY>',
'--' + boundary,
'Content-Disposition: form-data; name="signature"',
'',
'<SIGNATURE>',
'--' + boundary,
'Content-Disposition: form-data; name="file"',
'',
atob(data),
'--' + boundary + '--'
].join('\r\n');

console.log("Length of upload_data: " + upload_data.length);
xhr.sendAsBinary(upload_data);
--------------------------------------------------------------------

I believe the 'key' parameter has to appear first and the 'file'
parameter has to appear last; AWS may also be sensitive to the order
of the other parameters. I found the raw POST request examples here
very helpful:

http://docs.amazonwebservices.com/AmazonS3/latest/dev/HTTPPOSTExamples.html

Once the webcam images were uploaded, I wanted them to appear
automatically in the Meteor interface in a reverse-chronological image
feed without broken image icons, slowly-loading images, or flicker,
but I haven't yet implemented the ideal solution. My initial approach
was to have the uploadComplete handler set a flag in the database
record corresponding to each freshly-uploaded photo and for the
template to display only the photos that had this flag set. However,
this still occasionally elicits 403 errors from AWS, possibly because
an image has been uploaded but isn't quite available for downloading
yet, and it's often possible to see individual images drawn
progressively as they are re-downloaded on a slow connection.

It would probably be better to preload the images from S3 and only set
the flag when they have been downloaded completely. Better still would
be some mechanism for feeding the local copy of the webcam image to
the template as a placeholder when possible and having it seamlessly
fall back to a preloaded remote copy of the image otherwise.

Thanks for reading this lengthy post -- I hope you find it useful!

Zak


> On Sunday, September 30, 2012 11:08:36 AM UTC-7, Zak wrote:
>>
>> Hello everyone,
>>
>> I'm excited that discussions are underway about managing binary data
>> with Meteor, and I'd like to share a use-case and some of the issues
>> we encountered in our initial attempts to use Meteor to work with
>> image data.
>>
>> ------------
>>
>>
>> We've since adapted our code to post images directly to S3 and manage
>> references to those images with Meteor, but it would be fantastic if
>> Meteor could handle use-cases like this natively in the future.
>>
>> Thanks,
>> Zak
>
> --
> You received this message because you are subscribed to the Google Groups
> "meteor-talk" group.
> To view this discussion on the web visit
> https://groups.google.com/d/msg/meteor-talk/-/Gst-SEAbHt8J.
Reply all
Reply to author
Forward
0 new messages