0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GitHubプロフィールに「動き」を。GIFで動く惑星を置く方法 ― GitHub Planet

0
Posted at

GitHubのプロフィールREADME、せっかくならもう少し動かしたくないですか?

自分は、GitHubでの活動を3D惑星として可視化する「GitHub Planet」というWebサービスを作っています。

そして今、自分のGitHubプロフィールにはこんなものを置いています。

GitHub Planet

自分のGitHub活動から生成された惑星が、そのままGitHubプロフィール上で動いています。

惑星が回る。星が動く。使っている言語によって惑星の見た目も変わる。

今回は、Three.jsで動いているこの惑星をどうやってGIFにして、GitHubプロフィールへ置いたのかを書きます。

そして後半では、

あなた自身のGitHubプロフィールにも、自分の惑星を置く方法

まで紹介します。🪐


GitHub Planetとは

GitHub Planetは、GitHubでの活動を取得して、その人だけの3D惑星を生成するWebサービスです。

GitHubでログインすると、使用言語やContributionなどから自分だけの惑星が生成されます。

単純に言語によって色を変えているだけではなく、言語ごとに惑星で起きる現象も変えています。

例えば、

  • CSSなら色が惑星表面を流れる
  • C++ならプラズマや放電
  • Goなら風
  • TypeScriptなら型安全をイメージした外殻
  • Rustなら砂や塵
  • Kotlinなら紫色の電気

といった感じです。

ログインせずに惑星だけ見たい場合は、Showcaseもあります。


この惑星をGitHubプロフィールにも置きたかった

GitHub Planetには、惑星を横長のカードとして表示するページがあります。

例えば自分の場合は、

https://githubplanet.dev/card.html?username=nitr0yukkuri

です。

ブラウザ上なので、ここではThree.jsが普通に動きます。

惑星が回って、星が動いて、Shaderによる表現も動いています。

これを見ていて思いました。

これ、そのままGitHubプロフィールに置けたらよくない?

GitHub Planetを見るためにサイトへ来てもらうだけではなく、自分のGitHubプロフィールを開いた瞬間に自分の惑星が動いている。

GitHub上の活動から惑星を作るサービスなので、GitHubプロフィールとの相性もかなり良さそうでした。


でもGitHub READMEではThree.jsをそのまま動かせない

ここで問題があります。

GitHubのプロフィールREADMEに、

<script>
  // Three.jsを実行
</script>

のようなJavaScriptを置いて、自由に動かすことはできません。

GitHub Planetのカードページをiframeでそのまま埋め込む、という方法も使えません。

つまり、

ブラウザでは動いているThree.jsのカードを、そのままGitHub READMEへ持っていくことはできない。

じゃあ画像にすればいい。

最初はそう考えました。


PNGにすると、一番見せたいものが消える

カードのスクリーンショットをPNGにしてREADMEへ貼るだけなら簡単です。

でもGitHub Planetの場合、静止画にするとかなり大きなものが失われます。

  • 惑星が回らない
  • 星が動かない
  • Shaderの変化が止まる
  • 言語ごとの現象が伝わりにくい

例えば、最初に見せたプロフィールカード。

GitHub Planet

これを一枚だけ切り取っても、惑星の形自体は伝わります。

でも、

GitHub Planetの「生きている感じ」がかなり減る。

それなら、

GitHub README上でThree.jsを動かすのではなく、
動いている状態そのものを画像にすればいいのでは?

と考えました。

そこで使ったのが、

GIFです。


Three.jsをGIF用に作り直すのはやめた

次に考えたのが、

「どうやってThree.jsの惑星をGIFにするか」

でした。

GIF用に別の惑星レンダラーを作ることもできます。

でもGitHub Planetには、すでに完成しているカードページがあります。

card.html

には、

  • Three.js
  • WebGL
  • Shader
  • 惑星
  • GitHubのデータ
  • 言語ごとのアニメーション

が全部入っています。

だったら、同じものをGIF用にもう一度実装する必要はありません。

完成しているWebページそのものを録画すればいい。

という方針にしました。


Playwrightで本物のカードを録画する

録画にはPlaywrightを使っています。

仕組みはかなり単純です。

GitHub Planetのカードを開く
        ↓
PlaywrightでChromiumを起動
        ↓
Three.jsで惑星を描画
        ↓
数秒間ブラウザを録画
        ↓
WebM
        ↓
FFmpeg
        ↓
GIF

つまり、

Three.js → GIF

と直接変換しているわけではありません。

