Python Code Samples

The following code samples, which use the Google APIs Client Library for Python, are available for the YouTube Content ID API.

Retrieve a content owner's managed channels

The following code sample calls the YouTube Data API's channels.list method to retrieve a list of channels managed by the content owner making the API request.

#!/usr/bin/python

import httplib2
import os
import sys

from apiclient.discovery import build
from oauth2client.file import Storage
from oauth2client.client import flow_from_clientsecrets
from oauth2client.tools import argparser, run_flow


# 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 API Console at
# https://console.cloud.google.com/.
# Please ensure that you have enabled the YouTube Data API for your project.
# For more information about using OAuth2 to access the YouTube Data API, see:
#   https://developers.google.com/youtube/v3/guides/authentication
# For more information about the client_secrets.json file format, see:
#   https://developers.google.com/api-client-library/python/guide/aaa_client_secrets
CLIENT_SECRETS_FILE = "client_secrets.json"

# This variable defines a 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 will need to populate the client_secrets.json file
found at:

   %s

with information from the API 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))

YOUTUBE_SCOPES = (
  # This OAuth 2.0 access scope allows for read-only access to the authenticated
  # user's account, but not other types of account access.
  "https://www.googleapis.com/auth/youtube.readonly",
  # This OAuth 2.0 scope grants access to YouTube Content ID API functionality.
  "https://www.googleapis.com/auth/youtubepartner")
YOUTUBE_API_SERVICE_NAME = "youtube"
YOUTUBE_API_VERSION = "v3"
YOUTUBE_PARTNER_API_SERVICE_NAME = "youtubePartner"
YOUTUBE_PARTNER_API_VERSION = "v1"


# Authorize the request and store authorization credentials.
def get_authenticated_services(args):
  flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE,
    scope=" ".join(YOUTUBE_SCOPES),
    message=MISSING_CLIENT_SECRETS_MESSAGE)

  storage = Storage("%s-oauth2.json" % sys.argv[0])
  credentials = storage.get()

  if credentials is None or credentials.invalid:
    credentials = run_flow(flow, storage, args)

  youtube = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION,
    http=credentials.authorize(httplib2.Http()))

  youtube_partner = build(YOUTUBE_PARTNER_API_SERVICE_NAME,
    YOUTUBE_PARTNER_API_VERSION, http=credentials.authorize(httplib2.Http()))

  return (youtube, youtube_partner)

def get_content_owner_id(youtube_partner):
  # Call the contentOwners.list method to retrieve the ID of the content
  # owner associated with the currently authenticated user's account. If the
  # authenticated user's has access to multiple YouTube content owner accounts,
  # you need to iterate through the results to find the appropriate one.
  content_owners_list_response = youtube_partner.contentOwners().list(
    fetchMine=True
  ).execute()

  return content_owners_list_response["items"][0]["id"]

def list_managed_channels(youtube, content_owner_id):
  print "Channels managed by content owner '%s':" % content_owner_id

  # Retrieve a list of the channels that the content owner manages.
  channels_list_request = youtube.channels().list(
    onBehalfOfContentOwner=content_owner_id,
    managedByMe=True,
    part="snippet",
    maxResults=50
  )

  while channels_list_request:
    channels_list_response = channels_list_request.execute()

    for channel_item in channels_list_response["items"]:
      channel_title = channel_item["snippet"]["title"]
      channel_id = channel_item["id"]
      print "  %s (%s)" % (channel_title, channel_id)

    channels_list_request = youtube.channels().list_next(
      channels_list_request, channels_list_response)


if __name__ == "__main__":
  args = argparser.parse_args()
  (youtube, youtube_partner) = get_authenticated_services(args)
  content_owner_id = get_content_owner_id(youtube_partner)
  list_managed_channels(youtube, content_owner_id)

Create, manage, and use asset labels

The following code sample makes a series of API calls that demonstrate how to create and use asset labels to categorize and search for items in your asset library.

#!/usr/bin/python

