1. บทนำ
ภาพรวม
ใน Codelab นี้ คุณจะต้องสร้างบริการ Cloud Run ที่เขียนด้วย Node.js ที่มีคำอธิบายเป็นภาพของทุกฉากในวิดีโอ ขั้นแรก บริการของคุณจะใช้ Video Intelligence API เพื่อตรวจหาการประทับเวลาเมื่อฉากต่างๆ เปลี่ยนไป ถัดไป บริการของคุณจะใช้ไบนารีของบุคคลที่สามที่เรียกว่า ffmpeg เพื่อจับภาพหน้าจอสำหรับการประทับเวลาการเปลี่ยนฉากแต่ละครั้ง สุดท้าย เราจะใช้คำบรรยายภาพ Vertex AI เพื่อสร้างคำอธิบายด้วยภาพของภาพหน้าจอ
Codelab นี้ยังสาธิตวิธีใช้ ffmpeg ภายในบริการ Cloud Run เพื่อจับภาพจากวิดีโอในการประทับเวลาที่ระบุ เนื่องจากต้องติดตั้ง ffmpeg แยกต่างหาก Codelab นี้จะแสดงวิธีสร้าง Dockerfile เพื่อติดตั้ง ffmpeg เป็นส่วนหนึ่งของบริการ Cloud Run
นี่เป็นภาพวิธีการทำงานของบริการ Cloud Run
สิ่งที่คุณจะได้เรียนรู้
- วิธีสร้างอิมเมจคอนเทนเนอร์โดยใช้ Dockerfile เพื่อติดตั้งไบนารีของบุคคลที่สาม
- วิธีปฏิบัติตามหลักการให้สิทธิ์ขั้นต่ำที่สุดด้วยการสร้างบัญชีบริการสำหรับบริการ Cloud Run เพื่อเรียกใช้บริการอื่นๆ ของ Google Cloud
- วิธีใช้ไลบรารีของไคลเอ็นต์ Video Intelligence จากบริการ Cloud Run
- วิธีเรียกใช้ Google APIs เพื่อดูคำอธิบายภาพของแต่ละฉากจาก Vertex AI
2. การตั้งค่าและข้อกำหนด
ข้อกำหนดเบื้องต้น
- คุณเข้าสู่ระบบ Cloud Console แล้ว
- คุณได้ทำให้บริการ Cloud Run ใช้งานได้ก่อนหน้านี้ ตัวอย่างเช่น คุณทำตามวิธีทำให้บริการเว็บใช้งานได้จากการเริ่มต้นอย่างรวดเร็วสำหรับซอร์สโค้ดเพื่อเริ่มต้นใช้งาน
เปิดใช้งาน Cloud Shell
- คลิกเปิดใช้งาน Cloud Shell จาก Cloud Console
หากเริ่มต้นใช้งาน Cloud Shell เป็นครั้งแรก คุณจะเห็นหน้าจอตรงกลางที่อธิบายว่านี่คืออะไร หากระบบแสดงหน้าจอตรงกลาง ให้คลิกต่อไป
การจัดสรรและเชื่อมต่อกับ Cloud Shell ใช้เวลาเพียงไม่กี่นาที
เครื่องเสมือนนี้โหลดด้วยเครื่องมือการพัฒนาทั้งหมดที่จำเป็น โดยมีไดเรกทอรีหลักขนาด 5 GB ถาวรและทำงานใน Google Cloud ซึ่งช่วยเพิ่มประสิทธิภาพของเครือข่ายและการตรวจสอบสิทธิ์ได้อย่างมาก งานส่วนใหญ่ใน Codelab นี้สามารถทำได้โดยใช้เบราว์เซอร์
เมื่อเชื่อมต่อกับ Cloud Shell แล้ว คุณควรเห็นข้อความตรวจสอบสิทธิ์และโปรเจ็กต์ได้รับการตั้งค่าเป็นรหัสโปรเจ็กต์แล้ว
- เรียกใช้คำสั่งต่อไปนี้ใน Cloud Shell เพื่อยืนยันว่าคุณได้รับการตรวจสอบสิทธิ์แล้ว
gcloud auth list
เอาต์พุตจากคำสั่ง
Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com> To set the active account, run: $ gcloud config set account `ACCOUNT`
- เรียกใช้คำสั่งต่อไปนี้ใน Cloud Shell เพื่อยืนยันว่าคำสั่ง gcloud รู้เกี่ยวกับโปรเจ็กต์ของคุณ
gcloud config list project
เอาต์พุตจากคำสั่ง
[core] project = <PROJECT_ID>
หากไม่ใช่ ให้ตั้งคำสั่งด้วยคำสั่งนี้
gcloud config set project <PROJECT_ID>
เอาต์พุตจากคำสั่ง
Updated property [core/project].
3. เปิดใช้ API และตั้งค่าตัวแปรสภาพแวดล้อม
ก่อนที่คุณจะเริ่มใช้ Codelab นี้ได้ คุณจะต้องเปิดใช้ API หลายรายการ Codelab นี้ต้องใช้ API ต่อไปนี้ คุณเปิดใช้ API เหล่านั้นได้โดยเรียกใช้คำสั่งต่อไปนี้
gcloud services enable run.googleapis.com \ storage.googleapis.com \ cloudbuild.googleapis.com \ videointelligence.googleapis.com \ aiplatform.googleapis.com
จากนั้นคุณจะตั้งค่าตัวแปรสภาพแวดล้อมที่จะใช้ทั่วทั้ง Codelab นี้ได้
REGION=<YOUR-REGION> PROJECT_ID=<YOUR-PROJECT-ID> PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)') SERVICE_NAME=video-describer export BUCKET_ID=$PROJECT_ID-video-describer
4. สร้างที่เก็บข้อมูล Cloud Storage
สร้างที่เก็บข้อมูล Cloud Storage ที่คุณอัปโหลดวิดีโอเพื่อประมวลผลโดยบริการ Cloud Run ได้ด้วยคำสั่งต่อไปนี้
gsutil mb -l us-central1 gs://$BUCKET_ID/
[ไม่บังคับ] คุณใช้วิดีโอตัวอย่างนี้ได้โดยการดาวน์โหลดในเครื่อง
gsutil cp gs://cloud-samples-data/video/visionapi.mp4 testvideo.mp4
อัปโหลดไฟล์วิดีโอไปยังที่เก็บข้อมูลของพื้นที่เก็บข้อมูลได้เลย
FILENAME=<YOUR-VIDEO-FILENAME> gsutil cp $FILENAME gs://$BUCKET_ID
5. สร้างแอป Node.js
ขั้นแรก ให้สร้างไดเรกทอรีสำหรับซอร์สโค้ดและ cd ลงในไดเรกทอรีนั้น
mkdir video-describer && cd $_
จากนั้นสร้างไฟล์package.json ด้วยเนื้อหาต่อไปนี้
{ "name": "video-describer", "version": "1.0.0", "private": true, "description": "describes the image in every scene for a given video", "main": "index.js", "author": "Google LLC", "license": "Apache-2.0", "scripts": { "start": "node index.js" }, "dependencies": { "@google-cloud/storage": "^7.7.0", "@google-cloud/video-intelligence": "^5.0.1", "axios": "^1.6.2", "express": "^4.18.2", "fluent-ffmpeg": "^2.1.2", "google-auth-library": "^9.4.1" } }
แอปนี้ประกอบด้วยไฟล์ต้นฉบับหลายไฟล์เพื่อให้อ่านง่ายขึ้น ก่อนอื่น ให้สร้างไฟล์ต้นฉบับ index.js ที่มีเนื้อหาด้านล่างนี้ ไฟล์นี้ประกอบด้วยจุดแรกเข้าสำหรับบริการและประกอบด้วยตรรกะหลักสำหรับแอป
const { captureImages } = require('./imageCapture.js'); const { detectSceneChanges } = require('./sceneDetector.js'); const transcribeScene = require('./imageDescriber.js'); const { Storage } = require('@google-cloud/storage'); const fs = require('fs').promises; const path = require('path'); const express = require('express'); const app = express(); const bucketName = process.env.BUCKET_ID; const port = parseInt(process.env.PORT) || 8080; app.listen(port, () => { console.log(`video describer service ready: listening on port ${port}`); }); // entry point for the service app.get('/', async (req, res) => { try { // download the requested video from Cloud Storage let videoFilename = req.query.filename; console.log("processing file: " + videoFilename); // download the file to locally to the Cloud Run instance let localFilename = await downloadVideoFile(videoFilename); // detect all the scenes in the video & save timestamps to an array let timestamps = await detectSceneChanges(localFilename); console.log("Detected scene changes at the following timestamps: ", timestamps); // create an image of each scene change // and save to a local directory called "output" await captureImages(localFilename, timestamps); // get an access token for the Service Account to call the Google APIs let accessToken = await transcribeScene.getAccessToken(); console.log("got an access token"); let imageBaseName = path.parse(localFilename).name; // the data structure for storing the scene description and timestamp // e.g. an array of json objects {timestamp: 1, description: "..."}, etc. let scenes = [] // for each timestamp, send the image to Vertex AI console.log("getting Vertex AI description all the timestamps"); scenes = await Promise.all( timestamps.map(async (timestamp) => { let filepath = path.join("./output", imageBaseName + "-" + timestamp + ".png"); // get the base64 encoded image const encodedFile = await fs.readFile(filepath, 'base64'); // send each screenshot to Vertex AI for description let description = await transcribeScene.transcribeScene(accessToken, encodedFile) return { timestamp: timestamp, description: description }; })); console.log("finished collecting all the scenes"); //console.log(scenes); return res.json(scenes); } catch (error) { //return an error console.log("received error: ", error); return res.status(500).json("an internal error occurred"); } }); async function downloadVideoFile(videoFilename) { // Creates a client const storage = new Storage(); // keep same name locally let localFilename = videoFilename; const options = { destination: localFilename }; // Download the file await storage.bucket(bucketName).file(videoFilename).download(options); console.log( `gs://${bucketName}/${videoFilename} downloaded locally to ${localFilename}.` ); return localFilename; }
จากนั้นสร้างไฟล์ sceneDetector.js ที่มีเนื้อหาต่อไปนี้ ไฟล์นี้ใช้ Video Intelligence API เพื่อตรวจจับเมื่อฉากต่างๆ ในวิดีโอมีการเปลี่ยนแปลง
const fs = require('fs'); const util = require('util'); const readFile = util.promisify(fs.readFile); const ffmpeg = require('fluent-ffmpeg'); const Video = require('@google-cloud/video-intelligence'); const client = new Video.VideoIntelligenceServiceClient(); module.exports = { detectSceneChanges: async function (downloadedFile) { // Reads a local video file and converts it to base64 const file = await readFile(downloadedFile); const inputContent = file.toString('base64'); // setup request for shot change detection const videoContext = { speechTranscriptionConfig: { languageCode: 'en-US', enableAutomaticPunctuation: true, }, }; const request = { inputContent: inputContent, features: ['SHOT_CHANGE_DETECTION'], }; // Detects camera shot changes const [operation] = await client.annotateVideo(request); console.log('Shot (scene) detection in progress...'); const [operationResult] = await operation.promise(); // Gets shot changes const shotChanges = operationResult.annotationResults[0].shotAnnotations; console.log("Shot (scene) changes detected: " + shotChanges.length); // data structure to be returned let sceneChanges = []; // for the initial scene sceneChanges.push(1); // if only one scene, keep at 1 second if (shotChanges.length === 1) { return sceneChanges; } // get length of video const videoLength = await getVideoLength(downloadedFile); shotChanges.forEach((shot, shotIndex) => { if (shot.endTimeOffset === undefined) { shot.endTimeOffset = {}; } if (shot.endTimeOffset.seconds === undefined) { shot.endTimeOffset.seconds = 0; } if (shot.endTimeOffset.nanos === undefined) { shot.endTimeOffset.nanos = 0; } // convert to a number let currentTimestampSecond = Number(shot.endTimeOffset.seconds); let sceneChangeTime = 0; // double-check no scenes were detected within the last second if (currentTimestampSecond + 1 > videoLength) { sceneChangeTime = currentTimestampSecond; } else { // otherwise, for simplicity, just round up to the next second sceneChangeTime = currentTimestampSecond + 1; } sceneChanges.push(sceneChangeTime); }); return sceneChanges; } } async function getVideoLength(localFile) { let getLength = util.promisify(ffmpeg.ffprobe); let length = await getLength(localFile); console.log("video length: ", length.format.duration); return length.format.duration; }
จากนั้นสร้างไฟล์ชื่อ imageCapture.js โดยมีเนื้อหาต่อไปนี้ ไฟล์นี้ใช้แพ็กเกจโหนด fluent-ffmpeg เพื่อเรียกใช้คำสั่ง ffmpeg จากภายในแอปโหนด
const ffmpeg = require('fluent-ffmpeg'); const path = require('path'); const util = require('util'); module.exports = { captureImages: async function (localFile, scenes) { let imageBaseName = path.parse(localFile).name; try { for (scene of scenes) { console.log("creating screenshot for scene: ", + scene); await createScreenshot(localFile, imageBaseName, scene); } } catch (error) { console.log("error gathering screenshots: ", error); } console.log("finished gathering the screenshots"); } } async function createScreenshot(localFile, imageBaseName, scene) { return new Promise((resolve, reject) => { ffmpeg(localFile) .screenshots({ timestamps: [scene], filename: `${imageBaseName}-${scene}.png`, folder: 'output', size: '320x240' }).on("error", () => { console.log("Failed to create scene for timestamp: " + scene); return reject('Failed to create scene for timestamp: ' + scene); }) .on("end", () => { return resolve(); }); }) }
สุดท้าย ให้สร้างไฟล์ชื่อ `image descriptionr.js`` ด้วยเนื้อหาต่อไปนี้ ไฟล์นี้ใช้ Vertex AI เพื่อรับคำอธิบายด้วยภาพของรูปภาพฉากแต่ละภาพ
const axios = require("axios"); const { GoogleAuth } = require('google-auth-library'); const auth = new GoogleAuth({ scopes: 'https://www.googleapis.com/auth/cloud-platform' }); module.exports = { getAccessToken: async function () { return await auth.getAccessToken(); }, transcribeScene: async function(token, encodedFile) { let projectId = await auth.getProjectId(); let config = { headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json; charset=utf-8' } } const json = { "instances": [ { "image": { "bytesBase64Encoded": encodedFile } } ], "parameters": { "sampleCount": 1, "language": "en" } } let response = await axios.post('https://us-central1-aiplatform.googleapis.com/v1/projects/' + projectId + '/locations/us-central1/publishers/google/models/imagetext:predict', json, config); return response.data.predictions[0]; } }
สร้าง Dockerfile และไฟล์ .dockerignore
เนื่องจากบริการนี้ใช้ ffmpeg คุณจึงต้องสร้าง Dockerfile ที่ติดตั้ง ffmpeg
สร้างไฟล์ชื่อ Dockerfile
ที่มีเนื้อหาต่อไปนี้
# Copyright 2020 Google, LLC. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Use the official lightweight Node.js image. # https://hub.docker.com/_/node FROM node:20.10.0-slim # Create and change to the app directory. WORKDIR /usr/src/app RUN apt-get update && apt-get install -y ffmpeg # Copy application dependency manifests to the container image. # A wildcard is used to ensure both package.json AND package-lock.json are copied. # Copying this separately prevents re-running npm install on every code change. COPY package*.json ./ # Install dependencies. # If you add a package-lock.json speed your build by switching to 'npm ci'. # RUN npm ci --only=production RUN npm install --production # Copy local code to the container image. COPY . . # Run the web service on container startup. CMD [ "npm", "start" ]
และสร้างไฟล์ชื่อ .dockerignore เพื่อละเว้นการสร้างคอนเทนเนอร์ไฟล์บางไฟล์
Dockerfile .dockerignore node_modules npm-debug.log
6. สร้างบัญชีบริการ
คุณจะสร้างบัญชีบริการสำหรับบริการ Cloud Run เพื่อใช้เข้าถึง Cloud Storage, Vertex AI และ Video Intelligence API
SERVICE_ACCOUNT="cloud-run-video-description" SERVICE_ACCOUNT_ADDRESS=$SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com gcloud iam service-accounts create $SERVICE_ACCOUNT \ --display-name="Cloud Run Video Scene Image Describer service account" # to view & download storage bucket objects gcloud projects add-iam-policy-binding $PROJECT_ID \ --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \ --role=roles/storage.objectViewer # to call the Vertex AI imagetext model gcloud projects add-iam-policy-binding $PROJECT_ID \ --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \ --role=roles/aiplatform.user
7. ทำให้บริการ Cloud Run ใช้งานได้
ตอนนี้คุณสามารถใช้การทำให้ใช้งานได้ตามแหล่งที่มาเพื่อสร้างคอนเทนเนอร์สำหรับบริการ Cloud Run โดยอัตโนมัติแล้ว
หมายเหตุ: เวลาประมวลผลเริ่มต้นสำหรับบริการ Cloud Run คือ 60 วินาที Codelab นี้ใช้ระยะหมดเวลา 5 นาทีเนื่องจากวิดีโอทดสอบที่แนะนำมีความยาว 2 นาที คุณอาจต้องแก้ไขเวลาหากใช้วิดีโอที่มีระยะเวลานานกว่า
gcloud run deploy $SERVICE_NAME \ --region=$REGION \ --set-env-vars BUCKET_ID=$BUCKET_ID \ --no-allow-unauthenticated \ --service-account $SERVICE_ACCOUNT_ADDRESS \ --timeout=5m \ --source=.
เมื่อทำให้ใช้งานได้แล้ว ให้บันทึก URL ของบริการในตัวแปรสภาพแวดล้อม
SERVICE_URL=$(gcloud run services describe $SERVICE_NAME --platform managed --region $REGION --format 'value(status.url)')
8. เรียกใช้บริการ Cloud Run
ตอนนี้คุณเรียกใช้บริการได้โดยระบุชื่อของวิดีโอที่อัปโหลดไปยัง Cloud Storage
curl -X GET -H "Authorization: Bearer $(gcloud auth print-identity-token)" ${SERVICE_URL}?filename=${FILENAME}
ผลลัพธ์ควรมีลักษณะคล้ายกับตัวอย่างเอาต์พุตด้านล่าง
[{"timestamp":1,"description":"an aerial view of a city with a bridge in the background"},{"timestamp":7,"description":"a man in a blue shirt sits in front of shelves of donuts"},{"timestamp":11,"description":"a black and white photo of people working in a bakery"},{"timestamp":12,"description":"a black and white photo of a man and woman working in a bakery"}]
9. ยินดีด้วย
ขอแสดงความยินดีที่เรียน Codelab จนจบ
เราขอแนะนำให้อ่านเอกสารประกอบเกี่ยวกับ Video Intelligence API, Cloud Run และคำบรรยายแทนเสียงแบบภาพ Vertex AI
หัวข้อที่ครอบคลุม
- วิธีสร้างอิมเมจคอนเทนเนอร์โดยใช้ Dockerfile เพื่อติดตั้งไบนารีของบุคคลที่สาม
- วิธีปฏิบัติตามหลักการให้สิทธิ์ขั้นต่ำที่สุดด้วยการสร้างบัญชีบริการสำหรับบริการ Cloud Run เพื่อเรียกใช้บริการอื่นๆ ของ Google Cloud
- วิธีใช้ไลบรารีของไคลเอ็นต์ Video Intelligence จากบริการ Cloud Run
- วิธีเรียกใช้ Google APIs เพื่อดูคำอธิบายภาพของแต่ละฉากจาก Vertex AI
10. ล้างข้อมูล
เพื่อหลีกเลี่ยงการเรียกเก็บเงินที่ไม่ตั้งใจ (เช่น หากมีการเรียกใช้บริการ Cloud Run นี้โดยไม่ได้ตั้งใจมากกว่าการจัดสรรการเรียกใช้ Cloud Run รายเดือนในรุ่นฟรี) คุณจะลบบริการ Cloud Run หรือลบโปรเจ็กต์ที่คุณสร้างในขั้นตอนที่ 2 ก็ได้
หากต้องการลบบริการ Cloud Run ให้ไปที่ Cloud Run บน Cloud Console ที่ https://console.cloud.google.com/run/ แล้วลบฟังก์ชัน video-describer
(หรือ $SERVICE_NAME ในกรณีที่คุณใช้ชื่ออื่น)
หากเลือกลบทั้งโปรเจ็กต์ ให้ไปที่ https://console.cloud.google.com/cloud-resource-manager เลือกโปรเจ็กต์ที่คุณสร้างในขั้นตอนที่ 2 แล้วเลือกลบ หากลบโปรเจ็กต์ คุณจะต้องเปลี่ยนโปรเจ็กต์ใน Cloud SDK คุณสามารถดูรายการโปรเจ็กต์ที่ใช้ได้ทั้งหมดโดยเรียกใช้ gcloud projects list