openfree commited on
Commit
92dd616
·
verified ·
1 Parent(s): bf71450

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +143 -323
app.py CHANGED
@@ -1,334 +1,156 @@
1
- """
2
- SMARTok Demo Stable Build (2025-06-09)
3
- ────────────────────────────────────────
4
- Tabs
5
- 1) 🎙️ 오디오 번역
6
- 2) 📄 문서·이미지 번역
7
- 3) ⏱️ 실시간 1언어
8
- 4) 🌏 실시간 4언어 (영·중간·태·러)
9
- ────────────────────────────────────────
10
- 필수 apt : packages.txt 참고
11
- 필수 pip : requirements.txt 참고
12
- """
13
-
14
- import gradio as gr, openai, os, io, mimetypes, tempfile
15
  from dotenv import load_dotenv
16
- from PIL import Image
17
- import pdfplumber, ocrmypdf, pytesseract
18
 
19
- # ───── 0. Init ──────────────────────────────────────────────────
20
  load_dotenv()
21
- client = openai.OpenAI(api_key=os.getenv("OPENAI_API_KEY", ""))
22
-
23
- LANG = ["Korean","English","Japanese","Chinese","Thai","Russian",
24
- "Vietnamese","Spanish","French"]
25
- LC = { # tesseract lang codes
26
- "Korean":"kor","English":"eng","Japanese":"jpn","Chinese":"chi_sim",
27
- "Thai":"tha","Russian":"rus","Vietnamese":"vie","Spanish":"spa","French":"fra"
28
- }
29
- VOICE = {l:("nova" if l in ["Korean","Japanese","Chinese"] else "alloy") for l in LANG}
30
- FOUR = ["English","Chinese","Thai","Russian"]
31
- CHUNK = 4 # sec – 실시간 청크 길이
32
-
33
- # ───── 1. helpers ───────────────────────────────────────────────
34
- def _path(v):
35
- return None if v is None else (v["name"] if isinstance(v,dict) else v)
36
-
37
- def _gpt(txt, src, tgt):
38
- rsp = client.chat.completions.create(
39
  model="gpt-3.5-turbo",
40
- messages=[
41
- {"role":"system",
42
- "content":f"Translate the following {src} text to {tgt}. "
43
- "Return only the translated text."},
44
- {"role":"user","content":txt}],
45
- temperature=0.3,max_tokens=4096)
46
  return rsp.choices[0].message.content.strip()
47
 
48
- def _tts(txt, lang):
49
- res = client.audio.speech.create(model="tts-1",voice=VOICE[lang],input=txt[:4096])
50
- f = tempfile.NamedTemporaryFile(delete=False,suffix=".mp3")
51
- f.write(res.content); f.close(); return f.name
52
-
53
- # ───── 2. audio → 번역 + TTS ────────────────────────────────────
54
- def trans_audio(audio, src, tgt):
55
- p=_path(audio)
56
- if not p or not os.path.exists(p): return "⚠️ 음성 파일 필요","",None
57
- with open(p,"rb") as f:
58
- stt = client.audio.transcriptions.create(model="whisper-1",file=f,
59
- language=LC.get(src,"eng"))
60
- orig = stt.text.strip()
61
- if not orig: return "⚠️ 음성 인식 실패","",None
62
- trans = _gpt(orig,src,tgt)
63
- return orig, trans, _tts(trans,tgt)
64
-
65
- # ───── 3. PDF / Image OCR → 번역 ────────────────────────────────
66
- def trans_doc(file, src, tgt):
67
- p=_path(file)
68
- if not p or not os.path.exists(p): return "⚠️ 파일 업로드",""
69
- ext = os.path.splitext(p)[1].lower()
70
- mime= mimetypes.guess_type(p)[0] or ""
71
- txt = ""
72
- try:
73
- # PDF
74
- if ext==".pdf" or "pdf" in mime:
75
- with pdfplumber.open(p) as pdf:
76
- txt="\n".join(pg.extract_text() or "" for pg in pdf.pages[:5])
77
- # 이미지
78
- else:
79
- tmp_pdf=tempfile.NamedTemporaryFile(delete=False,suffix=".pdf").name
80
- Image.open(p).save(tmp_pdf,"PDF")
81
- ocr_pdf=tempfile.NamedTemporaryFile(delete=False,suffix=".pdf").name
82
- try:
83
- ocrmypdf.ocr(tmp_pdf, ocr_pdf,
84
- lang=f"{LC.get(src,'eng')}+eng",
85
- deskew=True,optimize=0,progress_bar=False)
86
- with pdfplumber.open(ocr_pdf) as pdf:
87
- txt="\n".join(pg.extract_text() or "" for pg in pdf.pages)
88
- except Exception:
89
- txt = pytesseract.image_to_string(
90
- Image.open(p), lang=LC.get(src,"eng"))
91
- except Exception as e:
92
- return f"❌ 추출 오류: {e}",""
93
- txt = txt.strip()
94
- if not txt: return "⚠️ 텍스트 추출 실패",""
95
- return txt, _gpt(txt,src,tgt)
96
-
97
- # ───── 4. 실시간 1언어 스트림 ──────────────────────────────────
98
- def stream_one(path, src, tgt, st):
99
- st = st or {"o":"","t":""}
100
- if not path or not os.path.exists(path): return st["o"],st["t"],st
101
- with open(path,"rb") as f:
102
- stt=client.audio.transcriptions.create(model="whisper-1",file=f,
103
- language=LC.get(src,"eng"))
104
- full=stt.text.strip(); new=full[len(st["o"]):]
105
- if new:
106
- st["o"]=full
107
- st["t"]+=" "+_gpt(new,src,tgt)
108
- return st["o"],st["t"].strip(),st
109
-
110
- # ───── 5. 실시간 4언어 스트림 ──────────────────────────────────
111
- def stream_four(path, src, st):
112
- st = st or {k:"" for k in ["o"]+FOUR}
113
- if not path or not os.path.exists(path):
114
- return st["o"],st["English"],st["Chinese"],st["Thai"],st["Russian"],st
115
- with open(path,"rb") as f:
116
- stt=client.audio.transcriptions.create(model="whisper-1",file=f,
117
- language=LC.get(src,"eng"))
118
- full=stt.text.strip(); new=full[len(st["o"]):]
119
- if new:
120
- st["o"]=full
121
- for l in FOUR:
122
- st[l]+=" "+_gpt(new,src,l)
123
- return (st["o"].strip(),st["English"].strip(),st["Chinese"].strip(),
124
- st["Thai"].strip(),st["Russian"].strip(),st)
125
-
126
- # ───── 6. UI ───────────────────────────────────────────────────
127
- with gr.Blocks(title="SMARTok Demo",theme=gr.themes.Soft()) as app:
 
 
 
 
 
 
 
 
 
128
  with gr.Tabs():
129
- # 오디오 번역
130
- with gr.TabItem("🎙️ 오디오 번역"):
131
- s1=gr.Dropdown(LANG,value="Korean",label="입력")
132
- t1=gr.Dropdown(LANG,value="English",label="출력")
133
- a1=gr.Audio(sources=["microphone","upload"],type="filepath")
134
  btn1=gr.Button("번역")
135
- o1=gr.Textbox(label="원문",lines=5); tr1=gr.Textbox(label="번역",lines=5)
136
- aud1=gr.Audio(label="TTS",type="filepath",autoplay=True)
137
- btn1.click(trans_audio,[a1,s1,t1],[o1,tr1,aud1])
138
-
139
- # ② 문서·이미지 번역
140
- with gr.TabItem("📄 문서·이미지 번역"):
141
- s2=gr.Dropdown(LANG,value="Korean",label="입력")
142
- t2=gr.Dropdown(LANG,value="English",label="출력")
143
- f2=gr.File(file_types=[".pdf",".png",".jpg",".jpeg",".bmp",".tiff",".gif"])
 
144
  btn2=gr.Button("번역")
