source: main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/components/TranscriptionItem.vue@ 35631

Last change on this file since 35631 was 35631, checked in by cstephen, 3 years ago

Improve audio loading flow.
Load audio on word editor click.

File size: 7.6 KB
Line 
1<template>
2 <div class="card">
3 <!-- Header containing info and actions for the transcription -->
4 <div class="transcription__header">
5 <button class="btn-fab" v-on:click="toggleAudio" type="button" :title="translations.get('TranscriptionItem_PlayButtonTooltip')">
6 <span class="material-icons mdi-l play-button" v-if="!isPlaying">play_arrow</span>
7 <span class="material-icons mdi-l play-button" v-if="isPlaying">pause</span>
8 </button>
9
10 <span>{{ translations.get("TranscriptionItem_FileName") }}: {{ transcription.fileName }}</span>
11
12 <div style="position: relative;">
13 <button class="btn-primary" @mouseover="showDownloadOptions = true" @mouseout="showDownloadOptions = false" type="button">
14 <span class="material-icons">download</span>
15 <span>{{ translations.get("TranscriptionItem_Download") }}</span>
16 </button>
17
18 <div class="download-popup card" :class="{ 'download-popup-show': showDownloadOptions }"
19 @mouseover="showDownloadOptions = true" @mouseout="showDownloadOptions = false">
20 <button @click="downloadAsText" type="button" class="btn-primary theme-flat">
21 <span class="material-icons">text_snippet</span>
22 <span>{{ translations.get("TranscriptionItem_DownloadAsText") }}</span>
23 </button>
24
25 <button @click="downloadAsJson" type="button" class="btn-primary theme-flat">
26 <span class="material-icons">integration_instructions</span>
27 <span>{{ translations.get("TranscriptionItem_DownloadAsJson") }}</span>
28 </button>
29
30 <button @click="downloadAsWebvtt" type="button" class="btn-primary theme-flat">
31 <span class="material-icons">subtitles</span>
32 <span>{{ translations.get("TranscriptionItem_DownloadAsWebvtt") }}</span>
33 </button>
34 </div>
35 </div>
36
37 <button class="btn-primary theme-error" @click="remove" type="button">
38 <span class="material-icons">delete</span>
39 <span>{{ translations.get("TranscriptionItem_Remove") }}</span>
40 </button>
41 </div>
42
43 <div class="editor-controls">
44 <audio-time-bar v-model.number="currentPlaybackTime" :audio-length="audioLength" :isDisabled="playbackState.id != transcription.id" />
45 </div>
46
47 <hr />
48
49 <div class="editor-controls">
50 <TranscriptionItemEditor ref="editor" :transcription="transcription" style="margin-bottom: 1em" :enableEditing="enableEditing" />
51 <toggle-button v-model="enableEditing" :title="translations.get('TranscriptionItemEditor_ToggleEditTooltip')">
52 <span class="material-icons">edit</span>
53 </toggle-button>
54 </div>
55 </div>
56</template>
57
58<style scoped lang="scss">
59.transcription__header {
60 display: grid;
61 gap: 0.5em 0.5em;
62 grid-template-columns: auto 1fr auto auto;
63 align-items: center;
64}
65
66.download-popup {
67 display: flex;
68 flex-direction: column;
69 align-items: stretch;
70
71 position: absolute;
72 top: 97%;
73 z-index: 2;
74
75 padding: 2px;
76 margin: 0;
77 width: 14em;
78
79 transition-duration: var(--transition-duration);
80 visibility: hidden;
81 opacity: 0;
82}
83
84.download-popup-show {
85 visibility: visible;
86 opacity: 1;
87}
88
89.editor-controls {
90 display: grid;
91 align-items: flex-start;
92 grid-template-columns: 1fr auto;
93 margin: 0.5em 0 0.3em 0;
94 gap: 1em;
95}
96
97.rotate-180 {
98 transform: rotate(180deg);
99}
100</style>
101
102<script>
103import { mapState } from "vuex";
104import { saveAs } from "file-saver"
105import { TranscriptionViewModel } from "../main";
106import AudioPlayback from "../js/AudioPlaybackModule"
107import Util from "../js/Util"
108import AudioTimeBar from "./AudioTimeBar.vue"
109import TranscriptionItemEditor from "./TranscriptionItemEditor.vue"
110
111export default {
112 name: "TranscriptionItem",
113 components: {
114 AudioTimeBar,
115 TranscriptionItemEditor
116 },
117 props: {
118 transcription: TranscriptionViewModel
119 },
120 data() {
121 return {
122 enableEditing: false,
123 showDownloadOptions: false
124 }
125 },
126 computed: {
127 currentPlaybackTime: {
128 get() {
129 return this.$store.getters.transcriptionPlaybackTime(this.transcription.id);
130 },
131 set(value) {
132 this.$store.commit("playbackStateSetTime", { id: this.transcription.id, time: value });
133 }
134 },
135 audioLength() {
136 return this.$store.getters.transcriptionPlaybackLength(this.transcription.id);
137 },
138 isPlaying() {
139 return this.playbackState.isPlaying && this.playbackState.id === this.transcription.id;
140 },
141 ...mapState({
142 translations: state => state.translations,
143 playbackState: state => state.playbackState
144 })
145 },
146 methods: {
147 async toggleAudio() {
148 this.isPlaying ? AudioPlayback.pause() : await AudioPlayback.play(this.transcription.id, -1);
149 },
150 remove() {
151 this.$store.commit("rawTranscriptionRemove", this.transcription.id);
152 },
153 downloadAsText() {
154 const fileName = buildDownloadableFileName(this.transcription.fileName, "txt");
155
156 const blob = new Blob([ this.$refs.editor.words.map(w => w.word).join(" ") ], { type: "text/plain;charset=utf-8" });
157 saveAs(blob, fileName);
158 },
159 downloadAsJson() {
160 const fileName = buildDownloadableFileName(this.transcription.fileName, "json");
161 const toDownload = (({ fileName, transcription }) => ({ fileName, transcription }))(this.transcription);
162 toDownload.words = this.$refs.editor.words.map(w => (({ word, startTime, endTime }) => ({ word, startTime, endTime }))(w));
163
164 const blob = new Blob([ JSON.stringify(toDownload, null, 4) ], { type: "application/json;charset=utf-8" });
165 saveAs(blob, fileName);
166 },
167 downloadAsWebvtt() {
168 const fileName = buildDownloadableFileName(this.transcription.fileName, "vtt");
169 const toDownload = buildWebvttFileContents(this.transcription, this.$refs.editor);
170
171 const blob = new Blob([ toDownload ], { type: "text/vtt;charset=utf-8" });
172 saveAs(blob, fileName);
173 }
174 }
175}
176
177/**
178 * Builds a file name for a download.
179 * @param {String} transcriptionFileName The name of the transcription that will be downloaded.
180 * @param {String} extension The file extension of the download. Do not include a period.
181 * @returns {String} The file name.
182 */
183function buildDownloadableFileName(transcriptionFileName, extension) {
184 const extensionIndex = transcriptionFileName.lastIndexOf(".");
185 let fileName = transcriptionFileName.slice(0, extensionIndex);
186 fileName += "_transcription." + extension;
187
188 return fileName;
189}
190
191/**
192 * Builds a WebVTT file of the given transcription
193 * @param {TranscriptionViewModel} transcription The transcription.
194 * @returns {String} The WebVTT content.
195 */
196function buildWebvttFileContents(transcription, editor) {
197 let contents = "WEBVTT Transcription of " + transcription.fileName + "\n\n";
198
199 for (const word of editor.words) {
200 const startTime = Util.formatSecondsTimeString(word.startTime, true);
201 const endTime = Util.formatSecondsTimeString(word.endTime, true);
202
203 contents += startTime + " --> " + endTime + "\n";
204 contents += "- " + word.word + "\n\n";
205 }
206
207 return contents;
208}
209</script>
Note: See TracBrowser for help on using the repository browser.