From 14ebf7c135f0ce302def7a859986445d212ac98b Mon Sep 17 00:00:00 2001 From: Sebastian Villena <97059974+ruisebas@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:53:05 -0500 Subject: [PATCH 1/3] feat(TranscribeStreaming): Adding support for missing languages. --- .../AWSTranscribeStreamingModel.h | 49 +- .../AWSTranscribeStreamingModel.m | 483 ++++++++++++++++-- .../AWSTranscribeStreamingResources.m | 48 +- .../AWSTranscribeStreamingSwiftTests.swift | 321 ++++-------- AWSTranscribeStreamingTests/hola_mundo.wav | Bin 0 -> 74958 bytes CHANGELOG.md | 5 +- 6 files changed, 629 insertions(+), 277 deletions(-) create mode 100644 AWSTranscribeStreamingTests/hola_mundo.wav diff --git a/AWSTranscribeStreaming/AWSTranscribeStreamingModel.h b/AWSTranscribeStreaming/AWSTranscribeStreamingModel.h index 7ceb19c7743..cbcdc2465c4 100644 --- a/AWSTranscribeStreaming/AWSTranscribeStreamingModel.h +++ b/AWSTranscribeStreaming/AWSTranscribeStreamingModel.h @@ -1,5 +1,5 @@ // -// Copyright 2010-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// Copyright 2010-2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). // You may not use this file except in compliance with the License. @@ -50,8 +50,48 @@ typedef NS_ENUM(NSInteger, AWSTranscribeStreamingLanguageCode) { AWSTranscribeStreamingLanguageCodeJaJP, AWSTranscribeStreamingLanguageCodeKoKR, AWSTranscribeStreamingLanguageCodeZhCN, - AWSTranscribeStreamingLanguageCodeHiIN, AWSTranscribeStreamingLanguageCodeThTH, + AWSTranscribeStreamingLanguageCodeEsES, + AWSTranscribeStreamingLanguageCodeArSA, + AWSTranscribeStreamingLanguageCodePtPT, + AWSTranscribeStreamingLanguageCodeCaES, + AWSTranscribeStreamingLanguageCodeArAE, + AWSTranscribeStreamingLanguageCodeHiIN, + AWSTranscribeStreamingLanguageCodeZhHK, + AWSTranscribeStreamingLanguageCodeNlNL, + AWSTranscribeStreamingLanguageCodeNoNO, + AWSTranscribeStreamingLanguageCodeSvSE, + AWSTranscribeStreamingLanguageCodePlPL, + AWSTranscribeStreamingLanguageCodeFiFI, + AWSTranscribeStreamingLanguageCodeZhTW, + AWSTranscribeStreamingLanguageCodeEnIN, + AWSTranscribeStreamingLanguageCodeEnIE, + AWSTranscribeStreamingLanguageCodeEnNZ, + AWSTranscribeStreamingLanguageCodeEnAB, + AWSTranscribeStreamingLanguageCodeEnZA, + AWSTranscribeStreamingLanguageCodeEnWL, + AWSTranscribeStreamingLanguageCodeDeCH, + AWSTranscribeStreamingLanguageCodeAfZA, + AWSTranscribeStreamingLanguageCodeEuES, + AWSTranscribeStreamingLanguageCodeHrHR, + AWSTranscribeStreamingLanguageCodeCsCZ, + AWSTranscribeStreamingLanguageCodeDaDK, + AWSTranscribeStreamingLanguageCodeFaIR, + AWSTranscribeStreamingLanguageCodeGlES, + AWSTranscribeStreamingLanguageCodeElGR, + AWSTranscribeStreamingLanguageCodeHeIL, + AWSTranscribeStreamingLanguageCodeIdID, + AWSTranscribeStreamingLanguageCodeLvLV, + AWSTranscribeStreamingLanguageCodeMsMY, + AWSTranscribeStreamingLanguageCodeRoRO, + AWSTranscribeStreamingLanguageCodeRuRU, + AWSTranscribeStreamingLanguageCodeSrRS, + AWSTranscribeStreamingLanguageCodeSkSK, + AWSTranscribeStreamingLanguageCodeSoSO, + AWSTranscribeStreamingLanguageCodeTlPH, + AWSTranscribeStreamingLanguageCodeUkUA, + AWSTranscribeStreamingLanguageCodeViVN, + AWSTranscribeStreamingLanguageCodeZuZA, }; typedef NS_ENUM(NSInteger, AWSTranscribeStreamingMediaEncoding) { @@ -308,6 +348,11 @@ typedef NS_ENUM(NSInteger, AWSTranscribeStreamingMediaEncoding) { */ @property (nonatomic, strong) NSError* _Nullable limitExceededException; +/** +

The service is currently unavailable. Try your request later.

