亚马逊转录没有SDK的Streaming API

问题描述 投票:11回答:2

我正在尝试使用Go 1.11中亚马逊的新流式转录API。目前亚马逊只提供Java SDK,所以我尝试的是低级方式。

唯一相关的文档是here,但它没有显示端点。我在Java example发现它是https://transcribestreaming.<region>.amazonaws.com,我正在尝试爱尔兰地区,即https://transcribestreaming.eu-west-1.amazonaws.com。这是我打开HTTP / 2双向流的代码:

import (
    "crypto/tls"
    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/aws/external"
    "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
    "golang.org/x/net/http2"
    "io"
    "io/ioutil"
    "log"
    "net/http"
    "os"
    "time"
)

const (
    HeaderKeyLanguageCode   = "x-amzn-transcribe-language-code"  // en-US
    HeaderKeyMediaEncoding  = "x-amzn-transcribe-media-encoding" // pcm only
    HeaderKeySampleRate     = "x-amzn-transcribe-sample-rate"    // 8000, 16000 ... 48000
    HeaderKeySessionId      = "x-amzn-transcribe-session-id"     // For retrying a session. Pattern: [a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}
    HeaderKeyVocabularyName = "x-amzn-transcribe-vocabulary-name"
    HeaderKeyRequestId = "x-amzn-request-id"
)

...

region := "eu-west-1"

cfg, err := external.LoadDefaultAWSConfig(aws.Config{
    Region: region,
})
if err != nil {
    log.Printf("could not load default AWS config: %v", err)
    return
}

signer := v4.NewSigner(cfg.Credentials)

transport := &http2.Transport{
    TLSClientConfig: &tls.Config{
        // allow insecure just for debugging
        InsecureSkipVerify: true,
    },
}
client := &http.Client{
    Transport: transport,
}

signTime := time.Now()

header := http.Header{}
header.Set(HeaderKeyLanguageCode, "en-US")
header.Set(HeaderKeyMediaEncoding, "pcm")
header.Set(HeaderKeySampleRate, "16000")
header.Set("Content-type", "application/json")

// Bi-directional streaming via a pipe.
pr, pw := io.Pipe()

req, err := http.NewRequest(http.MethodPost, "https://transcribestreaming.eu-west-1.amazonaws.com/stream-transcription", ioutil.NopCloser(pr))
if err != nil {
    log.Printf("err: %+v", err)
    return
}
req.Header = header

_, err = signer.Sign(req, nil, "transcribe", region, signTime)
if err != nil {
    log.Printf("problem signing headers: %+v", err)
    return
}

// This freezes and ends after 5 minutes with "unexpected EOF".
res, err := client.Do(req)
...

问题是执行请求(client.Do(req))冻结五分钟然后以“意外的EOF”错误结束。

我有什么想法我做错了吗?有人在没有Java SDK的情况下成功使用了新的流式转录API吗?

编辑(2019年3月11日):

我再次测试了这个,现在它没有超时,但立即返回200 OK响应。但是响应主体中有一个“例外”:{"Output":{"__type":"com.amazon.coral.service#SerializationException"},"Version":"1.0"}

我尝试使用io.Pipe(如上面的代码)打开HTTP2流,并使用文档中描述的JSON主体:

{
    "AudioStream": { 
        "AudioEvent": { 
            "AudioChunk": ""
        }
    }
}

结果是一样的。

编辑(2019年3月13日):

正如@gpeng所提到的,从标题中删除content-type将修复SerializationException。但是有一个IAM异常,需要将transcription:StartStreamTranscription权限添加到您的IAM用户。这虽然在AWS IAM控制台中没有任何地方,但必须手动添加为自定义JSON权限:/

还有一个新的/另一个文档文档here显示不正确的host和一个新的content-type(不要使用content-type,请求将返回404)。

删除content-type,并添加新的权限后,现在我得到一个例外{"Message":"A complete signal was sent without the preceding empty frame."}。还要永远写入管道块,所以我又被卡住了。新文档中描述的消息与旧文档中的消息不同,现在最终是二进制的,但我不理解它们。有关如何在Go中发送此类HTTP2消息的任何想法?

编辑(第15场,2019年):*

如果您收到有关签名不匹配的HTTP 403错误,请不要设置transfer-encodingx-amz-content-sha256 HTTP标头。当我设置它们时,使用AWS SDK的V4签名者签署请求,然后我收到HTTP 403 The request signature we calculated does not match the signature you provided.

amazon-web-services go aws-sdk aws-transcribe
2个回答
1
投票

尝试不设置内容类型标题,看看你得到了什么回应。我正在尝试做同样的事情(但在Ruby中)并且'修复'了SerializationException。仍然无法让它工作,但我现在有一个新的错误想想:)

更新:我现在已经开始工作了。我的问题在于签名。如果传递了hostauthority标题,则在检查签名时,它们将与,连接并在服务器端被视为host,因此签名永远不会匹配。这似乎不是AWS方面的正确行为,但它看起来不像是Go中的问题。


0
投票

我还在和Node.js打架。不明确的文件是in one place它说Content-Type不应该是application/json,但in some other place,它使它看起来像有效载荷应编码为application/vnd.amazon.eventstream。看起来有效负载应该以二进制格式而不是JSON对象仔细格式化,如下所示:

Amazon Transcribe使用称为事件流编码的格式进行流式转录。此格式编码二进制数据,其头信息描述每个事件的内容。您可以将此信息用于不使用Amazon Transcribe SDK调用Amazon Transcribe终结点的应用程序。 Amazon Transcribe使用HTTP / 2协议进行流式转录。流式传输请求的关键组件是:

  • 标题框架。这包含请求的HTTP标头,以及Amazon Transcribe用作签名以跟踪数据帧的种子签名的授权标头中的签名。
  • 事件流编码中的一个或消息帧。该帧包含元数据和原始音频字节。
  • 结束框架。这是具有空主体的事件流编码中的签名消息。

有一个示例函数,显示如何implement all of that using Java可能会如何完成这种编码。


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);
}

我知道这是NodeJS,但我发布这个作为答案(直到更好的一个出现)为了可见性,因为片段得到很好的评论,并且可能是最佳实践,因为它是由AWS编写的

© www.soinside.com 2019 - 2024. All rights reserved.