LoginSignup
0
0

More than 1 year has passed since last update.

箱推しのためのYoutube予定表

Posted at

前置き

「あのVTuber事務所の配信スケジュール、一週間以上落ちてるんだよね」
「仕方ない、俺が作るか」
そんなノリで作ろうとしたら翌日には復旧した俺の気持ち。

要件

・YoutubeAPIのリクエストは日単位での回数制限があるため、一度取得した結果は保存しておく
・変更・削除に対応するため、再取得する際、前回の結果は削除する
・チャンネル数は20程度またはそれ以下を想定
・アクセス数もそんなに大量にない
・移行の容易性を考えデータはDBではなくファイルに保持
・ちゃんと配信予定(枠)を事前に作ってくれると効果大

作り方

API keyの発行

今回はyoutube APIを使用しているのですが、使用にはAPI keyを発行する必要があります。
一日の使用回数に制限がありますが、googleアカウントがあれば無料で発行できます。
下記の記事に詳しい手順が記載してあります。
https://qiita.com/shinkai_/items/10a400c25de270cb02e4

チャンネルの枠の一覧を取得

下記が公式リファレンスなのですが、ちょっと情報量としては心許ないです。
https://developers.google.com/youtube/v3/docs/search/list?hl=ja

リクエスト

※変数は${}で括ってます。

https://content-youtube.googleapis.com/youtube/v3/search?order=date&channelId=${ch_id}&part=id&part=snippet&key=${api_key}&eventType=upcoming&type=video

eventType=upcomingとtype=videoで予定のみに絞り込んでいます。

レスポンス

サンプルは2022/05/16時点での月紫アリアさんのチャンネルです。
毎週月曜に一週間分の予定を作るタイプの方なので、いつも見栄えのする検証に使わせて頂いています。感謝を込めて宣伝。

{
  "kind": "youtube#searchListResponse",
  "etag": "xx5NiXUjsXpks1JuRmEQ_i8t2GM",
  "nextPageToken": "CAUQAA",
  "regionCode": "JP",
  "pageInfo": {
    "totalResults": 7,
    "resultsPerPage": 5
  },
  "items": [
    {
      "kind": "youtube#searchResult",
      "etag": "aAckZdUnkx9QlNxuwUyU_5JA5NA",
      "id": {
        "kind": "youtube#video",
        "videoId": "96RcK_TkG-4"
      },
      "snippet": {
        "publishedAt": "2022-05-16T09:12:59Z",
        "channelId": "UC5XQhzMH08PgWa4Zp02Gcsw",
        "title": "【歌枠/singing】初見歓迎💜日曜日のかわいいおうた、聴きに来てよ~💜  karaoke【月紫アリア/新人Vtuber】",
        "description": "5月ボイス ♡ ⸝ ▶️ https://react.booth.pm/items/3799773 新衣装アリアと初めてのデートっ! そのあとはおうちで…甘えても ...",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/96RcK_TkG-4/default_live.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/96RcK_TkG-4/mqdefault_live.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/96RcK_TkG-4/hqdefault_live.jpg",
            "width": 480,
            "height": 360
          }
        },
        "channelTitle": "月紫アリアch / Tsukushi Aria",
        "liveBroadcastContent": "upcoming",
        "publishTime": "2022-05-16T09:12:59Z"
      }
    },
    {
      "kind": "youtube#searchResult",
      "etag": "1ku9uEzetM2hpoPBcTADzzdU8R0",
      "id": {
        "kind": "youtube#video",
        "videoId": "_UfGM-2wQq0"
      },
      "snippet": {
        "publishedAt": "2022-05-16T04:49:34Z",
        "channelId": "UC5XQhzMH08PgWa4Zp02Gcsw",
        "title": "【雀魂】ルーレットで決められた属性で麻雀🀄💜色んなアリアを見て…💜そしてボコす【月紫アリア/夢川かなう/姫熊りぼん/九楽ライ/新人Vtuber】",
        "description": "夢川かなう/Kanau ch @Himekuma Ribon Ch. 姫熊 りぼん @Kulaku Lie ch.九楽ライ すべてはノリ #ぴちくまねくろにーと ...",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/_UfGM-2wQq0/default_live.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/_UfGM-2wQq0/mqdefault_live.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/_UfGM-2wQq0/hqdefault_live.jpg",
            "width": 480,
            "height": 360
          }
        },
        "channelTitle": "月紫アリアch / Tsukushi Aria",
        "liveBroadcastContent": "upcoming",
        "publishTime": "2022-05-16T04:49:34Z"
      }
    },
    {
      "kind": "youtube#searchResult",
      "etag": "qr8tKI6lbK9P68A3NwfXgbURYHo",
      "id": {
        "kind": "youtube#video",
        "videoId": "x09-gfZITm0"
      },
      "snippet": {
        "publishedAt": "2022-05-16T04:44:24Z",
        "channelId": "UC5XQhzMH08PgWa4Zp02Gcsw",
        "title": "【Escape Simulator】謎解きで脱出ゲーム🔓❕頭やわらかぁくするぞ~💜【月紫アリア/フンボルトペンギン/Vtuber】",
        "description": "初コラボ     @フンボルトペンギン / Humboldt Penguin ⋱⋰ ⋱⋰ ⋱⋰ ⋱⋰⋱⋰ ⋱⋰ ⸜ ♡ 5月ボイス ...",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/x09-gfZITm0/default_live.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/x09-gfZITm0/mqdefault_live.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/x09-gfZITm0/hqdefault_live.jpg",
            "width": 480,
            "height": 360
          }
        },
        "channelTitle": "月紫アリアch / Tsukushi Aria",
        "liveBroadcastContent": "upcoming",
        "publishTime": "2022-05-16T04:44:24Z"
      }
    },
    {
      "kind": "youtube#searchResult",
      "etag": "FxMUGIK9_ZGVNfLvpZTb_SzE1jA",
      "id": {
        "kind": "youtube#video",
        "videoId": "TIdlzHDjVFQ"
      },
      "snippet": {
        "publishedAt": "2022-05-16T04:36:46Z",
        "channelId": "UC5XQhzMH08PgWa4Zp02Gcsw",
        "title": "【朝活】初見歓迎💜週の真ん中!可愛いアリアと気合い入れるぞ💜 good moning【月紫アリア/新人Vtuber】",
        "description": "5月ボイス ♡ ⸝ ▶️ https://react.booth.pm/items/3799773 新衣装アリアと初めてのデートっ! そのあとはおうちで…甘えても ...",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/TIdlzHDjVFQ/default_live.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/TIdlzHDjVFQ/mqdefault_live.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/TIdlzHDjVFQ/hqdefault_live.jpg",
            "width": 480,
            "height": 360
          }
        },
        "channelTitle": "月紫アリアch / Tsukushi Aria",
        "liveBroadcastContent": "upcoming",
        "publishTime": "2022-05-16T04:36:46Z"
      }
    },
    {
      "kind": "youtube#searchResult",
      "etag": "ISSqY8DCr-Lb8frnKTLV6dHd4Ss",
      "id": {
        "kind": "youtube#video",
        "videoId": "P4PdMaItZ1w"
      },
      "snippet": {
        "publishedAt": "2022-05-16T04:35:26Z",
        "channelId": "UC5XQhzMH08PgWa4Zp02Gcsw",
        "title": "【ASMR】心音耐久💜ぎゅってして寝たいから、こっちおいで?💜 whisper/heart beat/sleeping sound【月紫アリア/新人Vtuber】",
        "description": "すやすやしてってね~   #ASMR #バイノーラル #睡眠誘導 ⋱⋰ ⋱⋰ ⋱⋰ ⋱⋰⋱⋰ ⋱⋰ ⸜ ♡ 5月ボイス ...",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/P4PdMaItZ1w/default_live.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/P4PdMaItZ1w/mqdefault_live.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/P4PdMaItZ1w/hqdefault_live.jpg",
            "width": 480,
            "height": 360
          }
        },
        "channelTitle": "月紫アリアch / Tsukushi Aria",
        "liveBroadcastContent": "upcoming",
        "publishTime": "2022-05-16T04:35:26Z"
      }
    }
  ]
}

ざっくりレスポンス解説

totalResults: 7
resultsPerPage: 5
となっているので、ページに続きがあるようです。
必要に応じて「maxResults」パラメータを追加するか、「pageToken」を使ってループさせましょう。

items:枠の配列
items[].id.videoId:動画のID
 https://www.youtube.com/watch?v=${videoId}
 で動画へのリンクになる。
items[].snippet.publishedAt:登録日時 更新の場合にどうなるかは不明
items[].snippet.publishTime:publishedAtとの違いは不明
items[].snippet.title:動画・配信のタイトル
items[].snippet.description:いわゆる概要欄 80文字くらいで省略される
items[].snippet.thumbnails:サムネイル 大きさによって3つに分かれている

枠の詳細を取得

一覧だけでもそれなりの情報は得られますが、肝心な配信・公開日時は別のAPIで取得しなければならないようです。
勿論、リファレンスも一応あります。
https://developers.google.com/youtube/v3/docs/videos/list?hl=ja

リクエスト

先のAPIで取得したitems[].id.videoIdを使用して検索します。

https://www.googleapis.com/youtube/v3/videos?key=${apikey}&id=${videoId}&part=liveStreamingDetails

レスポンス

{
  "kind": "youtube#videoListResponse",
  "etag": "3VLGoEdDbvn1ADPHHeO4ZEmq2ik",
  "items": [
    {
      "kind": "youtube#video",
      "etag": "cWvV7JhHFmOqagMLX8GHXKBuIoE",
      "id": "TIdlzHDjVFQ",
      "liveStreamingDetails": {
        "scheduledStartTime": "2022-05-17T22:00:00Z",
        "activeLiveChatId": "Cg0KC1RJZGx6SERqVkZRKicKGFVDNVhRaHpNSDA4UGdXYTRacDAyR2NzdxILVElkbHpIRGpWRlE"
      }
    }
  ],
  "pageInfo": {
    "totalResults": 1,
    "resultsPerPage": 1
  }
}

videoIdを配列で指定したり未指定だとitemsが複数になりますが、今回は単一のvideoIdを指定しているので、itemsは常に0-1件です。
items[0].liveStreamingDetails.scheduledStartTimeが配信開始日時になります。

サンプルプログラム

まずバッチを一定周期で実行することでデータを取得し、'./files/schedule'に保存します。
チャンネルIDの一覧は./files/ch_list.txtに改行区切りで保存しています。
実行周期を短くすることでyoutubeの最新情報とのタイムラグを軽減できますが、短くしすぎるとAPIの実行回数が上限に達しやすくなります。

get_schedule_data.php
<?php
date_default_timezone_set('Asia/Tokyo');
$apikey = "hoge";

// 前回実行時のファイルを削除
foreach(glob("./files/schedule/*") as $val) {
    unlink($val);
}

// チャンネルidの一覧を取得
$ch_ids = explode("\r\n", file_get_contents('./files/ch_list.txt'));

foreach($ch_ids as $ch_id){
    if(empty($ch_id)){
        continue;
    }
    // チャンネル毎に枠の一覧を取得
    $upcomings = json_decode(`curl -XGET "https://content-youtube.googleapis.com/youtube/v3/search?order=date&channelId=$ch_id&part=id&part=snippet&key=$apikey&eventType=upcoming&type=video&maxResults=10"`, true);
    foreach($upcomings['items'] as $item){
        $videoid = $item['id']['videoId'];
        // 枠の詳細を取得
        $video = json_decode(`curl -XGET "https://www.googleapis.com/youtube/v3/videos?key=$apikey&id=$videoid&part=liveStreamingDetails"`, true);
        $item['video'] = $video;
        
        file_put_contents("./files/schedule/" . $item['id']['videoId'] . ".json", json_encode($item));
    }
}

保存した情報をWEBで参照します。
サンプルでは、24時間前(配信中の枠を考慮)~7日後を表示対象に、予定のない日は非表示にしています。

schedule.php
<?php
date_default_timezone_set('Asia/Tokyo');

// 1日前~7日後
$min_time = (time() - (24 * 60 * 60));
$max_time = (time() + (7 * 24 * 60 * 60));

$items = [];
foreach(glob("./files/schedule/*.json") as $file){
    $item = json_decode(file_get_contents($file), true);
    $scheduledStartTime = $item['video']['items'][0]['liveStreamingDetails']['scheduledStartTime'];
    
    $start_time = strtotime($scheduledStartTime);
    if($min_time < $start_time && $start_time < $max_time){
        $start_date = date('Y/m/d', strtotime($scheduledStartTime));
    
        $videoid = $item['id']['videoId'];
        //$sortkey = $scheduledStartTime . '_' . $item['snippet']['channelId'];
        $sortkey = $scheduledStartTime;
        
        if(!isset($items[$start_date])){
            $items[$start_date] = [];
        }
        $items[$start_date][$sortkey] = $item;
    }
}

// 配信開始時刻・チャンネルID順でソート
$sorted_items = [];
foreach($items as $key => $item){
    ksort($item);
    $sorted_items[$key] = $item;
}
// 日付順でソート
ksort($sorted_items);

?>
<html lang="ja">
<head>
<meta charset="UTF-8">
</head>
<body>
<table style="border:solid 1px">
    <thead>
        <tr>
<?php foreach($sorted_items as $key => $item){ ?>
            <td style="border:solid 1px"><?= $key ?></td>
<?php } ?>
        </tr>
    </thead>
    <tbody>
        <tr>
<?php foreach($sorted_items as $item){ ?>
            <td style="vertical-align:top;border:solid 1px">
    <?php foreach($item as $column){ ?>
        <?= $column['snippet']['channelTitle'] ?><br>
        <?= $column['snippet']['title'] ?><br>
        <?= date('Y/m/d H:i:s',strtotime($column['video']['items'][0]['liveStreamingDetails']['scheduledStartTime'])); ?><br>
                <a href="https://www.youtube.com/watch?v=<?=$column['id']['videoId'] ?>"><img src="<?= $column['snippet']['thumbnails']['default']['url'] ?>"><br></a>
                <br>
    <?php } ?>
            </td>
<?php } ?>
        </tr>
    </tbody>
</table>
</body>
</html>

課題

複数推しのファンや新興の事務所運営さん等に提供したいが、オンプレにするかAWSサーバーでも立ててクラウド提供するか。
・オンプレの場合は自前でサーバーを用意する必要があるため、導入の敷居がやや高くなる。
・クラウドの場合はAPI実行回数の制約がある。利用者のAPIkeyを使わせてもらう手もあるが、人様のkeyを預かれるほどセキュリティを強固にするのも面倒臭い。

参考

https://schedule.hololive.tv/
大手の割にはシンプルですが、これくらい余計な情報はないほうがいいのかもしれません。

https://schedule.v-react.com/
概要欄を自動スクロールで流しているのがオシャレですね。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0