環境
Ubuntu 25.10 on WSL2 UV 0.9.8 Python 3.12.12
実行環境構築
sudo apt install build-essential sudo apt install libsndfile1
uv venv --python 3.12 uv add omnilingual-asr
これだけで環境構築が出来ました。
実行
音声はこちらから男性ナレーション、医療WEBドラマ医師役の音声をダウンロードさせて頂きました。
Wisperを使った時と同じものです。
from omnilingual_asr.models.inference.pipeline import ASRInferencePipeline pipeline = ASRInferencePipeline(model_card="omniASR_LLM_7B") transcriptions = pipeline.transcribe(["./sample.mp3"], batch_size=1) print(transcriptions[0])
結果
血管収縮には血管閉活菌細胞内のカルシウムノ ドが関与しており 血管閉活菌細胞内へのカルシウム流入をブロックすれば 血管収縮を抑制できます しかし近年 カルシウム流入とは独立した経路としてロックが見出され 血管収縮に深く関与していることが明らかになりました
専門用語には弱いようです。
PySide6でGUI作成
こちらと同じGUIを作りました。
Ubuntu 25.10でPySide6を使おうとしたのですがうまくいきませんでした。
環境
Ubuntu 24.04 on WSL2 uv 0.9.9 Python 3.12.12
実行環境構築
sudo apt install libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render0 libxcb-shape0 libxcb-xfixes0 libxkbcommon-x11-0 libxcb-cursor0 # 1. Notoフォント(日本語)をインストール sudo apt install -y fonts-noto-cjk # 2. フォントキャッシュを強制的に更新 fc-cache -fv
uv venv --python 3.12 uv add omnilingual-asr uv add pyside6
Pythonコード
import sys import os from pathlib import Path from datetime import datetime from queue import Queue from threading import Thread from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget, QTextEdit, QLabel from PySide6.QtCore import QUrl, Signal, QObject, QTimer from PySide6.QtGui import QFont from PySide6.QtMultimedia import QMediaRecorder, QMediaCaptureSession, QAudioInput, QMediaFormat from omnilingual_asr.models.inference.pipeline import ASRInferencePipeline # --- omnilingual-asr モデルのロード --- # アプリケーション起動時にモデルをロードします。 # これには時間がかかる場合があります。 try: print("ASRモデルをロード中... これには時間がかかる場合があります。") pipeline = ASRInferencePipeline(model_card="omniASR_LLM_7B") print("モデルのロードが完了しました。") except Exception as e: print(f"モデルのロード中に重大なエラーが発生しました: {e}") print("アプリケーションを終了します。") sys.exit() # ------------------------------------- class TranscriptionSignals(QObject): """ 文字起こしスレッドからメインGUIスレッドへシグナルを送信するためのクラス """ transcription_done = Signal(str) error_occurred = Signal(str) class AudioTranscriptionApp(QMainWindow): """ メインのアプリケーションウィンドウクラス """ def __init__(self): super().__init__() self.setWindowTitle("音声録音と自動文字起こし (omnilingual-asr)") self.setGeometry(100, 100, 400, 300) layout = QVBoxLayout() # --- GUIコンポーネント --- self.record_button = QPushButton("録音開始") self.record_button.clicked.connect(self.toggle_recording) layout.addWidget(self.record_button) self.status_label = QLabel("待機中") layout.addWidget(self.status_label) self.text_edit = QTextEdit() self.text_edit.setReadOnly(True) # 結果表示用 layout.addWidget(self.text_edit) container = QWidget() container.setLayout(layout) self.setCentralWidget(container) # --- メディアコンポーネントの設定 --- self.audio_input = QAudioInput() self.media_recorder = QMediaRecorder() self.capture_session = QMediaCaptureSession() self.capture_session.setAudioInput(self.audio_input) self.capture_session.setRecorder(self.media_recorder) # 録音フォーマットの設定 (WAV) audio_settings = self.media_recorder.mediaFormat() audio_settings.setFileFormat(QMediaFormat.FileFormat.Wave) audio_settings.setAudioCodec(QMediaFormat.AudioCodec.Wave) self.media_recorder.setMediaFormat(audio_settings) self.media_recorder.setQuality(QMediaRecorder.Quality.HighQuality) self.is_recording = False self.temp_file = None # --- 文字起こしスレッドの設定 --- self.audio_queue = Queue() self.transcription_thread = Thread(target=self.transcribe_audio_thread, daemon=True) self.transcription_thread.start() # --- シグナルの設定 --- self.signals = TranscriptionSignals() self.signals.transcription_done.connect(self.update_transcription) self.signals.error_occurred.connect(self.show_error) # メディアレコーダーのシグナル接続 self.media_recorder.recorderStateChanged.connect(self.on_recorder_state_changed) def toggle_recording(self): """ 録音開始/停止ボタンが押されたときの処理 """ if not self.is_recording: self.start_recording() else: self.stop_recording() def start_recording(self): """ 録音を開始する """ try: now = datetime.now() current_time = now.strftime("%Y%m%d_%H%M%S") current_directory = Path.cwd() # 一時ファイル名を生成 self.temp_file = Path(current_directory, f"temp_rec_{current_time}.wav") self.media_recorder.setOutputLocation(QUrl.fromLocalFile(str(self.temp_file))) self.media_recorder.record() self.is_recording = True self.record_button.setText("録音停止") self.status_label.setText("録音中...") except Exception as e: self.show_error(f"録音開始エラー: {e}") def stop_recording(self): """ 録音を停止する """ if self.is_recording: self.media_recorder.stop() self.is_recording = False self.record_button.setText("録音開始") self.status_label.setText("録音停止中...") def on_recorder_state_changed(self, state): """ レコーダーの状態が変化したときの処理 """ if state == QMediaRecorder.StoppedState: # ファイルが完全に書き込まれるのを少し待つ QTimer.singleShot(500, self.process_recorded_file) def process_recorded_file(self): """ 録音されたファイルを処理(キューに追加)する """ if self.temp_file and os.path.exists(self.temp_file): self.status_label.setText("文字起こし中...") # スレッドにファイルパスを渡す self.audio_queue.put(str(self.temp_file)) self.temp_file = None # 処理が終わったのでリセット else: if self.temp_file: # ファイルパスはあるのにファイルが存在しない場合 self.signals.error_occurred.emit(f"録音ファイルが見つかりません: {self.temp_file}") # 録音されなかった場合 (stopが押されたがファイルが生成されなかった等) self.status_label.setText("待機中") def transcribe_audio_thread(self): """ 文字起こしを実行するワーカースレッド """ while True: audio_file = self.audio_queue.get() # キューからファイルパスを取得 (ブロッキング) try: # --- omnilingual-asr を使用した文字起こし --- transcriptions = pipeline.transcribe( [audio_file], lang=["jpn_Jpan"], batch_size=1 ) # --------------------------------------------- if transcriptions: # 成功シグナルを送信 self.signals.transcription_done.emit(transcriptions[0]) else: self.signals.error_occurred.emit("文字起こしが空の結果を返しました。") except Exception as e: # エラーシグナルを送信 self.signals.error_occurred.emit(f"文字起こし中にエラーが発生しました: {str(e)}") finally: # 処理が完了したらタスク完了を通知 self.audio_queue.task_done() # 一時ファイルを削除 try: if os.path.exists(audio_file): os.remove(audio_file) except OSError as e: print(f"一時ファイル {audio_file} の削除に失敗しました: {e}") def update_transcription(self, text): """ 文字起こし結果をGUIに反映する (メインスレッドで実行) """ self.text_edit.append(text) self.status_label.setText("待機中") def show_error(self, error_message): """ エラーメッセージをGUIに反映する (メインスレッドで実行) """ self.status_label.setText("エラー") self.text_edit.append(f"エラー: {error_message}") # エラー後も待機状態に戻す QTimer.singleShot(3000, lambda: self.status_label.setText("待機中")) if __name__ == "__main__": app = QApplication(sys.argv) window = AudioTranscriptionApp() window.show() sys.exit(app.exec())