|
1 | 1 | <template lang="pug">
|
2 | 2 | #app
|
3 |
| - navbar |
| 3 | + navbar |
| 4 | + vk-modal(:show.sync="modalShow") |
| 5 | + vk-modal-title |
| 6 | + | Error |
| 7 | + p |
| 8 | + | {{modalText}} |
| 9 | + p.uk-text-right |
| 10 | + vk-button.uk-margin-small-right(@click="modalShow = false") |
| 11 | + | Close |
| 12 | + .container |
| 13 | + .panel |
| 14 | + div(v-if="stage === 'prepare'") |
| 15 | + input.hidden(type="file", ref="textLoader", accept="text/plain", @change="readText") |
| 16 | + vk-button(@click="loadText") |
| 17 | + | Load Text File |
| 18 | + .uk-margin |
| 19 | + textarea.uk-textarea(rows="20", placeholder="Subtitle Preview", v-model="subtitleText") |
| 20 | + p.uk-margin |
| 21 | + | Lines of Subtitle: {{ subtitleText.split('\n').length }} line(s) |
| 22 | + vk-button.uk-margin(type="primary", @click="startEdit") |
| 23 | + | Start Editing |
| 24 | + div(v-if="stage === 'edit'") |
| 25 | + h4 |
| 26 | + | React Time: {{ reactTime }} |
| 27 | + input.uk-range(type="range", min="0.0" max="1.0", step="0.01", v-model="reactTime") |
| 28 | + h2 |
| 29 | + | Current Line |
| 30 | + h4.alt-text(v-if="currentLine === null") |
| 31 | + | [Empty] |
| 32 | + h4(v-else) |
| 33 | + | {{ subtitles[currentLine] }} |
| 34 | + h2 |
| 35 | + | Coming Lines |
| 36 | + h4.alt-text(v-for="subtitle in nextLines") |
| 37 | + | {{ subtitle }} |
| 38 | + h4.alt-text(v-if="nextLines.length < 4") |
| 39 | + | [End of File] |
| 40 | + vk-button.uk-margin(type="primary", @click="startReview") |
| 41 | + | Start Reviewing |
| 42 | + div(v-if="stage === 'review'") |
| 43 | + textarea.uk-textarea(rows="20", placeholder="Subtitle Preview", v-model="subtitleReview") |
| 44 | + vk-button.uk-margin(type="primary", @click="saveFile") |
| 45 | + | Save File |
| 46 | + a.hidden(ref="download", href="") |
| 47 | + .panel |
| 48 | + input.hidden( |
| 49 | + type="file", |
| 50 | + ref="videoLoader", |
| 51 | + accept="audio/mp4, video/mp4", |
| 52 | + @change="readVideo") |
| 53 | + vk-button(@click="loadVideo", v-if="stage === 'prepare'") |
| 54 | + | Load Video |
| 55 | + video.video.uk-margin( |
| 56 | + ref="video", |
| 57 | + src="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", |
| 58 | + controls) |
| 59 | + shortcut(v-show="stage === 'edit'") |
4 | 60 | </template>
|
5 | 61 |
|
6 | 62 | <script>
|
7 | 63 | import Navbar from './components/Navbar';
|
| 64 | +import Shortcut from './components/Shortcut'; |
8 | 65 |
|
9 | 66 | export default {
|
10 | 67 | name: 'App',
|
11 | 68 | components: {
|
12 | 69 | Navbar,
|
| 70 | + Shortcut, |
13 | 71 | },
|
14 | 72 | data() {
|
15 | 73 | return {
|
| 74 | + modalShow: false, |
| 75 | + modalText: '', |
16 | 76 | stage: 'prepare',
|
| 77 | + subtitleText: '', |
| 78 | + subtitles: [], |
| 79 | + subtitleStarts: [], |
| 80 | + subtitleEnds: [], |
| 81 | + currentLine: null, |
| 82 | + nextLine: 0, |
| 83 | + previousTiming: 0, |
| 84 | + reactTime: 0.3, |
| 85 | + subtitleReview: '', |
17 | 86 | };
|
18 | 87 | },
|
| 88 | + computed: { |
| 89 | + nextLines() { |
| 90 | + return this.subtitles.slice(this.nextLine, this.nextLine + 4); |
| 91 | + }, |
| 92 | + }, |
| 93 | + beforeDestroy() { |
| 94 | + // Makesure remove all event handlers |
| 95 | + window.removeEventListener('keypress', this.keyHandler); |
| 96 | + }, |
| 97 | + methods: { |
| 98 | + loadText() { |
| 99 | + this.$refs.textLoader.click(); |
| 100 | + }, |
| 101 | + loadVideo() { |
| 102 | + this.$refs.videoLoader.click(); |
| 103 | + }, |
| 104 | + readText(evt) { |
| 105 | + const filename = evt.target.files[0]; |
| 106 | + this.subtitleText = ''; // Empty the previous results |
| 107 | +
|
| 108 | + const reader = new FileReader(); |
| 109 | + reader.onload = () => { |
| 110 | + this.subtitleText = reader.result; |
| 111 | + }; |
| 112 | +
|
| 113 | + reader.readAsText(filename); |
| 114 | + }, |
| 115 | + readVideo(evt) { |
| 116 | + const filename = evt.target.files[0]; |
| 117 | + const url = URL.createObjectURL(filename); |
| 118 | + this.$refs.video.src = url; |
| 119 | + this.$refs.video.load(); |
| 120 | + }, |
| 121 | + startEdit() { |
| 122 | + if (this.subtitleText.length === 0) { |
| 123 | + this.modalShow = true; |
| 124 | + this.modalText = 'Please import the subtitle text first.'; |
| 125 | + } else { |
| 126 | + this.subtitles = this.subtitleText.split('\n'); |
| 127 | + this.subtitleStarts = new Array(this.subtitles.length).fill(null); |
| 128 | + this.subtitleEnds = new Array(this.subtitles.length).fill(null); |
| 129 | + this.stage = 'edit'; |
| 130 | + window.addEventListener('keypress', this.keyHandler); |
| 131 | + } |
| 132 | + }, |
| 133 | + startReview() { |
| 134 | + window.removeEventListener('keypress', this.keyHandler); |
| 135 | + this.stage = 'review'; |
| 136 | + this.generateSubtitle(); |
| 137 | + }, |
| 138 | + keyHandler(e) { |
| 139 | + const pressed = String.fromCharCode(e.keyCode).toLowerCase(); |
| 140 | +
|
| 141 | + switch (pressed) { |
| 142 | + case 'k': |
| 143 | + // Switch to Next Line |
| 144 | + if (this.currentLine !== null) { |
| 145 | + this.subtitleEnds[this.currentLine] = this.$refs.video.currentTime - 0.01; |
| 146 | + } |
| 147 | + this.currentLine = this.nextLine; |
| 148 | + this.nextLine += 1; |
| 149 | + if (this.currentLine < this.subtitles.length) { |
| 150 | + this.subtitleStarts[this.currentLine] = this.$refs.video.currentTime; |
| 151 | + } else { |
| 152 | + this.currentLine = null; |
| 153 | + } |
| 154 | + break; |
| 155 | + case 'l': |
| 156 | + // Stop Current Line |
| 157 | + if (this.currentLine !== null) { |
| 158 | + this.subtitleEnds[this.currentLine] = this.$refs.video.currentTime - 0.01; |
| 159 | + } |
| 160 | + this.currentLine = null; |
| 161 | + break; |
| 162 | + case 'u': |
| 163 | + // Prev 3 Secs |
| 164 | + this.$refs.video.currentTime -= 3; |
| 165 | + break; |
| 166 | + case 'p': |
| 167 | + // Skip 3 Secs |
| 168 | + this.$refs.video.currentTime += 3; |
| 169 | + break; |
| 170 | + case 'i': |
| 171 | + if (this.nextLine > 0) { |
| 172 | + this.currentLine = this.nextLine - 2; |
| 173 | + this.nextLine = this.nextLine - 1; |
| 174 | + } |
| 175 | +
|
| 176 | + if (this.currentLine === -1) { |
| 177 | + this.currentLine = null; |
| 178 | + } |
| 179 | + break; |
| 180 | + case 'o': |
| 181 | + if (this.nextLine < this.subtitles.length) { |
| 182 | + this.currentLine = this.nextLine; |
| 183 | + this.nextLine = this.nextLine + 1; |
| 184 | + } |
| 185 | + break; |
| 186 | + default: |
| 187 | + break; |
| 188 | + } |
| 189 | + }, |
| 190 | + timeFormat(secs) { |
| 191 | + if (secs === null) { |
| 192 | + return '0:0:0,0'; |
| 193 | + } |
| 194 | + const hour = Math.floor(secs / 60 / 60); |
| 195 | + const min = Math.floor((secs / 60) % 60); |
| 196 | + const sec = Math.floor(secs % 60); |
| 197 | + const mil = Math.floor((secs * 1000) % 1000); |
| 198 | + return `${hour}:${min}:${sec},${mil}`; |
| 199 | + }, |
| 200 | + generateSubtitle() { |
| 201 | + this.subtitleReview = ''; |
| 202 | + for (let i = 0; i < this.subtitles.length; i += 1) { |
| 203 | + this.subtitleReview += `${i + 1}\n`; |
| 204 | + this.subtitleReview += `${this.timeFormat(this.subtitleStarts[i])} --> ${this.timeFormat(this.subtitleEnds[i])}\n`; |
| 205 | + this.subtitleReview += `${this.subtitles[i]}\n\n`; |
| 206 | + } |
| 207 | + }, |
| 208 | + saveFile() { |
| 209 | + const a = this.$refs.download; |
| 210 | + const file = new Blob([this.subtitleReview], { type: 'text/plain' }); |
| 211 | + a.href = URL.createObjectURL(file); |
| 212 | + a.download = 'result.srt'; |
| 213 | + a.click(); |
| 214 | + }, |
| 215 | + }, |
19 | 216 | };
|
20 | 217 | </script>
|
21 | 218 |
|
22 |
| -<style> |
| 219 | +<style lang="stylus" scoped> |
| 220 | +.container |
| 221 | + display flex |
| 222 | + margin 20px |
| 223 | +.hidden |
| 224 | + display none |
| 225 | +.panel |
| 226 | + flex 1 |
| 227 | + margin 10px |
| 228 | +.uk-textarea |
| 229 | + resize vertical |
| 230 | +.video |
| 231 | + width 100% |
| 232 | +.alt-text |
| 233 | + color #aaa |
23 | 234 | </style>
|
0 commit comments