You may want to inform yourself about human rights in China.

On Using YouTube's API With Go

date: 2023-04-03

YouTube’s UI is a bit limited when it comes to exploring the content of rich channels (e.g. MIT OpenCourseWare, which has over 7k videos): in such cases, text tables with playlists / available videos are much easier and lighter to inspect, with a bare text editor or Unix CLI tools.

This is the kind of simple use of the API that I’ll present here, but it should get you started for more sophisticated use cases.

Note: There are similarities between Google APIs, especially if you use bindings for the same language: as a result, some content here is shared with an earlier article on Google’s Search API.

Hyangwonjeong under the snow, Gyeongbokgung, Seoul South Korea, Winter 2013

Hyangwonjeong under the snow, Gyeongbokgung, Seoul South Korea, Winter 2013 by Cultural Heritage Administration (CHA / λ¬Έν™”μž¬μ²­ / ζ–‡εŒ–θ²‘ε»³) through wikimedia.org – Korea Open Government License Type I: Attribution.

Boilerplate

Create a project

Assuming you already have an Google Cloud account (a GMail account really), the official procedure is rather straightforward, essentially:

  1. Go to the Google Cloud Console;
  2. Click the burger menu, IAM & Admin then Create a Project;
  3. Fill in the form and click Create.

Enable access to the Search Console API for the project

Again, the procedure is quite clear from the official documentation:

  1. Go to the Google Cloud Console;
  2. Make sure the previously created project is selected; you can change project using the list immediately to the right of the burger menu;
  3. In the burger menu, APIs & Services then Library;
  4. From there, search for “youtube data api”;
  5. Select YouTube Data API v3:
  6. You may click ENABLE to, well, enable the API.

Create a Service account

There are different types of credentials:

In order to provide a full and automatic access to the API, a Service account is required. By comparison, OAuth requires a human to validate API accesses from a browser, while API key only provides public data access.

The procedure to create a Service account is as follow:

  1. There’s a little bit of background regarding Google’s API ways of handling resources access that you may want to familiarize yourself with;
  2. Go to the Google Cloud Console;
  3. Click the burger menu, IAM & Admin then Service accounts;
  4. Click on + Create Service Account at the top
  5. Fill in the form;

Note: That seems to be all we need to do: you can grant access our Service account to the project, but even without, it seems to still be able to access the API, at least in read-only.

Generate a key for the Service account

As before, we’ll start by going to the Service accounts tab of the IAM & Admin entry:

  1. Go to the cloud console, make sure the correct project is selected;
  2. Click the burger menu, IAM & Admin then Service accounts;
  3. On the row corresponding to the newly created Service account, click the triple dots at the far right, and select Manage Keys;
  4. Click Add key, select Create new key;
  5. Select JSON and Create:
  6. Your browser should download a <something>.json file: this is the key we’ll later need to provide to our Go program to access the API.

And that’s all we need to start using with the API.

Hyangwonjeong in late autumn, Gyeongbokgung, Seoul South Korea

Hyangwonjeong in late autumn, Gyeongbokgung, Seoul South Korea by μ„œμšΈνŠΉλ³„μ‹œμ˜ μ†Œλ°© (Seoul Fire Services) through wikimedia.org – CC-BY-SA-4.0

API access

Quotas

Before going further, know that the API access is restricted by quotas. As of today, by default, you have 10Β 000 units per day to spend. As an example, list operations generally cost 1 unit, search operations, 100. Video upload is 1Β 600. Enumating a playlist of 7k videos requires “because” of pagination about 140 units (140 distinct list operations).

I’ll be solely using listing operations, so there should be no issue, but if you’re testing video upload code, you might exhaust available units quickly.

Refer to the relevant section of the “getting started” page for more.

“Philosophy”

YouTube API is a REST API, with JSON encoded input/output, over HTTP(s). Available routes, parameters, costs (quotas), all are documented on the official documentation.

I’ll be using the official Go wrapper, which essentially maps the official routes and “components” to Go “object”/methods. Usually, you’ll have to go back and forth between the official documentation and the wrapper documentation to understand understand the Go interface/object hierarchy. The wrapper avoids you most of the boilerplate: authentication handling, JSON encoding, and so forth.

You should start by instantiating a youtube.Service object. It’s a struct which regroups all specialized services (e.g. youtube.PlaylistsService to act on playlists, youtube.VideosService to act on videos and so forth, you get the idea).

type Service struct {

	...

	Channels *ChannelsService

	PlaylistItems *PlaylistItemsService

	Playlists *PlaylistsService

	Search *SearchService

	Videos *VideosService

	...

	// contains filtered or unexported fields
}

So, you start by initializing such an object via the appropriate method, youtube.NewService, and then you use the field corresponding to the kind of objects you’re trying to act on, on which you can generally call a first function to prepare the API call, which is later executed by a .Do() method. Let’s make this clear with an example:

	...

	yts, err := youtube.NewService(context.Background())
	if err != nil {
		log.Fatal(err)
	}
	xs, err := yts.PlaylistItems.List(...).Do(...)

	...

