Skip to content

Commit 16ef85c

Browse files
authored
Feature/streamlit webapp (#2)
* add streamlit webapp and update read.me * run pre commit
1 parent 1bb5211 commit 16ef85c

8 files changed

Lines changed: 363 additions & 7 deletions

File tree

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,10 @@ poetry.toml
185185
pyrightconfig.json
186186

187187
# End of https://www.toptal.com/developers/gitignore/api/python,jupyternotebooks
188+
189+
190+
#streamlit
191+
streamlit_webapp/.DS_Store
192+
193+
#sample file
194+
streamlit_webapp/**/.wav

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,19 @@ This project showcases the use of the Triton Inference Server and Tritony Voice
1818
- **Requirements**: pydub, tritonclient, tritony
1919
- **Setup**: Build and run a Docker image for the Triton Inference Server. Testing is facilitated through predefined scripts and pytest. For more information, visit the project's [README](./tritony-sample/).
2020

21+
### 3. STT and STT summary WEBAPP with Python streamlit
22+
23+
Implementation of a web app using only Python's Streamlit and Return Zero's API, without knowledge of frontend and backend development. This web app include functionality to convert audio files to text using Return Zero's API for Speech-to-Text (STT), and then summarizes the converted text.
24+
25+
- **Requirements**: streamlit, requests, pytorch, transformers
26+
- **Setup**: Install requirement librarys, Run streamlit and submit your information. For more information, visit the project's [README](./streamlit-webapp/).
27+
28+
29+
2130
## General Installation
2231

2332
Most project dependencies can be installed via pip:
2433

2534
```bash
2635
pip install -r requirements.txt
36+
```

python-stt-sample/src/sample_stt.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,28 +38,32 @@
3838

3939
SAMPLE_RATE = 8000
4040
ENCODING = pb.DecoderConfig.AudioEncoding.LINEAR16
41-
BYTES_PER_SAMPLE= 2
41+
BYTES_PER_SAMPLE = 2
42+
4243

4344
# 본 예제에서는 스트리밍 입력을 음성파일을 읽어서 시뮬레이션 합니다.
4445
# 실제사용시에는 마이크 입력 등의 실시간 음성 스트림이 들어와야합니다.
45-
class FileStreamer:
46-
def __init__(self,filepath):
46+
class FileStreamer:
47+
def __init__(self, filepath):
4748
self.filepath = filepath
4849
self.file = None
50+
4951
def __enter__(self):
50-
self.file = open(self.filepath,"rb")
52+
self.file = open(self.filepath, "rb")
5153
return self
54+
5255
def __exit__(self, exc_type, exc_val, exc_tb):
5356
self.file.close()
5457
os.remove(self.filepath)
55-
58+
5659
def read(self, size):
5760
if size > 1024 * 1024:
58-
size = 1024*1024
59-
time.sleep(size / (SAMPLE_RATE*BYTES_PER_SAMPLE))
61+
size = 1024 * 1024
62+
time.sleep(size / (SAMPLE_RATE * BYTES_PER_SAMPLE))
6063
content = self.file.read(size)
6164
return content
6265

66+
6367
class RTZROpenAPIClient:
6468
def __init__(self, client_id, client_secret):
6569
super().__init__()
@@ -94,6 +98,7 @@ def req_iterator():
9498
if buff is None or len(buff) == 0:
9599
break
96100
yield pb.DecoderRequest(audio_content=buff)
101+
97102
req_iter = req_iterator()
98103
resp_iter = stub.Decode(req_iter, credentials=cred)
99104
for resp in resp_iter:

streamlit-webapp/README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# STT and STT summary WEBAPP with Python streamlit
2+
## Get API Token
3+
1. Access [Developers](https://developers.rtzr.ai/)
4+
2. Sign up and Sign in
5+
3. Access `My console`
6+
4. `My application`-> `New registration` click
7+
5. Get API token
8+
9+
10+
## Requirements
11+
This repository requires `streamlit`, `requests`, `pytorch`, `transformers` python libraries. So you need to run following command.
12+
13+
```bash
14+
pip install -r requirements.txt
15+
```
16+
17+
18+
## Run!
19+
To start Streamlit webapp!
20+
Run and submit `client_id`, `client_secret` and `audio_file` at sidebar submit field
21+
22+
```bash
23+
streamlit run ./src/main.py
24+
```
25+
26+
27+
## Summary
28+
```bash
29+
git clone https://github.com/vito-ai/python-tutorial.git
30+
cd ./python-tutorial/streamlit-webapp
31+
pip install -r requirements.txt
32+
streamlit run ./src/main.py
33+
```

streamlit-webapp/requirements.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
numpy<2
2+
requests
3+
streamlit
4+
torch>=2.2.2
5+
transformers>=4.44.2

streamlit-webapp/src/main.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import streamlit as st
2+
from utils import display_result, page_setup
3+
4+
RTZR_LOGO_URL = "https://www.rtzr.ai/rtzr_logo.svg"
5+
RTZR_HOMEPAGE_URL = "http://rtzr.ai"
6+
API_TUTORIAL_URL = "https://developers.rtzr.ai/docs/authentications"
7+
AUDIO_FILE_PATH = "./resource"
8+
9+
# streamlit setting
10+
11+
st.set_page_config(layout="wide", page_title="STT and Summary", page_icon=RTZR_LOGO_URL)
12+
13+
if __name__ == "__main__":
14+
# streamlit main
15+
with st.container():
16+
my_upload: st.file_uploader = page_setup(RTZR_LOGO_URL, RTZR_HOMEPAGE_URL, API_TUTORIAL_URL)
17+
display_result(AUDIO_FILE_PATH, my_upload)

streamlit-webapp/src/model.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import json
2+
3+
import requests
4+
5+
6+
class RtzrAPI:
7+
def __init__(
8+
self,
9+
client_id: str,
10+
client_sceret: str,
11+
dev: bool,
12+
file_path: dict,
13+
speaker_num: int,
14+
domain: str,
15+
profanity_filter: bool,
16+
keyword: list,
17+
summary_model,
18+
summary_tokenizer,
19+
) -> None:
20+
"""api 사용에 필요한 인자 초기화"""
21+
self.dev: str = "dev-" if dev else ""
22+
self.client_id: str = client_id
23+
self.client_sceret: str = client_sceret
24+
self.file_path: dict = file_path
25+
self.speaker_num: int = speaker_num
26+
self.config: dict = {"domain": "GENERAL"} if domain == "일반" else {"domain": "CALL"}
27+
28+
if speaker_num != 0:
29+
self.config["use_diarization"] = True
30+
self.config["diarization"] = {"spk_count": speaker_num}
31+
if profanity_filter:
32+
self.config["use_profanity_filter"] = True
33+
if keyword:
34+
self.config["keyword"] = keyword
35+
36+
self.model = summary_model
37+
self.tokenizer = summary_tokenizer
38+
39+
self.raw_data: json = None
40+
self.voice_data: str = None
41+
self.summary_data: str = None
42+
self.access_token: str = self.auth_check(client_id, client_sceret)
43+
self.transcribe_id: str = self.api_post(self.access_token)
44+
45+
def auth_check(self, client_id: str, client_sceret: str) -> str:
46+
"""client id와 client sceret을 인자로 받고 api요청에 필요한 access token을 리턴하는 메소드"""
47+
resp = requests.post(
48+
f"https://{self.dev}openapi.vito.ai/v1/authenticate",
49+
data={"client_id": client_id, "client_secret": client_sceret},
50+
)
51+
52+
resp.raise_for_status()
53+
return resp.json()["access_token"]
54+
55+
def api_post(self, access_token: str) -> None:
56+
"""access_token을 인자로 받고 api요청을 하는 함수. Get에 필요한 transcribe_id 리턴하는 메소드"""
57+
resp = requests.post(
58+
f"https://{self.dev}openapi.vito.ai/v1/transcribe",
59+
headers={"Authorization": f"Bearer {access_token}"},
60+
files=self.file_path,
61+
data={"config": json.dumps(self.config)},
62+
)
63+
64+
resp.raise_for_status()
65+
return resp.json()["id"]
66+
67+
def api_get(
68+
self,
69+
) -> None:
70+
"""텍스트로 변환된 값을 받는 메소드"""
71+
resp = requests.get(
72+
f"https://{self.dev}openapi.vito.ai/v1/transcribe/" + self.transcribe_id,
73+
headers={"Authorization": "bearer " + self.access_token},
74+
)
75+
resp.raise_for_status()
76+
if resp.json()["status"] == "transcribing":
77+
self.raw_data = None
78+
else:
79+
self.raw_data = resp.json()
80+
self.voice_data = self.preprocessing(self.raw_data)
81+
82+
def preprocessing(self, raw_data: dict) -> str:
83+
"""
84+
텍스트를 전처리 하는 메소드
85+
화자가 두 명 이상일 경우 speaker n] text... 형태로 출력
86+
"""
87+
if len(set([x["spk"] for x in raw_data["results"]["utterances"]])) == 1:
88+
return " ".join([data["msg"] for data in raw_data["results"]["utterances"]])
89+
else:
90+
return " \n".join(
91+
[f"화자{text_data['spk']} ] {text_data['msg']}" for text_data in raw_data["results"]["utterances"]]
92+
)
93+
94+
def summary_inference(
95+
self,
96+
) -> None:
97+
"""hugging face 모델을 사용해 텍스트 내용을 요약해주는 함수. 문장이 짧으면 'Text too short를 반환"""
98+
99+
# short data handling
100+
if len(self.voice_data) < 40:
101+
self.summary_data = "Text too short!!"
102+
return None
103+
104+
# Encoding
105+
inputs = self.tokenizer(
106+
self.voice_data,
107+
return_tensors="pt",
108+
padding="max_length",
109+
truncation=True,
110+
max_length=1026,
111+
)
112+
113+
# Generate Summary Text
114+
summary_text_ids = self.model.generate(
115+
input_ids=inputs["input_ids"],
116+
attention_mask=inputs["attention_mask"],
117+
bos_token_id=self.model.config.bos_token_id,
118+
eos_token_id=self.model.config.eos_token_id,
119+
length_penalty=1.0,
120+
max_length=300,
121+
min_length=12,
122+
num_beams=6,
123+
repetition_penalty=1.5,
124+
no_repeat_ngram_size=15,
125+
)
126+
127+
# Decoding
128+
self.summary_data = self.tokenizer.decode(summary_text_ids[0], skip_special_tokens=True)
129+
130+
def get_raw_data(self) -> dict:
131+
"""api를 통해 얻은 원시 데이터 반환"""
132+
return self.raw_data
133+
134+
def get_text_data(self) -> str:
135+
"""텍스트로 변환된 데이터 반환"""
136+
return self.voice_data
137+
138+
def get_summary_data(self) -> str:
139+
"""텍스트가 요약된 데이터 반환"""
140+
return self.summary_data

0 commit comments

Comments
 (0)