Skip to content
This repository was archived by the owner on Feb 11, 2026. It is now read-only.

Commit 802a09b

Browse files
committed
Update design for recognition page
1 parent 7a865b0 commit 802a09b

File tree

3 files changed

+179
-43
lines changed

3 files changed

+179
-43
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
![Sample](./sample.gif)
44

5-
Closed Captions Overlay는 자막을 방송 송출 프로그램(OBS, XSplit 등)에 표시하기 위한 애드온입니다.
5+
Closed Captions Overlay는 자막을 방송 송출 프로그램(OBS, XSplit 등)에 표시하기 위한 서비스입니다.
66

7-
애드온을 사용하면 마이크를 통해 말한 내용을 방송 화면에 실시간으로 자막처럼 표시할 수 있습니다.
7+
서비스을 사용하면 마이크를 통해 말한 내용을 방송 화면에 실시간으로 자막처럼 표시할 수 있습니다.
88

99
이 프로젝트는 [Closed Captions for Streams](https://www.twitch.tv/ext/xxwoffr2lnpxrgpq228mawvdgxetip)에서 영감을 받아 제작되었습니다.
1010

frontend/overlay.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<!DOCTYPE html>
22

33
<head>
4+
<title>Closed Captions Overlay - Overlay</title>
45
<link rel="preload" as="style" href="https://fonts.googleapis.com/css?family=Jua:400">
56
<style>
67
@import url('https://fonts.googleapis.com/css?family=Jua:400');

frontend/recognition.html

Lines changed: 176 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,133 @@
11
<!DOCTYPE html>
22

33
<head>
4+
<title>Closed Captions Overlay - Recognition</title>
5+
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js"></script>
6+
<link rel="stylesheet" href="https://unpkg.com/spectre.css/dist/spectre.min.css">
7+
<link rel="stylesheet" href="https://unpkg.com/spectre.css/dist/spectre-exp.min.css">
8+
<link rel="stylesheet" href="https://unpkg.com/spectre.css/dist/spectre-icons.min.css">
49
<style>
10+
html {
11+
min-height: 100vh;
12+
}
13+
14+
body {
15+
background: #454d5d;
16+
}
17+
18+
.container {
19+
max-width: 980px;
20+
margin-top: 24px;
21+
}
22+
23+
.toast {
24+
margin-top: 8px;
25+
}
26+
27+
.main-card {
28+
margin-top: 8px;
29+
}
30+
31+
.link-card {
32+
margin-top: 8px;
33+
}
34+
35+
.result-card {
36+
margin-top: 8px;
37+
}
38+
39+
.mic-buttons .btn {
40+
margin-right: 8px;
41+
}
42+
43+
#micToggle.badge-primary:after {
44+
background: #5755d9;
45+
}
46+
47+
#micToggle.badge-success:after {
48+
background: #32b643;
49+
}
50+
51+
#micToggle.badge-warning:after {
52+
background: #ffb700;
53+
}
54+
55+
#micToggle.badge-error:after {
56+
background: #e85600;
57+
}
58+
59+
#micToggle.badge-red:after {
60+
background: #cf1300;
61+
}
62+
63+
.recognition-result {
64+
height: 100px;
65+
background: #f7f8f9;
66+
color: inherit;
67+
display: block;
68+
line-height: 1.5;
69+
overflow-x: auto;
70+
padding: 1rem;
71+
width: 100%;
72+
border-radius: 0.1rem;
73+
}
574
</style>
6-
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js"></script>
775
</head>
876

977
<body>
10-
<div class="message">
11-
{% with messages = get_flashed_messages(with_categories=true) %}
12-
{% if messages %}
13-
<ul class="flash">
14-
{% for category, message in messages %}
15-
<li class="{{ category }}">{{ message }}</li>
16-
{% endfor %}
17-
</ul>
18-
{% endif %}
19-
{% endwith %}
20-
</div>
21-
<div class="root-container">
22-
<div class="links">
23-
<div>오버레이 주소: <a target="_blank"
24-
href="{{ url_for('overlay', _external=True) }}?channel={{ current_user.twitch_id }}">{{ url_for('overlay', _external=True) }}?channel={{ current_user.twitch_id }}</a>
78+
<div class="container">
79+
<div class="columns">
80+
{% with messages = get_flashed_messages(with_categories=true) %}
81+
{% if messages %}
82+
<div class="column col-12">
83+
{% for category, message in messages %}
84+
<div class="toast text-center">
85+
{{ message }}
86+
</div>
87+
{% endfor %}
88+
</div>
89+
{% endif %}
90+
{% endwith %}
91+
<div class="column col-12">
92+
<div class="card link-card">
93+
<div class="card-body">
94+
<div class="links">
95+
<div>오버레이 주소: <a target="_blank"
96+
href="{{ url_for('overlay', _external=True) }}?channel={{ current_user.twitch_id }}">{{ url_for('overlay', _external=True) }}?channel={{ current_user.twitch_id }}</a>
97+
</div>
98+
</div>
99+
</div>
100+
</div>
101+
</div>
102+
<div class="column col-12">
103+
<div class="card main-card">
104+
<div class="card-body">
105+
<div class="mic-buttons">
106+
<button id="micToggle" class="btn btn-success" data-badge="&nbsp;">인식 시작</button>
107+
<a class="btn" href="{{ url_for('logout') }}">로그인 정보 갱신</a>
108+
</div>
109+
</div>
110+
</div>
111+
</div>
112+
<div class="column col-12">
113+
<div class="card result-card">
114+
<div class="card-header">
115+
<div class="card-title">최근인식결과</div>
116+
</div>
117+
<div class="card-body">
118+
<div class="recognition-result">
119+
<span id="finalStr"></span>
120+
<span id="interim"></span>
121+
</div>
122+
</div>
123+
</div>
25124
</div>
26-
<div><a href="{{ url_for('logout') }}">로그인 정보 갱신</a></div>
27-
</div>
28-
<div class="mic-buttons">
29-
<button id="micOn">인식 시작</button>
30-
<button id="micOff">인식 종료</button>
31-
</div>
32-
<div class="recognition-result">
33-
<span>최근인식결과: </span>
34-
<span id="finalStr"></span>
35-
<span id="interim"></span>
36125
</div>
37126
</div>
38127
<script>
39128
const LANG = 'ko-KR';
40129

