YouTubeVideoUploadService 可讓您透過 Google Ads API 直接將影片上傳到 YouTube。這些影片可用於建立各種廣告類型的影片素材資源,例如最高成效廣告活動或需求開發廣告活動。
這項服務會處理 YouTube 上傳程序,確保影片正確連結至您的帳戶,簡化影片廣告製作流程。
核心概念
開始之前,請務必瞭解影片上傳的管理方式,以及影片可能經歷的不同狀態。
端對端工作流程
如要上傳影片檔案並在廣告中放送,請依序完成下列四個步驟:
| 步驟 | 服務和方法 | 摘要 |
|---|---|---|
| 1. 啟動並上傳影片位元組 | CreateYouTubeVideoUpload (YouTubeVideoUploadService) |
上傳影片中繼資料和二進位檔案,建立 YouTubeVideoUpload 資源 (customers/{customer_id}/youTubeVideoUploads/{video_upload_id})。 |
| 2. 輪詢處理狀態 | SearchStream (GoogleAdsService) |
查詢 you_tube_video_upload 直到 state = 'PROCESSED',並填入 11 個半形字元的 YouTube video_id。 |
| 3. 建立可重複使用的影片素材資源 | MutateAssets (AssetService) |
建立 YoutubeVideoAsset (youtube_video_id = video_id),取得素材資源名稱 (customers/{customer_id}/assets/{asset_id})。 |
| 4. 將素材資源連結至廣告活動或廣告 | AssetGroupAssetService的MutateAssetGroupAssets,或AdGroupAdService的MutateAdGroupAds |
將素材資源連結至最高成效廣告活動 AssetGroupAsset (field_type = YOUTUBE_VIDEO) 或需求開發廣告活動 DemandGenVideoResponsiveAdInfo (videos[].asset)。 |
頻道擁有權
上傳影片時,您可以使用 YouTubeVideoUpload 資源中的 channel_id 欄位,指定目的地 YouTube 頻道:
- 廣告主擁有的 (品牌) 頻道:提供廣告主現有 YouTube 頻道的
channel_id。這項功能僅支援使用者驗證流程,無法搭配服務帳戶使用。 - Google 管理的頻道:如果省略
channel_id,影片會上傳到與 Google Ads 帳戶相關聯、由 Google 管理的 YouTube 頻道。
上傳狀態
YouTube 影片上傳的生命週期是由 state 欄位追蹤。YouTubeVideoUploadState 列舉會定義下列狀態:
| 州 | 說明 |
|---|---|
PENDING |
正在上傳影片。 |
UPLOADED |
影片已成功上傳,YouTube 正在處理中。 |
PROCESSED |
影片已處理完畢,可以正常使用。 |
FAILED |
上傳或處理失敗,無法完成。 |
REJECTED |
影片因驗證或政策原因而遭拒。 |
UNAVAILABLE |
無法取得影片狀態,影片可能已從 YouTube 移除。 |
隱私權設定
video_privacy 欄位可控制誰能觀看上傳的影片。YouTubeVideoPrivacy 列舉支援:
PUBLIC:YouTube 上的任何人都能觀看影片。(僅限品牌頻道)。UNLISTED:影片不會出現在搜尋結果中,但只要有連結就能觀看。這是預設選項,也是 Google 管理管道的唯一選項。
揭露生成式內容
自第 26 版起,根據歐盟《人工智慧法》等法規,廣告主可揭露上傳的影片內容是否包含 AI 生成或合成媒體。
YouTubeVideoUpload 中的 video_content_metadata 欄位包含 VideoContentMetadata。使用 YouTubeVideoUploadGenerativeContentDisclosure 列舉設定巢狀 generative_content_disclosure 欄位:
| 值 | 說明 |
|---|---|
NON_GENERATIVE_CONTENT |
廣告主揭露影片並非由 AI 生成。 |
GENERATIVE_CONTENT |
廣告主已揭露影片為 AI 生成。 |
上傳影片
如要上傳影片,請使用多部分要求向 CreateYouTubeVideoUpload 方法提出要求。要求包含上傳的中繼資料和影片檔案本身。
1. 啟動上傳程序
使用下列欄位建構 CreateYouTubeVideoUploadRequest:
| 欄位 | 規定 | 說明和限制 |
|---|---|---|
customer_id |
必填 | 10 位數的 Google Ads 客戶 ID (不含連字號)。 |
you_tube_video_upload.video_title |
必要 (INPUT_ONLY、IMMUTABLE) |
上傳至 YouTube 的影片標題 (最多 100 個半形字元,不得包含 < 或 >)。 |
you_tube_video_upload.video_description |
選用 (INPUT_ONLY、IMMUTABLE) |
YouTube 影片說明 (最多 5,000 個半形字元,不得包含 < 或 >)。 |
you_tube_video_upload.channel_id |
選用 (IMMUTABLE) |
廣告主自有品牌頻道的目的地 24 個半形字元 YouTube 頻道 ID (開頭為 UC)。如省略,則會上傳到 Google 管理的頻道。 |
you_tube_video_upload.video_privacy |
選用 (可在品牌頻道上變更) | YouTubeVideoPrivacy 設定 (預設為 UNLISTED,廣告主擁有的品牌頻道則為 PUBLIC)。 |
you_tube_video_upload.video_content_metadata |
選用 (INPUT_ONLY、IMMUTABLE) |
自第 26 版起提供:VideoContentMetadata,其中 generative_content_disclosure 設為 YouTubeVideoUploadGenerativeContentDisclosure 值 (NON_GENERATIVE_CONTENT 或 GENERATIVE_CONTENT)。 |
如果您使用用戶端程式庫,請呼叫
CreateYouTubeVideoUpload
方法,傳遞影片檔案,系統就會在內部處理影片上傳作業。
Java
String videoUploadResourceName; try (YouTubeVideoUploadServiceClient youTubeVideoUploadServiceClient = googleAdsClient.getLatestVersion().createYouTubeVideoUploadServiceClient()) { YouTubeVideoUpload youTubeVideoUpload = YouTubeVideoUpload.newBuilder() .setVideoTitle("Test Video") .setVideoDescription("Test Video Description") .setVideoPrivacy(YouTubeVideoPrivacy.UNLISTED) .build(); CreateYouTubeVideoUploadRequest createUploadRequest = CreateYouTubeVideoUploadRequest.newBuilder() .setCustomerId(Long.toString(customerId)) .setYouTubeVideoUpload(youTubeVideoUpload) .build(); CreateYouTubeVideoUploadResponse response = youTubeVideoUploadServiceClient.createYouTubeVideoUpload( createUploadRequest, () -> new FileInputStream(videoFile), null); videoUploadResourceName = response.getResourceName(); System.out.printf("Created YouTube video upload: %s%n", videoUploadResourceName); }
C#
YouTubeVideoUploadServiceClient ytService = client.GetService( Services.V26.YouTubeVideoUploadService); CreateYouTubeVideoUploadRequest createUploadRequest = new CreateYouTubeVideoUploadRequest() { CustomerId = customerId.ToString(), YouTubeVideoUpload = new YouTubeVideoUpload() { VideoTitle = "Test Video", VideoDescription = "Test Video Description", VideoPrivacy = YouTubeVideoPrivacy.Unlisted } }; string videoUploadResourceName; using (FileStream stream = File.OpenRead(videoFilePath)) { ResumableUploadSession<CreateYouTubeVideoUploadRequest, CreateYouTubeVideoUploadResponse> session = ytService.CreateYouTubeVideoUpload(); CreateYouTubeVideoUploadResponse response = session.BeginUploadAsync(createUploadRequest, stream).Result; videoUploadResourceName = response.ResourceName; Console.WriteLine($"Created YouTube video upload: {videoUploadResourceName}"); }
PHP
$youTubeVideoUploadServiceClient = $googleAdsClient->getYouTubeVideoUploadServiceClient(); $youTubeVideoUpload = new YouTubeVideoUpload([ 'video_title' => 'Test Video', 'video_description' => 'Test Video Description', 'video_privacy' => YouTubeVideoPrivacy::UNLISTED ]); $createYouTubeVideoUploadRequest = CreateYouTubeVideoUploadRequest::build( $customerId, $youTubeVideoUpload ); /** @var ResumableUpload $resumableUpload */ $resumableUpload = $youTubeVideoUploadServiceClient->createYouTubeVideoUpload( $createYouTubeVideoUploadRequest ); $stream = Utils::streamFor(fopen($videoFilePath, 'rb')); /** @var CreateYouTubeVideoUploadResponse $response */ $response = $resumableUpload->startUpload($stream); $videoUploadResourceName = $response->getResourceName(); printf("Created YouTube video upload: '%s'%s", $videoUploadResourceName, PHP_EOL);
Python
yt_service: YouTubeVideoUploadServiceClient = client.get_service( "YouTubeVideoUploadService" ) create_upload_request: CreateYouTubeVideoUploadRequest = ( youtube_video_upload_service.CreateYouTubeVideoUploadRequest() ) create_upload_request.customer_id = customer_id create_upload_request.you_tube_video_upload.video_title = "Test Video" create_upload_request.you_tube_video_upload.video_description = ( "Test Video Description" ) create_upload_request.you_tube_video_upload.video_privacy = ( client.enums.YouTubeVideoPrivacyEnum.UNLISTED ) video_upload_resource_name: str with open(video_file_path, "rb") as stream: response: CreateYouTubeVideoUploadResponse = ( yt_service.create_you_tube_video_upload( stream=stream, request=create_upload_request, retry=None, ) ) video_upload_resource_name = response.resource_name print(f"Created YouTube video upload: {video_upload_resource_name}")
小茹
video_upload = client.resource.you_tube_video_upload do |v| v.video_title = "Test Video" v.video_description = "Test Video Description" v.video_privacy = :UNLISTED end upload = client.service.you_tube_video_upload.create_you_tube_video_upload( customer_id: customer_id, you_tube_video_upload: video_upload, ) video_upload_resource_name = nil File.open(video_file_path, "rb") do |stream| response = upload.start( stream: stream, content_type: "application/octet-stream", ) video_upload_resource_name = response.resource_name puts "Created YouTube video upload: #{video_upload_resource_name}" end
Perl
This example is not yet available in Perl; you can take a look at the other languages.
curl
# # Use the --i curl parameter to capture response headers in the $RESPONSE # variable. FILE_SIZE=$(wc -c < "${VIDEO_FILE_NAME}" | tr -d '\r') RESPONSE=$(curl -i -f -v -s --request POST \ "https://googleads.googleapis.com/resumable/upload/v${API_VERSION}/customers/${CUSTOMER_ID}/youTubeVideoUploads:create" \ --header "Content-Type: application/json" \ --header "login-customer-id: ${MANAGER_CUSTOMER_ID}" \ --header "Authorization: Bearer ${OAUTH2_ACCESS_TOKEN}" \ --header "X-Goog-Upload-Protocol: resumable" \ --header "X-Goog-Upload-Command: start" \ --header "X-Goog-Upload-Header-Content-Length: ${FILE_SIZE}" \ --data @- <<EOF { "customer_id": "${CUSTOMER_ID}", "you_tube_video_upload": { "video_title": "${VIDEO_TITLE}", "video_description": "${VIDEO_DESCRIPTION}", "video_privacy": "UNLISTED" } } EOF ) # Extract the value of the "x-goog-upload-url" header from the HTTP response. UPLOAD_URL=$(echo "${RESPONSE}" \ | grep -i '^x-goog-upload-url' \ | awk '{print $2}' \ | tr -d '\r') CHUNK_SIZE=$(echo "${RESPONSE}" \ | grep -i '^x-goog-upload-chunk-granularity' \ | awk '{print $2}' \ | tr -d '\r')
如果您使用 REST,請參閱下一節,瞭解如何管理影片上傳作業。
2. 上傳影片
使用 REST 上傳時,CreateYouTubeVideoUpload 會使用兩步驟的支援續傳的上傳作業通訊協定:
- 開始上傳工作階段:向
CreateYouTubeVideoUpload上傳端點 (/upload/v26/customers/{customer_id}/youTubeVideoUploads:create) 傳送初始POST要求,並附上x-goog-upload-command: start(以及選擇性地附上x-goog-upload-header-content-length來宣告檔案大小)。回應會在x-goog-upload-urlHTTP 回應標頭中傳回上傳工作階段網址,以及預期區塊大小等中繼資料。 - 串流並完成影片位元組:使用
x-goog-upload-command: upload, finalize將二進位影片酬載傳送至x-goog-upload-url端點。上傳完成後,回應會傳回建立的resource_name(customers/{customer_id}/youTubeVideoUploads/{video_upload_id})。
如要完整瞭解影片上傳通訊協定中使用的 HTTP 標頭,請參閱下列程式碼範例:
# Take the first ${CHUNK_SIZE} bytes of the video file and upload them. head -c ${CHUNK_SIZE} ${VIDEO_FILE_NAME} | curl -i -v -X PUT "${UPLOAD_URL}" \ --header "Authorization: Bearer ${OAUTH2_ACCESS_TOKEN}" \ --header "X-Goog-Upload-Offset: 0" \ --header "X-Goog-Upload-Command: upload" \ --header "Content-Length: ${CHUNK_SIZE}" \ --data-binary @- # Query the status of the upload. QUERY_RESPONSE=$(curl -i -s -X POST "${UPLOAD_URL}" \ --header "Authorization: Bearer ${OAUTH2_ACCESS_TOKEN}" \ --header "X-Goog-Upload-Command: query") # Extract the value of the "x-goog-upload-size-received" header from the HTTP # response. UPLOADED_BYTES=$(echo "${QUERY_RESPONSE}" \ | grep -i '^x-goog-upload-size-received' \ | awk '{print $2}' \ | tr -d '\r') echo "Uploaded ${UPLOADED_BYTES} bytes." REMAINING_BYTES=$((FILE_SIZE - UPLOADED_BYTES)) echo "${REMAINING_BYTES} bytes remaining to upload." FINALIZE_RESPONSE=$(tail -c ${REMAINING_BYTES} ${VIDEO_FILE_NAME} | curl -v -X PUT "${UPLOAD_URL}" \ --header "Authorization: Bearer ${OAUTH2_ACCESS_TOKEN}" \ --header "X-Goog-Upload-Offset: ${UPLOADED_BYTES}" \ --header "X-Goog-Upload-Command: upload, finalize" \ --data-binary @-) UPLOADED_VIDEO_RESOURCE_NAME=$(echo $FINALIZE_RESPONSE | jq -r '.resourceName')
3. 擷取影片上傳狀態
開始上傳影片後,請查詢 you_tube_video_upload 資源的可選取欄位 (resource_name、video_upload_id、channel_id、video_id、state 和 video_privacy),藉此輪詢處理狀態,直到 state = 'PROCESSED' (無法在 GAQL 中選取 INPUT_ONLY 欄位):
Java
// Retrieve the metadata of the newly uploaded video. String query = String.format( "SELECT " + "you_tube_video_upload.resource_name, " + "you_tube_video_upload.video_id, " + "you_tube_video_upload.state " + "FROM you_tube_video_upload " + "WHERE you_tube_video_upload.resource_name = '%s'", videoUploadResourceName); try (GoogleAdsServiceClient googleAdsServiceClient = googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) { SearchGoogleAdsStreamRequest request = SearchGoogleAdsStreamRequest.newBuilder() .setCustomerId(Long.toString(customerId)) .setQuery(query) .build(); ServerStream<SearchGoogleAdsStreamResponse> stream = googleAdsServiceClient.searchStreamCallable().call(request); for (SearchGoogleAdsStreamResponse response : stream) { for (GoogleAdsRow googleAdsRow : response.getResultsList()) { YouTubeVideoUpload video = googleAdsRow.getYouTubeVideoUpload(); System.out.printf( "Video with ID %s was found in state %s.%n", video.getVideoId(), video.getState()); } } }
C#
// Retrieve the metadata of the newly uploaded video. string query = $@" SELECT you_tube_video_upload.resource_name, you_tube_video_upload.video_id, you_tube_video_upload.state FROM you_tube_video_upload WHERE you_tube_video_upload.resource_name = '{videoUploadResourceName}'"; GoogleAdsServiceClient gaService = client.GetService( Services.V26.GoogleAdsService); gaService.SearchStream(customerId.ToString(), query, delegate (SearchGoogleAdsStreamResponse resp) { foreach (GoogleAdsRow row in resp.Results) { Console.WriteLine( $"Video with ID {row.YouTubeVideoUpload.VideoId} was found in " + $"state {row.YouTubeVideoUpload.State}."); } } );
PHP
// Retrieve the metadata of the newly uploaded video. $query = sprintf( "SELECT you_tube_video_upload.resource_name, " . "you_tube_video_upload.video_id, " . "you_tube_video_upload.state " . "FROM you_tube_video_upload " . "WHERE you_tube_video_upload.resource_name = '%s'", $videoUploadResourceName ); $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient(); $stream = $googleAdsServiceClient->searchStream( SearchGoogleAdsStreamRequest::build($customerId, $query) ); foreach ($stream->iterateAllElements() as $googleAdsRow) { /** @var GoogleAdsRow $googleAdsRow */ printf( "Video with ID '%s' was found in state '%s'.%s", $googleAdsRow->getYouTubeVideoUpload()->getVideoId(), YouTubeVideoUploadState::name($googleAdsRow->getYouTubeVideoUpload()->getState()), PHP_EOL ); }
Python
# Retrieve the metadata of the newly uploaded video. query: str = f""" SELECT you_tube_video_upload.resource_name, you_tube_video_upload.video_id, you_tube_video_upload.state FROM you_tube_video_upload WHERE you_tube_video_upload.resource_name = '{video_upload_resource_name}'""" ga_service: GoogleAdsServiceClient = client.get_service("GoogleAdsService") stream: Iterator[SearchGoogleAdsStreamResponse] = ga_service.search_stream( customer_id=customer_id, query=query ) for row in itertools.chain.from_iterable(batch.results for batch in stream): video = row.you_tube_video_upload print( f"Video with ID {row.you_tube_video_upload.video_id} was found in state {row.you_tube_video_upload.state}." )
小茹
# Retrieve the metadata of the newly uploaded video. query = <<~EOQ SELECT you_tube_video_upload.resource_name, you_tube_video_upload.video_id, you_tube_video_upload.state FROM you_tube_video_upload WHERE you_tube_video_upload.resource_name = '#{video_upload_resource_name}' EOQ responses = client.service.google_ads.search_stream( customer_id: customer_id, query: query, ) responses.each do |batch| batch.results.each do |row| puts "Video with ID #{row.you_tube_video_upload.video_id} was found in " \ "state #{row.you_tube_video_upload.state}." end end
Perl
This example is not yet available in Perl; you can take a look at the other languages.
curl
curl -i -v -X POST \ "https://qa-prod-googleads.sandbox.googleapis.com/v${API_VERSION}/customers/${CUSTOMER_ID}/googleAds:search" \ --header "Content-Type: application/json" \ --header "Developer-Token: ${DEVELOPER_TOKEN}" \ --header "login-customer-id: ${MANAGER_CUSTOMER_ID}" \ --header "Authorization: Bearer ${OAUTH2_ACCESS_TOKEN}" \ --data @- <<EOF { "query": "SELECT you_tube_video_upload.resource_name, you_tube_video_upload.video_id, you_tube_video_upload.state FROM you_tube_video_upload WHERE you_tube_video_upload.resource_name = '$UPLOADED_VIDEO_RESOURCE_NAME'" } EOF
管理上傳項目
影片上傳完畢後,即可做為影片素材資源使用。
使用上傳的影片
影片達到 PROCESSED 狀態後,您可以在 YouTubeVideoUpload 資源的 video_id 欄位中,找到 11 個字元的 YouTube 影片 ID。
使用這個 video_id 建立含有 MutateAssets 的 YoutubeVideoAsset,並將產生的素材資源名稱 (customers/{customer_id}/assets/{asset_id}) 連結至廣告活動或廣告:
將
youtube_video_id傳遞至AssetService.MutateAssets,即可建立YoutubeVideoAsset:POST https://googleads.googleapis.com/v26/customers/{customer_id}/assets:mutate { "operations": [ { "create": { "name": "Product Launch Video", "youtubeVideoAsset": { "youtubeVideoId": "dQw4w9WgXcQ" } } } ] }將
Asset連結至AssetGroup或AdGroupAd:最高成效廣告活動:使用
AssetGroupAsset建立AssetGroupAssetService.MutateAssetGroupAssets,並將field_type設為YOUTUBE_VIDEO:POST https://googleads.googleapis.com/v26/customers/{customer_id}/assetGroupAssets:mutate { "operations": [ { "create": { "assetGroup": "customers/{customer_id}/assetGroups/{asset_group_id}", "asset": "customers/{customer_id}/assets/{asset_id}", "fieldType": "YOUTUBE_VIDEO" } } ] }需求開發:呼叫
AdGroupAdService.MutateAdGroupAds時,請在DemandGenVideoResponsiveAdInfo物件的videos清單中參照customers/{customer_id}/assets/{asset_id}:POST https://googleads.googleapis.com/v26/customers/{customer_id}/adGroupAds:mutate { "operations": [ { "create": { "adGroup": "customers/{customer_id}/adGroups/{ad_group_id}", "status": "PAUSED", "ad": { "name": "Demand Gen Video Ad", "finalUrls": ["https://www.example.com"], "demandGenVideoResponsiveAd": { "videos": [ { "asset": "customers/{customer_id}/assets/{asset_id}" } ], "logoImages": [ { "asset": "customers/{customer_id}/assets/{logo_asset_id}" } ], "headlines": [ { "text": "Example Headline" } ], "longHeadlines": [ { "text": "Example Long Headline" } ], "descriptions": [ { "text": "Example Description" } ], "businessName": { "text": "Example Business" } } } } } ] }
使用
GoogleAdsService.SearchStream查詢現有YOUTUBE_VIDEO資產:SELECT asset.id, asset.resource_name, asset.name, asset.type, asset.youtube_video_asset.youtube_video_id, asset.youtube_video_asset.youtube_video_title FROM asset WHERE asset.type = 'YOUTUBE_VIDEO' AND asset.youtube_video_asset.youtube_video_id = '{video_id}'
更新中繼資料
如要更新透過這個 API 上傳的影片隱私設定,請使用 UpdateYouTubeVideoUpload 方法,並將 update_mask 設為 "video_privacy"。建立後只能更新 video_privacy 欄位 (PUBLIC 或 UNLISTED,適用於上傳至廣告主擁有的品牌頻道的影片);video_title、video_description、channel_id 和 video_content_metadata 則無法變更。
移除上傳內容
如要刪除透過 Google Ads API 上傳的影片,請在 RemoveYouTubeVideoUpload 方法的 resource_names 欄位中,傳遞這些影片的customers/{customer_id}/youTubeVideoUploads/{video_upload_id}資源名稱。這樣一來,影片就會從 Google Ads 素材資源庫和 YouTube 中移除。