YouTubeVideoUploadService
를 사용하면 Google Ads API를 통해 동영상을 YouTube에 직접 업로드할 수 있습니다. 이러한
동영상은 실적 최대화 캠페인 또는
디맨드젠 캠페인과 같은 다양한 광고 유형에서 동영상 애셋을 만드는 데 사용할 수 있습니다.
이 서비스는 YouTube 업로드 프로세스를 처리하여 동영상이 계정과 올바르게 연결되도록 함으로써 동영상 광고를 만드는 워크플로를 간소화합니다.
주요 개념
시작하기 전에 동영상 업로드가 관리되는 방식과 동영상 업로드가 거칠 수 있는 다양한 상태를 이해하는 것이 중요합니다.
엔드 투 엔드 워크플로
동영상 파일을 업로드하고 광고에 게재하려면 다음과 같은 4가지 순차적 단계가 필요합니다.
| 단계 | 서비스 및 메서드 | 요약 |
|---|---|---|
| 1. 동영상 바이트 시작 및 업로드 | YouTubeVideoUploadService의 CreateYouTubeVideoUpload |
동영상 메타데이터와 바이너리 파일을 업로드하여 YouTubeVideoUpload 리소스 (customers/{customer_id}/youTubeVideoUploads/{video_upload_id})를 만듭니다. |
| 2. 처리 상태 폴링 | GoogleAdsService의 SearchStream |
state = 'PROCESSED'이고 11자리 YouTube video_id가 채워질 때까지 you_tube_video_upload를 쿼리합니다. |
| 3. 재사용 가능한 동영상 애셋 만들기 | AssetService의 MutateAssets |
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)에 연결합니다. |
채널 소유권
동영상을 업로드할 때 대상 YouTube 채널을 사용하여
channel_id 필드를 지정할 수 있습니다. YouTubeVideoUpload
리소스:
- 광고주 소유 (브랜드) 채널: 광고주가 소유한 기존 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 관리 채널의 기본값이자 유일한 옵션입니다.
생성형 콘텐츠 공개
v26부터 광고주는 EU AI 법과 같은 규정에 따라 업로드된 동영상 콘텐츠에 AI 생성 또는 합성 미디어가 포함되어 있는지 공개할 수 있습니다.
YouTubeVideoUpload의 video_content_metadata에는
VideoContentMetadata가 포함되어 있습니다. 중첩된
generative_content_disclosure 필드를
YouTubeVideoUploadGenerativeContentDisclosure
열거형을 사용하여 설정합니다.
| 값 | 설명 |
|---|---|
NON_GENERATIVE_CONTENT |
광고주가 동영상을 비 AI 생성으로 공개했습니다. |
GENERATIVE_CONTENT |
광고주가 동영상을 AI 생성으로 공개했습니다. |
동영상 업로드
동영상을 업로드하려면 멀티파트 요청을 CreateYouTubeVideoUpload 메서드에 사용해야 합니다. 요청에는 업로드 메타데이터와 동영상 파일 자체가 모두 포함됩니다.
1. 업로드 시작
다음 필드로 CreateYouTubeVideoUploadRequest를 구성합니다.
| 필드 | 요구사항 | 설명 및 제약 조건 |
|---|---|---|
customer_id |
필수 | Google Ads 고객 ID (하이픈 제외) 10자리입니다. |
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) |
v26부터 사용 가능: VideoContentMetadata가 generative_content_disclosure 값 (NON_GENERATIVE_CONTENT 또는 GENERATIVE_CONTENT)으로 설정된 YouTubeVideoUploadGenerativeContentDisclosure입니다. |
클라이언트 라이브러리를 사용하는 경우 동영상 파일을 전달하여
CreateYouTubeVideoUpload
메서드를 호출하면 동영상 업로드가
내부적으로 처리됩니다.
자바
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}")
Ruby
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는 2단계
재개 가능한 업로드 프로토콜을 사용합니다.
- 업로드 세션 시작: 초기
POST요청을CreateYouTubeVideoUpload업로드 엔드포인트 (/upload/v26/customers/{customer_id}/youTubeVideoUploads:create) 와 함께 보냅니다.x-goog-upload-command: start(선택적으로x-goog-upload-header-content-length를 사용하여 파일 크기 선언). 응답은 예상 청크 크기와 같은 메타데이터와 함께x-goog-upload-urlHTTP 응답 헤더에 업로드 세션 URL을 반환합니다. - 동영상 바이트 스트리밍 및 완료:
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. 동영상 업로드 상태 가져오기
동영상 업로드를 시작한 후 state = 'PROCESSED'가 될 때까지 GAQL로 you_tube_video_upload 리소스(resource_name, video_upload_id, channel_id, video_id, state, video_privacy)의
선택 가능한 필드를 쿼리하여 처리 상태를 폴링합니다 (INPUT_ONLY 필드는
GAQL에서 선택할 수 없음).
자바
// 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}." )
Ruby
# 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를 사용하여 YoutubeVideoAsset로
MutateAssets을 만들고 결과 애셋 리소스 이름
(customers/{customer_id}/assets/{asset_id})을 캠페인 또는 광고에 연결합니다.
YoutubeVideoAsset를AssetService.MutateAssets에 전달하여youtube_video_id을 만듭니다.POST https://googleads.googleapis.com/v26/customers/{customer_id}/assets:mutate { "operations": [ { "create": { "name": "Product Launch Video", "youtubeVideoAsset": { "youtubeVideoId": "dQw4w9WgXcQ" } } } ] }Asset을AssetGroup또는AdGroupAd에 연결합니다.실적 최대화:
field_type이YOUTUBE_VIDEO로 설정된AssetGroupAssetService.MutateAssetGroupAssets를 사용하여AssetGroupAsset을 만듭니다.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" } } } } } ] }
을 사용하여 기존
YOUTUBE_VIDEO애셋을 쿼리합니다GoogleAdsService.SearchStream.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로 업로드된 동영상을 삭제해야 하는 경우
customers/{customer_id}/youTubeVideoUploads/{video_upload_id} 리소스 이름을
resource_names 필드에
RemoveYouTubeVideoUpload 메서드를 전달합니다. 이렇게 하면 Google Ads 애셋 라이브러리와 YouTube에서 동영상이 삭제됩니다.