Skipper in SailsJS (beta) image resize before upload - image

I'm using SailsJS (beta). I'm trying to find a way to use graphicsmagick to take the stream parsed by Skipper in SailsJS-beta to resize the image before calling the Skipper-function req.file('inputName').upload().
My goal is to take my large, original image, and resize it before uploading it. Sails beta have introduced the Skipper-file-parser which are poorly documented (at least I don't understand it). Please help me understand how to resize the image before upload.
This works (code in my controller action):
req.file('fileName').upload('storedImage.png', function(err, files){
// File is now uploaded to storedImage.png
});
What I want is something like:
// Read the file stream into a file upload
var stream = req.file('fileName');
gm(stream).resize(200, 200).write('storedImage.png', function(err){
// File is now resized to 200x200 px and uploaded to storedImage.png
});
My problem is: how do I properly fetch the stream from req.file('fileName') to send it to gm?

This should work for you:
var Writable = require('stream').Writable;
var resize = require('image-resize-stream')(100); // Or any other resizer
// The output stream to pipe to
var output = require('fs').createWriteStream('storedImage.png');
// Let's create a custom receiver
var receiver = new Writable({objectMode: true});
receiver._write = function(file, enc, cb) {
file.pipe(resize).pipe(output);
cb();
};
Now in your action you just have to use your receiver:
req.file('fileName').upload(receiver, function(err, files){
// File is now resized to 100px width and uploaded to ./storedImage.png
});
I have a feeling that Skipper's API is going to change, a lot, but this will work for now (with v0.1.x).
UPDATE
Specifically, if using gm for resizing, it'll be something like this:
var gm = require('gm');
var Writable = require('stream').Writable;
// The output stream to pipe to
var output = require('fs').createWriteStream('storedImage.png');
// Let's create a custom receiver
var receiver = new Writable({objectMode: true});
receiver._write = function(file, enc, cb) {
gm(file).resize('200', '200').stream().pipe(output);
cb();
};

I had problems with #bredikhin solution so I dig deeper into this and found this thread very helpful: Uploading files using Skipper with Sails.js v0.10 - how to retrieve new file name
I just changed one line of his Uploader:
[...]
file.pipe(outputs);
[...]
into:
gm(file).resize(200, 200).stream().pipe(outputs);
and this does the trick.
I wrote this answer because it may be helpful for someone.

Related

How do you create and then insert an image file from Google Drive into a Google Apps Script UrlFetchApp.fetch call to an External API?

I am using Google Apps Script to call an external API to post both text and an image to a contact in an external system. I have posted the text fine, many times, no problems. I have not worked with sending or even using images in Apps Script before, so I am unsure of how to send the image as a file. I've done quite a bit of research on Stack Overflow and elsewhere, but have not found the answer to this yet.
The API documentation for the external system says that it needs the following:
contactId - Type: String
Message:
text - Type: String... Description: Message text (Media or Text is required).
upload - Type: File... Description: Message image (Media or Text is required). Media must be smaller than 1.5mb. Use a jpg, jpeg, png, or gif.
The "upload", type "File" (a jpg picture/image) is what I cannot figure out how to grab, format, and send. I currently have the image in Google Drive, have shared it for anyone to access via its URL, and it is well under 1.5MB.
Here is most of my test code (marked as JS, but really Google Apps Script), with the identifying info changed, with several different ways I have tried it. At this point, I am just banging my head against the wall! Any help is greatly appreciated!!! Thank you!
function TestAPI() {
var apiKey2 = '9xxxxx-xxxx2-xxxxx-bxxx-3xxxxxxa'; //API Key for the external system
var url4 = 'https://www.externalsystem.com/api/v1/[contactID]/send';
var pic = DriveApp.getFileById("1yyyyyy_Nyyyyxxxxxx-_xxxxxxx_xyyyy_"); // I Tried this
// var pic = driveService.files().get('1yyyyyy_Nyyyyxxxxxx-_xxxxxxx_xyyyy_'); //And tried this
// var pic = DriveApp.getFileByID('1yyyyyy_Nyyyyxxxxxx-_xxxxxxx_xyyyy_').getAs(Family.JPG); //And this
// var pic = { "image" : { "source": {"imageUri": "https://drive.google.com/file/d/1yyyyyy_Nyyyyxxxxxx-_xxxxxxx_xyyyy_" } } }; //And this
// var pic = { file : DriveApp.getFileById("1yyyyyy_Nyyyyxxxxxx-_xxxxxxx_xyyyy_") }; //And this
var formData = {
'contactID': '[contactID]',
'text': "Text here to send to external system through API", // This works fine every time!
'upload': pic // Tried this
// 'upload': DriveApp.getFileByID("1yyyyyy_Nyyyyxxxxxx-_xxxxxxx_xyyyy_").getBlob() // And tried this
// 'upload': { "image" : { "source": {"imageUri": "https://drive.google.com/file/d/1yyyyyy_Nyyyyxxxxxx-_xxxxxxx_xyyyy_" } } } // And this
};
var options4 = {
"headers":{"x-api-key":apiKey2},
'method' : 'post',
'payload': formData
};
var response4 = UrlFetchApp.fetch(url4, options4);
}
Once again, everything is working fine (text, etc.) except for the image (the "upload") not coming through. I am pretty sure it is because I don't know how to "package" the image from Google Drive through the UrlFetchApp call to the API.
The official document says that Message text (Media or Text is required) and Message image (Media or Text is required). From this, please try to test as following modification.
Modified request body:
var formData = {
upload: DriveApp.getFileById("###").getBlob()
};
I thought that from the official document, when both 'upload' and 'text' are used, only 'text' might be used.
And also, from your tested result, it was found that the request body is required to be sent as the form data.
Reference:
Contacts - Send Message To Contact

Insert image in editor after upload

I've managed to upload images through drag & drop to a SP 2013 library by intercepting the paste and fileUploadrequest events (+ added mandatory headers and used /_api/web/getfolderbyserverrelativeurl(\'/sites/theSite/theLibrary\')/files/add(overwrite=true,%20url=\'aDynamicFilename.jpg\') as the request's URL).
The problem with this approach is that even if the image is uploaded, the image is not inserted in the editor (no error). I'm not setting config.uploadUrl for this approach.
Q#1: Is there any step which I should go through after the image is uploaded? Like telling the CKEDITOR instance to insert the image?
Later on, I've noticed that if I'm setting the config.uploadUrl to the same URL as I'm using above, the editor inserts successfully the image. The problem is that, from my trials, the config.uploadUrl is initialized together with the CKEDITOR instance (therefore, can't be assigned dynamically for each image, in case that multiple images are dragged and dropped on the editor).
Q#2: Is there another way to configure the uploadUrl or maybe some other config property that would allow the custom upload to work and insert the image in the editor?
Eventually made it work by following a similar approach as the on in this repo:
RyanSiu1995/ckeditor-ImageUploader
Use a FileReader and start loading the image when it's pasted to the
CKEditor
On the fileReader's onload event, create a img element in the
editor's document object with some opacity and with the fileReader's
Base64 string as the src
On the fileLoader's uploaded event, remove
the img and re-add it with the actual file's url (changing the src
attribute on the img was not triggering the editor's change event, which I was hooking into,so I chose to replace the whole element)
Here's the relevant section from the ckeditor-ImageUploader plugin:
fileDialog.on('change', function (e) {
var fileTools = CKEDITOR.fileTools,
uploadUrl = fileTools.getUploadUrl( editor.config, 'image' ),
file = e.target.files[0],
loader = editor.uploadRepository.create(file),
reader = new FileReader(),
notification,
img;
// verify
if (!/image/i.test(file.type)) {
notification = editor.showNotification( 'Please check the correct format.', 'warning' );
setTimeout(function() {
notification.hide()
}, 2000);
return false
}
loader.upload(uploadUrl);
// preview image
reader.readAsDataURL(e.target.files[0]);
reader.onload = function (e) {
img = editor.document.createElement('img');
img.setAttribute('src', e.target.result);
img.setStyle('opacity', 0.3);
editor.insertElement(img);
}
loader.on('uploaded', function(evt) {
editor.widgets.initOn(img, 'image', {
src: evt.sender.url
});
img.setAttribute('src', evt.sender.url);
img.setStyle('opacity', 1);
});
loader.on('error', function() {
img.$ && img.$.remove();
});
fileTools.bindNotifications(editor, loader);
// empty input
fileDialog[0].value = "";

StaticImage in a layer. Is the change of source synchronous or asynchronous?

I'm developing a an application which displays images ( png ) in a layer of an OpenLayer's map. So far so good, it works like a charm. Nonetheless it may happen
that the requested image has to be created on the fly by the server and then a delay of few or more seconds can be observed before the image appears on the client's side. To make the user understand that displaying the image takes some time I use an animation that appears when the request is sent to the server and should disappear when the image is served and displayed (see code below). What I observe though is that the animation never appears exactly as is the service of the image was processed in an asynchronous way. I really would like to know if my interpretation is correct and in such a case which can of event I should trap and only then make the animation disappear.
document.getElementById('loading').style.display='block';
_im_layer_1 = new ol.layer.Image({
source: new ol.source.ImageStatic({
url: 'http://'+server+':'+port+'/png/?path="'+_path+'"&si='+_sliceIndex,
projection: _projection_1,
imageExtent: _extent
})
});
console.log("adding the image layer");
_map_1.addLayer(_im_layer_1);
document.getElementById('loading').style.display='none';
I was expecting to listen to the source with
imageStaticSource.on('change', evt => {
console.log('no event, contrary to what I would expect');
});
but it fails contrary to what I expected from the API docs.
So, looking at the code, I've seen there is an imageLoadFunction option for the ol.source.ImageStatic constructor. When not set, this function fallback to a default one. I've created a new one, derived from the default and made some changes to demonstrate how you can get the start of the loading event and then the end of it.
You will find a demo derived from the official sample that should illustrate the process. Try it on you side as the demo does not wait some seconds to return the image, contrary to your use case.
var imageLoadFunction = function(image, src) {
// Where you start showing the loader using a variable
console.time('loader');
image.getImage().addEventListener('load', function() {
// Where you should mention to stop the loader
console.timeEnd('loader');
});
image.getImage().src = src;
};
var imageStaticSource = new ol.source.ImageStatic({
attributions: '© xkcd',
url: 'https://imgs.xkcd.com/comics/online_communities.png',
projection: projection,
imageLoadFunction: imageLoadFunction,
imageExtent: extent
});

How do I block uploads that lack "DateTimeOriginal" exif data with Fine Uploader?

I have an app where having the DateTimeOriginal time stamp on photos are absolutely necessary. Is there a way for me to stop uploading and display a message using Fine Uploader?
I've never heard of the "taken-at" tag, and I don't believe this is a standard field. The rest of this answer assumes you really do want to focus on this tag, but even if you don't you can make a simple change in the source code below to focus on another EXIF tag instead.
One approach is to check each file in an onSubmit callback handler and simply reject the file is it does not contain a "taken-at" field. The following example utilizes the exif-js library to parse an image file's EXIF data:
var uploader = new qq.FineUploader({
callbacks: {
onSubmit: function(id) {
var blob = this.getFile(id)
return new Promise(function(resolve, reject) {
EXIF.getData(blob, function() {
var takenAt = EXIF.getTag(this, 'taken-at')
if (takenAt) {
resolve()
}
else {
reject()
}
})
})
}
}
})

fineuploader - Read file dimensions / Validate by resolution

I would like to validate by file dimensions (resolution).
on the documentation page there is only information regarding file name and size, nothing at all in the docs about dimensions, and I also had no luck on Google.
The purpose of this is that I don't want users to upload low-res photos to my server. Thanks.
As Ray Nicholus had suggested, using the getFile method to get the File object and then use that with the internal instance object qq.ImageValidation to run fineuploader's validation on the file. A promise must be return because this proccess is async.
function onSubmit(e, id, filename){
var promise = validateByDimensions(id, [1024, 600]);
return promise;
}
function validateByDimensions(id, dimensionsArr){
var deferred = new $.Deferred(),
file = uploaderElm.fineUploader('getFile', id),
imageValidator = new qq.ImageValidation(file, function(){}),
result = imageValidator.validate({
minWidth : dimensionsArr[0],
minHeight : dimensionsArr[1]
});
result.done(function(status){
if( status )
deferred.reject();
else
deferred.resolve();
});
return deferred.promise();
}
Remained question:
Now I wonder how to show the thumbnail of the image that was rejected, while not uploading it to the server, the UI could mark in a different color as an "invalid image", yet the user could see which images we valid and which weren't...
- Update - (regarding the question above)
While I do not see how I could have the default behavior of a thumbnail added to the uploader, but not being uploaded, but there is a way to generate thumbnail manually, like so:
var img = new Image();
uploaderElm.fineUploader("drawThumbnail", id, img, 200, false);
but then I'll to create an item to be inserted to qq-upload-list myself, and handle it all myself..but still it's not so hard.
Update (get even more control over dimensions validation)
You will have to edit (currently) the qq.ImageValidation function to expose outside the private function getWidthHeight. just change that function deceleration to:
this.getWidthHeight = function(){
Also, it would be even better to change the this.validate function to:
this.validate = function(limits) {
var validationEffort = new qq.Promise();
log("Attempting to validate image.");
if (hasNonZeroLimits(limits)) {
this.getWidthHeight().done(function(dimensions){
var failingLimit = getFailingLimit(limits, dimensions);
if (failingLimit) {
validationEffort.failure({ fail:failingLimit, dimensions:dimensions });
}
else {
validationEffort.success({ dimensions:dimensions });
}
}, validationEffort.success);
}
else {
validationEffort.success();
}
return validationEffort;
};
So you would get the fail reason, as well as the dimensions. always nice to have more control.
Now, we could write the custom validation like this:
function validateFileDimensions(dimensionsLimits){
var deferred = new $.Deferred(),
file = this.holderElm.fineUploader('getFile', id),
imageValidator = new qq.ImageValidation(file, function(){});
imageValidator.getWidthHeight().done(function(dimensions){
var minWidth = dimensions.width > dimensionsLimits.width,
minHeight = dimensions.height > dimensionsLimits.height;
// if min-width or min-height satisfied the limits, then approve the image
if( minWidth || minHeight )
deferred.resolve();
else
deferred.reject();
});
return deferred.promise();
}
This approach gives much more flexibility. For example, you would want to have different validation for portrait images than landscape ones, you could easily identify the image orientation and run your own custom code to do whatever.

Resources