ใน 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

สิ่งที่คุณจะได้เรียนรู้

สิ่งที่คุณต้องเตรียมพร้อมก่อนเริ่ม Codelab

สมัครเป็น LINE Developer

จุดเริ่มขบวนสำหรับการพัฒนาแอปพลิเคชันต่างๆบนแพลตฟอร์มของ LINE คือคุณจะต้องสมัครเป็น LINE Developer ก่อน

  1. เข้าไปที่ https://developers.line.biz/console/ แล้วเลือก Log in with LINE account(สีเขียว) เพื่อเข้าสู่ระบบ

  1. เข้าสู่ระบบด้วยบัญชี LINE ของคุณให้เรียบร้อย
  2. กรอกชื่อและอีเมล พร้อมกดยอมรับ Agreement จากนั้นกดปุ่ม Create my account เป็นอันเสร็จสิ้นขั้นตอนการสมัครเป็น LINE Developer


สร้าง Provider

Provider คือชื่อผู้ให้บริการ ซึ่งจะไปแสดงตามหน้า consent ต่างๆ หรือเรียกได้ว่าเป็น superset ของแอปทั้งหลายที่เราจะพัฒนาขึ้นรวมถึง LIFF app ด้วย โดยการสร้างเพียงให้ระบุชื่อของ Provider ลงไป ซึ่งอาจจะตั้งเป็นชื่อตัวเอง, ชื่อบริษัท, ชื่อทีม หรือชื่อกลุ่มก็ได้


สร้าง Channel

Channel เปรียบเสมือนแอป หรือเรียกได้ว่าเป็น subset ของ Provider โดยมีอยู่ 3 รูปแบบ คือ LINE Login, Messaging API และ Clova Skill

  1. สำหรับการพัฒนา Chatbot เราจะต้องเลือก Create a Messaging API channel

  1. เมื่อกดเลือก Messaging API channel จะเข้าสู่หน้าที่ให้ระบุรายละเอียดต่างๆลงไป แล้วกดสร้าง


เพิ่ม Chatbot เป็นเพื่อนและตั้งค่า Channel

  1. หลังจากกดสร้าง Channel แล้ว ให้ไปที่ Tab ชื่อ Messaging API และทำการแสกน QR code ด้วยแอป LINE เพื่อเพิ่ม Chatbot เป็นเพื่อน

  1. ให้ปิด Auto-reply messages เนื่องจากฟีเจอร์นี้จะเป็น default การตอบกลับของ Chatbot ซึ่งไม่จำเป็นต้องใช้ฟีเจอร์นี้

  1. กลับมาที่ Channel ที่เราสร้างใน Tab ชื่อ Messaging API ตรงส่วนของ Channel access token ให้กดปุ่ม Issue

  1. ก่อนจะไปเริ่มพัฒนาเราต้องการ API Key เพื่อเอาไว้เข้าใช้งาน ZAMZAR API ก่อน อันดับแรกสุดให้ไป Sign up สมัครเข้าใช้งานที่ https://developers.zamzar.com/signup?plan=test

  1. พอสมัครเรียบร้อยแล้วให้ทำการ Login และให้เราไปที่ Your Account > Account Details เพื่อทำการ Copy API Key เอาไว้ใช้เรียก API โดยมี Free tier ในการแปลงเดือนละ 100 ไฟล์

เบื้องหลังของ Chatbot ตัวนี้ เราจะใช้บริการ Cloud Functions for Firebaseในการจัดการ Webhook

สร้างโปรเจคใน Firebase

  1. ให้ Sign in ใน Firebase console ด้วย Google account
  2. ในหน้า Firebase console ให้คลิก Add project จากนั้นตั้งชื่อโปรเจคตามต้องการ

  1. เมื่อกด Continue แล้วให้ข้ามการตั้งค่า Google Analytics ไป เพราะคุณจะไม่ได้ใช้มันในโปรเจคนี้


เปลี่ยนแพลนจาก Spark ไปเป็น Blaze (Pay as you go)

เนื่องจาก Cloud Functions for Firebase มีเงื่อนไขว่า หากต้องการไป request ตัว APIs ที่อยู่ภายนอก Google คุณจำเป็นจะต้องใช้ Blaze plan(เราจะต้องไปเรียก Messaging API ของ LINE)

  1. หลังจากสร้าง Firebase Project เสร็จแล้วเราจะได้ Project ใน Google Cloud Platform ด้วยให้เราไปทำการเปิดใช้งาน Speech-to-Text ที่นี่ https://console.cloud.google.com/apis/library/speech.googleapis.com
  2. กดปุ่ม Enable เพื่อเปิดใช้งาน

