Skip to content

Instantly share code, notes, and snippets.

@hyuki

hyuki/README.md Secret

Last active May 7, 2025 05:10
Show Gist options
  • Select an option

  • Save hyuki/2bd913279da609eef2a357f03eccf712 to your computer and use it in GitHub Desktop.

Select an option

Save hyuki/2bd913279da609eef2a357f03eccf712 to your computer and use it in GitHub Desktop.
speak-memo: macOSで音声を録音し、OpenAIのWhisper APIでテキストに起こし、ChatGPTで整形するRubyスクリプト

speak-memo README

speak-memo.rb は、音声入力からテキストを自動生成・整形して、タイムスタンプ付きで保存・表示する CLI ツールです。

🔧 依存環境

  • macOS(ffmpeg with avfoundation 対応)
  • Ruby 3.x
  • OpenAI API Key(ENV['OPENAI_API_KEY']
  • ffmpegbrew install ffmpeg
  • $EDITOR 環境変数(例: code, vi, emacs

📁 ファイル構成

  • speak-memo.rb:メインスクリプト
  • prompt.txt:整形ルール(ChatGPT system message用)
  • tmp/:録音と中間ファイル(自動生成)
  • output/:整形後テキストの保存先(自動生成)

▶️ 実行方法

ruby speak-memo.rb
  1. マイクから音声を録音(Enterで録音終了)
  2. Whisper APIで文字起こし
  3. ChatGPT APIで整形(prompt.txtを使用)
  4. output/YYYY-MM-DD-HHMMSS.txt に保存
  5. 整形テキストを $EDITOR で開く

✍️ prompt.txt の書き方(例)

・誤変換や文のねじれを整えてください。
・「ブルースカイ」は「Bluesky」にしてください。
・「ルビー」は「Ruby」にしてください。
...

📝 注意点

  • Whisper APIの使用量は 1分あたり $0.006(2025年5月現在)
  • 録音デバイスは :0 固定(必要に応じて変更)
require 'net/http'
require 'uri'
require 'json'
require 'open3'
require 'fileutils'
# === 設定 ===
TMP_DIR = "tmp"
OUTPUT_DIR = "output"
AUDIO_FILE = File.join(TMP_DIR, "recording.mp3")
TRANSCRIBED_FILE = File.join(TMP_DIR, "transcribed.txt")
PROMPT_FILE = "prompt.txt"
DEVICE = ":0" # macOS の内蔵マイク(avfoundation)
OPENAI_API_KEY = ENV['OPENAI_API_KEY']
EDITOR = ENV['EDITOR'] || "vi"
# === ディレクトリ準備 ===
FileUtils.mkdir_p(TMP_DIR)
FileUtils.mkdir_p(OUTPUT_DIR)
# === 録音開始 ===
puts "🎙️ 録音を開始します。Enterキーで終了します。"
ffmpeg_cmd = ["ffmpeg", "-y", "-f", "avfoundation", "-i", DEVICE, "-acodec", "libmp3lame", AUDIO_FILE]
stdin, stdout, stderr, wait_thr = Open3.popen3(*ffmpeg_cmd)
puts "(話し始めてください。録音中です…)"
STDIN.gets
Process.kill("INT", wait_thr.pid)
wait_thr.value
puts "📁 録音完了:#{AUDIO_FILE}"
# === Whisper API で文字起こし ===
puts "📝 Whisper API に送信中..."
whisper_uri = URI("https://api.openai.com/v1/audio/transcriptions")
whisper_req = Net::HTTP::Post.new(whisper_uri)
whisper_req["Authorization"] = "Bearer #{OPENAI_API_KEY}"
whisper_req.set_form(
[['file', File.open(AUDIO_FILE)], ['model', 'whisper-1']],
'multipart/form-data'
)
whisper_res = Net::HTTP.start(whisper_uri.hostname, whisper_uri.port, use_ssl: true) do |http|
http.request(whisper_req)
end
transcribed_text = JSON.parse(whisper_res.body)["text"]
File.write(TRANSCRIBED_FILE, transcribed_text)
puts "📄 テキスト化結果を保存しました:#{TRANSCRIBED_FILE}"
# === ChatGPT で整形 ===
puts "🎨 ChatGPT で整形中..."
prompt_text = File.read(PROMPT_FILE)
chat_uri = URI("https://api.openai.com/v1/chat/completions")
chat_headers = {
"Content-Type" => "application/json",
"Authorization" => "Bearer #{OPENAI_API_KEY}"
}
chat_body = {
model: "gpt-4",
messages: [
{ role: "system", content: prompt_text },
{ role: "user", content: transcribed_text }
],
temperature: 0.2
}
chat_req = Net::HTTP::Post.new(chat_uri.path, chat_headers)
chat_req.body = chat_body.to_json
chat_res = Net::HTTP.start(chat_uri.hostname, chat_uri.port, use_ssl: true) do |http|
http.request(chat_req)
end
formatted_text = JSON.parse(chat_res.body)["choices"][0]["message"]["content"]
puts "\n🧾 整形後のテキスト:\n\n#{formatted_text}"
# === タイムスタンプ付きファイルに保存 ===
timestamp = Time.now.strftime("%Y-%m-%d-%H%M%S")
output_path = File.join(OUTPUT_DIR, "#{timestamp}.txt")
File.write(output_path, formatted_text)
puts "\n💾 整形済みテキストを保存しました:#{output_path}"
# === エディタで開く ===
puts "🖊️ エディタ(#{EDITOR})でファイルを開きます..."
system(EDITOR, output_path)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment