シャニマスのガシャ・イベントロゴを収集したい！

> ⚠️ : 加筆修正した記事を  [Zenn](https://zenn.dev/arrow2nd/articles/433909fc4f70c4) に投稿しています。

# まえがき

[アイドルマスターシャイニーカラーズ](https://shinycolors.idolmaster.jp) は バンダイナムコエンターテインメントが運営する、「THE IDOLM@STER シリーズ」のブラウザゲームです。

このシャニマス、とにかく「イラストが凄く綺麗」なのですが、**ロゴも最高に綺麗** なんですよね...。

そんなロゴたちを集めていつでも見られるようにしたい！

# 方法

ガシャ・イベント共に、開催前にツイッターに以下のような動画が投稿される場合が多いので、今回はこの動画を保存して、ロゴ部分のフレームを画像としてエクスポートしようと思います。

https://twitter.com/imassc_official/status/1336189011870748673?s=20&t=mo2QUP7K4r4M83BtfapQDw

# 環境

- macOS Monterey 12.3 (Intel)
- zsh
- Python / Deno の環境がある
- Twitter の開発者アカウントがある

# 手順

## 1. ツイートを集める

Twitter の [Recent search API](https://developer.twitter.com/en/docs/twitter-api/tweets/search/api-reference) では 直近7日間のツイートしか検索できないので、Python製スクレイピングツールの [Twint](https://github.com/twintproject/twint) を利用しました。

```sh
pip3 install twint
```

[Basic-usage](https://github.com/twintproject/twint/wiki/Basic-usage) を参考にしつつ検索していきます。

```sh
# 「復刻を除くシナリオイベントかつ、動画があるツイート」を検索してJSONで出力
twint -u imassc_official -s "シナリオイベント -復刻" --videos -o sc_event.json --json --limit 1000
```

これを実行すると検索結果を含んだ JSON ファイルが出力されます。

が、なぜか配列になっていないので手直しする必要があります。

私の場合は VSCode 上で、行頭・行末に角括弧を追加、正規表現検索で `}\n$` を `},\n` に置換して対応しました。

```jsonc:sc_event.json
[
  {"id": 1505063643624665093, "conversation_id": "1505063643624665093", "created_at": "2022-03-19 15:08:30 JST", "date": "2022-03-19", "time": "15:08:30", "timezone": "+0900", "user_id": 958615648799662080, "username": "imassc_official", "name": "アイドルマスター シャイニーカラーズ公式", "place": "", "tweet": "「期間限定 聞こえていますか？ 冬優子・真乃スタンプガシャPlus」のアップデート情報を動画でご紹介いたしますね～  #シャニマス #idolmaster  https://t.co/VvQuag1edY", "language": "ja", "mentions": [], "urls": [], "photos": [], "replies_count": 0, "retweets_count": 2280, "likes_count": 3776, "hashtags": ["シャニマス", "idolmaster"], "cashtags": [], "link": "https://twitter.com/imassc_official/status/1505063643624665093", "retweet": false, "quote_url": "https://twitter.com/imassc_official/status/1505063015292366848", "video": 1, "thumbnail": "https://pbs.twimg.com/ext_tw_video_thumb/1505063202748780546/pu/img/Z7_2g_k9kkNiMvKj.jpg", "near": "", "geo": "", "source": "", "user_rt_id": "", "user_rt": "", "retweet_id": "", "reply_to": [], "retweet_date": "", "translate": "", "trans_src": "", "trans_dest": ""},
  {"id": 1501801978669993986, "conversation_id": "1501801978669993986", "created_at": "2022-03-10 15:07:48 JST", "date": "2022-03-10", "time": "15:07:48", "timezone": "+0900", "user_id": 958615648799662080, "username": "imassc_official", "name": "アイドルマスター シャイニーカラーズ公式", "place": "", "tweet": "「SHEER 円香・愛依スタンプガシャ」のアップデート情報を動画でご紹介いたしますね～  #シャニマス #idolmaster  https://t.co/jS913EfKD2", "language": "ja", "mentions": [], "urls": [], "photos": [], "replies_count": 1, "retweets_count": 1951, "likes_count": 3322, "hashtags": ["シャニマス", "idolmaster"], "cashtags": [], "link": "https://twitter.com/imassc_official/status/1501801978669993986", "retweet": false, "quote_url": "https://twitter.com/imassc_official/status/1501801021454102530", "video": 1, "thumbnail": "https://pbs.twimg.com/ext_tw_video_thumb/1501801566273437698/pu/img/DoqJ66rj1-kgJLyA.jpg", "near": "", "geo": "", "source": "", "user_rt_id": "", "user_rt": "", "retweet_id": "", "reply_to": [], "retweet_date": "", "translate": "", "trans_src": "", "trans_dest": ""}
  // ...
]
```

## 2. 動画を保存する

先ほどの JSON ファイルからツイートの ID を読み込み、Twitter API の [statuses/lookup](https://developer.twitter.com/en/docs/twitter-api/v1/tweets/post-and-engage/api-reference/get-statuses-lookup) を叩いて動画のURLを取得。URLにアクセスして動画を保存していきます。

今回使用したコードは以下のリポジトリに置いています。

Deno、便利 🦕

https://github.com/arrow2nd/dl-twitter-img

これで `out/sc_event` 以下に動画が保存されます。

```sh
deno run -A main.js sc_event.json --video
```

### 引っかかったとこ: Twitter API から動画の URL を取得する

現在提供されている Twitter API v2 では [動画のURLが取得できない](https://developer.twitter.com/en/docs/twitter-api/data-dictionary/object-model/media) ので、従来の Standard v1.1 を利用しました。

また、`tweet_mode=extended` を付加しないと `extended_entities` がレスポンスに含まれないのでお忘れなく...。

```js:twitter.js
/**
 * 動画のURLを取得
 * @param {string[]} ids ツイートIDの配列
 * @returns 動画URLの配列
 */
export async function fetchVideoUrl(ids) {
  const limit = 100;

  /** @type {string[]} */
  let results = [];

  for (let i = 0; i < Math.ceil(ids.length % limit); i += limit) {
    // NOTE: API v2では動画のURLが取得できないのでv1.1のエンドポイントを使用
    const endpointUrl = new URL(
      "https://api.twitter.com/1.1/statuses/lookup.json"
    );

    endpointUrl.searchParams.append("tweet_mode", "extended");
    endpointUrl.searchParams.append("id", ids.slice(i, i + limit).join(","));

    const res = await fetch(endpointUrl.toString(), {
      headers: {
        Authorization: `Bearer ${Deno.env.get("BEARER_TOKEN")}`,
      },
    });

    /** @type {TweetResponse[]} */
    const json = await res.json();

    const urls = json.map((e) => {
      // bitrateがないものを除外
      const variants = e.extended_entities.media[0].video_info.variants.filter(
        (e) => typeof e.bitrate !== "undefined"
      );

      // ビットレートで降順ソート
      variants.sort((a, b) => b.bitrate - a.bitrate);

      return variants[0].url;
    });

    results = results.concat(urls);
  }

  return results;
}
```

## 3. 動画からロゴ部分のフレーム画像を抜き出す

[ffmpeg](https://ffmpeg.org) を利用します。

### ガシャの動画の場合

動画の冒頭1秒から2秒間程度ロゴが表示されるので、そこを画像としてエクスポートします。

> 参考: [期間限定 聞こえていますか？ 冬優子・真乃スタンプガシャPlus](https://twitter.com/imassc_official/status/1505063643624665093?s=20&t=U-gWeu_P5BN6BOClFyxFvw)

```zsh
for file in *.mp4; do
  ffmpeg -ss 1 -i "$file" -t 2 -r 1 "${file%.*}_%d.png"
done
```

### シナリオイベントの場合

[「Star n dew by me」](https://twitter.com/imassc_official/status/1188742448941535232?s=20&t=U-gWeu_P5BN6BOClFyxFvw)のように最初にロゴが表示されるパターンと、[「天塵」](https://mobile.twitter.com/imassc_official/status/1276757401715314688)のように最後にロゴが表示されるパターンがあります。

この2つに対応するため、以下のようにしました。

```zsh
for file in *.mp4; do
  # 冒頭2秒からの2秒間を切り出す
  ffmpeg -ss 2 -i "$file" -t 2 -r 1 "${file%.*}_0_%d.png"
  # 末尾4秒前からの2秒間を切り出す
  ffmpeg -sseof -4 -i "$file" -t 2 -r 1 "${file%.*}_1_%d.png"
done
```

## 4. 選別する

以上までの工程を踏むと png 画像が何枚か出力されるので、あとは人力で選別していきます...。

# あとがき

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286055/0c7f2533-1504-d6b1-c715-6aed609ab7af.png)

ガシャについては動画があるもの・ないものがあるらしく（？）全ては収集できませんでしたが、シナリオイベントについてはほとんど網羅できました！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286055/d67d64f3-48ca-4865-5a55-c526b68d3daa.png)

ここまでやってから、最近実装された「Pデスク」でシナリオイベントのロゴが見れることに気づいたのは内緒です。