การติดตั้ง Firebase CLI

Firebase CLI เป็นเครื่องมือที่จำเป็นสำหรับการ deploy ตัวฟังก์ชันที่เราพัฒนาขึ้น อีกทั้งยังสามารถจำลองการทำงานฟังก์ชัน(Emulate) ภายในเครื่องที่เราพัฒนาอยู่(Locally) ได้

  1. เปิด Terminal ขึ้นมาแล้ว run คำสั่ง
npm install -g firebase-tools
  1. ตรวจสอบว่า Firebase CLI ได้ติดตั้งเรียบร้อยแล้วโดย run คำสั่ง (หากสำเร็จจะเห็นเลขเวอร์ชัน)
firebase --version


Initialize โปรเจค

  1. รันคำสั่งนี้ จากนั้นตัว browser จะเปิดขึ้นมาให้เราเข้าสู้ระบบด้วย Google account เดียวกันกับที่สร้างโปรดจคใน Firebase
firebase login
  1. สร้างโฟลเดอร์เปล่า(ตัวอย่างโฟลเดอร์ชื่อ bot) แล้วให้ shell เข้าไปในนั้น
mkdir speechToTextBot
cd speechToTextBot
  1. เมื่อเข้ามาในโฟลเดอร์แล้ว ให้ Initial โปรเจคด้วยคำสั่ง
firebase init functions
  1. เลือก Use an existing project จากนั้นจะเห็นเชื่อโปรเจคที่เราสร้างไว้ ให้เลือก speech-to-text-chatbot และทำการกด enter

  1. ถัดไปจะมีตัวเลือกภาษา 2 ตัวคือ JavaScript และ TypeScript โดยตัวอย่างนี้ให้เลือก JavaScript
  2. จากนั้นมันจะถามว่าจะให้ติดตั้ง ESLint ไหม ตรงนี้แนะนำให้ตอบ N ไปก่อน(สำหรับมือใหม่)
  3. สุดท้ายมันจะถามว่าจะให้ติดตั้ง dependencies เลยไหมก็ให้ตอบว่า Y ไป

เพิ่ม Dependencies ที่จำเป็นสำหรับโปรเจคนี้

  1. เปิดไฟล์ package.json ขี้นมา
  2. เพิ่ม dependency ชื่อ axios และ openai เข้าไปครับ
"dependencies": {
    "axios": "^1.3.6",
    "firebase-admin": "^11.7.0",
    "firebase-functions": "^4.3.1","@google-cloud/speech": "^5.1.0"
}
  1. ในโฟลเดอร์ที่เราสร้างมา ให้เรา shell เข้าไปต่อในโฟลเดอร์ชื่อ functions จากนั้นสั่ง Install ตัว dependencies ที่เพิ่มเข้ามาใหม่ใน terminal ด้วยคำสั่ง
npm install
  1. เปิดไฟล์ 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 หลักๆที่เราจะเรียกใช้เพื่อแปลงไฟล์จะมีอยู่ทั้งหมด 3 ตัวด้วยกันได้แก่

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

โดยวิธีการพัฒนาให้มาที่ไฟล์เดิม 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;
}
  1. สุดท้ายให้เราสร้างฟังก์ชันชื่อ LineWebhook() และ reply() เพื่อรับ Webhook event ที่ถูกส่งมาจากไลน์ โดยเราจะสนใจเฉพาะกรณีที่ผู้ใช้ส่งไฟล์เสียงเข้ามา ประมวลผลต่างๆและทำการส่งผลลัพธ์กลับไปหาผู้ใช้
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
        })
     });
};
  1. เมื่อทุกอย่างพร้อมแล้ว ก็ deploy ฟังก์ชันผ่าน terminal ด้วยคำสั่ง
firebase deploy --only functions

  1. ให้เอา URL /LineWebhook ไปใส่ใน LINE Developer Console

ผลลัพธ์

ยินดีด้วย! ถึงตรงนี้คุณก็มี LINE Chatbot เพื่อแกะไฟล์เสียง Speech-to-text เป็นของคุณเองแล้ว!!!

สิ่งที่คุณได้เรียนรู้ใน Codelab นี้

เรียนรู้เพิ่มเติม

Reference docs

บอกเราหน่อยว่า Codelab ชุดนี้เป็นอย่างไรบ้าง