带有Http / 2的Node.js中的Amazon Transcribe流服务请求未响应

时间:2019-03-09 01:06:06

标签: node.js amazon-web-services aws-transcribe

我正在尝试将Amazon Transcribe流服务与Node.js的http2请求一起使用,这是我关注的文档链接 Streaming request format。根据该文档,端点为https://transcribe-streaming。<'region'>。amazonaws.com,但是对此URL进行请求将导致URL找不到错误。 但是在Java Example找到的端点为https://transcribestreaming。''。amazonaws.com中,因此对此URL发出请求不会产生任何错误或响应。我正在从us-east-1地区尝试。

这是我尝试使用的代码。

const http2 = require('http2');
var aws4  = require('aws4');

var opts = {
  service: 'transcribe', 
  region: 'us-east-1', 
  path: '/stream-transcription', 
  headers:{
   'content-type': 'application/json',
   'x-amz-target': 'com.amazonaws.transcribe.Transcribe.StartStreamTranscription'
  }
}

var urlObj = aws4.sign(opts, {accessKeyId: '<access key>', secretAccessKey: '<aws secret>'});
const client = http2.connect('https://transcribestreaming.<region>.amazonaws.com');
client.on('error', function(err){
  console.error("error in request ",err);
});

const req = client.request({
  ':method': 'POST',
  ':path': '/stream-transcription',
  'authorization': urlObj.headers.Authorization,  
  'content-type': 'application/json',
  'x-amz-content-sha256': 'STREAMING-AWS4-HMAC-SHA256-EVENTS',
  'x-amz-target': 'com.amazonaws.transcribe.Transcribe.StartStreamTranscription',
  'x-amz-date': urlObj['headers']['X-Amz-Date'],
  'x-amz-transcribe-language-code': 'en-US',
  'x-amz-transcribe-media-encoding': 'pcm',
  'x-amz-transcribe-sample-rate': 44100
});

req.on('response', (headers, flags) => {
  for (const name in headers) {
    console.log(`${name}: ${headers[name]}`);
  }
});
let data = '';
req.on('data', (chunk) => { data += chunk; });
req.on('end', () => {
  console.log(`\n${data}`);
  client.close();
});
req.end();

谁能指出我在这里所缺少的。我也找不到任何使用HTTP / 2实现此功能的示例。

更新:   将Content-type更改为application / json的响应状态为200,但有以下异常:

`{"Output":{"__type":"com.amazon.coral.service#SerializationException"},"Version":"1.0"}`

更新(2019年4月22日):

req.setEncoding('utf8');
req.write(audioBlob);

