source:
main/trunk/model-interfaces-dev/atea/macron-restoration/src/js/TranscribeModule.js@
35714
Last change on this file since 35714 was 35714, checked in by , 2 years ago | |
---|---|
File size: 10.0 KB |
Line | |
---|---|
1 | /** |
2 | * @file Defines components that are used to interact with the transcription proxy servlet. |
3 | * @author Carl Stephens |
4 | * @module |
5 | */ |
6 | |
7 | import { log } from "./Util" |
8 | |
9 | /* eslint-disable camelcase */ |
10 | |
11 | export class TranscriptionMetadata { |
12 | /** |
13 | * A metadata object held by a {@link TranscriptionModel}. |
14 | * |
15 | * @param {String} char The character. |
16 | * @param {Number} confidence The confidence that the character is correct. Smaller values are more confident. |
17 | * @param {Number} start_time The timestamp in the audio at which this character is spoken. |
18 | */ |
19 | constructor(char, confidence, start_time) { |
20 | this.char = char; |
21 | this.confidence = confidence; |
22 | this.start_time = start_time; |
23 | } |
24 | } |
25 | |
26 | export class TranscriptionModel { |
27 | /** |
28 | * The transcription object returned from our transcription endpoint. |
29 | * |
30 | * @param {String} file_name The name of the file that was transcribed. |
31 | * @param {String} log A note of how the transcription was processed. |
32 | * @param {TranscriptionMetadata[]} metadata The character metadata. |
33 | * @param {boolean} success A value indicating if the transcription was successful or not. |
34 | * @param {String} transcription The transcription. |
35 | */ |
36 | constructor(file_name, log, metadata, success, transcription) { |
37 | this.file_name = file_name; |
38 | this.log = log; |
39 | this.metadata = metadata; |
40 | this.success = success; |
41 | this.transcription = transcription; |
42 | } |
43 | } |
44 | |
45 | /* eslint-enable camelcase */ |
46 | |
47 | export class TranscriptionError extends Error { |
48 | /** |
49 | * Initialises a new instance of the {@link TranscriptionError} object. |
50 | * |
51 | * @param {Number | undefined} statusCode The HTTP status code of the error. |
52 | * @param {String | undefined} message The status message. |
53 | * @param {String | null} fileName The file on which the transcription failed. |
54 | */ |
55 | constructor(message = undefined, fileName = null, statusCode = -1) { |
56 | super(message); |
57 | |
58 | /** @type {String | null} The name of the file that the transcription error occured on. */ |
59 | this.fileName = fileName; |
60 | |
61 | /** @type {Number | undefined} The status code returned by the API when the erro was generated. */ |
62 | this.statusCode = statusCode; |
63 | } |
64 | } |
65 | |
66 | /** |
67 | * A service that uploads audio files in a multipart request to the Korero Maori API Interface servlet, and interprets the result. |
68 | */ |
69 | export default class TranscribeService { |
70 | constructor() { |
71 | /* eslint-disable quotes */ |
72 | this.TEST_JSON = '{"file_name":"akl_mi_pk_0002.wav","log":"Took 0.6s to transcribe 8.7s audio file /tmp/transcribe-aw7ujpu8-clean.wav","metadata":[{"char":"k","prob":7.0E-6,"start_time":0.24},{"char":"o","prob":8.3E-5,"start_time":0.26},{"char":"t","prob":0.995578,"start_time":0.42},{"char":"a","prob":0.997089,"start_time":0.44},{"char":"h","prob":3.0E-6,"start_time":0.54},{"char":"i","prob":8.0E-6,"start_time":0.56},{"char":" ","prob":2.2E-5,"start_time":0.6},{"char":"a","prob":0.982987,"start_time":0.64},{"char":"n","prob":5.0E-6,"start_time":0.8},{"char":"Å","prob":1.82E-4,"start_time":0.84},{"char":" ","prob":0.996944,"start_time":1.08},{"char":"t","prob":1.23E-4,"start_time":1.14},{"char":"e","prob":6.3E-5,"start_time":1.16},{"char":" ","prob":0.587595,"start_time":1.28},{"char":"t","prob":4.91E-4,"start_time":1.34},{"char":"u","prob":0.010654,"start_time":1.36},{"char":"p","prob":5.9E-5,"start_time":1.52},{"char":"u","prob":1.26E-4,"start_time":1.54},{"char":"n","prob":0.0,"start_time":1.7},{"char":"a","prob":0.99997,"start_time":1.72},{"char":" ","prob":0.993724,"start_time":2.14},{"char":"o","prob":4.3E-5,"start_time":2.22},{"char":" ","prob":0.99696,"start_time":2.32},{"char":"t","prob":1.4E-5,"start_time":2.36},{"char":"e","prob":3.8E-5,"start_time":2.38},{"char":" ","prob":0.996606,"start_time":2.48},{"char":"t","prob":0.0,"start_time":2.52},{"char":"a","prob":0.994873,"start_time":2.54},{"char":"Å","prob":2.9E-5,"start_time":2.64},{"char":"a","prob":0.994639,"start_time":2.66},{"char":"t","prob":3.0E-6,"start_time":2.8},{"char":"a","prob":0.99995,"start_time":2.82},{"char":" ","prob":0.0,"start_time":2.9},{"char":"m","prob":0.999606,"start_time":2.98},{"char":"Ä","prob":0.010628,"start_time":3.0},{"char":"o","prob":0.996415,"start_time":3.2},{"char":"r","prob":0.0,"start_time":3.28},{"char":"i","prob":0.999698,"start_time":3.32},{"char":" ","prob":0.997927,"start_time":4.5},{"char":"k","prob":0.0,"start_time":4.58},{"char":"o","prob":3.2E-5,"start_time":4.6},{"char":" ","prob":0.956949,"start_time":4.7},{"char":"r","prob":2.5E-5,"start_time":4.74},{"char":"a","prob":0.997649,"start_time":4.76},{"char":"Å","prob":0.0,"start_time":4.9},{"char":"i","prob":1.63E-4,"start_time":4.92},{"char":"n","prob":1.3E-5,"start_time":5.28},{"char":"u","prob":4.0E-6,"start_time":5.3},{"char":"i","prob":0.99997,"start_time":5.42},{"char":" ","prob":0.994305,"start_time":5.78},{"char":"e","prob":3.1E-5,"start_time":5.84},{"char":" ","prob":0.966097,"start_time":6.04},{"char":"t","prob":2.0E-6,"start_time":6.1},{"char":"Å«","prob":5.4E-5,"start_time":6.12},{"char":" ","prob":0.837368,"start_time":6.34},{"char":"n","prob":0.0,"start_time":6.38},{"char":"e","prob":0.004238,"start_time":6.4},{"char":"i","prob":0.99966,"start_time":6.5},{"char":" ","prob":0.99728,"start_time":7.28},{"char":"o","prob":6.28E-4,"start_time":7.36},{"char":" ","prob":0.961049,"start_time":7.48},{"char":"p","prob":5.0E-6,"start_time":7.52},{"char":"a","prob":0.984707,"start_time":7.54},{"char":"p","prob":9.0E-6,"start_time":7.66},{"char":"a","prob":0.998754,"start_time":7.68},{"char":"t","prob":0.002356,"start_time":7.88},{"char":"Å«","prob":1.24E-4,"start_time":7.9},{"char":"Ä","prob":0.883958,"start_time":8.0},{"char":"n","prob":0.985871,"start_time":8.24},{"char":"u","prob":0.907369,"start_time":8.26},{"char":"k","prob":0.999045,"start_time":8.38},{"char":"u","prob":0.995632,"start_time":8.4}],"success":true,"transcription":"kotahi anÅ te tupuna o te tangata mÄori ko ranginui e tÅ« nei o papatÅ«Änuku"}'; |
73 | /* eslint-enable quotes */ |
74 | |
75 | /** @type {String} The URL to which query POST requests should be made. */ |
76 | if (process.env.NODE_ENV !== "production") { |
77 | this.queryUrl = "//localhost:8383/gs3-koreromaori/transcribe"; |
78 | } |
79 | else { |
80 | this.queryUrl = "/gs3-koreromaori/transcribe"; |
81 | } |
82 | |
83 | /** @type {Number} The maximum number of files which can be transcribed in one request to the API. */ |
84 | this.MAX_BATCH_COUNT = 3; |
85 | |
86 | /** @type {Number} The soft upper limit on how many bytes can be submitted in one request. */ |
87 | this.BATCH_BYTE_LIMIT = 5242880; // 5 MiB |
88 | } |
89 | |
90 | /** |
91 | * Performs chunked queries to transcribe the given audio files, returning the data in iterations. |
92 | * Data is chunked according to which ever occurs first: |
93 | * A maximum of three files per request, or; |
94 | * A maximum of 5 MiB before chunking. |
95 | * |
96 | * @param {FileList | File[]} files The files to upload |
97 | * @returns {AsyncGenerator<TranscriptionModel[]>} The transcribed audio files. |
98 | * @throws {TranscriptionError} When the transcription request fails to complete. |
99 | */ |
100 | async* batchTranscribeFiles(files) { |
101 | let filesToSubmit = []; |
102 | let fileCounter = 0; |
103 | let byteCounter = 0; |
104 | |
105 | for (const file of files) { |
106 | if (fileCounter === this.MAX_BATCH_COUNT || byteCounter > this.BATCH_BYTE_LIMIT) { // 5 MiB |
107 | yield await this.transcribeFiles(filesToSubmit); |
108 | filesToSubmit = []; |
109 | byteCounter = 0; |
110 | fileCounter = 0; |
111 | } |
112 | |
113 | filesToSubmit[fileCounter++] = file; |
114 | byteCounter += file.size; |
115 | } |
116 | |
117 | if (filesToSubmit.length > 0) { |
118 | yield await this.transcribeFiles(filesToSubmit); |
119 | } |
120 | } |
121 | |
122 | /** |
123 | * Performs a query to transcribe the given audio files. |
124 | * |
125 | * @param {File[]} files The files to upload. |
126 | * @returns {Promise<TranscriptionModel[]>} The transcribed audio file. |
127 | * @throws {TranscriptionError} When the transcription request fails to complete. |
128 | */ |
129 | async transcribeFiles(files) { |
130 | if (process.env.NODE_ENV !== "production" || files.some(file => file.name === "akl_mi_pk_0002_offline.wav")) { |
131 | return await this.transcribeFilesTest(files); |
132 | } |
133 | else { |
134 | return await this.transcribeFilesActual(files); |
135 | } |
136 | } |
137 | |
138 | async transcribeFilesTest(files) { |
139 | const objects = []; |
140 | |
141 | for (const file of files) { |
142 | const object = JSON.parse(this.TEST_JSON); |
143 | object.file_name = file.name; |
144 | objects.push(object); |
145 | } |
146 | |
147 | return objects; |
148 | } |
149 | |
150 | /** |
151 | * Performs a query to transcribe the given audio files. |
152 | * |
153 | * @param {FileList | File[]} files The files to upload. |
154 | * @returns {Promise<TranscriptionModel[]>} The transcribed audio file. |
155 | * @throws {TranscriptionError} When the transcription request fails to complete. |
156 | */ |
157 | async transcribeFilesActual(files) { |
158 | const that = this; |
159 | const formData = new FormData(); |
160 | |
161 | let audioFileKeys = ""; |
162 | for (let i = 0; i < files.length; i++) { |
163 | const f = files[i]; |
164 | const key = "audioFile" + i; |
165 | |
166 | formData.append(key, f, f.name); |
167 | audioFileKeys += key + "|"; |
168 | } |
169 | formData.append("audioFileKeys", audioFileKeys); |
170 | |
171 | try { |
172 | const response = await fetch( |
173 | that.queryUrl, |
174 | { |
175 | method: "POST", |
176 | body: formData |
177 | } |
178 | ); |
179 | |
180 | if (!response.ok) { |
181 | log(`Transcription API failed with status ${response.status} and message ${response.statusText}`, "error") |
182 | throw new TranscriptionError(response.statusText, undefined, response.status); |
183 | } |
184 | |
185 | return await response.json(); |
186 | } |
187 | catch (e) { |
188 | log(`Transcription failed with reason ${e}`, "error"); |
189 | throw new TranscriptionError(undefined, "Unknown"); |
190 | } |
191 | } |
192 | } |
Note:
See TracBrowser
for help on using the repository browser.