├── .gitignore
├── CITATION.cff
├── LICENSE
├── README.md
├── build
├── tag.sh
└── update-citation.js
├── chi.mp4
├── chi.ogg
├── chi.srt
├── demo
├── ep0.gif
└── word.gif
├── img
├── icon.png
├── microphone-permission.png
├── sound-playback.png
└── sound-recording.png
├── index.html
├── lib
├── file-loader.js
├── output-download.js
├── recognition.js
├── speech-to-text.js
├── voice-access.js
└── youtuble-utils.js
├── locale
├── README.md
├── en-us.js
└── zh-tw.js
├── nbproject
├── project.properties
└── project.xml
├── package.json
├── script.js
├── style.css
├── vender
├── autosize.min.js
├── download-as-file.js
├── download-files-as-zip.js
├── garlic
│ ├── README.md
│ └── garlic.min.js
├── i18n
│ ├── README.md
│ ├── i18n-config.js
│ └── i18n.js
├── jquery.min.js
├── jquery.ns-autogrow.min.js
├── js-xlsx
│ ├── Blob.js
│ ├── FileSaver.js
│ ├── README.md
│ ├── data-example.json
│ ├── shim.min.js
│ ├── xlsx-helper.js
│ ├── xlsx.full.min.js
│ └── xlsxworker.js
├── semantic-ui
│ ├── semantic.min.css
│ ├── semantic.min.js
│ └── themes
│ │ └── default
│ │ └── assets
│ │ └── fonts
│ │ ├── icons.svg
│ │ ├── icons.ttf
│ │ ├── icons.woff
│ │ └── icons.woff2
└── vue
│ └── vue.js
└── vue-init.js
/.gitignore:
--------------------------------------------------------------------------------
1 | *.mp4
2 | *.mp3
3 | *.m4a
4 | *.ogg
5 | *.srt
6 | *.aac
7 | *.avi
8 | *.amr
9 | *.wav
10 | !chi.mp4
11 | !chi.srt
12 | !chi.ogg
13 | /[[]document]/
14 | /nbproject/private/
--------------------------------------------------------------------------------
/CITATION.cff:
--------------------------------------------------------------------------------
1 | cff-version: 1.2.0
2 | message: "If you use this software, please cite it using these metadata."
3 | authors:
4 | - family-names: Chen
5 | given-names: Yung-Ting
6 | orcid: https://orcid.org/0000-0003-3751-610X
7 | email: https://blog.pulipuli.info
8 | title: "HTML5-Speech-to-Text"
9 | license: "MIT"
10 | repository-code: "https://github.com/pulipulichen/HTML5-Speech-to-Text"
11 | abstract: "This is an online speech recognition tool implemented entirely in pure JavaScript. It utilizes the browser's built-in Web Speech API to convert audio captured by the microphone into text and combines it with time segments to create subtitles."
12 | identifiers:
13 | - type: doi
14 | value: 10.5281/zenodo.11213438
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Pulipuli Chen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://zenodo.org/doi/10.5281/zenodo.11213438)
2 |
3 | # HTML5-Speech-to-Text: 線上檔案語音轉文字字幕
4 |
5 | This is an online speech recognition tool implemented entirely in pure JavaScript. It utilizes the browser's built-in Web Speech API to convert audio captured by the microphone into text and combines it with time segments to create subtitles.
6 |
7 | 純JavaScript實作的線上語音識別工具。它利用瀏覽器內建的Web Speech API,將麥克風收到的聲音轉換成文字,並搭配時間片段組合成字幕。
8 |
9 | 
10 |
11 | ## Online Demo
12 |
13 | https://pulipulichen.github.io/HTML5-Speech-to-Text/
14 |
15 | ## Techniques
16 |
17 | - JavaScript
18 | - Web Speech API: 瀏覽器內建的語音識別API。
19 |
20 | ## Article
21 |
22 | - [線上檔案語音轉文字字幕:Web Speech to Text / Speech recognition with Google Chrome: Web Speech to Text](https://blog.pulipuli.info/2019/01/web-speech-to-text-speech-recognition.html)
23 |
24 | # Citation
25 |
26 | Chen, Y.-T. (2024). *HTML5-Speech-to-Text* (20240518.182526) [Computer software]. Zenodo. [https://doi.org/10.5281/ZENODO.11213438](https://doi.org/10.5281/ZENODO.11213438)
27 |
28 | ----
29 |
30 | # Reference
31 | - https://wavesurfer-js.org/
32 | - 華語文能力測驗關鍵詞彙:牛刀小試篇 https://www.youtube.com/watch?v=GE7sc_XvJ8w
33 | - Semantic UI: https://semantic-ui.com/globals/site.html
34 | - Semantic ICON: https://semantic-ui.com/elements/icon.html
35 | - ICON: http://www.iconninja.com/comment-chat-speak-talk-run-icon-509858
36 | - https://www.nikse.dk/subtitleedit/online#
37 | - https://www.pkstep.com/archives/38130#%E4%B8%8B%E8%BC%89
38 |
39 |
--------------------------------------------------------------------------------
/build/tag.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | node build/update-citation.js
4 |
5 | # =================================================================
6 |
7 | new_version=$(date '+%Y%m%d.%H%M%S')
8 |
9 | git tag $new_version
10 | git push origin $new_version
11 |
12 | # # =================================================================
13 |
14 | git add .
15 | git commit -m "${new_version}"
16 | git push --force-with-lease
17 |
18 | # =================================================================
19 |
20 | GITHUB_HOMEPAGE=`jq -r '.homepage' package.json`
21 | #echo $GITHUB_HOMEPAGE
22 |
23 | GITHUB_USER=`echo $GITHUB_HOMEPAGE | awk -F'/' '{print $4}'`
24 |
25 | GITHUB_REPO=`jq -r '.name' package.json`
26 | #echo $GITHUB_REPO
27 |
28 | DOI=`jq -r '.doi' package.json`
29 |
30 | echo "================================================"
31 | echo "Integrate GitHub with Zenodo: https://rb.gy/ql60qi"
32 | echo "GitHub Homepage: ${GITHUB_HOMEPAGE}"
33 | echo "GitHub New Release: https://github.com/${GITHUB_USER}/${GITHUB_REPO}/releases/new"
34 | echo "Zenono GitHub Setting: https://zenodo.org/account/settings/github/"
35 | echo "Zenono Repository Management: https://zenodo.org/account/settings/github/repository/${GITHUB_USER}/${GITHUB_REPO}"
36 | if [ -n "$DOI" ]; then
37 | echo "Zenono Public: ${DOI}"
38 | fi
39 | echo "================================================"
--------------------------------------------------------------------------------
/build/update-citation.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 |
4 | const package_info = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf-8').toString())
5 |
6 | const author_name = package_info.author.name
7 | const family_name = author_name.slice(author_name.lastIndexOf(' ') + 1)
8 | const given_name = author_name.slice(0, author_name.lastIndexOf(' '))
9 |
10 | let repository_code = package_info.homepage
11 | if (repository_code.indexOf('#') > -1) {
12 | repository_code = repository_code.slice(0, repository_code.indexOf('#'))
13 | }
14 |
15 | let citation_cff_yaml = `cff-version: 1.2.0
16 | message: "If you use this software, please cite it using these metadata."
17 | authors:
18 | - family-names: ${family_name}
19 | given-names: ${given_name}
20 | orcid: ${package_info.author.orcid}
21 | email: ${package_info.author.url}
22 | title: "${package_info.name}"
23 | license: "${package_info.license}"
24 | repository-code: "${repository_code}"
25 | abstract: "${package_info.description}"`
26 |
27 | if (package_info.doi) {
28 | citation_cff_yaml = citation_cff_yaml + `
29 | identifiers:
30 | - type: doi
31 | value: ${package_info.doi}`
32 | }
33 |
34 | fs.writeFileSync(path.resolve(__dirname, '../CITATION.cff'), citation_cff_yaml, 'utf-8')
35 |
--------------------------------------------------------------------------------
/chi.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pulipulichen/HTML5-Speech-to-Text/249351fd9f1f2fd8109c7393997e54b523e04541/chi.mp4
--------------------------------------------------------------------------------
/chi.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pulipulichen/HTML5-Speech-to-Text/249351fd9f1f2fd8109c7393997e54b523e04541/chi.ogg
--------------------------------------------------------------------------------
/chi.srt:
--------------------------------------------------------------------------------
1 | 1
2 | 00:00:01,625 --> 00:00:06,981
3 | 昨天晚上的約會怎麼樣介紹的對像不錯吧
4 |
5 | 2
6 | 00:00:06,981 --> 00:00:10,199
7 | 別忘了昨晚已經被問夠了
8 |
9 | 3
10 | 00:00:10,199 --> 00:00:24,450
11 | 他來的舅媽才見面就問了我一堆問題在哪工作薪水多少是家裡的老大嗎家裡有哪些人就像是警察問犯人一樣
12 |
13 | 4
14 | 00:00:24,450 --> 00:00:28,638
15 | 心臟病什麼都不說
--------------------------------------------------------------------------------
/demo/ep0.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pulipulichen/HTML5-Speech-to-Text/249351fd9f1f2fd8109c7393997e54b523e04541/demo/ep0.gif
--------------------------------------------------------------------------------
/demo/word.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pulipulichen/HTML5-Speech-to-Text/249351fd9f1f2fd8109c7393997e54b523e04541/demo/word.gif
--------------------------------------------------------------------------------
/img/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pulipulichen/HTML5-Speech-to-Text/249351fd9f1f2fd8109c7393997e54b523e04541/img/icon.png
--------------------------------------------------------------------------------
/img/microphone-permission.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pulipulichen/HTML5-Speech-to-Text/249351fd9f1f2fd8109c7393997e54b523e04541/img/microphone-permission.png
--------------------------------------------------------------------------------
/img/sound-playback.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pulipulichen/HTML5-Speech-to-Text/249351fd9f1f2fd8109c7393997e54b523e04541/img/sound-playback.png
--------------------------------------------------------------------------------
/img/sound-recording.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pulipulichen/HTML5-Speech-to-Text/249351fd9f1f2fd8109c7393997e54b523e04541/img/sound-recording.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Web Speech to Text
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
by
布丁布丁吃什麼?
27 |
28 |
{{ i18n.t('Web Speech to Text') }}
29 |
30 |
31 |
32 |
33 |
34 | {{ i18n.t('Please install and enable Virtual Audio Cable:') }}
35 |
Windows
36 | |
37 |
Mac
38 |
39 |
40 |
41 |
42 |
43 | {{ i18n.t('Help') }}
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
69 |
70 |
71 |
72 | {{ i18n.t('Loading') }}
73 |
74 |
75 |
76 |
77 | {{ i18n.t('Start') }}
78 |
79 |
80 |
81 |
82 | {{ i18n.t('Playing') }}
83 |
84 |
85 |
86 | {{ i18n.t("Finish in") }}
87 | {{ finishTime }}
88 |
89 |
90 |
91 |
92 | {{ i18n.t('Finish') }}
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
105 |
106 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
173 |
203 |
204 |
205 |
206 |
207 |
208 |
244 |
245 |
{{ i18n.t('Cancel') }}
246 |
{{ i18n.t('OK') }}
249 |
250 |
251 |
252 |
253 |
254 |
260 |
261 |
{{ i18n.t('Grant') }}
263 |
264 |
265 |
266 |
267 |
268 |
269 | {{ i18n.t('Open your Sound setting.', false) }}
270 | {{ i18n.t('Set Virtual Audio Cable default in playback and recording.', false) }}
271 |
272 |
273 |

274 |

275 |
276 |
277 |
{{ i18n.t('OK') }}
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
--------------------------------------------------------------------------------
/lib/file-loader.js:
--------------------------------------------------------------------------------
1 |
2 | let openLoadFromUrlModel = function () {
3 | $('.ui.modal.load-from-url').modal('show')
4 | }
5 | $('.open-load-url-model-btn').click(openLoadFromUrlModel)
6 |
7 | let getFullFilenameFromURL = function (url) {
8 | if (typeof(url) !== 'string') {
9 | return
10 | }
11 |
12 | // Remove everything to the last slash in URL
13 | url = url.substr(1 + url.lastIndexOf("/"));
14 |
15 | // Break URL at ? and take first part (file name, extension)
16 | url = url.split('?')[0];
17 |
18 | // Sometimes URL doesn't have ? but #, so we should aslo do the same for #
19 | url = url.split('#')[0];
20 |
21 | // Now we have only extension
22 | return url;
23 | }
24 |
25 | let getExtFromURL = function (url) {
26 | if (typeof(url) !== 'string') {
27 | return
28 | }
29 |
30 | // Remove everything to the last slash in URL
31 | url = getFullFilenameFromURL(url)
32 |
33 | if (url.lastIndexOf('.') > -1) {
34 | url = url.slice(url.lastIndexOf('.')+1, url.length)
35 | }
36 |
37 | // Now we have only extension
38 | return url.toLowerCase();
39 | }
40 |
41 | let getFilenameFromURL = function (url) {
42 | if (typeof(url) !== 'string') {
43 | return
44 | }
45 |
46 | // Remove everything to the last slash in URL
47 | url = getFullFilenameFromURL(url)
48 |
49 | if (url.lastIndexOf('.') > -1) {
50 | url = url.slice(0, url.lastIndexOf('.'))
51 | }
52 |
53 | // Now we have only extension
54 | return url;
55 | }
56 |
57 | var URL = window.URL || window.webkitURL
58 | /*
59 | *
60 | var displayMessage = function (message, isError) {
61 | var element = document.querySelector('#message')
62 | element.innerHTML = message
63 | element.className = isError ? 'error' : 'info'
64 | }
65 | */
66 |
67 | var playSelectedFile = function (event) {
68 | var file = this.files[0]
69 | if (file === undefined) {
70 | return
71 | }
72 |
73 | var type = file.type
74 | var fullFilename = file.name
75 | var filename = getFilenameFromURL(fullFilename)
76 | app.setFilename(filename)
77 |
78 | //console.log(file)
79 | //console.log(type)
80 | var videoNode, template
81 | if (type === 'video/mp4') {
82 | if ($('#audio_player video').length === 0) {
83 | template = app.$data.playerTemplate.video
84 | template = template.split('{{URL}}').join('')
85 | $('#audio_player').html(template)
86 | }
87 | videoNode = document.querySelector('video')
88 | }
89 | else {
90 | if ($('#audio_player audio').length === 0) {
91 | template = app.$data.playerTemplate.audio
92 | template = template.split('{{URL}}').join('')
93 |
94 | var mime = type
95 | template = template.split('{{MIME_TYPE}}').join(mime)
96 |
97 | template = template.split('{{FILENAME}}').join(filename)
98 |
99 | $('#audio_player').html(template)
100 | }
101 | videoNode = document.querySelector('audio')
102 | }
103 | //var videoNode = document.querySelector('video')
104 |
105 | var canPlay = videoNode.canPlayType(type)
106 | if (canPlay === '') canPlay = 'no'
107 | var message = 'Can play type "' + type + '": ' + canPlay
108 | var isError = canPlay === 'no'
109 | //displayMessage(message, isError)
110 |
111 | if (isError) {
112 | return
113 | }
114 |
115 | var fileURL = URL.createObjectURL(file)
116 | videoNode.src = fileURL
117 | app.clearLoadFromURLValue()
118 | app.reset()
119 | }
120 |
121 | var inputNode = document.getElementById('local_file')
122 | $('.open-load-local-file-model-btn').click(function () {
123 | //inputNode.click()
124 | //console.log('aaa')
125 | $('#local_file').click();
126 |
127 | })
128 | inputNode.addEventListener('change', playSelectedFile, false)
129 |
130 | // ---------------------
131 |
132 | /**
133 | * 程式開始時就先開始播放
134 | */
135 | $(function () {
136 | setTimeout(function () {
137 | //$('.start-btn').click()
138 | //SpeechToText.addExampleRow(30)
139 | //$('.open-load-url-model-btn').click()
140 | }, 1000)
141 | })
--------------------------------------------------------------------------------
/lib/output-download.js:
--------------------------------------------------------------------------------
1 | let downloadCaption = function () {
2 | let filetype = $('.output-data-type').val()
3 | if (filetype === 'srt') {
4 | downloadCaptionSrt()
5 | }
6 | else if (filetype === 'txt') {
7 | downloadCaptionTxt()
8 | }
9 | else if (filetype === 'ods') {
10 | downloadCaptionOds()
11 | }
12 | else if (filetype === 'caption') {
13 | downloadCaptionCaption()
14 | }
15 | }
16 |
17 | let downloadCaptionSrt = function () {
18 | let filetype = $('.output-data-type').val()
19 | let title = $('.filename').val().trim() + '.' + filetype
20 |
21 | let content = SpeechToText.getContent()
22 | for (let i = 0; i < content.length; i++) {
23 | let contentItem = content[i]
24 | let item = []
25 | // 45
26 | // 00:02:52,184 --> 00:02:53,617
27 | // 慢慢来
28 |
29 | item.push((i + 1))
30 | item.push(contentItem.start.hour +
31 | ':'+
32 | contentItem.start.minute +
33 | ':'+
34 | contentItem.start.second +
35 | ','+
36 | contentItem.start.millisecond +
37 | ' --> '+
38 | contentItem.end.hour +
39 | ':'+
40 | contentItem.end.minute +
41 | ':'+
42 | contentItem.end.second +
43 | ','+
44 | contentItem.end.millisecond)
45 | item.push(contentItem.caption)
46 | content[i] = item.join('\n')
47 | }
48 | content = content.join('\n\n')
49 |
50 | downloadAsFile(title, content)
51 | }
52 |
53 | let downloadCaptionTxt = function () {
54 | let filetype = $('.output-data-type').val()
55 | let title = $('.filename').val().trim() + '.' + filetype
56 |
57 | let content = SpeechToText.getContent()
58 | for (let i = 0; i < content.length; i++) {
59 | let contentItem = content[i]
60 | let item = []
61 | // 45
62 | // 00:02:52,184 --> 00:02:53,617
63 | // 慢慢来
64 |
65 | item.push(contentItem.start.hour +
66 | ':'+
67 | contentItem.start.minute +
68 | ':'+
69 | contentItem.start.second +
70 | ':'+
71 | contentItem.start.millisecond +
72 | ','+
73 | contentItem.end.hour +
74 | ':'+
75 | contentItem.end.minute +
76 | ':'+
77 | contentItem.end.second +
78 | ':'+
79 | contentItem.end.millisecond)
80 | item.push(contentItem.caption)
81 | content[i] = item.join(',')
82 | }
83 | content = content.join('\n')
84 |
85 | downloadAsFile(title, content)
86 | }
87 |
88 |
89 | let downloadCaptionCaption = function () {
90 | let title = $('.filename').val().trim() + '-caption.txt'
91 |
92 | let content = SpeechToText.getContent()
93 | for (let i = 0; i < content.length; i++) {
94 | let contentItem = content[i]
95 | content[i] = contentItem.caption
96 | }
97 | content = content.join('\n')
98 |
99 | downloadAsFile(title, content)
100 | }
101 |
102 | let downloadCaptionOds = function () {
103 | //let filetype = $('.output-data-type').val()
104 | //let title = $('.filename').val().trim() + '.' + filetype
105 |
106 | let content = SpeechToText.getContent()
107 |
108 | for (let i = 0; i < content.length; i++) {
109 | let contentItem = content[i]
110 | content[i].start = contentItem.start.hour +
111 | ':'+
112 | contentItem.start.minute +
113 | ':'+
114 | contentItem.start.second +
115 | ':'+
116 | contentItem.start.millisecond
117 | content[i].end = contentItem.end.hour +
118 | ':'+
119 | contentItem.end.minute +
120 | ':'+
121 | contentItem.end.second +
122 | ':'+
123 | contentItem.end.millisecond
124 | }
125 |
126 | xlsx_helper_ods_download($('.filename').val().trim(), content)
127 | }
128 |
129 | $('.button.download-btn').click(downloadCaption)
130 |
--------------------------------------------------------------------------------
/lib/recognition.js:
--------------------------------------------------------------------------------
1 |
2 | /* global SpeechToText, app */
3 |
4 | let startBtn = document.querySelector('.start-btn')
5 | let audioPlay
6 | // audioPlay.playbackRate = 0.8
7 |
8 | let recognition = new webkitSpeechRecognition()
9 | // set params
10 | recognition.continuous = false
11 | recognition.interimResults = true
12 | adjustStartSecond = -0.5
13 | adjustEndSecond = 0.5
14 |
15 | let pauseInterval = 30 * 60
16 | let pauseSeconds = 3
17 |
18 | // start immediately
19 | // recognition.start();
20 |
21 | settingExceptionCases = {
22 | 'cmn-Hant-TW': ['台', '桃', '巧虎', '彩虹', 'trial', 'iO', 'trials'],
23 | 'cmn-Hans-CN': ['台', '桃', '巧虎', '彩虹', 'trial', 'iO', 'trials'],
24 | 'en-US': ['巧虎', '彩虹', 'trial', 'iO', 'trials'],
25 | }
26 | let disableExceptionCount = false
27 |
28 | let playingFlag = false
29 | let currentCaption
30 | let settingExceptionCount
31 | startBtn.addEventListener('click', function () {
32 | audioPlay = document.querySelector('.audio-player')
33 | let customLang = $('#customLang').val()
34 | if (customLang.trim() === '') {
35 | recognition.lang = $('.lang').val()
36 | }
37 | else {
38 | recognition.lang = customLang.trim()
39 | }
40 | console.log(recognition.lang)
41 |
42 | $('.recognition-status').attr('data-recognition-status', 'wait')
43 |
44 | playingFlag = true
45 | //SpeechToText.setPlayer()
46 | SpeechToText.addRow()
47 |
48 | var errorCount = 0
49 | settingExceptionCount = 0
50 |
51 | var startRecognition = function () {
52 | try {
53 | //audioPlay.play()
54 | SpeechToText.play()
55 | }
56 | catch (e) {
57 | console.log(e)
58 | errorCount++
59 | console.log('Audio player load failed (' + errorCount + ')')
60 |
61 | if (errorCount > 3) {
62 | if (window.confirm('Audio player is broken. Do you want to refresh this page?')) {
63 | location.reload()
64 | }
65 | return null
66 | }
67 | setTimeout(function () {
68 | //console.log('被暫停了1', SpeechToText.getPlayerCurrentTime(), SpeechToText.getDuration())
69 | startRecognition()
70 | }, 0)
71 | }
72 |
73 | /**
74 | * 改在另外的地方關閉
75 | */
76 | //setTimeout(() => {
77 | // recognitionFinish()
78 | //}, (SpeechToText.getDuration() * 1000 + 100))
79 | //console.log(audioPlay.duration * 1000 + 100)
80 |
81 | // $('0:00
').insertBefore(text)
82 | recognition.setFinishEvent()
83 | recognition.start()
84 |
85 | //document.querySelector('.text-content').style.display = 'block'
86 | $('.text-content').removeClass('hide')
87 | }
88 | startRecognition()
89 | })
90 |
91 | let recognitionFinish = function () {
92 | recognition.stop()
93 | playingFlag = false
94 | $('.content-controller .button.disabled').removeClass('disabled')
95 | $('.recognition-status').attr('data-recognition-status', 'finish')
96 | SpeechToText.complete()
97 | //console.log('finish')
98 | }
99 |
100 | let tempStart
101 | let tempTransscript
102 | recognition.onresult = function (event) {
103 | let result = event.results[ event.results.length - 1 ]
104 | // let section = $('').text(result[result.length - 1 ].transcript)
105 | // $(text).append(section);
106 | let transcript = result[ result.length - 1 ].transcript.trim()
107 | let caption = SpeechToText.getCaption()
108 | if (caption === '' && transcript !== '' && !tempStart) {
109 | tempStart = SpeechToText.getPlayerCurrentTime(adjustStartSecond)
110 | //SpeechToText.setStart(SpeechToText.getPlayerCurrentTime(adjustStartSecond))
111 | }
112 | tempTransscript = transcript
113 |
114 | // 偵錯
115 | if (disableExceptionCount === false
116 | && Array.isArray(settingExceptionCases[recognition.lang])
117 | && settingExceptionCases[recognition.lang].indexOf(transcript) > -1) {
118 | settingExceptionCount++
119 | if (settingExceptionCount === 3) {
120 | //settingExceptionCount = 0
121 | app.openVirtualAudioCableHelpModel()
122 | }
123 | }
124 | else {
125 | disableExceptionCount = true
126 | }
127 |
128 | }
129 |
130 | // speech error handling
131 | recognition.onerror = function (event) {
132 | console.log('error', event)
133 | }
134 |
135 | let lastPauseTime = 0
136 | recognition.onend = function () {
137 | let hasAdd = true
138 | if (tempTransscript) {
139 | if (Array.isArray(settingExceptionCases[recognition.lang])
140 | && settingExceptionCases[recognition.lang].indexOf(tempTransscript) > -1) {
141 | tempStart = null
142 | tempTransscript = null
143 | hasAdd = false
144 | }
145 | else {
146 | SpeechToText.setStart(tempStart)
147 | SpeechToText.setCaption(tempTransscript)
148 | tempStart = null
149 | tempTransscript = null
150 | }
151 | }
152 |
153 | let caption = SpeechToText.getCaption()
154 | if (caption !== '' && hasAdd === true) {
155 | SpeechToText.setEnd(SpeechToText.getPlayerCurrentTime(adjustEndSecond))
156 | }
157 | //console.log('end')
158 | // auto restart
159 | if (playingFlag === true) {
160 | if (caption !== '' && hasAdd === true) {
161 | SpeechToText.addRow()
162 | }
163 |
164 | //console.log('被暫停了2', SpeechToText.getCurrentTime(), SpeechToText.getDuration())
165 | if (SpeechToText.getCurrentTime() < pauseInterval
166 | || (SpeechToText.getCurrentTime() - lastPauseTime) < pauseInterval) {
167 | recognition.setFinishEvent()
168 | recognition.start()
169 | }
170 | else {
171 | console.log('現在暫停休息中', window.performance.memory)
172 | lastPauseTime = SpeechToText.getCurrentTime()
173 | SpeechToText.pause()
174 | recognition.clearFinishEvent()
175 | //settingExceptionCount = 0
176 | //console.log(window.performance)
177 | setTimeout(() => {
178 | recognition.start()
179 | SpeechToText.play()
180 | recognition.setFinishEvent()
181 | }, pauseSeconds * 1000)
182 | }
183 | }
184 | }
185 |
186 | let finishTimer
187 | recognition.clearFinishEvent = function () {
188 | clearTimeout(finishTimer)
189 | }
190 | recognition.setFinishEvent = function () {
191 | let time = SpeechToText.getDuration() - SpeechToText.getCurrentTime()
192 | time = (time * 1000) + 100
193 | //console.log([SpeechToText.getDuration(), SpeechToText.getCurrentTime()])
194 |
195 | //console.log('重新設定結束時間', time)
196 | clearTimeout(finishTimer)
197 | finishTimer = setTimeout(() => {
198 | clearTimeout(finishTimer)
199 | console.log('結束了')
200 | recognitionFinish()
201 | }, time)
202 | //console.log(audioPlay.duration * 1000 + 100)
203 | }
--------------------------------------------------------------------------------
/lib/speech-to-text.js:
--------------------------------------------------------------------------------
1 | /* global app, YouTubeUtils */
2 |
3 | let SpeechToText = {
4 | tbody: $('tbody.text-content'),
5 | player: null,
6 | setPlayer: function () {
7 | this.player = $('.audio-player')[0]
8 | },
9 | audioPlay: function () {
10 | if (this.player === null) {
11 | this.setPlayer()
12 | }
13 |
14 | if (this.getPlayerMode() === 'html') {
15 | this.player.play()
16 | }
17 | else if (this.getPlayerMode() === 'youtube') {
18 | YouTubeUtils.play()
19 | }
20 | },
21 | play () {
22 | return this.audioPlay()
23 | },
24 | stop () {
25 | if (this.player === null) {
26 | this.setPlayer()
27 | }
28 |
29 | if (this.getPlayerMode() === 'html') {
30 | this.player.stop()
31 | }
32 | else if (this.getPlayerMode() === 'youtube') {
33 | YouTubeUtils.stop()
34 | }
35 | },
36 | pause () {
37 | if (this.player === null) {
38 | this.setPlayer()
39 | }
40 |
41 | if (this.getPlayerMode() === 'html') {
42 | this.player.pause()
43 | }
44 | else if (this.getPlayerMode() === 'youtube') {
45 | YouTubeUtils.pause()
46 | }
47 | },
48 | getPlayerMode: function () {
49 | if ($('#audio_player_youtube').length === 0) {
50 | return 'html'
51 | }
52 | else {
53 | return 'youtube'
54 | }
55 | },
56 | playerDuration: null,
57 | getDuration: function () {
58 | if (this.playerDuration !== null) {
59 | return this.playerDuration
60 | }
61 |
62 | if (this.getPlayerMode() === 'html') {
63 | this.playerDuration = this.player.duration
64 | }
65 | else if (this.getPlayerMode() === 'youtube') {
66 | this.playerDuration = YouTubeUtils.getDuration()
67 | }
68 |
69 | // 設定完成時間
70 | var currentTime = (new Date()).getTime()
71 |
72 | let duration = this.playerDuration
73 | // 每30秒,增加3秒
74 | duration = duration + Math.ceil((duration / 30) * 3)
75 | //console.log(duration, this.playerDuration, YouTubeUtils.getDuration())
76 | var finishTime = new Date(currentTime + (duration * 1000) + 100)
77 | var finishTimeStr = this.prefixPaddingZero(finishTime.getHours(), 2)
78 | + ':'
79 | + this.prefixPaddingZero(finishTime.getMinutes(), 2)
80 | + ":"
81 | + this.prefixPaddingZero(finishTime.getSeconds(), 2)
82 |
83 | app.finishTime = finishTimeStr
84 |
85 | //console.log(finishTimeStr)
86 |
87 | return this.playerDuration
88 | },
89 | getCurrentTime () {
90 | if (this.getPlayerMode() === 'html') {
91 | return this.player.currentTime
92 | }
93 | else if (this.getPlayerMode() === 'youtube') {
94 | return YouTubeUtils.getCurrentTime()
95 | }
96 | },
97 | addRow: function () {
98 | if (this.isLastRowEmpty()) {
99 | return null
100 | }
101 |
102 | let tr = $(`
103 |
104 |
105 |
106 | :
107 |
108 | :
109 |
110 | ,
111 |
112 | |
113 | - |
114 |
115 |
116 | :
117 |
118 | :
119 |
120 | ,
121 |
122 | |
123 | |
124 |
`)
125 | //tr.find('textarea').autogrow({vertical: true, horizontal: false})
126 | tr.find('textarea').css('height', '2.1rem')
127 | this.tbody.append(tr)
128 |
129 | // scroll to bottom
130 | let container = $('table.tbody')[0]
131 | container.scrollTop = container.scrollHeight;
132 | },
133 | addExampleRow: function (_loop) {
134 | this.addRow()
135 | this.tbody.find('input').val('999')
136 | this.tbody.find('textarea').val('Test test test')
137 |
138 | if (typeof(_loop) === 'number') {
139 | _loop--
140 | if (_loop > 0) {
141 | this.addExampleRow(_loop)
142 | }
143 | }
144 | },
145 | isLastRowEmpty: function () {
146 | let lastCaption = this.tbody.find('tr:last .caption textarea')
147 | if (lastCaption.length === 0) {
148 | return false
149 | }
150 | else {
151 | return (lastCaption.val().trim() === '')
152 | }
153 | },
154 | hasContent: function () {
155 | return (this.tbody.children().length > 0)
156 | },
157 | removeEmptyRow: function () {
158 | this.tbody.find('tr .caption textarea').each((i, caption) => {
159 | caption = $(caption)
160 | if (caption.val().trim() === '') {
161 | caption.parents('tr:first').remove()
162 | }
163 | })
164 | },
165 | getCaption: function () {
166 | return this.tbody.find('tr:last .caption textarea').val()
167 | },
168 | reset: function () {
169 | // 清理資料
170 | this.tbody.empty()
171 |
172 | // 把狀態改掉
173 | $('.recognition-status').attr('data-recognition-status', 'ready')
174 | },
175 | setCaption: function (caption) {
176 | let textarea = this.tbody.find('tr:last .caption textarea')
177 | textarea.val(caption.trim()).autogrow({vertical: true, horizontal: false, flickering: false})
178 |
179 | },
180 | setStart: function (time) {
181 | var [hour, minute, second, millisecond, currentTime] = time
182 | let timeField = this.tbody.find('tr:last .start')
183 | timeField.find('.hour').val(hour)
184 | timeField.find('.minute').val(minute)
185 | timeField.find('.second').val(second)
186 | timeField.find('.millisecond').val(millisecond)
187 | timeField.attr('currentTime', currentTime)
188 |
189 | // 回去調整最後一個欄位
190 | if (this.tbody.find('tr').length > 1) {
191 | let lastEnd = this.tbody.find('tr:last').prev().find('.end')
192 | let lastEndCurrentTime = lastEnd.attr('currentTime')
193 | lastEndCurrentTime = parseFloat(lastEndCurrentTime)
194 | if (currentTime < lastEndCurrentTime) {
195 | lastEnd.find('.hour').val(hour)
196 | lastEnd.find('.minute').val(minute)
197 | lastEnd.find('.second').val(second)
198 | lastEnd.find('.millisecond').val(millisecond)
199 | lastEnd.attr('currentTime', currentTime)
200 | }
201 | }
202 | },
203 | setEnd: function (time) {
204 | var [hour, minute, second, millisecond, currentTime] = time
205 | let timeField = this.tbody.find('tr:last .end')
206 | timeField.find('.hour').val(hour)
207 | timeField.find('.minute').val(minute)
208 | timeField.find('.second').val(second)
209 | timeField.find('.millisecond').val(millisecond)
210 | timeField.attr('currentTime', currentTime)
211 | },
212 | getPlayerCurrentTime: function (adjust) {
213 | let currentTime
214 |
215 | if (this.getPlayerMode() === 'html') {
216 | currentTime = this.player.currentTime
217 | }
218 | else if (this.getPlayerMode() === 'youtube') {
219 | currentTime = YouTubeUtils.getCurrentTime()
220 | }
221 |
222 | if (currentTime > this.getDuration()) {
223 | currentTime = this.getDuration()
224 | }
225 |
226 | if (typeof(adjust) === 'number') {
227 | currentTime = currentTime + adjust
228 | }
229 | let sec = currentTime
230 |
231 | let minSec = Math.floor(sec * 1000 % 1000)
232 |
233 | /*
234 | if (minSec < 10) {
235 | minSec = '00' + minSec
236 | } else if (minSec < 100) {
237 | minSec = '0' + minSec
238 | }
239 | */
240 |
241 | let min = Math.floor(sec / 60 % 60)
242 | let hour = Math.floor(sec / (60 * 60))
243 |
244 | sec = Math.floor(sec % 60)
245 |
246 | /*
247 | sec = Math.floor(sec & 60)
248 | if (sec < 10) {
249 | sec = '0' + sec
250 | }
251 | */
252 |
253 | return [hour, min, sec, minSec, currentTime]
254 | },
255 | prefixPaddingZero: function (number, length) {
256 | if (typeof(number) === 'number') {
257 | number = number + ''
258 | }
259 |
260 | while (number.length < length) {
261 | number = '0' + number
262 | }
263 | return number
264 | },
265 | getContent: function () {
266 | let content = []
267 | this.tbody.find('tr').each((i, tr) => {
268 | tr = $(tr)
269 | let item = {}
270 | item.start = {
271 | hour: this.prefixPaddingZero(tr.find('.start .hour').val(), 2),
272 | minute: this.prefixPaddingZero(tr.find('.start .minute').val(), 2),
273 | second: this.prefixPaddingZero(tr.find('.start .second').val(), 2),
274 | millisecond: this.prefixPaddingZero(tr.find('.start .millisecond').val(), 3),
275 | }
276 | item.end = {
277 | hour: this.prefixPaddingZero(tr.find('.end .hour').val(), 2),
278 | minute: this.prefixPaddingZero(tr.find('.end .minute').val(), 2),
279 | second: this.prefixPaddingZero(tr.find('.end .second').val(), 2),
280 | millisecond: this.prefixPaddingZero(tr.find('.end .millisecond').val(), 3),
281 | }
282 | item.caption = tr.find('.caption textarea').val().trim(),
283 |
284 | content.push(item)
285 | })
286 | return content
287 | },
288 | complete: function () {
289 | SpeechToText.removeEmptyRow()
290 | app.finishTime = ''
291 | },
292 | confirmBeforeUnload() {
293 | if (SpeechToText.hasContent()) {
294 | return i18n.t('Text will be reset. Are you sure?')
295 | }
296 | }
297 | }
298 |
299 | $(window).resize(function () {
300 | $('.caption textarea').css('height', '3rem').autogrow({vertical: true, horizontal: false, flickering: false})
301 | })
302 |
303 | window.onbeforeunload = SpeechToText.confirmBeforeUnload;
--------------------------------------------------------------------------------
/lib/voice-access.js:
--------------------------------------------------------------------------------
1 | VoiceAccess = {
2 | check: function () {
3 | // 如果本機有,那就確認
4 | // initialization
5 | if( localStorage.getItem("voice_access") === null
6 | || localStorage.getItem("voice_access") !== 'granted'){
7 | localStorage.setItem("voice_access", "prompt")
8 | $('.ui.modal.voice-access-modal').modal('show')
9 | }
10 | },
11 | grantMicrophonePermission: function () {
12 | // Then somewhere
13 | navigator.getUserMedia({
14 | audio: true
15 | }, function (e) {
16 |
17 | // http://stackoverflow.com/q/15993581/1008999
18 | //
19 | // In chrome, If your app is running from SSL (https://),
20 | // this permission will be persistent.
21 | // That is, users won't have to grant/deny access every time.
22 | localStorage.setItem("voice_access", "granted");
23 |
24 | }, function(err){
25 |
26 | if(err.name === "PermissionDismissedError"){
27 | localStorage.setItem("voice_access", "prompt");
28 | }
29 | if(err.name === "PermissionDeniedError"){
30 | localStorage.setItem("voice_access", "denied");
31 | }
32 |
33 | });
34 | }
35 | }
36 |
37 | $(function () {
38 | VoiceAccess.check()
39 | })
--------------------------------------------------------------------------------
/lib/youtuble-utils.js:
--------------------------------------------------------------------------------
1 | YouTubeUtils = {
2 | player: null,
3 | isAPIReady: true,
4 | isPlayerReady: false,
5 | playerReadyCallback: null,
6 | validateYouTubeUrl: function (url) {
7 | if (url != undefined || url != '') {
8 | var regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=|\?v=)([^#\&\?]*).*/;
9 | var match = url.match(regExp);
10 | if (match && match[2].length == 11) {
11 | // Do anything for being valid
12 | // if need to change the url to embed url then use below line
13 | return match[2]
14 | //$('#ytplayerSide').attr('src', 'https://www.youtube.com/embed/' + match[2] + '?autoplay=0');
15 | }
16 | else {
17 | // Do anything for not being valid
18 | return false
19 | }
20 | }
21 | },
22 | setPlayer: function (containerId, videoId, callback) {
23 | this.isPlayerReady = false
24 | if (this.isAPIReady === false) {
25 | setTimeout(function () {
26 | YouTubeUtils.setPlayer(videoId)
27 | }, 1000)
28 | return null
29 | }
30 |
31 | // GE7sc_XvJ8w
32 |
33 | if (typeof(callback) === 'function') {
34 | this.playerReadyCallback = callback
35 | }
36 |
37 | $('#' + containerId).empty()
38 |
39 | YouTubeUtils.player = new YT.Player(containerId, {
40 | height: '180',
41 | width: '320',
42 | start: 0,
43 | videoId: videoId,
44 | events: {
45 | 'onReady': YouTubeUtils.onPlayerReady,
46 | 'onStateChange': YouTubeUtils.onPlayerStateChange
47 | }
48 | });
49 |
50 | },
51 | onPlayerReady: function (event) {
52 | event.target.unMute()
53 | event.target.setVolume(100)
54 | event.target.seekTo(0)
55 | event.target.playVideo()
56 | //event.target.playVideo();
57 | /*
58 | this.isPlayerReady = true
59 | setTimeout(function () {
60 | console.log(YouTubeUtils.getDuration())
61 | }, 0)
62 | */
63 | },
64 | onPlayerStateChange: function (event) {
65 | if (event.data === YT.PlayerState.PLAYING && !YouTubeUtils.isPlayerReady) {
66 | //setTimeout(stopVideo, 6000);
67 | YouTubeUtils.player.pauseVideo()
68 | YouTubeUtils.isPlayerReady = true
69 | //console.log(YouTubeUtils.getDuration())
70 |
71 | if (typeof(YouTubeUtils.playerReadyCallback) === 'function') {
72 | YouTubeUtils.playerReadyCallback()
73 | }
74 | YouTubeUtils.playerReadyCallback = null
75 | }
76 | },
77 | play: function () {
78 | if (this.isPlayerReady === false) {
79 | return
80 | }
81 | this.player.playVideo()
82 | },
83 | stop: function () {
84 | if (this.isPlayerReady === false) {
85 | return
86 | }
87 | this.player.stopVideo()
88 | },
89 | pause: function () {
90 | if (this.isPlayerReady === false) {
91 | return
92 | }
93 | this.player.pauseVideo()
94 | },
95 | getDuration: function () {
96 | if (this.isPlayerReady === false) {
97 | return
98 | }
99 | return this.player.getDuration()
100 | },
101 | getCurrentTime: function () {
102 | if (this.isPlayerReady === false) {
103 | return
104 | }
105 | return this.player.getCurrentTime()
106 | },
107 | getTitle: function () {
108 | if (this.isPlayerReady === false) {
109 | return
110 | }
111 | return this.player.getVideoData().title
112 | }
113 | }
114 |
115 |
116 | if (!window['YT']) {var YT = {loading: 0,loaded: 0};}if (!window['YTConfig']) {var YTConfig = {'host': 'http://www.youtube.com'};}if (!YT.loading) {YT.loading = 1;(function(){var l = [];YT.ready = function(f) {if (YT.loaded) {f();} else {l.push(f);}};window.onYTReady = function() {YT.loaded = 1;for (var i = 0; i < l.length; i++) {try {l[i]();} catch (e) {}}};YT.setConfig = function(c) {for (var k in c) {if (c.hasOwnProperty(k)) {YTConfig[k] = c[k];}}};var a = document.createElement('script');a.type = 'text/javascript';a.id = 'www-widgetapi-script';a.src = 'https://s.ytimg.com/yts/jsbin/www-widgetapi-vflkA4wlR/www-widgetapi.js';a.async = true;var c = document.currentScript;if (c) {var n = c.nonce || c.getAttribute('nonce');if (n) {a.setAttribute('nonce', n);}}var b = document.getElementsByTagName('script')[0];b.parentNode.insertBefore(a, b);})();}
--------------------------------------------------------------------------------
/locale/README.md:
--------------------------------------------------------------------------------
1 | # 撰寫語系檔注意事項
2 | https://github.com/essoduke/javascript-i18n-core
3 |
4 |
5 | * JSON格式務必正確 https://jsonlint.com/
6 | * 不可以使用註解
--------------------------------------------------------------------------------
/locale/en-us.js:
--------------------------------------------------------------------------------
1 | {
2 | "setting": {
3 | "UTC": 0,
4 | "format": "D, d F Y H:i:s",
5 | "AM": "AM",
6 | "PM": "PM"
7 | },
8 | "shortMonths": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
9 | "longMonths": ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
10 | "shortDays": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
11 | "longDays": ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/locale/zh-tw.js:
--------------------------------------------------------------------------------
1 | {
2 | "setting": {
3 | "UTC": 8,
4 | "format": "Y 年 m 月 d 日 (D) H:i:s",
5 | "AM": "上午",
6 | "PM": "下午"
7 | },
8 | "shortMonths": ["1 月", "2 月", "3 月", "4 月", "5 月", "6 月", "7 月", "8 月", "9 月", "10 月", "11 月", "12 月"],
9 | "longMonths": ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
10 | "shortDays": ["日", "一", "二", "三", "四", "五", "六"],
11 | "longDays": ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"],
12 |
13 | "Web Speech to Text": "Web Speech to Text",
14 | "Please install and enable Virtual Audio Cable:": "請安裝並啟用Virtual Audio Cable:",
15 | "Load Local File": "讀取本機檔案",
16 | "Load from URL": "讀取網址",
17 | "Start": "開始辨識",
18 | "Playing": "辨識中",
19 | "Finish": "辨識完成",
20 | "Loading": "讀取中",
21 | "Filename": "檔案名稱",
22 | "Language": "辨識語言",
23 | "cmn-Hant-TW": "中文 (台灣)(cmn-Hant-TW)",
24 | "cmn-Hans-CN": "普通话 (中国大陆)(cmn-Hans-CN)",
25 | "en-US": "英文(en-US)",
26 | "ja": "日文(ja)",
27 | "ko": "韓文(ko)",
28 | "Custom Language": "自選語言",
29 | "Subtitle (.srt)": "字幕檔(.srt)",
30 | "OpenDocument Spreadsheet (.ods)": "開放文件試算表(.ods)",
31 | "Plain text (.txt)": "純文字格式(.txt)",
32 | "Only caption (.txt)": "只有辨識文字(.txt)",
33 | "Output type": "輸出格式",
34 | "Output download": "下載檔案",
35 | "Download": "下載",
36 | "Start from": "開始時間",
37 | "End in": "結束時間",
38 | "Caption": "辨識文字",
39 | "Support:": "支援格式:",
40 | "YouTube, for example: ": "YouTube網址,例如:",
41 | "Video:": "影片:",
42 | "Audio:": "聲音:",
43 | "wav, ogg, mp3 format": "wav、ogg、mp3格式",
44 | "Cancel": "取消",
45 | "OK": "確定",
46 | "Help": "說明",
47 | "Grant Microphone Permission": "啟用麥克風權限",
48 | "Grant": "允許",
49 | "Enable Virtual Audio Cable": "啟用Virtual Audio Cable",
50 | "Open your Sound setting.": "開啟聲音設定,",
51 | "Set Virtual Audio Cable default in playback and recording.": "在播放裝置和錄音裝置中,將Virtual Audio Cable設為預設。",
52 | "Load Demo": "讀取展示影片",
53 | "Finish in": "完成時間",
54 | "Long video": "長影片"
55 | }
56 |
--------------------------------------------------------------------------------
/nbproject/project.properties:
--------------------------------------------------------------------------------
1 | auxiliary.org-netbeans-modules-css-prep.less_2e_compiler_2e_options=
2 | auxiliary.org-netbeans-modules-css-prep.less_2e_enabled=false
3 | auxiliary.org-netbeans-modules-css-prep.less_2e_mappings=/less:/css
4 | auxiliary.org-netbeans-modules-css-prep.sass_2e_compiler_2e_options=
5 | auxiliary.org-netbeans-modules-css-prep.sass_2e_enabled=false
6 | auxiliary.org-netbeans-modules-css-prep.sass_2e_mappings=/scss:/css
7 | auxiliary.org-netbeans-modules-javascript-nodejs.enabled=false
8 | auxiliary.org-netbeans-modules-javascript-nodejs.node_2e_default=true
9 | auxiliary.org-netbeans-modules-javascript-nodejs.run_2e_enabled=false
10 | auxiliary.org-netbeans-modules-javascript-nodejs.sync_2e_enabled=true
11 | browser.autorefresh.Chrome=false
12 | browser.autorefresh.Chrome.INTEGRATED=false
13 | browser.highlightselection.Chrome=true
14 | browser.highlightselection.Chrome.INTEGRATED=true
15 | browser.run=true
16 | file.reference.projects-html5-HTML5-Speech-to-Text=.
17 | files.encoding=UTF-8
18 | site.root.folder=${file.reference.projects-html5-HTML5-Speech-to-Text}
19 | start.file=index.html
20 | web.context.root=/HTML5-Speech-to-Text
21 |
--------------------------------------------------------------------------------
/nbproject/project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | org.netbeans.modules.web.clientproject
4 |
5 |
6 | HTML5-Speech-to-Text
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "HTML5-Speech-to-Text",
3 | "version": "1.0.0",
4 | "description": "This is an online speech recognition tool implemented entirely in pure JavaScript. It utilizes the browser's built-in Web Speech API to convert audio captured by the microphone into text and combines it with time segments to create subtitles.",
5 | "main": "index.js",
6 | "scripts": {
7 | "tag": "./build/tag.sh"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://pulipuli.chen@gmail.com@github.com/pulipulichen/HTML5-Speech-to-Text.git"
12 | },
13 | "keywords": [
14 | "Web Speech API",
15 | "JavaScript"
16 | ],
17 | "author": {
18 | "name": "Yung-Ting Chen",
19 | "email": "blog@pulipuli.info",
20 | "orcid": "https://orcid.org/0000-0003-3751-610X",
21 | "url": "https://blog.pulipuli.info"
22 | },
23 | "license": "MIT",
24 | "bugs": {
25 | "url": "https://github.com/pulipulichen/HTML5-Speech-to-Text/issues"
26 | },
27 | "homepage": "https://github.com/pulipulichen/HTML5-Speech-to-Text#readme",
28 | "doi": "10.5281/zenodo.11213438",
29 | "devDependencies": {
30 | },
31 | "dependencies": {
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/script.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | // ---------------------
4 |
5 | /**
6 | * 程式開始時就先開始播放
7 | */
8 | $(function () {
9 | setTimeout(function () {
10 | //$('.start-btn').click()
11 | //SpeechToText.addExampleRow(3)
12 |
13 | /*
14 | console.log(xlsx_helper_ods_download('t', {
15 | "global": {
16 | "version": "1.0",
17 | "author": "布丁布丁吃布丁",
18 | "time": "2019/1/15 21:34"
19 | },
20 | "data": [
21 | {
22 | "col1": "1-1",
23 | "col2": "1-2"
24 | },
25 | {
26 | "col1": "2-1",
27 | "col2": "2-2"
28 | }
29 | ]
30 | }))
31 | */
32 |
33 | }, 1000)
34 | })
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 |
2 | @keyframes blink {
3 | from, to { border-color: transparent }
4 | 50% { border-color: black }
5 | }
6 |
7 | .blinking {
8 | border-left:1px solid black;
9 | animation: 1s blink step-end infinite;
10 | }
11 |
12 | tbody.text-content {
13 | overflow-y: auto;
14 | overflow-x: hidden;
15 | max-height: 50vh;
16 | }
17 |
18 | tbody.text-content.hide {
19 | display: none;
20 | }
21 |
22 | /*
23 | .content table {
24 | width: 100%;
25 | }
26 | */
27 |
28 | table tr th.start,
29 | table tr th.end {
30 | width: 280px;
31 | }
32 |
33 | table tr th.hyper {
34 | width: 0.5rem;
35 | padding-left: 0 !important;
36 | padding-right: 0 !important;
37 | }
38 |
39 | table.tbody tr th.hyper {
40 | padding-top: 1.1rem;
41 | }
42 |
43 | table tr td.caption {
44 | width: calc(100vw - 37rem);
45 | }
46 |
47 | tbody.text-content th {
48 | /*width: 1px;*/
49 | vertical-align: top;
50 | padding-top: 10px;
51 | }
52 |
53 | tbody.text-content th input {
54 | text-align: right;
55 | width: 2.2rem !important;
56 | font-size: 0.8rem !important;
57 | padding-left: 0 !important;
58 | padding-right: 0 !important;
59 | }
60 |
61 | tbody.text-content th input.hour {
62 | width: 2.7rem !important;
63 | }
64 |
65 | tbody.text-content th input.millisecond {
66 | width: 3rem !important;
67 | }
68 |
69 | tbody.text-content td textarea {
70 | width: 100%;
71 | /*height: auto !important;*/
72 | min-height: 2.1rem !important;
73 | height: 2.1rem;
74 | max-height: auto !important;
75 | padding-top: 0 !important;
76 | padding-bottom: 0 !important;
77 | line-height: 1.8rem !important;
78 | }
79 |
80 | table.thead {
81 | margin-bottom: 0 !important;
82 | border-radius: .28571429rem .28571429rem 0 0 !important;
83 | border-bottom-width: 0 !important;
84 | }
85 | table.tbody {
86 | margin-top: 0 !important;
87 | border-radius: 0 0 .28571429rem .28571429rem !important;
88 | border-top-width: 0 !important;
89 | display: block;
90 | max-height: calc(100vh - 35rem);
91 | min-height: 9rem;
92 | overflow-y: auto;
93 | overflow-x: hidden;
94 | }
95 |
96 | button.download-btn {
97 | line-height: 1.4rem !important;
98 | /*margin-top: 1.7rem !important;*/
99 | }
100 |
101 | audio {
102 | display: none;
103 | }
104 |
105 | h1 {
106 | text-align: center;
107 | }
108 |
109 | .recognition-status .button,
110 | .recognition-status .finish-time {
111 | display: none !important;
112 | }
113 |
114 | .recognition-status[data-recognition-status="ready"] .start-btn,
115 | .recognition-status[data-recognition-status="ready"] .buttons .button {
116 | display: block !important;
117 | }
118 |
119 | .recognition-status[data-recognition-status='loading'] .loading-btn {
120 | display: block !important;
121 | }
122 |
123 | .recognition-status[data-recognition-status='wait'] .playing-btn,
124 | .recognition-status[data-recognition-status='wait'] .finish-time {
125 | display: block !important;
126 | }
127 |
128 | .recognition-status[data-recognition-status='finish'] .finish-btn,
129 | .recognition-status[data-recognition-status="finish"] .buttons .button {
130 | display: block !important;
131 | }
132 |
133 | #audio_player iframe,
134 | #audio_player video {
135 | pointer-events: none;
136 | }
137 |
138 | .virtual-audio-cable-warning {
139 | margin-bottom: 0.5rem !important;
140 | }
141 |
142 | .finish-time {
143 | text-align: center;
144 | }
145 |
146 | .content img {
147 | width: 49%;
148 | height: auto;
149 | }
--------------------------------------------------------------------------------
/vender/autosize.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | autosize 4.0.2
3 | license: MIT
4 | http://www.jacklmoore.com/autosize
5 | */
6 | !function(e,t){if("function"==typeof define&&define.amd)define(["module","exports"],t);else if("undefined"!=typeof exports)t(module,exports);else{var n={exports:{}};t(n,n.exports),e.autosize=n.exports}}(this,function(e,t){"use strict";var n,o,p="function"==typeof Map?new Map:(n=[],o=[],{has:function(e){return-1 {
30 | if (i < fileList.length) {
31 |
32 | let url = fileList[i]
33 |
34 |
35 | let fileName = url
36 | if (fileName.lastIndexOf('/') > 0) {
37 | fileName = fileName.slice(fileName.lastIndexOf('/') + 1)
38 | }
39 | if (fileName.indexOf('?') > 0) {
40 | fileName = fileName.slice(0, fileName.indexOf('?'))
41 | }
42 | if (fileName.indexOf('.') === -1) {
43 | fileName = fileName + '.png'
44 | }
45 |
46 | console.log('start download file ' + (i+1) + '/' + fileList.length + ' ' + fileName + ': ' + url)
47 |
48 | if (isAppendNumber === true) {
49 | fileName = (i+1) + '-' + fileName
50 | }
51 |
52 | JSZipUtils.getBinaryContent(url, function (err, data) {
53 | if (err) {
54 | console.log('Problem happened when download img ' + (i+1) + '/' + fileList.length + ': ' + url)
55 | loop(i)
56 | return
57 | } else {
58 | zip.file(fileName, data, {binary: true});
59 | //deferred.resolve(zip);
60 | }
61 |
62 | setTimeout(() => {
63 | i++
64 | loop(i)
65 | },10)
66 | });
67 | }
68 | else {
69 | var generateOptions = {type:"blob",
70 | compression: 'DEFLATE',
71 | compressionOptions: {
72 | level: 9
73 | }
74 | };
75 |
76 | zip.generateAsync(generateOptions).then(function(content) {
77 | saveAs(content, filename + '.zip');
78 | console.log('download finish: ' + filename + '.zip')
79 | });
80 | }
81 | }
82 |
83 | loop(0)
84 | }
--------------------------------------------------------------------------------
/vender/garlic/README.md:
--------------------------------------------------------------------------------
1 | https://cdn.bootcss.com/garlic.js/1.4.0/garlic.min.js
2 |
3 | Form remember
4 |
5 | Usage:
6 |
7 | javascript garlic
8 |
9 | data-persist="garlic"
10 |
11 | http://garlicjs.org/
12 |
13 | $('[data-persist="garlic"] input').change();
14 |
15 | 不能儲存
16 | 但可以
--------------------------------------------------------------------------------
/vender/garlic/garlic.min.js:
--------------------------------------------------------------------------------
1 | // Garlic.js v1.4.0
2 | !function(t){function e(n){if(i[n])return i[n].exports;var s=i[n]={i:n,l:!1,exports:{}};return t[n].call(s.exports,s,s.exports,e),s.l=!0,s.exports}var i={};e.m=t,e.c=i,e.d=function(t,i,n){e.o(t,i)||Object.defineProperty(t,i,{configurable:!1,enumerable:!0,get:n})},e.n=function(t){var i=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(i,"a",i),i},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=0)}([function(t,e){!function(t){"use strict";var e=function(t){this.defined="undefined"!=typeof localStorage;var e="garlic:"+document.domain+">test";try{localStorage.setItem(e,e),localStorage.removeItem(e)}catch(t){this.defined=!1}};e.prototype={constructor:e,get:function(t,e){var i=localStorage.getItem(t);if(i){try{i=JSON.parse(i)}catch(t){}return i}return void 0!==e?e:null},has:function(t){return!!localStorage.getItem(t)},set:function(t,e,i){return""===e||e instanceof Array&&0===e.length?this.destroy(t):(e=JSON.stringify(e),localStorage.setItem(t,e)),"function"!=typeof i||i()},destroy:function(t,e){return localStorage.removeItem(t),"function"!=typeof e||e()},clean:function(t){for(var e=localStorage.length-1;e>=0;e--)void 0===Array.indexOf&&-1!==localStorage.key(e).indexOf("garlic:")&&localStorage.removeItem(localStorage.key(e));return"function"!=typeof t||t()},clear:function(t){return localStorage.clear(),"function"!=typeof t||t()}};var i=function(t,e,i){this.init("garlic",t,e,i)};i.prototype={constructor:i,init:function(e,i,n,s){this.type=e,this.$element=t(i),this.options=this.getOptions(s),this.storage=n,this.path=this.options.getPath(this.$element)||this.getPath(),this.parentForm=this.$element.closest("form"),this.$element.addClass("garlic-auto-save"),this.expiresFlag=!!this.options.expires&&(this.$element.data("expires")?this.path:this.getPath(this.parentForm))+"_flag",this.$element.on(this.options.events.join("."+this.type+" "),!1,t.proxy(this.persist,this)),this.options.destroy&&t(this.parentForm).on("submit reset",!1,t.proxy(this.remove,this)),this.retrieve()},getOptions:function(e){return t.extend({},t.fn[this.type].defaults,e,this.$element.data())},persist:function(){if(this.val!==this.getVal()){this.val=this.getVal(),this.options.expires&&this.storage.set(this.expiresFlag,((new Date).getTime()+1e3*this.options.expires).toString());var t=this.options.prePersist(this.$element,this.val);"string"==typeof t&&(this.val=t),this.storage.set(this.path,this.val),this.options.onPersist(this.$element,this.val)}},getVal:function(){return this.$element.is("input[type=checkbox]")?this.$element.prop("checked")?"checked":"unchecked":this.$element.val()},retrieve:function(){if(this.storage.has(this.path)){if(this.options.expires){var t=(new Date).getTime();if(this.storage.get(this.expiresFlag)1&&!t(a).is("input[type=radio]")&&(o+=":eq("+h.index(a)+")"),i=o+(i?">"+i:""),"form"==a.nodeName.toLowerCase())break;s=r}else s=r}return"garlic:"+document.domain+(this.options.domain?"*":window.location.pathname)+">"+i},getStorage:function(){return this.storage}},t.fn.garlic=function(n,s){function a(e){var s=t(e),a=s.data("garlic"),h=t.extend({},o,s.data());if((void 0===h.storage||h.storage)&&"password"!==t(e).attr("type"))return a||s.data("garlic",a=new i(e,r,h)),"string"==typeof n&&"function"==typeof a[n]?a[n]():void 0}var o=t.extend(!0,{},t.fn.garlic.defaults,n,this.data()),r=new e,h=!1;return!!r.defined&&(this.each(function(){if(t(this).is("form"))t(this).find(o.inputs).each(function(){t(this).is(o.excluded)||(h=a(t(this)))});else if(t(this).is(o.inputs)){if(t(this).is(o.excluded))return;h=a(t(this))}}),"function"==typeof s?s():h)},t.fn.garlic.Constructor=i,t.fn.garlic.defaults={destroy:!0,inputs:"input, textarea, select",excluded:'input[type="file"], input[type="hidden"], input[type="submit"], input[type="reset"]',events:["DOMAttrModified","textInput","input","change","click","keypress","paste","focus"],domain:!1,expires:!1,conflictManager:{enabled:!1,garlicPriority:!0,template:'',message:"This is your saved data. Click here to see default one",onConflictDetected:function(t,e){return!0}},getPath:function(t){},preRetrieve:function(t,e,i){return i},onRetrieve:function(t,e){},prePersist:function(t,e){return!1},onPersist:function(t,e){},onSwap:function(t,e,i){}},t(window).on("load",function(){t('[data-persist="garlic"]').each(function(){t(this).garlic()})})}(window.jQuery||window.Zepto)}]);
3 |
--------------------------------------------------------------------------------
/vender/i18n/README.md:
--------------------------------------------------------------------------------
1 | https://github.com/essoduke/javascript-i18n-core
--------------------------------------------------------------------------------
/vender/i18n/i18n-config.js:
--------------------------------------------------------------------------------
1 | i18n.set({
2 | 'lang': 'zh-tw', //e.g. en-us, zh-tw. Default is auto detect from browser.
3 | 'path': 'locale' // Default is empty (same level as i18n.js)
4 | });
--------------------------------------------------------------------------------
/vender/i18n/i18n.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Javascript Internationalization (i18n)
3 | * The easy way to perform i18n to your javascript project..
4 | *
5 | * @author Essoduke Chang
6 | * @see https://code.essoduke.org/i18n/
7 | * @version 1.2
8 | *
9 | * Last Modified 2015.07.10.165852
10 | */
11 | i18n = (function (window, undefined) {
12 |
13 | 'use strict';
14 |
15 | /**
16 | * Check the client timezone to adjust DST
17 | */
18 | var DST = function () {
19 | var rightNow = new Date(),
20 | temp = '',
21 | date1 = new Date(rightNow.getFullYear(), 0, 1, 0, 0, 0, 0),
22 | date2 = new Date(rightNow.getFullYear(), 6, 1, 0, 0, 0, 0),
23 | date3 = '',
24 | date4 = '',
25 | hoursDiffStdTime = 0,
26 | hoursDiffDaylightTime = 0;
27 | temp = date1.toGMTString();
28 | date3 = new Date(temp.substring(0, temp.lastIndexOf(" ") - 1));
29 | temp = date2.toGMTString();
30 | date4 = new Date(temp.substring(0, temp.lastIndexOf(" ") - 1));
31 | hoursDiffStdTime = (date1 - date3) / (1000 * 60 * 60);
32 | hoursDiffDaylightTime = (date2 - date4) / (1000 * 60 * 60);
33 | return (hoursDiffDaylightTime !== hoursDiffStdTime) ? true : false;
34 | },
35 |
36 | //language code
37 | lang = '',
38 |
39 | // language file's path
40 | path = '',
41 |
42 | //localize object
43 | localize = {},
44 |
45 | //default setting
46 | setting = {
47 | "UTC" : -4,
48 | "format" : "Y-m-d H:i:s",
49 | "AM" : "AM",
50 | "PM" : "PM"
51 | },
52 |
53 | //fetch lauguage string in JSON object
54 | locale = function (langcode) {
55 |
56 | var result = {},
57 | xmlhttp = {},
58 | url = [(0 !== path.length ? path.replace(/\/$/, '') + '/' : ''), langcode, '.js'].join(''),
59 | callback = {};
60 |
61 | // Use jQuery ajax
62 | if (window.jQuery) {
63 | jQuery.ajax({
64 | 'url' : url,
65 | 'async' : false,
66 | 'cache' : true,
67 | 'dataType': 'JSON'
68 | })
69 | .fail(function (xhr, status, error) {
70 | })
71 | .done(function (resp) {
72 | result = resp;
73 | });
74 | } else {
75 | // XMLHttpRequest callback
76 | callback = function () {
77 | if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
78 | try {
79 | if ('function' === typeof JSON.parse) {
80 | result = JSON.parse(xmlhttp.responseText);
81 | }
82 | } catch (ignore) {
83 | console.warn(ignore.message);
84 | }
85 | }
86 | };
87 | // Native XMLHttpRequest
88 | if (window.XMLHttpRequest) {
89 | xmlhttp = new XMLHttpRequest();
90 | if (xmlhttp.overrideMimeType) {
91 | xmlhttp.overrideMimeType('application/json; charset=UTF-8');
92 | }
93 | } else if (window.ActiveXObject) {
94 | var activexName = ['MSXML2.XMLHTTP', 'Microsoft.XMLHTTP'],
95 | i = 0;
96 | for (i = 0; i < activexName.length; i += 1) {
97 | try {
98 | xmlhttp = new ActiveXObject(activexName[i]);
99 | break;
100 | } catch (ignore) {
101 | console.warn(ignore.message);
102 | }
103 | }
104 | }
105 | xmlhttp.onreadystatechange = callback;
106 | //console.log(url)
107 | xmlhttp.open('GET', url, false);
108 | xmlhttp.send(null);
109 | }
110 | return result;
111 | };
112 |
113 | /**
114 | * _hasOwnProperty for compatibility IE
115 | * @param {Object} obj Object
116 | * @param {string} property Property name
117 | * @return {boolean}
118 | * @version 2.4.3
119 | */
120 | function _hasOwnProperty (obj, property) {
121 | try {
122 | return (!window.hasOwnProperty) ?
123 | Object.prototype.hasOwnProperty.call(obj, property.toString()) :
124 | obj.hasOwnProperty(property.toString());
125 | } catch (ignore) {
126 | console.warn(ignore.message);
127 | }
128 | }
129 |
130 | /**
131 | * Datetime format
132 | */
133 | Date.prototype.format = function (format) {
134 | var returnStr = '',
135 | replace = Date.replaceChars,
136 | curChar = '',
137 | i = 0;
138 | replace.reload();
139 | for (i = 0; i < format.length; i += 1) {
140 | curChar = format.charAt(i);
141 | returnStr += (replace[curChar] ? replace[curChar].call(this) : curChar);
142 | }
143 | return returnStr;
144 | };
145 |
146 | /**
147 | * Datetime function object
148 | * @see {@link http://jacwright.com/projects/javascript/date_format (unavailable)}
149 | * @see {@link http://code.google.com/p/omeglelogger/source/browse/trunk/dateformat.js?spec=svn2&r=2}
150 | */
151 | Date.replaceChars = {
152 |
153 | reload: function () {
154 | this.shortMonths = _hasOwnProperty(localize, 'shortMonths') ?
155 | localize.shortMonths :
156 | ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
157 | this.longMonths = _hasOwnProperty(localize, 'longMonths') ?
158 | localize.longMonths :
159 | ['January','February','March','April','May','June','July','August','September','October','November','December'];
160 | this.shortDays = _hasOwnProperty(localize, 'shortDays') ?
161 | localize.shortDays :
162 | ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
163 | this.longDays = _hasOwnProperty(localize, 'longDays') ?
164 | localize.longDays :
165 | ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
166 | },
167 | shortMonths: [],
168 | longMonths : [],
169 | shortDays : [],
170 | longDays : [],
171 | d: function () {
172 | return (this.getUTCDate() < 10 ? '0' : '') + this.getUTCDate();
173 | },
174 | D: function () {
175 | return Date.replaceChars.shortDays[this.getUTCDay()];
176 | },
177 | j: function () {
178 | return this.getUTCDate();
179 | },
180 | l: function () {
181 | return Date.replaceChars.longDays[this.getUTCDay()];
182 | },
183 | N: function () {
184 | return this.getDay() + 1;
185 | },
186 | S: function () {
187 | return (
188 | this.getUTCDate() % 10 === 1 && this.getUTCDate() !== 11 ?
189 | 'st' :
190 | (this.getUTCDate() % 10 === 2 && this.getUTCDate() !== 12 ? 'nd' :
191 | (this.getUTCDate() % 10 === 3 && this.getUTCDate() !== 13 ? 'rd' : 'th')
192 | )
193 | );
194 | },
195 | w: function () {
196 | return this.getUTCDay();
197 | },
198 | z: function () {
199 | return '';
200 | },
201 | W: function () {
202 | return '';
203 | },
204 | F: function () {
205 | return Date.replaceChars.longMonths[this.getUTCMonth()];
206 | },
207 | m: function () {
208 | return (this.getUTCMonth() < 9 ? '0' : '') + (this.getUTCMonth() + 1);
209 | },
210 | M: function () {
211 | return Date.replaceChars.shortMonths[this.getUTCMonth()];
212 | },
213 | n: function () {
214 | return this.getUTCMonth() + 1;
215 | },
216 | t: function () {
217 | return '';
218 | },
219 | L: function () {
220 | return '';
221 | },
222 | o: function () {
223 | return '';
224 | },
225 | Y: function () {
226 | return this.getUTCFullYear();
227 | },
228 | y: function () {
229 | return ('' + this.getUTCFullYear()).substr(2);
230 | },
231 | a: function () {
232 | var set = _hasOwnProperty(localize, 'setting') ? localize.setting : setting;
233 | return this.getUTCHours() < 12 ? set.AM : set.PM;
234 | },
235 | A: function () {
236 | var set = _hasOwnProperty(localize, 'setting') ? localize.setting : setting;
237 | return this.getUTCHours() < 12 ? set.AM : set.PM;
238 | },
239 | B: function () {
240 | return '';
241 | },
242 | g: function () {
243 | return this.getUTCHours() %12 || 12;
244 | },
245 | G: function () {
246 | return this.getUTCHours();
247 | },
248 | h: function () {
249 | return ((this.getUTCHours() % 12 || 12 ) < 10 ? '0' : '' ) +
250 | (this.getUTCHours() %12 || 12);
251 | },
252 | H: function () {
253 | return (this.getUTCHours() < 10 ? '0' : '') + this.getUTCHours();
254 | },
255 | i: function () {
256 | return (this.getUTCMinutes() < 10 ? '0' : '') +
257 | this.getUTCMinutes();
258 | },
259 | s: function () {
260 | return (this.getUTCSeconds() < 10 ? '0' : '') +
261 | this.getUTCSeconds();
262 | },
263 | e: function () {
264 | return '';
265 | },
266 | I: function () {
267 | return '';
268 | },
269 | O: function () {
270 | return (-this.getTimezoneOffset() < 0 ? '-' : '+') +
271 | (Math.abs(this.getTimezoneOffset() / 60) < 10 ? '0' : '') +
272 | (Math.abs(this.getTimezoneOffset() / 60)) + '00';
273 | },
274 | T: function () {
275 | var m = this.getUTCMonth();
276 | this.setMonth(0);
277 | var result = this.toTimeString().replace(/^.+ \(?([^\)]+)\)?$/, '$1');
278 | this.setMonth(m);
279 | return result;
280 | },
281 | Z: function () {
282 | return -this.getTimezoneOffset() * 60;
283 | },
284 | c: function () {
285 | return '';
286 | },
287 | r: function () {
288 | return this.toString();
289 | },
290 | U: function () {
291 | return this.getUTCTime() / 1000;
292 | }
293 | };
294 |
295 | /**
296 | * Similar the ASP Dateadd function
297 | */
298 | var DateAdd = function (interval, number, date ) {
299 | var d;
300 | number = parseInt(number, 10);
301 | if ('string' === typeof date) {
302 | date = date.split(/\D/);
303 | date[1] -= 1;
304 | d = new Date(date.join(','));
305 | }
306 | d = ('object' === typeof d) ? d : date;
307 | switch (interval) {
308 | case 'y':
309 | d.setFullYear(d.getFullYear() + number);
310 | break;
311 | case 'm':
312 | d.setMonth(d.getMonth() + number);
313 | break;
314 | case 'd':
315 | d.setDate(d.getDate() + number);
316 | break;
317 | case 'w':
318 | d.setDate(d.getDate() + 7 * number);
319 | break;
320 | case 'h':
321 | d.setHours(d.getHours() + number);
322 | break;
323 | case 'n':
324 | d.setMinutes(d.getMinutes() + number);
325 | break;
326 | case 's':
327 | d.setSeconds(d.getSeconds() + number);
328 | break;
329 | case 'l':
330 | d.setMilliseconds(d.getMilliseconds() + number);
331 | break;
332 | }
333 | return d;
334 | };
335 |
336 | /**
337 | * Public functions
338 | */
339 | return {
340 |
341 | /**
342 | * i18n setting
343 | */
344 | set: function (options) {
345 | if ('object' === typeof options) {
346 | lang = _hasOwnProperty(options, 'lang') ? options.lang : null;
347 | path = _hasOwnProperty(options, 'path') ? options.path : path;
348 | }
349 | //console.log(lang)
350 | //console.log(path)
351 | localize = locale(lang || (navigator.browserLanguage || navigator.language).toLowerCase());
352 | //console.log(localize)
353 | },
354 |
355 | /**
356 | * Core of the datetime replace
357 | */
358 | datetime: function (d) {
359 | var set = _hasOwnProperty(localize, 'setting') ?
360 | localize.setting :
361 | setting,
362 | r = set.format ?
363 | DateAdd('h', set.UTC, d ? new Date(d) : new Date()) :
364 | (d ? new Date(d) : new Date());
365 | return r.format(set.format);
366 | },
367 |
368 | needTransStrAry: [],
369 | needTransTimer: null,
370 |
371 | needTrans: function (str) {
372 | if (NeedTransDone === true) {
373 | return
374 | }
375 | if (this.needTransStrAry.indexOf(str) > -1) {
376 | return
377 | }
378 | this.needTransStrAry.push(str)
379 | if (typeof(NeedTransTimer) !== 'undefined') {
380 | clearTimeout(NeedTransTimer)
381 | }
382 | var _this = this
383 | NeedTransTimer = setTimeout(function () {
384 | _this.printNeedTrans()
385 | //clearTimeout(this.needTransTimer)
386 | }, 3000)
387 | },
388 | printNeedTrans: function () {
389 | //console.log(this.needTransStrAry)
390 | for (var i = 0; i < this.needTransStrAry.length; i++) {
391 | var str = this.needTransStrAry[i]
392 | str = '"' + str + '": "' + str + '"'
393 | this.needTransStrAry[i] = str
394 | }
395 | console.log(this.needTransStrAry.join(',\n'))
396 | this.needTransStrAry = []
397 | NeedTransDone = true
398 | },
399 |
400 | /**
401 | * Core of the strings replace
402 | */
403 | t: function (string, allow_nest) {
404 | if (allow_nest === undefined) {
405 | allow_nest = true;
406 | }
407 | try {
408 | string = string.toString() || '';
409 | var args = arguments,
410 | pattern = (args.length > 0) ?
411 | new RegExp('%([1-' + args.length.toString() + '])', 'g') :
412 | null,
413 | str = '';
414 | /*
415 | array = ~string.indexOf('.') ?
416 | string.split(/\./gi) :
417 | string;
418 | */
419 | var array = string;
420 | if (allow_nest === true) {
421 | array = ~string.indexOf('.') ?
422 | string.split(/\./gi) :
423 | string;
424 | }
425 |
426 | if ('string' === typeof array) {
427 | //console.log(localize)
428 | //console.log(_hasOwnProperty(localize, array))
429 | //console.log(array)
430 | if (_hasOwnProperty(localize, array) === false) {
431 | this.needTrans(array)
432 | }
433 |
434 | str = _hasOwnProperty(localize, array) ?
435 | localize[array] :
436 | array;
437 | } else if (Object.prototype.toString.call(array) === '[object Array]' && 2 === array.length) {
438 | str = _hasOwnProperty(localize, array[0]) ?
439 | (
440 | _hasOwnProperty(localize[array[0]], array[1]) ?
441 | localize[array[0]][array[1]] :
442 | array.join('.')
443 | ) :
444 | array.join('.');
445 | }
446 | return String(str).replace(pattern, function (match, index) {
447 | return args[index];
448 | });
449 | } catch (ignore) {
450 | console.dir(ignore);
451 | console.log(ignore);
452 | }
453 | }
454 | };
455 | }(window));
456 | //#EOF
457 |
458 |
459 | NeedTransTimer = null
460 | NeedTransDone = false
--------------------------------------------------------------------------------
/vender/jquery.min.js:
--------------------------------------------------------------------------------
1 | /*! jQuery v3.3.1 | (c) JS Foundation and other contributors | jquery.org/license */
2 | !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){"use strict";var n=[],r=e.document,i=Object.getPrototypeOf,o=n.slice,a=n.concat,s=n.push,u=n.indexOf,l={},c=l.toString,f=l.hasOwnProperty,p=f.toString,d=p.call(Object),h={},g=function e(t){return"function"==typeof t&&"number"!=typeof t.nodeType},y=function e(t){return null!=t&&t===t.window},v={type:!0,src:!0,noModule:!0};function m(e,t,n){var i,o=(t=t||r).createElement("script");if(o.text=e,n)for(i in v)n[i]&&(o[i]=n[i]);t.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[c.call(e)]||"object":typeof e}var b="3.3.1",w=function(e,t){return new w.fn.init(e,t)},T=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;w.fn=w.prototype={jquery:"3.3.1",constructor:w,length:0,toArray:function(){return o.call(this)},get:function(e){return null==e?o.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=w.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return w.each(this,e)},map:function(e){return this.pushStack(w.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(o.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n0&&t-1 in e)}var E=function(e){var t,n,r,i,o,a,s,u,l,c,f,p,d,h,g,y,v,m,x,b="sizzle"+1*new Date,w=e.document,T=0,C=0,E=ae(),k=ae(),S=ae(),D=function(e,t){return e===t&&(f=!0),0},N={}.hasOwnProperty,A=[],j=A.pop,q=A.push,L=A.push,H=A.slice,O=function(e,t){for(var n=0,r=e.length;n+~]|"+M+")"+M+"*"),z=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),X=new RegExp(W),U=new RegExp("^"+R+"$"),V={ID:new RegExp("^#("+R+")"),CLASS:new RegExp("^\\.("+R+")"),TAG:new RegExp("^("+R+"|[*])"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+W),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+P+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},G=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Q=/^[^{]+\{\s*\[native \w/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,K=/[+~]/,Z=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ee=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},te=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ne=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},re=function(){p()},ie=me(function(e){return!0===e.disabled&&("form"in e||"label"in e)},{dir:"parentNode",next:"legend"});try{L.apply(A=H.call(w.childNodes),w.childNodes),A[w.childNodes.length].nodeType}catch(e){L={apply:A.length?function(e,t){q.apply(e,H.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function oe(e,t,r,i){var o,s,l,c,f,h,v,m=t&&t.ownerDocument,T=t?t.nodeType:9;if(r=r||[],"string"!=typeof e||!e||1!==T&&9!==T&&11!==T)return r;if(!i&&((t?t.ownerDocument||t:w)!==d&&p(t),t=t||d,g)){if(11!==T&&(f=J.exec(e)))if(o=f[1]){if(9===T){if(!(l=t.getElementById(o)))return r;if(l.id===o)return r.push(l),r}else if(m&&(l=m.getElementById(o))&&x(t,l)&&l.id===o)return r.push(l),r}else{if(f[2])return L.apply(r,t.getElementsByTagName(e)),r;if((o=f[3])&&n.getElementsByClassName&&t.getElementsByClassName)return L.apply(r,t.getElementsByClassName(o)),r}if(n.qsa&&!S[e+" "]&&(!y||!y.test(e))){if(1!==T)m=t,v=e;else if("object"!==t.nodeName.toLowerCase()){(c=t.getAttribute("id"))?c=c.replace(te,ne):t.setAttribute("id",c=b),s=(h=a(e)).length;while(s--)h[s]="#"+c+" "+ve(h[s]);v=h.join(","),m=K.test(e)&&ge(t.parentNode)||t}if(v)try{return L.apply(r,m.querySelectorAll(v)),r}catch(e){}finally{c===b&&t.removeAttribute("id")}}}return u(e.replace(B,"$1"),t,r,i)}function ae(){var e=[];function t(n,i){return e.push(n+" ")>r.cacheLength&&delete t[e.shift()],t[n+" "]=i}return t}function se(e){return e[b]=!0,e}function ue(e){var t=d.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function le(e,t){var n=e.split("|"),i=n.length;while(i--)r.attrHandle[n[i]]=t}function ce(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function fe(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}function pe(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function de(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&ie(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function he(e){return se(function(t){return t=+t,se(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function ge(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}n=oe.support={},o=oe.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},p=oe.setDocument=function(e){var t,i,a=e?e.ownerDocument||e:w;return a!==d&&9===a.nodeType&&a.documentElement?(d=a,h=d.documentElement,g=!o(d),w!==d&&(i=d.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",re,!1):i.attachEvent&&i.attachEvent("onunload",re)),n.attributes=ue(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ue(function(e){return e.appendChild(d.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=Q.test(d.getElementsByClassName),n.getById=ue(function(e){return h.appendChild(e).id=b,!d.getElementsByName||!d.getElementsByName(b).length}),n.getById?(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){return e.getAttribute("id")===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n=t.getElementById(e);return n?[n]:[]}}):(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){var n="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),r.find.TAG=n.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):n.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=n.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&g)return t.getElementsByClassName(e)},v=[],y=[],(n.qsa=Q.test(d.querySelectorAll))&&(ue(function(e){h.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+P+")"),e.querySelectorAll("[id~="+b+"-]").length||y.push("~="),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+b+"+*").length||y.push(".#.+[+~]")}),ue(function(e){e.innerHTML="";var t=d.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),h.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(n.matchesSelector=Q.test(m=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&ue(function(e){n.disconnectedMatch=m.call(e,"*"),m.call(e,"[s!='']:x"),v.push("!=",W)}),y=y.length&&new RegExp(y.join("|")),v=v.length&&new RegExp(v.join("|")),t=Q.test(h.compareDocumentPosition),x=t||Q.test(h.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return f=!0,0;var r=!e.compareDocumentPosition-!t.compareDocumentPosition;return r||(1&(r=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!n.sortDetached&&t.compareDocumentPosition(e)===r?e===d||e.ownerDocument===w&&x(w,e)?-1:t===d||t.ownerDocument===w&&x(w,t)?1:c?O(c,e)-O(c,t):0:4&r?-1:1)}:function(e,t){if(e===t)return f=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===d?-1:t===d?1:i?-1:o?1:c?O(c,e)-O(c,t):0;if(i===o)return ce(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?ce(a[r],s[r]):a[r]===w?-1:s[r]===w?1:0},d):d},oe.matches=function(e,t){return oe(e,null,null,t)},oe.matchesSelector=function(e,t){if((e.ownerDocument||e)!==d&&p(e),t=t.replace(z,"='$1']"),n.matchesSelector&&g&&!S[t+" "]&&(!v||!v.test(t))&&(!y||!y.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(e){}return oe(t,d,null,[e]).length>0},oe.contains=function(e,t){return(e.ownerDocument||e)!==d&&p(e),x(e,t)},oe.attr=function(e,t){(e.ownerDocument||e)!==d&&p(e);var i=r.attrHandle[t.toLowerCase()],o=i&&N.call(r.attrHandle,t.toLowerCase())?i(e,t,!g):void 0;return void 0!==o?o:n.attributes||!g?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null},oe.escape=function(e){return(e+"").replace(te,ne)},oe.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},oe.uniqueSort=function(e){var t,r=[],i=0,o=0;if(f=!n.detectDuplicates,c=!n.sortStable&&e.slice(0),e.sort(D),f){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return c=null,e},i=oe.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else while(t=e[r++])n+=i(t);return n},(r=oe.selectors={cacheLength:50,createPseudo:se,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(Z,ee),e[3]=(e[3]||e[4]||e[5]||"").replace(Z,ee),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||oe.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&oe.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return V.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=a(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(Z,ee).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=E[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&E(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=oe.attr(r,e);return null==i?"!="===t:!t||(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i.replace($," ")+" ").indexOf(n)>-1:"|="===t&&(i===n||i.slice(0,n.length+1)===n+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,d,h,g=o!==a?"nextSibling":"previousSibling",y=t.parentNode,v=s&&t.nodeName.toLowerCase(),m=!u&&!s,x=!1;if(y){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===v:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?y.firstChild:y.lastChild],a&&m){x=(d=(l=(c=(f=(p=y)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1])&&l[2],p=d&&y.childNodes[d];while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if(1===p.nodeType&&++x&&p===t){c[e]=[T,d,x];break}}else if(m&&(x=d=(l=(c=(f=(p=t)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1]),!1===x)while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===v:1===p.nodeType)&&++x&&(m&&((c=(f=p[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]=[T,x]),p===t))break;return(x-=i)===r||x%r==0&&x/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||oe.error("unsupported pseudo: "+e);return i[b]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?se(function(e,n){var r,o=i(e,t),a=o.length;while(a--)e[r=O(e,o[a])]=!(n[r]=o[a])}):function(e){return i(e,0,n)}):i}},pseudos:{not:se(function(e){var t=[],n=[],r=s(e.replace(B,"$1"));return r[b]?se(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),t[0]=null,!n.pop()}}),has:se(function(e){return function(t){return oe(e,t).length>0}}),contains:se(function(e){return e=e.replace(Z,ee),function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:se(function(e){return U.test(e||"")||oe.error("unsupported lang: "+e),e=e.replace(Z,ee).toLowerCase(),function(t){var n;do{if(n=g?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===h},focus:function(e){return e===d.activeElement&&(!d.hasFocus||d.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:de(!1),disabled:de(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return Y.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:he(function(){return[0]}),last:he(function(e,t){return[t-1]}),eq:he(function(e,t,n){return[n<0?n+t:n]}),even:he(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:he(function(e,t,n){for(var r=n<0?n+t:n;++r1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function be(e,t,n){for(var r=0,i=t.length;r-1&&(o[l]=!(a[l]=f))}}else v=we(v===a?v.splice(h,v.length):v),i?i(null,a,v,u):L.apply(a,v)})}function Ce(e){for(var t,n,i,o=e.length,a=r.relative[e[0].type],s=a||r.relative[" "],u=a?1:0,c=me(function(e){return e===t},s,!0),f=me(function(e){return O(t,e)>-1},s,!0),p=[function(e,n,r){var i=!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):f(e,n,r));return t=null,i}];u1&&xe(p),u>1&&ve(e.slice(0,u-1).concat({value:" "===e[u-2].type?"*":""})).replace(B,"$1"),n,u0,i=e.length>0,o=function(o,a,s,u,c){var f,h,y,v=0,m="0",x=o&&[],b=[],w=l,C=o||i&&r.find.TAG("*",c),E=T+=null==w?1:Math.random()||.1,k=C.length;for(c&&(l=a===d||a||c);m!==k&&null!=(f=C[m]);m++){if(i&&f){h=0,a||f.ownerDocument===d||(p(f),s=!g);while(y=e[h++])if(y(f,a||d,s)){u.push(f);break}c&&(T=E)}n&&((f=!y&&f)&&v--,o&&x.push(f))}if(v+=m,n&&m!==v){h=0;while(y=t[h++])y(x,b,a,s);if(o){if(v>0)while(m--)x[m]||b[m]||(b[m]=j.call(u));b=we(b)}L.apply(u,b),c&&!o&&b.length>0&&v+t.length>1&&oe.uniqueSort(u)}return c&&(T=E,l=w),x};return n?se(o):o}return s=oe.compile=function(e,t){var n,r=[],i=[],o=S[e+" "];if(!o){t||(t=a(e)),n=t.length;while(n--)(o=Ce(t[n]))[b]?r.push(o):i.push(o);(o=S(e,Ee(i,r))).selector=e}return o},u=oe.select=function(e,t,n,i){var o,u,l,c,f,p="function"==typeof e&&e,d=!i&&a(e=p.selector||e);if(n=n||[],1===d.length){if((u=d[0]=d[0].slice(0)).length>2&&"ID"===(l=u[0]).type&&9===t.nodeType&&g&&r.relative[u[1].type]){if(!(t=(r.find.ID(l.matches[0].replace(Z,ee),t)||[])[0]))return n;p&&(t=t.parentNode),e=e.slice(u.shift().value.length)}o=V.needsContext.test(e)?0:u.length;while(o--){if(l=u[o],r.relative[c=l.type])break;if((f=r.find[c])&&(i=f(l.matches[0].replace(Z,ee),K.test(u[0].type)&&ge(t.parentNode)||t))){if(u.splice(o,1),!(e=i.length&&ve(u)))return L.apply(n,i),n;break}}}return(p||s(e,d))(i,t,!g,n,!t||K.test(e)&&ge(t.parentNode)||t),n},n.sortStable=b.split("").sort(D).join("")===b,n.detectDuplicates=!!f,p(),n.sortDetached=ue(function(e){return 1&e.compareDocumentPosition(d.createElement("fieldset"))}),ue(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||le("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&ue(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||le("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ue(function(e){return null==e.getAttribute("disabled")})||le(P,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),oe}(e);w.find=E,w.expr=E.selectors,w.expr[":"]=w.expr.pseudos,w.uniqueSort=w.unique=E.uniqueSort,w.text=E.getText,w.isXMLDoc=E.isXML,w.contains=E.contains,w.escapeSelector=E.escape;var k=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&w(e).is(n))break;r.push(e)}return r},S=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},D=w.expr.match.needsContext;function N(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var A=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,t,n){return g(t)?w.grep(e,function(e,r){return!!t.call(e,r,e)!==n}):t.nodeType?w.grep(e,function(e){return e===t!==n}):"string"!=typeof t?w.grep(e,function(e){return u.call(t,e)>-1!==n}):w.filter(t,e,n)}w.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?w.find.matchesSelector(r,e)?[r]:[]:w.find.matches(e,w.grep(t,function(e){return 1===e.nodeType}))},w.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(w(e).filter(function(){for(t=0;t1?w.uniqueSort(n):n},filter:function(e){return this.pushStack(j(this,e||[],!1))},not:function(e){return this.pushStack(j(this,e||[],!0))},is:function(e){return!!j(this,"string"==typeof e&&D.test(e)?w(e):e||[],!1).length}});var q,L=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(w.fn.init=function(e,t,n){var i,o;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(i="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:L.exec(e))||!i[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(i[1]){if(t=t instanceof w?t[0]:t,w.merge(this,w.parseHTML(i[1],t&&t.nodeType?t.ownerDocument||t:r,!0)),A.test(i[1])&&w.isPlainObject(t))for(i in t)g(this[i])?this[i](t[i]):this.attr(i,t[i]);return this}return(o=r.getElementById(i[2]))&&(this[0]=o,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):g(e)?void 0!==n.ready?n.ready(e):e(w):w.makeArray(e,this)}).prototype=w.fn,q=w(r);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};w.fn.extend({has:function(e){var t=w(e,this),n=t.length;return this.filter(function(){for(var e=0;e-1:1===n.nodeType&&w.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?w.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?u.call(w(e),this[0]):u.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(w.uniqueSort(w.merge(this.get(),w(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}w.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return k(e,"parentNode")},parentsUntil:function(e,t,n){return k(e,"parentNode",n)},next:function(e){return P(e,"nextSibling")},prev:function(e){return P(e,"previousSibling")},nextAll:function(e){return k(e,"nextSibling")},prevAll:function(e){return k(e,"previousSibling")},nextUntil:function(e,t,n){return k(e,"nextSibling",n)},prevUntil:function(e,t,n){return k(e,"previousSibling",n)},siblings:function(e){return S((e.parentNode||{}).firstChild,e)},children:function(e){return S(e.firstChild)},contents:function(e){return N(e,"iframe")?e.contentDocument:(N(e,"template")&&(e=e.content||e),w.merge([],e.childNodes))}},function(e,t){w.fn[e]=function(n,r){var i=w.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=w.filter(r,i)),this.length>1&&(O[e]||w.uniqueSort(i),H.test(e)&&i.reverse()),this.pushStack(i)}});var M=/[^\x20\t\r\n\f]+/g;function R(e){var t={};return w.each(e.match(M)||[],function(e,n){t[n]=!0}),t}w.Callbacks=function(e){e="string"==typeof e?R(e):w.extend({},e);var t,n,r,i,o=[],a=[],s=-1,u=function(){for(i=i||e.once,r=t=!0;a.length;s=-1){n=a.shift();while(++s-1)o.splice(n,1),n<=s&&s--}),this},has:function(e){return e?w.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=a=[],o=n="",this},disabled:function(){return!o},lock:function(){return i=a=[],n||t||(o=n=""),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=[e,(n=n||[]).slice?n.slice():n],a.push(n),t||u()),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!r}};return l};function I(e){return e}function W(e){throw e}function $(e,t,n,r){var i;try{e&&g(i=e.promise)?i.call(e).done(t).fail(n):e&&g(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}w.extend({Deferred:function(t){var n=[["notify","progress",w.Callbacks("memory"),w.Callbacks("memory"),2],["resolve","done",w.Callbacks("once memory"),w.Callbacks("once memory"),0,"resolved"],["reject","fail",w.Callbacks("once memory"),w.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},"catch":function(e){return i.then(null,e)},pipe:function(){var e=arguments;return w.Deferred(function(t){w.each(n,function(n,r){var i=g(e[r[4]])&&e[r[4]];o[r[1]](function(){var e=i&&i.apply(this,arguments);e&&g(e.promise)?e.promise().progress(t.notify).done(t.resolve).fail(t.reject):t[r[0]+"With"](this,i?[e]:arguments)})}),e=null}).promise()},then:function(t,r,i){var o=0;function a(t,n,r,i){return function(){var s=this,u=arguments,l=function(){var e,l;if(!(t=o&&(r!==W&&(s=void 0,u=[e]),n.rejectWith(s,u))}};t?c():(w.Deferred.getStackHook&&(c.stackTrace=w.Deferred.getStackHook()),e.setTimeout(c))}}return w.Deferred(function(e){n[0][3].add(a(0,e,g(i)?i:I,e.notifyWith)),n[1][3].add(a(0,e,g(t)?t:I)),n[2][3].add(a(0,e,g(r)?r:W))}).promise()},promise:function(e){return null!=e?w.extend(e,i):i}},o={};return w.each(n,function(e,t){var a=t[2],s=t[5];i[t[1]]=a.add,s&&a.add(function(){r=s},n[3-e][2].disable,n[3-e][3].disable,n[0][2].lock,n[0][3].lock),a.add(t[3].fire),o[t[0]]=function(){return o[t[0]+"With"](this===o?void 0:this,arguments),this},o[t[0]+"With"]=a.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(e){var t=arguments.length,n=t,r=Array(n),i=o.call(arguments),a=w.Deferred(),s=function(e){return function(n){r[e]=this,i[e]=arguments.length>1?o.call(arguments):n,--t||a.resolveWith(r,i)}};if(t<=1&&($(e,a.done(s(n)).resolve,a.reject,!t),"pending"===a.state()||g(i[n]&&i[n].then)))return a.then();while(n--)$(i[n],s(n),a.reject);return a.promise()}});var B=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;w.Deferred.exceptionHook=function(t,n){e.console&&e.console.warn&&t&&B.test(t.name)&&e.console.warn("jQuery.Deferred exception: "+t.message,t.stack,n)},w.readyException=function(t){e.setTimeout(function(){throw t})};var F=w.Deferred();w.fn.ready=function(e){return F.then(e)["catch"](function(e){w.readyException(e)}),this},w.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--w.readyWait:w.isReady)||(w.isReady=!0,!0!==e&&--w.readyWait>0||F.resolveWith(r,[w]))}}),w.ready.then=F.then;function _(){r.removeEventListener("DOMContentLoaded",_),e.removeEventListener("load",_),w.ready()}"complete"===r.readyState||"loading"!==r.readyState&&!r.documentElement.doScroll?e.setTimeout(w.ready):(r.addEventListener("DOMContentLoaded",_),e.addEventListener("load",_));var z=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===x(n)){i=!0;for(s in n)z(e,t,s,n[s],!0,o,a)}else if(void 0!==r&&(i=!0,g(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(w(e),n)})),t))for(;s1,null,!0)},removeData:function(e){return this.each(function(){K.remove(this,e)})}}),w.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=J.get(e,t),n&&(!r||Array.isArray(n)?r=J.access(e,t,w.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=w.queue(e,t),r=n.length,i=n.shift(),o=w._queueHooks(e,t),a=function(){w.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return J.get(e,n)||J.access(e,n,{empty:w.Callbacks("once memory").add(function(){J.remove(e,[t+"queue",n])})})}}),w.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length\x20\t\r\n\f]+)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,""],col:[2,""],tr:[2,""],td:[3,""],_default:[0,"",""]};ge.optgroup=ge.option,ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td;function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&N(e,t)?w.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n-1)i&&i.push(o);else if(l=w.contains(o.ownerDocument,o),a=ye(f.appendChild(o),"script"),l&&ve(a),n){c=0;while(o=a[c++])he.test(o.type||"")&&n.push(o)}return f}!function(){var e=r.createDocumentFragment().appendChild(r.createElement("div")),t=r.createElement("input");t.setAttribute("type","radio"),t.setAttribute("checked","checked"),t.setAttribute("name","t"),e.appendChild(t),h.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,e.innerHTML="",h.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue}();var be=r.documentElement,we=/^key/,Te=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ce=/^([^.]*)(?:\.(.+)|)/;function Ee(){return!0}function ke(){return!1}function Se(){try{return r.activeElement}catch(e){}}function De(e,t,n,r,i,o){var a,s;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(s in t)De(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=ke;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return w().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=w.guid++)),e.each(function(){w.event.add(this,t,i,r,n)})}w.event={global:{},add:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.get(e);if(y){n.handler&&(n=(o=n).handler,i=o.selector),i&&w.find.matchesSelector(be,i),n.guid||(n.guid=w.guid++),(u=y.events)||(u=y.events={}),(a=y.handle)||(a=y.handle=function(t){return"undefined"!=typeof w&&w.event.triggered!==t.type?w.event.dispatch.apply(e,arguments):void 0}),l=(t=(t||"").match(M)||[""]).length;while(l--)d=g=(s=Ce.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=w.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=w.event.special[d]||{},c=w.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&w.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(e,r,h,a)||e.addEventListener&&e.addEventListener(d,a)),f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),w.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.hasData(e)&&J.get(e);if(y&&(u=y.events)){l=(t=(t||"").match(M)||[""]).length;while(l--)if(s=Ce.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){f=w.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,y.handle)||w.removeEvent(e,d,y.handle),delete u[d])}else for(d in u)w.event.remove(e,d+t[l],n,r,!0);w.isEmptyObject(u)&&J.remove(e,"handle events")}},dispatch:function(e){var t=w.event.fix(e),n,r,i,o,a,s,u=new Array(arguments.length),l=(J.get(this,"events")||{})[t.type]||[],c=w.event.special[t.type]||{};for(u[0]=t,n=1;n=1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n-1:w.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u\x20\t\r\n\f]*)[^>]*)\/>/gi,Ae=/