We’ll progressively complete this mockup, so as to be able in a few paragraphs to enumerate all videos in a playlist.

Authentication

I’ll be using here the default approach: set the environment variable GOOGLE_APPLICATION_CREDENTIALS to contain a path to the previously downloaded .json credential file: youtube.NewService will automatically look for the environment variable and know what to do with the credential file.

% GOOGLE_APPLICATION_CREDENTIALS=$HOME/.youtube.json go run youtube.go
...
Hyangwonjeong in summer, Gyeongbokgung, Seoul South Korea

Hyangwonjeong in summer, Gyeongbokgung, Seoul South Korea by Cultural Heritage Administration (CHA / λ¬Έν™”μž¬μ²­ / ζ–‡εŒ–θ²‘ε»³) through wikimedia.org – Korea Open Government License Type I: Attribution.

Request preparation parameters, part []string

If you naively try to execute some requests, you should find that often, the result is an almost empty struct. That’s the default behavior, which can be altered with a common request preparation parameter.

Let me be clear: there are two kinds of parameters (I don’t think this is standard terminology, but this should help clarify things):

  1. request preparation parameters;
  2. request parameters.
	...

	yts, err := youtube.NewService(context.Background())
	if err != nil {
		log.Fatal(err)
	}
	xs, err := yts.PlaylistItems.List(
		/* request preparation parameters */
	).Do(
		/* request parameters */
	)

	...

There is a recurrent request preparation parameter called part, which is a list of string ([]string). It allows you to ask the API to fill some parts of the returned JSON blobs. The parts are identified by the names of some JSON fields, which are described either the API’s documentation, or identically in the wrapper’s (they are most certainly the same).

Let’s see how we can find such names, for instance by going through the wrapper’s documentation.

Consider say the youtube.PlaylistItemsService service, which has a List() method, which takes such a part []string parameter, and returns a prepared call youtube.PlaylistItemsListCall, which itself has a .Do() method allowing to execute the prepared call, and which returns a youtube.PlaylistItemListResponse

func (r *PlaylistItemsService) List(part []string) *PlaylistItemsListCall

...

func (c *PlaylistItemsListCall) Do(opts ...googleapi.CallOption) (*PlaylistItemListResponse, error)

...

type PlaylistItemListResponse struct {
	Etag string `json:"etag,omitempty"`

	// EventId: Serialized EventId of the request which produced this
	// response.
	EventId string `json:"eventId,omitempty"`

	// Items: A list of playlist items that match the request criteria.
	Items []*PlaylistItem `json:"items,omitempty"`

	...
}

We’re almost there: look at the Items field containing an array of youtube.PlaylistItem:

type PlaylistItem struct {
	// ContentDetails: The contentDetails object is included in the resource
	// if the included item is a YouTube video. The object contains
	// additional information about the video.
	ContentDetails *PlaylistItemContentDetails `json:"contentDetails,omitempty"`

	// Etag: Etag of this resource.
	Etag string `json:"etag,omitempty"`

	// Id: The ID that YouTube uses to uniquely identify the playlist item.
	Id string `json:"id,omitempty"`

	// Kind: Identifies what kind of resource this is. Value: the fixed
	// string "youtube#playlistItem".
	Kind string `json:"kind,omitempty"`

	// Snippet: The snippet object contains basic details about the playlist
	// item, such as its title and position in the playlist.
	Snippet *PlaylistItemSnippet `json:"snippet,omitempty"`

	// Status: The status object contains information about the playlist
	// item's privacy status.
	Status *PlaylistItemStatus `json:"status,omitempty"`

	...
}

Those are the fields we can specify via the part []string parameter. For example, the following will fill both ContentDetails and Snippet, and the name correspond to the JSON field names:

	xs, err := yts.PlaylistItems.List([]string{"contentDetails", "snippet"}).Do(...)

Remark: Most of the content seems to always be located in a Snippet field. More generally, there’s a pleasant consistency in the naming of the returned data accross various routes.

Alright? Let’s move on to specifying parameters to the request, for instance let’s provide a playlist id to this request.

Hyangwonjeong in late summer, Gyeongbokgung, Seoul South Korea, August 2011

Hyangwonjeong in late summer, Gyeongbokgung, Seoul South Korea, August 2011 by Anne Dirkse through wikimedia.org – CC-BY-SA-4.0

Request parameters

Those are specified to the .Do() calls. We can use the google.golang.org/api/googleapi package, which provides a googleapi.QueryParameter() function, allowing to build parameters. You can refer to the API’s documentation for a list of the parameters for each route.

For instance, the following will list at most the first 42 videos of the given playlist:

	xs, err := yts.PlaylistItems.List([]string{"snippet"}).Do(
		googleapi.QueryParameter("playlistId", "PLUl4u3cNGP629n_3fX7HmKKgin_rqGzbx"),
		googleapi.QueryParameter("maxResults", "42"),
	)

