⊢ AI 모델 활용

음성 생성과 번역을 활용한 데스크톱 번역기

최 수빈 2025. 3. 28. 15:33

 

PyQt5, Hugging Face Transformers, Eleven Labs API 활용

  • 영어 → 한국어 자동 번역
  • 번역된 텍스트 → 음성 변환
  • 변환된 음성 → 재생
  • GUI 인터페이스 제공
번역 영어 문장을 입력하면 NLLB-200 모델로 한국어로 번역
음성 생성 번역된 문장을 Eleven Labs API로 음성(mp3)으로 변환
음성 재생 생성된 mp3 파일을 PyDub으로 재생
GUI 구성 PyQt5로 사용자 인터페이스 구성 (입력, 버튼, 출력 등)

 

pip install requests PyQt5 pydub dotenv transformers torch torchaudio torchmedia

 

 

필요 라이브러리

import os
import requests
from dotenv import load_dotenv
from PyQt5 import QtWidgets
from PyQt5.QtCore import QUrl
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
from pydub import AudioSegment
from pydub.playback import play
import io

PyQt5: GUI 및 재생 인터페이스

transformers: Facebook의 NLLB 번역 모델 사용

pydub: 생성된 mp3 음성 재생

requests, dotenv: Eleven Labs API 호출, 환경변수 로딩

 

 

 클래스 구조

 

TranslatorApp 클래스