145
- o2=gr.Textbox(label="추출 원문",lines=15); tr2=gr.Textbox(label="번역",lines=15)
146
- btn2.click(trans_doc,[f2,s2,t2],[o2,tr2])
147
-
148
- # ③ 실시간 1언어
149
- with gr.TabItem("⏱️ 실시간 1언어"):
150
- s3=gr.Dropdown(LANG,value="Korean",label="입력")
151
- t3=gr.Dropdown(LANG,value="English",label="출력")
152
- mic3=gr.Audio(sources=["microphone"],streaming=True)
153
- o3=gr.Textbox(label="원문(실시간)",lines=8); tr3=gr.Textbox(label="번역(실시간)",lines=8)
154
- st3=gr.State()
155
- mic3.stream(stream_one,inputs=[s3,t3,st3],outputs=[o3,tr3,st3])
156
-
157
- # ④ 실시간 4언어
158
- with gr.TabItem("🌏 실시간 4언어"):
159
- s4=gr.Dropdown(LANG,value="Korean",label="입력 언어")
160
- mic4=gr.Audio(sources=["microphone"],streaming=True)
161
- o4=gr.Textbox(label="원문",lines=8)
162
- e4=gr.Textbox(label="English",lines=8)
163
- c4=gr.Textbox(label="Chinese(简体)",lines=8)
164
- th4=gr.Textbox(label="Thai",lines=8)
165
- r4=gr.Textbox(label="Russian",lines=8)
166
- st4=gr.State()
167
- mic4.stream(stream_four,inputs=[s4,st4],
168
- outputs=[o4,e4,c4,th4,r4,st4])
169
-
170
- # ───── 7. Launch ───────────────────────────────────────────────
171
- if __name__=="__main__":
172
- app.launch(server_name="0.0.0.0",server_port=7860,share=False,debug=True)
173
- """
174
- SMARTok Demo – Stable Build (2025-06-09)
175
- ────────────────────────────────────────
176
- Tabs
177
- 1) 🎙️ 오디오 번역
178
- 2) 📄 문서·이미지 번역
179
- 3) ⏱️ 실시간 1언어
180
- 4) 🌏 실시간 4언어 (영·중간·태·러)
181
- ────────────────────────────────────────
182
- 필수 apt : packages.txt 참고
183
- 필수 pip : requirements.txt 참고
184
- """
185
-
186
- import gradio as gr, openai, os, io, mimetypes, tempfile
187
- from dotenv import load_dotenv
188
- from PIL import Image
189
- import pdfplumber, ocrmypdf, pytesseract
190
-
191
- # ───── 0. Init ──────────────────────────────────────────────────
192
- load_dotenv()
193
- client = openai.OpenAI(api_key=os.getenv("OPENAI_API_KEY", ""))
194
-
195
- LANG = ["Korean","English","Japanese","Chinese","Thai","Russian",
196
- "Vietnamese","Spanish","French"]
197
- LC = { # tesseract lang codes
198
- "Korean":"kor","English":"eng","Japanese":"jpn","Chinese":"chi_sim",
199
- "Thai":"tha","Russian":"rus","Vietnamese":"vie","Spanish":"spa","French":"fra"
200
- }
201
- VOICE = {l:("nova" if l in ["Korean","Japanese","Chinese"] else "alloy") for l in LANG}
202
- FOUR = ["English","Chinese","Thai","Russian"]
203
- CHUNK = 4 # sec – 실시간 청크 길이
204
-
205
- # ───── 1. helpers ───────────────────────────────────────────────
206
- def _path(v):
207
- return None if v is None else (v["name"] if isinstance(v,dict) else v)
208
-
209
- def _gpt(txt, src, tgt):
210
- rsp = client.chat.completions.create(
211
- model="gpt-3.5-turbo",
212
- messages=[
213
- {"role":"system",
214
- "content":f"Translate the following {src} text to {tgt}. "
215
- "Return only the translated text."},
216
- {"role":"user","content":txt}],
217
- temperature=0.3,max_tokens=4096)
218
- return rsp.choices[0].message.content.strip()
219
-
220
- def _tts(txt, lang):
221
- res = client.audio.speech.create(model="tts-1",voice=VOICE[lang],input=txt[:4096])
222
- f = tempfile.NamedTemporaryFile(delete=False,suffix=".mp3")
223
- f.write(res.content); f.close(); return f.name
224
-
225
- # ───── 2. audio → 번역 + TTS ────────────────────────────────────
226
- def trans_audio(audio, src, tgt):
227
- p=_path(audio)
228
- if not p or not os.path.exists(p): return "⚠️ 음성 파일 필요","",None
229
- with open(p,"rb") as f:
230
- stt = client.audio.transcriptions.create(model="whisper-1",file=f,
231
- language=LC.get(src,"eng"))
232
- orig = stt.text.strip()
233
- if not orig: return "⚠️ 음성 인식 실패","",None
234
- trans = _gpt(orig,src,tgt)
235
- return orig, trans, _tts(trans,tgt)
236
-
237
- # ───── 3. PDF / Image OCR → 번역 ────────────────────────────────
238
- def trans_doc(file, src, tgt):
239
- p=_path(file)
240
- if not p or not os.path.exists(p): return "⚠️ 파일 업로드",""
241
- ext = os.path.splitext(p)[1].lower()
242
- mime= mimetypes.guess_type(p)[0] or ""
243
- txt = ""
244
- try:
245
- # PDF
246
- if ext==".pdf" or "pdf" in mime:
247
- with pdfplumber.open(p) as pdf:
248
- txt="\n".join(pg.extract_text() or "" for pg in pdf.pages[:5])
249
- # 이미지
250
- else:
251
- tmp_pdf=tempfile.NamedTemporaryFile(delete=False,suffix=".pdf").name
252
- Image.open(p).save(tmp_pdf,"PDF")
253
- ocr_pdf=tempfile.NamedTemporaryFile(delete=False,suffix=".pdf").name
254
- try:
255
- ocrmypdf.ocr(tmp_pdf, ocr_pdf,
256
- lang=f"{LC.get(src,'eng')}+eng",
257
- deskew=True,optimize=0,progress_bar=False)
258
- with pdfplumber.open(ocr_pdf) as pdf:
259
- txt="\n".join(pg.extract_text() or "" for pg in pdf.pages)
260
- except Exception:
261
- txt = pytesseract.image_to_string(
262
- Image.open(p), lang=LC.get(src,"eng"))
263
- except Exception as e:
264
- return f"❌ 추출 오류: {e}",""
265
- txt = txt.strip()
266
- if not txt: return "⚠️ 텍스트 추출 실패",""
267
- return txt, _gpt(txt,src,tgt)
268
-
269
- # ───── 4. 실시간 1언어 스트림 ──────────────────────────────────
270
- def stream_one(path, src, tgt, st):
271
- st = st or {"o":"","t":""}
272
- if not path or not os.path.exists(path): return st["o"],st["t"],st
273
- with open(path,"rb") as f:
274
- stt=client.audio.transcriptions.create(model="whisper-1",file=f,
275
- language=LC.get(src,"eng"))
276
- full=stt.text.strip(); new=full[len(st["o"]):]
277
- if new:
278
- st["o"]=full
279
- st["t"]+=" "+_gpt(new,src,tgt)
280
- return st["o"],st["t"].strip(),st
281
-
282
- # ───── 5. 실시간 4언어 스트림 ──────────────────────────────────
283
- def stream_four(path, src, st):
284
- st = st or {k:"" for k in ["o"]+FOUR}
285
- if not path or not os.path.exists(path):
286
- return st["o"],st["English"],st["Chinese"],st["Thai"],st["Russian"],st
287
- with open(path,"rb") as f:
288
- stt=client.audio.transcriptions.create(model="whisper-1",file=f,
289
- language=LC.get(src,"eng"))
290
- full=stt.text.strip(); new=full[len(st["o"]):]
291
- if new:
292
- st["o"]=full
293
- for l in FOUR:
294
- st[l]+=" "+_gpt(new,src,l)
295
- return (st["o"].strip(),st["English"].strip(),st["Chinese"].strip(),
296
- st["Thai"].strip(),st["Russian"].strip(),st)
297
-
298
- # ───── 6. UI ───────────────────────────────────────────────────
299
- with gr.Blocks(title="SMARTok Demo",theme=gr.themes.Soft()) as app:
300
- with gr.Tabs():
301
- # ① 오디오 번역
302
- with gr.TabItem("🎙️ 오디오 번역"):
303
- s1=gr.Dropdown(LANG,value="Korean",label="입력")
304
- t1=gr.Dropdown(LANG,value="English",label="출력")
305
- a1=gr.Audio(sources=["microphone","upload"],type="filepath")
306
- btn1=gr.Button("번역")
307
- o1=gr.Textbox(label="원문",lines=5); tr1=gr.Textbox(label="번역",lines=5)
308
- aud1=gr.Audio(label="TTS",type="filepath",autoplay=True)
309
- btn1.click(trans_audio,[a1,s1,t1],[o1,tr1,aud1])
310
-
311
- # ② 문서·이미지 번역
312
- with gr.TabItem("📄 문서·이미지 번역"):
313
- s2=gr.Dropdown(LANG,value="Korean",label="입력")
314
- t2=gr.Dropdown(LANG,value="English",label="출력")
315
- f2=gr.File(file_types=[".pdf",".png",".jpg",".jpeg",".bmp",".tiff",".gif"])
316
- btn2=gr.Button("번역")
317
- o2=gr.Textbox(label="추출 원문",lines=15); tr2=gr.Textbox(label="번역",lines=15)
318
- btn2.click(trans_doc,[f2,s2,t2],[o2,tr2])
319
-
320
- # ③ 실시간 1언어
321
- with gr.TabItem("⏱️ 실시간 1언어"):
322
- s3=gr.Dropdown(LANG,value="Korean",label="입력")
323
- t3=gr.Dropdown(LANG,value="English",label="출력")
324
  mic3=gr.Audio(sources=["microphone"],streaming=True)
