Displaying images from private subdirectory in Meteor - javascript

I have a set of images I am storing in my /private sub-directory, I am trying to retrieve the data inside a server method and sending the data back to the client to be displayed.
How can I do that?
I have an image named test.png inside /private/photos. Here's what I've tried.
/client/test.js
Template.test.onRendered(function () {
Meteor.call('returnPhoto', 'photos/test.png', function (e, data) {
console.log(data);
console.log(window.btoa(data));
$('#imgContainerImg').attr('src', 'data:image/png;base64,' + window.btoa(data));
});
})
/server/methods.js
returnPhoto: function (assetPath) {
return Assets.getText(assetPath);
return Assets.getBinary(assetPath);
}
I tried both Assets.getText and Assets.getBinary, the first gives me some binary gibberish, and the second gives me an array of numbers. Using the btoa function doesn't work regardless.
I have looked at the CollectionFS package, but I do not need to upload the pictures and store them all in a collection. I'd like the images to be available as soon as I put them in that directory, without having to call myFSCollection.insert.

Using the following, I was able to get images from the private directory, send it over to the client as a byte array, which then gets converted into a base64 string and displayed as data URL.
client/test.js
Template.test.onRendered(function () {
Meteor.call('returnPhoto', 'photos/test.png', function (e, data) {
var base64String = btoa(String.fromCharCode.apply(null, new Uint8Array(data)));
$('#imgContainerImg').attr('src', 'data:image/png;base64,' + base64String);
});
})
server/methods.js
returnPhoto: function (assetPath) {
return Assets.getBinary(assetPath);
}

This is the solution I work with:
client/main.js
const imagesLookup = new ReactiveDict();
Template.registerHelper('img', function(src) {
Meteor.call('image', src, (err, img)=> {
imagesLookup.set(src, img);
});
return imagesLookup.get(src);
});
client/main.html
<template="stuffWithImage">
<!-- src is the path of the image in private -->
<img src="{{img src}}"/>
</template>
imports/methods.js
Meteor.methods({
image(src){
//validate input and check if use is allowed to see this asset
if(Meteor.isClient){
//return some loading animation
}
const buffer = new Buffer(Assets.getBinary(src), 'binary');
const magicNumber = buffer.toString('hex',0,4)
const base64String = buffer.toString('base64');
return `data:image/${getImageType(magicNumber)};base64,${base64String}`;
}
});
function getImageType(magicNumber){
if (magicNumber === 'ffd8ffe0') {
return 'jpeg';
}
//check for other formats... here is a table: https://asecuritysite.com/forensics/magic
}

Related

Vue js not rendering image with base64 or binary data from Express API response

