YouTube Data API: Go Code Samples

The following code samples, which use the Google APIs Client Library for Go, are available for the YouTube Data API. You can download these code samples from the go folder of the YouTube APIs code sample repository on GitHub.

  1. Authorize a request
  2. Post a channel bulletin (activities.insert)
  3. Retrieve my uploads (playlistItems.list)
  4. Search by keyword (search.list)
  5. Search by topic (search.list)
  6. Upload a video (videos.insert)

Authorize a request

The code sample below performs OAuth 2.0 authorization by checking for the presence of a local file that contains authorization credentials. If the file is not present, the script opens a browser and waits for a response, then saves the returned credentials locally.

package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"

	"code.google.com/p/goauth2/oauth"
)

const missingClientSecretsMessage = `
Please configure OAuth 2.0

To make this sample run, you need to populate the client_secrets.json file
found at:

   %v

with information from the Google Developers Console
https://cloud.google.com/console

For more information about the client_secrets.json file format, please visit:
https://developers.google.com/api-client-library/python/guide/aaa_client_secrets
`

var (
	clientSecretsFile = flag.String("secrets", "client_secrets.json", "Client Secrets configuration")
	cacheFile         = flag.String("cache", "request.token", "Token cache file")
)

// ClientConfig is a data structure definition for the client_secrets.json file.
// The code unmarshals the JSON configuration file into this structure.
type ClientConfig struct {
	ClientID     string   `json:"client_id"`
	ClientSecret string   `json:"client_secret"`
	RedirectURIs []string `json:"redirect_uris"`
	AuthURI      string   `json:"auth_uri"`
	TokenURI     string   `json:"token_uri"`
}

// Config is a root-level configuration object.
type Config struct {
	Installed ClientConfig `json:"installed"`
	Web       ClientConfig `json:"web"`
}

// openURL opens a browser window to the specified location.
// This code originally appeared at:
//   http://stackoverflow.com/questions/10377243/how-can-i-launch-a-process-that-is-not-a-file-in-go
func openURL(url string) error {
	var err error
	switch runtime.GOOS {
	case "linux":
		err = exec.Command("xdg-open", url).Start()
	case "windows":
		err = exec.Command("rundll32", "url.dll,FileProtocolHandler", "http://localhost:4001/").Start()
	case "darwin":
		err = exec.Command("open", url).Start()
	default:
		err = fmt.Errorf("Cannot open URL %s on this platform", url)
	}
	return err
}

// readConfig reads the configuration from clientSecretsFile.
// It returns an oauth configuration object for use with the Google API client.
func readConfig(scope string) (*oauth.Config, error) {
	// Read the secrets file
	data, err := ioutil.ReadFile(*clientSecretsFile)
	if err != nil {
		pwd, _ := os.Getwd()
		fullPath := filepath.Join(pwd, *clientSecretsFile)
		return nil, fmt.Errorf(missingClientSecretsMessage, fullPath)
	}

	cfg := new(Config)
	err = json.Unmarshal(data, &cfg;)
	if err != nil {
		return nil, err
	}

	var redirectUri string
	if len(cfg.Web.RedirectURIs) > 0 {
		redirectUri = cfg.Web.RedirectURIs[0]
	} else if len(cfg.Installed.RedirectURIs) > 0 {
		redirectUri = cfg.Installed.RedirectURIs[0]
	} else {
		return nil, errors.New("Must specify a redirect URI in config file or when creating OAuth client")
	}

	return &oauth.Config;{
		ClientId:     cfg.Installed.ClientID,
		ClientSecret: cfg.Installed.ClientSecret,
		Scope:        scope,
		AuthURL:      cfg.Installed.AuthURI,
		TokenURL:     cfg.Installed.TokenURI,
		RedirectURL:  redirectUri,
		TokenCache:   oauth.CacheFile(*cacheFile),
		// Get a refresh token so we can use the access token indefinitely
		AccessType: "offline",
		// If we want a refresh token, we must set this attribute
		// to force an approval prompt or the code won't work.
		ApprovalPrompt: "force",
	}, nil
}

// startWebServer starts a web server that listens on http://localhost:8080.
// The webserver waits for an oauth code in the three-legged auth flow.
func startWebServer() (codeCh chan string, err error) {
	listener, err := net.Listen("tcp", "localhost:8080")
	if err != nil {
		return nil, err
	}
	codeCh = make(chan string)
	go http.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		code := r.FormValue("code")
		codeCh <- code // send code to OAuth flow
		listener.Close()
		w.Header().Set("Content-Type", "text/plain")
		fmt.Fprintf(w, "Received code: %v\r\nYou can now safely close this browser window.", code)
	}))

	return codeCh, nil
}

// buildOAuthHTTPClient takes the user through the three-legged OAuth flow.
// It opens a browser in the native OS or outputs a URL, then blocks until
// the redirect completes to the /oauth2callback URI.
// It returns an instance of an HTTP client that can be passed to the
// constructor of the YouTube client.
func buildOAuthHTTPClient(scope string) (*http.Client, error) {
	config, err := readConfig(scope)
	if err != nil {
		msg := fmt.Sprintf("Cannot read configuration file: %v", err)
		return nil, errors.New(msg)
	}

	transport := &oauth.Transport;{Config: config}

	// Try to read the token from the cache file.
	// If an error occurs, do the three-legged OAuth flow because
	// the token is invalid or doesn't exist.
	token, err := config.TokenCache.Token()
	if err != nil {
		// Start web server.
		// This is how this program receives the authorization code
		// when the browser redirects.
		codeCh, err := startWebServer()
		if err != nil {
			return nil, err
		}

		// Open url in browser
		url := config.AuthCodeURL("")
		err = openURL(url)
		if err != nil {
			fmt.Println("Visit the URL below to get a code.",
				" This program will pause until the site is visted.")
		} else {
			fmt.Println("Your browser has been opened to an authorization URL.",
				" This program will resume once authorization has been provided.\n")
		}
		fmt.Println(url)

		// Wait for the web server to get the code.
		code := <-codeCh

		// This code caches the authorization code on the local
		// filesystem, if necessary, as long as the TokenCache
		// attribute in the config is set.
		token, err = transport.Exchange(code)
		if err != nil {
			return nil, err
		}
	}

	transport.Token = token
	return transport.Client(), nil
}

Post a channel bulletin

The code sample below calls the API's activities.insert method to post a bulletin to the channel associated with the request.

package main

import (
	"flag"
	"fmt"
	"log"

	"code.google.com/p/google-api-go-client/youtube/v3"
)

var (
	message    = flag.String("message", "", "Text message to post")
	videoID    = flag.String("videoid", "", "ID of video to post")
	playlistID = flag.String("playlistid", "", "ID of playlist to post")
)

func main() {
	flag.Parse()

	// A bulletin must contain a message and may also contain a video or a
	// playlist. You can post a message with or without an accompanying video
	// or playlist, but you can't post a video and playlist at the same time.
	if *message == "" {
		log.Fatalf("Please provide a message.")
	}

	if *videoID != "" && *playlistID != "" {
		log.Fatalf("You cannot post a video and a playlist at the same time.")
	}

	client, err := buildOAuthHTTPClient(youtube.YoutubeScope)
	if err != nil {
		log.Fatalf("Error building OAuth client: %v", err)
	}

	service, err := youtube.New(client)
	if err != nil {
		log.Fatalf("Error creating YouTube client: %v", err)
	}

	// Start making YouTube API calls.
	parts := "snippet"
	bulletin := &youtube.Activity;{
		Snippet: &youtube.ActivitySnippet;{
			Description: *message,
		},
	}

	if *videoID != "" || *playlistID != "" {
		parts = "snippet,contentDetails"

		// The resource ID element value differs depending on
		// whether a playlist or a video is being posted.
		var resourceId *youtube.ResourceId
		switch {
		case *videoID != "":
			resourceId = &youtube.ResourceId;{
				Kind:    "youtube#video",
				VideoId: *videoID,
			}
		case *playlistID != "":
			resourceId = &youtube.ResourceId;{
				Kind:       "youtube#playlist",
				PlaylistId: *playlistID,
			}
		}

		bulletin.ContentDetails = &youtube.ActivityContentDetails;{
			Bulletin: &youtube.ActivityContentDetailsBulletin;{
				ResourceId: resourceId,
			},
		}
	}

	call := service.Activities.Insert(parts, bulletin)
	_, err = call.Do()
	if err != nil {
		log.Fatalf("Error making API call to post bulletin: %v", err.Error())
	}

	fmt.Println("The bulletin was posted to your channel.")
}