実際にChromium上でGitHub Planetを動かして、そのブラウザを撮っています。


Playwrightで録画する

録画処理は、ざっくりこんな形です。

import { chromium } from 'playwright';

const browser = await chromium.launch();

const context = await browser.newContext({
  viewport: {
    width: 1200,
    height: 400
  },
  recordVideo: {
    dir: 'artifacts/video',
    size: {
      width: 1200,
      height: 400
    }
  }
});

const page = await context.newPage();

await page.goto(
  'https://githubplanet.dev/card.html?username=nitr0yukkuri&fix=true'
);

Playwrightにはブラウザを動画として録画する機能があるので、それを使っています。

ここで生成されるのは、まずWebMです。


「5秒待つ」ではなく、惑星が完成するまで待つ

ただし、ページを開いた瞬間に録画を終わらせるわけにはいきません。

GitHub Planetでは、

ページを開く
↓
GitHubのデータを取得
↓
Three.jsを初期化
↓
惑星を作る
↓
言語ごとの表現を適用

という処理があります。

なので単純に、

await page.goto(url);
await page.waitForTimeout(5000);

とするだけだと、通信が遅かった場合に、

まだLOADINGの状態を録画してしまう

可能性があります。

そこで実際には、

await page.waitForFunction(() => {
  const canvas =
    document.querySelector('#planet-canvas canvas');

  const username =
    document.querySelector('#username-display')
      ?.textContent
      ?.trim();

  const language =
    document.querySelector('#main-lang-stat')
      ?.textContent
      ?.trim();

  const planetName =
    document.querySelector('#planet-name-sub')
      ?.textContent
      ?.trim();

  return canvas instanceof HTMLCanvasElement
    && canvas.width > 0
    && canvas.height > 0
    && username
    && username !== 'USERNAME'
    && language
    && language !== 'LOADING'
    && language !== 'UNKNOWN'
    && planetName
    && planetName !== 'UNKNOWN PLANET'
    && planetName !== 'ERROR PLANET';
});

のように、

本当に惑星が表示されたか

を確認してから録画しています。

時間ではなく、状態を待つようにしています。


FFmpegでWebMをGIFにする

Playwrightで録画したら、次はFFmpegです。

WebMをGIFへ変換します。

ざっくり書くと、

ffmpeg \
  -i profile-card.webm \
  -vf "fps=15,scale=800:266:flags=lanczos" \
  -loop 0 \
  profile_card.gif

のような処理です。

GitHub Planetでは、GIFの色が崩れにくいようにpalette生成も使っています。

最終的には、

800 × 266
15fps
無限ループ

のGIFとして出力しています。

これで、

GitHub READMEでも惑星が動くようになりました。


READMEでは普通の画像として貼れる

一度GIFになってしまえば、README側では普通の画像と同じです。

例えば、

