Changeset 35302
- Timestamp:
- 2021-08-17T14:15:34+12:00 (3 years ago)
- Location:
- main/trunk/model-interfaces-dev/atea
- Files:
-
- 4 edited
Legend:
- Unmodified
- Added
- Removed
-
main/trunk/model-interfaces-dev/atea/js/asr/asr-controller.js
r35301 r35302 5 5 6 6 import { TranscribeService, TranscriptionError, TranscriptionModel, TranscriptionMetadata } from "./TranscribeModule.js"; 7 8 /** @type {HTMLAudioElement} */9 // @ts-ignore10 const TRANSCRIPTION_AUDIO_ELEMENT = document.getElementById("transcriptionAudio");11 12 /** @type {HTMLSourceElement} */13 // @ts-ignore14 const TRANSCRIPTION_AUDIO_SOURCE_ELEMENT = document.getElementById("transcriptionAudioSource");15 7 16 8 class TranscriptionViewModel { … … 36 28 /** @type {File} The file from which the transcription was generated. */ 37 29 this.file = file; 38 } 39 } 30 31 /** @type {Boolean} Gets or sets a value indicating if the transcription view is expanded. */ 32 this.isExpanded = false; 33 34 /** @type {Boolean} Gets or sets a value indicating if this transcription view has been deleted. */ 35 this.isDeleted = false; 36 } 37 } 38 39 /** @type {HTMLAudioElement} */ 40 var EL_AUDIO_TRANSCRIPTION_AUDIO; 41 42 /** @type {HTMLSourceElement} */ 43 var EL_SOURCE_TRANSCRIPTION_AUDIO; 44 45 /** @type {Number} The size of each character in our monospace font. */ 46 var MONOSPACE_CHAR_SIZE; 47 48 /** @type {HTMLInputElement} */ 49 var EL_INPUT_AUDIO_FILE; 40 50 41 51 var transcribeService = new TranscribeService(); 52 53 window.addEventListener("load", function() 54 { 55 // @ts-ignore | Object could be null 56 EL_AUDIO_TRANSCRIPTION_AUDIO = document.getElementById("transcriptionAudio"); 57 58 // @ts-ignore | Object could be null 59 EL_INPUT_AUDIO_FILE = document.getElementById("audioFileInput"); 60 61 // @ts-ignore | Object could be null 62 EL_SOURCE_TRANSCRIPTION_AUDIO = document.getElementById("transcriptionAudioSource"); 63 64 const monoCharSizeTestElement = document.querySelector(".monospace-font-sizer"); 65 if (monoCharSizeTestElement == null || monoCharSizeTestElement.textContent == null) 66 { 67 MONOSPACE_CHAR_SIZE = 8; // Slightly over-estimated guess for 16px font-size. 68 return; 69 } 70 71 MONOSPACE_CHAR_SIZE = monoCharSizeTestElement.clientWidth / monoCharSizeTestElement.textContent.length; 72 73 pollAudioTime(); 74 }); 75 76 // Adapted from https://davidwalsh.name/javascript-polling 77 function pollAudioTime() 78 { 79 var lastTime = 0; 80 (function p() 81 { 82 if (EL_AUDIO_TRANSCRIPTION_AUDIO.currentTime != lastTime) 83 { 84 lastTime = EL_AUDIO_TRANSCRIPTION_AUDIO.currentTime; 85 console.log("time updated! " + lastTime); 86 } 87 88 setTimeout(p, 50); 89 })(); 90 } 42 91 43 92 /** … … 47 96 * @link http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136 48 97 **/ 49 var UUID = (function() { 98 var UUID = (function() 99 { 50 100 var self = {}; 51 var lut = []; for (var i=0; i<256; i++) { lut[i] = (i<16?'0':'')+(i).toString(16); } 52 self.generate = function() { 53 var d0 = Math.random()*0xffffffff|0; 54 var d1 = Math.random()*0xffffffff|0; 55 var d2 = Math.random()*0xffffffff|0; 56 var d3 = Math.random()*0xffffffff|0; 57 return lut[d0&0xff]+lut[d0>>8&0xff]+lut[d0>>16&0xff]+lut[d0>>24&0xff]+'-'+ 58 lut[d1&0xff]+lut[d1>>8&0xff]+'-'+lut[d1>>16&0x0f|0x40]+lut[d1>>24&0xff]+'-'+ 59 lut[d2&0x3f|0x80]+lut[d2>>8&0xff]+'-'+lut[d2>>16&0xff]+lut[d2>>24&0xff]+ 60 lut[d3&0xff]+lut[d3>>8&0xff]+lut[d3>>16&0xff]+lut[d3>>24&0xff]; 61 } 101 var lut = []; 102 103 for (var i=0; i<256; i++){ 104 lut[i] = (i < 16 ? '0' : '') + (i).toString(16); 105 } 106 107 self.generate = function() 108 { 109 var d0 = Math.random() * 0xffffffff | 0; 110 var d1 = Math.random() * 0xffffffff | 0; 111 var d2 = Math.random() * 0xffffffff | 0; 112 var d3 = Math.random() * 0xffffffff | 0; 113 114 return lut[d0 & 0xff] + lut[d0 >> 8 & 0xff] + lut[d0 >> 16 & 0xff] + lut[d0 >> 24 & 0xff] + '-' + 115 lut[d1 & 0xff] + lut[d1 >> 8 & 0xff] + '-' + lut[d1 >> 16 & 0x0f | 0x40] + lut[d1 >> 24 & 0xff] + '-' + 116 lut[d2 & 0x3f | 0x80] + lut[d2 >> 8 & 0xff] + '-' + lut[d2 >> 16 & 0xff] + lut[d2 >> 24 & 0xff] + 117 lut[d3 & 0xff] + lut[d3 >> 8 & 0xff] + lut[d3 >> 16 & 0xff] + lut[d3 >> 24 & 0xff]; 118 } 119 62 120 return self; 63 })(); 64 // TODO: Hash file name and size instead. Good indicator that the user has uploaded a duplicate. 65 66 // Get the size of each character in our monospace font 67 var MONOSPACE_CHAR_SIZE; 68 window.addEventListener("load", function() 69 { 70 const monoCharSizeTestElement = document.querySelector(".monospace-font-sizer"); 71 if (monoCharSizeTestElement == null || monoCharSizeTestElement.textContent == null) 72 { 73 MONOSPACE_CHAR_SIZE = 8; // Slightly over-estimated guess for size 16 font. 74 return; 75 } 76 77 MONOSPACE_CHAR_SIZE = monoCharSizeTestElement.clientWidth / monoCharSizeTestElement.textContent.length; 78 }); 121 })(); 122 // TODO: Hash file name and size instead. Good indicator that the user has uploaded a duplicate. 79 123 80 124 // @ts-ignore … … 108 152 { 109 153 openFilePicker() { 110 document.getElementById("audioFileInput")?.click();154 EL_INPUT_AUDIO_FILE.click(); 111 155 }, 112 156 onFilesChanged() 113 157 { 114 /** @type {HTMLInputElement} */ 115 // @ts-ignore | Object could be null 116 const audioFileInput = document.getElementById("audioFileInput"); 117 118 if (audioFileInput.files?.length != undefined && audioFileInput.files?.length > 0) { 158 this.files = []; 159 160 if (EL_INPUT_AUDIO_FILE.files?.length != undefined && EL_INPUT_AUDIO_FILE.files?.length > 0) { 119 161 120 for (const f of audioFileInput.files) { 121 this.files.push(f); 122 } 123 } 124 else { 125 this.files = []; 162 for (const file of EL_INPUT_AUDIO_FILE.files) 163 this.files.push(file); 126 164 } 127 165 }, … … 130 168 // Else call getTranscriptions(); 131 169 // Then, we don't have to worry about preventing the user from transcribing multiple items. 132 await getTranscriptions(this.files); 170 const files = this.files; 171 this.files = []; // Clear the file list, as there is no reason the user would want to transcribe the same file multiple times over 172 await getTranscriptions(files); 133 173 } 134 174 } … … 148 188 failures: new Map(), 149 189 showCharDisplay: false, 150 showWordList: false,151 190 /** @type {{id: String, url: String} | null} Gets the ID of the transcription for which the audio is currently loaded */ 152 191 currentlyLoadedAudio: null … … 155 194 methods: 156 195 { 157 playAudioFile(transcriptionId, startTime = 0) // TODO: Convert to ID196 playAudioFile(transcriptionId, startTime = 0) 158 197 { 159 198 if (startTime < 0) 160 199 { 161 200 startTime = 0; 162 console.warn("Cannot start a audio playback at a time of less than zero.");163 } 164 165 if (startTime >= TRANSCRIPTION_AUDIO_ELEMENT.duration)166 { 167 console.warn("Cannot start a audio playback at a time longer than the audio duration.");201 console.warn("Cannot start audio playback at a time of less than zero."); 202 } 203 204 if (startTime >= EL_AUDIO_TRANSCRIPTION_AUDIO.duration) 205 { 206 console.warn("Cannot start audio playback at a time longer than the audio duration."); 168 207 return; 169 208 } … … 171 210 this.currentlyLoadedAudio = loadTranscriptionAudio(transcriptionId, this.currentlyLoadedAudio); 172 211 173 TRANSCRIPTION_AUDIO_ELEMENT.currentTime = startTime; 174 TRANSCRIPTION_AUDIO_ELEMENT.play(); 175 }, 176 removeTranscription(id) { 177 this.transcriptions.delete(id); 178 // TODO: delete cached audio file 212 EL_AUDIO_TRANSCRIPTION_AUDIO.currentTime = startTime; 213 EL_AUDIO_TRANSCRIPTION_AUDIO.play(); 214 }, 215 removeTranscription(id) 216 { 217 this.transcriptions.get(id).isDeleted = true; 218 delay(550).then(() => this.transcriptions.delete(id)); 179 219 }, 180 220 removeFailure(id) { … … 235 275 236 276 return chars; 237 },238 toggleWordList() {239 this.showWordList = !this.showWordList;240 277 } 241 278 } … … 273 310 AudioUploadVM.isTranscribing = true; 274 311 275 //await delay(200); // TODO: Remove - UI testing purposes only312 await delay(200); // TODO: Remove - UI testing purposes only 276 313 277 314 // Transcribe each audio file in batches. … … 384 421 385 422 const urlObject = URL.createObjectURL(TranscriptionsListVM.transcriptions.get(transcriptionId).file); 386 TRANSCRIPTION_AUDIO_SOURCE_ELEMENT.src = urlObject;387 TRANSCRIPTION_AUDIO_ELEMENT.load();423 EL_SOURCE_TRANSCRIPTION_AUDIO.src = urlObject; 424 EL_AUDIO_TRANSCRIPTION_AUDIO.load(); 388 425 389 426 return { id: transcriptionId, url: urlObject }; … … 392 429 return current; 393 430 } 394 395 TRANSCRIPTION_AUDIO_ELEMENT.addEventListener('durationchange', function() {396 TranscriptionsListVM.audioDuration = TRANSCRIPTION_AUDIO_ELEMENT.duration;397 });398 399 TRANSCRIPTION_AUDIO_ELEMENT.addEventListener('timeupdate', function() {400 console.log("time updated");401 TranscriptionsListVM.currentAudioTime = TRANSCRIPTION_AUDIO_ELEMENT.currentTime;402 }); -
main/trunk/model-interfaces-dev/atea/style/asr.scss
r35300 r35302 1 /* 2 * Defines styles for asr.xsl 3 * Author: Carl Stephens 4 */ 5 1 6 @use "./material.scss"; 2 7 @import url('https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;700&display=swap'); … … 21 26 22 27 /* === End theme application === */ 28 29 .transform-slideout { 30 -webkit-transition-duration: 0.5s; 31 -moz-transition-duration: 0.5s; 32 transition-duration: 0.5s; 33 34 transform: translate(100%, 0); 35 opacity: 0; 36 } 23 37 24 38 body { … … 49 63 display: grid; 50 64 gap: 0.5em 0.5em; 51 grid-template-columns: auto1fr auto;65 grid-template-columns: 1fr auto; 52 66 align-items: stretch; 53 67 … … 74 88 padding: 0; 75 89 list-style-type: none; 76 }77 78 .transcription__container {79 -webkit-transition-duration: 0.15s;80 transition-duration: 0.15s;81 82 // &:hover {83 // background-color: #EEE;84 // }85 90 } 86 91 … … 121 126 122 127 .transcription__error-container { 123 @extend .transcription__container;124 128 display: grid; 125 129 gap: 0.5em 0.5em; -
main/trunk/model-interfaces-dev/atea/style/material.scss
r35300 r35302 1 /* 2 * Defines themes that match the material design specification 3 * Author: Carl Stephens 4 */ 5 1 6 /* === Start theme definitions === */ 2 7 … … 132 137 align-items: center; 133 138 134 line-height: 1.5em;139 //line-height: 1.5em; 135 140 border-radius: var(--border-radius); 136 background: #D CDCDCDC;141 background: #DDD; 137 142 padding: 0.5em; 143 144 .text-placeholder { 145 cursor: default; 146 } 138 147 } 139 148 … … 147 156 148 157 .text-input { 149 display: flex; 150 align-items: center; 158 @extend .text-container; 151 159 152 160 border-radius: var(--border-radius) var(--border-radius) 0 0; 153 161 border-bottom: 1px solid #AAA; 154 background: #EEE; 155 padding: 0.5em; 156 157 @include transition-set; 158 159 &:hover { 160 background-color: #DDD; 162 cursor: text; 163 164 @include transition-set; 165 166 &:hover { 167 background-color: #CDCDCD; 161 168 } 162 169 … … 179 186 } 180 187 188 .text-input-sl { 189 @extend .text-input; 190 191 overflow-x: auto; 192 overflow-y: hidden; 193 white-space: nowrap; 194 } 195 181 196 .text-placeholder { 182 197 color: #666; 183 198 cursor: default; 184 199 font-style: italic; 200 cursor: inherit; 185 201 } 186 202 -
main/trunk/model-interfaces-dev/atea/transform/pages/asr.xsl
r35301 r35302 49 49 <!-- Contains the file input, transcribe button and transcription progress indicator --> 50 50 <div id="audioUploadContainer" class="audio-file-picker"> 51 <button type="button" v-on:click="openFilePicker" 52 v-bind:disabled="isTranscribing"> 53 <span class="material-icons"></span> <!-- file_upload --> 51 <!-- <button type="button" v-on:click="openFilePicker"> 52 <span class="material-icons"></span> file_upload 54 53 <span>Upload Audio Files</span> 55 </button> 54 </button>--> 56 55 57 <div class="text-container-sl" v-on:click="openFilePicker"> 56 <div class="text-input-sl" style="cursor: pointer;" v-on:click="openFilePicker"> 57 <span class="text-placeholder material-icons" v-if="!anyFiles"></span> <!-- attach_file --> 58 58 <span class="text-placeholder" v-if="!anyFiles">Select file/s...</span> 59 59 <span v-if="anyFiles">{{ getFileNameList }}</span> … … 96 96 <!-- Displays each transcription --> 97 97 <li v-for="[id, transcription] in transcriptions"> 98 <xsl:attribute name="v-bind:class"> 99 <xsl:text disable-output-escaping="yes">{ 'transform-slideout': transcription.isDeleted }</xsl:text> 100 </xsl:attribute> 101 98 102 <div class="transcription__container card"> 99 103 <!-- Header containing info and actions for the transcription --> … … 111 115 </div> 112 116 113 <div class="transcription__word-list" v-if=" showWordList">117 <div class="transcription__word-list" v-if="transcription.isExpanded"> 114 118 <div class="transcription__word-list__controls"> 115 119 <button class="btn-fab" v-on:click="playAudioFile(id)" type="button"> … … 138 142 <hr /> 139 143 140 <button type="button" class="theme-flat" v-on:click="t oggleWordList">141 <span class="material-icons" v-if="! showWordList">expand_more</span>142 <span class="material-icons" v-if=" showWordList">expand_less</span>144 <button type="button" class="theme-flat" v-on:click="transcription.isExpanded = !transcription.isExpanded"> 145 <span class="material-icons" v-if="!transcription.isExpanded">expand_more</span> 146 <span class="material-icons" v-if="transcription.isExpanded">expand_less</span> 143 147 <span>Playback and Edit</span> 144 148 </button>
Note:
See TracChangeset
for help on using the changeset viewer.