注意:YouTube Data API 仅供 YouTube 内容合作伙伴使用,并非所有开发者或 YouTube 用户都可以访问。您需要拥有 YouTube 内容管理器账号才能访问。如果您拥有 YouTube Content Manager 账号,但未在 Google Cloud 控制台中看到 YouTube Data API 列为服务之一,请与您的指定合作伙伴经理或合作伙伴支持团队联系。
此代码示例演示了如何上传 YouTube 视频并为其应用创收政策。若要通过视频创收,您必须在 YouTube 权利管理系统中通过 asset 声明视频版权。此示例会上传视频、创建新资产、使用该资产声明视频版权,并对视频应用创收政策。
此示例以一系列步骤的形式呈现,并附带相关的代码部分。您可以在本页末尾找到完整脚本。代码是用 Python 编写的。我们还提供适用于其他热门编程语言的客户端库。
要求
- Python 3.7 或更高版本
- google-api-python-client
第 1 步:常见的清理函数
代码示例的前几个部分执行许多脚本通用的基本清理功能:解析命令行、对用户进行身份验证,以及获取必要的 API 服务。
解析命令行
parse_options 方法使用 Python 客户端库中的 OptionParser 创建一个 options 对象,其中包含每个命令行实参作为属性。后续方法会根据需要从 options 对象中检索值。
示例脚本的命令行实参如下所示。前两个(file 和 channelId)是必需的;其余是可选的。
-
file:要上传的视频文件的名称和位置。Example: --file="/home/path/to/file.mov"
-
channelId:您要将视频上传到的 YouTube 频道。频道必须由经过身份验证的用户的 YouTube 内容管理器账号管理。您可以在经过身份验证的用户的 YouTube 账号设置中检索频道 ID,也可以使用channels.list方法检索。Example: --channelId="UC_x5XG1OV2P6uZZ5FSM9Ttw"
-
title:您要上传的视频的标题。默认值为Test title。Example: --title="Summer vacation in California"
-
description:您要上传的视频的说明。默认值为Test description。Example: --description="Had a great time surfing in Santa Cruz"
-
category:与视频关联的 YouTube 视频类别的类别 ID。默认值为22,表示People & Blogs类别。Example: --category=22
-
keywords:与视频相关联的关键字列表(以英文逗号分隔)。默认值为空字符串。Example: --keywords="surfing, beach volleyball"
-
privacyStatus:视频的隐私设置。默认情况下,上传的视频是公开的 (public)。上传测试视频时,您可能需要指定--privacyStatus实参值,以确保这些视频是私享或不公开列出的。有效值为public、private和unlisted。Example: --privacyStatus="private"
-
policyId:要应用于上传视频的创收政策。政策必须与经过身份验证的用户的 YouTube 内容管理器账号相关联。默认采用标准的 YouTube“创收”政策。Example: --policyId="S309961703555739"
def parse_options(): parser = argparse.ArgumentParser( description="Uploads and claims a video on YouTube.") parser.add_argument("--file", required=True, help="Video file to upload") parser.add_argument("--title", default="Test Title", help="Video title") parser.add_argument("--description", default="Test Description", help="Video description") parser.add_argument("--category", default="22", help="Numeric video category. See https://developers.google.com/youtube/data/reference/rest/v3/videoCategories/list") parser.add_argument("--keywords", default="", help="Video keywords, comma separated") parser.add_argument("--privacyStatus", default="public", choices=["public", "private", "unlisted"], help="Video privacy status: public, private or unlisted") parser.add_argument("--policyId", help="Optional id of a saved claim policy") parser.add_argument("--channelId", required=True, help="Id of the channel to upload to. Must be managed by your CMS account") return parser.parse_args()
为请求授权
在此步骤中,我们将 OAuth 2.0 授权纳入脚本中。这样一来,运行脚本的用户便可授权脚本执行归因于用户账号的 API 请求。
创建 client_secrets.json 文件
示例中显示的授权类型需要使用 client_secrets.json 文件(其中包含来自 Google Cloud 控制台的信息)才能执行授权。您还需要注册应用。如需更全面地了解授权的运作方式,请参阅授权指南。请注意,此示例需要在 Cloud 控制台中为您的项目配置 YouTube Data API V3 和 YouTube Content ID API 服务。
{ "web": { "client_id": "INSERT CLIENT ID HERE", "client_secret": "INSERT CLIENT SECRET HERE", "redirect_uris": [], "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://accounts.google.com/o/oauth2/token" } }
脚本中的授权代码
该脚本包含以下 import 语句,以启用用户身份验证和授权:
from oauth2client.file import Storage from oauth2client.client import flow_from_clientsecrets from oauth2client.tools import run
接下来,get_authenticated_services 方法使用上一步中配置的 client_secrets.json 文件中的数据创建 FLOW 对象。如果用户授权我们的应用代表用户提交 API 请求,则生成的凭据会存储在 Storage 对象中以供日后使用。如果凭据过期,用户需要重新授权我们的应用。
YOUTUBE_SCOPES = ( # An OAuth 2 access scope that allows for full read/write access. "https://www.googleapis.com/auth/youtube", # A scope that grants access to YouTube Partner API functionality. "https://www.googleapis.com/auth/youtubepartner") flow = flow_from_clientsecrets( CLIENT_SECRETS_FILE, scope=" ".join(YOUTUBE_SCOPES), message=MISSING_CLIENT_SECRETS_MESSAGE ) storage = Storage(CACHED_CREDENTIALS_FILE) credentials = storage.get() if credentials is None or credentials.invalid: credentials = run(flow, storage)
获取服务
成功授权后,我们会获取执行所需操作所需的必要服务。该示例使用 YouTube Data API 上传视频,并使用 YouTube Content ID API 创建资产和对视频提出版权主张。我们创建了单独的服务,以提供对这两个 API 功能的授权访问。
from googleapiclient.discovery import build import httplib2 YOUTUBE_API_SERVICE_NAME = "youtube" YOUTUBE_API_VERSION = "v3" YOUTUBE_CONTENT_ID_API_SERVICE_NAME = "youtubePartner" YOUTUBE_CONTENT_ID_API_VERSION = "v1" youtube = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, http=credentials.authorize(httplib2.Http())) youtube_partner = build(YOUTUBE_CONTENT_ID_API_SERVICE_NAME, YOUTUBE_CONTENT_ID_API_VERSION, http=credentials.authorize(httplib2.Http()), static_discovery=False) return (youtube, youtube_partner)
第 2 步:确定内容所有者
如需创建资源和提出版权主张,经过身份验证的用户必须拥有 YouTube 内容管理器账号。内容管理器账号包含一个或多个内容所有者的版权管理对象。内容所有者是指版权持有者,有权决定是否通过视频创收、跟踪视频或禁播视频。
get_content_owner 方法用于检索已通过身份验证的用户的 Content Manager 账号中的内容所有者 ID。大多数账号只有一个内容所有者(经过身份验证的用户),但如果账号有多个内容所有者,该方法会返回第一个。
def get_content_owner_id(youtube_partner): try: content_owners_list_response = youtube_partner.contentOwners().list( fetchMine=True ).execute() except HttpError as e: if INVALID_CREDENTIALS in e.content: logging.error("The request is not authorized by a Google Account that " "is linked to a YouTube content owner. Please delete '%s' and " "re-authenticate with a YouTube content owner account." % CACHED_CREDENTIALS_FILE) exit(1) else: raise # This returns the CMS user id of the first entry returned # by youtubePartner.contentOwners.list() # See https://developers.google.com/youtube/partner/reference/rest/v1/contentOwners/list # Normally this is what you want, but if you authorize with a Google Account # that has access to multiple YouTube content owner accounts, you need to # iterate through the results. return content_owners_list_response["items"][0]["id"]
第 3 步:上传视频
如需上传视频,我们需构建一个表示视频的部分 JSON 资源,并将其传递给 videos.insert 方法。我们使用在解析命令行时创建的 options 对象中的值来设置视频元数据。对于媒体文件本身,我们使用 MediaFileUpload 以便能够使用可续传上传。如需了解详情,请参阅上传视频。
upload 方法会返回新视频的视频 ID,脚本需要在后续步骤中将该值传递给其他方法。
def upload(youtube, content_owner_id, options): if options.keywords: tags = options.keywords.split(",") else: tags = None insert_request = youtube.videos().insert( onBehalfOfContentOwner=content_owner_id, onBehalfOfContentOwnerChannel=options.channelId, part="snippet,status", body=dict( snippet=dict( title=options.title, description=options.description, tags=tags, categoryId=options.category ), status=dict( privacyStatus=options.privacyStatus ) ), # chunksize=-1 means that the entire file will be uploaded in a single # HTTP request. (If the upload fails, it will still be retried where it # left off.) media_body=MediaFileUpload(options.file, chunksize=-1, resumable=True) ) response = None error = None retry = 0 duration_seconds=0 while response is None: try: logging.debug("Uploading file...") start_seconds = time.time() status, response = insert_request.next_chunk() delta_seconds = time.time() - start_seconds duration_seconds += delta_seconds if "id" in response: return (response["id"], duration_seconds) else: logging.error("The upload failed with an unexpected response: %s" % response) exit(1) except HttpError as e: if e.resp.status in RETRIABLE_STATUS_CODES: error = "A retriable HTTP error %d occurred:\n%s" % (e.resp.status, e.content) else: raise except RETRIABLE_EXCEPTIONS as e: error = "A retriable error occurred: %s" % e if error is not None: logging.error(error) retry += 1 if retry > MAX_RETRIES: logging.error("No longer attempting to retry.") exit(1) max_sleep = 2 ** retry sleep_seconds = random.random() * max_sleep logging.debug("Sleeping %f seconds and then retrying..." % sleep_seconds) time.sleep(sleep_seconds)
第 4 步:创建资源
若要通过 YouTube 视频创收,您必须先将该视频与资产相关联。create_asset 方法会为新上传的视频创建新资产。
与我们为视频所做的一样,我们构建了一个部分 JSON 资源,用于标识要创建的资产类型(网络视频),并为新资产提供标题和说明。我们将 JSON 资源传递给 assets.insert 方法,该方法会创建相应资产并返回其唯一 ID。同样,脚本需要在后续步骤中将该值传递给其他方法。
def create_asset(youtube_partner, content_owner_id, title, description): # This creates a new asset corresponding to a video on the web. # The asset is linked to the corresponding YouTube video via a # claim that will be created later. body = dict( type="web", metadata=dict( title=title, description=description ) ) assets_insert_response = youtube_partner.assets().insert( onBehalfOfContentOwner=content_owner_id, body=body ).execute() return assets_insert_response["id"]
第 5 步:更新所有权
在您开始通过视频创收之前,YouTube 必须知道相关资产的所有者是谁。创建完素材资源后,我们现在来配置素材资源的 ownership。在示例中,我们指定内容所有者拥有相应素材资源的全球所有权。
def set_asset_ownership(youtube_partner, content_owner_id, asset_id): # This specifies that content_owner_id owns 100% of the asset worldwide. # Adjust as needed. body = dict( general=[dict( owner=content_owner_id, ratio=100, type="exclude", territories=[] )] ) youtube_partner.ownership().update( onBehalfOfContentOwner=content_owner_id, assetId=asset_id, body=body ).execute()
第 6 步:声明视频
下一步是通过主张版权将上传的视频与其对应的资产相关联。版权主张可将视频与 YouTube 权利管理系统相关联,从而确立视频的所有权,并使所有者能够设置创收政策。
claim_video 方法用于声明视听内容版权。如果您在命令行中添加 policyId 参数,该方法会将指定的政策应用于相应视频;如果您未添加该参数,该方法会应用标准的“创收”政策。
def claim_video(youtube_partner, content_owner_id, asset_id, video_id, policy_id): # policy_id can be set to the id of an existing policy, which can be obtained # via youtubePartner.policies.list() # See https://developers.google.com/youtube/partner/reference/rest/v1/policies/list # If you later update that existing policy, the claim will also be updated. if policy_id: policy = dict( id=policy_id ) # If policy_id is not provided, a new inline policy is created. else: policy = dict( rules=[dict( action="monetize" )] ) body = dict( assetId=asset_id, videoId=video_id, policy=policy, contentType="audiovisual" ) youtube_partner.claims().insert( onBehalfOfContentOwner=content_owner_id, body=body ).execute()
第 7 步:设置广告选项
我们已对该视频声明版权并应用了创收政策。最后一步是指定要在视频中展示哪种类型的广告。每当“创收”政策适用时,YouTube 都会检查广告选项,并展示可带来最高收入的广告类型。
此示例表示 YouTube 应在此视频中展示插播广告 TrueView。
def set_advertising_options(youtube_partner, content_owner_id, video_id): # This enables the TrueView ad format for the given video. # Adjust as needed. body = dict( adFormats=["trueview_instream"] ) youtube_partner.videoAdvertisingOptions().update( videoId=video_id, onBehalfOfContentOwner=content_owner_id, body=body ).execute()
完整代码示例
下面列出了完整的有效示例 upload_monetize_video_example.py:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # Copyright 2014 Google Inc. All Rights Reserved. # # 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. """Simple command-line sample for Youtube Partner API. Command-line application that creates an asset, uploads and claims a video for that asset. Usage: $ python3 upload_monetize_video_example.py --file=VIDEO_FILE --channelId=CHANNEL_ID \\ [--title=VIDEO_TITLE] [--description=VIDEO_DESCRIPTION] [--category=CATEGORY_ID] \\ [--keywords=KEYWORDS] [--privacyStatus=PRIVACY_STATUS] [--policyId=POLICY_ID] You can also get help on all the command-line flags the program understands by running: $ python3 upload_monetize_video_example.py --help """ __author__ = 'jeffy+pub@google.com (Jeffrey Posnick)' import argparse import http.client as httplib import httplib2 import logging import os import random import sys import time from apiclient.discovery import build from apiclient.errors import HttpError from apiclient.http import MediaFileUpload from oauth2client.client import flow_from_clientsecrets from oauth2client.file import Storage from oauth2client.tools import run # Explicitly tell the underlying HTTP transport library not to retry, since # we are handling retry logic ourselves. httplib2.RETRIES = 1 # Maximum number of times to retry before giving up. MAX_RETRIES = 10 # Always retry when these exceptions are raised. RETRIABLE_EXCEPTIONS = (httplib2.HttpLib2Error, IOError, httplib.NotConnected, httplib.IncompleteRead, httplib.ImproperConnectionState, httplib.CannotSendRequest, httplib.CannotSendHeader, httplib.ResponseNotReady, httplib.BadStatusLine,) # Always retry when an apiclient.errors.HttpError with one of these status # codes is raised. RETRIABLE_STATUS_CODES = (500, 502, 503, 504,) # The message associated with the HTTP 401 error that's returned when a request # is authorized by a user whose account is not associated with a YouTube # content owner. INVALID_CREDENTIALS = "Invalid Credentials" # The CLIENT_SECRETS_FILE variable specifies the name of a file that contains # the OAuth 2.0 information for this application, including its client_id and # client_secret. You can acquire an OAuth 2.0 client ID and client secret from # the Google Cloud console at # https://console.cloud.google.com/. # See the "Registering your application" instructions for an explanation # of how to find these values: # https://developers.google.com/youtube/partner/guides/registering_an_application # For more information about using OAuth2 to access Google APIs, please visit: # https://developers.google.com/accounts/docs/OAuth2 # For more information about the client_secrets.json file format, please visit: # https://developers.google.com/api-client-library/python/guide/aaa_client_secrets CLIENT_SECRETS_FILE = "client_secrets.json" # The local file used to store the cached OAuth 2 credentials after going # through a one-time browser-based login. CACHED_CREDENTIALS_FILE = "%s-oauth2.json" % sys.argv[0] YOUTUBE_SCOPES = ( # An OAuth 2 access scope that allows for full read/write access. "https://www.googleapis.com/auth/youtube", # A scope that grants access to YouTube Partner API functionality. "https://www.googleapis.com/auth/youtubepartner",) YOUTUBE_API_SERVICE_NAME = "youtube" YOUTUBE_API_VERSION = "v3" YOUTUBE_CONTENT_ID_API_SERVICE_NAME = "youtubePartner" YOUTUBE_CONTENT_ID_API_VERSION = "v1" # Helpful message to display if the CLIENT_SECRETS_FILE is missing. MISSING_CLIENT_SECRETS_MESSAGE = """ WARNING: Please configure OAuth 2.0 To make this sample run you need to populate the client_secrets.json file at: %s with information from the Cloud console https://console.cloud.google.com/ For more information about the client_secrets.json file format, please visit: https://developers.google.com/api-client-library/python/guide/aaa_client_secrets """ % os.path.abspath(os.path.join(os.path.dirname(__file__), CLIENT_SECRETS_FILE)) def parse_options(): parser = OptionParser() parser.add_option("--file", dest="file", help="Video file to upload") parser.add_option("--title", dest="title", help="Video title", default="Test Title") parser.add_option("--description", dest="description", help="Video description", default="Test Description") parser.add_option("--category", dest="category", help="Numeric video category. " + "See https://developers.google.com/youtube/data/reference/rest/v3/videoCategories/list", default="22") parser.add_option("--keywords", dest="keywords", help="Video keywords, comma separated", default="") parser.add_option("--privacyStatus", dest="privacyStatus", help="Video privacy status: public, private or unlisted", default="public") parser.add_option("--policyId", dest="policyId", help="Optional id of a saved claim policy") parser.add_option("--channelId", dest="channelId", help="Id of the channel to upload to. Must be managed by your CMS account") (options, args) = parser.parse_args() return options def get_authenticated_services(): flow = flow_from_clientsecrets( CLIENT_SECRETS_FILE, scope=" ".join(YOUTUBE_SCOPES), message=MISSING_CLIENT_SECRETS_MESSAGE ) storage = Storage(CACHED_CREDENTIALS_FILE) credentials = storage.get() if credentials is None or credentials.invalid: credentials = run(flow, storage) youtube = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, http=credentials.authorize(httplib2.Http())) youtube_partner = build(YOUTUBE_CONTENT_ID_API_SERVICE_NAME, YOUTUBE_CONTENT_ID_API_VERSION, http=credentials.authorize(httplib2.Http()), static_discovery=False) return (youtube, youtube_partner) def get_content_owner_id(youtube_partner): try: content_owners_list_response = youtube_partner.contentOwners().list( fetchMine=True ).execute() except HttpError, e: if INVALID_CREDENTIALS in e.content: logging.error("Your request is not authorized by a Google Account that " "is associated with a YouTube content owner. Please delete '%s' and " "re-authenticate with an account that is associated " "with a content owner." % CACHED_CREDENTIALS_FILE) exit(1) else: raise # This returns the CMS user id of the first entry returned # by youtubePartner.contentOwners.list() # See https://developers.google.com/youtube/partner/reference/rest/v1/contentOwners/list # Normally this is what you want, but if you authorize with a Google Account # that has access to multiple YouTube content owner accounts, you need to # iterate through the results. return content_owners_list_response["items"][0]["id"] def upload(youtube, content_owner_id, options): if options.keywords: tags = options.keywords.split(",") else: tags = None insert_request = youtube.videos().insert( onBehalfOfContentOwner=content_owner_id, onBehalfOfContentOwnerChannel=options.channelId, part="snippet,status", body=dict( snippet=dict( title=options.title, description=options.description, tags=tags, categoryId=options.category ), status=dict( privacyStatus=options.privacyStatus ) ), # chunksize=-1 means that the entire file will be uploaded in a single # HTTP request. (If the upload fails, it will still be retried where it # left off.) This is usually a best practice, but if you're using Python # older than 2.6 or if you're running on App Engine, you should set the # chunksize to something like 1024 * 1024 (1 megabyte). media_body=MediaFileUpload(options.file, chunksize=-1, resumable=True) ) response = None error = None retry = 0 duration_seconds=0 while response is None: try: logging.debug("Uploading file...") start_seconds = time.time() status, response = insert_request.next_chunk() delta_seconds = time.time() - start_seconds duration_seconds += delta_seconds if "id" in response: return (response["id"], duration_seconds) else: logging.error("The upload failed with an unexpected response: %s" % response) exit(1) except HttpError, e: if e.resp.status in RETRIABLE_STATUS_CODES: error = "A retriable HTTP error %d occurred:\n%s" % (e.resp.status, e.content) else: raise except RETRIABLE_EXCEPTIONS, e: error = "A retriable error occurred: %s" % e if error is not None: logging.error(error) retry += 1 if retry > MAX_RETRIES: logging.error("No longer attempting to retry.") exit(1) max_sleep = 2 ** retry sleep_seconds = random.random() * max_sleep logging.debug("Sleeping %f seconds and then retrying..." % sleep_seconds) time.sleep(sleep_seconds) def create_asset(youtube_partner, content_owner_id, title, description): # This creates a new asset corresponding to a video on the web. # The asset is linked to the corresponding YouTube video via a # claim that will be created later. body = dict( type="web", metadata=dict( title=title, description=description ) ) assets_insert_response = youtube_partner.assets().insert( onBehalfOfContentOwner=content_owner_id, body=body ).execute() return assets_insert_response["id"] def set_asset_ownership(youtube_partner, content_owner_id, asset_id): # This specifies that content_owner_id owns 100% of the asset worldwide. # Adjust as needed. body = dict( general=[dict( owner=content_owner_id, ratio=100, type="exclude", territories=[] )] ) youtube_partner.ownership().update( onBehalfOfContentOwner=content_owner_id, assetId=asset_id, body=body ).execute() def claim_video(youtube_partner, content_owner_id, asset_id, video_id, policy_id): # policy_id can be set to the id of an existing policy, which can be obtained # via youtubePartner.policies.list() # See https://developers.google.com/youtube/partner/reference/rest/v1/policies/list # If you later update that existing policy, the claim will also be updated. if policy_id: policy = dict( id=policy_id ) # If policy_id is not provided, a new inline policy is created. else: policy = dict( rules=[dict( action="monetize" )] ) body = dict( assetId=asset_id, videoId=video_id, policy=policy, contentType="audiovisual" ) claims_insert_response = youtube_partner.claims().insert( onBehalfOfContentOwner=content_owner_id, body=body ).execute() return claims_insert_response["id"] def set_advertising_options(youtube_partner, content_owner_id, video_id): # This enables the true view ad format for the given video. # Adjust as needed. body = dict( adFormats=["trueview_instream"] ) youtube_partner.videoAdvertisingOptions().update( videoId=video_id, onBehalfOfContentOwner=content_owner_id, body=body ).execute() if __name__ == '__main__': logging.basicConfig( level=logging.DEBUG, format="%(asctime)s [%(name)s] %(levelname)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S" ) options = parse_options() if options.file is None or not os.path.exists(options.file): logging.error("Please specify a valid file using the --file= parameter.") exit(1) # The channel ID looks something like "UC..." and needs to correspond to a # channel managed by the YouTube content owner authorizing the request. # youtube.channels.list(part="snippet", managedByMe=true, # onBehalfOfContentOwner=*CONTENT_OWNER_ID*) # can be used to retrieve a list of managed channels and their channel IDs. # See https://developers.google.com/youtube/data/reference/rest/v3/channels/list if options.channelId is None: logging.error("Please specify a channel ID via the --channelId= parameter.") exit(1) (youtube, youtube_partner) = get_authenticated_services() content_owner_id = get_content_owner_id(youtube_partner) logging.info("Authorized by content owner ID '%s'." % content_owner_id) (video_id, duration_seconds) = upload(youtube, content_owner_id, options) logging.info("Successfully uploaded video ID '%s'." % video_id) file_size_bytes = os.path.getsize(options.file) logging.debug("Uploaded %d bytes in %0.2f seconds (%0.2f megabytes/second)." % (file_size_bytes, duration_seconds, (file_size_bytes / (1024 * 1024)) / duration_seconds)) asset_id = create_asset(youtube_partner, content_owner_id, options.title, options.description) logging.info("Created new asset ID '%s'." % asset_id) set_asset_ownership(youtube_partner, content_owner_id, asset_id) logging.info("Successfully set asset ownership.") claim_id = claim_video(youtube_partner, content_owner_id, asset_id, video_id, options.policyId) logging.info("Successfully claimed video.") set_advertising_options(youtube_partner, content_owner_id, video_id) logging.info("Successfully set advertising options.") logging.info("All done!")