Azure Video Indexer stalls when uploading MP4s with NodeJS API calls - javascript

I'm using NodeJS to upload a directory of mp4s one-by-one to Azure. For some reason, the video indexing stalls about 20% of the time. I'm not clear as to why. There are no errors displayed by the API call. When trying to load data for the video, I just get the status "Processing", but it never ends. I have to manually delete the video and start over.
Logging into the videoindexer.ai website, I can see that the process is stalled indefinitely:
Here is my NodeJS for uploading an MP4. I'd greatly appreciate any advice if I'm doing something wrong here:
const fs = require('fs');
const path = require('path');
const fetch = require('node-fetch');
const FormData = require('form-data');
const uploadVideo = async (filePath) => {
const fileName = path.parse(filePath).name;
const url = `https://api.videoindexer.ai/${LOCATION}/Accounts/${ACCOUNTID}/Videos?accessToken=${TOKEN}&name=${fileName}&description=TEST&privacy=private&partition=some_partition`;
const fileBase = path.parse(filePath).base; // e.g. "video1.mp4"
const formData = new FormData();
formData.append('file', fs.createReadStream(filePath), { filename: fileBase });
const headers = {
'x-ms-client-request-id': '',
'Ocp-Apim-Subscription-Key': APIKEY,
...formData.getHeaders()
};
const response = await fetch(url, {
cache: 'no-cache',
headers,
method: 'POST',
body: formData
});
const json = await response.json();
if (json.ErrorType && json.Message) {
throw new Error(`${json.ErrorType} - ${json.Message}`);
}
console.log('Successfully uploaded video');
console.log(json);
return json.id;
};
uploadVideo('./video1.mp4').then((videoId) => console.log('result: ' + videoId)) // <- returns the correct videoId even when process stalls

Please add header 'Content-Type': 'multipart/form-data'.
You can remove the ...formData.getHeaders()
and please provide your account Id, and/or videoId that stalls so we can check internally
Thanks,
Video Indexer Team

Related

Upload and pin image with Piniata api on client side, no nodejs

I am trying to use the Piniata api. Here it is:
https://docs.pinata.cloud/
The idea is to upload and pin and image using the api, into my account in Piniata.
I got this sample to upload a file in base64, using Node.js and server side.
The sample use this api call:
"https://api.pinata.cloud/pinning/pinFileToIPFS"
I am suppose to be able to do this in client side as well.
However, there is no sample of client side without using Node.js. And I can't seem to find exactly a documentation of what the api call expects.
Here is the sample I got from the Piniata support:
const { Readable } = require("stream");
const FormData = require("form-data");
const axios = require("axios");
(async () => {
try {
const base64 = "BASE64 FILE STRING";
const imgBuffer = Buffer.from(base64, "base64");
const stream = Readable.from(imgBuffer);
const data = new FormData();
data.append('file', stream, {
filepath: 'FILENAME.png'
})
const res = await axios.post("https://api.pinata.cloud/pinning/pinFileToIPFS", data, {
maxBodyLength: "Infinity",
headers: {
'Content-Type': `multipart/form-data; boundary=${data._boundary}`,
pinata_api_key: pinataApiKey,
pinata_secret_api_key: pinataSecretApiKey
}
});
console.log(res.data);
} catch (error) {
console.log(error);
}
})();
Here is my attempt to perform an upload from client side without Node.js
async function uploadFile(base64Data)
{
const url = `https://api.pinata.cloud/pinning/pinFileToIPFS`;
var status = 0;
try {
let data = new FormData();
var fileName = "FILENAME.png";
var file = new File([base64Data], fileName, {type: "image/png+base64"});
data.append(`data`, file, file.name);
data.append(`maxBodyLength`, "Infinity");
const response = await postData('POST', url, {
'Content-Type': `multipart/form-data; boundary=${data._boundary}`,
"Authorization": "Bearer Redacted"
},
data
);
} catch (error) {
console.log('error');
console.log(error);
}
}
What I get as a response from the server is 400 and the error being:
{"error":"Invalid request format."}
What am I doing wrong?
Also, it seems like when I try to use FormData .append with a stream as a sample, it doesn't work. As if it only expects a blob.

