こんにちは!CryptoGamesというブロックチェーンゲーム企業でエンジニアをしているかるでねです!
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。
CryptoGamesは、「クリプトスペルズ」をはじめとするブロックチェーンゲームの開発・運営や、ブロックチェーン関連の開発を行っている会社です。
現在エンジニアを中心に積極採用中ですので、少しでも興味を持った方はぜひお話しだけでもできたら嬉しいです!
以下のWantedlyのサイトで会社について詳しく知ることができます!
https://www.wantedly.com/companies/cryptogames
以下のストーリーでは、実際にCryptoGamesで働いているメンバーのリアルな声を知ることができます。
https://www.wantedly.com/companies/cryptogames/stories
ぜひ上記Wantedlyからか、以下のCardeneのTwitter などから連絡してください!
今回の記事では、OpenAI APIを使用して、「VoicyやYoutubeなどのリンクを入力すると、その内容を要約してくれるアプリ」を作っていこうと思います!
実際に僕が作ったものは以下になります(サイドメニューから「Audio Summary」を選択してもらえれば使用できます!)。
使い方やソースコードは以下のGithub内に記載してあります。
https://github.com/cardene777/ai_app/tree/main/audio_summary
実際に動かすことはできますが、セキュリティの観点から使用後は生成したOpenAI API Keyは削除しておくことをお勧めします。
記事の中ではPythonのフレームワークである「Streamlit」を使用して、実際に誰でも触れる状態までの手順を紹介します!
では早速やっていきましょう!
リンクから音声ファイル取得
まずはVoicyやYoutubeのリンクから音声ファイルを取得していきましょう!
上記をPythonで実行するには「yt_dlp」というモジュールを使用します。
https://pypi.org/project/yt-dlp/
今回は仮想環境として「Poetry」というものを使用します。
https://github.com/python-poetry/poetry
「Poetry」は、Pythonのパッケージマネージャの1つで、結構便利なので個人的には愛用してます。
使用したことがない人は以下の記事などを参考に「Poetry」を使用できる状態にしてください!
(「Poetry」以外のパッケージマネージャを使用している方はそのまま使い慣れているものを使用してください!)
https://qiita.com/ksato9700/items/b893cf1db83605898d8a
では以下のコマンドで何かしらディレクトリを作成して、仮想環境を有効化ください。
$ mkdir audio
$ cd audio
$ poetry init
色々聞かれますが全部Enterボタンを押すで問題ないです!
では次に以下のコマンドを実行して仮想環境を有効化したのち、「yt_dlp」をインストールしてください。
$ poetry shell
$ poetry add yt_dlp
これで「yt_dlp」が使用できるようになりました。
先ほど作成したディレクトリの中にファイルを作成しましょう。
$ touch download_audio.py
ファイルを作成したら開いて以下のコードを貼り付けてください。
from yt_dlp import YoutubeDL
def get_audio_file(url: str):
ydl_opts = {
'outtmpl': './audio.%(ext)s',
'format': 'bestaudio/best',
'postprocessors': [{
'key': 'FFmpegExtractAudio',
'preferredcodec': 'mp3',
'preferredquality': '192'
}],
}
ydl = YoutubeDL(ydl_opts)
ydl.download([url])
def main():
get_audio_file("https://voicy.jp/channel/2627/559190")
if __name__ == '__main__':
main()
では分割して1つずつ説明していきます!
from yt_dlp import YoutubeDL
先ほどインストールした「yt_dlp」をimportしています。
def get_audio_file(url: str):
ydl_opts = {
'outtmpl': './audio.%(ext)s',
'format': 'bestaudio/best',
'postprocessors': [{
'key': 'FFmpegExtractAudio',
'preferredcodec': 'mp3',
'preferredquality': '192'
}],
}
ydl = YoutubeDL(ydl_opts)
ydl.download([url])
ここはメインの処理になります。
引数で何らかのURLを受け取り、そのURL内の音声ファイルを取得しています。
ydl_opts
という辞書型の部分では音声ファイルを取得するときのオプションを設定しています。
ざっくり重要な部分は以下になります。
ポイント
outtmpl
- 保存先のファイルパス。
format
- どの暮らしのクオリティでダウンロードするか。
preferredcodec
- ファイル拡張子。
その後指定したURL内の音声ファイルをダウンロードしています。
今回はVoicyのパーソナリティである「中島聡」さんの放送を取得しています。
では実行してみましょう。
以下のように実行して色々と出力されたのち、「audio.mp3」というファイルが作成されていれば成功です!
$ python download_audio.py
[voicy] Extracting URL: https://voicy.jp/channel/2627/559190
[voicy] 559190: Downloading JSON metadata
[download] Downloading multi_video: SlashGPTをオープンソース化しました
[voicy] Playlist SlashGPTをオープンソース化しました: Downloading 2 items of 2
[download] Downloading item 1 of 2
[info] 1934043: Downloading 1 format(s): hls
[hlsnative] Downloading m3u8 manifest
[hlsnative] Total fragments: 2
[download] Destination: ./audio.m4a
[download] 100% of 103.18KiB in 00:00:00 at 1.08MiB/s
[FixupM3u8] Fixing MPEG-TS in MP4 container of "./audio.m4a"
[ExtractAudio] Destination: ./audio.mp3
Deleting original file ./audio.m4a (pass -k to keep)
[download] Downloading item 2 of 2
[info] 1314556: Downloading 1 format(s): hls
[hlsnative] Downloading m3u8 manifest
[hlsnative] Total fragments: 45
[download] Destination: ./audio.m4a
[download] 100% of 3.92MiB in 00:00:02 at 1.67MiB/s
[FixupM3u8] Fixing MPEG-TS in MP4 container of "./audio.m4a"
[ExtractAudio] Destination: ./audio.mp3
Deleting original file ./audio.m4a (pass -k to keep)
[download] Finished downloading playlist: SlashGPTをオープンソース化しました
これで「URLから音声ファイルを取得する」は実行できました。
音声をテキストに変換
次に先ほど取得した音声ファイルをテキストに変換していきます。
今回は「Whisper」というOpenAIのが提供している音声認識モデルで、文字起こしなどをすることができるサービスを使用していきます。
そしてこの章で紹介する「音声をテキストに変換」と「テキストを要約」については以下の記事を参考に実装しています。
https://zenn.dev/voicy/articles/4cca87815296df
まずは環境変数を定義していきます。
以下のコマンドを実行して.env
というファイルを作成してください。
$ touch .env
作成したらファイルを開き、以下を貼り付けてください。
OPENAI_API_KEY="<Open AI API Key>"
「<Open AI API Key>
」の部分にOpenAIのAPI Keyを入力してください。
次に以下のモジュールをインストールしてください。
$ poetry add openai
$ poetry add python-dotenv
OpenAIのAPIを使用するモジュールと環境変数を使用するモジュールになります。
では、次に以下のコマンドを実行してファイルを作成してください。
$ touch audio_to_text.py
ファイルを作成したら以下のコードをファイル内に貼り付けてください。
import os
import openai
from dotenv import load_dotenv
load_dotenv()
openai.api_key = os.getenv('OPENAI_API_KEY')
def get_audio_text(file_path: str) -> str:
audio_file = open(file_path, "rb")
transcript = openai.Audio.transcribe("whisper-1", audio_file)
return transcript["text"]
def main():
file_path = "./audio.mp3"
audio_text = get_audio_text(file_path)
print(audio_text)
if __name__ == "__main__":
main()
このコードについても1つずつ解説していきます。
import os
import openai
from dotenv import load_dotenv
先ほどインストールしたモジュールなどをimportしています。
load_dotenv()
openai.api_key = os.getenv('OPENAI_API_KEY')
.env
ファイル内に記載している環境変数を読み込んで、openaiモジュールに渡しています。
このようにすることで、OpenAIのAPI Keyを使用することができます。
def get_audio_text(file_path: str) -> str:
audio_file = open(file_path, "rb")
transcript = openai.Audio.transcribe("whisper-1", audio_file)
return transcript["text"]
ここがメインの処理になります。
引数で渡されたfile_path
にあるファイルを読み込んでいます。
その後先ほど説明した「Whisper」を使用して、音声ファイルをテキストに変換しています。
最後に変換したテキストを呼び出し元に返しています。
def main():
file_path = "./audio.mp3"
audio_text = get_audio_text(file_path)
print(audio_text)
1つ前に解説したget_audio_text
を実行しています。
前章で取得した音声ファイルはaudio.mp3
という名前で保存されているので、そのファイルを読み込んでget_audio_text
に渡しています。
最後に変換したテキストを標準出力しています。
では実行してみましょう!
$ python audio_to_text.py
こんにちは中島です 日本に出張したりしててちょっと...
上記のようにテキストが出力されれば成功です!
テキストを要約
では次にテキストを要約してみましょう!
この章でも前章で紹介した記事を参考にしています。
https://zenn.dev/voicy/articles/4cca87815296df
まずは必要なモジュールを追加していきましょう。
$ poetry add llama-index
次にファイルを作成していきましょう。
$ touch summary_text.py
ファイルを作成したら以下を貼り付けてください。
import os
import openai
from llama_index import StorageContext, StringIterableReader, GPTVectorStoreIndex, load_index_from_storage
from dotenv import load_dotenv
load_dotenv()
openai.api_key = os.getenv('OPENAI_API_KEY')
STORAGE_PATH = "./storage"
def get_summary_text(audio_text: str, summary_prompt: str) -> str:
if not os.path.exists(STORAGE_PATH):
os.makedirs(STORAGE_PATH)
try:
storage_context = StorageContext.from_defaults(persist_dir=STORAGE_PATH)
vector_index = load_index_from_storage(storage_context)
except Exception:
documents = StringIterableReader().load_data(texts=[audio_text])
vector_index = GPTVectorStoreIndex.from_documents(documents)
vector_index.storage_context.persist(persist_dir=STORAGE_PATH)
query_engine = vector_index.as_query_engine()
response = query_engine.query(summary_prompt)
return response.response
def main():
text = """
こんにちは中島です 日本に出張したりしててちょっと...
"""
prompt = "テキストの内容を5~10の項目に分けて要約してください。"
summary_text = get_summary_text(text, prompt)
print(f"summary_text: {summary_text}")
if __name__ == "__main__":
main()
では1つずつ解説していきます。
import os
import openai
from llama_index import StorageContext, StringIterableReader, GPTVectorStoreIndex, load_index_from_storage
from dotenv import load_dotenv
ここでは先ほどインストールしたモジュールなどをimporthしています。
load_dotenv()
openai.api_key = os.getenv('OPENAI_API_KEY')
STORAGE_PATH = "./storage"
ここでは先ほど同様OpenAIのAPI Keyをセットしています。
STORAGE_PATH
は、テキストをベクトル化した際に生成されたファイルを保存するディレクトリになります。
def get_summary_text(audio_text: str, summary_prompt: str) -> str:
if not os.path.exists(STORAGE_PATH):
os.makedirs(STORAGE_PATH)
try:
storage_context = StorageContext.from_defaults(persist_dir=STORAGE_PATH)
vector_index = load_index_from_storage(storage_context)
except Exception:
documents = StringIterableReader().load_data(texts=[audio_text])
vector_index = GPTVectorStoreIndex.from_documents(documents)
vector_index.storage_context.persist(persist_dir=STORAGE_PATH)
query_engine = vector_index.as_query_engine()
response = query_engine.query(summary_prompt)
return response.response
ここがメインの処理になります。
引数でaudio_text
という音声ファイルをテキストに変換したデータと、summary_prompt
という要約するためのプロンプトを受け取っています。
まずはSTORAGE_PATH
にディレクトリが存在するかチェックして、もし存在しなければ作成しています。
次にSTORAGE_PATH
が存在して、そのディレクトリ内にベクトル情報のファイルが存在すればそれを使用し、もし存在しなかったり何かしらエラーが出たら、引数で受け取ったテキストをベクトルに変換しています。
ベクトルかが完了したのち、引数で受け取ったプロンプトと上記で作成したベクトル情報を使用してテキストを要約しています。
テキストを要約できたら呼び出し元に要約テキストを返しています。
def main():
text = """
こんにちは中島です 日本に出張したりしててちょっと...
"""
prompt = "テキストの内容を5~10の項目に分けて要約してください。"
summary_text = get_summary_text(text, prompt)
print(f"summary_text: {summary_text}")
先ほど説明していたget_summary_text
を呼び出しています。
text
変数には前章で音声ファイルから変換してテキストを格納してください。
では実行していきましょう!
$ python summary_text.py
1. 自然言語のインターフェースを使うことで、複雑なシステムでも文脈を理解してメニューを探さなくてもやってくれるシステムができる。
2. ユーザーインターフェースの進化の意味で、一気に進んだという感じである。
...
上記のように項目に分けて出力されていれば成功です!
ここまでで全体の処理を確認できました。
Streamlitでアプリ作成
では次にStreamlitを使用してここまでの処理をアプリにしてみましょう!
まずはモジュールをインストールします。
$ poetry add streamlit
次にファイルを作成します。
$ touch lib.py
$ touch app.py
ファイルを作成したらそれぞれのファイルに以下を貼り付けてください。
from yt_dlp import YoutubeDL
import openai
import os
from llama_index import StorageContext, StringIterableReader, GPTVectorStoreIndex, load_index_from_storage
STORAGE_PATH = "./storage"
def get_audio_file(url: str):
ydl_opts = {
'outtmpl': './audio.%(ext)s',
'format': 'bestaudio/best',
'postprocessors': [{
'key': 'FFmpegExtractAudio',
'preferredcodec': 'mp3',
'preferredquality': '192'
}],
}
ydl = YoutubeDL(ydl_opts)
ydl.download([url])
def get_audio_text(file_path: str) -> str:
audio_file = open(file_path, "rb")
transcript = openai.Audio.transcribe("whisper-1", audio_file)
return transcript["text"]
def get_summary_text(audio_text: str, summary_prompt: str) -> str:
if not os.path.exists(STORAGE_PATH):
os.makedirs(STORAGE_PATH)
try:
storage_context = StorageContext.from_defaults(persist_dir=STORAGE_PATH)
vector_index = load_index_from_storage(storage_context)
except Exception:
documents = StringIterableReader().load_data(texts=[audio_text])
vector_index = GPTVectorStoreIndex.from_documents(documents)
vector_index.storage_context.persist(persist_dir=STORAGE_PATH)
query_engine = vector_index.as_query_engine()
response = query_engine.query(summary_prompt)
return response.response
import streamlit as st
import openai
import webbrowser
import os
import shutil
import lib
AUDIO_FILE_PATH = "./audio.mp3"
STORAGE_PATH = "./storage"
def main():
summary_prompt = ''
audio_text = ''
api_flag = False
st.title('Audio Summary')
if st.button("Reset", key="reset"):
if os.path.exists(STORAGE_PATH):
shutil.rmtree(STORAGE_PATH)
if os.path.exists(AUDIO_FILE_PATH):
os.remove(AUDIO_FILE_PATH)
open_ai_api_key: str = st.text_input(label="Open AI API Key (required)", type="password")
if st.button("Register Open AI API Key", key="api_key") and open_ai_api_key:
st.success("Open AI API Key is set.", icon="✅")
if (api_flag is False):
os.environ['OPENAI_API_KEY'] = open_ai_api_key
openai.api_key = open_ai_api_key
api_flag = True
audio_url: str = st.text_input(label="Audio URL (required)")
if audio_url:
st.success("Audio URL is set.", icon="✅")
if st.button("Get Audio File", key="get_audio_file"):
with st.spinner(text="Get Audio Data..."):
lib.get_audio_file(audio_url)
summary_prompt = st.selectbox(
label="Choice Prompt (required)",
options=[
"テキストの内容を300文字程度で要約してください。",
"テキストの内容を5~10の項目に分けて要約してください",
"テキストの内容を500文字程度にまとめてください。",
"テキストの内容を1000文字程度にまとめてください。",
]
)
if audio_url and os.path.exists(AUDIO_FILE_PATH):
st.success("Get Audio Data.", icon="✅")
audio_file = open(AUDIO_FILE_PATH, 'rb')
audio_bytes = audio_file.read()
st.audio(audio_bytes, format='audio/mp3')
st.download_button(label="Download Audio File", data=audio_bytes, file_name="audio.mp3", mime="audio/mp3")
col1, col2 = st.columns(2)
with col1:
st.write("Summary Text Using OpenAI")
summary_btn = st.button("Get Summary Text", key="get_summary_text")
with col2:
st.write("Open ChatGPT")
check_open_chatgpt_btn = st.button("Open ChatGPT Button", key="check_open_chatgpt")
if summary_btn:
with st.spinner(text="Convert Audio Text..."):
audio_text = lib.get_audio_text(AUDIO_FILE_PATH)
if audio_text:
st.success("Convert Audio Text.", icon="✅")
with st.expander("Audio Text"):
st.write(audio_text)
with st.spinner(text="Get Summary Text..."):
summary_text = lib.get_summary_text(audio_text, summary_prompt)
if summary_text:
st.success("Get Summary Text.", icon="✅")
st.write(summary_text)
if check_open_chatgpt_btn:
with st.spinner(text="Convert Audio Text..."):
audio_text = lib.get_audio_text(AUDIO_FILE_PATH)
if audio_text:
st.success("Convert Audio Text.", icon="✅")
st.info("以下のコードをコピーして、以下のボタンを押したのち、開いたChatGPTページ内の入力欄に貼り付けてください。")
st.write(f"{summary_prompt}\n{audio_text}")
if st.button("Open ChatGPT", key="open_chatgpt"):
search_url = 'https://chat.openai.com/'
with st.spinner(text="Open ChatGPT..."):
webbrowser.open(search_url)
if __name__ == "__main__":
main()
この部分はだいぶ長いので解説を省きます。
気になる方は以下のStremalitのドキュメントなどを参考にしてください!
では以下のコマンドを実行してください。
$ streamlit run app.p
You can now view your Streamlit app in your browser.
Local URL: http://localhost:8501
Network URL: http://192.168.1.2:8501
上記を実行するとブラウザでページが自動で開くと思いますが、もし開かない場合は出力されているURLにアクセスしてください。
上記のようなページが開いていれば完璧です!
触り方は以下のGithub内の動画を参考にしてください。
https://github.com/cardene777/ai_app/tree/main/audio_summary
これでStreamlitでアプリを立ち上げることに成功しました!
Streamlitでデプロイ
最後に先ほど作成したアプリをデプロイしてみましょう!
今回は簡単にデプロイできる「Streamlit Share」というものを使用します。
まずは準備をしましょう。
必要なファイルを作成します。
$ touch packages.txt
作成したファイル内に以下を記述してください。
ffmpeg
ではここまで作成したファイルをGithubにあげてください。
手順としては以下などの記事が参考になります。
リポジトリをPrivateにするとデプロイできない可能性があるため、Publicにすることをおすすめします。
では以下のURLにアクセスしてください。
上記のようなページが開くので、Githubでログインしてください。
その後上記のようなページが開くので、右上にある「New app」をクリックしてください。
上記のようなページが開くので、「Repository」で先ほど作成したリポジトリを作成してください。
「Branch」は「main」で問題ないです。
「Main file path」は「app.py
」にしてください。
必要であれば「App URL」を任意のものに設定もできます。
上記の設定ができたら「Deploy!」を押してください。
何とこれでデプロイは終わりです!
あとはボタンを押した後に開くページに、先ほどローカルで確認したStreamlitの画面が表示されれば成功です!
ソースコード
今回の記事で使用したコードは以下に置いてあります。
https://github.com/cardene777/ai_app/tree/main/audio_summary/blog
最後に
今回の記事では「VoicyやYoutubeなどのリンクを入力すると、その内容を要約してくれるアプリ」の作成とデプロイの手順まで解説してきました。
いかがだったでしょうか?
OpenAI APIを使用することでもっと面白いことができると思うので、これからも記事にしていこうと思います。
また、Streamlitを使用すると簡単にアプリにすることができるのでおすすめです。
もし何か質問などがあれば以下のTwitterなどからDMしてください!
普段はPythonやブロックチェーンメインに情報発信をしています。
Twiiterでは図解でわかりやすく解説する投稿をしているのでぜひフォローしてくれると嬉しいです!