var audioBlob = new Buffer(JSON.stringify({
    "AudioStream": { 
       "AudioEvent": { 
          "AudioChunk": audioBufferData
     }
 }

在结束请求之前,我要通过序列化添加“ audioblod”作为有效载荷。我的“ audioBufferData”是来自浏览器的原始PCM音频格式。 我从documentation看,有效载荷必须被编码为“事件流编码”,但是无法弄清楚如何实现它。

因此,在没有此事件流编码的情况下,我得到了以下具有200个响应状态的异常。

{"Output":{"__type":"com.amazon.coral.service#UnknownOperationException"},"Version":"1.0"}

4 个答案:

答案 0 :(得分:0)

我联系了AWS支持,他们为此提供了支持。我已经在本地进行了测试,它似乎可以正常工作

var access_key = process.env.AWS_ACCESS_KEY_ID;
var secret_key = process.env.AWS_SECRET_ACCESS_KEY;

// I changed the region according my account, you can ignore it.
var region = 'eu-west-1';
var url = 'https://transcribestreaming.' + region + '.amazonaws.com';

var myService = 'transcribe';
var myMethod = 'POST';
var myPath = '/stream-transcription';
var query = ''
var crypto = require('crypto-js');
var http2 = require('http2');
var fs = require('fs')

const trimAll = (val) => {
  return val.trim().replace(/\s+/g, ' ');
}

// this function gets the Signature Key, see AWS documentation(https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html)  for more details
const getSignatureKey = (Crypto, key, dateStamp, regionName, serviceName) => {
  var kDate = Crypto.HmacSHA256(dateStamp, "AWS4" + key);
  var kRegion = Crypto.HmacSHA256(regionName, kDate);
  var kService = Crypto.HmacSHA256(serviceName, kRegion);
  var kSigning = Crypto.HmacSHA256("aws4_request", kService);
  return kSigning;
}

// this function converts the generic JS ISO8601 date format to the specific format the AWS API wants
const getAmzDate = (dateStr) => {
  var chars = [":", "-"];
  for (var i = 0; i < chars.length; i++) {
    while (dateStr.indexOf(chars[i]) != -1) {
      dateStr = dateStr.replace(chars[i], "");
    }
  }
  dateStr = dateStr.split(".")[0] + "Z";
  return dateStr;
}

const getAuthHeaders = () => {
  // get the various date formats needed to form our request
  var amzDate = getAmzDate(new Date().toISOString());
  var authDate = amzDate.split("T")[0];
  const buf = fs.readFileSync('./test.mp3')
  let arraybuffer = Uint8Array.from(buf).buffer;

  // Payload needs to be managed on client side
  var payload = `{
         "AudioStream": {
         "AudioEvent": {
                 "AudioChunk": ${arraybuffer}
             }
         }
    }`;
  // get the SHA256 hash value for payload
  //var payload = '';
  //var hashedPayload = crypto.SHA256(payload).toString();
  var cannonicalHeaders = '';
  var signedHeaders = [];

  var headers = {
    ':authority': url,
    ':method': myMethod,
    ':path': myPath,
    'content-type': 'application/vnd.amazon.eventstream',
    //'transfer-encoding': 'chunked',
    'x-amz-content-sha256': 'STREAMING-AWS4-HMAC-SHA256-EVENTS',
    'x-amz-date': amzDate,
    //'content-type':'application/json',
    //'x-amz-security-token': session_token,
    'x-amzn-target': 'com.amazonaws.transcribe.Transcribe.StartStreamTranscription',
    'x-amzn-transcribe-language-code': 'en-US',
    'x-amzn-transcribe-media-encoding': 'pcm',
    'x-amzn-transcribe-sample-rate': '8000'

  }


  Object.keys(headers).sort().forEach((key) => {
    header_key = key.toLowerCase();
    if (header_key == ':authority') {
      header_key = 'host';
    }
    if (header_key == 'x-amz-content-sha256' || header_key == 'x-amz-date' || header_key == 'host') {
      cannonicalHeaders += header_key + ':' + trimAll(headers[key]) + '\n';
      signedHeaders.push(header_key);
    }
  });
  signedHeaders = signedHeaders.join(';');

  var cannonicalReq = myMethod + '\n'
    + myPath + '\n'
    + query + '\n'
    + cannonicalHeaders + '\n'
    + signedHeaders + '\n'
    + 'STREAMING-AWS4-HMAC-SHA256-EVENTS';

  console.log('\n=== cannonicalReq ===');
  console.log(cannonicalReq);

  // This is what the Canonical request should look like, you can get it from the 403 error message

  //    cannonicalReq = `POST
  ///stream-transcription
  //
  //host:https://transcribestreaming.eu-west-1.amazonaws.com
  //x-amz-content-sha256:STREAMING-AWS4-HMAC-SHA256-EVENTS
  //x-amz-date:${amzDate}
  //
  //host;x-amz-content-sha256;x-amz-date
  //STREAMING-AWS4-HMAC-SHA256-EVENTS`

  // hash the canonical request
  var canonicalReqHash = crypto.SHA256(cannonicalReq).toString();
  var stringToSign = 'AWS4-HMAC-SHA256\n'
    + amzDate + '\n'
    + authDate + '/' + region + '/transcribe/aws4_request\n'
    + canonicalReqHash;

  console.log('\n=== StringToSign ===');
  console.log(stringToSign);

  // get our Signing Key
  var signingKey = getSignatureKey(crypto, secret_key, authDate, region, myService);

  // Sign our String-to-Sign with our Signing Key
  var authKey = crypto.HmacSHA256(stringToSign, signingKey);

  // Form our authorization header
  var authString = 'AWS4-HMAC-SHA256 ' +
    'Credential=' +
    access_key + '/' +
    authDate + '/' +
    region + '/' +
    myService + '/aws4_request,' +
    'SignedHeaders=host;x-amz-content-sha256;x-amz-date,' +
    'Signature=' + authKey;

  console.log('\n=== authorization in headers ===');
  console.log(authString);

  headers['authorization'] = authString;
  console.log('\n=== headers ===');
  console.log('HEADERS:::::::');
  console.log(headers);
  return {
    headers,
    payload
  };

}


try {

  const client = http2.connect(url);
  const { headers, payload } = getAuthHeaders();
  var data;
  client.on('error', (err) => console.error(err))
  const req = client.request(headers);
  console.log('Main Requestt');
  console.log(req);
  req.on('response', (headers, flags) => {
    // may check and play with the http/2 response headers, and flags
    console.dir(headers);


  });a
  req.setEncoding('utf8');
  console.log(req)
  req.on('data', (chunk) => {
    // do something with the data
    data += new Buffer.from(chunk).toString('ascii');
    console.log(new Buffer.from(chunk).toString('ascii'));
  });
  req.on('end', () => {
    client.close();
  });
  req.write(payload);
  req.end();

} catch (error) {
  console.error(error);
}

答案 1 :(得分:0)

这不能直接回答问题,但我认为将其作为回答而不是评论是足够有用的。

AWS just announced WebSocket对Amazon Transcribe的支持。 Here are the docs,这里是client-side sample app。我认为最大的区别是使您无需像http / 2那样对每个音频块进行签名,这使我与WebSockets集成更加简单。

用于使用预签名URL授权和启动连接的相关代码在lib/aws-signature-v4.js

exports.createPresignedURL = function(method, host, path, service, payload, options) {
  options = options || {};
  options.key = options.key || process.env.AWS_ACCESS_KEY_ID;
  options.secret = options.secret || process.env.AWS_SECRET_ACCESS_KEY;
  options.protocol = options.protocol || 'https';
  options.headers = options.headers || {};
  options.timestamp = options.timestamp || Date.now();
  options.region = options.region || process.env.AWS_REGION || 'us-east-1';
  options.expires = options.expires || 86400; // 24 hours
  options.headers = options.headers || {};

  // host is required
  options.headers.Host = host;

  var query = options.query ? querystring.parse(options.query) : {};
  query['X-Amz-Algorithm'] = 'AWS4-HMAC-SHA256';
  query['X-Amz-Credential'] = options.key + '/' + exports.createCredentialScope(options.timestamp, options.region, service);
  query['X-Amz-Date'] = toTime(options.timestamp);
  query['X-Amz-Expires'] = options.expires;
  query['X-Amz-SignedHeaders'] = exports.createSignedHeaders(options.headers);

  var canonicalRequest = exports.createCanonicalRequest(method, path, query, options.headers, payload);
  var stringToSign = exports.createStringToSign(options.timestamp, options.region, service, canonicalRequest);
  var signature = exports.createSignature(options.secret, options.timestamp, options.region, service, stringToSign);
  query['X-Amz-Signature'] = signature;
  return options.protocol + '://' + host + path + '?' + querystring.stringify(query);
};

然后我们在lib/main.js中调用它:

function createPresignedUrl() {
    let endpoint = "transcribestreaming." + region + ".amazonaws.com:8443";

    // get a preauthenticated URL that we can use to establish our WebSocket
    return v4.createPresignedURL(
        'GET',
        endpoint,
        '/stream-transcription-websocket',
        'transcribe',
        crypto.createHash('sha256').update('', 'utf8').digest('hex'), {
            'key': $('#access_id').val(),
            'secret': $('#secret_key').val(),
            'protocol': 'wss',
            'expires': 15,
            'region': region,
            'query': "language-code=" + languageCode + "&media-encoding=pcm&sample-rate=" + sampleRate
        }
    );
}

要以事件流消息格式打包内容,我们wrap the PCM-encoded audio in a JSON envelope and convert it to binary

function convertAudioToBinaryMessage(audioChunk) {
    let raw = mic.toRaw(audioChunk);

    if (raw == null)
        return;

    // downsample and convert the raw audio bytes to PCM
    let downsampledBuffer = audioUtils.downsampleBuffer(raw, sampleRate);
    let pcmEncodedBuffer = audioUtils.pcmEncode(downsampledBuffer);

    // add the right JSON headers and structure to the message
    let audioEventMessage = getAudioEventMessage(Buffer.from(pcmEncodedBuffer));

    //convert the JSON object + headers into a binary event stream message
    let binary = eventStreamMarshaller.marshall(audioEventMessage);

    return binary;
}

function getAudioEventMessage(buffer) {
    // wrap the audio data in a JSON envelope
    return {
        headers: {
            ':message-type': {
                type: 'string',
                value: 'event'
            },
            ':event-type': {
                type: 'string',
                value: 'AudioEvent'
            }
        },
        body: buffer
    };
}

答案 2 :(得分:0)

对于在节点js中将AWS transcribe服务与WebSocket API结合使用,我有类似的要求。鉴于目前官方包中尚无此支持,因此我继续在on github的此实现之后编写了一个包。它称为AWS转录,可以在here中找到。希望对您有所帮助。

它提供了围绕WebSocket的流接口,并且可以像下面的示例一样使用

import { AwsTranscribe, StreamingClient } from "aws-transcribe"

const client = new AwsTranscribe({
    // if these aren't provided, they will be taken from the environment
    accessKeyId: "ACCESS KEY HERE",
    secretAccessKey: "SECRET KEY HERE",
})

const transcribeStream = client
    .createStreamingClient({
        region: "eu-west-1",
        sampleRate,
        languageCode: "en-US",
    })
    // enums for returning the event names which the stream will emit
    .on(StreamingClient.EVENTS.OPEN, () => console.log(`transcribe connection opened`))
    .on(StreamingClient.EVENTS.ERROR, console.error)
    .on(StreamingClient.EVENTS.CLOSE, () => console.log(`transcribe connection closed`))
    .on(StreamingClient.EVENTS.DATA, (data) => {
        const results = data.Transcript.Results

        if (!results || results.length === 0) {
            return
        }

        const result = results[0]
        const final = !result.IsPartial
        const prefix = final ? "recognized" : "recognizing"
        const text = result.Alternatives[0].Transcript
        console.log(`${prefix} text: ${text}`)
    })

someStream.pipe(transcribeStream)

答案 3 :(得分:0)