Changeset 35282 for main/trunk/model-interfaces-dev
- Timestamp:
- 2021-08-11T14:11:58+12:00 (3 years ago)
- Location:
- main/trunk/model-interfaces-dev/atea
- Files:
-
- 1 added
- 1 deleted
- 6 edited
Legend:
- Unmodified
- Added
- Removed
-
main/trunk/model-interfaces-dev/atea/js/asr/TranscribeService.js
r35270 r35282 106 106 try 107 107 { 108 response = await fetch 109 ( 108 response = await fetch( 110 109 that.queryUrl, 111 110 { -
main/trunk/model-interfaces-dev/atea/js/asr/asr-controller.js
r35276 r35282 4 4 */ 5 5 6 /**7 * The name of the file input that audio files can be uploaded to.8 */9 const FILE_UPLOAD_INPUT_NAME = "#audioFileUpload";10 11 /**12 * The name of the container that holds the progress display for the audio upload.13 */14 const FILE_UPLOAD_PROGRESS_CONTAINER_NAME = "#prgFileUploadContainer";15 16 6 /** @type {HTMLAudioElement} */ 17 7 // @ts-ignore … … 32 22 /** @type {HTMLTemplateElement} */ 33 23 // @ts-ignore 34 const METADATA_TEMPLATE = document.getElementById("metadataTemplate"); 35 36 /** @type {HTMLTemplateElement} */ 37 // @ts-ignore 38 const ERROR_TEMPLATE = document.querySelector("#errorTemplate"); 24 const ERROR_TEMPLATE = document.getElementById("errorTemplate"); 39 25 40 26 let cachedAudioFileList = new Map(); … … 78 64 } 79 65 80 var transcribeService; 66 var transcribeService;// = new TranscribeService(); 81 67 loadScript("./interfaces/atea/js/asr/TranscribeService.js", () => { 82 68 transcribeService = new TranscribeService(); 83 69 }); 84 70 85 async function doAudioUpload() 71 // @ts-ignore 72 const AudioUploadComponent = Vue.createApp( 73 { 74 data() 75 { 76 return { 77 files: undefined, 78 canTranscribe: false, 79 isTranscribing: false 80 } 81 }, 82 methods: 83 { 84 openFilePicker() { 85 document.getElementById("audioFileInput")?.click(); 86 }, 87 onFilesChanged() 88 { 89 /** @type {HTMLInputElement} */ 90 // @ts-ignore | Object could be null 91 const audioFileInput = document.getElementById("audioFileInput"); 92 93 if (audioFileInput.files?.length != undefined && audioFileInput.files?.length > 0) { 94 this.canTranscribe = true; 95 this.files = audioFileInput?.files 96 } 97 else { 98 this.canTranscribe = false; 99 this.files = undefined; 100 } 101 }, 102 async doTranscription() { 103 await getTranscriptions(); 104 } 105 } 106 }); 107 const AudioUploadVM = AudioUploadComponent.mount("#audioUploadContainer"); 108 109 /** 110 * When awaited, creates a delay for the given number of milliseconds. 111 * 112 * @param {Number} delayInms The number of milliseconds to delay for. 113 * @returns A promise that will resolve when the delay has finished. 114 */ 115 function delay(delayInms) 116 { 117 return new Promise( 118 resolve => 119 { 120 setTimeout( 121 () => { 122 resolve(2); 123 }, 124 delayInms 125 ); 126 } 127 ); 128 } 129 130 /** 131 * Gets the transcription of each submitted audio file. 132 */ 133 async function getTranscriptions() 86 134 { 87 135 /** @type {FileList} */ 88 // @ts-ignore 89 const files = $(FILE_UPLOAD_INPUT_NAME)[0].files; 90 91 // Disable the file input while transcribing, and show the progress bar 92 $(FILE_UPLOAD_INPUT_NAME).prop("disabled", true); 93 $(FILE_UPLOAD_PROGRESS_CONTAINER_NAME).removeClass("asr-hidden"); 94 136 const files = AudioUploadVM.files; 137 138 AudioUploadVM.isTranscribing = true; 139 await delay(1000); // TODO: Remove - UI testing purposes only 95 140 // Cache the file list so that we can playback audio in the future 96 141 for (const file of files) { … … 121 166 } 122 167 123 // Hide the progress bar and re-enable the file input. 124 $(FILE_UPLOAD_PROGRESS_CONTAINER_NAME).addClass("asr-hidden"); 125 $(FILE_UPLOAD_INPUT_NAME).prop("disabled", false); 168 AudioUploadVM.isTranscribing = false; 126 169 } 127 170 … … 170 213 audioSlider.step = "0.01"; 171 214 172 // Get the metadata list173 /** @type {HTMLUListElement} */174 // @ts-ignore175 const metadataList = clone.querySelector(".metadata-list");176 177 // Insert metadata rows178 for (const metadata of transcription.metadata) {179 metadataList.appendChild(buildMetadataNode(metadata));180 }181 182 215 // Set the filename data property on every element in the clone's DOM node 183 216 recurseAddData(clone, "file-name", transcription.file_name); … … 187 220 TRANSCRIPTIONS_LIST.appendChild(clone); 188 221 222 const TranscriptionWordListComponent = Vue.createApp( 223 { 224 data() 225 { 226 return { 227 lines: [] 228 } 229 } 230 }); 231 const TranscriptionWordListVM = TranscriptionWordListComponent.mount("#transcriptionWordList"); 232 233 transcription.transcription.replace('', "\u00A0"); // This helps with formatting, as whitespace is trimmed. 234 189 235 // Get the amount of space that we have to align words within 190 236 /** @type {HTMLDivElement} */ … … 193 239 const charsPerLine = Math.floor(transcriptionTextList.clientWidth / MONOSPACE_CHAR_SIZE); 194 240 195 for (let i = 0; i < transcription.transcription.length; i += 0) 241 // Insert words with correct line alignment 242 const tText = transcription.transcription; 243 244 for (let i = 0; i < tText.length; i += 0) 196 245 { 197 246 /** @type {String} */ 198 247 let slice; 199 248 200 if (i + charsPerLine < t ranscription.transcription.length)201 { 202 slice = t ranscription.transcription.slice(i, charsPerLine);203 204 if (t ranscription.transcription.charAt(i) != ' ' && transcription.transcription.charAt(i + 1) != ' ') {249 if (i + charsPerLine < tText.length) 250 { 251 slice = tText.slice(i, charsPerLine); 252 253 if (tText.charAt(i) != ' ' && tText.charAt(i + 1) != ' ') { 205 254 let lastSpacePos = slice.lastIndexOf(' '); 206 255 let decrement = slice.length - lastSpacePos; … … 211 260 else 212 261 { 213 slice = transcription.transcription.slice(i); 214 } 215 216 slice.lastIndexOf(' '); 217 218 let lineNode = document.createElement("p"); 219 lineNode.textContent = slice; 220 221 transcriptionTextList.appendChild(lineNode); 262 slice = tText.slice(i); 263 } 264 265 TranscriptionWordListVM.lines.push({ words: slice.split(' ') }); 222 266 i += charsPerLine; 223 267 } 224 225 }226 227 /**228 * Builds a metadata node.229 *230 * @param {{char: string, confidence: number, start_time: number}} metadata The metadata used to create the node.231 * @returns {Node} The constructed DOM node.232 */233 function buildMetadataNode(metadata)234 {235 const metadataClone = METADATA_TEMPLATE.content.firstElementChild.cloneNode(true);236 237 /** @type {HTMLParagraphElement} */238 const p = metadataClone.querySelector("p");239 240 /** @type {HTMLButtonElement} */241 const button = metadataClone.querySelector("button");242 243 button.addEventListener("click", function(e)244 {245 // Have to traverse the event path, because the event target is the child element of the button.246 const requestedAudioFile = e.composedPath().find((t) => t instanceof HTMLButtonElement).dataset.fileName;247 loadAudioFile(requestedAudioFile);248 249 TRANSCRIPTION_AUDIO_ELEMENT.currentTime = metadata.start_time;250 TRANSCRIPTION_AUDIO_ELEMENT.play();251 });252 253 if (metadata.char == ' ') {254 p.textContent = "\u00A0 " // This helps with formatting, as elements that only contain whitespace are collapsed.255 }256 else {257 p.textContent = metadata.char;258 }259 260 return metadataClone;261 268 } 262 269 … … 337 344 TRANSCRIPTIONS_LIST.appendChild(clone); 338 345 } 339 340 $(FILE_UPLOAD_INPUT_NAME).on("input", e =>341 {342 if (e.target.files.length <= 0) {343 $("#btnBeginTranscription").prop("disabled", true);344 }345 else {346 $("#btnBeginTranscription").prop("disabled", false);347 }348 }); -
main/trunk/model-interfaces-dev/atea/package-lock.json
r35276 r35282 5 5 "requires": true, 6 6 "dependencies": { 7 "@babel/helper-validator-identifier": { 8 "version": "7.14.9", 9 "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", 10 "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==" 11 }, 12 "@babel/parser": { 13 "version": "7.15.2", 14 "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.2.tgz", 15 "integrity": "sha512-bMJXql1Ss8lFnvr11TZDH4ArtwlAS5NG9qBmdiFW2UHHm6MVoR+GDc5XE2b9K938cyjc9O6/+vjjcffLDtfuDg==" 16 }, 17 "@babel/types": { 18 "version": "7.15.0", 19 "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.0.tgz", 20 "integrity": "sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==", 21 "requires": { 22 "@babel/helper-validator-identifier": "^7.14.9", 23 "to-fast-properties": "^2.0.0" 24 } 25 }, 7 26 "@types/jquery": { 8 27 "version": "3.5.6", … … 25 44 "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", 26 45 "dev": true 46 }, 47 "@vue/compiler-core": { 48 "version": "3.2.1", 49 "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.1.tgz", 50 "integrity": "sha512-UEJf2ZGww5wGVdrWIXIZo04KdJFGPmI2bHRUsBZ3AdyCAqJ5ykRXKOBn1OR1hvA2YzimudOEyHM+DpbBv91Kww==", 51 "requires": { 52 "@babel/parser": "^7.12.0", 53 "@babel/types": "^7.12.0", 54 "@vue/shared": "3.2.1", 55 "estree-walker": "^2.0.1", 56 "source-map": "^0.6.1" 57 } 58 }, 59 "@vue/compiler-dom": { 60 "version": "3.2.1", 61 "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.1.tgz", 62 "integrity": "sha512-tXg8tkPb3j54zNfWqoao9T1JI41yWPz8TROzmif/QNNA46eq8/SRuRsBd36i47GWaz7mh+yg3vOJ87/YBjcMyQ==", 63 "requires": { 64 "@vue/compiler-core": "3.2.1", 65 "@vue/shared": "3.2.1" 66 } 67 }, 68 "@vue/reactivity": { 69 "version": "3.2.1", 70 "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.1.tgz", 71 "integrity": "sha512-4Lja2KmyiKvuraDed6dXK2A6+r/7x7xGDA7vVR2Aqc8hQVu0+FWeVX+IBfiVOSpbZXFlHLNmCBFkbuWLQSlgxg==", 72 "requires": { 73 "@vue/shared": "3.2.1" 74 } 75 }, 76 "@vue/runtime-core": { 77 "version": "3.2.1", 78 "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.1.tgz", 79 "integrity": "sha512-IsgelRM/5hYeRhz5+ECi66XvYDdjG2t4lARjHvCXw5s9Q4N6uIbjLMwtLzAWRxYf3/y258BrD+ehxAi943ScJg==", 80 "requires": { 81 "@vue/reactivity": "3.2.1", 82 "@vue/shared": "3.2.1" 83 } 84 }, 85 "@vue/runtime-dom": { 86 "version": "3.2.1", 87 "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.1.tgz", 88 "integrity": "sha512-bUAHUSe49A5wYdHQ8wsLU1CMPXaG2fRuv2661mx/6Q9+20QxglT3ss8ZeL6AVRu16JNJMcdvTTsNpbnMbVc/lQ==", 89 "requires": { 90 "@vue/runtime-core": "3.2.1", 91 "@vue/shared": "3.2.1", 92 "csstype": "^2.6.8" 93 } 94 }, 95 "@vue/shared": { 96 "version": "3.2.1", 97 "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.1.tgz", 98 "integrity": "sha512-INN92dVBNgd0TW9BqfQQKx/HWGCHhUUbAV5EZ5FgSCiEdwuZsJbGt1mdnaD9IxGhpiyOjP2ClxGG8SFp7ELcWg==" 27 99 }, 28 100 "abbrev": { … … 222 294 "dev": true 223 295 }, 296 "csstype": { 297 "version": "2.6.17", 298 "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.17.tgz", 299 "integrity": "sha512-u1wmTI1jJGzCJzWndZo8mk4wnPTZd1eOIYTYvuEyOQGfmDl3TrabCCfKnOC86FZwW/9djqTl933UF/cS425i9A==" 300 }, 224 301 "dateformat": { 225 302 "version": "3.0.3", … … 263 340 "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 264 341 "dev": true 342 }, 343 "estree-walker": { 344 "version": "2.0.2", 345 "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 346 "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" 265 347 }, 266 348 "eventemitter2": { … … 1230 1312 } 1231 1313 }, 1314 "source-map": { 1315 "version": "0.6.1", 1316 "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 1317 "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" 1318 }, 1232 1319 "sprintf-js": { 1233 1320 "version": "1.1.2", … … 1271 1358 } 1272 1359 }, 1360 "to-fast-properties": { 1361 "version": "2.0.0", 1362 "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", 1363 "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" 1364 }, 1273 1365 "to-regex-range": { 1274 1366 "version": "5.0.1", … … 1311 1403 } 1312 1404 }, 1405 "vue": { 1406 "version": "3.2.1", 1407 "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.1.tgz", 1408 "integrity": "sha512-0jhXluF5mzTAK5bXw/8yq4McvsI8HwEWI4cnQwJeN8NYGRbwh9wwuE4FNv1Kej9pxBB5ajTNsWr0M6DPs5EJZg==", 1409 "requires": { 1410 "@vue/compiler-dom": "3.2.1", 1411 "@vue/runtime-dom": "3.2.1", 1412 "@vue/shared": "3.2.1" 1413 } 1414 }, 1313 1415 "websocket-driver": { 1314 1416 "version": "0.7.4", -
main/trunk/model-interfaces-dev/atea/package.json
r35276 r35282 16 16 "load-grunt-tasks": "^5.1.0", 17 17 "sass": "^1.37.5" 18 }, 19 "dependencies": { 20 "vue": "^3.2.1" 18 21 } 19 22 } -
main/trunk/model-interfaces-dev/atea/style/asr.scss
r35276 r35282 172 172 } 173 173 174 .asr-hidden {175 display: none;176 }177 178 174 #btnBeginTranscription { 179 175 float: right; … … 230 226 margin-bottom: 0; 231 227 } 232 }233 234 .metadata-list {235 font-size: 16px;236 padding: 0;237 list-style-type: none;238 overflow-x: auto;239 overflow-y: hidden;240 display: flex;241 }242 243 .metadata-list-container {244 position: relative;245 }246 247 .metadata-list-item {248 z-index: 1;249 width: 1.5em;250 text-align: center;251 vertical-align: middle;252 253 p {254 margin: 0;255 }256 257 button {258 padding: 0;259 margin: 0;260 }261 }262 263 .metadata-list-item .spaced-block {264 width: 100%;265 border-right: 1px solid black;266 267 &:last-child {268 margin-top: -10%;269 margin-bottom: 20%;270 border-right: none;271 }272 228 } 273 229 -
main/trunk/model-interfaces-dev/atea/transform/pages/asr.xsl
r35276 r35282 6 6 xmlns:gsf="http://www.greenstone.org/greenstone3/schema/ConfigFormat" 7 7 xmlns:gslib="http://www.greenstone.org/skinning" 8 xmlns:v-bind="http://vuejs.org" 9 xmlns:v-on="http://vuejs.org" 8 10 extension-element-prefixes="java util" 9 11 exclude-result-prefixes="java util"> … … 35 37 <xsl:call-template name="audio-transcription"/> 36 38 </xsl:template> 39 40 <!-- <xsl:attribute-set name="vue"> 41 <xsl:attribute name="v-on" /> 42 <xsl:attribute name="v-bind" /> 43 </xsl:attribute-set> --> 37 44 38 45 <!-- Template for processing audio file uploads --> 39 46 <xsl:template name="audio-transcription"> 40 47 <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" /> 41 <!-- <link rel="preconnect" href="https://fonts.googleapis.com" />42 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="crossorigin" />43 <link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;700&display=swap" rel="stylesheet" /> -->44 48 <link href="interfaces/{$interface_name}/style/asr.css" rel="stylesheet" type="text/css" /> 45 49 … … 47 51 48 52 <section id="audio-transcription-container" class="paper"> 49 <div> 50 51 <form onSubmit="doAudioUpload(); return false;" enctype="multipart/form-data"> 52 <button class="btn-primary" type="button" onclick="document.getElementById('audioFileUpload').click()" title="test"> 53 <span class="material-icons"></span> <!-- file_upload --> 54 <span>Upload Audio Files</span> 55 </button> 53 <!-- Contains the file input, transcribe button and transcription progress indicator --> 54 <div id="audioUploadContainer"> 55 <button class="btn-primary" type="button" v-on:click="openFilePicker"> 56 <span class="material-icons"></span> <!-- file_upload --> 57 <span>Upload Audio Files</span> 58 </button> 56 59 57 <input id="audioFileUpload" type="file" accept="audio/wav" multiple="multiple" /> 58 59 <button id="btnBeginTranscription" class="btn-primary" type="submit" disabled="disabled"> 60 <span class="material-icons"></span> <!-- history_edu --> 61 <span>Transcribe</span> 62 </button> 63 </form> 60 <input id="audioFileInput" type="file" v-on:input="onFilesChanged" 61 accept="audio/wav" multiple="multiple" v-bind:disabled="isTranscribing" /> 64 62 65 <div id="prgFileUploadContainer" class="asr-hidden"> 66 <label for="prgFileUpload">Processing:</label> 67 <progress id="prgFileUpload" /> 68 </div> 63 <button id="btnBeginTranscription" class="btn-primary" type="submit" 64 v-bind:disabled="!canTranscribe" v-on:click="doTranscription"> 65 <span class="material-icons"></span> <!-- history_edu --> 66 <span>Transcribe</span> 67 </button> 68 69 <progress v-if="isTranscribing" class="indeterminateLoadingBar" /> 69 70 </div> 70 71 … … 77 78 </div> --> 78 79 79 <!-- The list of each transcription-->80 <!-- Contains the audio element and transcription list --> 80 81 <div id="transcriptionsDisplayContainer"> 81 82 … … 100 101 </div> 101 102 102 <gsf:div class="transcription__word-list" />103 104 <details>105 <summary>Character Information</summary>106 <div class="metadata-list-container">107 <ul class="metadata-list"></ul>108 </div>109 <div class="audio-slider-container">110 <input type="range" min="0" value="0" class="audio-slider" />111 < /div>112 </d etails>103 <gsf:div class="transcription__word-list" id="transcriptionWordList"> 104 <ul class="transcription__list"> 105 <li v-for="line in lines" style="display: flex"> 106 <span v-for="word in line.words" style="border: 1px solid blue">{{ word }}</span> 107 </li> 108 </ul> 109 </gsf:div> 110 111 <div class="audio-slider-container"> 112 <input type="range" min="0" value="0" class="audio-slider" /> 113 </div> 113 114 114 115 <hr class="divider" /> … … 129 130 </li> 130 131 </template> 131 132 <template id="metadataTemplate">133 <!-- <li class="metadata-list-item tooltip-parent tooltip">134 <div class="spaced-block">135 <p></p>136 </div>137 <div class="spaced-block">138 <button type="button"><img src="interfaces/{$interface_name}/assets/play_arrow_black_18dp.svg" /></button>139 </div>140 <div class="tooltip-wrapper">141 <span class="tooltip">0.02</span>142 </div>143 </li> -->144 <li class="metadata-list-item">145 <div class="spaced-block">146 <p></p>147 </div>148 <div class="spaced-block">149 <button type="button"><img src="interfaces/{$interface_name}/assets/play_arrow_black_18dp.svg" /></button>150 </div>151 </li>152 </template>153 132 </div> 154 133 </section> 155 134 135 <!-- TODO: Switch to production version for release builds --> 136 <gsf:script src="https://unpkg.com/vue@next"></gsf:script> 156 137 <gsf:script src="interfaces/{$interface_name}/js/asr/asr-controller.js"></gsf:script> 157 138 </xsl:template>
Note:
See TracChangeset
for help on using the changeset viewer.