325
- o3=gr.Textbox(label="원문(실시간)",lines=8); tr3=gr.Textbox(label="번역(실시간)",lines=8)
 
326
  st3=gr.State()
327
- mic3.stream(stream_one,inputs=[s3,t3,st3],outputs=[o3,tr3,st3])
 
328
 
329
- # 실시간 4언어
330
- with gr.TabItem("🌏 실시간 4언어"):
331
- s4=gr.Dropdown(LANG,value="Korean",label="입력 언어")
332
  mic4=gr.Audio(sources=["microphone"],streaming=True)
333
  o4=gr.Textbox(label="원문",lines=8)
334
  e4=gr.Textbox(label="English",lines=8)
@@ -336,9 +158,7 @@ with gr.Blocks(title="SMARTok Demo",theme=gr.themes.Soft()) as app:
336
  th4=gr.Textbox(label="Thai",lines=8)
337
  r4=gr.Textbox(label="Russian",lines=8)
338
  st4=gr.State()
339
- mic4.stream(stream_four,inputs=[s4,st4],
340
  outputs=[o4,e4,c4,th4,r4,st4])
341
 
342
- # ───── 7. Launch ───────────────────────────────────────────────
343
- if __name__=="__main__":
344
- app.launch(server_name="0.0.0.0",server_port=7860,share=False,debug=True)
 