import httplib
import httplib2
import logging
import os
import sys

from apiclient.discovery import build
from apiclient.errors import HttpError
from oauth2client.file import Storage
from oauth2client.client import flow_from_clientsecrets
from oauth2client.tools import argparser, run_flow


# 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 user
# authenticates with a non-CMS account.
INVALID_CREDENTIALS = "Invalid Credentials"

# The reason associated with the HTTP 409 error that's returned when an asset label
# with the same name already exists.
ASSETLABEL_EXISTS = "assetLabelExists"

# 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 API Console at
# https://console.cloud.google.com/.
# Please ensure that you have enabled the YouTube Data API for your project.
# For more information about using OAuth2 to access the YouTube Data API, see:
#   https://developers.google.com/youtube/v3/guides/authentication
# For more information about the client_secrets.json file format, see:
#   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 = (
  # This OAuth 2.0 access scope allows for full read/write access to the
  # authenticated user's account.
  "https://www.googleapis.com/auth/youtube",
  # This OAuth 2.0 scope grants access to YouTube Content ID 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"

# This variable defines a 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 will need to populate the client_secrets.json file
found at:

   %s

with information from the API 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))

# Authorize the request and store authorization credentials.
def get_authenticated_service(args):
  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(flow, storage, args)

  youtube_partner = build(YOUTUBE_CONTENT_ID_API_SERVICE_NAME,
    YOUTUBE_CONTENT_ID_API_VERSION,
    http=credentials.authorize(httplib2.Http()),
    static_discovery=False)

  return youtube_partner

# Call the contentOwners.list method to retrieve the ID of the content
# owner associated with the currently authenticated user's account. If the
# authenticated user's has access to multiple YouTube CMS accounts, you will
# need to iterate through the results to find the appropriate one.
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("You are not authenticated as a Google Account that has "
        "YouTube CMS access. Please delete '%s' and re-authenticate with a "
        "YouTube CMS account." % CACHED_CREDENTIALS_FILE)
      exit(1)
    else:
      raise

  return content_owners_list_response["items"][0]["id"]

def create_asset_label(youtube_partner, content_owner_id, labelName):
  # Create a new asset label.
  body = dict(
    labelName=labelName
  )

  try:
    asset_labels_insert_response = youtube_partner.assetLabels().insert(
      onBehalfOfContentOwner=content_owner_id,
      body=body
    ).execute()
    logging.info("Created new asset label '%s'." % asset_labels_insert_response["labelName"])
    return asset_labels_insert_response["labelName"]
  except HttpError, e:
    if ASSETLABEL_EXISTS in e.content:
      logging.error("Asset label '%s' already exists." % labelName)
      return labelName
    else:
      raise

def list_asset_labels(youtube_partner, content_owner_id):
  # Search for existing assets using labels.
  asset_labels_list_response = youtube_partner.assetLabels().list(
    onBehalfOfContentOwner=content_owner_id
  ).execute()

  for asset_label in asset_labels_list_response.get("items", []):
    logging.info("Found asset label '%s'." % asset_label["labelName"])

def create_asset(youtube_partner, content_owner_id, title):
  # Create a new web asset, which corresponds to a video that was originally
  # distributed online.
  body = dict(
    type="web",
    metadata=dict(
      title=title
    )
  )

  assets_insert_response = youtube_partner.assets().insert(
    onBehalfOfContentOwner=content_owner_id,
    body=body
  ).execute()

  return assets_insert_response

def search_asset(youtube_partner, content_owner_id, include_any_label, labels):
  # Search for existing assets using labels.
  asset_search_response = youtube_partner.assetSearch().list(
    onBehalfOfContentOwner=content_owner_id,
    includeAnyProvidedlabel=include_any_label,
    labels=labels
  ).execute()

  for asset in asset_search_response.get("items", []):
    logging.info("Found asset ID '%s'." % asset["id"])

