作成したアプリケーションは以下のように動作します。
APEXアプリケーションのアプリケーション定義の置換文字列G_REQUEST_URLを、新しく作成した音声を返すRESTサービスの完全なURLに置き換えます。
ORDSのREST APIにテンプレートとしてanimal-speechを作成し、POSTハンドラに以下のコードを記述します。パッケージUTL_OPENAI_VISIONに、OpenAIのspeechエンドポイントを呼び出すプロシージャとしてSPEECHを追加し、それをコード中で呼び出しています。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
declare | |
l_response clob; | |
l_role varchar2(16); | |
l_content clob; | |
l_usage clob; | |
l_prompt clob; | |
l_input clob; | |
l_audio blob; | |
begin | |
l_prompt := 'この画像に写っている動物の名前と生息地および生態を教えてください。小学生にわかるような文章でお願いします。'; | |
utl_openai_vision.chat_completions( | |
p_text => l_prompt | |
,p_image => :body | |
,p_mimetype => :content_type | |
,p_max_tokens => 1000 | |
,p_role => l_role | |
,p_content => l_content | |
,p_usage => l_usage | |
,p_response => l_response | |
,p_credential_static_id => 'OPENAI_API_KEY' | |
); | |
l_input := l_content; | |
utl_openai_vision.speech( | |
p_input => l_input | |
,p_audio => l_audio | |
,p_credential_static_id => 'OPENAI_API_KEY' | |
); | |
/* audio/mp3のファイルを返す。 */ | |
:status := 200; | |
sys.htp.init; | |
sys.htp.p('Content-Length: ' || dbms_lob.getlength(l_audio)); | |
sys.htp.p('Content-Type: audio/mp3'); | |
sys.htp.p('Content-Disposition: attachment; filename=speech.mp3'); | |
sys.owa_util.http_header_close; | |
sys.wpg_docload.download_file(l_audio); | |
exception | |
when others then | |
if l_audio is not null then | |
l_response := apex_util.blob_to_clob(l_audio); | |
end if; | |
htp.p(l_response); | |
:status := 400; | |
end; |
リージョンResponseに音声要素を埋め込みます。
ソースのHTMLコードに以下を記述します。
<audio controls id="my-audio"></audio>
TRUEアクションのクリアを非表示に変更し、影響を受ける要素の選択タイプにJavaScript式を選択し、JavaScript式として以下を記述します。
document.getElementById("my-audio")
選択タイプにjQueryセレクタを選択し、jQueryセレクタとして#my-audioを指定することもできます。
ボタンSUBMIT(ラベルは問い合わせる)を押した時に、音声要素を表示するTRUEアクションを追加します。
TRUEアクションは表示、影響を受ける要素は先ほどの非表示のアクションと同じです。ページが最初に表示されるときは音声要素は表示しないため、初期化時に実行はオフにします。
TRUEアクションのJavaScriptコードの実行の設定のコードを、以下に記述に変更します。macOSのChromeで実行するとelem.play()を呼び出した時点で再生が開始されますが、iPhoneのSafariでは、elem.play()では再生されないようです。再生ボタンをクリックする必要がありました。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// スピナーを表示し、画面操作をブロックする。 | |
let spinner = apex.widget.waitPopup(); | |
// P1_IMAGEに設定されているファイルを取り出す。 | |
let file = document.getElementById("P1_IMAGE").files[0]; | |
// OPenAI GPT-4VをORDSのREST API経由で呼び出す。 | |
const req = new XMLHttpRequest(); | |
req.open("POST", "&G_REQUEST_URL.", true); | |
// 現行のAPEXは画像のクロッピングを行うと、フォーマットがPNGに変わる。 | |
// この動作は将来のバージョンで変更される可能性がある。 | |
req.setRequestHeader('content-type','image/png'); | |
// APEXのセッションでORDS APIを認証するためにApex-Sessionヘッダーを設定している。 | |
const authHeader = apex.env.APP_ID.concat(',',apex.env.APP_SESSION); | |
req.setRequestHeader('Apex-Session', authHeader); | |
// text to speechを呼び出すので出力はaudio/mp3 | |
req.responseType = "blob"; | |
req.onload = (event) => { | |
const audio = req.response; | |
const audioURL = URL.createObjectURL(audio); | |
const elem = document.getElementById("my-audio"); | |
elem.src = audioURL; | |
// スピナーを解除して解説を読み上げ。 | |
spinner.remove(); | |
elem.play(); | |
}; | |
// 要求の送信。 | |
req.send(file); |
影響を受ける要素の選択タイプを- 選択 -に戻し、無指定にします。
ページ・アイテムP1_RESPONSEを削除します。
以上で写真の説明を音声として読み上げる実装ができました。
少しだけAPEXアプリケーションを改良します。
ページ・アイテムP1_IMAGEに表示されているアイコンを変更してみます。
FontAPEXのページを開きます。
cameraのアイコンを検索します。fa-cameraを開きます。
表示したいアイコンを修飾し、HTMLまたはCSSとしての記述を取得します。今回は、SizeはLarge、Scaleは5xを選択します。Iconの記述は以下になります。
fa-camera fa-5x fa-lg
Iconの記述をコピーします。
ページ・アイテムP1_IMAGEの外観のアイコンに、コピーした文字列を設定します。
以上でアプリケーションは完成です。実行すると記事の先頭のGIF動画のように動作します。
OpenAIのText-to-speechのドキュメントに記載があるように、現在は英語に最適化されているようです。
今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/text-to-speech.zip
Oracle APEXのアプリケーション作成の参考になれば幸いです。
完