Files
jarvis/archive/test_jarvis.py
2026-09-01 01:25:09 +09:00

311 lines
10 KiB
Python

#!/usr/bin/env python3
"""자비스 원격 테스트 — 볼륨 키우고 스피커/마이크/제미나이 확인.
사용:
source ~/jarvis_env/bin/activate
python ~/test_jarvis.py # 전체 테스트 (큰 소리 재생)
python ~/test_jarvis.py --volume 100 # 볼륨 %
python ~/test_jarvis.py --speak-only # 스피커만
python ~/test_jarvis.py --mic-only # 마이크 레벨만
python ~/test_jarvis.py --gemini-only
"""
from __future__ import annotations
import argparse
import os
import subprocess
import sys
import time
from ctypes import CFUNCTYPE, c_char_p, c_int, cdll
os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "hide"
ERROR_HANDLER_FUNC = CFUNCTYPE(None, c_char_p, c_int, c_char_p, c_int, c_char_p)
def _py_error_handler(filename, line, function, err, fmt):
pass
try:
asound = cdll.LoadLibrary("libasound.so.2")
asound.snd_lib_error_set_handler(ERROR_HANDLER_FUNC(_py_error_handler))
except Exception:
pass
def set_volume(percent: int) -> None:
"""시스템 스피커 볼륨을 percent%로 맞춤 (여러 방식 시도)."""
percent = max(0, min(150, int(percent)))
print(f"\n[볼륨] {percent}% 로 설정 시도...")
cmds = [
["wpctl", "set-volume", "@DEFAULT_AUDIO_SINK@", f"{percent / 100:.2f}"],
["pactl", "set-sink-volume", "@DEFAULT_SINK@", f"{percent}%"],
["amixer", "-q", "sset", "Master", f"{min(percent, 100)}%"],
["amixer", "-q", "sset", "PCM", f"{min(percent, 100)}%"],
["amixer", "-c", "0", "-q", "sset", "Headphone", f"{min(percent, 100)}%"],
]
ok = False
for cmd in cmds:
try:
r = subprocess.run(cmd, capture_output=True, text=True, timeout=5)
if r.returncode == 0:
print(f" OK: {' '.join(cmd)}")
ok = True
except (FileNotFoundError, subprocess.TimeoutExpired):
pass
# 음소거 해제
for cmd in (
["wpctl", "set-mute", "@DEFAULT_AUDIO_SINK@", "0"],
["pactl", "set-sink-mute", "@DEFAULT_SINK@", "0"],
["amixer", "-q", "sset", "Master", "unmute"],
):
try:
subprocess.run(cmd, capture_output=True, timeout=5)
except (FileNotFoundError, subprocess.TimeoutExpired):
pass
if not ok:
print(" 경고: 볼륨 명령을 적용하지 못했습니다. 재생은 시도합니다.")
def show_volume() -> None:
for cmd in (
["wpctl", "get-volume", "@DEFAULT_AUDIO_SINK@"],
["pactl", "get-sink-volume", "@DEFAULT_SINK@"],
["amixer", "sget", "Master"],
):
try:
r = subprocess.run(cmd, capture_output=True, text=True, timeout=5)
if r.returncode == 0 and r.stdout.strip():
print(f" 현재: {r.stdout.strip().splitlines()[0]}")
return
except (FileNotFoundError, subprocess.TimeoutExpired):
pass
def list_devices() -> None:
import pyaudio
pa = pyaudio.PyAudio()
print("\n[오디오 장치]")
for i in range(pa.get_device_count()):
d = pa.get_device_info_by_index(i)
if d["maxInputChannels"] > 0 or d["maxOutputChannels"] > 0:
print(
f" {i}: in={d['maxInputChannels']} out={d['maxOutputChannels']} "
f"{d['name']}"
)
pa.terminate()
def load_api_key() -> str:
key = os.environ.get("GOOGLE_API_KEY", "")
if key and "여기에_" not in key:
return key
# jarvis.py 기본값에서 읽기 (이미 키가 들어있는 경우)
jarvis = os.path.join(os.path.dirname(os.path.abspath(__file__)), "jarvis.py")
if os.path.isfile(jarvis):
text = open(jarvis, encoding="utf-8").read()
import re
m = re.search(
r'GOOGLE_API_KEY = os\.environ\.get\(\s*"GOOGLE_API_KEY",\s*"([^"]+)"\s*\)',
text,
)
if not m:
m = re.search(
r'os\.environ\.get\(\s*"GOOGLE_API_KEY",\s*\n\s*"([^"]+)"',
text,
)
if m and "여기에_" not in m.group(1):
return m.group(1)
raise SystemExit("GOOGLE_API_KEY 를 찾을 수 없습니다. jarvis.py 에 키를 넣으세요.")
def test_speak(volume_pygame: float = 1.0) -> None:
from gtts import gTTS
import pygame
print("\n[스피커] TTS 재생 — 카메라로 들리는지 확인해 보세요.")
msg = (
"안녕하세요. 자비스 스피커 테스트입니다. "
"지금 소리가 크게 나고 있습니다. 하나, 둘, 셋."
)
path = "/tmp/jarvis_test_reply.mp3"
gTTS(text=msg, lang="ko").save(path)
pygame.mixer.init()
pygame.mixer.music.set_volume(max(0.0, min(1.0, volume_pygame)))
pygame.mixer.music.load(path)
pygame.mixer.music.play()
while pygame.mixer.music.get_busy():
time.sleep(0.1)
pygame.mixer.music.unload()
print(" 재생 완료.")
def test_beep() -> None:
"""네트워크 없이도 스피커 확인용 짧은 비프."""
print("\n[스피커] 비프음 재생...")
# sox/aplay 또는 speaker-test
for cmd in (
["speaker-test", "-t", "sine", "-f", "880", "-l", "1", "-c", "2"],
[
"bash",
"-c",
"ffmpeg -f lavfi -i 'sine=frequency=880:duration=1' -f wav - "
"| aplay -q 2>/dev/null",
],
):
try:
r = subprocess.run(cmd, capture_output=True, timeout=8)
if r.returncode == 0:
print(" 비프 완료.")
return
except (FileNotFoundError, subprocess.TimeoutExpired):
pass
print(" 비프 도구 없음 — TTS만 사용합니다.")
def test_mic(seconds: float = 3.0, mic_index: int | None = None) -> None:
import numpy as np
import pyaudio
print(f"\n[마이크] {seconds:.0f}초간 입력 레벨 측정 (밖에 있어도 OK)...")
pa = pyaudio.PyAudio()
candidates: list[int | None] = []
if mic_index is not None:
candidates.append(mic_index)
else:
for i in range(pa.get_device_count()):
d = pa.get_device_info_by_index(i)
if d["maxInputChannels"] <= 0:
continue
name = d["name"]
if "pulse" in name or name == "default":
candidates.append(i)
elif "USB" in name:
candidates.append(i)
candidates.append(None) # 시스템 기본
stream = None
rate = 16000
used_index: int | None = None
for idx in candidates:
for try_rate in (16000, 44100, 48000):
kwargs = dict(
format=pyaudio.paInt16,
channels=1,
rate=try_rate,
input=True,
frames_per_buffer=1024,
)
if idx is not None:
kwargs["input_device_index"] = idx
try:
stream = pa.open(**kwargs)
rate = try_rate
used_index = idx
name = (
pa.get_device_info_by_index(idx)["name"]
if idx is not None
else "default"
)
print(f" 열림: index={idx} ({name}) rate={rate}")
break
except OSError:
continue
if stream is not None:
break
if stream is None:
pa.terminate()
print(" → 마이크를 열 수 없습니다.")
return
peaks = []
n = max(1, int(rate / 1024 * seconds))
for _ in range(n):
data = stream.read(1024, exception_on_overflow=False)
arr = np.frombuffer(data, dtype=np.int16).astype(np.float32)
peaks.append(float(np.max(np.abs(arr))))
stream.stop_stream()
stream.close()
pa.terminate()
peak = max(peaks) if peaks else 0
avg = sum(peaks) / len(peaks) if peaks else 0
print(f" peak={peak:.0f} / 32767 avg={avg:.0f} (device={used_index})")
if peak < 200:
print(" → 거의 무음. 마이크 연결·권한·MIC_INDEX 확인 필요.")
elif peak < 2000:
print(" → 입력은 있으나 작음. 마이크 게인 올려보세요.")
else:
print(" → 마이크 입력 정상으로 보입니다.")
def test_gemini() -> None:
import google.generativeai as genai
print("\n[제미나이] 짧은 텍스트 질의...")
key = load_api_key()
genai.configure(api_key=key)
model_name = os.environ.get("GEMINI_MODEL", "gemini-2.0-flash")
model = genai.GenerativeModel(model_name)
try:
r = model.generate_content("한 문장으로만 답해: 너는 누구야?")
print(f" 모델={model_name}")
print(f" 답: {(r.text or '').strip()}")
except Exception as e:
print(f" 실패: {e}")
print(" → 모델 이름을 gemini-1.5-flash 등으로 바꿔보세요.")
def main() -> None:
p = argparse.ArgumentParser(description="자비스 원격 테스트")
p.add_argument("--volume", type=int, default=100, help="시스템 볼륨 %% (기본 100)")
p.add_argument("--speak-only", action="store_true")
p.add_argument("--mic-only", action="store_true")
p.add_argument("--gemini-only", action="store_true")
p.add_argument("--mic-index", type=int, default=None)
p.add_argument("--no-beep", action="store_true")
args = p.parse_args()
print("=== 자비스 원격 테스트 ===")
set_volume(args.volume)
show_volume()
only = args.speak_only or args.mic_only or args.gemini_only
if not only:
list_devices()
if args.mic_only:
test_mic(mic_index=args.mic_index)
return
if args.gemini_only:
test_gemini()
return
if args.speak_only:
if not args.no_beep:
test_beep()
test_speak(volume_pygame=1.0)
return
if not args.no_beep:
test_beep()
test_speak(volume_pygame=1.0)
test_mic(mic_index=args.mic_index)
test_gemini()
print("\n=== 끝 ===")
print("카메라로 TTS/비프가 들렸는지 확인해 주세요.")
print("다시 크게: python ~/test_jarvis.py --speak-only --volume 100")
if __name__ == "__main__":
main()