voice_id 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,33 @@
1
+ require 'http'
2
+
3
+ module VoiceId
4
+ module RequestHelpers
5
+ class InvalidApiRequestError < StandardError; end
6
+
7
+ # generate body for content-type "multipart/form-data"
8
+ def create_body_for_enrollment(audio_file_path)
9
+ { :form => { :file => HTTP::FormData::File.new(audio_file_path) } }
10
+ end
11
+
12
+ def parse_error_response(response)
13
+ msg = response.parse["error"]["message"] || "request returned status #{response.code}"
14
+ raise InvalidApiRequestError, msg
15
+ end
16
+
17
+ def send_request(path, method, req_headers, body)
18
+ _headers = req_headers ? headers.merge(req_headers) : headers
19
+ _path = api_base_url + path
20
+ _req = HTTP.headers(_headers)
21
+
22
+ case method
23
+ when :Post
24
+ body ? _req.post(_path, body) : _req.post(_path)
25
+ when :Get
26
+ _req.get(_path)
27
+ when :Delete
28
+ _req.delete(_path)
29
+ end
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,7 @@
1
+ module VoiceId
2
+ module Utils
3
+ def get_operation_id(operation_url)
4
+ operation_url.match(/operations\/(.+)/)[1]
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,251 @@
1
+ module VoiceId
2
+ class Verification < VoiceId::Base
3
+
4
+ # params
5
+ # profile_id
6
+ # unique profile id
7
+ # audio_file_path
8
+ # path to the audio of speaker
9
+ #
10
+ # Microsoft API response
11
+ # 200 Response
12
+ # {
13
+ # "result" : "Accept", // [Accept | Reject]
14
+ # "confidence" : "Normal", // [Low | Normal | High]
15
+ # "phrase": "recognized phrase"
16
+ # }
17
+ #
18
+ #
19
+ # returns
20
+ # success
21
+ # verification results { Hash }
22
+ # fail (500 error)
23
+ # false
24
+ def verify_speaker(profile_id, audio_file_path)
25
+ _method = :Post
26
+ _path = "/verify?verificationProfileId=#{profile_id}"
27
+ _headers = { }
28
+ _body = create_body_for_enrollment(audio_file_path)
29
+ _response = send_request(_path, _method, _headers, _body)
30
+
31
+ _response.code == 200 ? _response.parse : parse_error_response(_response)
32
+ end
33
+
34
+ # Microsoft API response
35
+ # 200 Response
36
+ # {
37
+ # "verificationProfileId": "49a36324-fc4b-4387-aa06-090cfbf0064f",
38
+ # }
39
+ #
40
+ # 500 Response
41
+ # {
42
+ # "error":{
43
+ # "code" : "InternalServerError",
44
+ # "message" : "SpeakerInvalid",
45
+ # }
46
+ # }
47
+ #
48
+ # returns
49
+ # success
50
+ # new profileId { Hash }
51
+ # fail
52
+ # false (indicating new profile was not created)
53
+ def create_profile
54
+ super("/verificationProfiles")
55
+ end
56
+
57
+ # params
58
+ # profileId - required
59
+ # a valid id { String }
60
+ #
61
+ # Microsoft API response
62
+ # 200 Response
63
+ # "" (empty string)
64
+ #
65
+ # 500 Response
66
+ # {
67
+ # "error": {
68
+ # "code" : "InternalServerError",
69
+ # "message" : "SpeakerInvalid",
70
+ # }
71
+ # }
72
+ #
73
+ # returns
74
+ # success
75
+ # profile id that was deleted { String }
76
+ # fail
77
+ # false (indicating delete of id failed)
78
+ def delete_profile(profileId)
79
+ super("/verificationProfiles/#{profileId}")
80
+ end
81
+
82
+ # Microsoft API response
83
+ # 200 Response
84
+ # [{
85
+ # "verificationProfileId" : "111f427c-3791-468f-b709-fcef7660fff9",
86
+ # "locale" : "en-US",
87
+ # "enrollmentsCount" : 2,
88
+ # "remainingEnrollmentsCount" : 0,
89
+ # "createdDateTime" : "2015-04-23T18:25:43.511Z",
90
+ # "lastActionDateTime" : "2015-04-23T18:25:43.511Z",
91
+ # "enrollmentStatus" : "Enrolled" //[Enrolled | Enrolling | Training]
92
+ # }, …]
93
+ #
94
+ # 500 Response
95
+ # {
96
+ # "error": {
97
+ # "code" : "InternalServerError",
98
+ # "message" : "SpeakerInvalid",
99
+ # }
100
+ # }
101
+ #
102
+ # returns
103
+ # success
104
+ # A list of all the profiles { Array }
105
+ # fail
106
+ # false (indicating delete of id failed)
107
+ def get_all_profiles
108
+ super('/verificationProfiles')
109
+ end
110
+
111
+ # params
112
+ # profileId
113
+ # a valid profileId { String }
114
+ #
115
+ # Microsoft API response
116
+ # 200 Response
117
+ # {
118
+ # "verificationProfileId" : "111f427c-3791-468f-b709-fcef7660fff9",
119
+ # "locale" : "en-US",
120
+ # "enrollmentsCount" : 2,
121
+ # "remainingEnrollmentsCount" : 0,
122
+ # "createdDateTime" : "2015-04-23T18:25:43.511Z",
123
+ # "lastActionDateTime" : "2015-04-23T18:25:43.511Z",
124
+ # "enrollmentStatus" : "Enrolled" // [Enrolled | Enrolling | Training]
125
+ # }
126
+ #
127
+ # 500 Response
128
+ # {
129
+ # "error": {
130
+ # "code" : "InternalServerError",
131
+ # "message" : "SpeakerInvalid",
132
+ # }
133
+ # }
134
+ #
135
+ # returns
136
+ # success
137
+ # a profile { Hash }
138
+ # fail
139
+ # false (indicating delete of id failed)
140
+ def get_profile(profileId)
141
+ super("/verificationProfiles/#{profileId}")
142
+ end
143
+
144
+ # each speaker(profile) must provide at least 3 enrollments to the api
145
+ # params
146
+ # profileId
147
+ # a valid profileId { String }
148
+ # audio_file_path
149
+ # path to the audio file { String }
150
+ # audio requirments => Wav, PCM, 16k rate, 16 bit sample rate, mono
151
+ #
152
+ # Microsoft API response
153
+ # 200 Response
154
+ # {
155
+ # "enrollmentStatus" : "Enrolled", // [Enrolled | Enrolling | Training]
156
+ # "enrollmentsCount":0,
157
+ # "remainingEnrollments" : 0,
158
+ # "phrase" : "Recognized verification phrase"
159
+ # }
160
+ #
161
+ # 500 Response
162
+ # {
163
+ # "error": {
164
+ # "code" : "InternalServerError",
165
+ # "message" : "SpeakerInvalid",
166
+ # }
167
+ # }
168
+ #
169
+ # returns
170
+ # success
171
+ # enrollment details { Hash }
172
+ # fail
173
+ # false
174
+ def create_enrollment(profileId, audio_file_path)
175
+ _method = :Post
176
+ _path = "/verificationProfiles/#{profileId}/enroll"
177
+ _headers = { }
178
+ _body = create_body_for_enrollment(audio_file_path)
179
+ _response = send_request(_path, _method, _headers, _body)
180
+
181
+ _response.code == 200 ? _response.parse : parse_error_response(_response)
182
+ end
183
+
184
+ # params
185
+ # profileId - required
186
+ # a valid id { String }
187
+ #
188
+ # Microsoft API response
189
+ # 200 Response
190
+ # "" (empty string)
191
+ #
192
+ # 500 Response
193
+ # {
194
+ # "error": {
195
+ # "code" : "InternalServerError",
196
+ # "message" : "SpeakerInvalid",
197
+ # }
198
+ # }
199
+ #
200
+ # returns
201
+ # success
202
+ # profile id that was deleted { String }
203
+ # fail
204
+ # false (indicating delete of enrollments failed)
205
+ def reset_all_enrollments_for_profile(profileId)
206
+ super("/verificationProfiles/#{profileId}/reset")
207
+ end
208
+
209
+ # params
210
+ # locale - required
211
+ # a valid query string (used to return phrases in selected language)
212
+ #
213
+ # Microsoft API response
214
+ # 200 Response
215
+ # [
216
+ # {"phrase"=>"i am going to make him an offer he cannot refuse"},
217
+ # {"phrase"=>"houston we have had a problem"},
218
+ # {"phrase"=>"my voice is my passport verify me"},
219
+ # {"phrase"=>"apple juice tastes funny after toothpaste"},
220
+ # {"phrase"=>"you can get in without your password"},
221
+ # {"phrase"=>"you can activate security system now"},
222
+ # {"phrase"=>"my voice is stronger than passwords"},
223
+ # {"phrase"=>"my password is not your business"},
224
+ # {"phrase"=>"my name is unknown to you"},
225
+ # {"phrase"=>"be yourself everyone else is already taken"}
226
+ # ]
227
+ #
228
+ # 500 Response
229
+ # {
230
+ # "error": {
231
+ # "code" : "InternalServerError",
232
+ # "message" : "SpeakerInvalid",
233
+ # }
234
+ # }
235
+ #
236
+ # returns
237
+ # success
238
+ # list of acceptable phrases { Array }
239
+ # fail
240
+ # false
241
+ def list_all_verification_phrases
242
+ _method = :Get
243
+ _locale = "en-us"
244
+ _path = "/verificationPhrases?locale=#{_locale}"
245
+ _headers = { "Content-Type" => "application/json" }
246
+ _response = send_request(_path, _method, _headers, nil)
247
+
248
+ _response.code == 200 ? _response.parse : parse_error_response(_response)
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,95 @@
1
+ require_relative "spec_helper"
2
+
3
+ describe VoiceId::Identification do
4
+
5
+ @profile = {
6
+ "identificationProfileId" => "111f427c-3791-468f-b709-fcef7660fff9",
7
+ "locale" => "en-US",
8
+ "enrollmentSpeechTime" => 0.0,
9
+ "remainingEnrollmentSpeechTime" => 0.0,
10
+ "createdDateTime" => "2015-04-23T18:25:43.511Z",
11
+ "lastActionDateTime" => "2015-04-23T18:25:43.511Z",
12
+ "enrollmentStatus" => "Enrolled"
13
+ }
14
+
15
+ before :each do
16
+ @identification = VoiceId::Identification.new("some_api_key")
17
+ @identification.api_base_url = "http://localhost:11988"
18
+ end
19
+
20
+
21
+ describe "#create_profile" do
22
+ it "should be able to create a new profile and return the details" do
23
+ expect(@identification.create_profile).to eql(@identification_profile)
24
+ end
25
+ end
26
+
27
+ describe "#delete_profile" do
28
+ it "should delete a profile and return true" do
29
+ profileId = "12345678"
30
+ expect(@identification.delete_profile(profileId)).to eql(true)
31
+ end
32
+ end
33
+
34
+ describe "#get_all_profiles" do
35
+ it "should return an array of all profiles" do
36
+ expect(@identification.get_all_profiles).to eql(@identification_profiles)
37
+ end
38
+ end
39
+
40
+ describe "#get_profile" do
41
+ it "should return a profile hash" do
42
+ profileId = "987654321"
43
+ expect(@identification.get_profile(profileId)).to eql(@profile)
44
+ end
45
+ end
46
+
47
+ describe "#create_enrollment" do
48
+ it "should create a new enrollment for a profile and return an operation url" do
49
+ data = { :form => { :file => "cool.wav" } }
50
+ profileId = "0991883883"
51
+ shortAudio = true
52
+ allow_any_instance_of(VoiceId::RequestHelpers).to receive(:create_body_for_enrollment).and_return(data)
53
+ expect(@identification.create_enrollment(profileId , shortAudio, '/path/to/some/audio_file.wav')).to eql("https://www.coolsite/operations/123456789")
54
+ end
55
+ end
56
+
57
+ describe "#reset_all_enrollments_for_profile" do
58
+ it "should return true if all enrollments for a profile was deleted" do
59
+ profileId = "0991883883"
60
+ expect(@identification.reset_all_enrollments_for_profile(profileId)).to eql(true)
61
+ end
62
+ end
63
+
64
+ describe "#get_operation_id" do
65
+ it "returns an operation id" do
66
+ operation_url = "https://api.projectoxford.ai/spid/v1.0/operations/995a8745-0098-4c12-9889-bad14859y7a4"
67
+ expect(@identification.get_operation_id(operation_url)).to eql("995a8745-0098-4c12-9889-bad14859y7a4")
68
+ end
69
+ end
70
+
71
+ describe "#identify_speaker" do
72
+ it "identifies a speaker using any audio snippet" do
73
+ data = { :form => { :file => "cool.wav" } }
74
+ profile_ids = ["profile_id1", "profile_id2"]
75
+ short_audio = true
76
+ audio_file_path = '/path/to/some/audio_file.wav'
77
+ allow_any_instance_of(VoiceId::RequestHelpers).to receive(:create_body_for_enrollment).and_return(data)
78
+ expect(@identification.identify_speaker(profile_ids, short_audio, audio_file_path)).to eql("https://www.coolsite/operations/123456789")
79
+ end
80
+ end
81
+
82
+ describe "#get_operation_status(operationId)" do
83
+ it "returns operation status" do
84
+ operation_id = "1234567890"
85
+ expect(@identification.get_operation_status(operation_id)).to eql(@operation_status)
86
+ end
87
+ end
88
+
89
+ describe "#parse_error_response" do
90
+ it "throws an error" do
91
+ @identification.api_base_url = "http://localhost:11988/error"
92
+ expect { @identification.create_profile }.to raise_error(VoiceId::RequestHelpers::InvalidApiRequestError, "oh no everything's going wrong today!")
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,122 @@
1
+ json_type = { "content-type" => "application/json" }
2
+ form_data_type = { "content-type" => "multipart/form-data"}
3
+ enrollment_operation = { "Operation-Location" => "https://www.coolsite/operations/123456789" }
4
+
5
+ @new_identification_profile = {
6
+ "identificationProfileId" => "49a36324-fc4b-4387-aa06-090cfbf0064f",
7
+ }
8
+
9
+ @identification_profile = {
10
+ "identificationProfileId" => "111f427c-3791-468f-b709-fcef7660fff9",
11
+ "locale" => "en-US",
12
+ "enrollmentSpeechTime" => 0.0,
13
+ "remainingEnrollmentSpeechTime" => 0.0,
14
+ "createdDateTime" => "2015-04-23T18:25:43.511Z",
15
+ "lastActionDateTime" => "2015-04-23T18:25:43.511Z",
16
+ "enrollmentStatus" => "Enrolled"
17
+ }
18
+
19
+ @identification_profiles = [
20
+ {
21
+ "identificationProfileId" => "111f427c-3791-468f-b709-fcef7660fff9",
22
+ "locale" => "en-US",
23
+ "enrollmentSpeechTime" => 0.0,
24
+ "remainingEnrollmentSpeechTime" => 0.0,
25
+ "createdDateTime" => "2015-04-23T18:25:43.511Z",
26
+ "lastActionDateTime" => "2015-04-23T18:25:43.511Z",
27
+ "enrollmentStatus" => "Enrolled"
28
+ }
29
+ ]
30
+
31
+ @new_verification_profile = {
32
+ "verificationProfileId" => "49a36324-fc4b-4387-aa06-090cfbf0064f",
33
+ }
34
+
35
+ @verification_profiles = [
36
+ {
37
+ "verificationProfileId" => "111f427c-3791-468f-b709-fcef7660fff9",
38
+ "locale" => "en-US",
39
+ "enrollmentsCount" => 2,
40
+ "remainingEnrollmentsCount" => 0,
41
+ "createdDateTime" => "2015-04-23T18:25:43.511Z",
42
+ "lastActionDateTime" => "2015-04-23T18:25:43.511Z",
43
+ "enrollmentStatus" => "Enrolled"
44
+ }
45
+ ]
46
+
47
+ @verification_profile = {
48
+ "verificationProfileId" => "111f427c-3791-468f-b709-fcef7660fff9",
49
+ "locale" => "en-US",
50
+ "enrollmentsCount" => 2,
51
+ "remainingEnrollmentsCount" => 0,
52
+ "createdDateTime" => "2015-04-23T18:25:43.511Z",
53
+ "lastActionDateTime" => "2015-04-23T18:25:43.511Z",
54
+ "enrollmentStatus" => "Enrolled"
55
+ }
56
+
57
+ @all_verification_phrases = [
58
+ {"phrase" => "i am going to make him an offer he cannot refuse"},
59
+ {"phrase" => "houston we have had a problem"},
60
+ {"phrase" => "my voice is my passport verify me"},
61
+ {"phrase" => "apple juice tastes funny after toothpaste"},
62
+ {"phrase" => "you can get in without your password"},
63
+ {"phrase" => "you can activate security system now"},
64
+ {"phrase" => "my voice is stronger than passwords"},
65
+ {"phrase" => "my password is not your business"},
66
+ {"phrase" => "my name is unknown to you"},
67
+ {"phrase" => "be yourself everyone else is already taken"}
68
+ ]
69
+
70
+ @verification_enrollment_response = {
71
+ "enrollmentStatus" => "Enrolling",
72
+ "enrollmentsCount" => 1,
73
+ "remainingEnrollments" => 2,
74
+ "phrase" => "i am going to make him an offer he cannot refuse"
75
+ }
76
+
77
+ @verify_speaker_result = {
78
+ "result" => "Accept",
79
+ "confidence" => "High",
80
+ "phrase" => "i am going to make him an offer he cannot refuse"
81
+ }
82
+
83
+ @operation_status = {
84
+ "status" => "succeeded",
85
+ "createdDateTime" => "2016-09-20T01:51:39.134487Z",
86
+ "lastActionDateTime" => "2016-09-20T01:51:41.4183611Z",
87
+ "processingResult" => {
88
+ "enrollmentStatus" => "Enrolled",
89
+ "remainingEnrollmentSpeechTime" => 0.0,
90
+ "speechTime" => 7.93, # useful speech duration
91
+ "enrollmentSpeechTime" => 31.72
92
+ }
93
+ }
94
+
95
+ error_response = {
96
+ "error" => {
97
+ "message" => "oh no everything's going wrong today!"
98
+ }
99
+ }
100
+
101
+ Mimic.mimic do
102
+ post("/error/identificationProfiles").returning(error_response.to_json, 400, json_type)
103
+ post("/error/verificationProfiles").returning(error_response.to_json, 400, json_type)
104
+
105
+ get("/identificationProfiles").returning(@identification_profiles.to_json, 200, json_type)
106
+ get("/operations/:operationId").returning(@operation_status.to_json, 200, json_type)
107
+ get("/identificationProfiles/:profileId").returning(@identification_profile.to_json, 200, json_type)
108
+ post("/identificationProfiles").returning(@new_identification_profile.to_json, 200, json_type)
109
+ post("/identificationProfiles/:profileId/reset").returning("".to_json, 200, json_type)
110
+ post("/identificationProfiles/:profileId/enroll").returning("".to_json, 202, json_type.merge(enrollment_operation))
111
+ post("/identify").returning("".to_json, 202, json_type.merge(enrollment_operation))
112
+ delete("/identificationProfiles/:profileId").returning("".to_json, 200, json_type)
113
+
114
+ get("/verificationProfiles").returning(@verification_profiles.to_json, 200, json_type)
115
+ get("/verificationProfiles/:profileId").returning(@verification_profile.to_json, 200, json_type)
116
+ get("/verificationPhrases").returning(@all_verification_phrases.to_json, 200, json_type)
117
+ post("/verificationProfiles").returning(@new_verification_profile.to_json, 200, json_type)
118
+ post("/verificationProfiles/:profileId/reset").returning("".to_json, 200, json_type)
119
+ post("/verificationProfiles/:profileId/enroll").returning(@verification_enrollment_response.to_json, 200, json_type)
120
+ post("/verify").returning(@verify_speaker_result, 200, json_type)
121
+ delete("/verificationProfiles/:profileId").returning("".to_json, 200, json_type)
122
+ end