|
import os |
|
import sys |
|
import subprocess |
|
import tkinter as tk |
|
from tkinter import messagebox |
|
import datetime |
|
|
|
# フォルダパスの設定 |
|
ASSETS_DIR = os.path.join("misc", "assets") |
|
OUTPUT_DIR = os.path.join("misc", "output") |
|
|
|
# 必須ライブラリのリスト |
|
REQUIRED_LIBRARIES = [ |
|
("speech_recognition", "speechrecognition"), |
|
("pyperclip", "pyperclip"), |
|
("pyaudio", "pyaudio") |
|
] |
|
|
|
def create_directories(): |
|
"""必要なディレクトリを作成する""" |
|
os.makedirs(ASSETS_DIR, exist_ok=True) |
|
os.makedirs(OUTPUT_DIR, exist_ok=True) |
|
|
|
def check_and_install_libraries(): |
|
"""必須ライブラリの確認とインストールを行う""" |
|
for import_name, package_name in REQUIRED_LIBRARIES: |
|
try: |
|
__import__(import_name) |
|
except ImportError: |
|
print(f"{package_name} がインストールされていません。インストールを開始します...") |
|
try: |
|
subprocess.check_call([sys.executable, "-m", "pip", "install", package_name]) |
|
print(f"{package_name} のインストールが完了しました。") |
|
except subprocess.CalledProcessError as e: |
|
print(f"{package_name} のインストールに失敗しました: {str(e)}") |
|
messagebox.showerror("エラー", f"{package_name} のインストールに失敗しました。手動でインストールしてください。") |
|
sys.exit(1) |
|
|
|
# ディレクトリ作成 |
|
create_directories() |
|
|
|
# ライブラリの確認とインストールを実行 |
|
check_and_install_libraries() |
|
|
|
# 必要なライブラリをインポート |
|
import speech_recognition as sr |
|
import pyperclip |
|
|
|
class SpeechRecognitionApp: |
|
def __init__(self, root): |
|
self.root = root |
|
self.root.title("音声認識アプリ") |
|
self.recognizer = sr.Recognizer() |
|
self.setup_ui() |
|
|
|
def setup_ui(self): |
|
# メインフレーム |
|
main_frame = tk.Frame(self.root, padx=10, pady=10) |
|
main_frame.pack(fill=tk.BOTH, expand=True) |
|
|
|
# 操作ボタンエリア |
|
button_frame = tk.Frame(main_frame) |
|
button_frame.pack(fill=tk.X, pady=5) |
|
|
|
# マイクボタン(美しいボタンにする) |
|
self.mic_icon = tk.PhotoImage(file=os.path.join(ASSETS_DIR, "mic_icon.png")).subsample(4, 4) |
|
self.mic_button = tk.Button(button_frame, image=self.mic_icon, |
|
command=self.start_recognition, |
|
bd=0, highlightthickness=0, relief=tk.FLAT) |
|
self.mic_button.pack(side=tk.LEFT, padx=5) |
|
|
|
# キャンセルボタン(美しいボタンにする) |
|
self.cancel_icon = tk.PhotoImage(file=os.path.join(ASSETS_DIR, "cancel_icon.png")).subsample(4, 4) |
|
self.cancel_button = tk.Button(button_frame, image=self.cancel_icon, |
|
command=self.stop_recognition, |
|
bd=0, highlightthickness=0, relief=tk.FLAT) |
|
self.cancel_button.pack(side=tk.LEFT, padx=5) |
|
|
|
# 言語選択 |
|
lang_frame = tk.Frame(main_frame) |
|
lang_frame.pack(fill=tk.X, pady=5) |
|
self.lang_var = tk.StringVar(value="ja-JP") |
|
tk.Radiobutton(lang_frame, text="日本語", variable=self.lang_var, value="ja-JP").pack(side=tk.LEFT) |
|
tk.Radiobutton(lang_frame, text="英語", variable=self.lang_var, value="en-US").pack(side=tk.LEFT) |
|
# 追加言語 |
|
tk.Radiobutton(lang_frame, text="中国語", variable=self.lang_var, value="zh-CN").pack(side=tk.LEFT) |
|
tk.Radiobutton(lang_frame, text="スペイン語", variable=self.lang_var, value="es-ES").pack(side=tk.LEFT) |
|
tk.Radiobutton(lang_frame, text="ヒンディー語", variable=self.lang_var, value="hi-IN").pack(side=tk.LEFT) |
|
tk.Radiobutton(lang_frame, text="アラビア語", variable=self.lang_var, value="ar-SA").pack(side=tk.LEFT) |
|
tk.Radiobutton(lang_frame, text="ポルトガル語", variable=self.lang_var, value="pt-BR").pack(side=tk.LEFT) |
|
tk.Radiobutton(lang_frame, text="ロシア語", variable=self.lang_var, value="ru-RU").pack(side=tk.LEFT) |
|
tk.Radiobutton(lang_frame, text="フランス語", variable=self.lang_var, value="fr-FR").pack(side=tk.LEFT) |
|
|
|
# テキスト表示エリア |
|
self.text_area = tk.Text(main_frame, height=10, wrap=tk.WORD) |
|
self.text_area.pack(fill=tk.BOTH, expand=True, pady=5) |
|
|
|
# ログ表示エリア |
|
self.log_area = tk.Text(main_frame, height=5, wrap=tk.WORD, state=tk.DISABLED) |
|
self.log_area.pack(fill=tk.BOTH, expand=True, pady=5) |
|
|
|
# 下部ボタンエリア |
|
bottom_frame = tk.Frame(main_frame) |
|
bottom_frame.pack(fill=tk.X, pady=5) |
|
|
|
# クリアボタン |
|
self.clear_text_button = tk.Button(bottom_frame, text="テキストをクリア", |
|
command=self.clear_text) |
|
self.clear_text_button.pack(side=tk.LEFT, padx=5) |
|
|
|
self.clear_log_button = tk.Button(bottom_frame, text="ログをクリア", |
|
command=self.clear_log) |
|
self.clear_log_button.pack(side=tk.LEFT, padx=5) |
|
|
|
# コピーボタン |
|
self.copy_button = tk.Button(bottom_frame, text="クリップボードにコピー", command=self.copy_to_clipboard) |
|
self.copy_button.pack(side=tk.LEFT, padx=5) |
|
|
|
# 保存ボタン |
|
self.save_button = tk.Button(bottom_frame, text="ファイルに保存", command=self.save_to_file) |
|
self.save_button.pack(side=tk.LEFT, padx=5) |
|
|
|
# 終了ボタン |
|
self.exit_button = tk.Button(bottom_frame, text="終了", command=self.exit_app) |
|
self.exit_button.pack(side=tk.RIGHT) |
|
|
|
def start_recognition(self): |
|
# 別スレッドで音声認識を開始 |
|
import threading |
|
threading.Thread(target=self._recognition_thread, daemon=True).start() |
|
|
|
def _recognition_thread(self): |
|
self.update_log("音声認識を開始しました") |
|
self.mic_button.config(state=tk.DISABLED) # ボタンを無効化 |
|
|
|
try: |
|
with sr.Microphone() as source: |
|
self.recognizer.adjust_for_ambient_noise(source) |
|
self.update_log("ノイズ調整完了。話してください...") |
|
|
|
# 音声認識の設定を調整 |
|
self.recognizer.pause_threshold = 3 # 3秒の沈黙で終了 |
|
self.recognizer.phrase_time_limit = 90 # 最大90秒間の音声認識 |
|
audio = self.recognizer.listen(source) |
|
self.update_log("音声を認識中...") |
|
|
|
selected_lang = self.lang_var.get() |
|
text = self.recognizer.recognize_google(audio, language=selected_lang) |
|
|
|
self.text_area.insert(tk.END, text + "\n") |
|
self.update_log(f"認識結果: {text}") |
|
pyperclip.copy(text) |
|
self.update_log("クリップボードにコピーしました") |
|
|
|
except sr.WaitTimeoutError: |
|
self.update_log("タイムアウト: 音声が検出されませんでした") |
|
except sr.UnknownValueError: |
|
self.update_log("エラー: 音声を認識できませんでした") |
|
except sr.RequestError as e: |
|
self.update_log(f"エラー: 音声認識サービスに接続できませんでした - {str(e)}") |
|
except Exception as e: |
|
self.update_log(f"予期せぬエラーが発生しました: {str(e)}") |
|
finally: |
|
self.mic_button.config(state=tk.NORMAL) # ボタンを有効化 |
|
|
|
def stop_recognition(self): |
|
self.update_log("音声認識を停止しました") |
|
# ここに音声認識を停止する処理を追加 |
|
# (現状のspeech_recognitionライブラリでは強制停止機能がないため、 |
|
# 主にユーザーへのフィードバックとして使用) |
|
|
|
def update_log(self, message): |
|
# ログエリアにメッセージを追加 |
|
self.log_area.config(state=tk.NORMAL) |
|
self.log_area.insert(tk.END, f"[{datetime.datetime.now().strftime('%H:%M:%S')}] {message}\n") |
|
self.log_area.config(state=tk.DISABLED) |
|
self.log_area.see(tk.END) # 最新のログを表示 |
|
|
|
def copy_to_clipboard(self): |
|
# テキストエリアの内容を取得 |
|
text = self.text_area.get("1.0", tk.END).strip() |
|
|
|
if text: |
|
# クリップボードにコピー |
|
pyperclip.copy(text) |
|
self.update_log("クリップボードにコピーしました") |
|
messagebox.showinfo("成功", "クリップボードにコピーしました") |
|
else: |
|
self.update_log("コピーするテキストがありません") |
|
messagebox.showwarning("警告", "コピーするテキストがありません") |
|
|
|
def save_to_file(self): |
|
# テキストエリアの内容を取得 |
|
text = self.text_area.get("1.0", tk.END).strip() |
|
|
|
if text: |
|
try: |
|
# ファイル名をタイムスタンプで生成 |
|
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") |
|
filename = os.path.join(OUTPUT_DIR, f"speech_output_{timestamp}.txt") |
|
|
|
# ファイルに保存 |
|
with open(filename, "w", encoding="utf-8") as f: |
|
f.write(text) |
|
|
|
self.update_log(f"ファイルに保存しました: {filename}") |
|
messagebox.showinfo("成功", f"ファイルに保存しました: {filename}") |
|
except Exception as e: |
|
self.update_log(f"ファイル保存エラー: {str(e)}") |
|
messagebox.showerror("エラー", f"ファイル保存に失敗しました: {str(e)}") |
|
else: |
|
self.update_log("保存するテキストがありません") |
|
messagebox.showwarning("警告", "保存するテキストがありません") |
|
|
|
def exit_app(self): |
|
# 終了確認 |
|
if messagebox.askokcancel("終了", "本当に終了しますか?"): |
|
self.root.quit() |
|
|
|
def clear_text(self): |
|
self.text_area.delete("1.0", tk.END) |
|
self.update_log("テキストをクリアしました") |
|
|
|
def clear_log(self): |
|
self.log_area.config(state=tk.NORMAL) |
|
self.log_area.delete("1.0", tk.END) |
|
self.log_area.config(state=tk.DISABLED) |
|
self.update_log("ログをクリアしました") |
|
|
|
if __name__ == "__main__": |
|
root = tk.Tk() |
|
app = SpeechRecognitionApp(root) |
|
root.mainloop() |
mic_icon.pngとcancel_icon.pngが必要。