⚠️ : 加筆修正した記事を Zenn に投稿しています。
まえがき
アイドルマスターシャイニーカラーズ は バンダイナムコエンターテインメントが運営する、「THE IDOLM@STER シリーズ」のブラウザゲームです。
このシャニマス、とにかく「イラストが凄く綺麗」なのですが、ロゴも最高に綺麗 なんですよね...。
そんなロゴたちを集めていつでも見られるようにしたい!
方法
ガシャ・イベント共に、開催前にツイッターに以下のような動画が投稿される場合が多いので、今回はこの動画を保存して、ロゴ部分のフレームを画像としてエクスポートしようと思います。
環境
- macOS Monterey 12.3 (Intel)
- zsh
- Python / Deno の環境がある
- Twitter の開発者アカウントがある
手順
1. ツイートを集める
Twitter の Recent search API では 直近7日間のツイートしか検索できないので、Python製スクレイピングツールの Twint を利用しました。
pip3 install twint
Basic-usage を参考にしつつ検索していきます。
# 「復刻を除くシナリオイベントかつ、動画があるツイート」を検索してJSONで出力
twint -u imassc_official -s "シナリオイベント -復刻" --videos -o sc_event.json --json --limit 1000
これを実行すると検索結果を含んだ JSON ファイルが出力されます。
が、なぜか配列になっていないので手直しする必要があります。
私の場合は VSCode 上で、行頭・行末に角括弧を追加、正規表現検索で }\n$
を },\n
に置換して対応しました。
[
{"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 を叩いて動画のURLを取得。URLにアクセスして動画を保存していきます。
今回使用したコードは以下のリポジトリに置いています。
Deno、便利 🦕
これで out/sc_event
以下に動画が保存されます。
deno run -A main.js sc_event.json --video
引っかかったとこ: Twitter API から動画の URL を取得する
現在提供されている Twitter API v2 では 動画のURLが取得できない ので、従来の Standard v1.1 を利用しました。
また、tweet_mode=extended
を付加しないと extended_entities
がレスポンスに含まれないのでお忘れなく...。
/**
* 動画の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 を利用します。
ガシャの動画の場合
動画の冒頭1秒から2秒間程度ロゴが表示されるので、そこを画像としてエクスポートします。
for file in *.mp4; do
ffmpeg -ss 1 -i "$file" -t 2 -r 1 "${file%.*}_%d.png"
done
シナリオイベントの場合
「Star n dew by me」のように最初にロゴが表示されるパターンと、「天塵」のように最後にロゴが表示されるパターンがあります。
この2つに対応するため、以下のようにしました。
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 画像が何枚か出力されるので、あとは人力で選別していきます...。
あとがき
ガシャについては動画があるもの・ないものがあるらしく(?)全ては収集できませんでしたが、シナリオイベントについてはほとんど網羅できました!
ここまでやってから、最近実装された「Pデスク」でシナリオイベントのロゴが見れることに気づいたのは内緒です。