👌

高精度・低コストで実務に即した議事録を生成するPythonプログラム

2024/06/27に公開

議事録生成Pythonプログラム

はじめに

議事録作成にかかる時間と労力を大幅に削減しませんか?
今回ご紹介するPythonプログラムは、音声ファイルから高精度な議事録を自動生成します。定例会議や特定のフォーマットに合わせた議事録も簡単に作成可能です。
さらに、1回の使用コストは1時間の会議なら100円以内と、他の有料ソリューションに比べてコストパフォーマンスに優れています。

必須

OPENAIのAPIキーを持っていること

モチベーション

会社でOffice Copilotなどの議事要旨作成ソリューションを導入してくれない。
→ 幸いOPENAI_APIキーの契約を持っていたので
Teams会議の文字起こしを見ていると要約の精度も限界がありそう。
→ プロンプトをこちらで指定することで使い物になるようにする

主な特徴

プロンプトのカスタマイズ:

  • 特定のフォーマットに合わせた議事録出力が可能。
  • 定例会議用に、通常の開始時間や参加者をプロンプトで指定することで、実務に即した議事録を生成。

高精度な文字起こし:

  • 音声ファイルをMP3に変換し、音声を高精度に文字起こし。
  • 分割された音声ファイルにも対応し、長時間の会議でも問題なく処理。

コスト効率:

  • OPENAI APIを使用した場合、1回の議事録生成にかかるコストは約100円程度。
  • 有料ソリューションに比べて、非常に経済的。

使用方法

  • 環境変数 OPENAI_API_KEY にOpenAIのAPIキーを設定します。
  • 必要なプログラムをインストールします。
    ffmpegのインストール
pip install openai == "0.28"
pip install moviepy
pip install pydub
  • プログラムを実行し、プロンプトファイルを選択。
  • 音声ファイルのパスと会議情報を入力するだけで、議事録が自動的に生成されます。

実際のコード

https://github.com/yydevelop/minutes

import base64
import os
import time
import openai
import moviepy.editor as mp
from pydub import AudioSegment


