API 키 및 음성 인식 모델 설정 추가: Hugging Face API 키와 모델 제공자 설정을 config.ini에 추가하고, main.py에서 관련 설정을 반영하였습니다. 음성 인식 기능에 Hugging Face와 VOSK 모델 지원을 추가하였습니다.

This commit is contained in:
Envy_PC 2025-05-02 23:54:37 +09:00
parent 6f4309a616
commit bdbd7cb6f1
6 changed files with 618 additions and 153 deletions

View File

@ -1,5 +1,6 @@
[api] [api]
openai_api_key = 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 openai_api_key = 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
huggingface_api_key = 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
[audio] [audio]
sample_rate = 16000 sample_rate = 16000
@ -10,3 +11,7 @@ buffer_duration = 3
[app] [app]
theme = light theme = light
[model]
provider = huggingface
name = kresnik/wav2vec2-large-xlsr-korean

View File

@ -0,0 +1,6 @@
대화 ID: 20250502211201
시작 시간: 2025-05-02 21:12:01
--- 대화 내용 ---
[21:12:01] 알 수 없음: EE

150
main.py
View File

@ -23,7 +23,8 @@ def main(page: ft.Page):
# 기본 설정 생성 # 기본 설정 생성
if not os.path.exists("config.ini"): if not os.path.exists("config.ini"):
config["api"] = { config["api"] = {
"openai_api_key": "your_openai_api_key_here" "openai_api_key": "your_openai_api_key_here",
"huggingface_api_key": "your_huggingface_api_key_here"
} }
config["audio"] = { config["audio"] = {
"sample_rate": "16000", "sample_rate": "16000",
@ -34,6 +35,10 @@ def main(page: ft.Page):
config["app"] = { config["app"] = {
"theme": "light" "theme": "light"
} }
config["model"] = {
"provider": "huggingface",
"name": "facebook/wav2vec2-base-960h"
}
with open("config.ini", "w") as configfile: with open("config.ini", "w") as configfile:
config.write(configfile) config.write(configfile)
@ -178,36 +183,47 @@ def main(page: ft.Page):
status_indicator.set_detecting(True) status_indicator.set_detecting(True)
page.update() page.update()
# MP3 또는 WAV 파일 처리 # MP3 또는 WAV 파일 직접 처리
if file_path.lower().endswith('.mp3'): text = speech_recognizer.recognize_file(file_path)
# MP3를 WAV로 변환 (librosa 사용)
import librosa # 변환 실패 시 MP3 파일인 경우 추가 처리
if text is None and file_path.lower().endswith('.mp3'):
# MP3 파일 로드 try:
audio_data, sample_rate = librosa.load(file_path, sr=16000, mono=True) # MP3를 WAV로 변환 (librosa 사용)
import librosa
# WAV 파일로 변환
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_file:
temp_wav_path = temp_file.name
# 16비트 정수로 변환 # MP3 파일 로드
audio_data_int = (audio_data * 32767).astype(np.int16) audio_data, sample_rate = librosa.load(file_path, sr=16000, mono=True)
# WAV 파일 저장 - 쓰기 모드('wb')로 열기 # WAV 파일로 변환
with wave.open(temp_wav_path, 'wb') as wf: fd, temp_wav_path = tempfile.mkstemp(suffix=".wav")
wf.setnchannels(1) os.close(fd) # 파일 디스크립터 즉시 닫기
wf.setsampwidth(2) # 16-bit
wf.setframerate(16000)
wf.writeframes(audio_data_int.tobytes())
# OpenAI의 API를 사용해 음성 인식 try:
text = speech_recognizer.recognize_file(temp_wav_path) # 16비트 정수로 변환
audio_data_int = (audio_data * 32767).astype(np.int16)
# 임시 파일 삭제
os.unlink(temp_wav_path) # WAV 파일 저장 - Wave_write 객체 사용
else: wf = wave.open(temp_wav_path, 'wb')
# WAV 파일 직접 처리 try:
text = speech_recognizer.recognize_file(file_path) wf.setnchannels(1)
wf.setsampwidth(2) # 16-bit
wf.setframerate(16000)
wf.writeframes(audio_data_int.tobytes())
finally:
wf.close() # 명시적으로 닫아줌
# OpenAI의 API를 사용해 음성 인식
text = speech_recognizer.recognize_file(temp_wav_path)
finally:
# 임시 파일 항상 삭제
if os.path.exists(temp_wav_path):
try:
os.unlink(temp_wav_path)
except Exception as e:
print(f"임시 파일 삭제 오류: {e}")
except Exception as mp3_error:
print(f"MP3 처리 오류: {mp3_error}")
# 인식 결과 처리 # 인식 결과 처리
if text: if text:
@ -346,13 +362,49 @@ def main(page: ft.Page):
"""설정 대화상자 표시""" """설정 대화상자 표시"""
try: try:
# API 키 입력 필드 # API 키 입력 필드
api_key_field = ft.TextField( openai_api_key_field = ft.TextField(
label="OpenAI API 키", label="OpenAI API 키",
value=speech_recognizer.api_key, value=speech_recognizer.api_key,
password=True, password=True,
width=400 width=400
) )
# Hugging Face API 키 입력 필드
hf_api_key_field = ft.TextField(
label="Hugging Face API 키",
value=speech_recognizer.hf_api_key,
password=True,
width=400
)
# 모델 선택 관련 설정
# 모델 제공자 선택
model_provider_radio = ft.RadioGroup(
content=ft.Column([
ft.Radio(value="huggingface", label="Hugging Face (한국어 음성인식 권장)"),
ft.Radio(value="openai", label="OpenAI Whisper (백업)"),
ft.Radio(value="vosk", label="VOSK 오프라인 (완전 오프라인)"),
]),
value=speech_recognizer.model_provider
)
# 음성인식 모델 선택 드롭다운
model_options = [
ft.dropdown.Option("vosk-model-small-kr", "VOSK 모델 - 완전 오프라인 한국어 소형 모델"),
ft.dropdown.Option("kresnik/wav2vec2-large-xlsr-korean", "한국어 특화 음성인식 모델"),
ft.dropdown.Option("openai/whisper-small", "openai 한국어 특화 Whisper 소형 모델"),
ft.dropdown.Option("openai/whisper-medium", "openai 한국어 특화 Whisper 중형 모델"),
ft.dropdown.Option("openai/whisper-large-v3", "openai 한국어 특화 Whisper 대형 모델"),
ft.dropdown.Option("facebook/wav2vec2-base-960h", "영어 음성인식 기본 모델")
]
model_dropdown = ft.Dropdown(
label="음성인식 모델 선택",
width=400,
options=model_options,
value=speech_recognizer.model_name,
)
# 오디오 설정 슬라이더 # 오디오 설정 슬라이더
silence_threshold_slider = ft.Slider( silence_threshold_slider = ft.Slider(
min=0.01, min=0.01,
@ -413,8 +465,19 @@ def main(page: ft.Page):
def save_settings(e): def save_settings(e):
try: try:
# API 키 저장 # API 키 저장
if api_key_field.value: if openai_api_key_field.value:
speech_recognizer.set_api_key(api_key_field.value) speech_recognizer.set_api_key(openai_api_key_field.value)
# Hugging Face API 키 저장
if hf_api_key_field.value:
speech_recognizer.set_huggingface_api_key(hf_api_key_field.value)
# 모델 설정 저장
if model_provider_radio.value and model_dropdown.value:
speech_recognizer.set_model(
model_provider_radio.value,
model_dropdown.value
)
# 오디오 설정 저장 # 오디오 설정 저장
if hasattr(audio_source, 'update_settings'): if hasattr(audio_source, 'update_settings'):
@ -460,9 +523,21 @@ def main(page: ft.Page):
dialog = ft.AlertDialog( dialog = ft.AlertDialog(
title=ft.Text("설정"), title=ft.Text("설정"),
content=ft.Column([ content=ft.Column([
ft.Text("OpenAI API 설정", weight=ft.FontWeight.BOLD), ft.Text("API 설정", weight=ft.FontWeight.BOLD),
api_key_field, ft.Text("OpenAI API (백업용)", size=14),
ft.Divider(), openai_api_key_field,
ft.Divider(height=10),
ft.Text("Hugging Face API (권장)", size=14),
hf_api_key_field,
ft.Divider(height=20),
ft.Text("음성인식 모델 설정", weight=ft.FontWeight.BOLD),
ft.Text("API 제공자 선택", size=14),
model_provider_radio,
ft.Text("음성인식 모델 선택", size=14),
model_dropdown,
ft.Divider(height=20),
ft.Text("오디오 설정", weight=ft.FontWeight.BOLD), ft.Text("오디오 설정", weight=ft.FontWeight.BOLD),
silence_threshold_text, silence_threshold_text,
silence_threshold_slider, silence_threshold_slider,
@ -470,11 +545,12 @@ def main(page: ft.Page):
silence_duration_slider, silence_duration_slider,
buffer_duration_text, buffer_duration_text,
buffer_duration_slider, buffer_duration_slider,
ft.Divider(),
ft.Divider(height=20),
ft.Text("앱 설정", weight=ft.FontWeight.BOLD), ft.Text("앱 설정", weight=ft.FontWeight.BOLD),
ft.Text("테마"), ft.Text("테마", size=14),
theme_radio, theme_radio,
], scroll=ft.ScrollMode.AUTO, height=500), ], scroll=ft.ScrollMode.AUTO, height=600),
actions=[ actions=[
ft.TextButton("취소", on_click=lambda e: setattr(dialog, "open", False)), ft.TextButton("취소", on_click=lambda e: setattr(dialog, "open", False)),
ft.TextButton("저장", on_click=save_settings), ft.TextButton("저장", on_click=save_settings),

View File

@ -8,6 +8,9 @@ import struct
import threading import threading
import queue import queue
import time import time
import requests
import json
from vosk import Model, KaldiRecognizer
from typing import Optional, Callable from typing import Optional, Callable
class SpeechRecognizer: class SpeechRecognizer:
@ -17,11 +20,30 @@ class SpeechRecognizer:
if os.path.exists(config_path): if os.path.exists(config_path):
self.config.read(config_path) self.config.read(config_path)
# OpenAI API 키
self.api_key = self.config.get("api", "openai_api_key", fallback="") self.api_key = self.config.get("api", "openai_api_key", fallback="")
if not self.api_key or self.api_key == "your_openai_api_key_here": # Hugging Face API 키
print("경고: OpenAI API 키가 설정되지 않았습니다. config.ini 파일을 확인하세요.") self.hf_api_key = self.config.get("api", "huggingface_api_key", fallback="")
# 모델 선택
self.model_provider = self.config.get("model", "provider", fallback="huggingface")
self.model_name = self.config.get("model", "name", fallback="facebook/wav2vec2-base-960h")
# 한국어 모델 (기본값으로 사용 가능한 한국어 음성인식 모델들)
self.korean_models = [
"kresnik/wav2vec2-large-xlsr-korean", # 한국어 특화 모델
"openai/whisper-small", # 한국어 특화 Whisper 모델
"openai/whisper-medium", # 더 큰 한국어 특화 Whisper 모델
"openai/whisper-large-v3", # 다국어 모델 (한국어 포함)
"facebook/wav2vec2-base-960h" # 영어 기본 모델 (참고용)
]
# API 키가 설정되지 않았을 때 경고
if not self.api_key and not self.hf_api_key:
print("경고: API 키가 설정되지 않았습니다. config.ini 파일에 OpenAI API 키 또는 Hugging Face API 키를 설정하세요.")
# OpenAI 설정 (백업용)
openai.api_key = self.api_key openai.api_key = self.api_key
# 실시간 처리를 위한 설정 # 실시간 처리를 위한 설정
@ -30,83 +52,294 @@ class SpeechRecognizer:
self.process_thread = None self.process_thread = None
self.callback = None self.callback = None
# VOSK 모델 오프라인 초기화
if self.model_provider.lower() == "vosk":
try:
self.vosk_model = Model(self.model_name)
except Exception as e:
print(f"VOSK 모델 로드 오류: {e}")
self.vosk_model = None
else:
self.vosk_model = None
def recognize(self, audio_data: np.ndarray) -> Optional[str]: def recognize(self, audio_data: np.ndarray) -> Optional[str]:
"""오디오 데이터를 텍스트로 변환합니다.""" """오디오 데이터를 텍스트로 변환합니다."""
if len(audio_data) == 0: if len(audio_data) == 0:
return None return None
temp_file_path = None
try: try:
# 임시 파일로 오디오 저장 # 임시 파일 생성
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_file: fd, temp_file_path = tempfile.mkstemp(suffix=".wav")
# WAV 파일 형식으로 저장 os.close(fd) # 파일 디스크립터 즉시 닫기
with wave.open(temp_file.name, 'wb') as wf:
wf.setnchannels(1) # WAV 파일 작성 (wave 모듈 사용)
wf.setsampwidth(2) # 16-bit # 쓰기 모드('wb')로 열었으므로 Wave_write 객체가 반환됩니다
wf.setframerate(16000) wf = wave.open(temp_file_path, 'wb')
try:
wf.setnchannels(1) # 모노 채널
wf.setsampwidth(2) # 16-bit
wf.setframerate(16000) # 샘플레이트
# float32 데이터를 int16으로 변환 # float32 데이터를 int16으로 변환
audio_data_int = (audio_data * 32767).astype(np.int16) audio_data_int = (audio_data * 32767).astype(np.int16)
wf.writeframes(audio_data_int.tobytes()) wf.writeframes(audio_data_int.tobytes())
finally:
wf.close() # 명시적으로 닫아줌
if self.model_provider.lower() == "vosk":
# VOSK 오프라인 모델 사용
if self.vosk_model is None:
print("VOSK 모델이 로드되지 않았습니다.")
return None
return self._recognize_with_vosk(temp_file_path)
elif self.model_provider.lower() == "huggingface" and self.hf_api_key:
# Hugging Face 모델을 사용하여 음성 인식
return self._recognize_with_huggingface(temp_file_path)
elif self.api_key:
# OpenAI Whisper API를 백업으로 사용
return self._recognize_with_openai(temp_file_path)
else:
print("사용 가능한 API 키가 없습니다.")
return None
# OpenAI Whisper API를 사용하여 음성 인식
with open(temp_file.name, "rb") as audio_file:
try:
if not self.api_key:
print("API 키가 설정되지 않았습니다.")
return None
# 최신 OpenAI API 사용 방식
try:
client = openai.OpenAI(api_key=self.api_key)
result = client.audio.transcriptions.create(
model="whisper-1",
file=audio_file,
language="ko",
response_format="text"
)
text_result = result if isinstance(result, str) else result.text
except (AttributeError, ImportError, NameError):
# 이전 버전 OpenAI 라이브러리 지원 (최대한 호환성 유지)
try:
# audio 모듈 사용 (최신 버전)
result = openai.audio.transcriptions.create(
model="whisper-1",
file=audio_file,
language="ko",
response_format="text"
)
text_result = result if isinstance(result, str) else result.text
except (AttributeError, ImportError, NameError):
# Audio 클래스 사용 (이전 버전)
try:
result = openai.Audio.transcribe(
model="whisper-1",
file=audio_file,
language="ko",
response_format="text"
)
text_result = result if isinstance(result, str) else result.get("text", "")
except:
print("OpenAI API 호출 방식을 찾을 수 없습니다.")
return None
# 임시 파일 삭제
os.unlink(temp_file.name)
return text_result
except Exception as e:
print(f"API 호출 오류: {e}")
# 임시 파일 삭제 시도
try:
os.unlink(temp_file.name)
except:
pass
return None
except Exception as e: except Exception as e:
print(f"음성 인식 오류: {e}") print(f"음성 인식 오류: {e}")
return None return None
finally:
# 임시 파일 삭제 시도 (try-finally로 보장)
if temp_file_path and os.path.exists(temp_file_path):
try:
os.unlink(temp_file_path)
except Exception as e:
print(f"임시 파일 삭제 오류: {e}")
def _recognize_with_huggingface(self, file_path: str) -> Optional[str]:
"""Hugging Face API를 사용하여 음성 인식을 수행합니다."""
try:
# 사용할 모델 결정
model_to_use = self.model_name
max_retries = 5 # 최대 재시도 횟수를 5회로 늘림
# 파일 확장자에 따라 Content-Type 결정
content_type = "audio/wav" # 기본값은 WAV
if file_path.lower().endswith(".mp3"):
content_type = "audio/mpeg"
elif file_path.lower().endswith(".flac"):
content_type = "audio/flac"
# API 요청 URL (모델 로딩 대기 파라미터 추가)
api_url = f"https://api-inference.huggingface.co/models/{model_to_use}?wait_for_model=true"
# 파일 크기 확인 및 로그 출력
file_size = os.path.getsize(file_path)
print(f"[INFO] 파일 크기: {file_size} bytes, 파일 형식: {content_type}")
# 파일 로드
with open(file_path, "rb") as f:
audio_bytes = f.read()
# API 호출용 헤더
headers = {
"Authorization": f"Bearer {self.hf_api_key}",
"Content-Type": content_type # 올바른 Content-Type 설정
}
print(f"[INFO] 모델 {model_to_use}로 인식 시도")
# 첫 번째 시도: 설정된 모델 (Exponential Backoff 적용)
for attempt in range(1, max_retries + 1):
try:
# Exponential Backoff 대기 시간 계산 (첫 시도는 0초)
backoff_time = 0 if attempt == 1 else 2 ** (attempt - 2)
print(f"[INFO] 요청 {attempt}/{max_retries}: {model_to_use}" +
(f" (Backoff: {backoff_time}초 대기 후)" if backoff_time > 0 else ""))
# 첫 시도가 아니면 Exponential Backoff 대기
if backoff_time > 0:
time.sleep(backoff_time)
# 120초 타임아웃으로 요청 (모델 로딩 시간 고려)
response = requests.post(api_url, headers=headers, data=audio_bytes, timeout=120)
# 응답 상태 코드 확인
print(f"응답 상태 코드: {response.status_code}")
# 503 Service Unavailable - 모델 로딩 중 또는 서버 과부하
if response.status_code == 503:
print(f"[WARN] 서버 과부하 (503 Service Unavailable)")
print(f"응답 내용: {response.content[:200]}")
if attempt < max_retries:
print(f"Exponential Backoff: {2 ** (attempt - 1)}초 후 재시도...")
continue # 위에서 계산된 다음 Backoff 시간 적용
break
# 기타 HTTP 오류
elif response.status_code != 200:
print(f"[WARN] HTTP 오류: {response.status_code}")
print(f"응답 내용: {response.content[:200]}")
if attempt < max_retries:
print(f"{backoff_time}초 후 재시도...")
continue
break
# JSON 응답 파싱 시도
try:
result = response.json()
# 결과 확인
if isinstance(result, dict) and "text" in result:
return result["text"]
# text 키가 없는 경우 다른 형식 확인
if isinstance(result, list) and len(result) > 0:
if "generated_text" in result[0]:
return result[0]["generated_text"]
elif "text" in result[0]:
return result[0]["text"]
# 다른 결과 형식을 처리하기 위한 로그
print(f"알 수 없는 응답 구조: {result}")
except json.JSONDecodeError:
# JSON 디코딩 오류 (빈 응답이나 HTML이 온 경우)
print(f"[WARN] JSON 파싱 실패: HTTP {response.status_code}, 내용 길이={len(response.content)}")
print(f"응답 내용 일부: {response.content[:200]}")
except requests.exceptions.RequestException as e:
print(f"[WARN] 요청 실패: {e}")
# 마지막 시도가 아니고 다른 오류인 경우 짧은 대기 후 재시도
if attempt < max_retries and response.status_code != 503:
print(f"1초 후 재시도...")
time.sleep(1)
print("설정된 모델로 모든 시도 실패")
# 두 번째 시도: 다른 한국어 모델을 하나씩 시도
for korean_model in self.korean_models:
if korean_model == model_to_use:
continue # 이미 시도한 모델은 건너뜀
print(f"[INFO] 대체 모델 {korean_model}로 시도")
# 대체 모델 API URL
alt_api_url = f"https://api-inference.huggingface.co/models/{korean_model}?wait_for_model=true"
for attempt in range(1, max_retries + 1):
try:
# Exponential Backoff 대기 시간 계산 (첫 시도는 0초)
backoff_time = 0 if attempt == 1 else 2 ** (attempt - 2)
print(f"[INFO] 대체 모델 시도 {attempt}/{max_retries}: {korean_model}" +
(f" (Backoff: {backoff_time}초 대기 후)" if backoff_time > 0 else ""))
# 첫 시도가 아니면 Exponential Backoff 대기
if backoff_time > 0:
time.sleep(backoff_time)
# 120초 타임아웃으로 요청
response = requests.post(alt_api_url, headers=headers, data=audio_bytes, timeout=120)
# 응답 상태 코드 확인
print(f"응답 상태 코드: {response.status_code}")
# 503 Service Unavailable - 모델 로딩 중 또는 서버 과부하
if response.status_code == 503:
print(f"[WARN] 서버 과부하 (503 Service Unavailable)")
print(f"응답 내용: {response.content[:200]}")
if attempt < max_retries:
print(f"Exponential Backoff: {2 ** (attempt - 1)}초 후 재시도...")
continue # 위에서 계산된 다음 Backoff 시간 적용
break
# 기타 HTTP 오류
elif response.status_code != 200:
print(f"[WARN] HTTP 오류: {response.status_code}")
print(f"응답 내용: {response.content[:200]}")
if attempt < max_retries:
print(f"{backoff_time}초 후 재시도...")
continue
break
# JSON 응답 파싱 시도
try:
result = response.json()
# 결과 확인
if isinstance(result, dict) and "text" in result:
return result["text"]
if isinstance(result, list) and len(result) > 0:
if "generated_text" in result[0]:
return result[0]["generated_text"]
elif "text" in result[0]:
return result[0]["text"]
except json.JSONDecodeError:
print(f"[WARN] 대체 모델 JSON 파싱 실패: HTTP {response.status_code}")
print(f"응답 내용 일부: {response.content[:200]}")
except requests.exceptions.RequestException as e:
print(f"[WARN] 대체 모델 요청 실패: {e}")
# 마지막 시도가 아니고 다른 오류인 경우 짧은 대기 후 재시도
if attempt < max_retries and response.status_code != 503:
print(f"1초 후 재시도...")
time.sleep(1)
print("모든 모델 시도 실패")
return None
except Exception as e:
print(f"Hugging Face API 호출 오류: {e}")
return None
def _recognize_with_vosk(self, file_path: str) -> Optional[str]:
"""VOSK 오프라인 모델로 음성 인식을 수행합니다."""
try:
wf = wave.open(file_path, "rb")
except Exception as e:
print(f"VOSK 오디오 파일 열기 오류: {e}")
return None
if not self.vosk_model:
print("VOSK 모델이 로드되지 않았습니다.")
return None
rec = KaldiRecognizer(self.vosk_model, wf.getframerate())
text = ""
while True:
data = wf.readframes(4000)
if len(data) == 0:
break
if rec.AcceptWaveform(data):
res = json.loads(rec.Result())
text += res.get("text", "")
final_res = json.loads(rec.FinalResult())
text += final_res.get("text", "")
wf.close()
return text if text else None
def _recognize_with_openai(self, file_path: str) -> Optional[str]:
"""OpenAI Whisper API를 사용하여 음성 인식을 수행합니다. (백업 방식)"""
try:
with open(file_path, "rb") as audio_file:
try:
client = openai.OpenAI(api_key=self.api_key)
result = client.audio.transcriptions.create(
model="whisper-1", # 기본 모델
file=audio_file,
language="ko",
response_format="text"
)
text_result = result if isinstance(result, str) else result.text
return text_result
except Exception as api_error:
print(f"OpenAI API 호출 오류: {api_error}")
return None
except Exception as e:
print(f"OpenAI API 파일 로드 오류: {e}")
return None
def recognize_file(self, file_path: str) -> Optional[str]: def recognize_file(self, file_path: str) -> Optional[str]:
"""오디오 파일을 텍스트로 변환합니다.""" """오디오 파일을 텍스트로 변환합니다."""
@ -115,54 +348,22 @@ class SpeechRecognizer:
return None return None
try: try:
# OpenAI Whisper API를 사용하여 음성 인식 if self.model_provider.lower() == "vosk":
with open(file_path, "rb") as audio_file: # VOSK 오프라인 모델 사용
try: if self.vosk_model is None:
if not self.api_key: print("VOSK 모델이 로드되지 않았습니다.")
print("API 키가 설정되지 않았습니다.")
return None
# 최신 OpenAI API 사용 방식
try:
client = openai.OpenAI(api_key=self.api_key)
result = client.audio.transcriptions.create(
model="whisper-1",
file=audio_file,
language="ko",
response_format="text"
)
text_result = result if isinstance(result, str) else result.text
except (AttributeError, ImportError, NameError):
# 이전 버전 OpenAI 라이브러리 지원 (최대한 호환성 유지)
try:
# audio 모듈 사용 (최신 버전)
result = openai.audio.transcriptions.create(
model="whisper-1",
file=audio_file,
language="ko",
response_format="text"
)
text_result = result if isinstance(result, str) else result.text
except (AttributeError, ImportError, NameError):
# Audio 클래스 사용 (이전 버전)
try:
result = openai.Audio.transcribe(
model="whisper-1",
file=audio_file,
language="ko",
response_format="text"
)
text_result = result if isinstance(result, str) else result.get("text", "")
except:
print("OpenAI API 호출 방식을 찾을 수 없습니다.")
return None
return text_result
except Exception as e:
print(f"파일 API 호출 오류: {e}")
return None return None
return self._recognize_with_vosk(file_path)
elif self.model_provider.lower() == "huggingface" and self.hf_api_key:
# Hugging Face 모델을 사용하여 음성 인식
return self._recognize_with_huggingface(file_path)
elif self.api_key:
# OpenAI Whisper API를 백업으로 사용
return self._recognize_with_openai(file_path)
else:
print("사용 가능한 API 키가 없습니다.")
return None
except Exception as e: except Exception as e:
print(f"파일 음성 인식 오류: {e}") print(f"파일 음성 인식 오류: {e}")
return None return None
@ -230,5 +431,35 @@ class SpeechRecognizer:
try: try:
with open("config.ini", "w") as config_file: with open("config.ini", "w") as config_file:
self.config.write(config_file) self.config.write(config_file)
except Exception as e:
print(f"설정 파일 저장 오류: {e}")
def set_huggingface_api_key(self, api_key: str):
"""Hugging Face API 키를 설정합니다."""
self.hf_api_key = api_key
# config.ini 파일 업데이트
self.config.set("api", "huggingface_api_key", api_key)
try:
with open("config.ini", "w") as config_file:
self.config.write(config_file)
except Exception as e:
print(f"설정 파일 저장 오류: {e}")
def set_model(self, provider: str, model_name: str):
"""음성 인식 모델을 설정합니다."""
self.model_provider = provider
self.model_name = model_name
# config.ini 파일 업데이트
if not self.config.has_section("model"):
self.config.add_section("model")
self.config.set("model", "provider", provider)
self.config.set("model", "name", model_name)
try:
with open("config.ini", "w") as config_file:
self.config.write(config_file)
except Exception as e: except Exception as e:
print(f"설정 파일 저장 오류: {e}") print(f"설정 파일 저장 오류: {e}")

144
poetry.lock generated
View File

@ -115,6 +115,108 @@ files = [
[package.dependencies] [package.dependencies]
pycparser = "*" pycparser = "*"
[[package]]
name = "charset-normalizer"
version = "3.4.2"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"},
{file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"},
{file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"},
{file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"},
{file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"},
{file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"},
{file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"},
{file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"},
{file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"},
{file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"},
{file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"},
{file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"},
{file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"},
{file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"},
{file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"},
{file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"},
{file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"},
{file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"},
{file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"},
{file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"},
{file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"},
{file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"},
{file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"},
{file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"},
{file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"},
{file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"},
{file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"},
{file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"},
{file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"},
{file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"},
{file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"},
{file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"},
{file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"},
{file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"},
{file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"},
{file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"},
{file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"},
{file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"},
{file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"},
{file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"},
{file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"},
{file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"},
{file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"},
{file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"},
{file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"},
{file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"},
{file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"},
{file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"},
{file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"},
{file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"},
{file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"},
{file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"},
{file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"},
{file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"},
{file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"},
{file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"},
{file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"},
{file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"},
{file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"},
{file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"},
{file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"},
{file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"},
{file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"},
{file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"},
{file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"},
{file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"},
{file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"},
{file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"},
{file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"},
{file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"},
{file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"},
{file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"},
{file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"},
{file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"},
{file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"},
{file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"},
{file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"},
{file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"},
{file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"},
{file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"},
]
[[package]] [[package]]
name = "colorama" name = "colorama"
version = "0.4.6" version = "0.4.6"
@ -1050,6 +1152,28 @@ files = [
[package.dependencies] [package.dependencies]
six = ">=1.9.0" six = ">=1.9.0"
[[package]]
name = "requests"
version = "2.32.3"
description = "Python HTTP for Humans."
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
{file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
]
[package.dependencies]
certifi = ">=2017.4.17"
charset-normalizer = ">=2,<4"
idna = ">=2.5,<4"
urllib3 = ">=1.21.1,<3"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]] [[package]]
name = "six" name = "six"
version = "1.17.0" version = "1.17.0"
@ -1129,6 +1253,24 @@ files = [
{file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"},
] ]
[[package]]
name = "urllib3"
version = "2.2.3"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"},
{file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"},
]
[package.extras]
brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""]
h2 = ["h2 (>=4,<5)"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["zstandard (>=0.18.0)"]
[[package]] [[package]]
name = "watchdog" name = "watchdog"
version = "3.0.0" version = "3.0.0"
@ -1302,4 +1444,4 @@ type = ["pytest-mypy"]
[metadata] [metadata]
lock-version = "2.1" lock-version = "2.1"
python-versions = "^3.8" python-versions = "^3.8"
content-hash = "45cc5a82edeb1d93670ba5ee2e56925e68d129abeca2481bf890f70a48ed5ed2" content-hash = "675cc981f2dac0ff7f944f56b41285406ddbbf696f36627c85e61dbd88cd8d83"

View File

@ -13,6 +13,11 @@ openai = "^1.0.0"
numpy = "^1.22.0" numpy = "^1.22.0"
sounddevice = "^0.4.6" sounddevice = "^0.4.6"
pyaudio = "^0.2.13" pyaudio = "^0.2.13"
requests = "^2.32.3"
librosa = "^0.11.0"
wave = "^0.0.2"
cffi = "^1.17.1"
scipy = "^1.15.2"
[build-system] [build-system]
requires = ["poetry-core"] requires = ["poetry-core"]