source: main/trunk/model-interfaces-dev/atea/js/asr/asr-controller.js@ 35259

Last change on this file since 35259 was 35259, checked in by davidb, 3 years ago

Implement support for switching between audio files

File size: 7.2 KB
Line 
1/**
2 * @file Controller logic for asr.xsl.
3 * @author Carl Stephens
4 */
5// @ts-nocheck
6
7/**
8* The name of the file input that audio files can be uploaded to.
9*/
10const FILE_UPLOAD_INPUT_NAME = "#fileUpload";
11
12/**
13* The name of the container that holds the progress display for the audio upload.
14*/
15const FILE_UPLOAD_PROGRESS_CONTAINER_NAME = "#prgFileUploadContainer";
16
17/**
18* @callback loadScriptCallback
19* @param {Event} ev The event
20* @returns {void}
21*/
22
23/**
24* Loads a remote script.
25* Found at https://stackoverflow.com/questions/950087/how-do-i-include-a-javascript-file-in-another-javascript-file
26*
27* @param {string} url The URL from which to load the script.
28*/
29function loadScript(url, callback = () => {})
30{
31 var body = document.body;
32
33 var script = document.createElement('script');
34 script.type = 'text/javascript';
35 script.src = url;
36
37 script.onload = callback;
38
39 body.appendChild(script);
40}
41
42var transcribeService;
43loadScript("./interfaces/atea/js/asr/TranscribeService.js", () => {
44 transcribeService = new TranscribeService();
45});
46
47/** @type {HTMLAudioElement} */
48const transcriptionAudioElement = document.getElementById("transcriptionAudio");
49
50/** @type {HTMLSourceElement} */
51const transcriptionAudioSourceElement = document.getElementById("transcriptionAudioSource");
52
53let cachedAudioFileList = new Map();
54
55async function doAudioUpload()
56{
57 /** @type {FileList} */
58 const files = $(FILE_UPLOAD_INPUT_NAME)[0].files;
59
60 // Disable the file input while transcribing, and show the progress bar
61 $(FILE_UPLOAD_INPUT_NAME).prop("disabled", true);
62 $(FILE_UPLOAD_PROGRESS_CONTAINER_NAME).removeClass("asr-hidden");
63
64 clearTranscriptions();
65
66 // Cache the file list so that we can playback audio in the future
67 for (const file of files) {
68 cachedAudioFileList.set(file.name, file)
69 }
70
71 // Transcribe each audio file in batches.
72 try
73 {
74 for await (const batch of transcribeService.batchTranscribeFiles(files))
75 {
76 for (const t of batch)
77 {
78 if (!t.success) {
79 insertError(t.file_name, t.log);
80 }
81 else {
82 insertTranscription(t);
83 }
84 }
85 }
86 }
87 catch (e)
88 {
89 console.error("Failed to transcribe files: " + e);
90 insertError("all", e.statusMessage);
91 }
92
93 // Hide the progress bar and re-enable the file input.
94 $(FILE_UPLOAD_PROGRESS_CONTAINER_NAME).addClass("asr-hidden");
95 $(FILE_UPLOAD_INPUT_NAME).prop("disabled", false);
96}
97
98/** @type {HTMLUListElement} */
99const transcriptionsList = document.getElementById("transcriptionsList");
100
101/**
102 * Removes any transcriptions from the UI list.
103 */
104function clearTranscriptions()
105{
106 cachedAudioFileList = new Map();
107 while (transcriptionsList.lastChild) {
108 transcriptionsList.removeChild(transcriptionsList.lastChild);
109 }
110}
111
112/** @type {HTMLTemplateElement} */
113const transcriptionTemplate = document.getElementById("transcriptionTemplate");
114
115/**
116* Inserts a transcription object into the DOM.
117* Adapted from https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template
118*
119* @param {TranscriptionModel} transcription The transcription to insert.
120*/
121function insertTranscription(transcription)
122{
123 // Insert a new transcription row
124 const clone = transcriptionTemplate.content.firstElementChild.cloneNode(true);
125
126 /** @type {HTMLSpanElement[]} */
127 const spans = clone.querySelectorAll("span");
128 spans[0].textContent = transcription.file_name;
129 spans[1].textContent = transcription.transcription;
130
131 // Get the metadata list and template
132 /** @type {HTMLUListElement} */
133 const metadataList = clone.querySelector("#metadataList");
134
135 // Insert metadata rows
136 for (const metadata of transcription.metadata) {
137 metadataList.appendChild(buildMetadataNode(metadata, transcription.file_name));
138 }
139
140 transcriptionsList.appendChild(clone);
141}
142
143/** @type {HTMLTemplateElement} */
144const metadataTemplate = document.getElementById("metadataTemplate");
145
146/**
147 * Builds a metadata node.
148 *
149 * @param {{char: string, confidence: number, start_time: number}} metadata The metadata used to create the node.
150 * @param {String} fileName The file that this metadata was generated from.
151 * @returns {Node} The constructed DOM node.
152 */
153function buildMetadataNode(metadata, fileName)
154{
155 const metadataClone = metadataTemplate.content.firstElementChild.cloneNode(true);
156
157 /** @type {HTMLParagraphElement} */
158 const p = metadataClone.querySelector("p");
159
160 /** @type {HTMLButtonElement} */
161 const button = metadataClone.querySelector("button");
162
163 button.dataset.fileName = fileName;
164 button.onclick = function(e)
165 {
166 // Have to traverse the event path, because the event target is the child element of the button.
167 const requestedAudioFile = e.composedPath().find((t) => t instanceof HTMLButtonElement).dataset.fileName;
168 const currentAudioFile = transcriptionAudioSourceElement.dataset.fileName;
169
170 // Load the appropiate audio if necessary.
171 if (currentAudioFile != requestedAudioFile)
172 {
173 if (currentAudioFile) {
174 URL.revokeObjectURL(currentAudioFile);
175 }
176
177 const urlObject = URL.createObjectURL(cachedAudioFileList.get(requestedAudioFile));
178 transcriptionAudioSourceElement.src = urlObject;
179 transcriptionAudioSourceElement.dataset.fileName = requestedAudioFile;
180 transcriptionAudioElement.load();
181 }
182
183 transcriptionAudioElement.currentTime = metadata.start_time;
184 transcriptionAudioElement.play();
185 }
186
187 if (metadata.char == ' ') {
188 p.textContent = "\u00A0 " // This helps with formatting, as elements that only contain whitespace are collapsed.
189 }
190 else {
191 p.textContent = metadata.char;
192 }
193
194 return metadataClone;
195}
196
197/** @type {HTMLTemplateElement} */
198const errorTemplate = document.querySelector("#errorTemplate");
199
200/**
201* Inserts a transcription error into the DOM.
202*
203* @param {String} fileName The file for which an error occured.
204* @param {String | null} statusMessage An informative error message to display.
205*/
206function insertError(fileName, statusMessage)
207{
208 const clone = errorTemplate.content.firstElementChild.cloneNode(true);
209
210 /** @type {HTMLSpanElement[]} */
211 const spans = clone.querySelectorAll("span");
212 spans[0].textContent = statusMessage;
213 spans[1].textContent = fileName;
214
215 transcriptionsList.appendChild(clone);
216}
217
218// Ensure the transcribe button is disabled when there are no files selected.
219$(FILE_UPLOAD_INPUT_NAME).on("input", e =>
220{
221 if (e.target.files.length <= 0) {
222 $("#btnFileUpload").prop("disabled", true);
223 }
224 else {
225 $("#btnFileUpload").prop("disabled", false);
226 }
227});
228
229// whenever we hover over a menu item that has a submenu
230$('.tooltip-parent').on('mouseover', function() {
231 var $menuItem = $(this),
232 $submenuWrapper = $('> .tooltip-wrapper', $menuItem);
233
234 var menuItemPos = $menuItem.position();
235
236 $submenuWrapper.css({
237 top: menuItemPos.top,
238 left: menuItemPos.left + Math.round($menuItem.outerWidth() * 0.75)
239 });
240});
241
242// $(function() {
243
244// });
Note: See TracBrowser for help on using the repository browser.