+ */ +@property (nonatomic, strong) NSError* _Nullable serviceUnavailableException; + /**

A portion of the transcription of the audio stream. Events are sent periodically from Amazon Transcribe to your application. The event can be a partial transcription of a section of the audio stream, or it can be the entire transcription of that portion of the audio stream.

*/ diff --git a/AWSTranscribeStreaming/AWSTranscribeStreamingModel.m b/AWSTranscribeStreaming/AWSTranscribeStreamingModel.m index d100b5ecf79..0e4a957d966 100644 --- a/AWSTranscribeStreaming/AWSTranscribeStreamingModel.m +++ b/AWSTranscribeStreaming/AWSTranscribeStreamingModel.m @@ -1,5 +1,5 @@ // -// Copyright 2010-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// Copyright 2010-2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). // You may not use this file except in compliance with the License. @@ -168,32 +168,152 @@ + (NSValueTransformer *)languageCodeJSONTransformer { return @(AWSTranscribeStreamingLanguageCodeFrFR); } if ([value caseInsensitiveCompare:@"en-AU"] == NSOrderedSame) { - return @(AWSTranscribeStreamingLanguageCodeEnAU); - } - if ([value caseInsensitiveCompare:@"it-IT"] == NSOrderedSame) { - return @(AWSTranscribeStreamingLanguageCodeItIT); - } - if ([value caseInsensitiveCompare:@"de-DE"] == NSOrderedSame) { - return @(AWSTranscribeStreamingLanguageCodeDeDE); - } - if ([value caseInsensitiveCompare:@"pt-BR"] == NSOrderedSame) { - return @(AWSTranscribeStreamingLanguageCodePtBR); - } - if ([value caseInsensitiveCompare:@"ja-JP"] == NSOrderedSame) { - return @(AWSTranscribeStreamingLanguageCodeJaJP); - } - if ([value caseInsensitiveCompare:@"ko-KR"] == NSOrderedSame) { - return @(AWSTranscribeStreamingLanguageCodeKoKR); - } - if ([value caseInsensitiveCompare:@"zh-CN"] == NSOrderedSame) { - return @(AWSTranscribeStreamingLanguageCodeZhCN); - } - if ([value caseInsensitiveCompare:@"hi-IN"] == NSOrderedSame) { - return @(AWSTranscribeStreamingLanguageCodeHiIN); - } - if ([value caseInsensitiveCompare:@"th-TH"] == NSOrderedSame) { - return @(AWSTranscribeStreamingLanguageCodeThTH); - } + return @(AWSTranscribeStreamingLanguageCodeEnAU); + } + if ([value caseInsensitiveCompare:@"it-IT"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeItIT); + } + if ([value caseInsensitiveCompare:@"de-DE"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeDeDE); + } + if ([value caseInsensitiveCompare:@"pt-BR"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodePtBR); + } + if ([value caseInsensitiveCompare:@"ja-JP"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeJaJP); + } + if ([value caseInsensitiveCompare:@"ko-KR"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeKoKR); + } + if ([value caseInsensitiveCompare:@"zh-CN"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeZhCN); + } + if ([value caseInsensitiveCompare:@"th-TH"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeThTH); + } + if ([value caseInsensitiveCompare:@"es-ES"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeEsES); + } + if ([value caseInsensitiveCompare:@"ar-SA"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeArSA); + } + if ([value caseInsensitiveCompare:@"pt-PT"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodePtPT); + } + if ([value caseInsensitiveCompare:@"ca-ES"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeCaES); + } + if ([value caseInsensitiveCompare:@"ar-AE"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeArAE); + } + if ([value caseInsensitiveCompare:@"hi-IN"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeHiIN); + } + if ([value caseInsensitiveCompare:@"zh-HK"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeZhHK); + } + if ([value caseInsensitiveCompare:@"nl-NL"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeNlNL); + } + if ([value caseInsensitiveCompare:@"no-NO"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeNoNO); + } + if ([value caseInsensitiveCompare:@"sv-SE"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeSvSE); + } + if ([value caseInsensitiveCompare:@"pl-PL"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodePlPL); + } + if ([value caseInsensitiveCompare:@"fi-FI"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeFiFI); + } + if ([value caseInsensitiveCompare:@"zh-TW"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeZhTW); + } + if ([value caseInsensitiveCompare:@"en-IN"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeEnIN); + } + if ([value caseInsensitiveCompare:@"en-IE"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeEnIE); + } + if ([value caseInsensitiveCompare:@"en-NZ"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeEnNZ); + } + if ([value caseInsensitiveCompare:@"en-AB"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeEnAB); + } + if ([value caseInsensitiveCompare:@"en-ZA"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeEnZA); + } + if ([value caseInsensitiveCompare:@"en-WL"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeEnWL); + } + if ([value caseInsensitiveCompare:@"de-CH"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeDeCH); + } + if ([value caseInsensitiveCompare:@"af-ZA"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeAfZA); + } + if ([value caseInsensitiveCompare:@"eu-ES"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeEuES); + } + if ([value caseInsensitiveCompare:@"hr-HR"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeHrHR); + } + if ([value caseInsensitiveCompare:@"cs-CZ"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeCsCZ); + } + if ([value caseInsensitiveCompare:@"da-DK"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeDaDK); + } + if ([value caseInsensitiveCompare:@"fa-IR"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeFaIR); + } + if ([value caseInsensitiveCompare:@"gl-ES"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeGlES); + } + if ([value caseInsensitiveCompare:@"el-GR"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeElGR); + } + if ([value caseInsensitiveCompare:@"he-IL"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeHeIL); + } + if ([value caseInsensitiveCompare:@"id-ID"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeIdID); + } + if ([value caseInsensitiveCompare:@"lv-LV"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeLvLV); + } + if ([value caseInsensitiveCompare:@"ms-MY"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeMsMY); + } + if ([value caseInsensitiveCompare:@"ro-RO"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeRoRO); + } + if ([value caseInsensitiveCompare:@"ru-RU"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeRuRU); + } + if ([value caseInsensitiveCompare:@"sr-RS"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeSrRS); + } + if ([value caseInsensitiveCompare:@"sk-SK"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeSkSK); + } + if ([value caseInsensitiveCompare:@"so-SO"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeSoSO); + } + if ([value caseInsensitiveCompare:@"tl-PH"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeTlPH); + } + if ([value caseInsensitiveCompare:@"uk-UA"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeUkUA); + } + if ([value caseInsensitiveCompare:@"vi-VN"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeViVN); + } + if ([value caseInsensitiveCompare:@"zu-ZA"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeZuZA); + } return @(AWSTranscribeStreamingLanguageCodeUnknown); } reverseBlock:^NSString *(NSNumber *value) { switch ([value integerValue]) { @@ -221,10 +341,90 @@ + (NSValueTransformer *)languageCodeJSONTransformer { return @"ko-KR"; case AWSTranscribeStreamingLanguageCodeZhCN: return @"zh-CN"; - case AWSTranscribeStreamingLanguageCodeHiIN: - return @"hi-IN"; case AWSTranscribeStreamingLanguageCodeThTH: return @"th-TH"; + case AWSTranscribeStreamingLanguageCodeEsES: + return @"es-ES"; + case AWSTranscribeStreamingLanguageCodeArSA: + return @"ar-SA"; + case AWSTranscribeStreamingLanguageCodePtPT: + return @"pt-PT"; + case AWSTranscribeStreamingLanguageCodeCaES: + return @"ca-ES"; + case AWSTranscribeStreamingLanguageCodeArAE: + return @"ar-AE"; + case AWSTranscribeStreamingLanguageCodeHiIN: + return @"hi-IN"; + case AWSTranscribeStreamingLanguageCodeZhHK: + return @"zh-HK"; + case AWSTranscribeStreamingLanguageCodeNlNL: + return @"nl-NL"; + case AWSTranscribeStreamingLanguageCodeNoNO: + return @"no-NO"; + case AWSTranscribeStreamingLanguageCodeSvSE: + return @"sv-SE"; + case AWSTranscribeStreamingLanguageCodePlPL: + return @"pl-PL"; + case AWSTranscribeStreamingLanguageCodeFiFI: + return @"fi-FI"; + case AWSTranscribeStreamingLanguageCodeZhTW: + return @"zh-TW"; + case AWSTranscribeStreamingLanguageCodeEnIN: + return @"en-IN"; + case AWSTranscribeStreamingLanguageCodeEnIE: + return @"en-IE"; + case AWSTranscribeStreamingLanguageCodeEnNZ: + return @"en-NZ"; + case AWSTranscribeStreamingLanguageCodeEnAB: + return @"en-AB"; + case AWSTranscribeStreamingLanguageCodeEnZA: + return @"en-ZA"; + case AWSTranscribeStreamingLanguageCodeEnWL: + return @"en-WL"; + case AWSTranscribeStreamingLanguageCodeDeCH: + return @"de-CH"; + case AWSTranscribeStreamingLanguageCodeAfZA: + return @"af-ZA"; + case AWSTranscribeStreamingLanguageCodeEuES: + return @"eu-ES"; + case AWSTranscribeStreamingLanguageCodeHrHR: + return @"hr-HR"; + case AWSTranscribeStreamingLanguageCodeCsCZ: + return @"cs-CZ"; + case AWSTranscribeStreamingLanguageCodeDaDK: + return @"da-DK"; + case AWSTranscribeStreamingLanguageCodeFaIR: + return @"fa-IR"; + case AWSTranscribeStreamingLanguageCodeGlES: + return @"gl-ES"; + case AWSTranscribeStreamingLanguageCodeElGR: + return @"el-GR"; + case AWSTranscribeStreamingLanguageCodeHeIL: + return @"he-IL"; + case AWSTranscribeStreamingLanguageCodeIdID: + return @"id-ID"; + case AWSTranscribeStreamingLanguageCodeLvLV: + return @"lv-LV"; + case AWSTranscribeStreamingLanguageCodeMsMY: + return @"ms-MY"; + case AWSTranscribeStreamingLanguageCodeRoRO: + return @"ro-RO"; + case AWSTranscribeStreamingLanguageCodeRuRU: + return @"ru-RU"; + case AWSTranscribeStreamingLanguageCodeSrRS: + return @"sr-RS"; + case AWSTranscribeStreamingLanguageCodeSkSK: + return @"sk-SK"; + case AWSTranscribeStreamingLanguageCodeSoSO: + return @"so-SO"; + case AWSTranscribeStreamingLanguageCodeTlPH: + return @"tl-PH"; + case AWSTranscribeStreamingLanguageCodeUkUA: + return @"uk-UA"; + case AWSTranscribeStreamingLanguageCodeViVN: + return @"vi-VN"; + case AWSTranscribeStreamingLanguageCodeZuZA: + return @"zu-ZA"; default: return nil; } @@ -305,11 +505,131 @@ + (NSValueTransformer *)languageCodeJSONTransformer { if ([value caseInsensitiveCompare:@"zh-CN"] == NSOrderedSame) { return @(AWSTranscribeStreamingLanguageCodeZhCN); } + if ([value caseInsensitiveCompare:@"th-TH"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeThTH); + } + if ([value caseInsensitiveCompare:@"es-ES"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeEsES); + } + if ([value caseInsensitiveCompare:@"ar-SA"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeArSA); + } + if ([value caseInsensitiveCompare:@"pt-PT"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodePtPT); + } + if ([value caseInsensitiveCompare:@"ca-ES"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeCaES); + } + if ([value caseInsensitiveCompare:@"ar-AE"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeArAE); + } if ([value caseInsensitiveCompare:@"hi-IN"] == NSOrderedSame) { return @(AWSTranscribeStreamingLanguageCodeHiIN); } - if ([value caseInsensitiveCompare:@"th-TH"] == NSOrderedSame) { - return @(AWSTranscribeStreamingLanguageCodeThTH); + if ([value caseInsensitiveCompare:@"zh-HK"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeZhHK); + } + if ([value caseInsensitiveCompare:@"nl-NL"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeNlNL); + } + if ([value caseInsensitiveCompare:@"no-NO"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeNoNO); + } + if ([value caseInsensitiveCompare:@"sv-SE"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeSvSE); + } + if ([value caseInsensitiveCompare:@"pl-PL"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodePlPL); + } + if ([value caseInsensitiveCompare:@"fi-FI"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeFiFI); + } + if ([value caseInsensitiveCompare:@"zh-TW"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeZhTW); + } + if ([value caseInsensitiveCompare:@"en-IN"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeEnIN); + } + if ([value caseInsensitiveCompare:@"en-IE"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeEnIE); + } + if ([value caseInsensitiveCompare:@"en-NZ"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeEnNZ); + } + if ([value caseInsensitiveCompare:@"en-AB"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeEnAB); + } + if ([value caseInsensitiveCompare:@"en-ZA"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeEnZA); + } + if ([value caseInsensitiveCompare:@"en-WL"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeEnWL); + } + if ([value caseInsensitiveCompare:@"de-CH"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeDeCH); + } + if ([value caseInsensitiveCompare:@"af-ZA"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeAfZA); + } + if ([value caseInsensitiveCompare:@"eu-ES"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeEuES); + } + if ([value caseInsensitiveCompare:@"hr-HR"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeHrHR); + } + if ([value caseInsensitiveCompare:@"cs-CZ"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeCsCZ); + } + if ([value caseInsensitiveCompare:@"da-DK"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeDaDK); + } + if ([value caseInsensitiveCompare:@"fa-IR"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeFaIR); + } + if ([value caseInsensitiveCompare:@"gl-ES"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeGlES); + } + if ([value caseInsensitiveCompare:@"el-GR"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeElGR); + } + if ([value caseInsensitiveCompare:@"he-IL"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeHeIL); + } + if ([value caseInsensitiveCompare:@"id-ID"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeIdID); + } + if ([value caseInsensitiveCompare:@"lv-LV"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeLvLV); + } + if ([value caseInsensitiveCompare:@"ms-MY"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeMsMY); + } + if ([value caseInsensitiveCompare:@"ro-RO"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeRoRO); + } + if ([value caseInsensitiveCompare:@"ru-RU"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeRuRU); + } + if ([value caseInsensitiveCompare:@"sr-RS"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeSrRS); + } + if ([value caseInsensitiveCompare:@"sk-SK"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeSkSK); + } + if ([value caseInsensitiveCompare:@"so-SO"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeSoSO); + } + if ([value caseInsensitiveCompare:@"tl-PH"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeTlPH); + } + if ([value caseInsensitiveCompare:@"uk-UA"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeUkUA); + } + if ([value caseInsensitiveCompare:@"vi-VN"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeViVN); + } + if ([value caseInsensitiveCompare:@"zu-ZA"] == NSOrderedSame) { + return @(AWSTranscribeStreamingLanguageCodeZuZA); } return @(AWSTranscribeStreamingLanguageCodeUnknown); } reverseBlock:^NSString *(NSNumber *value) { @@ -338,26 +658,90 @@ + (NSValueTransformer *)languageCodeJSONTransformer { return @"ko-KR"; case AWSTranscribeStreamingLanguageCodeZhCN: return @"zh-CN"; - case AWSTranscribeStreamingLanguageCodeHiIN: - return @"hi-IN"; case AWSTranscribeStreamingLanguageCodeThTH: return @"th-TH"; - default: - return nil; - } - }]; -} - -+ (NSValueTransformer *)mediaEncodingJSONTransformer { - return [AWSMTLValueTransformer reversibleTransformerWithForwardBlock:^NSNumber *(NSString *value) { - if ([value caseInsensitiveCompare:@"pcm"] == NSOrderedSame) { - return @(AWSTranscribeStreamingMediaEncodingPcm); - } - return @(AWSTranscribeStreamingMediaEncodingUnknown); - } reverseBlock:^NSString *(NSNumber *value) { - switch ([value integerValue]) { - case AWSTranscribeStreamingMediaEncodingPcm: - return @"pcm"; + case AWSTranscribeStreamingLanguageCodeEsES: + return @"es-ES"; + case AWSTranscribeStreamingLanguageCodeArSA: + return @"ar-SA"; + case AWSTranscribeStreamingLanguageCodePtPT: + return @"pt-PT"; + case AWSTranscribeStreamingLanguageCodeCaES: + return @"ca-ES"; + case AWSTranscribeStreamingLanguageCodeArAE: + return @"ar-AE"; + case AWSTranscribeStreamingLanguageCodeHiIN: + return @"hi-IN"; + case AWSTranscribeStreamingLanguageCodeZhHK: + return @"zh-HK"; + case AWSTranscribeStreamingLanguageCodeNlNL: + return @"nl-NL"; + case AWSTranscribeStreamingLanguageCodeNoNO: + return @"no-NO"; + case AWSTranscribeStreamingLanguageCodeSvSE: + return @"sv-SE"; + case AWSTranscribeStreamingLanguageCodePlPL: + return @"pl-PL"; + case AWSTranscribeStreamingLanguageCodeFiFI: + return @"fi-FI"; + case AWSTranscribeStreamingLanguageCodeZhTW: + return @"zh-TW"; + case AWSTranscribeStreamingLanguageCodeEnIN: + return @"en-IN"; + case AWSTranscribeStreamingLanguageCodeEnIE: + return @"en-IE"; + case AWSTranscribeStreamingLanguageCodeEnNZ: + return @"en-NZ"; + case AWSTranscribeStreamingLanguageCodeEnAB: + return @"en-AB"; + case AWSTranscribeStreamingLanguageCodeEnZA: + return @"en-ZA"; + case AWSTranscribeStreamingLanguageCodeEnWL: + return @"en-WL"; + case AWSTranscribeStreamingLanguageCodeDeCH: + return @"de-CH"; + case AWSTranscribeStreamingLanguageCodeAfZA: + return @"af-ZA"; + case AWSTranscribeStreamingLanguageCodeEuES: + return @"eu-ES"; + case AWSTranscribeStreamingLanguageCodeHrHR: + return @"hr-HR"; + case AWSTranscribeStreamingLanguageCodeCsCZ: + return @"cs-CZ"; + case AWSTranscribeStreamingLanguageCodeDaDK: + return @"da-DK"; + case AWSTranscribeStreamingLanguageCodeFaIR: + return @"fa-IR"; + case AWSTranscribeStreamingLanguageCodeGlES: + return @"gl-ES"; + case AWSTranscribeStreamingLanguageCodeElGR: + return @"el-GR"; + case AWSTranscribeStreamingLanguageCodeHeIL: + return @"he-IL"; + case AWSTranscribeStreamingLanguageCodeIdID: + return @"id-ID"; + case AWSTranscribeStreamingLanguageCodeLvLV: + return @"lv-LV"; + case AWSTranscribeStreamingLanguageCodeMsMY: + return @"ms-MY"; + case AWSTranscribeStreamingLanguageCodeRoRO: + return @"ro-RO"; + case AWSTranscribeStreamingLanguageCodeRuRU: + return @"ru-RU"; + case AWSTranscribeStreamingLanguageCodeSrRS: + return @"sr-RS"; + case AWSTranscribeStreamingLanguageCodeSkSK: + return @"sk-SK"; + case AWSTranscribeStreamingLanguageCodeSoSO: + return @"so-SO"; + case AWSTranscribeStreamingLanguageCodeTlPH: + return @"tl-PH"; + case AWSTranscribeStreamingLanguageCodeUkUA: + return @"uk-UA"; + case AWSTranscribeStreamingLanguageCodeViVN: + return @"vi-VN"; + case AWSTranscribeStreamingLanguageCodeZuZA: + return @"zu-ZA"; default: return nil; } @@ -418,6 +802,7 @@ + (NSDictionary *)JSONKeyPathsByPropertyKey { @"conflictException" : @"ConflictException", @"internalFailureException" : @"InternalFailureException", @"limitExceededException" : @"LimitExceededException", + @"serviceUnavailableException" : @"ServiceUnavailableException", @"transcriptEvent" : @"TranscriptEvent", }; } diff --git a/AWSTranscribeStreaming/AWSTranscribeStreamingResources.m b/AWSTranscribeStreaming/AWSTranscribeStreamingResources.m index f69c74f540b..185cc17d8ba 100644 --- a/AWSTranscribeStreaming/AWSTranscribeStreamingResources.m +++ b/AWSTranscribeStreaming/AWSTranscribeStreamingResources.m @@ -1,5 +1,5 @@ // -// Copyright 2010-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// Copyright 2010-2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). // You may not use this file except in compliance with the License. @@ -209,8 +209,48 @@ - (nonnull NSString *)definitionString { \"ja-JP\",\ \"ko-KR\",\ \"zh-CN\",\ + \"th-TH\",\ + \"es-ES\",\ + \"ar-SA\",\ + \"pt-PT\",\ + \"ca-ES\",\ + \"ar-AE\",\ \"hi-IN\",\ - \"th-TH\"\ + \"zh-HK\",\ + \"nl-NL\",\ + \"no-NO\",\ + \"sv-SE\",\ + \"pl-PL\",\ + \"fi-FI\",\ + \"zh-TW\",\ + \"en-IN\",\ + \"en-IE\",\ + \"en-NZ\",\ + \"en-AB\",\ + \"en-ZA\",\ + \"en-WL\",\ + \"de-CH\",\ + \"af-ZA\",\ + \"eu-ES\",\ + \"hr-HR\",\ + \"cs-CZ\",\ + \"da-DK\",\ + \"fa-IR\",\ + \"gl-ES\",\ + \"el-GR\",\ + \"he-IL\",\ + \"id-ID\",\ + \"lv-LV\",\ + \"ms-MY\",\ + \"ro-RO\",\ + \"ru-RU\",\ + \"sr-RS\",\ + \"sk-SK\",\ + \"so-SO\",\ + \"tl-PH\",\ + \"uk-UA\",\ + \"vi-VN\",\ + \"zu-ZA\"\ ]\ },\ \"LimitExceededException\":{\ @@ -402,6 +442,10 @@ - (nonnull NSString *)definitionString { \"ConflictException\":{\ \"shape\":\"ConflictException\",\ \"documentation\":\"

A new stream started with the same session ID. The current stream has been terminated.

\"\ + },\ + \"ServiceUnavailableException\":{\ + \"shape\":\"ServiceUnavailableException\",\ + \"documentation\":\"

The service is currently unavailable. Try your request later.

\"\ }\ },\ \"documentation\":\"

Represents the transcription result stream from Amazon Transcribe to your application.

\",\ diff --git a/AWSTranscribeStreamingTests/AWSTranscribeStreamingSwiftTests.swift b/AWSTranscribeStreamingTests/AWSTranscribeStreamingSwiftTests.swift index f6bda773a2e..97ec35a5bf4 100644 --- a/AWSTranscribeStreamingTests/AWSTranscribeStreamingSwiftTests.swift +++ b/AWSTranscribeStreamingTests/AWSTranscribeStreamingSwiftTests.swift @@ -40,265 +40,140 @@ class AWSTranscribeStreamingSwiftTests: XCTestCase { transcribeStreamingClient = AWSTranscribeStreaming(forKey: AWSTranscribeStreamingSwiftTests.transcribeClientKey) AWSDDLog.sharedInstance.logLevel = .info - AWSDDLog.sharedInstance.add(AWSDDTTYLogger.sharedInstance) + AWSDDLog.add(AWSDDOSLogger.sharedInstance) } - func testStreamingExample() throws { - AWSDDLog.sharedInstance.logLevel = .info - AWSDDLog.add(AWSDDTTYLogger.sharedInstance) - - let bundle = Bundle(for: AWSTranscribeStreamingSwiftTests.self) - guard let audioPath = bundle.path(forResource: "hello_world", ofType: "wav") else { - XCTFail("Can't find audio path") - return - } - - let audioURL = URL(fileURLWithPath: audioPath) - let audioData = try Data(contentsOf: audioURL) - - guard let request = AWSTranscribeStreamingStartStreamTranscriptionRequest() else { - XCTFail("request unexpectedly nil") - return - } - - request.languageCode = .enUS - request.mediaEncoding = .pcm - request.mediaSampleRateHertz = 8000 - - // Set up delegate and its expectations - let delegate = MockTranscribeStreamingClientDelegate() - - // Connection open/close - let webSocketIsConnected = expectation(description: "Web socket is connected") - let webSocketIsClosed = expectation(description: "Web socket is closed") - delegate.connectionStatusCallback = { status, error in - if status == .connected { - DispatchQueue.main.async { - webSocketIsConnected.fulfill() - } - } - - if status == .closed && error == nil { - DispatchQueue.main.async { - webSocketIsClosed.fulfill() - } - } - } + func testStreamingExamples() throws { + let audioExamplesMap: [AWSTranscribeStreamingLanguageCode: String] = [ + .enUS: "hello_world", + .deDE: "guten_tag", + .esES: "hola_ mundo" + ] - // Event: for this test, we expect to only receive transcriptions, not errors - let receivedFinalTranscription = expectation(description: "Received final transcription") - delegate.receiveEventCallback = { event, error in - if let error = error { - XCTFail("Unexpected error receiving event: \(error)") - return - } - - guard let event = event else { - XCTFail("event unexpectedly nil") - return - } - - guard let transcriptEvent = event.transcriptEvent else { - XCTFail("transcriptEvent unexpectedly nil: event may be an error \(event)") + for (languageCode, fileName) in audioExamplesMap { + let bundle = Bundle(for: AWSTranscribeStreamingSwiftTests.self) + guard let audioPath = bundle.path(forResource: fileName, ofType: "wav") else { + XCTFail("Can't find audio path: \(fileName).wav") return } - guard let results = transcriptEvent.transcript?.results else { - print("No results, waiting for next event") - return - } + let audioURL = URL(fileURLWithPath: audioPath) + let audioData = try Data(contentsOf: audioURL) - guard let firstResult = results.first else { - print("firstResult nil--possibly a partial result: \(event)") + guard let request = AWSTranscribeStreamingStartStreamTranscriptionRequest() else { + XCTFail("request unexpectedly nil") return } - guard let isPartial = firstResult.isPartial as? Bool else { - XCTFail("isPartial unexpectedly nil, or cannot cast NSNumber to Bool") - return - } + request.languageCode = languageCode + request.mediaEncoding = .pcm + request.mediaSampleRateHertz = 24000 - guard !isPartial else { - print("Partial result received, waiting for next event (results: \(results))") - return - } + // Set up delegate and its expectations + let delegate = MockTranscribeStreamingClientDelegate() - print("Received final transcription event (results: \(results))") - DispatchQueue.main.async { - receivedFinalTranscription.fulfill() - } - } - - let callbackQueue = DispatchQueue(label: "testStreamingExample") - transcribeStreamingClient.setDelegate(delegate, callbackQueue: callbackQueue) + // Connection open/close + let webSocketIsConnected = expectation(description: "Web socket is connected") + let webSocketIsClosed = expectation(description: "Web socket is closed") + delegate.connectionStatusCallback = { status, error in + if status == .connected { + DispatchQueue.main.async { + webSocketIsConnected.fulfill() + } + } - // Now that we have a delegate ready to receive the "open" event, we can start the transcription request - transcribeStreamingClient.startTranscriptionWSS(request) + if status == .closed && error == nil { + DispatchQueue.main.async { + webSocketIsClosed.fulfill() + } + } + } - wait(for: [webSocketIsConnected], timeout: AWSTranscribeStreamingSwiftTests.networkOperationTimeout) + // Event: for this test, we expect to only receive transcriptions, not errors + let receivedFinalTranscription = expectation(description: "Received final transcription") + delegate.receiveEventCallback = { event, error in + if let error = error { + XCTFail("Unexpected error receiving event: \(error)") + return + } - // Now that the web socket is connected, it is safe to proceed with streaming + guard let event = event else { + XCTFail("event unexpectedly nil") + return + } - let headers = [ - ":content-type": "audio/wav", - ":message-type": "event", - ":event-type": "AudioEvent" - ] - - let chunkSize = 4096 - let audioDataSize = audioData.count - - var currentStart = 0 - var currentEnd = min(chunkSize, audioDataSize - currentStart) + guard let transcriptEvent = event.transcriptEvent else { + XCTFail("transcriptEvent unexpectedly nil: event may be an error \(event)") + return + } - while currentStart < audioDataSize { - let dataChunk = audioData[currentStart ..< currentEnd] - transcribeStreamingClient.send(dataChunk, headers: headers) + guard let results = transcriptEvent.transcript?.results else { + print("No results, waiting for next event") + return + } - currentStart = currentEnd - currentEnd = min(currentStart + chunkSize, audioDataSize) - } - - print("Sending end frame") - self.transcribeStreamingClient.sendEndFrame() + guard let firstResult = results.first else { + print("firstResult nil--possibly a partial result: \(event)") + return + } - print("Waiting for final transcription event") - wait(for: [receivedFinalTranscription], timeout: AWSTranscribeStreamingSwiftTests.networkOperationTimeout) - - print("Ending transcription") - transcribeStreamingClient.endTranscription() - - print("Waiting for websocket to close") - wait(for: [webSocketIsClosed], timeout: AWSTranscribeStreamingSwiftTests.networkOperationTimeout) - } - - func testStreamingExampleDeutsche() throws { - AWSDDLog.sharedInstance.logLevel = .info - AWSDDLog.add(AWSDDTTYLogger.sharedInstance) - - let bundle = Bundle(for: AWSTranscribeStreamingSwiftTests.self) - guard let audioPath = bundle.path(forResource: "guten_tag", ofType: "wav") else { - XCTFail("Can't find audio path") - return - } - - let audioURL = URL(fileURLWithPath: audioPath) - let audioData = try Data(contentsOf: audioURL) - - guard let request = AWSTranscribeStreamingStartStreamTranscriptionRequest() else { - XCTFail("request unexpectedly nil") - return - } + guard let isPartial = firstResult.isPartial as? Bool else { + XCTFail("isPartial unexpectedly nil, or cannot cast NSNumber to Bool") + return + } - request.languageCode = .deDE - request.mediaEncoding = .pcm - request.mediaSampleRateHertz = 24000 - - // Set up delegate and its expectations - let delegate = MockTranscribeStreamingClientDelegate() - - // Connection open/close - let webSocketIsConnected = expectation(description: "Web socket is connected") - let webSocketIsClosed = expectation(description: "Web socket is closed") - delegate.connectionStatusCallback = { status, error in - if status == .connected { - DispatchQueue.main.async { - webSocketIsConnected.fulfill() + guard !isPartial else { + print("Partial result received, waiting for next event (results: \(results))") + return } - } - - if status == .closed && error == nil { + + print("Received final transcription event (results: \(results))") DispatchQueue.main.async { - webSocketIsClosed.fulfill() + receivedFinalTranscription.fulfill() } } - } - // Event: for this test, we expect to only receive transcriptions, not errors - let receivedFinalTranscription = expectation(description: "Received final transcription") - delegate.receiveEventCallback = { event, error in - if let error = error { - XCTFail("Unexpected error receiving event: \(error)") - return - } - - guard let event = event else { - XCTFail("event unexpectedly nil") - return - } - - guard let transcriptEvent = event.transcriptEvent else { - XCTFail("transcriptEvent unexpectedly nil: event may be an error \(event)") - return - } + let callbackQueue = DispatchQueue(label: "testStreamingExample") + transcribeStreamingClient.setDelegate(delegate, callbackQueue: callbackQueue) - guard let results = transcriptEvent.transcript?.results else { - print("No results, waiting for next event") - return - } + // Now that we have a delegate ready to receive the "open" event, we can start the transcription request + transcribeStreamingClient.startTranscriptionWSS(request) - guard let firstResult = results.first else { - print("firstResult nil--possibly a partial result: \(event)") - return - } + wait(for: [webSocketIsConnected], timeout: AWSTranscribeStreamingSwiftTests.networkOperationTimeout) - guard let isPartial = firstResult.isPartial as? Bool else { - XCTFail("isPartial unexpectedly nil, or cannot cast NSNumber to Bool") - return - } + // Now that the web socket is connected, it is safe to proceed with streaming - guard !isPartial else { - print("Partial result received, waiting for next event (results: \(results))") - return - } + let headers = [ + ":content-type": "audio/wav", + ":message-type": "event", + ":event-type": "AudioEvent" + ] - print("Received final transcription event (results: \(results))") - DispatchQueue.main.async { - receivedFinalTranscription.fulfill() - } - } - - let callbackQueue = DispatchQueue(label: "testStreamingExample") - transcribeStreamingClient.setDelegate(delegate, callbackQueue: callbackQueue) + let chunkSize = 4096 + let audioDataSize = audioData.count - // Now that we have a delegate ready to receive the "open" event, we can start the transcription request - transcribeStreamingClient.startTranscriptionWSS(request) + var currentStart = 0 + var currentEnd = min(chunkSize, audioDataSize - currentStart) - wait(for: [webSocketIsConnected], timeout: AWSTranscribeStreamingSwiftTests.networkOperationTimeout) + while currentStart < audioDataSize { + let dataChunk = audioData[currentStart ..< currentEnd] + transcribeStreamingClient.send(dataChunk, headers: headers) - // Now that the web socket is connected, it is safe to proceed with streaming + currentStart = currentEnd + currentEnd = min(currentStart + chunkSize, audioDataSize) + } - let headers = [ - ":content-type": "audio/wav", - ":message-type": "event", - ":event-type": "AudioEvent" - ] - - let chunkSize = 4096 - let audioDataSize = audioData.count - - var currentStart = 0 - var currentEnd = min(chunkSize, audioDataSize - currentStart) + print("Sending end frame") + self.transcribeStreamingClient.sendEndFrame() - while currentStart < audioDataSize { - let dataChunk = audioData[currentStart ..< currentEnd] - transcribeStreamingClient.send(dataChunk, headers: headers) + print("Waiting for final transcription event") + wait(for: [receivedFinalTranscription], timeout: AWSTranscribeStreamingSwiftTests.networkOperationTimeout) - currentStart = currentEnd - currentEnd = min(currentStart + chunkSize, audioDataSize) - } - - print("Sending end frame") - self.transcribeStreamingClient.sendEndFrame() + print("Ending transcription") + transcribeStreamingClient.endTranscription() - print("Waiting for final transcription event") - wait(for: [receivedFinalTranscription], timeout: AWSTranscribeStreamingSwiftTests.networkOperationTimeout) - - print("Ending transcription") - transcribeStreamingClient.endTranscription() - - print("Waiting for websocket to close") - wait(for: [webSocketIsClosed], timeout: AWSTranscribeStreamingSwiftTests.networkOperationTimeout) + print("Waiting for websocket to close") + wait(for: [webSocketIsClosed], timeout: AWSTranscribeStreamingSwiftTests.networkOperationTimeout) + } } - } diff --git a/AWSTranscribeStreamingTests/hola_mundo.wav b/AWSTranscribeStreamingTests/hola_mundo.wav new file mode 100644 index 0000000000000000000000000000000000000000..81ec393067b8d366b91f5d9bed3b4cf5c311db9f GIT binary patch literal 74958 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 Date: Fri, 20 Dec 2024 17:32:50 -0500 Subject: [PATCH 2/3] Fixing tests --- .../AWSTranscribeStreamingSwiftTests.swift | 208 +++++++++--------- AWSiOSSDKv2.xcodeproj/project.pbxproj | 4 + 2 files changed, 110 insertions(+), 102 deletions(-) diff --git a/AWSTranscribeStreamingTests/AWSTranscribeStreamingSwiftTests.swift b/AWSTranscribeStreamingTests/AWSTranscribeStreamingSwiftTests.swift index 97ec35a5bf4..040ee250cb3 100644 --- a/AWSTranscribeStreamingTests/AWSTranscribeStreamingSwiftTests.swift +++ b/AWSTranscribeStreamingTests/AWSTranscribeStreamingSwiftTests.swift @@ -43,137 +43,141 @@ class AWSTranscribeStreamingSwiftTests: XCTestCase { AWSDDLog.add(AWSDDOSLogger.sharedInstance) } - func testStreamingExamples() throws { - let audioExamplesMap: [AWSTranscribeStreamingLanguageCode: String] = [ - .enUS: "hello_world", - .deDE: "guten_tag", - .esES: "hola_ mundo" - ] + func testStreamingExampleEnglish() throws { + try transcribeAudio(languageCode: .enUS, fileName: "hello_world") + } - for (languageCode, fileName) in audioExamplesMap { - let bundle = Bundle(for: AWSTranscribeStreamingSwiftTests.self) - guard let audioPath = bundle.path(forResource: fileName, ofType: "wav") else { - XCTFail("Can't find audio path: \(fileName).wav") - return - } + func testStreamingExampleDeutsche() throws { + try transcribeAudio(languageCode: .deDE, fileName: "guten_tag") + } - let audioURL = URL(fileURLWithPath: audioPath) - let audioData = try Data(contentsOf: audioURL) + func testStreamingExampleEspañol() throws { + try transcribeAudio(languageCode: .esES, fileName: "hola_mundo") + } - guard let request = AWSTranscribeStreamingStartStreamTranscriptionRequest() else { - XCTFail("request unexpectedly nil") - return - } + private func transcribeAudio(languageCode: AWSTranscribeStreamingLanguageCode, fileName: String) throws { + let bundle = Bundle(for: AWSTranscribeStreamingSwiftTests.self) + guard let audioPath = bundle.path(forResource: fileName, ofType: "wav") else { + XCTFail("Can't find audio path: \(fileName).wav") + return + } - request.languageCode = languageCode - request.mediaEncoding = .pcm - request.mediaSampleRateHertz = 24000 - - // Set up delegate and its expectations - let delegate = MockTranscribeStreamingClientDelegate() - - // Connection open/close - let webSocketIsConnected = expectation(description: "Web socket is connected") - let webSocketIsClosed = expectation(description: "Web socket is closed") - delegate.connectionStatusCallback = { status, error in - if status == .connected { - DispatchQueue.main.async { - webSocketIsConnected.fulfill() - } - } + let audioURL = URL(fileURLWithPath: audioPath) + let audioData = try Data(contentsOf: audioURL) - if status == .closed && error == nil { - DispatchQueue.main.async { - webSocketIsClosed.fulfill() - } - } - } + guard let request = AWSTranscribeStreamingStartStreamTranscriptionRequest() else { + XCTFail("request unexpectedly nil") + return + } - // Event: for this test, we expect to only receive transcriptions, not errors - let receivedFinalTranscription = expectation(description: "Received final transcription") - delegate.receiveEventCallback = { event, error in - if let error = error { - XCTFail("Unexpected error receiving event: \(error)") - return - } + request.languageCode = languageCode + request.mediaEncoding = .pcm + request.mediaSampleRateHertz = 24000 - guard let event = event else { - XCTFail("event unexpectedly nil") - return - } + // Set up delegate and its expectations + let delegate = MockTranscribeStreamingClientDelegate() - guard let transcriptEvent = event.transcriptEvent else { - XCTFail("transcriptEvent unexpectedly nil: event may be an error \(event)") - return + // Connection open/close + let webSocketIsConnected = expectation(description: "Web socket is connected") + let webSocketIsClosed = expectation(description: "Web socket is closed") + delegate.connectionStatusCallback = { status, error in + if status == .connected { + DispatchQueue.main.async { + webSocketIsConnected.fulfill() } + } - guard let results = transcriptEvent.transcript?.results else { - print("No results, waiting for next event") - return + if status == .closed && error == nil { + DispatchQueue.main.async { + webSocketIsClosed.fulfill() } + } + } - guard let firstResult = results.first else { - print("firstResult nil--possibly a partial result: \(event)") - return - } + // Event: for this test, we expect to only receive transcriptions, not errors + let receivedFinalTranscription = expectation(description: "Received final transcription") + delegate.receiveEventCallback = { event, error in + if let error = error { + XCTFail("Unexpected error receiving event: \(error)") + return + } - guard let isPartial = firstResult.isPartial as? Bool else { - XCTFail("isPartial unexpectedly nil, or cannot cast NSNumber to Bool") - return - } + guard let event = event else { + XCTFail("event unexpectedly nil") + return + } - guard !isPartial else { - print("Partial result received, waiting for next event (results: \(results))") - return - } + guard let transcriptEvent = event.transcriptEvent else { + XCTFail("transcriptEvent unexpectedly nil: event may be an error \(event)") + return + } - print("Received final transcription event (results: \(results))") - DispatchQueue.main.async { - receivedFinalTranscription.fulfill() - } + guard let results = transcriptEvent.transcript?.results else { + print("No results, waiting for next event") + return } - let callbackQueue = DispatchQueue(label: "testStreamingExample") - transcribeStreamingClient.setDelegate(delegate, callbackQueue: callbackQueue) + guard let firstResult = results.first else { + print("firstResult nil--possibly a partial result: \(event)") + return + } - // Now that we have a delegate ready to receive the "open" event, we can start the transcription request - transcribeStreamingClient.startTranscriptionWSS(request) + guard let isPartial = firstResult.isPartial as? Bool else { + XCTFail("isPartial unexpectedly nil, or cannot cast NSNumber to Bool") + return + } - wait(for: [webSocketIsConnected], timeout: AWSTranscribeStreamingSwiftTests.networkOperationTimeout) + guard !isPartial else { + print("Partial result received, waiting for next event (results: \(results))") + return + } - // Now that the web socket is connected, it is safe to proceed with streaming + print("Received final transcription event (results: \(results))") + DispatchQueue.main.async { + receivedFinalTranscription.fulfill() + } + } - let headers = [ - ":content-type": "audio/wav", - ":message-type": "event", - ":event-type": "AudioEvent" - ] + let callbackQueue = DispatchQueue(label: "testStreamingExample") + transcribeStreamingClient.setDelegate(delegate, callbackQueue: callbackQueue) - let chunkSize = 4096 - let audioDataSize = audioData.count + // Now that we have a delegate ready to receive the "open" event, we can start the transcription request + transcribeStreamingClient.startTranscriptionWSS(request) - var currentStart = 0 - var currentEnd = min(chunkSize, audioDataSize - currentStart) + wait(for: [webSocketIsConnected], timeout: AWSTranscribeStreamingSwiftTests.networkOperationTimeout) - while currentStart < audioDataSize { - let dataChunk = audioData[currentStart ..< currentEnd] - transcribeStreamingClient.send(dataChunk, headers: headers) + // Now that the web socket is connected, it is safe to proceed with streaming - currentStart = currentEnd - currentEnd = min(currentStart + chunkSize, audioDataSize) - } + let headers = [ + ":content-type": "audio/wav", + ":message-type": "event", + ":event-type": "AudioEvent" + ] - print("Sending end frame") - self.transcribeStreamingClient.sendEndFrame() + let chunkSize = 4096 + let audioDataSize = audioData.count - print("Waiting for final transcription event") - wait(for: [receivedFinalTranscription], timeout: AWSTranscribeStreamingSwiftTests.networkOperationTimeout) + var currentStart = 0 + var currentEnd = min(chunkSize, audioDataSize - currentStart) - print("Ending transcription") - transcribeStreamingClient.endTranscription() + while currentStart < audioDataSize { + let dataChunk = audioData[currentStart ..< currentEnd] + transcribeStreamingClient.send(dataChunk, headers: headers) - print("Waiting for websocket to close") - wait(for: [webSocketIsClosed], timeout: AWSTranscribeStreamingSwiftTests.networkOperationTimeout) + currentStart = currentEnd + currentEnd = min(currentStart + chunkSize, audioDataSize) } + + print("Sending end frame") + self.transcribeStreamingClient.sendEndFrame() + + print("Waiting for final transcription event") + wait(for: [receivedFinalTranscription], timeout: AWSTranscribeStreamingSwiftTests.networkOperationTimeout) + + print("Ending transcription") + transcribeStreamingClient.endTranscription() + + print("Waiting for websocket to close") + wait(for: [webSocketIsClosed], timeout: AWSTranscribeStreamingSwiftTests.networkOperationTimeout) } } diff --git a/AWSiOSSDKv2.xcodeproj/project.pbxproj b/AWSiOSSDKv2.xcodeproj/project.pbxproj index d5a4a2c636d..36db0f580bd 100644 --- a/AWSiOSSDKv2.xcodeproj/project.pbxproj +++ b/AWSiOSSDKv2.xcodeproj/project.pbxproj @@ -598,6 +598,7 @@ 5C1590172755727C00F88085 /* AWSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE0D416D1C6A66E5006B91B5 /* AWSCore.framework */; }; 5C1978DD2702364800F9C11E /* AWSLocationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1978DC2702364800F9C11E /* AWSLocationTests.swift */; }; 5C71F33F295672B8001183A4 /* guten_tag.wav in Resources */ = {isa = PBXBuildFile; fileRef = 5C71F33E295672B8001183A4 /* guten_tag.wav */; }; + 680BA6182D1626CF00D69A6C /* hola_mundo.wav in Resources */ = {isa = PBXBuildFile; fileRef = 680BA6172D1626CF00D69A6C /* hola_mundo.wav */; }; 685AA2112CDA7843008EFC7B /* AWSMQTTTimerRing.h in Headers */ = {isa = PBXBuildFile; fileRef = 685AA20F2CDA7843008EFC7B /* AWSMQTTTimerRing.h */; }; 685AA2122CDA7843008EFC7B /* AWSMQTTTimerRing.m in Sources */ = {isa = PBXBuildFile; fileRef = 685AA2102CDA7843008EFC7B /* AWSMQTTTimerRing.m */; }; 687952932B8FE2C5001E8990 /* AWSDDLog+Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = 687952922B8FE2C5001E8990 /* AWSDDLog+Optional.swift */; }; @@ -3217,6 +3218,7 @@ 5C1978DB2702364800F9C11E /* AWSLocationTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AWSLocationTests-Bridging-Header.h"; sourceTree = ""; }; 5C1978DC2702364800F9C11E /* AWSLocationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSLocationTests.swift; sourceTree = ""; }; 5C71F33E295672B8001183A4 /* guten_tag.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = guten_tag.wav; sourceTree = ""; }; + 680BA6172D1626CF00D69A6C /* hola_mundo.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = hola_mundo.wav; sourceTree = ""; }; 685AA20F2CDA7843008EFC7B /* AWSMQTTTimerRing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AWSMQTTTimerRing.h; sourceTree = ""; }; 685AA2102CDA7843008EFC7B /* AWSMQTTTimerRing.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AWSMQTTTimerRing.m; sourceTree = ""; }; 687952922B8FE2C5001E8990 /* AWSDDLog+Optional.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AWSDDLog+Optional.swift"; sourceTree = ""; }; @@ -5409,6 +5411,7 @@ FA53331F22D4065800BD88AF /* AWSTranscribeStreamingTests-Bridging-Header.h */, 5C71F33E295672B8001183A4 /* guten_tag.wav */, FABD9ED322D6661200BD4441 /* hello_world.wav */, + 680BA6172D1626CF00D69A6C /* hola_mundo.wav */, 178A805122B097B900B167D6 /* Info.plist */, ); path = AWSTranscribeStreamingTests; @@ -11487,6 +11490,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 680BA6182D1626CF00D69A6C /* hola_mundo.wav in Resources */, FABD9ED422D6661300BD4441 /* hello_world.wav in Resources */, 5C71F33F295672B8001183A4 /* guten_tag.wav in Resources */, ); From b00bb623378ebe79d8ed05a335c9751f774dd7f5 Mon Sep 17 00:00:00 2001 From: Sebastian Villena <97059974+ruisebas@users.noreply.github.com> Date: Mon, 23 Dec 2024 09:26:23 -0500 Subject: [PATCH 3/3] Updating media rate hertz --- .../AWSTranscribeStreamingSwiftTests.swift | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/AWSTranscribeStreamingTests/AWSTranscribeStreamingSwiftTests.swift b/AWSTranscribeStreamingTests/AWSTranscribeStreamingSwiftTests.swift index 040ee250cb3..5da349acb6a 100644 --- a/AWSTranscribeStreamingTests/AWSTranscribeStreamingSwiftTests.swift +++ b/AWSTranscribeStreamingTests/AWSTranscribeStreamingSwiftTests.swift @@ -44,18 +44,34 @@ class AWSTranscribeStreamingSwiftTests: XCTestCase { } func testStreamingExampleEnglish() throws { - try transcribeAudio(languageCode: .enUS, fileName: "hello_world") + try transcribeAudio( + languageCode: .enUS, + fileName: "hello_world", + mediaSampleRateHertz: 8000 + ) } func testStreamingExampleDeutsche() throws { - try transcribeAudio(languageCode: .deDE, fileName: "guten_tag") + try transcribeAudio( + languageCode: .deDE, + fileName: "guten_tag", + mediaSampleRateHertz: 24000 + ) } func testStreamingExampleEspañol() throws { - try transcribeAudio(languageCode: .esES, fileName: "hola_mundo") + try transcribeAudio( + languageCode: .esES, + fileName: "hola_mundo", + mediaSampleRateHertz: 24000 + ) } - private func transcribeAudio(languageCode: AWSTranscribeStreamingLanguageCode, fileName: String) throws { + private func transcribeAudio( + languageCode: AWSTranscribeStreamingLanguageCode, + fileName: String, + mediaSampleRateHertz: NSNumber + ) throws { let bundle = Bundle(for: AWSTranscribeStreamingSwiftTests.self) guard let audioPath = bundle.path(forResource: fileName, ofType: "wav") else { XCTFail("Can't find audio path: \(fileName).wav") @@ -72,7 +88,7 @@ class AWSTranscribeStreamingSwiftTests: XCTestCase { request.languageCode = languageCode request.mediaEncoding = .pcm - request.mediaSampleRateHertz = 24000 + request.mediaSampleRateHertz = mediaSampleRateHertz // Set up delegate and its expectations let delegate = MockTranscribeStreamingClientDelegate()