1
+ import os, asyncio, json, tempfile, websockets, pdfplumber
2
+ import gradio as gr
3
+ import openai
 
 
 
 
 
 
 
 
 
 
 
4
  from dotenv import load_dotenv
 
 
5
 
6
+ # ─── 0. 초기화 ───────────────────────────────────────────────
7
  load_dotenv()
8
+ openai.api_key = os.getenv("OPENAI_API_KEY")
9
+ if not openai.api_key:
10
+ raise RuntimeError("OPENAI_API_KEY 가 .env 에 없습니다!")
11
+
12
+ LANG = ["Korean","English","Japanese","Chinese",
13
+ "Thai","Russian","Vietnamese","Spanish","French"]
14
+ VOICE = {l: ("nova" if l in ["Korean","Japanese","Chinese"] else "alloy")
15
+ for l in LANG}
16
+ FOUR = ["English","Chinese","Thai","Russian"]
17
+ WS_URL = "wss://api.openai.com/v1/audio/transcriptions/stream"
18
+
19
+ # ─── 1. 공통 GPT 번역 / TTS ─────────────────────────────────
20
+ async def gpt_translate(text, src, tgt):
21
+ rsp = await openai.AsyncClient().chat.completions.create(
 
 
 
 
22
  model="gpt-3.5-turbo",
23
+ messages=[{"role":"system",
24
+ "content":f"Translate {src} → {tgt}. Return only the text."},
25
+ {"role":"user","content":text}],
26
+ temperature=0.3,max_tokens=2048)
 
 
27
  return rsp.choices[0].message.content.strip()
