Qiita Careers powered by IndeedPR

求人サイト「Qiita Careers powered by Indeed」では、エンジニアのあなたにマッチした求人が見つかります。

189
134

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

個人開発でClaudeCodeに画面操作付きPR動画を生成させてXに自動投稿してる話

189
Posted at

実際の投稿例

このパイプラインで自動生成・自動投稿されたXの投稿がこちらです。
https://x.com/aiteacher37681/status/2026187321477931407?s=20

Seat Arrangement Guide.gif

生成手順

  1. PlanモードでClaude Codeに「Remotion + Playwrightで〇〇機能のPR動画作成」を依頼
  2. 生成された台本を確認
  3. 問題なければPR動画の生成を依頼
  4. 生成後の品質確認
  5. 気になる点があれば指摘・修正依頼
  6. 問題なければpush → GitHub Actionsが台本の内容を読み取り、PR動画を添付してXに自動投稿

人間がやるのは台本と動画の品質チェックだけで、それ以外はすべて自動化されています。将来的にはクオリティが担保できるようになれば、新機能追加の時点で台本確認すら不要な完全自動化を目指しています。

きっかけ

個人開発で教員向けAI校務支援サービスを作っています。

元々PR動画を自身で作成していましたが、AIでの開発が中心となり新機能の追加が容易になりました。新規機能は作成できますが手動でのPR動画の作成が遅れ、機能としてはあるがユーザーには利用されていない、もしくはターゲット層に認知されていないのが実情でした。

そこで最近取り入れたXへのPR自動投稿のバッチと組み合わせて、PR動画を添付して自動投稿する仕組みを作りました。具体的には Playwright でブラウザ操作を自動録画し、Remotion でブランド統一されたプロモーション動画へ合成、pushしたらGitHub ActionsでXに自動投稿するパイプラインです。

本記事ではそのアーキテクチャと実装を紹介します。

全体アーキテクチャ

┌───────────────────────────────────────────────────────────┐
│  1. Playwright デモテスト                                    │
│     tests/e2e/demo/*.spec.ts                              │
│     ブラウザ操作を自動実行 → .webm動画を録画                     │
└──────────────────────┬────────────────────────────────────┘
                       │ public/remotion/videos/*.webm
                       ▼
┌───────────────────────────────────────────────────────────┐
│  2. Remotion コンポジション                                   │
│     remotion/compositions/*.tsx                            │
│     Hook → Problem → Demo(録画埋め込み) → CTA              │
└──────────────────────┬────────────────────────────────────┘
                       │ npm run remotion:render:all
                       ▼
┌───────────────────────────────────────────────────────────┐
│  3. 出力 MP4                                               │
│     out/*.mp4 (1920x1080, H.264)                          │
│     SNS投稿・LP掲載に使えるプロモ動画                          │
└───────────────────────────────────────────────────────────┘

ポイントは、Playwrightが録画した操作動画を素材として、Remotionがブランディングされたプロモ動画に仕上げるという2段構成です。

Step 1: Playwrightでデモ操作を自動録画

デモ専用のPlaywright設定

通常のE2Eテストとは別に、デモ録画専用の設定ファイルを用意しています。

// playwright.demo.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests/e2e/demo',
  fullyParallel: false,
  retries: 0,
  workers: 1,
  timeout: 120000,
  projects: [
    {
      name: 'demo',
      use: {
        ...devices['Desktop Chrome'],
        headless: true,
        storageState: 'playwright/.clerk/premium-user.json',
        video: {
          mode: 'on',
          size: { width: 1920, height: 1080 },
        },
      },
    },
  ],
});

video.mode: 'on'size: { width: 1920, height: 1080 } がポイントです。テスト実行中のブラウザ画面が自動で1080pのwebm動画として保存されます。

デモテストの書き方

各機能ごとに tests/e2e/demo/ 配下にspecファイルを作成しています。通常のE2Eテストとの違いは、APIをモックして理想的なデータを返すことと、ユーザーに見せたい操作手順を「台本」として記述することです。

// tests/e2e/demo/report-pc.spec.ts(抜粋)
test('AI予測・書き換え・生成を使って所見を作成する', async ({ page }) => {
  // Next.jsのエラーオーバーレイを非表示
  await page.addStyleTag({
    content: `
      [data-nextjs-dialog-overlay],
      [data-nextjs-toast],
      nextjs-portal { display: none !important; }
    `
  });

  // APIモック: 理想的なデータを返す
  await page.route('**/api/users/me', async (route) => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({
        id: 1, email: 'demo@example.com',
        is_premium: true, subscription_status: 'premium'
      })
    });
  });

  // AI予測APIモック: 入力に応じた予測テキストを返す
  await page.route('**/api/ai/report-prediction', async (route) => {
    const postData = route.request().postDataJSON();
    let prediction = '意欲的に取り組んでいます。';
    if (postData.currentText?.includes('国語の音読発表会では、')) {
      prediction = '場面の様子がよく伝わるように、声の大きさやトーンを工夫し...';
    }
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({ prediction })
    });
  });

  // AI書き換えAPIモック(600ms遅延でローディングを見せる)
  await page.route('**/api/ai/text-rewrite', async (route) => {
    await new Promise(r => setTimeout(r, 600));
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({ rewrittenText: '...' })
    });
  });

  // ========== デモ操作シナリオ ==========

  // Step 1: 所見作成画面へ遷移、新規作成
  await page.goto('http://localhost:8080/reports');
  await page.click('button:has-text("所見の新規作成")');
  // ...クラス・学期を選択して作成

  // Step 2: AI予測変換(文字入力 → Tab確定)
  await textarea.pressSequentially('国語の音読発表会では、', { delay: 100 });
  await page.waitForTimeout(2000);
  await page.keyboard.press('Tab'); // AI予測をTabで確定

  // Step 3: AI書き換え → プレビュー → 確定
  await page.locator('button:has-text("AI書き換え")').click();
  await page.waitForTimeout(1500);
  await page.locator('button:has-text("この内容で確定")').first().click();

  // Step 4: 道徳タブに切り替え
  await page.locator('[role="tab"]:has-text("道徳")').click();

  // Step 5: AI生成 → プロンプト入力 → 確定
  await page.locator('button:has-text("AI生成")').click();
  await promptInput.pressSequentially(
    '思いやりがあり、困っている友達を助ける', { delay: 80 }
  );
  await page.keyboard.press('Enter');

  // Step 6: 保存
  await page.locator('button:has-text("所見を更新")').click();

  // 動画を指定パスに保存
  await page.close();
  await page.video()?.saveAs('public/remotion/videos/report-editor.webm');
});

