When uploading video to YouTube, the resulting video link becomes immediately available, but the video is not yet ready to be played, because Youtube is processing it. How to get programmatically notified upon the completion of the processing? I am using node.js googleapis.
/youtube/videos/list provides the API to query the video upload status.
var videoId = 'aBcdEfjhkLm'
const Youtube = require("youtube-api"), fs = require("fs"),
readJson = require("r-json"), Lien = require("lien"),
Logger = require("bug-killer"), opn = require("opn"),
prettyBytes = require("pretty-bytes");
const CREDENTIALS = readJson(`${__dirname}/credentials.json`);
let server = new Lien({ host: "localhost" , port: 5000 });
let oauth = Youtube.authenticate({
type: "oauth", client_id: CREDENTIALS.web.client_id,
client_secret: CREDENTIALS.web.client_secret,
redirect_url: CREDENTIALS.web.redirect_uris[0]
});
opn(oauth.generateAuthUrl({
access_type: "offline", scope: ["https://www.googleapis.com/auth/youtube"]
}));
server.addPage("/oauth2callback", lien => {
Logger.log("code: " + lien.query.code);
oauth.getToken(lien.query.code, (err, tokens) => {
if (err) { lien.lien(err, 400); return Logger.log(err); }
Logger.log("Got the tokens.");
oauth.setCredentials(tokens);
lien.end(
'<script>window.close()</script>'
);
var req = Youtube.videos.list({
id: 'fEJc2CwddgU',
part: "status",
},
(err, data) => {
if (err) {
console.log('err:', err)
process.exit()
}
var s = data.items[0].status.uploadStatus
if (s == 'uploaded') console.log('Video is being processed.')
if (s == 'processed') console.log('Video processing completed.')
process.exit()
});
});
});
Related
So, I have those "cards" to which are attached files.
I want to be able to display the content of these files (when possible; I do not expect to show binary files obviously, but text, pdf, images,...) to the user.
Upon a longPress on an attachment, the openAttachment() function is be called. That function downloads the file from the server if necessary and then (tries to) open it:
// Opens an attachment
const openAttachment = async (attachment) => {
try {
// Download file if not already done
const fileInfo = await FileSystem.getInfoAsync(FileSystem.cacheDirectory + attachment.name)
let uri
if (!fileInfo.exists) {
console.log('Downloading attachment')
resp = await FileSystem.downloadAsync(
server.value + `/index.php/apps/deck/api/v1.0/boards/${route.params.boardId}/stacks/${route.params.stackId}/cards/${route.params.cardId}/attachments/${attachment.id}`,
FileSystem.cacheDirectory + attachment.name,
{
headers: {
'Authorization': token.value
},
},
)
console.log(resp)
uri = await FileSystem.getContentUriAsync(resp.uri)
} else {
console.log('File already in cache')
uri = await FileSystem.getContentUriAsync(fileInfo.uri)
}
console.log('Opening file ' + uri)
Sharing.shareAsync(uri);
} catch {
Toast.show({
type: 'error',
text1: i18n.t('error'),
text2: error.message,
})
console.log(error)
}
}
The issue always arrise at the Sharing.shareAsync(uri); line: Whatever I put there, it fails:
Sharing.shareAsync(uri) does not seem to be supported on my platform: https://docs.expo.dev/versions/latest/sdk/sharing/
Linking.openURL(uri) does not support the file:// scheme (the uri is in the form file:///var/mobile/Containers/Data/Application/5C1CB402-5ED1-4E17-B907-46111AE3FB7C/Library/Caches/test.pdf)
await WebBrowser.openBrowserAsync(uri) (from expo-web-browser) does not seem to be able to open local files
How am I supposed to do to display those files? Anyone has an idea?
Cyrille
I found a solution using react-native-file-viewer
// Opens an attachment
const openAttachment = async (attachment) => {
try {
// Download file if not already done
const fileInfo = await FileSystem.getInfoAsync(FileSystem.cacheDirectory + "attachment.name")
let uri
if (!fileInfo.exists) {
console.log('Downloading attachment')
const resp = await FileSystem.downloadAsync(
server.value + `/index.php/apps/deck/api/v1.0/boards/${route.params.boardId}/stacks/${route.params.stackId}/cards/${route.params.cardId}/attachments/${attachment.id}`,
FileSystem.cacheDirectory + attachment.name,
{
headers: {
'Authorization': token.value
},
},
)
console.log(resp)
uri = await FileSystem.getContentUriAsync(resp.uri)
} else {
console.log('File already in cache')
uri = await FileSystem.getContentUriAsync(fileInfo.uri)
}
console.log('opening file', uri)
FileViewer.open(uri)
} catch(error) {
Toast.show({
type: 'error',
text1: i18n.t('error'),
text2: error.message,
})
console.log(error)
}
}
Due to the nature of my project I have a image dataURL (NOT an actual image file) that I am trying to upload to IPFS via Pinata SDK. I have converted the image dataURL into a buffer(array) and tried 2 different methods but none of them works. Here is my code:
SAMPLE 1
var myBlob = new Blob([new Uint8Array(myBuffer)]);
var myReadableStream = myBlob.stream()
pinata.pinFileToIPFS(myReadableStream)
ERROR: Unhandled Rejection (TypeError): source.on is not a function
SAMPLE 2
var myBlob = new Blob([new Uint8Array(myBuffer)]);
var myHeaders = new Headers();
myHeaders.append("pinata_api_key", "MY_KEY");
myHeaders.append("pinata_secret_api_key", "MY_SECRET_KEY");
var formdata = new FormData();
formdata.append("test", myBlob);
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: formdata,
redirect: 'follow'
};
fetch("https://api.pinata.cloud/pinning/pinFileToIPFS", requestOptions)
.then(response => response.text())
.then(result => console.log('result',result))
.catch(error => console.log('error', error));
ERROR: 400 Bad Request, result {"error":"Unexpected field"}
with buffers things can be a little tricky. You'll need to format your request in a slightly different way.
I would take a look at this code snippet for an example of how somebody got this to work:
const pinataSDK = require("#pinata/sdk");
const pinata = pinataSDK(
"Pinata API Key",
"Pinata API Secret"
);
const { fs, vol } = require("memfs");
(async () => {
try {
const base64 = "base64 file string";
const buf = Buffer.from(base64, "base64");
memfs.writeFileSync("File Name", buf);
const read = vol.createReadStream("File Name");
const res = await pinata.pinFileToIPFS(read);
console.log(res);
} catch (error) {
console.log(error);
}
})();
I would like to use the AWS iOS SDK to upload an image directly from a public url to a S3 bucket.
My goal is to avoid downloading then uploading the image, which would be obviously slower.
I tried to naively pass the URL to AWSS3TransferUtility.uploadFile like so :
import AWSCore
import AWSS3
// ...
AWSS3TransferUtility.register(
with: AWSServiceManager.default().defaultServiceConfiguration!,
transferUtilityConfiguration: AWSS3TransferUtilityConfiguration(),
forKey: "foo"
)
let utility = AWSS3TransferUtility.s3TransferUtility(forKey: "foo")!
let imageURL = URL(string: "https://via.placeholder.com/150/")!
utility
.uploadFile(
imageURL,
bucket: "<bucket name>",
key: "bar.jpeg",
contentType: "image/jpg",
expression: nil,
completionHandler: nil)
.continueWith {
if let error = $0.error {
print("Error: \(error.localizedDescription)")
}
return nil
}
But it seems to accept only url of local files and return the error
The operation couldn’t be completed. (com.amazonaws.AWSS3TransferUtilityErrorDomain error 4.)
Any idea if this is possible and how ?
Based on #jarmod idead I was able to do it using a Lambda (simplified here) :
const https = require('https');
const AWS = require('aws-sdk');
const s3 = new AWS.S3();
function loadImage(url) {
return new Promise((resolve, reject) => {
https.get(url, (resp) => {
if(resp.statusCode > 200) {
reject('The image could not be loaded (HTTP ' + resp.statusCode + ')');
}
resp.setEncoding('binary');
let chunks = [];
resp.on('data', (chunk) => {
chunks.push(Buffer.from(chunk, 'binary'));
});
resp.on('end', () => {
let binary = Buffer.concat(chunks);
resolve(binary);
});
}).on("error", (err) => {
console.log("Error: " + err.message);
reject(err);
});
});
}
exports.handler = async (event, context, callback) => {
try {
const url = ...;
const bucket = ...;
const key = ...;
const buffer = await loadImage(url);
const destparams = {
Bucket: bucket,
Key: key,
Body: buffer,
ContentType: "image"
};
await s3.putObject(destparams).promise();
return "OK";
} catch(error) {
return error;
}
};
Calling a Lambda using AWSLambda is quite simple :
let lambda = AWSLambdaInvocationRequest()!
lambda.functionName = // Lambda name
lambda.invocationType = .requestResponse
let parameters: [String: Any] = [:] // Parameters to pass to the lambda
let payload = try! JSONSerialization.data(withJSONObject: parameters, options: [])
lambda.payload = payload
AWSLambda.default().invoke(lambda) { response, error in
//...
}
Currently I am able to record user input, pass the recording URL to the needed function, and download the audio file locally. What I am trying to do with the audio file is either get a buffer of it to send to Lex or convert it to the format Lex needs.
Per AWS Documentation the following values are accepted for the input stream param value:
var params = {
botAlias: 'STRING_VALUE', /* required */
botName: 'STRING_VALUE', /* required */
contentType: 'STRING_VALUE', /* required */
inputStream: new Buffer('...') || 'STRING_VALUE' || streamObject, /*required */
userId: 'STRING_VALUE', /* required */
accept: 'STRING_VALUE',
requestAttributes: any /* This value will be JSON encoded on your behalf with JSON.stringify() */,
sessionAttributes: any /* This value will be JSON encoded on your behalf with JSON.stringify() */
};
lexruntime.postContent(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
Per the twilio documentation it looks like the audio file is pretty flexible...
A request to the RecordingUrl will return a recording in binary WAV audio format by default. To request the recording in MP3 format, append ".mp3" to the RecordingUrl.
What do I need to do to get the twilio recorded audio in the right format for Lex? Is it just a matter of building the correct Lex param set or do I need to do some audio conversion before hand? I am writing this application in node js if that helps and I can add more code if it will help.
I was able to figure this out by downloading the file from Twilio as a PCM and changing my parameters a bit. Also, due to the way that Twilio handles the record verb, I needed to transfer the call to a hold state while waiting for the recordingStatusCallback to POST out. I also send a text to the caller with the final status from Lex.
The code I used to download the file:
app.post('/processRecording', (request, response) => {
var https = require('https');
var fs = require('fs');
let callSID = request.body.CallSid;
let url = request.body.RecordingUrl;
var saveFile = new Promise(function(resolve, reject) {
let fileName = callSID+ ".pcm";
var file = fs.createWriteStream(fileName);
var request = https.get(url, function(response) {
response.pipe(file);
resolve();
});
});
});
const accountSid = 'YOUR ACCOUNT SID';
const authToken = 'YOUR AUTH TOKEN';
const client = require('twilio')(accountSid, authToken);
//Once the file is downloaded, I then fetch the call from the hold state using this code:
saveFile.then(function(){
client.calls(callSID)
.update({method: 'POST', url: '/updateCall'})
.then(call => console.log(call.to))
.done();
});
And my updateCall endpoint looks like this:
app.post('/updateCall', (request, response) => {
let lexruntime = new AWS.LexRuntime();
let recordedFileName = request.body.CallSid + '.pcm';
let toNumber = request.body.To;
let fromNumber = request.body.From;
let twiml = new Twilio.twiml.VoiceResponse();
let lexFileStream = fs.createReadStream(recordedFileName);
let sid = request.body.CallSid;
var params = {
botAlias: 'prod', /* required */
botName: 'OrderFlowers', /* required */
contentType: 'audio/lpcm; sample-rate=8000; sample-size-bits=16; channel-count=1; is-big-endian=false',
accept: 'text/plain; charset=utf-8',
userId: sid /* required */
};
params.inputStream = lexFileStream;
lexruntime.postContent(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
if (data.dialogState == "ElicitSlot" || data.dialogState == "ConfirmIntent" || data.dialogState == "ElicitIntent" ){
twiml.say(data.message);
twiml.redirect({
method: 'POST'
}, '/recordVoice');
response.type('text/xml');
response.send(twiml.toString());
}
else if (data.dialogState == "Fulfilled" ){
twiml.say(data.message);
response.type('text/xml');
response.send(twiml.toString());
client.messages.create({
to: toNumber,
from: fromNumber,
body: data.message
}).then(msg => {
}).catch(err => console.log(err));
}
else{
twiml.say(data.message);
response.type('text/xml');
response.send(twiml.toString());
}
});
});
The recordVoice endpoint is actually a Twilio Serverless Function But I think this is what it would look like as an express endpoint:
app.post('/recordVoice', (request, response) => {
let twiml = new Twilio.twiml.VoiceResponse();
twiml.record({
action: '/deadAir',
recordingStatusCallback: '/processRecording',
trim: true,
maxLength: 10,
finishOnKey: '*'
});
twiml.say('I did not receive a recording');
response.type('text/xml');
response.send(twiml.toString());
});
The /deadAir endpoint is also a Twilio Serverless Function but this is what it would look like:
app.post('/deadAir', (request, response) => {
let twiml = new Twilio.twiml.VoiceResponse();
twiml.pause({
length: 60
});
response.type('text/xml');
response.send(twiml.toString());
});
Every time I make a call to acquireToken, it keeps launching the AAD login window and prompts me for a username/password, even though I've already authenticated successfully and consumed an access token to make API calls.
Here is my code
Step 1. Call the loadData function from controller
loadData = (): Rx.IPromise<Array<UserResult>> => {
var url = this.xxxApiUrl;
return Http.get<Array<UserResult>>(this._$http, url);
};
Step -2
export function get<TResult>(http: ng.IHttpService, url: string,
ignoreLoadingBar: boolean = false, retryCount = 0): Rx.IPromise<TResult> {
var req: any = {};
if (ignoreLoadingBar) {
req.ignoreLoadingBar = ignoreLoadingBar;
}
let resObservable = Rx.Observable.create(subscriber => {
acquireToken(url, (message, token) => {
req.headers.Authorization = `Bearer ${token}`;
http.get(url, req)
.then(res => {
subscriber.onNext(res.data);
subscriber.onCompleted();
}, (err) => { alert(JSON.stringify(err)); });
});
});
return resObservable.toPromise();
}
function acquireToken(apiUrl: string, callback) {
let innerCallback = (res) => callback('', res.accessToken);
let xConfig= JSON.parse(<any>sessionStorage.getItem('xConfig'));
window.AuthenticationContext = new
window.Microsoft.ADAL.AuthenticationContext
(xConfig.common.azure.authorityTenant);
window.AuthenticationContext.tokenCache.readItems().then(items => {
if (items.length > 0) {
let authority = items[0].authority;
window.AuthenticationContext = new
window.Microsoft.ADAL.AuthenticationContext(authority);
}
let resourceUri = getResourceUri(xConfig, apiUrl);
window.AuthenticationContext.acquireTokenSilentAsync(resourceUri,
xConfig.common.azure.clientId, xConfig.common.azure.redirectUri)
.then(innerCallback, (err) => {
window.AuthenticationContext.acquireTokenAsync(resourceUri,
xConfig.common.azure.clientId, xConfig.common.azure.redirectUri)
.then(innerCallback);
});
});
}
Looking at your code, it looks like that you are using acquireTokenSilentAsync using the common endpoint, this is not supported. Please make sure to use your tenant Id or name (like tenant.onmicrosoft.com) instead of common when using acquireTokenSilentAsync
For more information about the common endpoint please see here