Open4

スレッド形式で日記を書いたら画像を生成するやつの実装を再考したい

naarynaary

最大140文字まで書けるtextareaを最大9つまで増やし、一つ当たり1ページとして、トップページを含めて最大10枚の画像を生成するWebアプリをかつて作った(何を言ってるんだ)。

/pagesで書いたコンポーネントをPlaywrightでレンダリングしてスクショを撮影するという実装だったが、課題がある。

  • 実行時間が長く、Vercelのサーバーレス関数で実行したときに実行時間制限(10秒)を超えてしまうことがある
  • 自分だけで使うために Basic認証をかけているが、これが影響しているのか、最近はもはや画像が一枚も生成されなくなった

これか…「何もしてないのに壊れた」というのは
むしろ「何もしてないから」なんだろうけど。

naarynaary

Next.jsで作り続けるのが嫌だというのもあるけど、とりあえずPlaywrightを使い続けるかどうかも含めてスクショ撮影の実装を再検討したい。

naarynaary

とりあえずBingチャットに投げてみる。純粋にヘッドレスChromeを使用する実装例

import { NextApiRequest, NextApiResponse } from 'next';
import chrome from 'chrome-aws-lambda';
import { execFile } from 'child_process';
import { promisify } from 'util';
import { writeFileSync, unlinkSync } from 'fs';
import { join } from 'path';

const execFileAsync = promisify(execFile);

export default async (req: NextApiRequest, res: NextApiResponse) => {
  const url = req.query.url as string;

  if (!url) {
    res.status(400).json({ error: 'URL is required' });
    return;
  }

  const screenshotPath = join('/tmp', 'screenshot.png');

  try {
    const chromePath = await chrome.executablePath;
    const args = [
      '--headless',
      '--disable-gpu',
      '--no-sandbox',
      '--disable-dev-shm-usage',
      `--screenshot=${screenshotPath}`,
      '--window-size=1280x1024',
      url,
    ];

    await execFileAsync(chromePath, args);

    const screenshot = writeFileSync(screenshotPath);
    res.setHeader('Content-Type', 'image/png');
    res.send(screenshot);
  } catch (error) {
    res.status(500).json({ error: 'Failed to capture screenshot' });
  } finally {
    unlinkSync(screenshotPath);
  }
};

naarynaary

ファイルシステムを用いない例(エッジだと使えない気がするので)

import { NextApiRequest, NextApiResponse } from 'next';
import chrome from 'chrome-aws-lambda';
import { execFile } from 'child_process';
import { promisify } from 'util';

const execFileAsync = promisify(execFile);

export default async (req: NextApiRequest, res: NextApiResponse) => {
  const url = req.query.url as string;

  if (!url) {
    res.status(400).json({ error: 'URL is required' });
    return;
  }

  try {
    const chromePath = await chrome.executablePath;
    const args = [
      '--headless',
      '--disable-gpu',
      '--no-sandbox',
      '--disable-dev-shm-usage',
      '--window-size=1280x1024',
      url,
    ];

    const { stdout } = await execFileAsync(chromePath, [...args, '--screenshot', '--screenshot-format=png', '--screenshot-output=stdout']);

    res.setHeader('Content-Type', 'image/png');
    res.send(Buffer.from(stdout, 'binary'));
  } catch (error) {
    res.status(500).json({ error: 'Failed to capture screenshot' });
  }
};