デモテストで工夫しているポイント

工夫 理由
pressSequentially で1文字ずつ入力 タイピングの様子をリアルに見せる
mouse.move で段階的にカーソル移動 マウスの動きをスムーズに見せる
APIモックに意図的な遅延(600-800ms) AI処理のローディング状態を自然に見せる
Next.jsエラーオーバーレイの非表示 開発サーバーのオーバーレイが映り込むのを防止
キャッチオールAPIモック 未定義のAPIリクエストで画面が壊れるのを防止
page.video()?.saveAs() 録画をRemotionの素材パスに直接保存

Step 2: Remotionでプロモーション動画に仕上げる

Playwrightで録画した素のブラウザ操作動画を、Remotionでブランディングされたプロモーション動画に変換します。

動画の構成

各動画は統一された4セクション構成です。

┌─────────────────────────────────────────────────┐
│  Hook (0-2秒)                                     │
│  ロゴ + キャッチコピー(Springアニメーション付き)       │
│  例: 「学校業務を、AIでもっとスマートに」               │
├─────────────────────────────────────────────────┤
│  Problem (2-4秒)                                  │
│  課題提示(フェードアップアニメーション)                 │
│  例: 「事務作業に追われる毎日...」                      │
├─────────────────────────────────────────────────┤
│  Solution Demo (4-12秒)                           │
│  ★ Playwrightで録画した操作動画を再生 ★              │
│  playbackRateで再生速度調整可能                      │
├─────────────────────────────────────────────────┤
│  CTA (12-15秒)                                    │
│  「まずは無料で始めよう」+ ボタン風UI + URL           │
└─────────────────────────────────────────────────┘

コンポジションの実装例

// remotion/compositions/HomeDashboard.tsx
import { AbsoluteFill, staticFile } from 'remotion';
import { HookScene } from '../components/HookScene';
import { CtaScene } from '../components/CtaScene';
import { SceneContainer } from '../components/SceneContainer';
import { AnimatedVideo } from '../components/AnimatedVideo';
import { GradientBackground } from '../components/GradientBackground';
import { theme } from '../styles/theme';

const VIDEO_DASHBOARD = staticFile('remotion/videos/home-dashboard.webm');