![GitHub Planet](https://example.com/profile_card.gif)

で表示できます。

クリックしたらGitHub Planetへ飛ばしたいなら、

[![GitHub Planet](https://example.com/profile_card.gif)](https://githubplanet.dev)

です。

自分のプロフィールでは、

[![GitHub Planet](https://raw.githubusercontent.com/nitr0yukkuri/githubplanet/card-assets/profile_card.gif)](https://githubplanet.dev)

という形で使っています。

README上ではThree.jsは一切動いていません。

表示しているのは普通のGIFです。

でも見た目としては、

GitHubプロフィールの中で惑星が動いています。


一回GIFを作っただけでは終わらない

ここでGitHub Planetならではの問題があります。

この惑星は、

GitHub上での活動から作られています。

つまり、自分が開発するとデータも変化します。

コミット数が増える。

Contributionが増える。

使用言語の割合が変わる。

GitHub Planet側の表現自体がアップデートされることもあります。

それなのにプロフィールのGIFだけ昔の状態で止まっていたら、少しもったいない。

そこで、

GIF自体も自動更新する

ことにしました。


GitHub Actionsで自動更新する

GIF生成はGitHub Actionsから実行しています。

流れはこんな感じです。

GitHub Actions
      ↓
Node.jsを準備
      ↓
Playwrightを準備
      ↓
Chromiumをインストール
      ↓
FFmpegをインストール
      ↓
最新のGitHub Planetカードを開く
      ↓
録画
      ↓
GIFへ変換
      ↓
GitHub上へ保存

これで、自分で毎回スクリーンショットを撮ったりGIFを作ったりする必要はありません。

GitHubで活動する。

GitHub Planetの惑星が変わる。

Actionsが最新の惑星をGIFにする。

GitHubプロフィールに反映される。

という流れになります。


じゃあGitHub Planet側で全員分のGIFを作ればいいのでは?

ここまで作ると、

「GitHub Planetを使っている全員にGIFを配ればいいのでは?」

となります。

技術的にはできます。

例えば、

/card.html?username=userA
/card.html?username=userB
/card.html?username=userC

を順番に開いて、全部GIFにすればいい。

ただし、このGIFは普通の画像生成とは少し事情が違います。

一人分作るだけでも、

Chromiumを起動
↓
GitHub Planetを開く
↓
GitHubデータを取得
↓
Three.js / WebGLを描画
↓
数秒間録画
↓
WebMを生成
↓
FFmpegでGIFへ変換
↓
GIFを保存

という処理が必要です。

ユーザーが100人なら100回。

1000人なら1000回。

1万人なら1万回。

しかもGitHubの活動は変化するので、一度作って終わりではありません。

毎日最新にしたければ、

毎日全ユーザー分をもう一度レンダリングする

必要があります。


「複数人に対応できない」わけではない

ここは少し大事です。

GitHub Planetが、

複数ユーザーのGIFを技術的に生成できない

わけではありません。

できます。

でも、

GitHub Planet
↓
全ユーザー分Chromium起動
↓
全ユーザー分Three.js描画
↓
全ユーザー分録画
↓
全ユーザー分FFmpeg
↓
全ユーザー分保存

を中央で抱える必要があるのか。

そこには少し疑問がありました。

普通のカードを見るだけならWebページを一度描画すればいいのに、GIFを配るためだけに動画生成処理を全ユーザー分持つことになります。

でも、ここで考え方を変えます。


でも実は、あなたの惑星もGIFにできます

GitHub Planet側で、

全員分を作る必要はありません。

あなたの惑星なら、

あなた自身のGitHub Actionsで、あなた一人分だけ作ればいい。

これなら話が変わります。

GitHub Planetは、

あなたの惑星をWebページとして描画する

ところまで担当する。

あなたのGitHub Actionsは、

そのページを録画
↓
GIFにする
↓
GitHubプロフィールへ表示

を担当する。

つまり、

GitHub Planetが1000人分作る

のではなく、

1000人がそれぞれ自分の1枚だけ作る

という形です。


あなたのGitHubプロフィールにも惑星を置く

ここから実際にやってみます。

まずGitHub Planetを開きます。

GitHubでログインして、自分の惑星が表示されることを確認してください。

ログインする前にどんな惑星があるのか見たい場合はこちら。

GitHub IDが、

octocat

なら、カードURLは、

https://githubplanet.dev/card.html?username=octocat

です。

まずこれを開いて、

自分の惑星カードが表示されること

を確認します。


1. GitHubプロフィール用Repositoryを用意する

GitHubプロフィールREADMEを表示するには、自分のGitHub IDと同じ名前のRepositoryを作ります。

例えばGitHub IDが、

octocat

なら、

octocat/octocat

というRepositoryです。

すでにプロフィールREADMEを使っている人は、そのRepositoryをそのまま使えます。


2. GitHub Actionsを作る

プロフィールRepositoryに、

.github/workflows/update-planet-card.yml

を作ります。

以下は、GitHub Planet本体で使っているGIF生成の仕組みを、プロフィールRepository向けにした例です。

name: Update GitHub Planet Card

on:
  schedule:
    - cron: "0 0 * * *"

  workflow_dispatch:

permissions:
  contents: write

concurrency:
  group: update-github-planet-card
  cancel-in-progress: true

jobs:
  generate:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout profile repository
        uses: actions/checkout@v4
        with:
          path: profile

      - name: Checkout GitHub Planet
        uses: actions/checkout@v4
        with:
          repository: nitr0yukkuri/githubplanet
          path: githubplanet

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: npm
          cache-dependency-path: githubplanet/package-lock.json

      - name: Install dependencies
        working-directory: githubplanet
        run: |
          npm ci
          npm install --no-save --package-lock=false playwright@1.58.2
          npx playwright install chromium --with-deps

      - name: Install FFmpeg
        run: |
          sudo apt-get update
          sudo apt-get install --yes ffmpeg

      - name: Record my planet
        working-directory: githubplanet
        env:
          CARD_RECORD_URL: https://githubplanet.dev/card.html?username=${{ github.repository_owner }}&fix=true
          CARD_RECORD_WIDTH: "1200"
          CARD_RECORD_HEIGHT: "400"
          CARD_RECORD_WARMUP_MS: "2000"
          CARD_RECORD_MS: "8000"
        run: |
          node scripts/record-profile-card.mjs

      - name: Convert to GIF
        run: |
          ffmpeg -y \
            -sseof -8 \
            -i githubplanet/artifacts/profile-card.webm \
            -vf "fps=15,scale=800:266:flags=lanczos,split[frames][palette_source];[palette_source]palettegen=stats_mode=diff[palette];[frames][palette]paletteuse=dither=sierra2_4a:diff_mode=rectangle" \
            -loop 0 \
            profile_card.gif

      - name: Publish GIF
        working-directory: profile
        run: |
          set -euo pipefail

          asset_dir="$(mktemp -d)"
          cp ../profile_card.gif "$asset_dir/profile_card.gif"

          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

          git switch --orphan generated-planet-card
          git rm --recursive --force . || true

          cp "$asset_dir/profile_card.gif" .

          git add profile_card.gif
          git commit -m "Update GitHub Planet card [skip ci]"

          git push --force origin HEAD:planet-card-assets

少し長く見えますが、GitHub IDをWorkflowへ直接書く必要はありません。

ここです。

${{ github.repository_owner }}

Repositoryの所有者名をGitHub Actionsから取得しています。

例えば、

octocat/octocat

で実行すれば、

username=octocat

としてGitHub Planetを開きます。


3. 一度Actionsを実行する

GitHubのプロフィールRepositoryから、

Actions
↓
Update GitHub Planet Card
↓
Run workflow

を実行します。

成功すると、

planet-card-assets

というブランチが作られます。

その中に、

profile_card.gif

が生成されます。


4. READMEに惑星を貼る

あとはプロフィールREADMEへGIFを貼るだけです。

[![GitHub Planet](https://raw.githubusercontent.com/YOUR_USERNAME/YOUR_USERNAME/planet-card-assets/profile_card.gif)](https://githubplanet.dev)

YOUR_USERNAMEを自分のGitHub IDへ変更します。

例えば、

octocat

なら、

[![GitHub Planet](https://raw.githubusercontent.com/octocat/octocat/planet-card-assets/profile_card.gif)](https://githubplanet.dev)

です。

これで完成です。


GitHubで活動すると、プロフィールの惑星も変わる

これでGitHubプロフィールを開くと、

自分のGitHub活動から生成された惑星が動いています。

そしてGitHub Actionsを定期実行しているので、惑星のGIFも更新されます。

つまり、

GitHubで開発する
↓
GitHub上の活動が変わる
↓
GitHub Planetの惑星が変わる
↓
GitHub Actionsが新しい惑星を録画する
↓
GitHubプロフィールにも反映される

という流れになります。

普通のプロフィールREADMEは、自分で編集しない限り基本的には同じ見た目です。

でもこのカードなら、

開発を続けることで、プロフィール自体も少しずつ変化していきます。


READMEではThree.jsは動いていない。でも惑星は動いている

今回、最終的にREADMEが表示しているものは普通のGIFです。

でもそのGIFを作る裏側では、

  • Three.js
  • WebGL
  • GLSL
  • Playwright
  • Chromium
  • FFmpeg
  • GitHub Actions

を使っています。

GitHub READMEではJavaScriptを自由に動かせない。

だから諦めるのではなく、

動いているWebページそのものを録画して、READMEが扱える形式まで落とす。

という方法を取りました。

READMEではThree.jsは一切動いていません。

それでも見た目としては、

GitHubプロフィールの中で惑星がずっと動いています。


あなたもGitHubに惑星を置いてみませんか

最初は、自分のGitHubプロフィールにGitHub Planetを置きたくて作った仕組みでした。

でも、

GitHub Planet側で全員分のGIFを生成しなくても、

それぞれの人が、自分のGitHub Actionsで自分の惑星だけ作ればいい。

そう考えると、自分だけの機能にする必要はありません。

GitHub Planetは、

あなたのGitHub活動から惑星を作る。

GitHub Actionsは、

その惑星をGIFにする。

GitHub READMEは、

その惑星をプロフィールに表示する。

まずは、自分の惑星を見てみてください。

ログインせずに惑星を見たい場合はこちら。

GitHub Planetのソースコードも公開しています。

もし実際にプロフィールへ惑星を置いてくれた人がいたら、ぜひ見てみたいです。

GitHubプロフィールに、もう少し「動き」を。

あなたのGitHubにも、一つ惑星を置いてみてください。🪐

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?