Pagination

Most (all?) routes returning many “rows” of results are paginated. This is systematically implemented as follow:

Here’s how it works for listing all videos of a playlist:

	pid := "PLUl4u3cNGP629n_3fX7HmKKgin_rqGzbx"
	q := yts.PlaylistItems.List([]string{"snippet"})

	var pt = ""

	for {
		xs, err := q.Do(
			googleapi.QueryParameter("playlistId", pid),
			googleapi.QueryParameter("maxResults", "7"),
			googleapi.QueryParameter("pageToken", pt),
		)
		if err != nil {
			log.Fatal(err)
		}

		for _, x := range xs.Items {
			fmt.Println(x.Snippet.ResourceId.VideoId, x.Snippet.Position, x.Snippet.Title)
		}

		pt = xs.NextPageToken

		fmt.Println(pt)

		if pt == "" {
			return
		}
	}
Hyangwonjeong in winter, Gyeongbokgung, Seoul South Korea

Hyangwonjeong in winter, Gyeongbokgung, Seoul South Korea by Noh Mun Duek through wikimedia.org – CC-BY-SA-4.0

Listing all videos from a playlist

Here’s a standalone version of the previous excerpt (I’ve voluntarily reduced maxResults to demonstrate pagination)

package main

import (
	"log"
	"context"
	"fmt"
	youtube "google.golang.org/api/youtube/v3"
	"google.golang.org/api/googleapi"
)

// List all videos from a given playlist
func lsPlaylistVideos(yts *youtube.Service, pid string) error {
	q := yts.PlaylistItems.List([]string{"snippet"})

	var pt = ""

	for {
		xs, err := q.Do(
			googleapi.QueryParameter("playlistId", pid),
			googleapi.QueryParameter("maxResults", "7"),
			googleapi.QueryParameter("pageToken", pt),
		)
		if err != nil {
			return err
		}

		for _, x := range xs.Items {
			fmt.Println(x.Snippet.ResourceId.VideoId,
				x.Snippet.Position,
				x.Snippet.Title)
		}

		pt = xs.NextPageToken

		if pt == "" {
			return nil
		}

		fmt.Println("      -----")
	}

	return nil
}

func main() {
	yts, err := youtube.NewService(context.Background())
	if err != nil {
		log.Fatal(err)
	}

	err = lsPlaylistVideos(yts, "PLUl4u3cNGP629n_3fX7HmKKgin_rqGzbx")
	if err != nil {
		log.Fatal(err)
	}
}
% GOOGLE_APPLICATION_CREDENTIALS=$HOME/.youtube.json go run list-videos.go
iRVfaR3N5K4 0 1. Introduction and the geometric viewpoint on physics.
TiHHz3sKDbY 1 2. Introduction to tensors.
H6eR3sG524M 2 3. Tensors continued.
h9xaoGkyHwg 3 4. Volumes and volume elements; conservation laws.
OOmZkNa72t4 4 5. The stress energy tensor and the Christoffel symbol.
6MssatXXAzc 5 6. The principle of equivalence.
gnWKpHUj11w 6 7. Principle of equivalence continued; parallel transport.
      -----
LoIq6KElVxs 7 8. Lie transport, Killing vectors, tensor densities.
4QPKWFme0k4 8 9. Geodesics.
JWSdeg4jkoY 9 10. Spacetime curvature.
d1dtqw7f6pw 10 11. More on spacetime curvature.
OIjLUzS6SQA 11 12. The Einstein field equation.
JNWXzIFcf3g 12 13. The Einstein field equation (variant derivation).
9lIgAPvppk0 13 14. Linearized gravity I: Principles and static limit.
      -----
Oxk2nnuC130 14 15. Linearized gravity II: Dynamic sources
R2vL2wLqGYg 15 16. Gravitational radiation I
pUqA_iHLBWQ 16 17. Gravitational radiation II
wBvXOb59l-k 17 18. Cosmology I
p_10lgn2BiI 18 19. Cosmology II
PVYTNKZDHBo 19 20. Spherical compact sources I
K1vpc9YwlQI 20 21. Spherical compact sources II
      -----
ZqF-7bjnzCU 21 22. Black holes I
_uNWqE3LS1E 22 23. Black holes II

Listing all videos from a channel

Exercise: I’ll give room to try it yourself, but know that compared to what we just did, there are two twists. Tips and solution below if you don’t want to work.

Tip:[+]

Tip:[+]

Solution:[+]
Hyangwonjeong in late autumn, Gyeongbokgung, Seoul South Korea

Hyangwonjeong in late autumn, Gyeongbokgung, Seoul South Korea by 우승민 (Brucewoo almighy) through wikimedia.org – CC-BY-SA-4.0


  1. If you’re on the last page, nextPageToken will be empty for example. ↩︎


Comments

By email, at mathieu.bivert chez:

email