ปั้น LINE Chatbot ให้แกะไฟล์เสียงเป็นภาษาไทยได้แบบเนียนๆด้วย Google Speech-to-Text API

Tan Warit
LINE Developers Thailand
5 min readDec 16, 2022

สวัสดีครับ วันนี้มีบทความแนว Use-case สั้นๆมาฝากชาว LINE DEV ผมทดลองสร้าง LINE Chatbot เพื่อให้รับคำสั่งด้วยเสียงจากผู้ใช้(ภาษาไทย)และใช้ Google Speech-to-Text API ในการแกะข้อความออกมาจากไฟล์เสียงที่ได้รับ จะเป็นยังไงตามไปดูกันครับผม :)

Google Cloud Speech-to-Text

ตามชื่อเลยครับตัวนี้เป็น Cloud Service ของ Google ที่รองรับไฟล์เสียงมากถึง 125 ภาษาแน่ะ โดยเราจะส่งเป็น Local file หรือจะเอาไฟล์ขึ้น Cloud Storage ก่อนก็ได้

รีวิวสั้นๆการใช้งาน API ตัวนี้เท่าที่ลองด้วยภาษาไทยถือได้ว่าความแม่นยำค่อนข้างสูงเลย แต่ส่วนตัวผมปวดหัวกับการเตรียมไฟล์เสียงพอควรเพราะ Speech-to-Text V1 ที่เป็นตัว Stable ยังไม่ได้รองรับไฟล์เสียงพวก .mp3 .m4a เราเลยต้องแปลงเป็น .wav ก่อง (เจอใน Stackoverflow แนะนำให้แปลงเป็น .wav เพราะ API ตัวนี้จะรับได้ง่ายที่สุด)

ZAMZAR Online file conversion

จากข้อที่แล้วที่เราต้องแปลงเป็น .wav ก่อน ผมก็พยายามหาวิธีการแปลง ไปเจอ lib ของ node หลายอันก็จริงแต่ผมลองแล้วมันแปลงแล้วเหมือนมันใช้ไม่ได้ ไปเจอใน Stackoverflow เหมือนเดิมว่ามีคนแนะนำให้ใช้ ZAMZAR (เรียกเป็นไทยว่าแซมซ่าาาา 55+ ชื่อมันน่าใช้งานจริมๆ)ในการแปลงไฟล์ฮะ โดยมี Free tier ในการแปลงเดือนละ 100 ไฟล์

https://developers.zamzar.com/

5 Steps ในการพัฒนา

สิ่งที่เราจะทำใน Chatbot ตัวนี้ก็ง่ายๆมีอยู่แค่ 5 ขั้นตอนเท่านั้นครับ ได้แก่

  1. สร้าง LINE Chatbot ด้วย Cloud Functions
  2. สมัครเข้าใช้งาน Google Cloud Speech-to-Text
  3. สมัครเข้าใช้งาน ZAMZAR Developer
  4. พัฒนาระบบเพื่อเรียก API ในการแปลงไฟล์เสียงเป็น .wav
  5. พัฒนาระบบเพื่อรับไฟล์เสียงจาก Webhook เรียกใช้ Speech-to-Text API และส่งผลลัพธ์หาผู้ใช้

1. สร้าง LINE Chatbot ด้วย Cloud Functions

สำหรับใครที่ยังไม่เคยพัฒนา LINE Chatbot ด้วย Cloud Functions for Firebase ให้ทำตามขั้นตอนของบทความนี้ (ข้อ 1 และ 2 ก็พอครับ)

2. สมัครเข้าใช้งาน Google Cloud Speech-to-Text

หลังจากสร้าง Firebase Project เสร็จแล้วเราจะได้ Project ใน Google Cloud Platform ด้วยให้เราไปทำการเปิดใช้งาน Speech-to-Text ที่นี่ แล้วกดปุ่ม Enable ได้เบย

โดยเราจะมี Free tier ให้ใช้อยู่ ดูรายละเอียดเพิ่มเติมได้ที่นี่ครับผม

3. สมัครเข้าใช้งาน ZAMZAR Developer

คลิกไปที่นี่เพื่อทำการ Sign up แบบ Free-tier ให้เรียบร้อย หลังจากนั้นให้เราไปที่ Your Account > Account Details เพื่อทำการ Copy API Key เอาไว้ใช้เรียก API

4. พัฒนาระบบเพื่อเรียก API ในการแปลงไฟล์เสียงเป็น .wav

