1. Giriş
Genel Bakış
Bu codelab'de, Node.js'de yazılmış ve videodaki her sahnenin görsel açıklamasını sağlayan bir Cloud Run İşi oluşturacaksınız. İlk olarak, işiniz bir sahne değişikliği olduğunda zaman damgalarını algılamak için Video Intelligence API'yi kullanır. Daha sonra, işiniz her sahne değişikliği zaman damgası için ekran görüntüsü yakalamak üzere ffmpeg adlı bir üçüncü taraf ikili programını kullanır. Son olarak, ekran görüntülerinin görsel açıklamasını sağlamak için Vertex AI görsel altyazı özelliği kullanıldı.
Bu codelab'de, belirli bir zaman damgasında bir videodan görüntü yakalamak için Cloud Run İşinizde ffmpeg'in nasıl kullanılacağı da gösterilmektedir. ffmpeg'in bağımsız olarak yüklenmesi gerektiğinden bu codelab'de, Cloud Run İşinizin bir parçası olarak ffmpeg'i yüklemek için nasıl Dockerfile oluşturacağınızı öğreneceksiniz.
Aşağıda, Cloud Run İşi'nin işleyiş şeklini görebilirsiniz:
Neler öğreneceksiniz?
- Üçüncü taraf ikili programı yüklemek için Dockerfile kullanarak container görüntüsü oluşturma
- Diğer Google Cloud hizmetlerini çağırmak amacıyla Cloud Run İşi için bir hizmet hesabı oluşturarak en az ayrıcalık ilkesini uygulama
- Cloud Run İşinden Video Intelligence istemci kitaplığını kullanma
- Vertex AI'dan her sahnenin görsel açıklamasını almak için Google API'lerine çağrı yapma
2. Kurulum ve Gereksinimler
Ön koşullar
- Cloud Console'a giriş yaptınız.
- Daha önce bir Cloud Run hizmeti dağıttınız. Örneğin, başlamak için kaynak kodundan web hizmeti dağıtma başlıklı makaledeki adımları uygulayabilirsiniz.
Cloud Shell'i etkinleştirme
- Cloud Console'da, Cloud Shell'i etkinleştir
simgesini tıklayın.
Cloud Shell'i ilk kez başlatıyorsanız ne olduğunu açıklayan bir ara ekran gösterilir. Ara bir ekran görüntülendiyse Devam'ı tıklayın.
Temel hazırlık ve Cloud Shell'e bağlanmak yalnızca birkaç dakika sürer.
Gereken tüm geliştirme araçları bu sanal makinede yüklüdür. 5 GB boyutunda kalıcı bir ana dizin sunar ve Google Cloud'da çalışarak ağ performansını ve kimlik doğrulamasını büyük ölçüde iyileştirir. Bu codelab'deki çalışmalarınızın tamamı olmasa bile büyük bir kısmı tarayıcıyla yapılabilir.
Cloud Shell'e bağlandıktan sonra kimliğinizin doğrulandığını ve projenin proje kimliğinize ayarlandığını göreceksiniz.
- Kimlik doğrulamanızın tamamlandığını onaylamak için Cloud Shell'de aşağıdaki komutu çalıştırın:
gcloud auth list
Komut çıkışı
Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com> To set the active account, run: $ gcloud config set account `ACCOUNT`
- gcloud komutunun projenizi bildiğini onaylamak için Cloud Shell'de aşağıdaki komutu çalıştırın:
gcloud config list project
Komut çıkışı
[core] project = <PROJECT_ID>
Doğru değilse aşağıdaki komutla ayarlayabilirsiniz:
gcloud config set project <PROJECT_ID>
Komut çıkışı
Updated property [core/project].
3. API'leri Etkinleştirin ve Ortam Değişkenlerini Ayarlayın
Bu codelab'i kullanmaya başlamadan önce etkinleştirmeniz gereken birkaç API vardır. Bu codelab'de aşağıdaki API'lerin kullanılması gerekir. Bu API'leri şu komutu çalıştırarak etkinleştirebilirsiniz:
gcloud services enable run.googleapis.com \ storage.googleapis.com \ cloudbuild.googleapis.com \ videointelligence.googleapis.com \ aiplatform.googleapis.com
Ardından, bu codelab boyunca kullanılacak ortam değişkenlerini ayarlayabilirsiniz.
REGION=<YOUR-REGION> PROJECT_ID=<YOUR-PROJECT-ID> PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)') JOB_NAME=video-describer-job BUCKET_ID=$PROJECT_ID-video-describer SERVICE_ACCOUNT="cloud-run-job-video" SERVICE_ACCOUNT_ADDRESS=$SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com
4. Hizmet Hesabı oluşturma
Cloud Storage, Vertex AI ve Video Intelligence API'ye erişmek amacıyla kullanmak üzere Cloud Run İşi için bir hizmet hesabı oluşturacaksınız.
İlk olarak hizmet hesabını oluşturun.
gcloud iam service-accounts create $SERVICE_ACCOUNT \ --display-name="Cloud Run Video Scene Image Describer service account"
Ardından hizmet hesabına Cloud Storage paketine ve Vertex AI API'lerine erişim izni verin.
# 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
5. Cloud Storage paketi oluşturma
Aşağıdaki komutla Cloud Run İşi tarafından işlenmek üzere video yükleyebileceğiniz bir Cloud Storage paketi oluşturun:
gsutil mb -l us-central1 gs://$BUCKET_ID/
[İsteğe bağlı] Bu örnek videoyu yerel olarak indirerek kullanabilirsiniz.
gsutil cp gs://cloud-samples-data/video/visionapi.mp4 testvideo.mp4
Şimdi video dosyanızı depolama paketinize yükleyin.
FILENAME=<YOUR-VIDEO-FILENAME> gsutil cp $FILENAME gs://$BUCKET_ID
6. Cloud Run İşini Oluşturma
İlk olarak, kaynak kodu için bir dizin oluşturun ve bu dizin için cd'yi kullanın.
mkdir video-describer-job && cd $_
Ardından, aşağıdaki içeriğe sahip bir package.json
dosyası oluşturun:
{ "name": "video-describer-job", "version": "1.0.0", "private": true, "description": "describes the image in every scene for a given video", "main": "app.js", "author": "Google LLC", "license": "Apache-2.0", "scripts": { "start": "node app.js" }, "dependencies": { "@google-cloud/storage": "^7.7.0", "@google-cloud/video-intelligence": "^5.0.1", "axios": "^1.6.2", "fluent-ffmpeg": "^2.1.2", "google-auth-library": "^9.4.1" } }
Bu uygulama, okunabilirliği artırmak için çeşitli kaynak dosyalardan oluşur. İlk olarak aşağıdaki içerikle bir app.js
kaynak dosyası oluşturun. Bu dosya işin giriş noktasını ve uygulamanın ana mantığını içerir.
const bucketName = "<YOUR_BUCKET_ID>"; const videoFilename = "<YOUR-VIDEO-FILENAME>"; const { captureImages } = require("./helpers/imageCapture.js"); const { detectSceneChanges } = require("./helpers/sceneDetector.js"); const { getImageCaption } = require("./helpers/imageCaptioning.js"); const storageHelper = require("./helpers/storage.js"); const authHelper = require("./helpers/auth.js"); const fs = require("fs").promises; const path = require("path"); const main = async () => { try { // download the file to locally to the Cloud Run Job instance let localFilename = await storageHelper.downloadVideoFile( bucketName, videoFilename ); // PART 1 - Use Video Intelligence API // detect all the scenes in the video & save timestamps to an array // EXAMPLE OUTPUT // Detected scene changes at the following timestamps: // [1, 7, 11, 12] let timestamps = await detectSceneChanges(localFilename); console.log( "Detected scene changes at the following timestamps: ", timestamps ); // PART 2 - Use ffmpeg via dockerfile install // create an image of each scene change // and save to a local directory called "output" // returns the base filename for the generated images // EXAMPLE OUTPUT // creating screenshot for scene: 1 at output/video-filename-1.png // creating screenshot for scene: 7 at output/video-filename-7.png // creating screenshot for scene: 11 at output/video-filename-11.png // creating screenshot for scene: 12 at output/video-filename-12.png // returns the base filename for the generated images let imageBaseName = await captureImages(localFilename, timestamps); // PART 3a - get Access Token to call Vertex AI APIs via REST // needed for the image captioning // since we're calling the Vertex AI APIs directly let accessToken = await authHelper.getAccessToken(); console.log("got an access token"); // PART 3b - use Image Captioning to describe each scene per screenshot // EXAMPLE OUTPUT /* [ { 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" } ]; */ // instantiate the data structure for storing the scene description and timestamp // e.g. an array of json objects, // [{ timestamp: 5, description: "..." }, ...] let scenes = []; // for each timestamp, send the image to Vertex AI console.log("getting Vertex AI description for each timestamps"); scenes = await Promise.all( timestamps.map(async (timestamp) => { let filepath = path.join( "./output", imageBaseName + "-" + timestamp + ".png" ); // get the base64 encoded image bc sending via REST const encodedFile = await fs.readFile(filepath, "base64"); // send each screenshot to Vertex AI for description let description = await getImageCaption( accessToken, encodedFile ); return { timestamp: timestamp, description: description }; }) ); console.log("finished collecting all the scenes"); console.log(scenes); } catch (error) { //return an error console.error("received error: ", error); } }; // Start script main().catch((err) => { console.error(err); });
Sonra, Dockerfile
oluşturun.
# 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 job on container startup. CMD [ "npm", "start" ]
Ayrıca, belirli dosyaları container mimarisine almamak için .dockerignore
adlı bir dosya da oluşturabilirsiniz.
Dockerfile .dockerignore node_modules npm-debug.log
Şimdi helpers
adlı bir klasör oluşturun. Bu klasörde 5 yardımcı dosya yer alır.
mkdir helpers cd helpers
Sonra, aşağıdaki içeriğe sahip bir sceneDetector.js
dosyası oluşturun. Bu dosya, videodaki sahnelerin değiştiğini algılamak için Video Intelligence API'yi kullanır.
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; }
Şimdi aşağıdaki içeriğe sahip imageCapture.js
adlı bir dosya oluşturun. Bu dosya, ffmpeg komutlarını bir düğüm uygulaması içinden çalıştırmak için fluent-ffmpeg düğüm paketini kullanır.
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"); return imageBaseName; // return the base filename for each image } }; 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(); }); }); }
Son olarak, aşağıdaki içeriğe sahip imageCaptioning.js
adlı bir dosya oluşturun. Bu dosyada, her sahne görüntüsünün görsel açıklamasını almak için Vertex AI kullanılır.
const axios = require("axios"); const { GoogleAuth } = require("google-auth-library"); const auth = new GoogleAuth({ scopes: "https://www.googleapis.com/auth/cloud-platform" }); module.exports = { getImageCaption: async function (token, encodedFile) { // this example shows you how to call the Vertex REST APIs directly // https://cloud.google.com/vertex-ai/generative-ai/docs/image/image-captioning#get-captions-short // https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/image-captioning 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]; } };
auth.js
adlı bir dosya oluşturun. Bu dosya, Vertex AI uç noktalarını doğrudan çağırmak için gereken erişim jetonunu almak amacıyla Google kimlik doğrulama istemci kitaplığını kullanır.
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(); } };
Son olarak, storage.js
adlı bir dosya oluşturun. Bu dosya, Cloud Storage'dan video indirmek için Cloud Storage istemci kitaplıkları kullanılacak.
const { Storage } = require("@google-cloud/storage"); module.exports = { downloadVideoFile: async function (bucketName, 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; } };
7. Cloud Run İşini Dağıtma ve Yürütme
Öncelikle, codelab'inizin kök dizininde (video-describer-job
) olduğunuzdan emin olun.
cd .. && pwd
Ardından, Cloud Run İşini dağıtmak için bu komutu kullanabilirsiniz.
gcloud run jobs deploy $JOB_NAME --source . --region $REGION
Şimdi, aşağıdaki komutu çalıştırarak Cloud Run İşini yürütebilirsiniz:
gcloud run jobs execute $JOB_NAME
İşin yürütülmesi tamamlandıktan sonra, günlük URI'sının bağlantısını almak için aşağıdaki komutu çalıştırabilirsiniz. (Dilerseniz günlükleri görmek için Cloud Console'u kullanıp doğrudan Cloud Run İşleri'ne de gidebilirsiniz.)
gcloud run jobs executions describe <JOB_EXECUTION_ID>
Günlüklerde aşağıdaki çıkışı göreceksiniz:
[{ timestamp: 1, description: 'what is google cloud vision api ? is written on a white background .'}, { timestamp: 3, description: 'a woman wearing a google cloud vision api shirt sits at a table'}, { timestamp: 18, description: 'a person holding a cell phone with the words what is cloud vision api on the bottom' }, ...]
8. Tebrikler!
Tebrikler, codelab'i tamamladınız.
Video Intelligence API, Cloud Run ve Vertex AI görsel altyazısı ile ilgili belgeleri incelemenizi öneririz.
İşlediklerimiz
- Üçüncü taraf ikili programı yüklemek için Dockerfile kullanarak container görüntüsü oluşturma
- Diğer Google Cloud hizmetlerini çağırmak amacıyla Cloud Run İşi için bir hizmet hesabı oluşturarak en az ayrıcalık ilkesini uygulama
- Cloud Run İşinden Video Intelligence istemci kitaplığını kullanma
- Vertex AI'dan her sahnenin görsel açıklamasını almak için Google API'lerine çağrı yapma
9. Temizleme
Yanlışlıkla yapılan ücretleri önlemek için (örneğin, bu Cloud Run işi yanlışlıkla ücretsiz katmandaki aylık Cloud Run çağırma tahsisinizden daha fazla kez çağrıldıysa) Cloud Run işini veya 2. adımda oluşturduğunuz projeyi silebilirsiniz.
Cloud Run işini silmek için https://console.cloud.google.com/run/ adresinden Cloud Run Cloud Console'a gidip video-describer-job
işlevini (veya farklı bir ad kullandıysanız $JOB_NAME) silin.
Projenin tamamını silmeyi tercih ederseniz https://console.cloud.google.com/cloud-resource-manager adresine gidip 2. adımda oluşturduğunuz projeyi, ardından Sil'i seçebilirsiniz. Projeyi silerseniz Cloud SDK'nızdaki projeleri değiştirmeniz gerekir. gcloud projects list
komutunu çalıştırarak mevcut tüm projelerin listesini görüntüleyebilirsiniz.