Convert a .ogg blob into a google drive file - javascript

Background
I want to record students speaking and then upload their recordings automatically to Google drive.
Current state of affairs
I have client side code which can produce a blob containing .ogg recording.
var blob = new Blob(chunks, { 'type' : 'audio/ogg; codecs=opus' });
I can send the blob to a Google apps script standalone script in a doPost() request. In this case I'm using axios with a promise.
axios.post("https://script.google.com/macros/s/SOME_ID/exec", blob)
.then((response)=>{
console.log(response)
}).catch(error =>{
console.log(error)
})
The blob arrives at the standalone script.
The standalone Google apps script
function doPost(e) {
var params = JSON.stringify(e.parameters);
createNewSoundFile(params)
return ContentService.createTextOutput(params);
}
function createNewSoundFile(blob){
var title = 'Was created from a recording'
var folderId = 'SOME_FOLDER_ID'
var resource = {
title: title,
parents: [
{
"id": folderId,
"kind": "drive#fileLink"
}
],
mimeType: 'application/vnd.google-apps.audio',
};
try{
var newfile = Drive.Files.insert(resource, blob).id
} catch(e){
// Send error to Google sheet
// Exception: The mediaData parameter only supports blob types for uploads.
}
}
Problem
The above apps script code says the blob is not a support media type.
Question
How can I create become a new .ogg file in the Google drive from a .ogg blob created in the browser?

How about these modifications?
Modification points :
javascript side :
Encode blob to base64, and sent it as the string data.
GAS side :
Decode base64 to blob, and save it as a ogg file with the mimeType of audio/ogg.
Save the file as audio/ogg.
In my environment, it couldn't convert from audio/ogg to application/vnd.google-apps.audio.
Modified script : javascript side
From :
axios.post("https://script.google.com/macros/s/SOME_ID/exec", blob)
.then((response)=>{
console.log(response)
}).catch(error =>{
console.log(error)
})
To :
var reader = new FileReader();
reader.readAsDataURL(blob);
reader.onloadend = function() {
base64 = reader.result.replace(/^.*,/, "");
let data = new URLSearchParams();
data.append('data', base64);
axios.post(
"https://script.google.com/macros/s/SOME_ID/exec",
data,
{headers: {'Content-Type': 'application/x-www-form-urlencoded'}}
).then((response)=>{
console.log(response)
}).catch(error =>{
console.log(error)
});
}
Modified script : GAS side
To :
function createNewSoundFile(base64){
var data = Utilities.base64Decode(base64.parameters.data); // Added
var blob = Utilities.newBlob(data); // Added
var title = 'Was created from a recording'
var folderId = 'SOME_FOLDER_ID'
var resource = {
title: title,
parents: [
{
"id": folderId,
// "kind": "drive#fileLink" // I didn't know whether this is required.
}
],
mimeType: "audio/ogg", // Modified
};
try{
var newfile = Drive.Files.insert(resource, blob).id
} catch(e){
// Send error to Google sheet
// Exception: The mediaData parameter only supports blob types for uploads.
}
}
Note :
In my environment, {headers: {'Content-Type': 'application/x-www-form-urlencoded'}} was required for the script of javascript side. If it is not required in your environment, please remove it.
If I misunderstand your question, I'm sorry.

Related

Trouble using JavaScript and the google drive API to convert a google slide into a pdf, and upload the pdf onto a folder

