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 cstephen, 2 years ago

Add macroniser scaffolding.
Update translations.

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
7import { log } from "./Util"
8
9/* eslint-disable camelcase */
10
11export 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
26export 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
47export 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 */
69export 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.