def update_asset(youtube_partner, content_owner_id, asset, labels):
  # Update an existing asset.
  asset["label"] = labels

  assets_update_response = youtube_partner.assets().update(
    assetId=asset["id"],
    onBehalfOfContentOwner=content_owner_id,
    body=asset
  ).execute()

  return assets_update_response


if __name__ == '__main__':
  logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s [%(name)s] %(levelname)s: %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S"
  )
  
  args = argparser.parse_args()

  youtube_partner = get_authenticated_service(args)

  content_owner_id = get_content_owner_id(youtube_partner)
  logging.info("Authenticated as CMS user ID '%s'." % content_owner_id)

  asset_label_name = create_asset_label(youtube_partner, content_owner_id, "label1");

  list_asset_labels(youtube_partner, content_owner_id)

  asset1 = create_asset(youtube_partner, content_owner_id, "asset1")
  logging.info("Created new asset ID '%s'." % asset1["id"])

  asset1 = update_asset(youtube_partner, content_owner_id, asset1, [asset_label_name, "label3"])
  logging.info("Added asset labels '%s %s' to '%s'." % (asset1["label"][0], asset1["label"][1], asset1["id"]))

  asset2 = create_asset(youtube_partner, content_owner_id, "asset2")
  logging.info("Created new asset ID '%s'." % asset2["id"])

  asset2 = update_asset(youtube_partner, content_owner_id, asset2, ["label3"])
  logging.info("Added asset label '%s' to '%s'." % (asset2["label"][0], asset2["id"]))

  list_asset_labels(youtube_partner, content_owner_id)

  # AssetSearch may not be able to return the expected results right away, as there is a delay
  # in indexing the assets with labels for the asset search after they are added.
  # Second run of the code sample after this delay will return the expected assets
  # of the previous run.

  search_asset(youtube_partner, content_owner_id, "label1, label3", None)

  search_asset(youtube_partner, content_owner_id, "label1, label3", True)

  logging.info("All done!")

Create an asset; upload and claim a video

The following code sample makes a series of API calls to create an asset and set ownership for that asset, upload a video, claim the video as a match of the asset, and set advertising options for the video.

#!/usr/bin/python

import 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.file import Storage
from oauth2client.client import flow_from_clientsecrets
from oauth2client.tools import argparser, run_flow


# 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 user
# authenticates with a non-CMS account.
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 API Console at
# https://console.cloud.google.com/.
# Please ensure that you have enabled the YouTube Data API for your project.
# For more information about using OAuth2 to access the YouTube Data API, see:
#   https://developers.google.com/youtube/v3/guides/authentication
# For more information about the client_secrets.json file format, see:
#   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 = (
  # This OAuth 2.0 access scope allows for full read/write access to the
  # authenticated user's account.
  "https://www.googleapis.com/auth/youtube",
  # This OAuth 2.0 scope grants access to YouTube Content ID 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"

# This variable defines a 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 will need to populate the client_secrets.json file
found at:

   %s

with information from the API 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_args():
  argparser.add_argument("--file", dest="file", help="Video file to upload")
  argparser.add_argument("--title", dest="title", help="Video title",
    default="Test Title")
  argparser.add_argument("--description", dest="description",
    help="Video description",
    default="Test Description")
  argparser.add_argument("--category", dest="category",
    help="Numeric video category. " +
      "See https://developers.google.com/youtube/v3/docs/videoCategories/list",
    default="22")
  argparser.add_argument("--keywords", dest="keywords",
    help="Video keywords, comma separated", default="")
  argparser.add_argument("--privacyStatus", dest="privacyStatus",
    help="Video privacy status: public, private or unlisted",
    default="public")
  argparser.add_argument("--policyId", dest="policyId",
    help="Optional id of a saved claim policy")
  argparser.add_argument("--channelId", dest="channelId",
    help="Id of the channel to upload to. Must be managed by your " +
      "content owner account")
  args = argparser.parse_args()

  return args

# Authorize the request and store authorization credentials.
def get_authenticated_services(args):
  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(flow, storage, args)

  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)