41-
const MIC_ON_DOM = document.getElementById('micOn');
42-
const MIC_OFF_DOM = document.getElementById('micOff');
130+
const MIC_TOGGLE_DOM = document.getElementById('micToggle');
43131

44132
const FINAL_STR_DOM = document.getElementById('finalStr');
45133
const INTERIM_DOM = document.getElementById('interim');
@@ -60,10 +148,20 @@
60148
recognition.interimResults = true;
61149
recognition.maxAlternatives = 1;
62150

63-
recognition.onend = function () {
64-
if (is_mic_on) {
65-
recognition.start();
66-
}
151+
recognition.onstart = function (event) {
152+
setMicBadge('badge-error');
153+
}
154+
155+
recognition.onaudiostart = function (event) {
156+
setMicBadge('badge-warning');
157+
}
158+
159+
recognition.onsoundstart = function (event) {
160+
setMicBadge('badge-primary');
161+
}
162+
163+
recognition.onspeechstart = function (event) {
164+
setMicBadge('badge-success');
67165
}
68166

69167
recognition.onresult = function (event) {
@@ -72,7 +170,7 @@
72170

73171
for (var i = event.resultIndex; i < event.results.length; i++) {
74172
if (event.results[i].isFinal) {
75-
final_str += ' ' + event.results[i][0].transcript;
173+
final_str += event.results[i][0].transcript;
76174
} else {
77175
interim += event.results[i][0].transcript;
78176
}
@@ -81,21 +179,58 @@
81179
updateCaption(final_str, interim)
82180
};
83181

84-
recognition.onerror = function (event) {
85-
console.log('Error occurred in recognition: ' + event.error);
182+
recognition.onspeachend = function (event) {
183+
setMicBadge('badge-primary');
86184
}
87185

88-
MIC_ON_DOM.addEventListener('click', function () {
89-
is_mic_on = true;
90-
recognition.start();
186+
recognition.onsoundend = function (event) {
187+
setMicBadge('badge-warning');
188+
}
189+
190+
recognition.onaudioend = function (event) {
191+
setMicBadge('badge-warning');
192+
}
193+
194+
recognition.onend = function (event) {
195+
if (is_mic_on) {
196+
setMicBadge('badge-error');
197+
recognition.start();
198+
} else {
199+
setMicBadge('badge-red');
200+
}
201+
}
202+
203+
MIC_TOGGLE_DOM.addEventListener('click', function () {
204+
toggleMic();
91205
});
206+
}
92207

93-
MIC_OFF_DOM.addEventListener('click', function () {
208+
function toggleMic() {
209+
if (is_mic_on) {
94210
is_mic_on = false;
95-
recognition.stop();
96-
});
211+
MIC_TOGGLE_DOM.textContent = '인식 시작';
212+
MIC_TOGGLE_DOM.classList.remove('btn-error');
213+
MIC_TOGGLE_DOM.classList.add('btn-success');
214+
MIC_TOGGLE_DOM.classList.remove('badge');
215+
recognition.abort();
216+
} else {
217+
is_mic_on = true;
218+
MIC_TOGGLE_DOM.textContent = '인식 종료';
219+
MIC_TOGGLE_DOM.classList.remove('btn-success');
220+
MIC_TOGGLE_DOM.classList.add('btn-error');
221+
MIC_TOGGLE_DOM.classList.add('badge');
222+
recognition.start();
223+
}
97224
}
98225

226+
function setMicBadge(badge_type) {
227+
MIC_TOGGLE_DOM.classList.remove('badge-primary');
228+
MIC_TOGGLE_DOM.classList.remove('badge-success');
229+
MIC_TOGGLE_DOM.classList.remove('badge-warning');
230+
MIC_TOGGLE_DOM.classList.remove('badge-error');
231+
MIC_TOGGLE_DOM.classList.remove('badge-red');
232+
MIC_TOGGLE_DOM.classList.add(badge_type);
233+
}
99234

100235
function listenSocket() {
101236
socket = io("https://cc-overlay.update.sh/recognition", {

0 commit comments

Comments
 (0)