class AudioProcessor:
    def __init__(self, openai_api_key, whisper_model="whisper-1", whisper_language="ja", openai_model="gpt-3.5-turbo"):
        openai.api_key = openai_api_key
        self.whisper_model = whisper_model
        self.whisper_language = whisper_language
        self.openai_model = openai_model

    def convert_mp4_to_mp3(self, mp4_file_path):
        mp3_file_path = os.path.splitext(mp4_file_path)[0] + '.mp3'
        audio = mp.AudioFileClip(mp4_file_path)
        audio.write_audiofile(mp3_file_path)
        time.sleep(1)
        return mp3_file_path

    def transcribe_audio(self, mp3_file_path):
        with open(mp3_file_path, 'rb') as audio_file:
            transcription = openai.Audio.transcribe(
                self.whisper_model, audio_file, language=self.whisper_language)
        return transcription.text

    def save_text_to_file(self, text, output_file_path):
        with open(output_file_path, 'w', encoding='utf-8') as f:
            f.write(text)

    def split_audio(self, mp3_file_path, interval_ms, output_folder):
        if not os.path.isfile(mp3_file_path):
            print(f"File does not exist: {mp3_file_path}")
            return []

        audio = AudioSegment.from_file(mp3_file_path)
        file_name = os.path.splitext(os.path.basename(mp3_file_path))[0]

        mp3_file_path_list = []
        n_splits = len(audio) // interval_ms

        for i in range(n_splits + 1):
            start, end = i * interval_ms, (i + 1) * interval_ms
            split = audio[start:end]
            output_file_name = f"{output_folder}_{i}.mp3"
            split.export(output_file_name, format="mp3")
            mp3_file_path_list.append(output_file_name)

        return mp3_file_path_list

    def create_meeting_minutes(self, transcriptions, output_file_path, prompt_text, other_text):
        pre_summary = "".join(transcriptions)
        prompt = f"""
        {prompt_text}

        # その他の会議情報
        {other_text}

        # 会議文字起こしテキスト
        {pre_summary}
        """

        print("議事録を作成中です...")
        response = openai.ChatCompletion.create(
            model=self.openai_model,
            messages=[{'role': 'user', 'content': prompt}],
            temperature=0.0,
        )
        self.save_text_to_file(
            response['choices'][0]['message']['content'], f"{output_file_path}_minutes.txt")

    def process_file(self, file_path, output_path, interval_ms=600_000):
        if not os.path.isfile(file_path):
            print("File does not exist")
            return ""

        ext = os.path.splitext(file_path)[1].lower()
        if ext == '.mp4':
            mp3_file_path = self.convert_mp4_to_mp3(file_path)
            file_path = mp3_file_path
            ext = '.mp3'

        if ext == '.mp3':
            mp3_file_path_list = self.split_audio(
                file_path, interval_ms, output_path)
            transcriptions = [self.transcribe_audio(
                mp3_path) for mp3_path in mp3_file_path_list]
            # mp3ファイルを削除
            for mp3_path in mp3_file_path_list:
                os.remove(mp3_path)

            # transcriptionsを結合
            transcription_text = "\n".join(transcriptions)

            # ファイル出力
            output_file_path = f"{output_path}_transcription.txt"
            self.save_text_to_file(transcription_text, output_file_path)

            return "\n".join(transcriptions)

        if ext == '.txt':
            with open(file_path, 'r', encoding='utf-8') as f:
                return f.read()

        print("Unsupported file type")
        return ""

    def get_prompt_from_file(self, prompt_folder="./"):
        prompt_files = [f for f in os.listdir(
            prompt_folder) if os.path.isfile(os.path.join(prompt_folder, f))]
        if not prompt_files:
            return ""

        # ファイルが1つの場合はそのファイルを使用
        if len(prompt_files) == 1:
            with open(os.path.join(prompt_folder, prompt_files[0]), 'r', encoding='utf-8') as f:
                return f.read()
        else:
            print("使用するプロンプト:")
            for i, file in enumerate(prompt_files):
                print(f"{i+1}: {file}")

            selected_index = int(
                input("番号を入力: "))

            # 存在しない番号が入力された場合は空文字列を返す
            if selected_index < 1 or selected_index > len(prompt_files):
                return ""
            else:
                # 対応するファイルを読み込む
                with open(os.path.join(prompt_folder, prompt_files[selected_index-1]), 'r', encoding='utf-8') as f:
                    return f.read()


def main():
    # 環境変数OPENAI_API_KEYからAPIキーを取得
    api_key = os.getenv("OPENAI_API_KEY")
    if api_key is None:
        print("環境変数OPENAI_API_KEYが設定されていません")
        return

    processor = AudioProcessor(api_key, whisper_model="whisper-1",
                               whisper_language="ja", openai_model="gpt-3.5-turbo")

    prompt_text = processor.get_prompt_from_file(prompt_folder="./prompt/")

    # ファイルパスを入力
    file_path = input("ファイルパスを入力してください: ")
    output_path = os.path.splitext(file_path)[0]

    # 他の会議情報を入力
    # 空行で入力を終了
    print("その他の会議情報を入力してください。(空行で終了)")
    print("入力を終了するには空行を入力してください")
    other_input = []
    while True:
        line = input()
        if not line:
            break
        other_input.append(line)
    other_text = "\n".join(other_input)

    transcriptions = processor.process_file(
        file_path, output_path=output_path)

    processor.create_meeting_minutes(
        transcriptions, output_path, prompt_text, other_text)


if __name__ == "__main__":
    main()

あなたは、プロの議事録作成者です。
以下の制約条件、会議文字起こしテキストを元に要点をまとめ、議事録を作成してください。

# 制約条件
・要点をまとめ、簡潔に書いて下さい。
・誤字・脱字があるため、話の内容を予測して置き換えてください。
・見やすいフォーマットにしてください。

Discussion