# Call the contentOwners.list method to retrieve the ID of the content
# owner associated with the currently authenticated user's account. If the
# authenticated user's has access to multiple YouTube content owner accounts, you will
# need to iterate through the results to find the appropriate one.
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("You are not authenticated using the Google Account for "
        "a YouTube content owner. Please delete '%s' and re-authenticate as a "
        "YouTube content owner." % CACHED_CREDENTIALS_FILE)
      exit(1)
    else:
      raise

  return content_owners_list_response["items"][0]["id"]

def upload(youtube, content_owner_id, args):
  if args.keywords:
    tags = args.keywords.split(",")
  else:
    tags = None

  # Call the YouTube Data API's videos.insert method to create and upload
  # the video.
  insert_request = youtube.videos().insert(
    onBehalfOfContentOwner=content_owner_id,
    onBehalfOfContentOwnerChannel=args.channelId,
    part="snippet,status",
    body=dict(
      snippet=dict(
        title=args.title,
        description=args.description,
        tags=tags,
        categoryId=args.category
      ),
      status=dict(
        privacyStatus=args.privacyStatus
      )
    ),
    # The chunksize parameter specifies the size of each chunk of data, in
    # bytes, that will be uploaded at a time. Set a higher value for
    # reliable connections as fewer chunks lead to faster uploads. Set a lower
    # value for better recovery on less reliable connections.
    #
    # Setting "chunksize" equal to -1 in the code below 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(args.file, chunksize=-1, resumable=True)
  )

  # This code uses an exponential backoff strategy to resume a failed upload.
  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):
  # Create a new web asset, which corresponds to a video that was originally
  # distributed online. The asset will be linked to the corresponding YouTube
  # video via a claim that is created later in the script.
  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):
  # Update the asset's ownership data. This example indicates that the content
  # owner owns 100% of the asset worldwide.
  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):
  # Create a claim resource. Identify the video being claimed, the asset
  # that represents the claimed content, the type of content being claimed,
  # and the policy that you want to apply to the claimed video.
  #
  # You can identify a policy by using the policy_id of an existing policy as
  # obtained via youtubePartner.policies.list(). If you update that policy at
  # a later time, the updated policy will also be applied to a claim. If you
  # do not provide a policy_id, the code creates a new inline policy that
  # indicates that the video should be monetized.
  if policy_id:
    policy = dict(
      id=policy_id
    )
  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):
  # Enable ads for the video. This example enables the TrueView ad format.
  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"
  )

  args = parse_args()

  if args.file is None or not os.path.exists(args.file):
    logging.error("Please specify a valid file using the --file= parameter.")
    exit(1)

  # The channel ID value has a format like "UC..." and must identify a channel
  # managed by the YouTube content owner associated with the authenticated user.
  # You can use the YouTube Data API's youtube.channels.list method to
  # retrieve a list of managed channels and their IDs. (To do so, set the
  # "part" parameter value to "snippet", the "managedByMe" parameter value to
  # "true" and the "onBehalfOfContentOwner" parameter to the content owner ID.
  # The "get_content_owner_id" method in this code sample shows how
  # to retrieve the content owner ID.
  if args.channelId is None:
    logging.error("Please specify a channel ID via the --channelId= parameter.")
    exit(1)

  (youtube, youtube_partner) = get_authenticated_services(args)

  content_owner_id = get_content_owner_id(youtube_partner)
  logging.info("Authenticated as content owner ID '%s'." % content_owner_id)

  (video_id, duration_seconds) = upload(youtube, content_owner_id, args)
  logging.info("Successfully uploaded video ID '%s'." % video_id)

  file_size_bytes = os.path.getsize(args.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,
    args.title, args.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, args.policyId)
  logging.info("Created new claim ID '%s'." % claim_id)

  set_advertising_options(youtube_partner, content_owner_id, video_id)
  logging.info("Successfully set advertising options.")

  logging.info("All done!")