🌊

Google Home をミュージックジュークボックスにする(Google Drive 型)

2022/10/17に公開

Google Home をミュージックジュークボックスにする(Google Drive 型)

前回までの「Google Home をミュージックジュークボックスにする(改良型)」までで、十分に満足できる感じになってたのですが、どうせなら Google Drive からストリーミングできたらいいな、ということで、作成しました。

準備物

前回使用した go-chromecastgo-cast は今回も使います。

MP3 配信サーバ

前回作成した配信サーバを、今回は Google Drive からストリーミングできるように改造しました。

以下のコードをコンパイルして、バイナリを作成し、パスの通ったディレクトリへ設置してください。

package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"sort"
	"strconv"

	"google.golang.org/api/drive/v3"
)

var (
	mp3list map[string]string = map[string]string{}
	folder  string            = "./MP3"
)

func list(folderID, folderName string) {
	ctx := context.Background()

	srv, err := drive.NewService(ctx)
	if err != nil {
		log.Fatalf("Unable to retrieve Drive client: %v", err)
	}

	paging := ""
	for {
		r, err := srv.Files.List().
			SupportsAllDrives(true).PageSize(1000).
			Fields("nextPageToken, files(id, name, mimeType, parents)").
			PageToken(paging).
			Q(fmt.Sprintf("'%s' in parents", folderID)). // 特定のフォルダ配下
			Context(ctx).Do()
		if err != nil {
			log.Fatalf("Unable to retrieve files: %v", err)
		}

		for _, f := range r.Files {
			name := folderName + "/" + f.Name
			if f.MimeType == "audio/mp3" ||
				f.MimeType == "audio/mpeg" {
				mp3list[name] = f.Id
			} else if f.MimeType == "application/vnd.google-apps.folder" {
				list(f.Id, name)
			} else {
				// println(f.Name, f.MimeType, f.Id)
			}
		}

		paging = r.NextPageToken
		if paging == "" {
			break
		}
	}
}

func keys(m map[string]string) []string {
	ks := []string{}
	for k := range m {
		ks = append(ks, k)
	}
	return ks
}

func nextHandler(rw http.ResponseWriter, r *http.Request) {
	number, _ := strconv.Atoi(r.URL.Query().Get("number"))

	filelist := keys(mp3list)
	if number < 0 {
		number = 0
	} else if number >= len(filelist) {
		rw.Write([]byte(nil))
		return
	}
	sort.Strings(filelist)
	target := filelist[number]
	// fmt.Println("target: " + target)
	// fileID := mp3list[target]
	rw.Write([]byte(target))
}

func fileHandler(rw http.ResponseWriter, r *http.Request) {
	target := r.URL.Query().Get("target")
	fileID := mp3list[target]

	ctx := context.Background()
	srv, err := drive.NewService(ctx)
	if err != nil {
		log.Fatalf("Unable to retrieve Drive client: %v", err)
	}

	resp, err := srv.Files.Get(fileID).
		SupportsAllDrives(true).Context(ctx).Download()
	if err != nil || resp == nil {
		fmt.Printf("get drive file: %v", err)
	}
	defer resp.Body.Close()

	if _, err := io.Copy(rw, resp.Body); err != nil {
		fmt.Printf("write file: %v", err)
	}
}

func main() {
	if len(os.Args) < 2 {
		fmt.Println("usage: serve [google drive folder ID]")
		os.Exit(0)
	}
	folderID := os.Args[1]
	list(folderID, "")
	for name := range mp3list {
		fmt.Println(name)
	}

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
		log.Printf("Defaulting to port %s", port)
	}
	http.HandleFunc("/next", nextHandler)
	http.HandleFunc("/mp3", fileHandler)

	log.Printf("Listening on port %s", port)
	log.Fatal(
		http.ListenAndServe(fmt.Sprintf("0.0.0.0:%s", port),
			nil))
}

また、 この記事 を参考に、 Google Drive の MP3 ファイルを置いているフォルダへサービスアカウントからの読み込み許可と、 JSON ファイルのダウンロードを行います。

起動スクリプト

今回のミュージックジュークボックスのスクリプトは以下になります。

#!/bin/sh

# port number
port="8808"
# my ip address
myip="192.168.??.??:${port}"
# chromecast ip address
castip="192.168.??.??"

trap "exit" INT TERM
trap "kill 0" EXIT
(export PORT=${port}; export GOOGLE_APPLICATION_CREDENTIALS=./hogehoge.json; serve [Google Drive Folder ID] 2>/dev/null &)

play () {
	file="`wget -O - http://${myip}/next?number=$index 2> /dev/null`"
	if [ -z "$file" ]; then
		index=0
		file="`wget -O - http://${myip}/next?number=$index 2> /dev/null`"
	fi
	echo ${file}
	#echo go-cast --host=${castip} media play "http://${myip}/mp3?target=${file}"
	go-cast --host=${castip} media play "http://${myip}/mp3?target=${file}" >/dev/null
	index=$((index+1))
}

index=0
go-chromecast watch --addr "${castip}" |
        (\
        while read line
        do
		#echo $line
		status="`echo $line | sed 's/^.*"playerState":"//g' | sed 's/".*$//g'`"
                #echo $status
                if [ -n "`echo ${status} | grep 'YouTube Music'`" ] || [ "$status" = "IDLE" ]; then
			#echo "START!"
			#go-cast --host=${castip} quit
			play
                elif [ "$status" = "CLOSE" ]; then
                        #echo "STOP!"
			go-cast --host=${castip} quit
                fi
        done
        )

wait

以下の行の JSON ファイルをダウンロードした JSON ファイルへ、そして Google Drive の Folder ID への置き換えを忘れずに行ってください。

(export PORT=${port}; export GOOGLE_APPLICATION_CREDENTIALS=./hogehoge.json; serve [Google Drive Folder ID] 2>/dev/null &)

実行

すべて設置出来たら、前回と同じように、 Google Home に向かって 音楽を再生して などと命令してください。 Google Drive の指定のフォルダからファイル名の辞書順で音楽をループ再生し始めます。フォルダ構造はネストされていても、再帰的に読み込みます。

共有ドライブにも対応している(と思います)ので、活用してください。

Discussion