My Vue js component receives images via express API call as base64 or raw binary data, but it does not display (render) it.
I can see the image data in the variable given to img src, but for some reason it never renders it, only showing the "broken image icon".
Screenshot of broken image icon
This is the frontend component layout:
<img src="srcimg" v-if="srcimg">
The data mounted and watch functions call the async getsrcimg function which makes the Express API call:
data () {
return {
srcimg: null,
...
mounted () {
if (this.name) {
this.getsrcimg(this.name)
}
},
watch: {
name: function () {
if (this.name) {
this.getsrcimg(this.name)
}
}
},
And the function that receives the image via api:
async getsrcimg (name) {
try {
var getImgRes = await axios.post('http://localhost:5000/get_img?img_path=' + name)
const { data } = getImgRes
this.srcimg = 'data:image/png;base64,' + data
} catch (err) {
console.log(err)
this.message = err.response.data.error
}
}
The server backend function:
app.post('/get_img', (req, res) => {
var img_path = __dirname + req.query.img_path
var fs = require('fs');
const bitmap = fs.readFileSync(img_path);
const base64 = new Buffer.from(bitmap).toString("base64");
res.send(base64); // Send base64 instead of the raw file binary
});
I can see in the console log, as well as display the this.srcimg data available in this format:
data:image/png;base64,77+9UE5HDQoaCg...
But still, the HTML img element does not display the image.
I know the image is being sent correctly because I can see it in the browser inspect Network response preview so it probably is an issue within Vue frontend.
I have also tried to send the image raw binary data from server using res.sendFile and then loading it in the frontend using:
const blob = new Blob([data], {type: 'image/png'})
var imgName = 'image_name_example'
var file = new File([blob], imgName, { type: 'image/png' })
let reader = new FileReader()
reader.readAsDataURL(file)
reader.addEventListener('load', this.processReaderIMG, false)
...
processReaderIMG: function (readerData) {
this.srcimg = readerData.target.result
}
This also gives me the image data but the img element does not display it still.
Any help would be much much appreciated!
If this really is your frontend component:
<img src="srcimg" v-if="srcimg" />
, it attempts to render "srcimg", the actual string, instead of the contents of the reactive variable named srcimg.
And "srcimg" is not a valid source. It's not a url and it's not binary data.
You probably want to use :src instead of src:
<img :src="srcimg" v-if="srcimg" />
, shorthand for
<img v-bind:src="srcimg" v-if="srcimg" />
Docs: v-bind.
If the above is not the issue, test that the binary data is valid (manually).
Other possible causes:
the binary data might contain line breaks which break the HTML (highly improbable, but worth a check)
the response has a different encoding than your page
the response is already prefixed with 'data:image/png;base64,'
If everything fails, perhaps you could share the exact response of the server. There has to be something wrong with it, which you're missing.

Loop through all the images inside directory and convert to base64

I am trying to loop through all the images in my folder convert it into base64 and send to MongoDB.
I started with one image, worked fine.
var filename = '1500.jpg';
var binarydata = fs.readFileSync(filename);
var converted = new Buffer(binarydata).toString("base64");
console.log(converted);
The above code gives me base64 for one file.
I tried changing the code so that it will loop through all the files in my directory and give me base64 for each file.
here is what I wrote but it did not work;
var variantfolder = './variantimages';
fs.readdir(variantfolder, function(err, files){
if (err) {
console.log(err);
}
else {
fs.readFileSync(files, function(err, res){
if (err){console.log('err')} else {
var converted = new Buffer(res).toString("base64");
var onevariant = {
"imagename":files,
"imagebase64":converted
}
var newvariant = new Variant(onevariant)
newvariant.save(err, newvar){
if (err) {
console.log('err');
}
else {
console.log('saved to mongo');
}
}
}
})
}
})
I suspect the problem will be related to you calling functions in the wrong ways.
Check the inputs and outputs of the functions you are using.
The fs.readdir() function callback is passed 2 parameters, an error and an array of file names.
The fs.readFileSync() function takes the parameters path and options. It also returns the file contents, it doesn't take a callback. The callback version is fs.readFile().
So in your code you are passing an array of file names into the file path parameter, which will not work.
You can also pass base64 as the encoding when reading the file and you won't have to convert it after.
I expect you will want something more along these lines (add your own error handling as required):
fs.readdir(variantfolder, (err, fileNames) => {
fileNames.forEach((fileName) => {
fs.readFile(`${variantfolder}/${fileName}`, 'base64', (err, base64Data) => {
// Do your thing with the file data.
});
});
});
Note that you can use the async, sync or promise (fs.promises) version of the fs functions depending on what is most suitable for your code.

Passing HTML input file as the parameter of Firebase Cloud Function

I am pretty new this area and I started firebase cloud function 2 days ago.
Sorry, I am still a student so I might not understand clearly some documentation.
I tried to figure out how the parameter is passed from my client-side javascript to firebase cloud function.
my cloud function
exports.OCR = functions.https.onCall((req) => {
const vision = require('#google-cloud/vision');
// Creates a client
const client = new vision.ImageAnnotatorClient();
console.log(req);
// Performs label detection on the image file
client
.documentTextDetection(req)
.then((results) => {
console.log("Entered");
console.log(req);
const fullTextAnnotation = results[0].fullTextAnnotation;
console.log(fullTextAnnotation.text);
return results[0].fullTextAnnotation.text;
})
.catch(err => {
console.error('ERROR:', err);
return "error";
});
})
I am using firebase cloud function and Google Vision API.
actually I tried to pass the parameter like this
My client side coe
document.getElementById("fileInput").click();
var file = document.getElementById("fileInput");
var fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', function (e) {
var file = e.target.files[0];
// Do something with the image file.
var tmppath = URL.createObjectURL(file);
console.log(file);
console.log(tmppath);
//var url = "https://firebasestorage.googleapis.com/v0/b/recette-f3ef5.appspot.com/o/FB1.gif?alt=media&token=28727220-181c-440e-87ae-4808b5c9ba28";
OCR(file)
.then(function(result) {
console.log(result);
}).catch(function(err) {
console.log(err);
});
});
and it did not work. I always got null return when I trigger the function.
So, my question is that how can I pass the file (HTML INPUT TAG) to my cloud function?
p.s: when I tried the code with node the_code.js it works.
According to the Google Cloud Node.js library documentation the documentTextDetection function should receive a JS object like this:
var image = {
source: {imageUri: 'gs://path/to/image.jpg'}
};
vision.documentTextDetection(image).then(response => {
// doThingsWith(response);
}).catch(err => {
console.error(err);
});
The file you are passing to OCR function has probably a different structure than that defined in documentation.
There are some variants to this:
If the key is source, the value should be another object containing
imageUri or filename as a key and a string as a value.
If the key is content, the value should be a Buffer.
So your code should look something like this.
console.log(tmppath);
//var url = "https://firebasestorage.googleapis.com/v0/b/recette-f3ef5.appspot.com/o/FB1.gif?alt=media&token=28727220-181c-440e-87ae-4808b5c9ba28";
image = {source: {imageUri: 'https://firebasestorage.googleapis.com/v0/b/recette-f3ef5.appspot.com/o/FB1.gif?alt=media&token=28727220-181c-440e-87ae-4808b5c9ba28'}}
OCR(image)
Please provide complete error messages and description of what is file..