class TranslatorApp(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.init_ui()

        # 번역 모델 로딩
        model_name = "facebook/nllb-200-distilled-600M"
        self.tokenizer = AutoTokenizer.from_pretrained(model_name)
        self.model = AutoModelForSeq2SeqLM.from_pretrained(model_name)

        # API 설정 로드
        load_dotenv()
        self.api_key = os.getenv("API_KEY")
        self.url = os.getenv("API_URL")

        self.player = QMediaPlayer()

모델: facebook/nllb-200-distilled-600M (NLLB-200 경량 모델)

API 키와 URL: .env에서 불러옴

음성 재생기: QMediaPlayer 초기화

 

 

UI 구성 (init_ui)

def init_ui(self):
    self.text_input = QtWidgets.QLineEdit(self)
    self.text_input.setPlaceholderText("번역할 텍스트 입력")

    self.translate_button = QtWidgets.QPushButton("번역 및 음성 생성", self)
    self.output_label = QtWidgets.QLabel(self)
    self.play_button = QtWidgets.QPushButton("음성 재생", self)
    self.play_button.setEnabled(False)

    layout = QtWidgets.QVBoxLayout()
    layout.addWidget(self.text_input)
    layout.addWidget(self.translate_button)
    layout.addWidget(self.output_label)
    layout.addWidget(self.play_button)
    self.setLayout(layout)

    self.translate_button.clicked.connect(self.translate_and_generate_audio)
    self.play_button.clicked.connect(self.play_audio)

    self.setWindowTitle("번역 및 음성 생성기")
    self.show()

입력 필드 + 버튼 + 번역 결과 출력 + 음성 재생 버튼 구성

translate_button → 번역 및 음성 생성

play_button → 생성된 음성 파일 재생

 

 

번역 및 음성 생성 기능

def translate_and_generate_audio(self):
    text = self.text_input.text()

    # 번역 수행
    inputs = self.tokenizer(text, return_tensors="pt")
    generated_tokens = self.model.generate(
        inputs.input_ids,
        forced_bos_token_id=self.tokenizer.lang_code_to_id["kor_Hang"]
    )
    translated_text = self.tokenizer.decode(generated_tokens[0], skip_special_tokens=True)

    # Eleven Labs 음성 생성 요청
    data = {
        "text": translated_text,
        "model_id": "eleven_multilingual_v2",
        "voice_settings": {
            "stability": 0.5,
            "similarity_boost": 1,
            "style": 0.5,
            "use_speaker_boost": True
        }
    }
    headers = {
        "xi-api-key": self.api_key,
        "Content-Type": "application/json"
    }

    response = requests.post(self.url, json=data, headers=headers)

    if response.status_code == 200:
        output_audio_path = "audio_output/output_audio.mp3"
        with open(output_audio_path, "wb") as f:
            f.write(response.content)

        self.output_label.setText(f"번역 결과: {translated_text}")
        self.play_button.setEnabled(True)
    else:
        self.output_label.setText("음성 생성 실패")

NLLB 모델로 번역

Eleven Labs API로 mp3 음성 생성

성공 시 mp3 저장 + 결과 출력

 

 

음성 재생 기능

def play_audio(self):
    audio_path = "audio_output/output_audio.mp3"
    if os.path.exists(audio_path):
        audio = AudioSegment.from_mp3(audio_path)
        play(audio)
    else:
        self.output_label.setText("오디오 파일을 찾을 수 없습니다.")

pydub으로 mp3 로드 → 재생

파일 없으면 에러 메시지 출력

 

 

메인 실행부

if __name__ == '__main__':
    app = QtWidgets.QApplication([])
    translator = TranslatorApp()
    app.exec_()

QApplication 생성

TranslatorApp 실행

GUI 이벤트 루프 시작

 

 

코드 조각을 활용한 전체 코드

import os

os.environ["TOKENIZERS_PARALLELISM"] = "false"
import requests
from dotenv import load_dotenv
from PyQt5 import QtWidgets
from transformers import AutoTokenizer
from PyQt5.QtCore import QUrl
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent
from transformers import AutoModelForSeq2SeqLM
from pydub import AudioSegment


class TranslatorApp(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.init_ui()

        # 번역 모델 로드
        model_name = "facebook/nllb-200-distilled-600M"
        self.tokenizer = AutoTokenizer.from_pretrained(model_name)
        self.model = AutoModelForSeq2SeqLM.from_pretrained(model_name)

        # 언어 코드 매핑
        self.lang_map = {
            "영어": "eng_Latn",
            "한국어": "kor_Hang",
            "불어": "fra_Latn",
            "일본어": "jpn_Jpan",
            "중국어": "zho_Hans",
        }

        # API 설정
        load_dotenv()
        self.api_key = os.getenv("ELEVENLABS_API_KEY")
        self.api_url = os.getenv(
            "API_URL"
        )  # e.g., https://api.elevenlabs.io/v1/text-to-speech/{voice_id}

        # 음성 재생기
        self.player = QMediaPlayer()

    def init_ui(self):
        # 입력 텍스트
        self.text_input = QtWidgets.QLineEdit(self)
        self.text_input.setPlaceholderText("번역할 텍스트 입력")

        # 입력/출력 언어 선택 드롭다운
        self.input_lang = QtWidgets.QComboBox(self)
        self.input_lang.addItems(["영어", "한국어", "불어", "일본어", "중국어"])

        self.output_lang = QtWidgets.QComboBox(self)
        self.output_lang.addItems(["영어", "한국어", "불어", "일본어", "중국어"])

        # 번역 버튼
        self.translate_button = QtWidgets.QPushButton("번역 및 음성 생성", self)
        self.output_label = QtWidgets.QLabel(self)

        # 레이아웃 설정
        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(self.text_input)
        layout.addWidget(QtWidgets.QLabel("입력 언어"))
        layout.addWidget(self.input_lang)
        layout.addWidget(QtWidgets.QLabel("출력 언어"))
        layout.addWidget(self.output_lang)
        layout.addWidget(self.translate_button)
        layout.addWidget(self.output_label)
        self.setLayout(layout)

        # 이벤트 핸들링
        self.translate_button.clicked.connect(self.translate_and_generate_audio)

        # 창 설정
        self.setWindowTitle("다국어 번역 및 음성 생성기")
        self.resize(400, 200)
        self.show()

    def translate_and_generate_audio(self):
        text = self.text_input.text()
        if not text:
            self.output_label.setText("입력 텍스트를 작성해주세요.")
            return

        source_lang_code = self.lang_map[self.input_lang.currentText()]
        target_lang_code = self.lang_map[self.output_lang.currentText()]

        translated_text = ""

        # 입력에 언어 prefix 붙이기
        try:
            print("Source Language Code:", source_lang_code)
            self.tokenizer.src_lang = source_lang_code
            batch = self.tokenizer(text, return_tensors="pt")
            print("Input IDs:", batch["input_ids"])
            print("Attention Mask:", batch["attention_mask"])
            print("Target Language Code:", target_lang_code)
            forced_bos_token_id = self.tokenizer.convert_tokens_to_ids(target_lang_code)
            generated_tokens = self.model.generate(
                input_ids=batch["input_ids"],
                attention_mask=batch["attention_mask"],
                forced_bos_token_id=forced_bos_token_id,
                max_new_tokens=100,
            )
            translated_text = self.tokenizer.batch_decode(
                generated_tokens, skip_special_tokens=True
            )[0]
            print("Generated Tokens:", generated_tokens)
            print("Translated:", translated_text)
            self.output_label.setText(f"번역 결과: {translated_text}")
        except Exception as e:
            print("Translation error:", str(e))
            self.output_label.setText(f"번역 실패: {str(e)}")

        # 음성 생성
        data = {
            "text": translated_text,
            "model_id": "eleven_multilingual_v2",
            "voice_settings": {"stability": 0.5, "similarity_boost": 0.5},
        }
        headers = {"xi-api-key": self.api_key, "Content-Type": "application/json"}
        response = requests.post(self.api_url, json=data, headers=headers)

        if response.status_code == 200:
            output_audio_path = "audio_output/output_audio.mp3"
            os.makedirs(os.path.dirname(output_audio_path), exist_ok=True)
            with open(output_audio_path, "wb") as f:
                f.write(response.content)

            # mp3 → wav 변환 후 재생 (PyQt용)
            sound = AudioSegment.from_mp3(output_audio_path)
            wav_path = "audio_output/output_audio.wav"
            sound.export(wav_path, format="wav")

            audio_url = QUrl.fromLocalFile(os.path.abspath(wav_path))
            self.player.setMedia(QMediaContent(audio_url))
            self.player.play()
        else:
            self.output_label.setText(
                f"음성 생성 실패\n{response.status_code} - {response.text}"
            )


if __name__ == "__main__":
    app = QtWidgets.QApplication([])
    translator = TranslatorApp()
    app.exec_()

→ 입력언어와 출력언어를 다섯개로 늘리고, 사용자가 지정할 수 있도록 변경

→ 번역과 동시에 음성 생성