I'm new to JavaScript, and am trying to write some code that uses the google drive API (via the gapi client) to transform an existing slide into a pdf document, upload it to a specific folder, and return the pdf file id. This is all to be done in the browser, if possible.
I've already done this on python for another use case, and the code looks something like this:
import googleapiclient.http as client_methods
from io import BytesIO
...
data = drive.files().export(fileId=slideId, mimeType='application/pdf').execute()
body = {'name': fileName, 'mimeType': 'application/pdf', 'parents': [folderId]}
# wrapping the binary (data) file with BytesIO class
fh = io.BytesIO(data)
# creating the Media Io upload class for the file
media_body = client_methods.MediaIoBaseUpload(fh, mimetype='application/pdf')
pdfFileId = drive.files().create(body=body, media_body=media_body, supportsAllDrives=True).execute(['id'])
I've tried to replicate the same steps using JavaScript and my limited knowledge, and can successfully upload a pdf file into the desired folder, but the file shows as empty (doesn't even open in the drive).
I believe it might be due to the way I'm handling the binary data that I get from exporting the initial slide.
The last iteration of my JavaScript code is shown below (I have all the necessary permissions to use the gapi client):
async function createPdfFile() {
gapi.client.load("drive", "v3", function () {
// Set the MIME type for the exported file
const mimeType = "application/pdf";
// Set the file name for the exported PDF file
const fileName = "Trial upload.pdf";
// Export the Google Slides presentation as a PDF file
gapi.client.drive.files.export({
fileId,
mimeType
}).then(async function (response) {
// Get the binary data of the PDF file
const pdfData = await response.body;
const blob = await new Blob([pdfData], {type: 'application/pdf'})
const file = new File([blob], "presentation.pdf");
// Create a new file in the specified Google Drive folder with the PDF data
await gapi.client.drive.files.create({
name: fileName,
parents: [folderId],
mimeType: mimeType,
media: {mimeType: 'application/pdf', body: file},
supportsAllDrives: true
}).then(function (response) {
// Get the ID of the created PDF file
const pdfFileId = response.result.id;
console.log("PDF file created with ID: " + pdfFileId);
})
})
})
}
await createPdfFile()
As for the output, and as stated, it does create a pdf file, and logs the pdf file id, but the file itself is empty. I'd really appreciate it if someone could help me make sense of this (similar thread here, but can't replicate his success).
I believe your goal is as follows.
You want to convert Google Slides to PDF format using googleapis for Javascript.
Your access token can be exported and uploaded to Google Drive.
Issue and workaround:
When I tested your script, unfortunately, response.body from gapi.client.drive.files.export is binary data, and in this case, this cannot be correctly converted to the blob. And also, in the current stage, it seems that a file cannot be uploaded using gapi.client.drive.files.create. I thought that these might be the reason for your current issue.
From these situations, I would like to propose the flow for achieving your goal using fetch API. The modified script is as follows.
In this case, the access token is retrieved from the client like gapi.auth.getToken().access_token.
Modified script:
Please modify your script as follows.
From:
gapi.client.drive.files.export({
fileId,
mimeType
}).then(async function (response) {
// Get the binary data of the PDF file
const pdfData = await response.body;
const blob = await new Blob([pdfData], { type: 'application/pdf' })
const file = new File([blob], "presentation.pdf");
// Create a new file in the specified Google Drive folder with the PDF data
await gapi.client.drive.files.create({
name: fileName,
parents: [folderId],
mimeType: mimeType,
media: { mimeType: 'application/pdf', body: file },
supportsAllDrives: true
}).then(function (response) {
// Get the ID of the created PDF file
const pdfFileId = response.result.id;
console.log("PDF file created with ID: " + pdfFileId);
})
})
To:
gapi.client.drive.files.get({ fileId, fields: "exportLinks", supportsAllDrives: true }).then(function (response) {
const obj = JSON.parse(response.body);
if (Object.keys(obj).length == 0) throw new Error("This file cannot be converted to PDF format.");
const url = obj.exportLinks["application/pdf"];
if (!url) throw new Error("No exported URL.");
const accessToken = gapi.auth.getToken().access_token;
fetch(url, {
method: 'GET',
headers: { 'Authorization': 'Bearer ' + accessToken },
})
.then(res => res.blob())
.then(blob => {
const metadata = { name: fileName, parents: [folderId], mimeType };
const form = new FormData();
form.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }));
form.append('file', blob);
fetch('https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&supportsAllDrives=true', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + accessToken },
body: form
})
.then(res => res.json())
.then(obj => console.log("PDF file created with ID: " + obj.id));
});
});
When this script is run, the export URL of PDF data is retrieved from the file ID. And, the PDF data is downloaded and uploaded to Google Drive.
Note:
In your script, fileId is not declared. Please be careful about this.
If the file size is more than 5 MB, please use the resumable upload.
Reference:
Upload file data
Added:
From your following reply,
?uploadType=multipart also returns a 404 type error
I'm worried about that in your situation, new FormData() might not be able to be used. If my understanding is correct, please test the following script. In this script, the request body of multipart/form-data is manually created.
Modified script:
gapi.client.drive.files.get({ fileId, fields: "exportLinks", supportsAllDrives: true }).then(function (response) {
const obj = JSON.parse(response.body);
if (Object.keys(obj).length == 0) throw new Error("This file cannot be converted to PDF format.");
const url = obj.exportLinks["application/pdf"];
if (!url) throw new Error("No exported URL.");
const accessToken = gapi.auth.getToken().access_token;
fetch(url, {
method: 'GET',
headers: { 'Authorization': 'Bearer ' + accessToken },
})
.then(res => res.blob())
.then(blob => {
const metadata = { name: fileName, parents: [folderId], mimeType };
const fr = new FileReader();
fr.onload = e => {
const data = e.target.result.split(",");
const req = "--xxxxxxxx\r\n" +
"Content-Type: application/json\r\n\r\n" +
JSON.stringify(metadata) + "\r\n" +
"--xxxxxxxx\r\n" +
"Content-Transfer-Encoding: base64\r\n\r\n" +
data[1] + "\r\n" +
"--xxxxxxxx--";
fetch('https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&supportsAllDrives=true', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + accessToken, "Content-Type": "multipart/related; boundary=xxxxxxxx" },
body: req
})
.then(res => res.json())
.then(obj => {
console.log("PDF file created with ID: " + obj.id)
});
}
fr.readAsDataURL(blob);
});
});
When I tested this script, no error occurs. I confirmed that the Google Slides file could be converted to a PDF file and the PDF file was uploaded to the specific folder.