Node.js Stream :- Streams functions are not invoking for second time for same file

SFTB, i have a POST request coming to my server with multipart/form-data, now from that request i want to get contents of file.
I am reading the file supplied through streams and the piping that to cvsParser which is just an instance of csv-streamify after doing that we are passing the content to custom Transform function that fetch's the resource using http[ I am using got for that] and after fetching it i am compressing the image.
Now issue is, when i submit the file for first time it works like charm but, when i am trying to submit same file second time it skips the whole stream part and directly jumps to finish event handler.
Logs for first time :-
Submitting
Converting image at C:\image-minifier-sqd\build\src\1469004088476.bell.svg
Build is present at build\dest\1469004088476.bell.svg
Converting image at C:\image-minifier-sqd\build\src\1469004088996.mail.svg
Build is present at build\dest\1469004088996.mail.svg
Finished
Logs when i submit same file second time[Both with and without refresh on front-end]
Submitting
Finished
FYI, on front-end i am using fetch API to make POST request.
My Server code :-
function createParser() {
var parser = new Transform({objectMode: true});
parser._transform = function(data, encoding, done) {
const date = new Date();
const link = data.toString().slice(2,-3);
const fileName = date.getTime()+ '.' +link.split( '/' ).pop(),
filePath = path.resolve(__dirname,`build/src/${fileName}`);
got.stream(link)
.pipe(fs.createWriteStream(filePath,{flags:'a'}))
.on('close',_ => {
console.log(`Converting image at ${filePath}`)
//Compressing images
imagemin([filePath],'build/dest',{
plugins: [
imageminMozjpeg(),
imageminPngquant({speed: 10}),
imageminSvgo(),
imageminGifsicle()
]
})
.then(file => {
console.log(`Build is present at ${file[0].path}`);
this.push(file[0].path);
done();
});
});
};
return parser;
}
//A request comes here with multipart/form-data
app.post('/submit/csv',upload.array('data'),(req, res) => {
console.log('Submitting')
const stream = fs.createReadStream(path.resolve(__dirname,req.files[0].path))
.pipe(csvParser)
.pipe(createParser())
.pipe(res)
.on('finish',_ => {
log('Finished');
res.end();
});
});
Thanks.
I think the problem is related with the reuse of the csvParser. Try to wrap the creation of the csvParser in a function an use it instead:
function createCsvParser() {
const parser = csv();
parser.on('data', function (line) {
[...]
});
return parser;
}
and change .pipe(csvParser) into .pipe(createCsvParser()).
Hope this helps.

How can I get a buffer for a file (image) from CollectionFS

I'm trying to insert an image into a pdf I'm creating server-side with PDFkit. I'm using cfs:dropbox to store my files. Before when I was using cvs:filesystem, it was easy to add the images to my pdf's cause they were right there. Now that they're stored remotely, I'm not sure how to add them, since PDFkit does not support adding images with just the url. It will, however, accept a buffer. How can I get a buffer from my CollectionFS files?
So far I have something like this:
var portrait = Portraits.findOne('vS2yFy4gxXdjTtz5d');
readStream = portrait.createReadStream('portraits');
I tried getting the buffer two ways so far:
First using dataMan, but the last command never comes back:
var dataMan = new DataMan.ReadStream(readStream, portrait.type());
var buffer = Meteor.wrapAsync(Function.prototype.bind(dataMan.getBuffer, dataMan))();
Second buffering the stream manually:
var buffer = new Buffer(0);
readStream.on('readable', function() {
buffer = Buffer.concat([buffer, readStream.read()]);
});
readStream.on('end', function() {
console.log(buffer.toString('base64'));
});
That never seems to come back either. I double-checked my doc to make sure it was there and it has a valid url and the image appears when I put the url in my browser. Am I missing something?
I had to do something similar and since there's no answer to this question, here is how I do it:
// take a cfs file and return a base64 string
var getBase64Data = function(file, callback) {
// callback has the form function (err, res) {}
var readStream = file.createReadStream();
var buffer = [];
readStream.on('data', function(chunk) {
buffer.push(chunk);
});
readStream.on('error', function(err) {
callback(err, null);
});
readStream.on('end', function() {
callback(null, buffer.concat()[0].toString('base64'));
});
};
// wrap it to make it sync
var getBase64DataSync = Meteor.wrapAsync(getBase64Data);
// get a cfs file
var file = Files.findOne();
// get the base64 string
var base64str = getBase64DataSync(file);
// get the buffer from the string
var buffer = new Buffer(base64str, 'base64')
Hope it'll help!

Categories