28
 
29
+ async def gpt_tts(text, lang):
30
+ rsp = await openai.AsyncClient().audio.speech.create(
31
+ model="tts-1", voice=VOICE[lang], input=text[:4096])
32
+ tmp = tempfile.NamedTemporaryFile(delete=False,suffix=".mp3")
33
+ tmp.write(rsp.content); tmp.close(); return tmp.name
34
+
35
+ # ─── 2. PDF 번역 ────────────────────────────────────────────
36
+ def translate_pdf(file, src, tgt):
37
+ if not file: return "⚠️ PDF 업로드 필요", ""
38
+ with pdfplumber.open(file.name) as pdf:
39
+ text = "\n".join(p.extract_text() or "" for p in pdf.pages[:5]).strip()
40
+ if not text:
41
+ return "⚠️ 텍스트 추출 실패", ""
42
+ return text, asyncio.run(gpt_translate(text, src, tgt))
43
+
44
+ # ─── 3. WebSocket STT 헬퍼 ──────────────────────────────────
45
+ async def ws_stt_generator(audio_queue: asyncio.Queue):
46
+ """
47
+ 백그라운드 태스크:
48
+ - audio_queue 로부터 chunk(bytes) 수신
49
+ - WS 전송, 서버 event 수신 yield (partial text, final?)
50
+ """
51
+ async with websockets.connect(
52
+ WS_URL,
53
+ extra_headers={"Authorization": f"Bearer {openai.api_key}"},
54
+ max_size=None
55
+ ) as ws:
56
+ async def sender():
57
+ while True:
58
+ chunk = await audio_queue.get()
59
+ if chunk is None: # 종료 플래그
60
+ await ws.send(json.dumps({"terminate": True}))
61
+ break
62
+ await ws.send(chunk)
63
+ asyncio.create_task(sender())
64
+ async for msg in ws:
65
+ data = json.loads(msg)
66
+ yield data["text"], data.get("final", False)
67
+
68
+ # ─── 4. Gradio 스트림 핸들러 ─────────────────────────────────
69
+ async def realtime_single(mic, src, tgt, state):
70
+ """
71
+ mic: bytes chunk (Gradio 자동)
72
+ state: {"queue": Queue, "task": Task, "orig": str, "trans": str}
73
+ """
74
+ if state is None:
75
+ state = {"queue": asyncio.Queue(), "task": None, "orig":"", "trans":""}
76
+
77
+ if mic is None: # 스트림 종료
78
+ await state["queue"].put(None)
79
+ return state["orig"], state["trans"], state
80
+
81
+ # 호출이면 WS 태스크 시작
82
+ if state["task"] is None:
83
+ async def run_ws():
84
+ async for text, final in ws_stt_generator(state["queue"]):
85
+ state["orig"] += (" " if state["orig"] else "") + text
86
+ add = await gpt_translate(text, src, tgt)
87
+ state["trans"] += (" " if state["trans"] else "") + add
88
+ state["task"] = asyncio.create_task(run_ws())
89
+
90
+ # 마이크 chunk enqueue
91
+ await state["queue"].put(mic)
92
+ return state["orig"], state["trans"], state
93
+
94
+ async def realtime_four(mic, src, state):
95
+ if state is None:
96
+ state = {"queue": asyncio.Queue(), "task": None,
97
+ "orig":"", "English":"", "Chinese":"", "Thai":"", "Russian":""}
98
+
99
+ if mic is None:
100
+ await state["queue"].put(None)
101
+ return tuple(state[k] for k in
102
+ ["orig","English","Chinese","Thai","Russian"]) + (state,)
103
+
104
+ if state["task"] is None:
105
+ async def run_ws():
106
+ async for text, _ in ws_stt_generator(state["queue"]):
107
+ state["orig"] += (" "+text)
108
+ for lang in FOUR:
109
+ state[lang] += (" "+ await gpt_translate(text, src, lang))
110
+ state["task"] = asyncio.create_task(run_ws())
111
+
112
+ await state["queue"].put(mic)
113
+ return tuple(state[k] for k in
114
+ ["orig","English","Chinese","Thai","Russian"]) + (state,)
115
+
116
+ # ─── 5. UI ──────────────────────────────────────────────────
117
+ with gr.Blocks(title="SMARTok Demo") as demo:
118
  with gr.Tabs():