ในโฟลเดอร์ /functions ของโปรเจคที่เราได้เตรียมไว้(ขั้นตอนที่ 1) ให้เปิดไฟล์ package.json ขึ้นมา แล้วเพิ่ม dependency ชื่อ axios และ @google-cloud/speech เข้าไปครับ

"dependencies": {
"@google-cloud/speech": "^5.1.0",
"axios": "^1.2.0",
"firebase-admin": "^11.3.0",
"firebase-functions": "^4.1.0"
}

จากนั้นให้เปิดไฟล์ index.js ขึ้นมา แล้ว import dependencies ต่างๆที่ต้องใช้

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 ในการแปลงไฟล์เป็น .wav โดย API หลักๆที่เราจะเรียกใช้เพื่อแปลงไฟล์จะมีอยู่ทั้งหมด 3 ตัวด้วยกัน

  1. Starting a job for a local file เป็น POST API ที่ให้เราส่งไฟล์จากเครื่องเพื่อทำการแปลง (แต่มันจะยังไม่คืนไฟล์ผลลัพธ์กลับมาทันที)
  2. Retrieving a job เป็น GET API ที่ให้เราตรวจสอบสถานะของการแปลงไฟล์ (Job ข้อที่ 1)
  3. Retrieving the content of a file เป็น GET API ที่ให้เราทำการ Download ไฟล์ผลลัพธ์ที่เราต้องการ
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);
}

5. พัฒนาระบบเพื่อรับไฟล์เสียงจาก Webhook เรียกใช้ Speech-to-Text API และส่งผลลัพธ์หาผู้ใช้

ถัดไปในไฟล์ index.js ให้เราสร้างฟังก์ชันชื่อ transcribeSpeech() เพื่อไปเรียกใช้ Google Cloud Speech-to-Text โดยเราจะส่งไฟล์ .wav ที่ได้จากการแปลงในข้อก่อนหน้าเข้าไป ส่วน languageCode เราจะเลือกเป็น th-TH (หรือถ้าใครจะเล่นภาษาอื่นดูได้ที่นี่ฮะ)

Note: วิธีการตรวจสอบ Metadata ของไฟล์เสียงเรา พวก encoding หรือ sampleRateHertz ของไฟล์เสียงให้รันคำสั่ง ffprobe ใน Command line

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;
}

สุดท้ายให้เราสร้างฟังก์ชันชื่อ LineWebhook() และ reply() เพื่อรับ Webhook event ที่ถูกส่งมาจากไลน์ โดยเราจะสนใจเฉพาะกรณีที่ผู้ใช้ส่งไฟล์เสียงเข้ามา ประมวลผลต่างๆและทำการส่งผลลัพธ์กลับไปหาผู้ใช้

exports.LineWebhook = functions.https.onRequest(async (req, res) => {
if (req.method === "POST") {

let event = req.body.events[0]
console.log("Webhook: " + JSON.stringify(event));
if (event === undefined) {
return res.end();
}

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
})
});
};

จากนั้นให้เปิด Terminal หรือ Command line แล้ว cd ไปที่ /functions จากนั้นให้เรียกคำสั่งด้านล่างก็สามารถทำการ Deploy ได้เบยยย

firebase deploy --only functions

เมื่อ Deploy เรียบร้อยแล้วให้เอา URL ที่มี /LineWebhook กลับไปใส่ใน LINE Developer Console ด้วยนะครับ เท่านี้ก็เป็นอันเสร็จ!

ทดสอบ

สรุป

ประมานนี้ก่อนละกันครับสำหรับการทดสองสร้าง LINE Chatbot เพื่อแกะไฟล์เสียงภาษาไทยออกมาด้วย Google Cloud Speech-to-Text API จริงๆยังมีส่วนที่ปรับปรุงได้อีก เช่น หา lib ใหม่ในการแปลงไฟล์เป็น .wav (ที่ง่ายกว่านี้) หรือเราสามารถเอาไปต่อกับ Dialogflow ก็ได้นะ

หวังว่าบทความนี้จะทำให้นักพัฒนาสนุกและได้ไอเดียกลับบ้านไปต่อยอดพัฒนาบริการดีๆและแปลกใหม่โดยใช้ LINE API กันนะครับ โค้ดทั้งหมดอยู่ตามลิงก์นี้ ลองไปเล่นกันดู ใครชอบใจฝากกด Follow Publication ของ LINE Developers Thailand ด้วยนะครับ แล้วพบกันใหม่ครับผม 😇

--

--