Now Loading ...
-
호시노 애니메이션 (5)
진행 상황
3인칭 카메라
에셋 스토어의 무료로 주어지는 Starter Assets을 사용하였습니다.
기본적으로 캐릭터의 점프, 이동과 마우스 회전에 따른 카메라 회전 기능이 스크립트로 제공되어 덕을 많이 보았습니다.
하루만 더 빨리 알았더라면..
애니메이션
캐릭터의 기본적인 자세는 다음과 같이 실제 게임 내에서 사용되는 애니메이션을 사용하였습니다.
확실히 제공된 애니메이션을 사용해보니 제가 만들려고 시도한 것보다 훨씬 자연스러워서 보기 좋았습니다.
다리 모션 구현
그러나 문제점이 있었습니다.
바로 제공되는 애니메이션의 수가 그리 많이 않았다는 것입니다.
제공되는 애니메이션은 위가 끝이었고, 움직임과 관련된 애니메이션은 밑의 앞으로 뛰는 애니메이션만 존재헀습니다.
제가 원하고자 하는 바는 캐릭터는 한 방향을 조준을 유지하되, 이동 방향에 맞추어 알맞은 다리 모션을 취하도록 하고 싶었습니다.
그래서 저는 언리얼 공부 중 알게된 믹사모라는 오토 리깅 사이트에서 필요한 애니메이션을 구하였습니다.
이 사이트에서는 다양한 애니메이션이 존재해서, 제가 원하는 애니메이션을 어렵지 않게 찾을 수 있었습니다.
그러나 위 사진을 보시면 아시겠지만, 믹사모 사이트는 캐릭터 모델을 업로드하면 이를 자동으로 리깅을 해주지만, 총기는 적용이 되지 않는다는 한계가 있었습니다.
.
.
곰곰이 생각을 해보았는데, 결국 제가 여분의 애니메이션을 구한 이유는 총구는 한곳을 유지하되 이동 방향에 따른 다리 모션을 구하기 위함이었습니다.
그래서 유니티에 아바타 마스크라는 것을 사용했습니다. 아바타 마스크는 애니메이션을 캐릭터의 특정 부위에만 적용시킬 수 있게 도와주는 역할을 합니다. 캐릭터의 상체 부분은 제공되는 애니메이션이 적용되고, 하체 부분은 제가 따로 구한 애니메이션이 적용되도록 하였습니다.
에임 상,하 구현
다음으로 제가 원하는 것은 카메라 상하 각도에 맞춰 캐릭터 조준 모션 또한 위아래로 변환되게끔 하고 싶었습니다.
유니티에 blend Tree 라고 언리얼 엔진에서의 blend space와 같은 역할을 가진 것이 있었습니다.
언리얼 엔진에서의 경험을 되살려 따로 구현하는데는 어렵지 않았습니다. 헛되지 않았구나
Pitch라는 float형 변수를 만들었고, 값은 Starter Assets에서 제공되는 카메라 스크립트에서 카메라 높이 값을 가져오게끔 하였습니다.
제공되는 기본 사격 자세에서 척추 뼈들의 회전값을 변경시켜 위, 중간, 아래를 조준하는 세가지 모션을 만들었습니다.
.
총기의 뼈대가 손 뼈대에 귀속되어 있는 것이 아닌, 캐릭터와 독립된 개체로서 존재했습니다…
위와 같이 총기 뼈대와 캐릭터 뼈대가 같은 부모를 가지고 있어, 애니메이션의 각 키프레임마다 총기의 회전 값은 물론, 위치 값까지 함께 수정해주어야 했습니다.
그렇다고 현재 총기의 뼈대를 수정하여 손에 귀속되도록 하고자 하였으나, 이 경우 기존에 제공되던 모든 애니메이션의 총기 뼈대 값을 다시 설정해야 했기에 번거로웠고, 총기 뼈대의 XYZ축도 이상하게 적용되어 있어, 단순히 총기가 앞으로 향하게 하는데도 XYZ 회전값을 모두 수정해주어야 했습니다..
이래도 문제, 저래도 문제, 제가 선택한 방법은 바로, 노가다.
아직 유니티에 대한 지식이 그리 많지 않던 저는 노가다를 통해 각각의 모션을 만들었습니다.
머리가 나쁘면 몸이 고생한다는데, 몸이 좋아서 다행입니다.
.
.
그 외에도, 조준 기능, 조준 시 카메라 줌인 등등 필요한 기능은 Starter Assets의 스크립트를 수정하여 구현하였습니다.
진행 상황 영상
Game_project
· 2024-08-20
-
호시노 애니메이션 (4)
여행을 다니는 동안 곰곰히 생각해보았습니다.
역시나 가장 큰 문제는 모델링. 모델링이 가장 힘든 작업이었는데, 과연 내가 다른 캐릭터들도 모델링을 할 수 있는가.
할 수는 있겠지만 시간이 너무 오래 걸릴 것 같다고 생각이 들었습니다.
그래서 생각해낸 점은 게임 어플 내에서 사용되는 3D 모델을 사용하는 것.
넥슨 게임 IP 사용가이드
이를 읽어보니, UGC ( 2차 창작물 )을 통해 금전적 이득을 얻으면 안되고, 비영리 목적을 위해 게임을 제작하고자 하는 경우 넥슨 고객센터에 개별적으로 문의를 넣으면 된다고 합니다.
저는 개인 공부, 심심풀이를 목적으로 게임을 제작하고, 이후 결과물을 배포하여 상업적으로 사용할 생각이 없기 때문에, 문의는 필요 없을 것 같습니다.
본격적으로 핸드폰을 데스크탑과 연결해서 블루아카이브 파일을 가져와서 모델링을 추출해보겠습니다.
찾아보니 이 과정에 Asset Studio라는 프로그램이 사용된다고 합니다.
https://github.com/Perfare/AssetStudio/releases
여기서 다운 받아주고, .bundle 형식의 파일을 가져와보았습니다.
텍스처 파일, mesh 파일, 애니메이션 파일들이 보이는데 추출을 진행하면,
이런 파일들이 나타났는데, .fbx 파일과, 텍스처 이미지 파일이 있습니다.
블렌더에서 먼저 .fbx 파일을 가져와서 실행해본 결과 정상적으로 작동되어, 언리얼 엔진으로 추출을 진행했습니다.
.
.
.
근데 문제가 있었는데, 추출을 진행하면 언리얼 엔진 프로그램 자체가 작동되질 않았습니다.
원인을 찾아봐도 도저히 알 수가 없었는데, 그래서 저는 유니티 엔진에서 .fbx 파일을 가져와 보았습니다.
결과는 성공 !
캐릭터 모델은 물론, 게임에서 사용되는 애니메이션 파일 또한 제대로 작동되는 것을 볼 수가 있었습니다.
.
.
아무래도 게임 제작을 유니티로 진행해야 할 것 같습니다..
다시 처음부터 배워야 한다는 점이 아쉽지만, 큰 틀은 언리얼 엔진과 비슷할 것 같고, 다른 캐릭터 모델링에 들어갈 시간을 생각하면 이 쪽이 더 나은 것 같습니다.
게임 구상
초기 기획으로는 오버워치와 같이 N vs N 형식의 3인칭 액션 슈팅 게임을 만들려고 했는데, 문제가 생겼습니다.
https://keyzard.org/devlib/tt/119
제가 하고자 한 바를 이미 먼저 하고 계신 분이 있었습니다.
끄응.. 3인칭 액션 슈팅이라는 장르는 버리고 싶지 않은데,,
차별화를 둘 필요가 있었습니다.
그래서 생각해낸 점이 3인칭 슈팅 게임 장르는 유지하되, 오버워치 형식의 N vs N이 아닌, PVE 형식의 디펜스 느낌으로 생각해보았습니다.
이는 제가 예전에 즐겨하던 팀포트리스2 라는 게임의 MVM (Mann VS. Machine) 모드를 참고하였습니다.
->MVM 나무위키<-
MVM 모드는 플레이어 vs AI의 PVE 형식의 모드이고, AI 측에서는 폭탄, 또는 수레를 기지의 특정 지점까지 옮기면 우승하게 되고, 플레이어 측에서는 이를 시간 안에 막아야하는 모드입니다.
디펜스 형식으로 매 턴마다 적들이 강해지며, 플레이어는 적을 처치함으로써 골드를 얻습니다.
매 턴이 끝나면 잠시 정비 시간이 있는데, 이 시간에 얻은 골드를 통해서 능력치 강화 또는 스킬을 구매하여 강해지는 방식입니다.
.
.
.
우선은 여기까지 생각해놓고, 내일부터 마음을 다잡고 유니티를 공부해보도록 하겠습니다…
Game_project
· 2024-08-10
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Kayoko AI with ChatGpt (2)
목표
카요코 보이스 기능 추가하기.
질문에 대한 답변의 텍스트를 딥러닝으로 학습한 카요코의 목소리가 출력될 것임.
구현 시작
준비
먼저 학습에 사용하기 위한 오디오 파일들을 준비할 것임.
데이터 처리하기 편하도록 이름을 voiceFile로 통합하여 저장하였다.
이제 학습에 사용할 데이터파일을 만들기 위해 ChatGpt한테 부탁하여 다음과 같은 코드를 만들었다.
이 코드를 실행하여
다음과 같은 텍스트 파일을 생성할 수 있었다.
모델 학습
엔비디아의 tacotron2 모델을 사용하여 학습을 진행하기로 했다.
tacotron2 링크
파일을 다운받고, tacotron2는 기본적으로 학습하는데 영어를 사용하기 때문에, japanese cleaner 따로 추가하여 코드를 수정하였다.
코드 수정 완료 후, 성공적으로 학습을 시작할 수 있게 되었다.
이 과정에서 수많은 오류를 직면하였다.
라이브러리의 버전이 바뀌면서 함수가 사라지거나 하는 등의 이유로 나는 6시간을 고군분투했다.
chatGpt의 도움이 없었다면 큰일날 뻔 했다.
샤워하고 오니 대략 이만큼 진행되었다.
보니까 컴퓨터 GPU 사용률이 90~100 % 사이를 왔다갔다하는데 글카의 비명소리가 들리는 것 같다.
체크포인트를 따로 설정하지 않아서 이게 잘 되고 있는지를 모르겠다…
학습된 모델을 가지고 검증을 시도했으나.. 결과는 처참했다. 그래서 학습할 데이터의 양을 더 늘리기로 했다.
카요코라는 캐릭터의 음성파일을 더 늘리기 위해 구글링 결과 카요코 asmr 1시간 짜리가 있었다.
이를 다운받고 음성 구간을 나누어 다수의 음성파일로 만들 것이다.
파이썬의 AudioSegment를 이용해서 음성 부분만 추출했는데, 결과가 만족스럽지 않아서 adobe media encoder를 통해서 asmr 음성 파일을 여러 구간으로 나눠준다. (손으로 직접)
추가한 wav 파일들을 학습에 사용할 txt 파일에 추가해준다..
총 486개의 5~10초 길이의 음성파일이 준비되었다.
다시 학습을 진행해보자..
.
.
.
.
.
2024-06-18
며칠간을 씨름하였으나 tacotron2 모델은 오래되기도 해서, 방법을 바꾸기로 했다.
https://github.com/coqui-ai/TTS
바로 coqui TTS의 기존에 학습된 모델에 파인 튜닝을 통해서 카요코의 목소리로 나타내기로 했다.
내일 다시 오도록 하겠다…
2024-06-20
다음 링크를 참고 해서, chatgpt의 도움을 받아 코드를 완성했다.
참고 링크
import os
from pydub import AudioSegment
from trainer import Trainer, TrainerArgs
import sys
OUT_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "run", "training")
# 현재 작업 디렉토리를 PYTHONPATH에 추가
current_dir = os.path.dirname(os.path.abspath(__file__))
tts_module_path = os.path.abspath(os.path.join(current_dir, '..', '..', '..'))
sys.path.append(tts_module_path)
from TTS.config.shared_configs import BaseDatasetConfig
from TTS.tts.datasets import load_tts_samples
from TTS.tts.layers.xtts.trainer.gpt_trainer import GPTArgs, GPTTrainer, GPTTrainerConfig, XttsAudioConfig
from TTS.utils.manage import ModelManager
RUN_NAME = "GPT_XTTS_v2.0_Japanese_FT"
PROJECT_NAME = "XTTS_trainer"
DASHBOARD_LOGGER = "tensorboard"
LOGGER_URI = None
OPTIMIZER_WD_ONLY_ON_WEIGHTS = True
START_WITH_EVAL = True
BATCH_SIZE = 3
GRAD_ACUMM_STEPS = 84
config_dataset = BaseDatasetConfig(
formatter="ljspeech",
dataset_name="custom_japanese_dataset",
path="C:/Users/aj200/Desktop/VS/Data/mine/MyTTSDataset/",
meta_file_train="C:/Users/aj200/Desktop/VS/Data/mine/MyTTSDataset/metadata.csv",
language="ja",
)
DATASETS_CONFIG_LIST = [config_dataset]
CHECKPOINTS_OUT_PATH = os.path.join(OUT_PATH, "XTTS_v2.0_original_model_files/")
os.makedirs(CHECKPOINTS_OUT_PATH, exist_ok=True)
DVAE_CHECKPOINT_LINK = "https://coqui.gateway.scarf.sh/hf-coqui/XTTS-v2/main/dvae.pth"
MEL_NORM_LINK = "https://coqui.gateway.scarf.sh/hf-coqui/XTTS-v2/main/mel_stats.pth"
DVAE_CHECKPOINT = os.path.join(CHECKPOINTS_OUT_PATH, os.path.basename(DVAE_CHECKPOINT_LINK))
MEL_NORM_FILE = os.path.join(CHECKPOINTS_OUT_PATH, os.path.basename(MEL_NORM_LINK))
if not os.path.isfile(DVAE_CHECKPOINT) or not os.path.isfile(MEL_NORM_FILE):
print(" > Downloading DVAE files!")
ModelManager._download_model_files([MEL_NORM_LINK, DVAE_CHECKPOINT_LINK], CHECKPOINTS_OUT_PATH, progress_bar=True)
TOKENIZER_FILE_LINK = "https://coqui.gateway.scarf.sh/hf-coqui/XTTS-v2/main/vocab.json"
XTTS_CHECKPOINT_LINK = "https://coqui.gateway.scarf.sh/hf-coqui/XTTS-v2/main/model.pth"
TOKENIZER_FILE = os.path.join(CHECKPOINTS_OUT_PATH, os.path.basename(TOKENIZER_FILE_LINK))
XTTS_CHECKPOINT = os.path.join(CHECKPOINTS_OUT_PATH, os.path.basename(XTTS_CHECKPOINT_LINK))
if not os.path.isfile(TOKENIZER_FILE) or not os.path.isfile(XTTS_CHECKPOINT):
print(" > Downloading XTTS v2.0 files!")
ModelManager._download_model_files(
[TOKENIZER_FILE_LINK, XTTS_CHECKPOINT_LINK], CHECKPOINTS_OUT_PATH, progress_bar=True
)
SPEAKER_REFERENCE = [
"C:/Users/aj200/Desktop/VS/Data/wavs/voiceFile0_0.wav",
"C:/Users/aj200/Desktop/VS/Data/wavs/voiceFile1_0.wav",
"C:/Users/aj200/Desktop/VS/Data/wavs/voiceFile22.wav",
"C:/Users/aj200/Desktop/VS/Data/wavs/voiceFile512_0.wav",
"C:/Users/aj200/Desktop/VS/Data/wavs/voiceFile82_0.wav",
"C:/Users/aj200/Desktop/VS/Data/wavs/voiceFile56_0.wav",
"C:/Users/aj200/Desktop/VS/Data/wavs/voiceFile97.wav"
]
LANGUAGE = config_dataset.language
# '。' 를 기준으로 텍스트를 나눠 문장의 길이를 줄임.
def split_text(text, delimiter='。'):
sentences = text.split(delimiter)
return [s + delimiter for s in sentences if s]
# 텍스트를 분할하는 기준에 맞춰 오디오도 같이 분할
def split_audio(audio_path, split_durations):
audio = AudioSegment.from_wav(audio_path)
segments = []
start = 0
for duration in split_durations:
end = start + duration
segments.append(audio[start:end])
start = end
return segments
# 텍스트와 오디오 파일을 분할하는 함수
def split_text_and_audio(sample, delimiter='。'):
text = sample['text']
audio_file = sample['audio_file']
speaker_name = sample['speaker_name']
language = sample['language']
split_texts = split_text(text, delimiter)
audio = AudioSegment.from_wav(audio_file)
total_duration = len(audio)
split_durations = [total_duration * len(t) / len(text) for t in split_texts]
split_audios = split_audio(audio_file, split_durations)
return [{'text': t, 'audio_file': audio_file.replace(".wav", f"_{i}.wav"), 'speaker_name': speaker_name, 'language': language}
for i, (t, a) in enumerate(zip(split_texts, split_audios))]
def main():
# 모델 파라미터 설정
model_args = GPTArgs(
max_conditioning_length=132300, # 6초
min_conditioning_length=66150, # 3초
debug_loading_failures=False,
max_wav_length=255995, # 약 11.6초
max_text_length=200,
mel_norm_file=MEL_NORM_FILE,
dvae_checkpoint=DVAE_CHECKPOINT,
xtts_checkpoint=XTTS_CHECKPOINT,
tokenizer_file=TOKENIZER_FILE,
gpt_num_audio_tokens=1026,
gpt_start_audio_token=1024,
gpt_stop_audio_token=1025,
gpt_use_masking_gt_prompt_approach=True,
gpt_use_perceiver_resampler=True,
)
# 오디오 설정 정의
audio_config = XttsAudioConfig(sample_rate=22050, dvae_sample_rate=22050, output_sample_rate=24000)
# 학습 파라미터 설정
config = GPTTrainerConfig(
output_path=OUT_PATH,
model_args=model_args,
run_name=RUN_NAME,
project_name=PROJECT_NAME,
run_description="GPT XTTS training",
dashboard_logger=DASHBOARD_LOGGER,
logger_uri=LOGGER_URI,
audio=audio_config,
batch_size=BATCH_SIZE,
batch_group_size=48,
eval_batch_size=BATCH_SIZE,
num_loader_workers=0, # 멀티프로세싱 비활성화
num_eval_loader_workers=0, # 멀티프로세싱 비활성화
eval_split_max_size=256,
print_step=100,
plot_step=100,
log_model_step=1000,
save_step=10000,
save_n_checkpoints=1,
save_checkpoints=True,
optimizer="AdamW",
optimizer_wd_only_on_weights=OPTIMIZER_WD_ONLY_ON_WEIGHTS,
optimizer_params={"betas": [0.9, 0.96], "eps": 1e-8, "weight_decay": 1e-2},
lr=5e-06,
lr_scheduler="MultiStepLR",
lr_scheduler_params={"milestones": [50000 * 18, 150000 * 18, 300000 * 18], "gamma": 0.5, "last_epoch": -1},
test_sentences=[
{
"text": "やあ、先生。今日はどうしたの?",
"speaker_wav": SPEAKER_REFERENCE,
"language": LANGUAGE,
},
{
"text": "うーん... 音楽のCDを集めるのが好きだよ。特にヘビーメタルとかね。先生も好きなものはあるの?",
"speaker_wav": SPEAKER_REFERENCE,
"language": LANGUAGE,
},
{
"text": "これくらいでいい?",
"speaker_wav": SPEAKER_REFERENCE,
"language": LANGUAGE,
},
{
"text": "なんか用?まあ、問題があったら解決してあげるけど。",
"speaker_wav": SPEAKER_REFERENCE,
"language": LANGUAGE,
},
{
"text": "耳かきって?そういう趣味はないけど、なんでそんなこと聞くの?",
"speaker_wav": SPEAKER_REFERENCE,
"language": LANGUAGE,
},
],
)
model = GPTTrainer.init_from_config(config)
try:
train_samples, eval_samples = load_tts_samples(config_dataset, eval_split = True, eval_split_max_size = config.eval_split_max_size, eval_split_size = config.eval_split_size,)
if len(train_samples) == 0:
raise ValueError("Training samples are empty.")
if len(eval_samples) == 0:
raise ValueError("Evaluation samples are empty.")
# 학습 시작
trainer = Trainer(
TrainerArgs(
restore_path = None,
skip_train_epoch = False,
start_with_eval = START_WITH_EVAL,
grad_accum_steps = GRAD_ACUMM_STEPS,
),
config,
output_path=OUT_PATH,
model=model,
train_samples=train_samples,
eval_samples=eval_samples,
)
trainer.fit()
except Exception as e:
print(f"Error during training: {e}")
if __name__ == "__main__":
main()
다음 오디오는 모델 평가 과정에서 생성된 음성이다.
Your browser does not support the audio element.
Your browser does not support the audio element.
이제 학습한 모델을 기존 코드에 합쳐준다..
import sys
import atexit
from PyQt5.QtWidgets import QMainWindow, QApplication, QTextEdit, QLineEdit, QVBoxLayout, QLabel, QPushButton
from PyQt5.QtGui import QPixmap, QFont, QFontDatabase
from PyQt5.QtCore import QTimer
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from bs4 import BeautifulSoup
import time
from selenium.webdriver.chrome.options import Options
import subprocess
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
import os
import torch
import torchaudio
from TTS.TTS.tts.configs.xtts_config import XttsConfig
from TTS.TTS.tts.models.xtts import Xtts
import sounddevice as sd
CONFIG_PATH = "TTS/voice/tts/custum_ja/config.json"
TOKENIZER_PATH = "TTS/voice/tts/XTTS_v2.0_original_model_files/vocab.json"
XTTS_CHECKPOINT = "TTS/voice/tts/custum_ja/checkpoint_9300.pth"
SPEAKER_REFERENCE = "TTS/voice/tts/custum_ja/voiceFile0.wav"
def play_kayoko_TTS(text):
out = model.inference(
text,
"ja",
gpt_cond_latent,
speaker_embedding,
temperature=0.7, # Add custom parameters here
)
audio_data = torch.tensor(out["wav"]).numpy()
volume_factor = 0.3 # For example, reduce volume to 50%
audio_data = audio_data * volume_factor
sd.play(audio_data, samplerate=24000)
# TTS
config = XttsConfig()
config.load_json(CONFIG_PATH)
model = Xtts.init_from_config(config)
model.load_checkpoint(config, checkpoint_path=XTTS_CHECKPOINT, vocab_path=TOKENIZER_PATH, use_deepspeed=False)
model.cuda()
gpt_cond_latent, speaker_embedding = model.get_conditioning_latents(audio_path=[SPEAKER_REFERENCE])
subprocess.Popen(r'C:\Program Files\Google\Chrome\Application\chrome.exe --remote-debugging-port=9222 --user-data-dir="C:\chromeCookie"')
option = Options()
option.add_experimental_option("debuggerAddress", "127.0.0.1:9222")
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=option)
# 웹사이트 접속
driver.get('https://chatgpt.com/g/g-1aLqNB1zW-kayoko')
chat_button_xpath = '//button[@class="mb-1 me-1 flex h-8 w-8 items-center justify-center rounded-full bg-black text-white transition-colors hover:opacity-70 focus-visible:outline-none focus-visible:outline-black disabled:bg-[#D7D7D7] disabled:text-[#f4f4f4] disabled:hover:opacity-100 dark:bg-white dark:text-black dark:focus-visible:outline-white disabled:dark:bg-token-text-quaternary dark:disabled:text-token-main-surface-secondary"]'
def addOneLenDiv():
lendiv = lendiv + 1
def input_Text(text):
inputarea = driver.find_element(By.ID, "prompt-textarea")
inputarea.send_keys(text)
driver.find_element("xpath",chat_button_xpath).click()
def get_latest_answer():
selector = ".markdown.prose.w-full.break-words.dark\:prose-invert.dark"
current_count = len(driver.find_elements(By.CSS_SELECTOR, selector))
WebDriverWait(driver, 20).until(
lambda x: len(x.find_elements(By.CSS_SELECTOR, selector)) > current_count
)
#time.sleep(4)
button = driver.find_element("xpath",chat_button_xpath)
while(button.is_enabled()):
pass
latest_answer_div = driver.find_elements(By.CSS_SELECTOR, selector)[-1]
p_text = latest_answer_div.find_elements(By.TAG_NAME, "p")
return p_text
def resource_path(relative_path):
try:
# PyInstaller creates a temp folder and stores path in _MEIPASS
base_path = sys._MEIPASS
except Exception:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
class ChatUI(QMainWindow):
def __init__(self):
super().__init__()
# Set the background image
self.initUI()
def initUI(self):
# Window settings
self.setGeometry(100, 100, 1280, 960)
self.setWindowTitle('Chat UI Example')
QFontDatabase.addApplicationFont('./GodoB.ttf')
# Set the background
self.backgorund_label = QLabel(self)
background_image_path = resource_path('img/kayoko.jpg')
self.backgorund_label.setPixmap(QPixmap(background_image_path))
self.backgorund_label.setScaledContents(True)
self.backgorund_label.setFixedSize(1280,960)
# Create the name area
self.nameArea = QLabel("카요코",self)
self.nameArea.setFixedSize(1000,70)
self.nameArea.setStyleSheet(
"background-color: rgba(255,255,255,0);"
"border-width: 0px 0px 3px 0px;"
"border-style: solid;"
"border-color: rgba(138,138,138,200);"
"color: white;"
)
self.nameArea.setFont(QFont('고도 B',40))
self.nameArea.move(140,575)
# Create the explaination area
self.explain = QTextEdit(self)
self.explain.setReadOnly(True)
self.explain.append("흥신소 68")
self.explain.setFixedSize(500,60)
self.explain.move(320,590)
self.explain.setStyleSheet(
"background-color: rgba(255,255,255,0);"
"border-style: solid;"
"border-color: rgba(255,255,255,0);"
"color: rgba(74,162,188,200);"
)
self.explain.setFont(QFont('고도 B',25))
# Create the answer area
self.answerArea = QTextEdit(self)
self.answerArea.setReadOnly(True)
self.answerArea.setLineWrapMode(1)
self.answerArea.setLineWrapColumnOrWidth(1)
self.answerArea.setStyleSheet(
"background-color: rgba(255,255,255,0);"
"border-color: rgba(255,255,255,0);"
"border-style: solid;"
"font-weight: bold;"
"color: white;"
)
self.answerArea.setFixedSize(1000,220)
self.answerArea.move(140,660)
self.answerArea.setFont(QFont('고도 B',25))
# Create the chat display area
self.chat_display = QTextEdit(self)
self.chat_display.setReadOnly(True)
self.chat_display.setStyleSheet(
"background-color: qLineargradient(y1: 0, y2: 1, stop: 0 rgba(20, 27, 59, 0), stop: 0.1 rgba(20, 27, 59, 160));"
"border-color: rgba(255,255,255,0);"
"border-style: solid;"
"font-weight: bold;"
"font-size: 20px;"
"color: rgba(255,255,255,200);"
)
self.chat_display.setFixedSize(1280,450)
self.chat_display.move(0,510)
# Create the input text box
self.text_input = QLineEdit(self)
self.text_input.setStyleSheet(
"background-color: rgba(255, 255, 255, 200);"
"border-color: rgba(255,255,255,0);"
"border-radius: 20px;"
)
self.text_input.returnPressed.connect(self.on_enter)
self.text_input.setFixedSize(1000,60)
self.text_input.move(140,880)
self.text_input.setFont(QFont('고도 B', 25))
self.text_input.setTextMargins(20,0,0,0)
layout = QVBoxLayout()
layout.addWidget(self.nameArea)
layout.addWidget(self.text_input)
layout.addWidget(self.chat_display)
layout.addWidget(self.answerArea)
layout.addWidget(self.explain)
self.chat_display.lower()
self.backgorund_label.lower()
self.setLayout(layout)
self.show()
def show_Answer(self):
answer_arr = get_latest_answer()
answer = answer_arr[0].text
ja_answer = answer_arr[1].text
play_kayoko_TTS(ja_answer)
#answer = get_latest_answer()
self.answerArea.setText('')
self.text = answer
self.idx = 0
self.timer = QTimer(self)
self.timer.timeout.connect(self.typeLetter)
self.timer.start(80)
self.text_input.clear()
def typeLetter(self):
if self.idx < len(self.text):
self.answerArea.insertPlainText(self.text[self.idx])
self.idx += 1
else:
self.timer.stop()
def on_enter(self):
# Get text from input box and display it in the chat display area
input_textAPP = self.text_input.text()
input_Text(input_textAPP)
self.show_Answer()
# 상태 확인 간격
def closeEvent(self, event):
# 윈도우가 닫힐 때 웹드라이버 종료
driver.quit()
event.accept()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = ChatUI()
sys.exit(app.exec_())
# Register the driver.quit function to be called on program exit
atexit.register(driver.quit)
생각보다 음성의 품질이 좋지 않아서 만족스럽지가 않다.
따로 방법을 찾아야겠다..
-
-
Touch background to close