ใน Codelab นี้คุณจะได้เรียนรู้การสร้าง LINE Chatbot ร่วมกับการใช้ Google Speech-to-Text API เพื่อให้ Chatbot รับคำสั่งด้วยเสียงจากผู้ใช้ (ภาษาไทย)และใช้ Google Speech-to-Text API ในการแกะข้อความออกมาจากไฟล์เสียงที่ได้รับ
Google Speech-to-Text API ตัวนี้เป็น Cloud Service ของ Google ที่รองรับไฟล์เสียงมากถึง 125 ภาษาซึ่งเราจะส่งเป็น Local file หรือจะเอาไฟล์ขึ้น Cloud Storage เพื่อทำการประมวลผลก็ได้ โดยเราจะต้องทำการแปลงไฟล์ให้เป็น .wav ก่อนด้วย ZAMZAR API
จุดเริ่มขบวนสำหรับการพัฒนาแอปพลิเคชันต่างๆบนแพลตฟอร์มของ LINE คือคุณจะต้องสมัครเป็น LINE Developer ก่อน
Provider คือชื่อผู้ให้บริการ ซึ่งจะไปแสดงตามหน้า consent ต่างๆ หรือเรียกได้ว่าเป็น superset ของแอปทั้งหลายที่เราจะพัฒนาขึ้นรวมถึง LIFF app ด้วย โดยการสร้างเพียงให้ระบุชื่อของ Provider ลงไป ซึ่งอาจจะตั้งเป็นชื่อตัวเอง, ชื่อบริษัท, ชื่อทีม หรือชื่อกลุ่มก็ได้
Channel เปรียบเสมือนแอป หรือเรียกได้ว่าเป็น subset ของ Provider โดยมีอยู่ 3 รูปแบบ คือ LINE Login, Messaging API และ Clova Skill
เบื้องหลังของ Chatbot ตัวนี้ เราจะใช้บริการ Cloud Functions for Firebaseในการจัดการ Webhook
เนื่องจาก Cloud Functions for Firebase มีเงื่อนไขว่า หากต้องการไป request ตัว APIs ที่อยู่ภายนอก Google คุณจำเป็นจะต้องใช้ Blaze plan(เราจะต้องไปเรียก Messaging API ของ LINE)
Firebase CLI เป็นเครื่องมือที่จำเป็นสำหรับการ deploy ตัวฟังก์ชันที่เราพัฒนาขึ้น อีกทั้งยังสามารถจำลองการทำงานฟังก์ชัน(Emulate) ภายในเครื่องที่เราพัฒนาอยู่(Locally) ได้
npm install -g firebase-tools
firebase --version
firebase login
mkdir speechToTextBot
cd speechToTextBot
firebase init functions
package.json
ขี้นมา"dependencies": {
"axios": "^1.3.6",
"firebase-admin": "^11.7.0",
"firebase-functions": "^4.3.1","@google-cloud/speech": "^5.1.0"
}
functions
จากนั้นสั่ง Install ตัว dependencies ที่เพิ่มเข้ามาใหม่ใน terminal ด้วยคำสั่งnpm install
const functions = require("firebase-functions");
const axios = require('axios');
const FormData = require('form-data');
const speech = require('@google-cloud/speech');
// สำหรับจัดการไฟล์
const path = require("path");
const os = require("os");
const fs = require("fs");
// Instantiates a client
const client = new speech.SpeechClient();
// LINE API
const LINE_MESSAGING_API = "https://api.line.me/v2/bot";
const LINE_CONTENT_API = "https://api-data.line.me/v2/bot/message";
const LINE_HEADER = {
"Content-Type": "application/json",
Authorization: "Bearer XXXXX"
};
// ZAMZAR API สำหรับแปลงไฟล์เสียงเป็น .wav
const ZAMZAR_API_KEY = "YYYYYYYY";
const ZAMZAR_CREATE_JOB_API = "https://api.zamzar.com/v1/jobs";
const ZAMZAR_DOWNLOAD_API = "https://api.zamzar.com/v1/files";
const ZAMZAR_HEADER = {
"Content-Type": "multipart/form-data"
};
โดย ZAMZAR API หลักๆที่เราจะเรียกใช้เพื่อแปลงไฟล์จะมีอยู่ทั้งหมด 3 ตัวด้วยกันได้แก่
โดยวิธีการพัฒนาให้มาที่ไฟล์เดิม index.js และสร้างฟังก์ชันตาม code ด้านล่างนี้
const createConvertJobToWAV = async (audioLocalFile) => {
var bodyFormData = new FormData();
bodyFormData.append('target_format', 'wav');
bodyFormData.append('source_file', fs.createReadStream(audioLocalFile));
return await axios({
method: "post",
url: ZAMZAR_CREATE_JOB_API,
headers: ZAMZAR_HEADER,
data: bodyFormData,
auth: {
username: ZAMZAR_API_KEY
}
});
}
const isConvertJobSuccess = async (jobId) => {
return await axios({
method: "get",
url: `${ZAMZAR_CREATE_JOB_API}/${jobId}`,
auth: {
username: ZAMZAR_API_KEY
}
});
}
const downloadWAVFile = async (targetFileId) => {
return await axios({
method: "get",
url: `${ZAMZAR_DOWNLOAD_API}/${targetFileId}/content`, responseType: "arraybuffer", auth: {
username: ZAMZAR_API_KEY
}
});
}
const convertAndDownloadWAVFile = async (m4aLocalFile) => {
const resultFromConvertFile = await createConvertJobToWAV(m4aLocalFile);
// เนื่องจาการแปลงไฟล์จะเป็น Job ซึ่งมันจะไม่คืนผลลัพธ์กลับมาในทันที เราเลยต้องทำการ
// Sleep Chatbot เราประมาน 5 วิ ก่อนที่จะไปดึงผลลัพธ์ได้
await new Promise(r => setTimeout(r, 5000));
const resultFromChecking = await isConvertJobSuccess(resultFromConvertFile.data.id);
return await downloadWAVFile(resultFromChecking.data.target_files[0].id);
}
สร้างฟังก์ชันชื่อ transcribeSpeech() เพื่อไปเรียกใช้ Google Cloud Speech-to-Text โดยเราจะส่งไฟล์ .wav ที่ได้จากการแปลงในข้อก่อนหน้าเข้าไป ส่วน languageCode จะเลือกเป็น th-TH (หรือถ้าต้องการใช้ภาษาอื่นดูได้ที่นี่)
const transcribeSpeech = async (wavFilename) => {
const audio = {
content: fs.readFileSync(wavFilename).toString('base64'),
};
// The audio file's encoding, sample rate in hertz, and BCP-47 language code
const config = {
encoding: 'LINEAR16',
sampleRateHertz: 16000,
languageCode: 'th-TH',
};
const request = {
audio: audio,
config: config,
};
// Detects speech in the audio file
const [response] = await client.recognize(request);
const transcription = await response.results.map(result => result.alternatives[0].transcript).join('\n');
console.log('Result: ', JSON.stringify(response.results));
return transcription;
}
exports.LineWebhook = functions.https.onRequest(async (req, res) => {
if (req.method === "POST") {
const events = req.body.events;
for (const event of events) {
if (event.type === 'message' && event.message.type === 'audio') {
// ดึงไฟล์เสียงของผู้ใช้ที่ส่งมาจาก LINE
const url = `${LINE_CONTENT_API}/${event.message.id}/content`;
const audioFile = await axios({
method: "get",
headers: LINE_HEADER,
url: url,
responseType: "arraybuffer"
});
// เซฟไฟล์เสียงของผู้ใช้ลงเครื่อง เนื่องจากเป็น iOS เลยได้ .m4a
const filenameTimestamp = event.timestamp;
const m4aLocalFile = path.join(os.tmpdir(), filenameTimestamp + ".m4a");
fs.writeFileSync(m4aLocalFile, audioFile.data);
// ทำการแปลงไฟล์เสียงจาก .m4a เป็น .wav
const wavFile = await convertAndDownloadWAVFile(m4aLocalFile);
// เซฟไฟล์เสียงที่เป็น .wav ลงเครื่อง
const wavLocalFile = path.join(os.tmpdir(), filenameTimestamp + ".wav");
fs.writeFileSync(wavLocalFile, wavFile.data);
// เรียกใช้ Google Speech-to-text API
const resultText = await transcribeSpeech(wavLocalFile);
await reply(event.replyToken,
[{
"type": "sticker",
"packageId": "6359",
"stickerId": "11069861"
},
{
"type": "text",
"text": "Speech-to-text Result: " + resultText
}]
);
}
}
}
return res.end();
})
const reply = async (replyToken, payload) => {
await axios({
method: "post",
url: `${LINE_MESSAGING_API}/message/reply`,
headers: LINE_HEADER,
data: JSON.stringify({
replyToken: replyToken,
messages: payload
})
});
};
firebase deploy --only functions
ยินดีด้วย! ถึงตรงนี้คุณก็มี LINE Chatbot เพื่อแกะไฟล์เสียง Speech-to-text เป็นของคุณเองแล้ว!!!