Pinata IPFS's pinFileToIPFS method not accepting a user uploaded file

I am working on a project (using React.js Express.js and Node.js) to convert a user uploaded image into and NFT on Ethereum blockchain and for that, I need to upload the image on an IPFS (I am using Pinata) and then use the pinata URI in the metadata to mint a new NFT. (Do let me know if I am wrong here, I am still newbie to web3)
For uploading my image onto the Pinata IPFS, I am sending the base64 string of the image from the client side to the server side and then calling the pinFileToIPFS method. This is the code of my server side file
const axios = require('axios');
const fs = require('fs');
const FormData = require('form-data');
const router = require('express').Router();
const { Readable } = require('stream');
const pinFileToIPFS = (image) => {
const url = `https://api.pinata.cloud/pinning/pinJSONToIPFS`;
const buffer = Buffer.from(image);
const stream = Readable.from(buffer);
const filename = `an-awesome-nft_${Date.now()}.png`;
stream.path = filename;
const formData = new FormData();
formData.append("file", stream);
return axios.post(url,
formData,
{
headers: {
'Content-Type': `multipart/form-data; boundary= ${formData._boundary}`,
'pinata_api_key': "*******************",
'pinata_secret_api_key': "**********************************",
}
}
).then(function (response) {
console.log("Success: ", response);
}).catch(function (error) {
console.log("Fail! ", error.response.data);
});
};
router.route('/').post((req, res) => {
const image = req.body.image;
pinFileToIPFS(image);
});
module.exports = router;
Here req.body.image contains the base64 string of the user uploaded file.
I have tried to convert the base64 string into a buffer and then convert the buffer into a readable stream (as done in the official Pianata documentation but for a localy file) and then wrap it up in FormData(), but I keep getting the following error.
data: {
error: 'This API endpoint requires valid JSON, and a JSON content-type'
}
I know the problem is with the format my image/file is being sent to the API but I can't figure out. I am still a newbie to web3 and blockchains so please help!
The recommended way of interacting with Pinata, is by using their Node.JS SDK. This SDK has a pinFileToIPFS function, allows you to upload an image to their IPFS nodes in the form of a readableStream.
A sample of this would look like
const fs = require('fs');
const readableStreamForFile = fs.createReadStream('./yourfile.png');
const options = {
pinataMetadata: {
name: MyCustomName,
keyvalues: {
customKey: 'customValue',
customKey2: 'customValue2'
}
},
pinataOptions: {
cidVersion: 0
}
};
pinata.pinFileToIPFS(readableStreamForFile, options).then((result) => {
//handle results here
console.log(result);
}).catch((err) => {
//handle error here
console.log(err);
});
However, if you are deadset on using their API endpoints and simply posting to them via axios, there is a seperate API endpoint. /pinning/pinFileToIPFS. Examples of this method can be found in their API Docs.
You may want to consider changing the following two lines and using the https://api.pinata.cloud/pinning/pinFileToIPFS endpoint instead:
const buffer = Buffer.from(image); -> const buffer = Buffer.from(image, "base64");
and
formData.append("file", stream); -> formData.append("file", stream, "fileNameOfChoiche.png);
When you are uploading an image or file to Pinata IPFS with node js. These are the steps that even don't need Pinata Node.js SDK.
1- You can upload an image from the front end with React or Next.js. Code is given below.
const uploadAttachment = async (data, token) => {
try {
return await Api.post(`${ApiRoutes.upload_attachment}`, data, {
headers: {
Authorization: "Bearer " + token, //the token is a variable which holds the token
},
});
} catch (error) {
return {
status: 404,
};
}
};
export default uploadAttachment;
2- You need to install multer to upload an image.
const multer = require("multer");
global.uploadSingleFile = multer({ dest: "uploads/" });
3- Set up your route with multer middleware and action which you are going to call.
.post(
"/attachments/upload",
uploadSingleFile.single("file"),
actions.attachments.upload.pinFileToIPFSLocal
);
4- Last step where you will hit the Pinata endpoint with Pinata API & Secret key.
pinFileToIPFSLocal: async (req, res, next) => {
try {
const url = "https://api.pinata.cloud/pinning/pinFileToIPFS";
let formData = new FormData();
formData.append("file", JSON.stringify(req.file), req.file.originalname);
axios
.post(url, formData, {
maxContentLength: -1,
headers: {
"Content-Type": `multipart/form-data; boundary=${formData._boundary}`,
pinata_api_key: process.env.PINATA_API_KEY,
pinata_secret_api_key: process.env.PINATA_API_SECRET,
path: "somename",
},
})
.then((data) => {
console.log("Result...", data);
return utils.response.response(
res,
"Upload image to ipfs.",
true,
200,
data.data
);
})
.catch((err) => {
return utils.response.response(
res,
"Image not upload to ipfs",
false,
200,
err
);
});
} catch (error) {
next(error);
}
The error message is clear. You are using url that used for json file upload. this is the url you should use to upload image
const url = `https://api.pinata.cloud/pinning/pinFileToIPFS`;
you don't have to convert buffer to a readable stream.
I am not sure if this is correct ${formData._boundary}. should be
"Content-Type": `multipart/form-data: boundary=${formData.getBoundary()}`,
There must be an error on the image parameter. A simple buffer representation of the image should work. The readable stream is not necessary.
Instead of creating the buffer, you could use middleware like express-fileupload to access the buffer representation of the file uploaded on the client-side directly.
const file = req.files;
const url = "https://api.pinata.cloud/pinning/pinFileToIPFS";
const data = new FormData();
data.append("file", file.file.data, { filepath: "anyname" });
const result = await axios.post(url, data, {
maxContentLength: -1,
headers: {
"Content-Type": `multipart/form-data; boundary=${data._boundary}`,
pinata_api_key: process.env.PINATA_API_KEY,
pinata_secret_api_key: process.env.PINATA_API_SECRET,
path: "somename",
},
});

Unable to get video file for S3 upload. (using Expo Camera)

I have been stuck with trying to upload a video to S3 for a while and was hoping to get some pointers. Currently, what I've read and was told is that we need to send an actual file to S3 and not the url (which we might do if we were sending it to the backend before aws).
I am trying to do this by
const getBlob = async (fileURi) => {
console.log('THIS IS IT', fileURi);
const resp = await fetch(fileURi);
const videoBody = await resp.blob();
console.log(videoBody);
};
getBlob(video.uri);
The problem I am having is I am unable to actually get the video file. When I stop recording a video with await camera.stopRecording(); what I get in return is
Object {
"uri": "file:///path/20DD0E08-11CA-423D-B83D-BD5ED40DFB25.mov",
}
Is there a recommended approach in order to successfully get the actual file in order to send it to S3 through the client?
The way I am trying to currently send the video which doesn't work is:
const formData = new FormData();
formData.append('file', video.uri);
await fetch(url, {
method: 'POST',
body: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
url: refers to the presignedUrl we get in return from aws.
P.S - Sending to the server through a fetch call does work but I noticed this approach also leave the User waiting for 10+ seconds since I need to send the video to the server then wait for it to finish uploading in AWS.
Thank you for all the help.
If I understand correctly you know how to upload file to your own server, but you want to send it directly to S3.
In that case I would suggest to use presigned URLs. https://docs.aws.amazon.com/AmazonS3/latest/dev/PresignedUrlUploadObject.html
You can generate presigned URL on your backend, it is basically regular URL pointing to S3 file and some key values. You need to send those values to mobile app and do the same fetch call you are already using, but replace url with the one generated on backend and add all key-values to FormData.
Example for node backend would look like this
import AWS from 'aws-sdk';
...
const client = new AWS.S3(config);
...
const presignedUrl = client.createPresignedPost({
Bucket: 'example-bucket-name',
Fields: { key: 'example-file-name' },
});
and in mobile app you would
const form = new FormData();
Object.keys(presignedUrl.fields).forEach(key => {
form.append(key, presignedUrl.fields[key]);
})
form.append('file', fileToUpload);
await fetch(presignedUrl.url, {
method: 'POST',
body: form,
headers: {
'Content-Type': 'multipart/form-data'
}
})
My solutions. Please review the sample application. https://github.com/expo/examples/tree/master/with-aws-storage-upload
const response = await fetch(video.uri);
const blob = await response.blob();
const params = {
Bucket: myBucket,
Metadata: {
long: long.toString(),
lat: lat.toString(),
size: videoSize.toString()
},
Key: myKey,
Body: blob
};

How to convert a readable stream to a blob in javascript?

I have a test that should read a image file and submit the image file to an api that accepts a multipart-formdata.
I am using the fetch api along with formdata class to set the image file. The formdata only accepts a blob. So in my test i must convert the the file i read in which is of type stream to a blob.
test("should submit front document", async () => {
const side = "front";
const stream = fs.createReadStream(process.cwd() + "/test/resources/" + "id/front.jpg");
const image = await streamToBlob(stream);
const front = await myLibrary.document(id, side, image);
expect(front.success).toBe(true);
});
I am attempting to use a library here to convert the stream to a blob https://www.npmjs.com/package/stream-to-blob. However the test is failing. If i attempt to console.log(image) i get the following Blob {}
Why is the blob empty {}?
async document(id, side, image) {
const url = this.API_URL + "/document"
let formData = new FormData();
formData.set("image", image, "front.jpg");
formData.set("side", side);
let headers = new Headers();
headers.set("Authorization", "Bearer " + this.API_KEY);
const request = {
method: "POST",
body: formData,
headers: headers,
};
try {
const response = await fetch(url, request);
const data = await response.json();
return data;
} catch (err) {
throw err;
}
}

React axios multiple files upload

I'm trying upload multiple images with axios in React but i cannot figure out what is wrong. First I tried to upload single image and that work just fine. But with multiple images I'm out of options.
I'm creating FormData like so:
for (let i = 0; i < images.length; i++) {
formData.append('productPhotos[' + i + ']', images[i]);
}
The axios request looking like this
const config = { headers: { 'Content-Type': 'multipart/form-data' } };
axios
.post(endPoints.createProduct, formData, config)
.then(res => console.log(res))
.catch(err => console.log(err));
My back-end is written is node/express and I'm using multer for uploading. The signature is look like this:
app.post("/product", upload.array("productPhotos"), (req, res) => {
I tried this back-end end point in PostMan and uploading works for just fine, so the error must be on front-end. Thanks for help.
UPDATE
Right way to pass multiple files in formData:
images.forEach(img => {
formData.append("productPhotos", img)
})
Here is a full working set up (expanded version of the answer above)
Client side:
// silly note but make sure you're constructing files for these (if you're recording audio or video yourself)
// if you send it something other than file it will fail silently with this set-up
let arrayOfYourFiles=[image, audio, video]
// create formData object
const formData = new FormData();
arrayOfYourFiles.forEach(file=>{
formData.append("arrayOfFilesName", file);
});
axios({
method: "POST",
url: serverUrl + "/multiplefiles",
data: formData,
headers: {
"Content-Type": "multipart/form-data"
}
})
//some error handling
Server side (express, node - mutler)
const UPLOAD_FILES_DIR = "./uploads";
const storage = multer.diskStorage({
destination(req, file, cb) {
cb(null, UPLOAD_FILES_DIR);
},
// in case you want to change the names of your files)
filename(req, file = {}, cb) {
file.mimetype = "audio/webm";
// console.log(req)
const {originalname} = file;
const fileExtension = (originalname.match(/\.+[\S]+$/) || [])[0];
cb(null, `${file.fieldname}${Date.now()}${fileExtension}`);
}
});
const upload = multer({storage});
// post route that will be hit by your client (the name of the array has to match)
app.post("/multiplefiles", upload.array('arrayOfFilesName', 5), function (req, res) {
console.log(req.files, 'files')
//logs 3 files that have been sent from the client
}
This is not the right way to generate keys. You can try something like this:
let productimages = [];
for (let i = 0; i < images.length; i++) {
productimages.push(images[i]);
}
formData.append('productPhotos', productimages);
You may want to send the files as an array to the endpoint:
images.forEach( img => {
formData.append('productPhotos[]', img);
})

Categories