export const HomeDashboard: React.FC = () => {
  return (
    <AbsoluteFill>
      {/* Hook: 0-2秒 - ロゴとキャッチコピー */}
      <SceneContainer startFrame={0} endFrame={60}>
        <HookScene
          title="学校業務を、AIでもっとスマートに"
          gradient={theme.gradients.primary}
        />
      </SceneContainer>

      {/* Problem: 2-4秒 - 課題提示 */}
      <SceneContainer startFrame={60} endFrame={120}>
        <GradientBackground colors={['#f9fafb', '#eff6ff']}>
          <TextOverlay
            text="事務作業に追われる毎日..."
            fontSize={56}
            animation="fadeUp"
            startFrame={60}
          />
        </GradientBackground>
      </SceneContainer>

      {/* Solution Demo: 4-12秒 - Playwrightの録画を再生 */}
      <SceneContainer startFrame={120} endFrame={360}>
        <GradientBackground colors={['#ffffff', '#f0f4ff']}>
          <AnimatedVideo
            src={VIDEO_DASHBOARD}
            startFrame={120}
            playbackRate={1.2}  // 少し早送り
          />
        </GradientBackground>
      </SceneContainer>

      {/* CTA: 12-15秒 */}
      <SceneContainer startFrame={360} endFrame={450}>
        <CtaScene
          text="まずは無料で始めよう"
          gradient={theme.gradients.primary}
        />
      </SceneContainer>
    </AbsoluteFill>
  );
};

可変速再生(ConferenceEditorの例)

個人面談のデモ動画では、シーンごとに再生速度を変えて見せたい部分をゆっくり、待ち時間を早送りにしています。

// remotion/compositions/ConferenceEditor.tsx
const VIDEO_SEGMENTS: VideoSegment[] = [
  { duration: 25, speed: 0.8 },   // フォーム表示(ゆっくり)
  { duration: 25, speed: 1.0 },   // ドロップダウン操作
  { duration: 25, speed: 1.2 },   // 生徒ロード
  { duration: 100, speed: 1.0 },  // プロンプト入力
  { duration: 50, speed: 1.5 },   // 生成ボタン
  { duration: 130, speed: 3.0 },  // AI生成処理(早送り)
  { duration: 13, speed: 1.0 },   // 結果表示
  { duration: 25, speed: 2.0 },   // サイドバー操作(高速化)
  { duration: 33, speed: 0.8 },   // textarea入力(ゆっくり)
  { duration: 83, speed: 1.5 },   // 保存+トースト
];

Remotionの SequenceOffthreadVideo を組み合わせて、セグメントごとに異なる playbackRate を適用しています。AI処理中は3倍速で早送りし、ユーザーに見せたい入力シーンは0.8倍速でゆっくり再生、という制御ができます。

再利用可能なコンポーネント群

remotion/components/
├── AnimatedVideo.tsx       # 動画埋め込み(Spring付きフェードイン)
├── HookScene.tsx           # Hook画面(ロゴ+タイトル+パーティクル)
├── CtaScene.tsx            # CTA画面(テキスト+ボタン+URL)
├── SceneContainer.tsx      # シーン区間管理
├── GradientBackground.tsx  # グラデーション背景
├── TextOverlay.tsx         # テキストオーバーレイ
├── TransitionWipe.tsx      # ワイプトランジション
├── SparkleEffect.tsx       # キラキラエフェクト
├── CursorAnimation.tsx     # カーソルアニメーション
├── Logo.tsx                # ロゴ表示
└── AnimatedScreenshot.tsx  # スクリーンショットアニメーション

これらを組み合わせることで、新しい機能のデモ動画を作る際もコンポジションファイルを1つ追加するだけで済みます。

テーマとアニメーション

ブランドカラーとSpringアニメーションの設定は一元管理しています。

// remotion/styles/theme.ts
export const theme = {
  gradients: {
    primary: ['#3b82f6', '#2563eb', '#1d4ed8'],
    hero: ['#eff6ff', '#ffffff', '#ede9fe'],
    dark: ['#1e3a8a', '#1e40af', '#2563eb'],
    ai: ['#8b5cf6', '#3b82f6', '#06b6d4'],
  },
  video: {
    width: 1920,
    height: 1080,
    fps: 30,
    durationInFrames: 450, // 15秒
  },
} as const;

// remotion/styles/animations.ts
export const SPRING_CONFIGS = {
  bouncy:  { damping: 12, stiffness: 200, mass: 0.5 },
  smooth:  { damping: 20, stiffness: 120, mass: 0.8 },
  gentle:  { damping: 30, stiffness: 80, mass: 1 },
  snappy:  { damping: 15, stiffness: 300, mass: 0.3, overshootClamping: true },
};

Step 3: バッチレンダリング

全9コンポジションを一括でMP4にレンダリングするスクリプトがあります。

// remotion/scripts/render-all.ts
import { bundle } from '@remotion/bundler';
import { renderMedia, selectComposition } from '@remotion/renderer';

const COMPOSITIONS = [
  { id: 'HomeDashboard', output: 'home-dashboard.mp4' },
  { id: 'ClassManagement', output: 'class-management.mp4' },
  { id: 'SeatArrangement', output: 'seat-arrangement.mp4' },
  { id: 'ReportEditor', output: 'report-editor.mp4' },
  { id: 'NewsletterEditor', output: 'newsletter-editor.mp4' },
  { id: 'PeGrouping', output: 'pe-grouping.mp4' },
  { id: 'ClassDivision', output: 'class-division.mp4' },
  { id: 'ConferenceEditor', output: 'conference-editor.mp4' },
  { id: 'TextOnlyNewsletter', output: 'text-only-newsletter.mp4' },
];

async function main() {
  // Remotionプロジェクトをバンドル
  const bundled = await bundle({ entryPoint });

  for (const comp of COMPOSITIONS) {
    const composition = await selectComposition({
      serveUrl: bundled, id: comp.id,
    });
    await renderMedia({
      composition, serveUrl: bundled,
      codec: 'h264',
      outputLocation: `out/${comp.output}`,
    });
  }
}

実行は簡単です。

# デモ動画を録画(Playwright)
npm run test:demo

# 録画素材を保存
bash scripts/preserve-demo-videos.sh

# プロモ動画をレンダリング(Remotion)
npm run remotion:render:all

# 個別にレンダリングすることも可能
npm run remotion:render:report
npm run remotion:render:newsletter

X(Twitter)自動投稿との連携

別リポジトリ(auto-postリポジトリ)で構築したXへの自動投稿バッチと連携しています。pushするとGitHub Actionsが台本の内容を読み取り、PR動画を添付してXに自動投稿します。

┌─────────────┐      ┌──────────────┐      ┌──────────────┐      ┌─────────────┐
│  pushする     │ ──→ │ GitHub Actions │ ──→ │ 台本の内容を    │ ──→ │ PR動画付きで   │
│              │      │ がトリガー     │      │ 読み取り       │      │ Xに自動投稿   │
└─────────────┘      └──────────────┘      └──────────────┘      └─────────────┘

人間が品質確認してpushするだけで、投稿文の作成からメディア添付まですべて自動で行われます。

実際の成果

残念ながらXの投稿のview数自体に大きな変化はありませんでした。
しかし、投稿に添付したリンクから直接サイトへ流入する人数は約1.8~2倍に増加しました。
動画付きの投稿はview数には直結しなくても、興味を持った人が実際にサービスを見に来る確率を押し上げる効果がありました。

まとめ

個人開発でデモ動画の作成が面倒だと感じている方は、Playwright + Remotionの組み合わせを試してみてください。一度パイプラインを組めば、AIにPR動画作成依頼するだけでXに自動投稿されるという体験はかなり快適です。
テストを書くついでにデモ素材も手に入る、という一石二鳥の仕組みとして重宝しています。
ぜひお試しあれ✨

PS:
最後まで読んでいただきありがとうございます🙇‍♂️
少しでも「いい記事だな」と思っていただけたら、こちらのサービスサイトもチラッと見ていただけると嬉しいです😆(被リンクがSEOに効くので、励みになります!)

189
134
1

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

Stocked in these public stock lists

akachiryo

@akachiryo(neko neko)

自分のメモ用に投稿していきます。 私の理解が及ばず、間違った情報を載せている場合があります。 お気づきの点がございましたら、コメントまでよろしくお願いします。
prum
技術力のみならず、ビジネス力・マインドをも総合的に鍛えられる育成制度「GYM」が自慢の当社。 AIには奪えない“次世代のキャリア力”を身につけ、時代が変わっても求められ続けるエンジニアを目指しませんか? エンジニア未経験の方も大歓迎です!!

Linked from these articles

Comments

@mk18

真似して動画が保存できました、ありがとうございます

1

Let's comment your feelings that are more than good

189
134

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Login to continue?

Login or Sign up with social account

Login or Sign up with your email address