😽

[2023年6月版]Astro.js 小ネタ集 その11 OGP画像にQRコード追加

2023/06/30に公開

前回に引き続きAstro.jsでの小ネタを紹介していきます。

ここでは前回生成したOGP画像にQRコードなどを追加してみたいと思います。

QRコードも追加したい

OG画像、このままでもよいのですが、せっかくなのでサイトのリンクが含まれたQRコード画像を追加してみたいと思います。
使用するパッケージは以下です。

インストール

パッケージのインストールは以下です。

$ npm i qrcode

画像エンドポイントに処理を追加

処理を記述するのは前回の記事で作成した画像生成エンドポイント src/pages/images/[slug].png.tsで、QRコード画像の生成の処理を追加します。

処理は非常にシンプルです。

src/pages/images/[slug].png.ts
import * as QRCode from 'qrcode';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
...
export async function get({ url, params, props }: APIContext) {
...
  const appPrefix = 'my-blog';
  const linkUrl = `${SITE_URL}/blog/${slug}`;
  let tmpDir;
  try {
    tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), appPrefix));

    const file_path = tmpDir + Date.now() +".png";

    await QRCode.toFile(file_path, linkUrl, { width , margin });

    qrCode = fs.readFileSync(file_path);
  }
  finally {
    try {
      if (tmpDir) {
        fs.rmSync(tmpDir, { recursive: true });
      }
    }
    catch (e) {
      console.error(`An error has occurred while removing the temp folder at ${tmpDir}. Please remove it manually. Error: ${e}`);
    }
  }
...

ここの処理で最も気を付ける必要があるのは一時ファイルの後始末です。

fs.mkdtempSync(path.join(os.tmpdir(), appPrefix))で適当なプレフィックスを持つ一時フォルダを作成します。

続いて await QRCode.toFile(...) にて今作った一時フォルダに、リンクURLをQRコード画像に変換して保存します。

書き込み終わったら fs.readFileSync(file_path); でファイルを読み込みます。

最後に finally ブロックで fs.rmSync(tmpDir, { recursive: true }) を行い、一時フォルダをリカーシブに削除いたします。

あとは変数に持っている qrCode のバッファをOG画像のSVGかPNGに追加すればQRコード付きOG画像の出来上がりです。

これは直接、メモリに書けないのかな・・・?

Satori で画像埋め込み

さて、QRコード画像のデータが上記の処理で取得できたので satori に混ぜてみましょう。

ところがちょっと問題がありまして、satori-html内のimgタグのsrcではローカルのファイルパスを指定するとうまく読み込めませんでした。
生成している最中のdistなどの中身を指定する方法がよくわからないです。

なのでデータURLの形式で直接、埋め込んでしまうのが手っ取り早いかと思います。ついでに favicon.svg もOG画像に含めてしまいたいと思います。

src/pages/images/[slug].png.ts
const favicon = import.meta.glob('../../../public/favicon.svg', {eager:true, as: 'raw'})["../../../public/favicon.svg"];

...

  const out = html`<div tw="flex w-full flex-col bg-red-400">
    <div tw="h-93 mx-6 px-4 flex flex-col bg-white">
      <h1 tw="text-4xl">${post?.data.title}</h1>
      <p tw="text-[1.8rem] w-[56rem] bottom-0">${(post?.data as any).description ?? ''}</p>
    </div>
    <div tw="flex flex-row mx-6 bg-white rounded-b-2xl mb-8">
    <img tw="w-30 h-30 ml-10 mb-2 inline-block grow-0" src="data:image/png;base64,${qrCode ? qrCode.toString('base64'):''}" />
    <div tw="flex flex-row-reverse w-230 mt-15">
        <p tw="text-[1.5rem] text-zinc-600">あそぴテックのごった煮ブログ</p>
        <img tw="w-12 h-12 rounded-full inline-block" src="data:image/svg+xml;base64,${btoa(favicon)}" />
      </div>
    </div>
  </div>`

publicにあるfavicon.svgをそのまま生データとして読み込むために、import.meta.glob(... {as: raw})[ファイル名] を使っています。

imgタグのsrc属性は data:image/png;base64,data:image/svg+xml;base64,のような"dataプロトコル:ファイルタイプ"の形式ではじめます。

で実際に埋め込むデータですが、qrCode のバッファは toString('base64') として埋め込みます。

SVGの生データとして読み込んだ faviconbtoa メソッドで埋め込みます。

これでどちらのパターンも無事に satori のSVG経由でPNG画像にすることができました🙌

今回はここまでといたします。

Discussion