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