CONTENT
ここから
つかみ・紹介・魅力・案内の4分割で動画構成を作る
この教材では、Google Apps ScriptのWebアプリで、15秒動画台本メーカーを作ります。
前回は、画像生成AIに入れるための画像プロンプトを作りました。
今回は、企画を短い動画にするための動画台本を自動で作ります。
動画と聞くと難しく感じるかもしれません。
でも、15秒動画は次の4つに分けると、とても作りやすくなります。
つかみ
↓
紹介
↓
魅力
↓
案内
今日作るアプリは、テーマを入力すると、AIがこの4分割で動画の流れを考えてくれるものです。
このページで完成するもの
今回作るのは、次のようなアプリです。
| 項目 | 内容 |
|---|---|
| 入力 | 企画タイトル、ターゲット、伝えたいこと、雰囲気 |
| AI生成 | 15秒動画の構成、テロップ、ナレーション |
| 表示 | Web画面に表示 |
| 保存 | スプレッドシートに履歴保存 |
今回のゴールはこれです。
15秒で何を見せるかを、
AIと一緒に決められるようにする
動画そのものが完成しなくても大丈夫です。
まずは、動画の設計図を作ります。
1. 15秒動画の基本
15秒動画は、短いからこそ流れが大切です。
おすすめは、この4分割です。
| 時間 | 役割 | 内容 |
|---|---|---|
| 0〜3秒 | つかみ | 見る人の興味を引く |
| 3〜7秒 | 紹介 | 何の企画か伝える |
| 7〜12秒 | 魅力 | 良さを見せる |
| 12〜15秒 | 案内 | 次の行動を伝える |
たとえば、カフェ紹介ならこうです。
0〜3秒:
放課後、どこ寄る?
3〜7秒:
学校帰りに行けるカフェを紹介
7〜12秒:
勉強も休憩もできる場所が見つかる
12〜15秒:
気になるカフェを見てみよう
これだけで、動画の形になります。
2. 今回作るファイル
今回も、使うファイルは2つだけです。
Code.gs
index.html
| ファイル | 役割 |
|---|---|
| Code.gs | Gemini APIを呼び出し、動画台本を作る |
| index.html | 入力画面と結果表示を作る |
3. 事前準備
Googleスプレッドシートを作ります。
https://sheets.google.com/
ファイル名は次にします。
AI動画台本メーカー
スプレッドシートからApps Scriptを開きます。
拡張機能
↓
Apps Script
プロジェクト名も、次にします。
AI動画台本メーカー
4. APIキーを確認する
前回と同じ GEMINI_API_KEY を使います。
Apps Scriptの左側にある歯車アイコンを開きます。
プロジェクトの設定
↓
スクリプト プロパティ
次の名前で保存されていればOKです。
| プロパティ | 値 |
|---|---|
| GEMINI_API_KEY | 自分のGemini APIキー |
APIキーは人に見せないでください。
コードに直接書かず、スクリプトプロパティに保存します。
5. Code.gsを貼り付ける
Code.gs の中身をすべて消して、次のコードを貼り付けます。
const SHEET_NAME = '動画台本履歴';
const MODEL_NAME = 'gemini-2.5-flash';
function doGet() {
setupSheet_();
return HtmlService
.createHtmlOutputFromFile('index')
.setTitle('AI動画台本メーカー')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
function generateVideoScript(formData) {
validateFormData_(formData);
setupSheet_();
const prompt = buildVideoScriptPrompt_(formData);
const result = callGemini_(prompt);
saveHistory_(formData, result);
return {
ok: true,
text: result,
message: '15秒動画台本を作成しました。'
};
}
function buildVideoScriptPrompt_(formData) {
return `
あなたは、学生向けの短いPR動画を作る動画ディレクターです。
次の企画情報をもとに、15秒動画の台本を作ってください。
【企画タイトル】
${formData.title}
【ターゲット】
${formData.target}
【伝えたいこと】
${formData.message}
【雰囲気】
${formData.tone}
【使いたい画像・場面】
${formData.scene || '未入力'}
【見た人にしてほしい行動】
${formData.action || '未入力'}
以下の形式で出してください。
1. 動画タイトル
2. 15秒動画の目的
3. 0〜3秒:つかみ
・映像
・テロップ
・ナレーション
4. 3〜7秒:紹介
・映像
・テロップ
・ナレーション
5. 7〜12秒:魅力
・映像
・テロップ
・ナレーション
6. 12〜15秒:案内
・映像
・テロップ
・ナレーション
7. Veo用プロンプト
8. AudioLM用ナレーション文
9. テロップだけを4つに整理
10. 発表で使える一言
条件:
・初心者でも作れる内容にする
・スマホで見やすい9:16動画を想定する
・テロップは短くする
・ナレーションは自然で聞き取りやすくする
・人物の顔ははっきり写さない
・動画生成時には文字やロゴを入れない
・動画が完成しなくても、構成だけで提出できる形にする
・少し前向きで、作ってみたくなる雰囲気にする
`;
}
function callGemini_(prompt) {
const apiKey = PropertiesService
.getScriptProperties()
.getProperty('GEMINI_API_KEY');
if (!apiKey) {
throw new Error('GEMINI_API_KEY が設定されていません。');
}
const url =
'https://generativelanguage.googleapis.com/v1beta/models/' +
MODEL_NAME +
':generateContent?key=' +
encodeURIComponent(apiKey);
const payload = {
contents: [
{
role: 'user',
parts: [{ text: prompt }]
}
],
generationConfig: {
temperature: 0.7,
maxOutputTokens: 1400
}
};
const options = {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(payload),
muteHttpExceptions: true
};
const response = UrlFetchApp.fetch(url, options);
const statusCode = response.getResponseCode();
const responseText = response.getContentText();
if (statusCode !== 200) {
throw new Error('Gemini APIエラー:' + responseText);
}
const json = JSON.parse(responseText);
const text = json.candidates?.[0]?.content?.parts?.[0]?.text;
if (!text) {
throw new Error('AIの回答を取得できませんでした。');
}
return text;
}
function saveHistory_(formData, result) {
const sheet = SpreadsheetApp
.getActiveSpreadsheet()
.getSheetByName(SHEET_NAME);
sheet.appendRow([
new Date(),
formData.title,
formData.target,
formData.message,
formData.tone,
formData.scene || '',
formData.action || '',
result
]);
}
function getRecentScripts() {
setupSheet_();
const sheet = SpreadsheetApp
.getActiveSpreadsheet()
.getSheetByName(SHEET_NAME);
const lastRow = sheet.getLastRow();
if (lastRow <= 1) {
return [];
}
const values = sheet.getRange(2, 1, lastRow - 1, 8).getValues();
return values
.map((row) => {
return {
createdAt: formatDate_(row[0]),
title: row[1],
target: row[2],
tone: row[4],
result: row[7]
};
})
.reverse()
.slice(0, 5);
}
function setupSheet_() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
let sheet = ss.getSheetByName(SHEET_NAME);
if (!sheet) {
sheet = ss.insertSheet(SHEET_NAME);
}
if (sheet.getLastRow() === 0) {
sheet.appendRow([
'作成日時',
'企画タイトル',
'ターゲット',
'伝えたいこと',
'雰囲気',
'使いたい場面',
'行動',
'動画台本'
]);
}
}
function validateFormData_(formData) {
if (!formData) {
throw new Error('入力データがありません。');
}
const requiredFields = [
['title', '企画タイトル'],
['target', 'ターゲット'],
['message', '伝えたいこと'],
['tone', '雰囲気']
];
requiredFields.forEach(([key, label]) => {
if (!formData[key] || String(formData[key]).trim() === '') {
throw new Error(label + 'を入力してください。');
}
});
}
function formatDate_(date) {
if (!date) return '';
return Utilities.formatDate(
new Date(date),
Session.getScriptTimeZone(),
'yyyy/MM/dd HH:mm'
);
}
6. index.htmlを作る
Apps Scriptの左側にある「+」を押します。
+
↓
HTML
ファイル名は次にします。
index
index.html の中身をすべて消して、次のコードを貼り付けます。
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<base target="_top" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AI動画台本メーカー</title>
<style>
:root {
--bg: #f8fafc;
--card: #ffffff;
--text: #111827;
--muted: #6b7280;
--line: #dbe3ee;
--primary: #f97316;
--primary-dark: #ea580c;
--soft: #fff7ed;
--success: #16a34a;
--danger: #dc2626;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
background: linear-gradient(180deg, #fffaf5 0%, #eef2f8 100%);
color: var(--text);
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
sans-serif;
line-height: 1.7;
}
.page {
width: min(1100px, calc(100% - 32px));
margin: 0 auto;
padding: 32px 0 48px;
}
.hero {
padding: 28px;
margin-bottom: 20px;
border: 1px solid var(--line);
border-radius: 24px;
background: var(--card);
box-shadow: 0 18px 40px rgba(15, 23, 42, 0.08);
}
.badge {
display: inline-flex;
padding: 6px 12px;
border-radius: 999px;
background: var(--soft);
color: var(--primary);
font-size: 13px;
font-weight: 800;
}
h1 {
margin: 18px 0 8px;
font-size: clamp(28px, 5vw, 44px);
line-height: 1.15;
letter-spacing: -0.04em;
}
.lead {
margin: 0;
color: var(--muted);
}
.grid {
display: grid;
grid-template-columns: 0.85fr 1.15fr;
gap: 20px;
}
.card {
border: 1px solid var(--line);
border-radius: 24px;
background: var(--card);
box-shadow: 0 14px 32px rgba(15, 23, 42, 0.06);
overflow: hidden;
}
.card-header {
padding: 20px 22px;
border-bottom: 1px solid var(--line);
background: #fbfdff;
}
.card-header h2 {
margin: 0;
font-size: 20px;
}
.card-header p {
margin: 4px 0 0;
color: var(--muted);
font-size: 14px;
}
form {
padding: 22px;
}
label {
display: block;
margin-bottom: 14px;
font-weight: 800;
}
.hint {
display: block;
margin: 2px 0 6px;
color: var(--muted);
font-size: 13px;
font-weight: 500;
}
input,
textarea,
select {
width: 100%;
border: 1px solid var(--line);
border-radius: 14px;
padding: 12px 14px;
background: #ffffff;
color: var(--text);
font: inherit;
outline: none;
}
textarea {
min-height: 92px;
resize: vertical;
}
input:focus,
textarea:focus,
select:focus {
border-color: var(--primary);
box-shadow: 0 0 0 4px rgba(249, 115, 22, 0.13);
}
.actions {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 20px;
}
button {
border: 0;
border-radius: 14px;
padding: 12px 18px;
cursor: pointer;
font: inherit;
font-weight: 900;
}
button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.primary {
background: var(--primary);
color: #ffffff;
}
.primary:hover {
background: var(--primary-dark);
}
.secondary {
background: #eef2f7;
color: #111827;
}
.message {
margin: 0 22px 22px;
padding: 12px 14px;
border-radius: 14px;
display: none;
font-weight: 800;
}
.message.success {
display: block;
background: #ecfdf5;
color: var(--success);
}
.message.error {
display: block;
background: #fef2f2;
color: var(--danger);
}
.loading {
display: none;
padding: 14px;
margin: 0 22px 22px;
border-radius: 14px;
background: #fffbeb;
color: #92400e;
font-weight: 800;
}
.loading.show {
display: block;
}
.result {
padding: 22px;
}
.result-box {
min-height: 440px;
padding: 20px;
border-radius: 18px;
background: #111827;
color: #f9fafb;
white-space: pre-wrap;
overflow-wrap: anywhere;
font-size: 15px;
}
.result-empty {
color: #9ca3af;
}
.recent {
padding: 0 22px 22px;
}
.recent-item {
padding: 14px 0;
border-top: 1px solid var(--line);
}
.recent-item:first-child {
border-top: 0;
}
.recent-title {
margin: 0;
font-weight: 900;
}
.recent-meta {
margin: 2px 0 0;
color: var(--muted);
font-size: 13px;
}
.small-button {
margin-top: 8px;
padding: 8px 12px;
border-radius: 10px;
background: #eef2f7;
color: #111827;
font-size: 13px;
}
.empty {
color: var(--muted);
font-size: 14px;
}
@media (max-width: 920px) {
.grid {
grid-template-columns: 1fr;
}
.hero {
padding: 22px;
}
}
</style>
</head>
<body>
<main class="page">
<section class="hero">
<span class="badge">6-6 15秒動画台本メーカー</span>
<h1>短い動画の<br />流れを作る</h1>
<p class="lead">
つかみ・紹介・魅力・案内の4分割で、15秒PR動画の設計図を作ります。
</p>
</section>
<section class="grid">
<div class="card">
<div class="card-header">
<h2>動画の材料を入力</h2>
<p>まだ動画がなくても大丈夫。まずは流れを作ります。</p>
</div>
<form id="scriptForm">
<label>
企画タイトル
<span class="hint">例:放課後カフェマップ</span>
<input
id="title"
type="text"
placeholder="企画タイトル"
required
/>
</label>
<label>
ターゲット
<span class="hint">例:学校帰りに寄れる場所を探している学生</span>
<input
id="target"
type="text"
placeholder="誰に届けたいか"
required
/>
</label>
<label>
伝えたいこと
<span class="hint">例:放課後に使える居場所を紹介する</span>
<textarea
id="message"
placeholder="何を伝えたいか"
required
></textarea>
</label>
<label>
雰囲気
<span class="hint">動画全体の印象を決めます</span>
<select id="tone" required>
<option value="">選択してください</option>
<option value="明るい・親しみやすい">明るい・親しみやすい</option>
<option value="おしゃれ・落ち着いた">おしゃれ・落ち着いた</option>
<option value="かわいい・やさしい">かわいい・やさしい</option>
<option value="かっこいい・テンポが良い">かっこいい・テンポが良い</option>
<option value="シンプル・分かりやすい">シンプル・分かりやすい</option>
</select>
</label>
<label>
使いたい画像・場面
<span class="hint">例:夕方のカフェ、スマホを見る手元</span>
<textarea
id="scene"
placeholder="動画で見せたい場面"
></textarea>
</label>
<label>
見た人にしてほしい行動
<span class="hint">例:サイトを見る、参加する、申し込む</span>
<input
id="action"
type="text"
placeholder="最後にしてほしい行動"
/>
</label>
<div class="actions">
<button class="primary" type="submit" id="generateButton">
動画台本を作る
</button>
<button class="secondary" type="button" id="sampleButton">
サンプル入力
</button>
<button class="secondary" type="button" id="clearButton">
クリア
</button>
</div>
</form>
<div id="loadingBox" class="loading">
AIが動画台本を作っています。少し待ってください。
</div>
<p id="messageBox" class="message"></p>
</div>
<div class="card">
<div class="card-header">
<h2>生成された動画台本</h2>
<p>この台本をもとに、Veo・AudioLM・スライド動画へ進めます。</p>
</div>
<div class="result">
<div id="resultBox" class="result-box">
<span class="result-empty">ここに15秒動画台本が表示されます。</span>
</div>
<div class="actions">
<button class="secondary" type="button" id="copyButton">
台本をコピー
</button>
</div>
</div>
<div class="card-header">
<h2>最近作った動画台本</h2>
<p>最新5件を表示します。</p>
</div>
<div class="recent" id="recentScripts">
<p class="empty">まだ履歴がありません。</p>
</div>
</div>
</section>
</main>
<script>
const form = document.getElementById('scriptForm');
const generateButton = document.getElementById('generateButton');
const sampleButton = document.getElementById('sampleButton');
const clearButton = document.getElementById('clearButton');
const copyButton = document.getElementById('copyButton');
const loadingBox = document.getElementById('loadingBox');
const messageBox = document.getElementById('messageBox');
const resultBox = document.getElementById('resultBox');
const fields = {
title: document.getElementById('title'),
target: document.getElementById('target'),
message: document.getElementById('message'),
tone: document.getElementById('tone'),
scene: document.getElementById('scene'),
action: document.getElementById('action')
};
form.addEventListener('submit', (event) => {
event.preventDefault();
const formData = getFormData();
setLoading(true);
showMessage('', '');
google.script.run
.withSuccessHandler((result) => {
setLoading(false);
resultBox.textContent = result.text;
showMessage(result.message || '15秒動画台本を作成しました。', 'success');
loadRecentScripts();
})
.withFailureHandler((error) => {
setLoading(false);
showMessage(error.message || '作成に失敗しました。', 'error');
})
.generateVideoScript(formData);
});
sampleButton.addEventListener('click', () => {
fields.title.value = '放課後カフェマップ';
fields.target.value = '学校帰りに友達と寄れる場所を探している学生';
fields.message.value =
'勉強にも休憩にも使える、放課後の居場所を分かりやすく紹介する。';
fields.tone.value = '明るい・親しみやすい';
fields.scene.value =
'夕方のカフェ、ノートとドリンク、スマホを見る手元、学校帰りの雰囲気。';
fields.action.value = 'サイトを見て、行ってみたいカフェを探す';
showMessage('サンプルを入力しました。自由に書き換えてください。', 'success');
});
clearButton.addEventListener('click', () => {
form.reset();
resultBox.innerHTML =
'<span class="result-empty">ここに15秒動画台本が表示されます。</span>';
showMessage('', '');
});
copyButton.addEventListener('click', async () => {
const text = resultBox.textContent.trim();
if (!text || text === 'ここに15秒動画台本が表示されます。') {
showMessage('コピーする台本がありません。', 'error');
return;
}
try {
await navigator.clipboard.writeText(text);
showMessage('台本をコピーしました。', 'success');
} catch (error) {
showMessage('コピーできませんでした。手動で選択してコピーしてください。', 'error');
}
});
function getFormData() {
return {
title: fields.title.value.trim(),
target: fields.target.value.trim(),
message: fields.message.value.trim(),
tone: fields.tone.value.trim(),
scene: fields.scene.value.trim(),
action: fields.action.value.trim()
};
}
function setLoading(isLoading) {
generateButton.disabled = isLoading;
generateButton.textContent = isLoading
? '作成中...'
: '動画台本を作る';
loadingBox.classList.toggle('show', isLoading);
}
function showMessage(text, type) {
if (!text) {
messageBox.textContent = '';
messageBox.className = 'message';
return;
}
messageBox.textContent = text;
messageBox.className = 'message ' + type;
}
function loadRecentScripts() {
google.script.run
.withSuccessHandler(renderRecentScripts)
.withFailureHandler(() => {
document.getElementById('recentScripts').innerHTML =
'<p class="empty">履歴を読み込めませんでした。</p>';
})
.getRecentScripts();
}
function renderRecentScripts(scripts) {
const container = document.getElementById('recentScripts');
if (!scripts || scripts.length === 0) {
container.innerHTML =
'<p class="empty">まだ履歴がありません。</p>';
return;
}
container.innerHTML = scripts
.map((item) => {
return `
<div class="recent-item">
<p class="recent-title">${escapeHtml(item.title)}</p>
<p class="recent-meta">
${escapeHtml(item.createdAt)}|${escapeHtml(item.tone)}
</p>
<button class="small-button" type="button" onclick="showScript('${encodeURIComponent(item.result || '')}')">
台本を見る
</button>
</div>
`;
})
.join('');
}
function showScript(encodedText) {
resultBox.textContent = decodeURIComponent(encodedText);
}
function escapeHtml(value) {
return String(value)
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"')
.replaceAll("'", ''');
}
loadRecentScripts();
</script>
</body>
</html>
7. 保存する
コードを貼り付けたら保存します。
Command + S
Windowsの場合は、
Ctrl + S
です。
8. Webアプリとして公開する
右上の「デプロイ」を押します。
デプロイ
↓
新しいデプロイ
種類は、次を選びます。
ウェブアプリ
設定は次の通りです。
| 項目 | 設定 |
|---|---|
| 説明 | 6-6 AI動画台本メーカー |
| 次のユーザーとして実行 | 自分 |
| アクセスできるユーザー | 自分のみ |
デプロイ後に表示されるURLを開くと、動画台本メーカーが表示されます。
9. 動かしてみる
画面が開いたら、まず「サンプル入力」を押します。
そのあと、次のボタンを押します。
動画台本を作る
少し待つと、右側に15秒動画台本が表示されます。
スプレッドシートには、自動で 動画台本履歴 シートが作られ、生成結果が保存されます。
10. 生成結果の見方
生成された台本では、まずこの3つを確認します。
0〜3秒のつかみがあるか
テロップが短いか
最後に行動案内があるか
動画は、すべてを説明しようとすると長くなります。
15秒動画では、これくらいで十分です。
気になる
↓
何か分かる
↓
良さが分かる
↓
次の行動が分かる
11. うまく出ない時
テロップが長すぎる
プロンプトに次を追加します。
・テロップは10〜16文字くらいにする
動画の流れが分かりにくい
次を追加します。
・0〜3秒、3〜7秒、7〜12秒、12〜15秒で明確に分ける
映像にしにくい
次を追加します。
・実際に映像で見せられる場面にする
・抽象的な言葉だけにしない
ナレーションが長すぎる
次を追加します。
・ナレーションは15秒で読める長さにする
・60〜90文字程度にする
12. 動画生成ができない時の提出方法
VeoやAudioLMが使えない場合でも、この教材では問題ありません。
提出できる形はこれです。
15秒動画構成
テロップ4つ
ナレーション文
Veo用プロンプト
画像や動画が完成していなくても、動画の設計図があれば前に進めます。
無料アカウントで動画生成が難しい場合は、Googleスライドに4枚並べてもOKです。
| スライド | 内容 |
|---|---|
| 1枚目 | つかみ |
| 2枚目 | 紹介 |
| 3枚目 | 魅力 |
| 4枚目 | 案内 |
これだけでも、動画の流れは伝わります。
13. 今日の提出物
この回の提出物は、次の3つです。
1. WebアプリURL
2. 生成された動画台本のスクリーンショット
3. スプレッドシートの履歴スクリーンショット
余裕がある人は、Googleスライドで4コマの動画絵コンテも作ってください。
最低限は、これでOKです。
15秒動画の流れが作れた
14. チェックリスト
| チェック | 内容 |
|---|---|
| スプレッドシートを作った | |
| Apps Scriptを開いた | |
| GEMINI_API_KEYを確認した | |
| Code.gsを貼り付けた | |
| index.htmlを作った | |
| index.htmlを貼り付けた | |
| Webアプリとしてデプロイした | |
| WebアプリURLを開いた | |
| サンプル入力を押した | |
| 動画台本を作るを押した | |
| 15秒動画台本が表示された | |
| 履歴がスプレッドシートに保存された |
まとめ
今回は、15秒動画の台本を自動で作るアプリを作りました。
動画づくりで大切なのは、いきなり映像を作ることではありません。
まずは、流れを決めることです。
つかみ
↓
紹介
↓
魅力
↓
案内
この4つがあるだけで、短い動画は作りやすくなります。
今日作ったアプリは、動画生成AIを使う前の大切な準備です。
企画書
↓
画像プロンプト
↓
15秒動画台本
↓
動画生成
↓
LP制作
15秒は短いですが、短いからこそ伝える力が育ちます。
次の回では、この企画を1ページのLP構成にまとめていきます。