119
+ # 1 – 오디오 번역
120
+ with gr.TabItem("🎙️ 오디오"):
121
+ src1=gr.Dropdown(LANG,value="Korean",label="입력")
122
+ tgt1=gr.Dropdown(LANG,value="English",label="출력")
123
+ aud1=gr.Audio(sources=["microphone","upload"],type="filepath")
124
  btn1=gr.Button("번역")
125
+ o1=gr.Textbox(label="원문"); t1=gr.Textbox(label="번역")
126
+ a1=gr.Audio(label="TTS",type="filepath",autoplay=True)
127
+ btn1.click(lambda a,s,t: translate_pdf.__wrapped__ if False else translate_pdf,
128
+ [aud1,src1,tgt1],[o1,t1,a1]) # dummy, 유지용
129
+
130
+ # 2 – PDF 번역
131
+ with gr.TabItem("📄 PDF"):
132
+ src2=gr.Dropdown(LANG,value="Korean",label="입력")
133
+ tgt2=gr.Dropdown(LANG,value="English",label="출력")
134
+ pdf=gr.File(file_types=[".pdf"])
135
  btn2=gr.Button("번역")
136
+ o2=gr.Textbox(label="추출 원문",lines=15)
137
+ t2=gr.Textbox(label="번역 결과",lines=15)
138
+ btn2.click(translate_pdf:=translate_pdf,[pdf,src2,tgt2],[o2,t2])
139
+
140
+ # 3 – 실시간 1언어
141
+ with gr.TabItem("⏱️ 실시간 1"):
142
+ src3=gr.Dropdown(LANG,value="Korean",label="입력")
143
+ tgt3=gr.Dropdown(LANG,value="English",label="출력")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  mic3=gr.Audio(sources=["microphone"],streaming=True)
145
+ o3=gr.Textbox(label="원문(실시간)",lines=8)
146
+ t3=gr.Textbox(label="번역(실시간)",lines=8)
147
  st3=gr.State()
148
+ mic3.stream(realtime_single,inputs=[src3,tgt3,st3],
149
+ outputs=[o3,t3,st3])
150
 
151
+ # 4 – 실시간 4언어
152
+ with gr.TabItem("🌏 실시간 4"):
153
+ src4=gr.Dropdown(LANG,value="Korean",label="입력")
154
  mic4=gr.Audio(sources=["microphone"],streaming=True)
155
  o4=gr.Textbox(label="원문",lines=8)
156
  e4=gr.Textbox(label="English",lines=8)
 
158
  th4=gr.Textbox(label="Thai",lines=8)
159
  r4=gr.Textbox(label="Russian",lines=8)
160
  st4=gr.State()
161
+ mic4.stream(realtime_four,inputs=[src4,st4],
162
  outputs=[o4,e4,c4,th4,r4,st4])
163
 
164
+ demo.launch(server_name="0.0.0.0",server_port=7860,debug=True)