# Text to Speech

## OpenAI: Text to Speech

```python
import os
from dotenv import load_dotenv

# 토큰 정보로드
api_key = os.getenv("OPENAI_API_KEY")
load_dotenv()
```

```
True
```

### 1. Text To Speech(TTS)

* TTS는 컴퓨터 프로그램이나 기기가 텍스트를 인간의 음성처럼 들리는 오디오로 변환하는 과정입니다.
* 이 기술은 음성 합성을 통해 텍스트 데이터를 자연스러운 음성으로 바꿉니다.
* 사용 예시: 오디오북, 음성 안내 시스템, 음성 기반 가상 어시스턴트 등.

**\[참고]**

* 공식문서: https://platform.openai.com/docs/guides/text-to-speech

**주요 파라미터**

* `model`: 사용 가능한 TTS 모델 중 하나를 지정합니다. `tts-1` 또는 `tts-1-hd`.
  * 최신 지원모델 확인: https://platform.openai.com/docs/models/tts
* `input`: 오디오를 생성할 텍스트입니다. 최대 길이는 4096자입니다.
* `voice`: 오디오를 생성할 때 사용할 음성입니다. 지원되는 음성은 `alloy`, `echo`, `fable`, `onyx`, `nova`, and `shimmer` 입니다. 음성의 미리듣기는 [여기](https://platform.openai.com/docs/guides/text-to-speech/voice-options) 에서 확인할 수 있습니다.
* `response_format`: 오디오를 입력할 형식입니다. 지원되는 형식은 `mp3`, `opus`, `aac` 및 `flac` 입니다.
* `speed`: 생성된 오디오의 속도입니다. `0.25` 에서 `4.0` 사이의 값을 선택합니다. 기본값은 `1.0` 입니다.

#### Client 생성

* `client` 는 OpenAI 모듈로 생성된 인스턴스 입니다.

\[주의] 아래의 코드에서 오류가 난다면 API 키의 오류일 가능성이 높습니다.

```python
from openai import OpenAI

client = OpenAI()
```

```python
speech_file_path = "tts_audio.mp3"

response = client.audio.speech.create(
    model="tts-1",
    input="아~ 오늘 파이썬 배우기 정말 좋은 날이네~",
    voice="shimmer",
    response_format="mp3",
    speed=2.0,
)

response.stream_to_file(speech_file_path)
```

```
/tmp/ipykernel_4084825/1531316983.py:11: DeprecationWarning: Due to a bug, this method doesn't actually stream the response content, `.with_streaming_response.method()` should be used instead
  response.stream_to_file(speech_file_path)
```

```python
from IPython.display import Audio

Audio(speech_file_path)
```

Your browser does not support the audio element.

### 2. Speech To Text(STT)

* STT는 사람의 말소리를 텍스트로 변환하는 기술입니다.
* 이는 음성 인식을 통해 구어체 언어를 캡처하고 이를 기록 가능한 형태의 텍스트로 변환합니다.
* 사용예시: 음성 명령 입력, 자동 회의록 작성, 음성 기반 검색 시스템 등.

**\[참고]**

* 공식문서: https://platform.openai.com/docs/guides/speech-to-text
* 파일 업로드는 현재 25MB로 제한되어 있으며, 지원되는 입력 파일 형식은 `mp3`, `MP4`, `MPEG`, `MPGA`, `M4A`, `WAV`, `WEBM` 입니다.
* 지원언어
  * 아프리칸스어, 아랍어, 아르메니아어, 아제르바이잔어, 벨라루스어, 보스니아어, 불가리아어, 카탈로니아어, 중국어, 크로아티아어, 체코어, 덴마크어, 네덜란드어, 영어, 에스토니아어, 핀란드어, 프랑스어, 갈리시아어, 독일어, 그리스어, 히브리어, 힌디어, 헝가리어, 아이슬란드어, 인도네시아어, 이탈리아어, 일본어, 인도네시아어, 칸나다어, 카자흐어, 한국어, 라트비아어, 리투아니아어, 마케도니아어, 말레이어, 마라티어, 마오리어, 네팔어, 노르웨이어, 페르시아어, 폴란드어, 포르투갈어, 루마니아어, 러시아어, 세르비아어, 슬로바키아어, 슬로베니아어, 스페인어, 스와힐리어, 스웨덴어, 타갈로그어, 타밀어, 태국어, 터키어, 우크라이나어, 우르두어, 베트남어 및 웨일스어.

**주요 파라미터**

* `file`: 변환할 오디오 파일 개체(파일 이름이 아님)로, 다음 형식 중 하나입니다: `FLAC`, `MP3`, `MP4`, `MPEG`, `MPGA`, `M4A`, `OGG`, `WAV` 또는 `WEBM`.
* `model`: 현재는 `whisper-1` 모델만 지정 가능합니다.
* `language`: 입력 오디오의 언어입니다. 입력 언어를 ISO-639-1 형식으로 제공하면 정확도와 지연 시간이 개선됩니다.
  * [ISO-639-1 형식](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
* `prompt`: (선택 사항) 모델의 스타일을 안내하거나 이전 오디오 세그먼트를 계속하기 위한 텍스트입니다. 프롬프트는 오디오 언어와 일치해야 합니다.
* `response_format`: 변환된 결과물 출력 형식입니다. 가능한 지정 옵션은 `json`, `text`, `srt`, `verbose_json` 또는 `vtt` 입니다.
* `temperature`: 0에서 1 사이의 샘플링 `temperature` 입니다. 0.8과 같이 값이 높을수록 출력은 더 무작위적이고, 0.2와 같이 값이 낮을수록 출력은 더 집중적이고 결정론적입니다. 0으로 설정하면 모델은 로그 확률을 사용하여 특정 임계값에 도달할 때까지 자동으로 `temperature` 을 높입니다.

```python
audio_file = open("data/채용면접_샘플_01.wav", "rb")
transcript = client.audio.transcriptions.create(
    file=audio_file,
    model="whisper-1",
    language="ko",
    response_format="text",
    temperature=0.0,
)
```

```python
# 결과물 출력
print(transcript)
```

```
지금 생각해보면 가장 기억에 남는 것이 외환위기 때의 경험입니다. 외환위기 때 나라뿐 아니라 회사에서는 달러가 많이 부족했고 우리는 수출을 하기 위해서 원자재라든지 대금지급 혹은 또 우리가 수출한 물건에 대한 대금을 받아야 되는 상황이었습니다. 일단은 우리가 해외로부터 받아야 될 때는 최대한 그것을 달러로 받았고 반대로 우리가 지불해야 될 것은 가능한 한 원자재를 통해서 지급을 했습니다. 그 원자재라는 것이 결국은 반도체 쪽이었는데 우리나라에서 가장 나름 손쉽게 구하면서도 꼭 필요한 제품인 반도체를 원자재값 대응으로 물건을 주면서 반대로 해외에서 우리가 받아야 될 것은 달러로 받으면서 그 환율차를 가장 줄일 수가 있었습니다. 그것으로 인해서 약 6개월 동안에 나름 회사에서는 달러 지출을 막을 수 있었고 그때 그나마 달러를 회사에서 확보를 해서 나름의 위기를 극복할 수 있었던 것으로 생각합니다.
```

### 3. 더욱 긴 오디오 입력 대한 처리

* 기본적으로 Whisper API는 **25MB 미만의 파일** 만 지원
* 이보다 긴 오디오 파일이 있는 경우 **25MB 이하의 청크로 나누거나 압축된 오디오 형식을 사용**
* 최상의 성능을 얻으려면 문장 중간에 오디오를 분할하면 일부 문맥이 손실될 수 있으므로 가급적 분할 사용에 주의
* 이를 처리하는 한 가지 방법은 `PyDub` 오픈 소스 Python 패키지를 사용하여 **오디오를 분할**

**샘플 데이터셋(채용면접 인터뷰 데이터)**

* 링크: https://aihub.or.kr/aihubdata/data/view.do?currMenu=115\&topMenu=100\&aihubDataSe=data\&dataSetSn=71592

```python
#%pip install pydub
```

```python
from pydub import AudioSegment

filename = "data/채용면접_샘플_02.wav"
myaudio = AudioSegment.from_mp3(filename)

# PyDub 는 밀리초 단위로 시간을 계산합니다.
thirty_seconds = 1 * 30 * 1000  # (1초 * 30) * 1000
total_milliseconds = myaudio.duration_seconds * 1000  # 전체 길이를 밀리초로 변환
```

```
/home/kubwa/anaconda3/lib/python3.11/site-packages/pydub/utils.py:170: RuntimeWarning: Couldn't find ffmpeg or avconv - defaulting to ffmpeg, but may not work
  warn("Couldn't find ffmpeg or avconv - defaulting to ffmpeg, but may not work", RuntimeWarning)
```

```python
# 전체 길이를 30초로 나누어서 반복할 횟수를 계산
total_iterations = int(total_milliseconds // thirty_seconds + 1)
total_iterations
```

```
3
```

```python
# 생성된 파일명을 저장할 리스트
output_filenames = []

for i in range(total_iterations):
    if i < total_iterations - 1:
        # 30초 단위로 오디오를 분할합니다.
        part_of_audio = myaudio[thirty_seconds * i: thirty_seconds * (i + 1)]
    else:
        # 마지막은 나머지 전체를 분할합니다.
        part_of_audio = myaudio[thirty_seconds * i:]

    output_filename = (
        # 예시: 채용면접_샘플_02-(1).mp3, 채용면접_샘플_02-(2).mp3 ...
        f"{filename[:-4]}-({i+1}).mp3"
    )

    # 분할된 오디오를 저장합니다.
    part_of_audio.export(output_filename, format="mp3")
    output_filenames.append(output_filename)
```

```python
# 결과물(파일명) 출력
output_filenames
```

```
['data/채용면접_샘플_02-(1).mp3',
 'data/채용면접_샘플_02-(2).mp3',
 'data/채용면접_샘플_02-(3).mp3']
```

```python
transcripts = []

for audio_filename in output_filenames:
    audio_file = open(audio_filename, "rb")  # audio file 을 읽어옵니다.

    # transcript 를 생성합니다.
    transcript = client.audio.transcriptions.create(
        file=audio_file,
        model="whisper-1",  # 모델은 whisper-1 을 사용
        language="ko",  # 한국어를 사용
        response_format="text",  # 결과물은 text 로 출력
        temperature=0.0,
    )

    # 생성된 transcript 를 리스트에 추가합니다.
    transcripts.append(transcript)

# 전체 transcript 출력(리스트를 문자열로 변환)
final_output = "---- 분할 ---- \n".join(transcripts)
print(final_output)
```

```
뭐 제가 평소에 제가 생각했던 것 말고 다른 사람들이 주로 저한테 말했던 걸 생각해보면은 일단은 되게 한가지에 꽂히면 그거를 끝을 보는 그런 성격을 장점이라고 해줬던 것 같습니다. 항상 실행을 하는 편이었고 주변에서 저한테 너 항상 행동력이 좋다 이런 말을 들었던 것 같아요. 저도 그렇게 생각합니다.
---- 분할 ---- 
제가 하고 싶은 일이 생기고 호기심이 생기면 어쨌든 그거를 실행해봐야 다음으로 넘어갈 수 있다고 생각하고 있어요. 그래서 사실 일적인 부분 말고도 취미적인 부분에서도 되게 많은 것들을 시도했던 경험이 있고요. 단점을 말하자면 좀 주변에서 제가 많이 들었던 게 자기 비하였던 것 같아요. 제가 좀 외적으로 컴플렉스를 스스로
---- 분할 ---- 
느끼고 있는 부분들이 많은데 안 그래야지 라고 생각하면서도 스스로 좀 비하하는 말들을 많이 하게 되더라구요. 그래서 주변에서 그런 것들에 대해서 주의를 좀 많이 줬던 순간들이 있었어요. 그래서 저도 최대한 그런 것들을 좀 지양해야겠다고 스스로 좀 다 지고 있습니다.
```

```python
example_prompt = """
당신은 채용 담당관입니다.
주어진 내용을 바탕으로, 면접자의 긍정적인 면과 부정적인 면을 나누어서 정리해 주세요.
결과물은 요약된 불렛포인트로 정리해 주세요.
한글로 작성해 주세요.
(예시)
#긍정적인면
- 
- 
-
#부정정인면
-
-  
"""


def generate_HR_opinion(temperature, prompt, transcript):
    response = client.chat.completions.create(
        model="gpt-4",
        temperature=temperature,
        messages=[
            {"role": "system", "content": prompt},
            {"role": "user", "content": transcript},
        ],
        stream=True,
    )
    final_answer = []

    # 스트림 모드에서는 completion.choices 를 반복문으로 순회
    for chunk in response:
        # chunk 를 저장
        chunk_content = chunk.choices[0].delta.content
        # chunk 가 문자열이면 final_answer 에 추가
        if isinstance(chunk_content, str):
            final_answer.append(chunk_content)
            # 토큰 단위로 실시간 답변 출력
            print(chunk_content, end="")
    return "".join(final_answer)


hr_opinion = generate_HR_opinion(0, example_prompt, final_output)
```

```
#긍정적인면
- 한 가지 일에 꽂히면 끝을 본다는 집중력
- 행동력이 좋음
- 호기심이 많고, 새로운 것을 시도하는 것을 두려워하지 않음

#부정적인면
- 자기 비하하는 경향이 있음
- 외적인 컴플렉스를 스스로 느끼고 있음
- 스스로를 지나치게 엄격하게 평가하는 경향이 있음
```

```python
```
