PythonとYoutube APIを使って任意のチャンネルのライブリストの時刻を取得した①

自分の関連記事
>Pythonを使ってCSVの予定をGoogleカレンダーに載っけてみた
>https://qiita.com/Octpascal/items/07e53bd89dfbca93bf3e
>
>PythonとGoogle APIを使ってCSVの予定でGoogleカレンダーを更新してみた
>https://qiita.com/Octpascal/items/f0b2dfc5fc4d58cf9d8e

# 本プログラムの概要

### 目的
~~推しのVTuberの配信をリスト化するため、~~YouTubeの動画のリストを取得する。
前述のGoogleカレンダーAPIに投げるためのCSVに直す。
~~これで、全員のその日の配信一覧を見るためにネットを巡回する必要がなくなる!~~

### 内容
+ ライブストリーミングアーカイブであれば、開始時刻と終了時刻の取得
+ 投稿動画であれば、投稿時刻と(投稿＋動画時間)の時刻を取得
+ ライブストリーミング中であれば、開始時刻と終了時刻（現在時刻＋1時間）を生成
+ ライブ配信予定では配信開始予定時刻と終了時刻(開始時刻+1時間)を生成

# Youtube APIをたたくプログラム
```Python
import datetime
import isodate
import requests
import json
from apiclient.discovery import build

YOUTUBE_API_KEY = 'your API Key'
youtube = build('youtube', 'v3', developerKey=YOUTUBE_API_KEY)

def YouTubelist(nPT,channnel):
	# APIをたたく
	search_response = youtube.search().list(
		channelId=channnel,
		part='snippet',
		maxResults=5,
		order='date',
		type='video',
		pageToken=nPT
	).execute()
	
	# NextPageTokenの表示
	print(search_response['nextPageToken'])

	# VideoIDのリストを生成
	video_ids = []
	items = search_response['items']
	for item in items :
		video_ids.append(item['id']['videoId'])

	# それぞれのライブ開始・終了時刻を取得
	details = youtube.videos().list(
		part='liveStreamingDetails',
		id=video_ids
	).execute()
	detailitems = details['items']

	# APIから返される時刻をy/m/d H:M:Sに変換
	JST = datetime.timedelta(hours=9)
	def timetrans(strtime):
		stime = datetime.datetime.fromisoformat(strtime[:-1]) + JST
		return stime.replace(microsecond=0)

	# 動画の長さを取得
	def videolength(video_id):
		Cdetail = youtube.videos().list(
			part='contentDetails',
			id=video_id
		).execute()
		duration = Cdetail['items'][0]['contentDetails']['duration']
		return isodate.parse_duration(duration)


	searchdelta = datetime.timedelta(hours=1)

	# 判定部
	csvdata = []
	for item, detail in zip(items, detailitems):

		title = item['snippet']['title']
		video_id = item['id']['videoId']

		state = item['snippet']['liveBroadcastContent']
		if state == 'upcoming':
			starttime = timetrans(detail['liveStreamingDetails']['scheduledStartTime'])
			endtime = starttime + datetime.timedelta(hours=1)
		elif state == 'live':
			starttime = timetrans(detail['liveStreamingDetails']['actualStartTime'])
			endtime = datetime.datetime.now().replace(microsecond=0) + searchdelta
		else:
			try:
				starttime = timetrans(detail['liveStreamingDetails']['actualStartTime'])
			except:
				starttime = timetrans(item['snippet']['publishTime'])
				endtime = starttime + videolength(video_id)
			else :
				endtime = timetrans(detail['liveStreamingDetails']['actualEndTime'])
			
		print(starttime, '->', endtime, title, video_id)

		# CSVデータの生成
		if endtime.second != 0:
			endtime += datetime.timedelta(minutes=1)
		csvdata.append(
			{
				'vid': video_id,
				'cid': '',
				'summary': title,
				# 'location': '',
				'description': 'https://www.youtube.com/watch?v='+video_id,
				'start': starttime.strftime('%Y/%m/%d %H:%M'),
				'end': endtime.strftime('%Y/%m/%d %H:%M'),
			}
		)

	return search_response['nextPageToken'], csvdata

if __name__ == "__main__":
	print(YouTubelist(None, 'Channnel ID'))
```