Google Drive API v3 uploads 'Untitled' file every time I try to upload something with Javascript

So the drive documentation is for NodeJs and i'm using plain JS. I can upload with no problem however the files appear to be uploaded incorrectly as 'Untitled' and unopenable
var file = document.getElementById('input-file').files[0];
var fileMetadata = {
'parents': ['0AN75N3P23eTJUk9PVA'], // Folder ID at Google Drive
'name':
cliente + '.' + file.name.substring(file.name.lastIndexOf('.') + 1) // Filename at Google Drive
};
var r = new FileReader();
r.readAsText(file);
r.onload = function (e) {
var body = r.result;
var media = {
mimeType: file.type,
body: body
};
gapi.client.drive.files.create({
resource: fileMetadata,
media,
fields: 'id'
})
.then(response => {
console.log('response: ', response);
})
.catch(() => {
console.log('something is wrong');
});
}
Your file is untitled because you are not properly setting the name of the file. Its cant be opened because you have not properly set the mime type.
Try testing it supplying the name and the mime type hard coded. Then once that works you can figure out what is wrong with your building of the name and file.type methods.
let fileMetadata = {
'name': 'icon.png',
'parents': [ '10krlloIS2i_2u_ewkdv3_1NqcpmWSL1w' ]
};
let media = {
mimeType: 'image/jpeg',
body: fs.createReadStream('icon.png')
};

Getting the pdf blob from url and insert to drive directly using puppeteer library and fetch

I´m trying to use puppeteer to log in a website and "download" a pdf directly to my drive. I've managed to reach the pdf page with puppeteer and I tried (between other tries) to get the blob using fetch with the cookies to send to drive. I can´t post the login information here, but if you could help me looking for an error (or more) in the code it would be great! For now, it goes to the page before pdf, gets the link, fetch with cookies and insert a pdf in drive, but the pdf is corrupted with 0 kb.
I tried setRequestInterception, getPdf (from puppeteer) and using buffer with some stuff I found on my research.
//Page before pdfPage. Here I got the link: urlPdf
//await page.goto(urlPdf);
//await page.waitForNavigation();
//const htmlPdf = await page.content();
const cookies = await page.cookies()
const opts = {
headers: {
cookie: cookies
}
};
let blob = await fetch(urlPdf,opts).then(r => r.blob());
console.log("pegou o blob")
// upload file in specific folder
var file ;
console.log("driveApi upload reached")
function blobToFile(req){
file = req.body.blob
//A Blob() is almost a File() - it's just missing the two properties below which we will add
file.lastModifiedDate = new Date();
file.name = teste.pdf;//req.body.word;
return file;
}
var folderId = myFolderId;
var fileMetadata = {
'name': 'teste.pdf',
parents: [folderId]
};
var media = {
mimeType: 'application/pdf',
body: file
};
drive.files.create({
auth: jwToken,
resource: fileMetadata,
media: media,
fields: 'id'
}, function(err, file) {
if (err) {
// Handle error
console.error(err);
} else {
console.log('File Id: ', file.data.id);
}
});
I tried many things, but the final solution I came with is posted here:
Puppeteer - How can I get the current page (application/pdf) as a buffer or file?
await page.setRequestInterception(true);
page.on('request', async request => {
if (request.url().indexOf('exibirFat.do')>0) { //This condition is true only in pdf page (in my case of course)
const options = {
encoding: null,
method: request._method,
uri: request._url,
body: request._postData,
headers: request._headers
}
/* add the cookies */
const cookies = await page.cookies();
options.headers.Cookie = cookies.map(ck => ck.name + '=' + ck.value).join(';');
/* resend the request */
const response = await request_client(options);
//console.log(response); // PDF Buffer
buffer = response;
let filename = 'file.pdf';
fs.writeFileSync(filename, buffer); //Save file
} else {
request.continue();
}
});
This solution needs: const request_client = require('request-promise-native');

Download and upload image without saving to disk