Retrieve my uploads

The code sample below calls the API's playlistItems.list method to retrieve a list of videos uploaded to the channel associated with the request. The code also calls the channels.list method with the mine parameter set to true to retrieve the playlist ID that identifies the channel's uploaded videos.

package main

import (
	"flag"
	"fmt"
	"log"

	"code.google.com/p/google-api-go-client/youtube/v3"
)

func main() {
	flag.Parse()

	client, err := buildOAuthHTTPClient(youtube.YoutubeReadonlyScope)
	if err != nil {
		log.Fatalf("Error building OAuth client: %v", err)
	}

	service, err := youtube.New(client)
	if err != nil {
		log.Fatalf("Error creating YouTube client: %v", err)
	}

	// Start making YouTube API calls.
	// Call the channels.list method. Set the mine parameter to true to
	// retrieve the playlist ID for uploads to the authenticated user's
	// channel.
	call := service.Channels.List("contentDetails").Mine(true)

	response, err := call.Do()
	if err != nil {
		// The channels.list method call returned an error.
		log.Fatalf("Error making API call to list channels: %v", err.Error())
	}

	for _, channel := range response.Items {
		playlistId := channel.ContentDetails.RelatedPlaylists.Uploads
		// Print the playlist ID for the list of uploaded videos.
		fmt.Printf("Videos in list %s\r\n", playlistId)

		nextPageToken := ""
		for {
			// Call the playlistItems.list method to retrieve the
			// list of uploaded videos. Each request retrieves 50
			// videos until all videos have been retrieved.
			playlistCall := service.PlaylistItems.List("snippet").
				PlaylistId(playlistId).
				MaxResults(50).
				PageToken(nextPageToken)

			playlistResponse, err := playlistCall.Do()

			if err != nil {
				// The playlistItems.list method call returned an error.
				log.Fatalf("Error fetching playlist items: %v", err.Error())
			}

			for _, playlistItem := range playlistResponse.Items {
				title := playlistItem.Snippet.Title
				videoId := playlistItem.Snippet.ResourceId.VideoId
				fmt.Printf("%v, (%v)\r\n", title, videoId)
			}

			// Set the token to retrieve the next page of results
			// or exit the loop if all results have been retrieved.
			nextPageToken = playlistResponse.NextPageToken
			if nextPageToken == "" {
				break
			}
			fmt.Println()
		}
	}
}

Search by keyword

The code sample below calls the API's search.list method to retrieve search results associated with a particular keyword.

package main

import (
	"flag"
	"fmt"
	"log"
	"net/http"

	"code.google.com/p/google-api-go-client/googleapi/transport"
	"code.google.com/p/google-api-go-client/youtube/v3"
)

var (
	query      = flag.String("query", "Google", "Search term")
	maxResults = flag.Int64("max-results", 25, "Max YouTube results")
)

const developerKey = "YOUR DEVELOPER KEY"

func main() {
	flag.Parse()

	client := &http.Client;{
		Transport: &transport.APIKey;{Key: developerKey},
	}

	service, err := youtube.New(client)
	if err != nil {
		log.Fatalf("Error creating new YouTube client: %v", err)
	}

	// Make the API call to YouTube.
	call := service.Search.List("id,snippet").
		Q(*query).
		MaxResults(*maxResults)
	response, err := call.Do()
	if err != nil {
		log.Fatalf("Error making search API call: %v", err)
	}

	// Group video, channel, and playlist results in separate lists.
	videos := make(map[string]string)
	channels := make(map[string]string)
	playlists := make(map[string]string)

	// Iterate through each item and add it to the correct list.
	for _, item := range response.Items {
		switch item.Id.Kind {
		case "youtube#video":
			videos[item.Id.VideoId] = item.Snippet.Title
		case "youtube#channel":
			channels[item.Id.ChannelId] = item.Snippet.Title
		case "youtube#playlist":
			playlists[item.Id.PlaylistId] = item.Snippet.Title
		}
	}

	printIDs("Videos", videos)
	printIDs("Channels", channels)
	printIDs("Playlists", playlists)
}

// Print the ID and title of each result in a list as well as a name that
// identifies the list. For example, print the word section name "Videos"
// above a list of video search results, followed by the video ID and title
// of each matching video.
func printIDs(sectionName string, matches map[string]string) {
	fmt.Printf("%v:\n", sectionName)
	for id, title := range matches {
		fmt.Printf("[%v] %v\n", id, title)
	}
	fmt.Printf("\n\n")
}

Search by topic

The code sample below calls the API's search.list method to retrieve search results associated with a particular Freebase topic.

package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"

	"code.google.com/p/google-api-go-client/googleapi/transport"
	"code.google.com/p/google-api-go-client/youtube/v3"
)

var (
	query      = flag.String("query", "Google", "Freebase search term")
	maxResults = flag.Int64("max-results", 25, "Max YouTube results")
	resultType = flag.String("type", "channel", "YouTube result type: video, playlist, or channel")
)

const developerKey = "YOUR DEVELOPER KEY HERE"
const freebaseSearchURL = "https://www.googleapis.com/freebase/v1/search?%s"

// Notable is struct for unmarshalling JSON values from the API.
type Notable struct {
	Name string
	ID   string
}

// FreebaseTopic is struct for unmarshalling JSON values from the API.
type FreebaseTopic struct {
	Mid     string
	ID      string
	Name    string
	Notable Notable
	Lang    string
	Score   float64
}

// FreebaseResponse is struct for unmarshalling JSON values from the Freebase API.
type FreebaseResponse struct {
	Status string
	Result []FreebaseTopic
}

func main() {
	flag.Parse()

	topicID, err := getTopicID(*query)
	if err != nil {
		log.Fatalf("Cannot fetch topic ID from Freebase: %v", err)
	}

	err = youtubeSearch(topicID)
	if err != nil {
		log.Fatalf("Cannot make YouTube API call: %v", err)
	}
}

// getTopicID queries Freebase with the given string. It then prompts the user
// to select a topic, then returns the selected topic so that the topic can be
// used to search YouTube for videos, channels or playlists.
func getTopicID(topic string) (string, error) {
	urlParams := url.Values{
		"query": []string{topic},
		"key":   []string{developerKey},
	}

	apiURL := fmt.Sprintf(freebaseSearchURL, urlParams.Encode())

	resp, err := http.Get(apiURL)
	if err != nil {
		return "", err
	} else if resp.StatusCode != http.StatusOK {
		errorMsg := fmt.Sprintf("Received HTTP status code %v using developer key: %v",
			resp.StatusCode, developerKey)
		return "", errors.New(errorMsg)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	var data FreebaseResponse
	err = json.Unmarshal(body, &data;)
	if err != nil {
		return "", nil
	}

	if len(data.Result) == 0 {
		return "", errors.New("No matching terms were found in Freebase.")
	}

	// Print a list of topics for the user to select.
	fmt.Println("The following topics were found:")
	for index, topic := range data.Result {
		if topic.Notable.Name == "" {
			topic.Notable.Name = "Unknown"
		}
		fmt.Printf("   %2d. %s (%s)\r\n", index+1, topic.Name, topic.Notable.Name)
	}

	prompt := fmt.Sprintf("Enter a topic number to find related YouTube %s [1-%v]: ",
		*resultType, len(data.Result))
	selection, err := readInt(prompt, 1, len(data.Result))
	if err != nil {
		return "", nil
	}
	choice := data.Result[selection-1]
	return choice.Mid, nil
}

// readInt reads an integer from standard input and verifies that the value
// is between the allowed min and max values (inclusive).
func readInt(prompt string, min int, max int) (int, error) {
	// Loop until we have a valid input.
	for {
		fmt.Print(prompt)
		var i int
		_, err := fmt.Fscan(os.Stdin, &i;)
		if err != nil {
			return 0, err
		}
		if i < min || i > max {
			fmt.Println("Invalid input.")
			continue
		}
		return i, nil
	}
}

// youtubeSearch searches YouTube for the topic given in the query flag and
// prints the results. This function takes a mid parameter, which specifies
// a value retrieved using the Freebase API.
func youtubeSearch(mid string) error {
	client := &http.Client;{
		Transport: &transport.APIKey;{Key: developerKey},
	}

	service, err := youtube.New(client)
	if err != nil {
		return err
	}

	// Make the API call to YouTube.
	call := service.Search.List("id,snippet").
		TopicId(mid).
		Type(*resultType).
		MaxResults(*maxResults)
	response, err := call.Do()
	if err != nil {
		return err
	}

	// Iterate through each item and output it.
	for _, item := range response.Items {
		itemID := ""
		switch item.Id.Kind {
		case "youtube#video":
			itemID = item.Id.VideoId
		case "youtube#channel":
			itemID = item.Id.ChannelId
		case "youtube#playlist":
			itemID = item.Id.PlaylistId
		}
		fmt.Printf("%v (%v)\r\n", item.Snippet.Title, itemID)
	}
	return nil
}

Upload a video

The code sample below calls the API's videos.insert method to upload a video to the channel associated with the request.

package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"strings"

	"code.google.com/p/google-api-go-client/youtube/v3"
)

var (
	filename    = flag.String("filename", "", "Name of video file to upload")
	title       = flag.String("title", "Test Title", "Video title")
	description = flag.String("description", "Test Description", "Video description")
	category    = flag.String("category", "22", "Video category")
	keywords    = flag.String("keywords", "", "Comma separated list of video keywords")
	privacy     = flag.String("privacy", "unlisted", "Video privacy status")
)

func main() {
	flag.Parse()

	if *filename == "" {
		log.Fatalf("You must provide a filename of a video file to upload")
	}

	client, err := buildOAuthHTTPClient(youtube.YoutubeUploadScope)
	if err != nil {
		log.Fatalf("Error building OAuth client: %v", err)
	}

	service, err := youtube.New(client)
	if err != nil {
		log.Fatalf("Error creating YouTube client: %v", err)
	}

	upload := &youtube.Video;{
		Snippet: &youtube.VideoSnippet;{
			Title:       *title,
			Description: *description,
			CategoryId:  *category,
		},
		Status: &youtube.VideoStatus;{PrivacyStatus: *privacy},
	}

	// The API returns a 400 Bad Request response if tags is an empty string.
	if strings.Trim(*keywords, "") != "" {
		upload.Snippet.Tags = strings.Split(*keywords, ",")
	}

	call := service.Videos.Insert("snippet,status", upload)

	file, err := os.Open(*filename)
	defer file.Close()
	if err != nil {
		log.Fatalf("Error opening %v: %v", *filename, err)
	}

	response, err := call.Media(file).Do()
	if err != nil {
		log.Fatalf("Error making YouTube API call: %v", err)
	}
	fmt.Printf("Upload successful! Video ID: %v\n", response.Id)
}

Authentication required

You need to be signed in with Google+ to do that.

Signing you in...

Google Developers needs your permission to do that.