### build
前述の関連記事内に書いてある。
Google Client Libraryからbuildを使います。

### APIを叩く
```Python
search_response = youtube.search().list(
    channelId=channnel,
    part='snippet',
    maxResults=5,
    order='date',
    type='video',
    pageToken=nPT
).execute()
```

Search.list APIでは動画を検索するAPIです。ワードで検索したりプレイリスト検索・ユーザー検索などもできますが、今回はチャンネルを指定し、動画を5つずつ取得しています。
pageTokenについて。例えば、最初に1~5のデータを取得したときにnextPageTokenが返されます。それを付けてAPIを叩くと6~10が返されます。
order='date'とすると、投稿時刻順に取得できますが、ライブストリーミングの場合、枠の生成順になっているらしく、必ずしも放送順とはならないようです。

### それぞれのライブ開始・終了時刻を取得
```Python
details = youtube.videos().list(
    part='liveStreamingDetails',
    id=video_ids
).execute()
detailitems = details['items']
```

videos.listで各動画の詳細を取得できます。
Searchで取得した動画idを投げます。idはリストで渡すことができます。partは取得するデータの内容ですが、
liveStreamingDetailsでライブのスケジュール時刻、実際の開始時刻・終了時刻を取得できます。

これは取得したいチャンネルがライブ数>動画投稿数なのもありますが、liveStreamingDetailsを使わなければ、「終了して動画化した配信」と「投稿動画」の区別がつかないようです。

### 判定部
**※時刻の変換、動画の長さ取得は後述**
liveStreamingDetailsでライブストリーミングの詳細が入手できます。
この情報には"scheduledStartTime":予定されている(いた)開始時刻、"actualStartTime":実際の開始時刻、"actualEndTime":実際の終了時刻。

終了していないライブはactualEndTimeは存在しません。
開始していないライブはactualStartTimeも存在しません。
投稿動画はすべての情報がありません。

また、search.list APIのsnippet内でliveBroadcastContentという項目があり、

- live:現在live中
- upcoming:ライブ予定
- none:ライブ終了or動画投稿

と判別できます。

```Python
state = item['snippet']['liveBroadcastContent']
if state == 'upcoming':
    starttime = timetrans(detail['liveStreamingDetails']['scheduledStartTime'])
    endtime = starttime + datetime.timedelta(hours=1)
elif state == 'live':
    starttime = timetrans(detail['liveStreamingDetails']['actualStartTime'])
    endtime = datetime.datetime.now().replace(microsecond=0) + searchdelta
else:
    try:
        starttime = timetrans(detail['liveStreamingDetails']['actualStartTime'])
    except:
        starttime = timetrans(item['snippet']['publishTime'])
        endtime = starttime + videolength(video_id)
    else :
        endtime = timetrans(detail['liveStreamingDetails']['actualEndTime'])
```
upcoming→開始予定時刻取得
live→開始時刻取得
none→開始時刻と終了時刻を取得
　　→取得できなければ、投稿動画であるので投稿時間と動画の長さを取得する

### 動画の長さを取得
```Python
def videolength(video_id):
    Cdetail = youtube.videos().list(
        part='contentDetails',
        id=video_id
    ).execute()
    duration = Cdetail['items'][0]['contentDetails']['duration']
    return isodate.parse_duration(duration)
```
videos.list APIのpart='contentDetails'とすると、動画の長さが取得できます。PT#M#Sの形で返ってきます。
isodateのライブラリで変換できます。

### 時刻変換
isodateでも変換できますが、datetime.fromisoformatで変換しています。

### CSVデータの生成
トップにある[関連記事](https://qiita.com/Octpascal/items/f0b2dfc5fc4d58cf9d8e)の形式のデータに変換しています。

# さいごに
CSVの記述部分はは別記事で書きます……。
YoutubeDataAPIでMembership限定動画を取得する方法はあるか、探索中。