Using Node.js, I am trying to get an image from a URL and upload that image to another service without saving image to disk. I have the following code that works when saving the file to disk and using fs to create a readablestream. But as I am doing this as a cron job on a read-only file system (webtask.io) I'd want to achieve the same result without saving the file to disk temporarily. Shouldn't that be possible?
request(image.Url)
.pipe(
fs
.createWriteStream(image.Id)
.on('finish', () => {
client.assets
.upload('image', fs.createReadStream(image.Id))
.then(imageAsset => {
resolve(imageAsset)
})
})
)
Do you have any suggestions of how to achieve this without saving the file to disk? The upload client will take the following
client.asset.upload(type: 'file' | image', body: File | Blob | Buffer | NodeStream, options = {}): Promise<AssetDocument>
Thanks!
How about passing the buffer down to the upload function? Since as per your statement it'll accept a buffer.
As a side note... This will keep it in memory for the duration of the method execution, so if you call this numerous times you might run out of resources.
request.get(url, function (res) {
var data = [];
res.on('data', function(chunk) {
data.push(chunk);
}).on('end', function() {
var buffer = Buffer.concat(data);
// Pass the buffer
client.asset.upload(type: 'buffer', body: buffer);
});
});
I tried some various libraries and it turns out that node-fetch provides a way to return a buffer. So this code works:
fetch(image.Url)
.then(res => res.buffer())
.then(buffer => client.assets
.upload('image', buffer, {filename: image.Id}))
.then(imageAsset => {
resolve(imageAsset)
})
well I know it has been a few years since the question was originally asked, but I have encountered this problem now, and since I didn't find an answer with a comprehensive example I made one myself.
i'm assuming that the file path is a valid URL and that the end of it is the file name, I need to pass an apikey to this API endpoint, and a successful upload sends me back a response with a token.
I'm using node-fetch and form-data as dependencies.
const fetch = require('node-fetch');
const FormData = require('form-data');
const secretKey = 'secretKey';
const downloadAndUploadFile = async (filePath) => {
const fileName = new URL(filePath).pathname.split("/").pop();
const endpoint = `the-upload-endpoint-url`;
const formData = new FormData();
let jsonResponse = null;
try {
const download = await fetch(filePath);
const buffer = await download.buffer();
if (!buffer) {
console.log('file not found', filePath);
return null;
}
formData.append('file', buffer, fileName);
const response = await fetch(endpoint, {
method: 'POST', body: formData, headers: {
...formData.getHeaders(),
"Authorization": `Bearer ${secretKey}`,
},
});
jsonResponse = await response.json();
} catch (error) {
console.log('error on file upload', error);
}
return jsonResponse ? jsonResponse.token : null;
}

Google Cloud Speech-to-Text api encoding issues in Node.js

I'm trying to capture audio from a user's microphone and send it to a server where it will get sent to Google's Speech-to-Text-API for translation. I'm accessing the audio using navigator.mediaDevices.GetuserMedia() which I capture using a MediaRecorder object. When I run the following code I get an error from Google that says "INVALID_ARGUMENT: RecognitionAudio not set." I'm not sure how to set it as the relavant page (https://cloud.google.com/speech-to-text/docs/reference/rest/v1/RecognitionAudio) doesn't say much about it.
Relevant client side code that runs after user presses stop:
mediaRecorder.onstop = function(e) {
var blob = new Blob(chunks, { type : 'audio/flac' });
var reader = new FileReader();
reader.readAsBinaryString(blob);
reader.onloadend = function() {
base64data = reader.result;
writeBinaryFile(base64data)
}
chunks = []; //array to store recording
}
//asynchronous binary file write
function writeBinaryFile(content) {
$.ajax({
type: "POST",
url: "/voice_api",
data: { content: content }
}).done(function(data) {
// TODO: display success status somewhere
});
Server side code running node.js:
app.post("/voice_api", (req, res) => {
const audioBytes = req.body;
// The audio file's encoding, sample rate in hertz, and BCP-47 language code
const audio = {
content: audioBytes,
};
const config = {
languageCode: 'en-US'
};
const request = {
audio: audio,
config: config
};
// Detects speech in the audio file
client
.recognize(request)
.then(data => {
const response = data[0];
const transcription = response.results
.map(result => result.alternatives[0].transcript)
.join('\n');
console.log(`Transcription: ${transcription}`);
res.send(transcription);
})
.catch(err => {
console.error('ERROR:', err);
});
});
If I run the server code with the line "const audioBytes = req.body;" changed to "const audioBytes = req.body.content;" I get an error message that there is bad encoding. I'm not sure if I'm encoding it properly on the client side or if I'm accessing it properly on the server side. Any help would be appreciated. Thanks!
const config = {
// "enableAutomaticPunctuation": true,
"encoding": "LINEAR16",
"languageCode": "en-US",
"model": "default",
"sampleRateHertz": 44100,
audioChannelCount: 2,
enableSeparateRecognitionPerChannel: true,
};

Categories

Resources