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.
Boilerplate
Create a project
Assuming you already have an Google Cloud account (a GMail account really), the official procedure is rather straightforward, essentially:
- Go to the Google Cloud Console;
- Click the burger menu,
IAM
& Admin
thenCreate a Project
; - 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:
- Go to the Google Cloud Console;
- Make sure the previously created project is selected; you can change project using the list immediately to the right of the burger menu;
- In the burger menu,
APIs & Services
thenLibrary
; - From there, search for “
youtube data api
”; - Select
YouTube Data API v3
: - You may click
ENABLE
to, well, enable the API.
Create a Service account
There are different types of credentials:
- API key;
- OAuth client ID;
- Service account.
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:
- There’s a little bit of background regarding Google’s API ways of handling resources access that you may want to familiarize yourself with;
- Go to the Google Cloud Console;
- Click the burger menu,
IAM
& Admin
thenService accounts
; - Click on
+ Create Service Account
at the top - 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:
- Go to the cloud console, make sure the correct project is selected;
- Click the burger menu,
IAM
& Admin
thenService accounts
; - On the row corresponding to the newly created Service account,
click the triple dots at the far right, and select
Manage Keys
; - Click
Add key
, selectCreate new key
; - Select
JSON
andCreate
: - 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.
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
...
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):
- request preparation parameters;
- 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.
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:
- all routes take a
maxResults
parameter; - and a
pageToken
parameter; - they won’t return all the results but only at most
maxResults
; maxResults
’s maximum value being 50;- such routes also returns both, when applicable1:
nextPageToken
;prevPageToken
; allowing navigation between pages of results. You can feed eithernextPageToken
orprevPageToken
to the query aspageToken
’s parameter’s value next time around to move forward/backward into the pages.
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
}
}
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:[+]-
If you’re on the last page,
nextPageToken
will be empty for example. ↩︎
Comments
By email, at mathieu.bivert chez: