最近の無料AIツールが進化しすぎて、何か面白いことができそうだと思いませんか? 今回は、複数のAIを組み合わせて、HTMLベースの簡易リップシンクアニメーションを作ってみました。 画像生成から音声処理、動作検証まで、すべて無料のツールで完結!
手順とコツをまとめたので、誰でも気軽に試せるはずです。
完成品はこちら👇
https://django6.kikuichige.com/lipsync/nyanporo/index.html
名前は「にゃんぽろぺ」です。適当に命名しました。
使ったAI
Google AI Studio imagen4(preview):元画像
Google AI Studio Gemini image generation:元画像を元に修正画像作成
Google AI Studio Gemini speech generation:音声(今回は、使用しません。)
Gemini CLI:コード生成
AIは便利だけどハマるときは、ハマるので、AIを信用しすぎると、余計、手間取ったりします。
動画でも使ってみた
材料を用意(画像作成、音声)
Google AI Studioを利用します。Google AI Studioはこちら↓を参考にしてください。
元画像
Google AI Studio→左のメニューでGenerate Media→Imagen
モデルはImagen4(preview)を選択。プロンプトに入力してRun Ctrl
プロンプト例:「まんが風の癒し系ねこ。背景は白」
動画で使ったものは背景を透明で指示した。
元画像を元に修正画像作成
ここが1番難しい。
imagen4は画像をアップロードできないので以下を使います。
Google AI Studio→左のメニューでGenerate Media→Gemini image generation
モデルはGemini 2.0 Flash Preview Image Generation
+で上で作った画像をアップロード
プロンプト「リップシンクで使うので口を閉じて」
結果、口だけの指示だったが、目も閉じてしまった。その後もいろいろ試した。微調整が難しい。
意図通りの絵はできませんでしたが、まあ、よしとしました。
目が閉じてしまうバージョン。
https://django6.kikuichi.com/lipsync/nyanporo/index.html
ひげが違うバージョン。
https://django6.kikuichige.com/lipsync/nyanporo/index2.html
コツ:
・うまくいかないときは、Clear Chatでアップロードからやり直し、プロンプトを変えて挑戦する。
・Run safety settingsに引っかかると⚠️が出て作ってくれない。
・Run safety settingsを全部OFFにしても、だめなときは、Clear Chatでやり直し、プロンプトも上品な表現になるよう工夫する。(OFFしても効いていないように感じる。すごく厳しい)
🛡️ Google AI Studioの「Run safety settings」とは?
この設定は、生成されるテキストや画像コンテンツの安全性を制御するためのフィルターです。プロンプトに対して返される出力が、ユーザーや利用シーンにとって有害・不適切でないように調整する仕組みです。
🔧 各設定内容(Run safety settings の一部)
テキスト生成の場合、以下のようなカテゴリがあります:
カテゴリ名 | オフにするとどうなるか |
---|---|
Harassment | 侮辱・嫌がらせを含む表現がブロックされなくなる可能性があります |
Hate | 差別的・憎悪を含む内容も生成され得ます |
Sexually Explicit | 性的な表現や露骨なコンテンツが許可されます |
Dangerous Content | 危険な行動や有害な情報(例:自傷や暴力など)も生成される恐れあり |
つまり、設定を「Off」にすると、フィルターが無効になり、刺激の強い/倫理的に懸念されるコンテンツも出力される可能性があるということです。しかし、実際はOFFしても厳しい。
💡 いつ設定をOffにする?
- 制限のないクリエイティブ制作(大人向け、風刺、実験的表現など)
- 学術的・技術的な検証目的(モデルの挙動や限界を調べるなど)
ちなみにChatGptで画像をアップロードして「口の開きパターンを分けた静止画を自動生成して」とお願いしたら、黒い楕円が体の上にある画像が出てきて、全然、ダメだった。
音声
音声はGoogle AI Studio Gemini speech generationで作れますが、今回は使いません。
Google AI Studio Gemini speech generationはhttps://kikuichige.com/31424/#toc8を参照してください。
今回は、こちら↓を利用
バイブコーディング
コードはGemini CLIでバイブコーディングした。(AIを活用して、自然言語による指示だけでソフトウェアを開発する新しいプログラミング手法)
プロンプト「imgのファイルを使ってリップシンクを作って」
実行して、修正してほしいところを何回か依頼した。
コードの中身は、ほぼ把握していない。
ディレクトリは以下、青いファイルは事前準備した。赤いファイルはGeminiが作成。
C:\USERS\user\GEMINI_CLI\LIPSYNC
│ index.html
│ nyanporropedayo.mp3
│ script.js
│ style.css
│
└─img
catclose.jpeg
catopen.jpeg
catori.jpg
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>リップシンク</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>簡易リップシンク<br>アニメーション</h1>
<img id="character" src="img/catori.jpg" alt="Character">
<br>
<button id="talkButton">話す</button>
<audio id="audio" src="nyanporropedayo.mp3"></audio>
<script src="script.js"></script>
<footer>
<p>声は<a href="https://voicevox.hiroshiba.jp/" target="_blank">VOICEVOX:ずんだもん</a></p>
</footer>
</body>
</html>
script.js
const characterImage = document.getElementById('character');
const talkButton = document.getElementById('talkButton');
const audio = document.getElementById('audio');
const openMouthImage = 'img/catopen.jpeg';
const closedMouthImage = 'img/catclose.jpeg';
const defaultImage = 'img/catori.jpg';
let audioContext;
let analyser;
// AudioContextとAnalyserを初期化する関数
function initializeAudio() {
// すでに初期化されていれば何もしない
if (audioContext) {
return;
}
audioContext = new (window.AudioContext || window.webkitAudioContext)();
const source = audioContext.createMediaElementSource(audio);
analyser = audioContext.createAnalyser();
source.connect(analyser);
analyser.connect(audioContext.destination);
analyser.fftSize = 32;
}
// リップシンクのアニメーション関数
function lipSyncAnimation() {
// analyserがなければ処理を中断
if (!analyser) {
return;
}
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
analyser.getByteFrequencyData(dataArray);
const sum = dataArray.reduce((a, b) => a + b, 0);
const avg = sum / bufferLength;
// しきい値を超えたら口を開く
if (avg > 100) { // しきい値は調整可能
characterImage.src = openMouthImage;
} else {
characterImage.src = closedMouthImage;
}
// 音声が再生中ならアニメーションを継続
if (!audio.paused) {
requestAnimationFrame(lipSyncAnimation);
} else {
characterImage.src = defaultImage;
}
}
talkButton.addEventListener('click', () => {
// 最初のクリックでAudioContextを初期化
initializeAudio();
// AudioContextが停止していたら再開
if (audioContext.state === 'suspended') {
audioContext.resume();
}
// 音声を最初から再生
audio.currentTime = 0;
audio.play();
// アニメーションを開始
lipSyncAnimation();
});
style.css
body {
text-align: center;
}
img {
width: 300px;
height: 300px;
}
#talkButton {
background-color: #007bff;
color: white;
padding: 15px 30px;
border: none;
border-radius: 5px;
font-size: 18px;
cursor: pointer;
margin-top: 20px; /* 画像との間に余白を追加 */
}
ページ内のリップシンクアニメーション関数は、JavaScriptで音声の周波数データを解析し、キャラクター画像を口の開閉に応じて切り替える仕組みになっています。
🎤 リップシンクのアニメーション関数の仕組み
function lipSyncAnimation() {
if (!analyser) return;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
analyser.getByteFrequencyData(dataArray);
const sum = dataArray.reduce((a, b) => a + b, 0);
const avg = sum / bufferLength;
if (avg > 10) {
characterImage.src = openMouthImage;
} else {
characterImage.src = closedMouthImage;
}
requestAnimationFrame(lipSyncAnimation);
}
🔍 解説ポイント
analyser.getByteFrequencyData(dataArray)
音声の周波数成分を取得。dataArray
に音の強さ(音量)が格納されます。reduce
で合計 → 平均値を算出
音量の平均値を求めて、口を開けるか閉じるかの判定に使います。avg > 10
の閾値で画像切り替え
音量が一定以上なら口を開けた画像(catopen.jpeg
)、それ以下なら閉じた画像(catclose.jpeg
)に変更。requestAnimationFrame
ブラウザの描画タイミングに合わせて再帰的に関数を呼び出し、滑らかなアニメーションを実現。
🧠 補足:初期化関数との連携
function initializeAudio() {
if (audioContext) return;
audioContext = new (window.AudioContext || window.webkitAudioContext)();
const source = audioContext.createMediaElementSource(audio);
analyser = audioContext.createAnalyser();
source.connect(analyser);
analyser.connect(audioContext.destination);
analyser.fftSize = 32;
}
AudioContext
とAnalyserNode
を使って音声解析の準備。fftSize
を小さくすることで、より細かく音の変化を検出。
Gemini CLIはこちら↓
コード生成は、Cursorのほうが優秀かも
CORS 制限を回避して実行
ローカルでindex.htmlをダブルクリックしてブラウザで表示する方法だと、ちゃんと動作しません。
開発者ツールでconsoleタブを見ると以下があります。
MediaElementAudioSource outputs zeroes due to CORS access restrictions for file:///C:/Users/user/gemini_cli/lipsync/nyanporropedayo.mp3
🎧 このエラーの意味と原因:
「MediaElementAudioSource outputs zeroes due to CORS access restrictions
」というメッセージは、Web Audio API がローカルファイル(file:///
)にアクセスしようとした際に、CORS(クロスオリジン制限)によってブロックされたことを示しています。
つまり、createMediaElementSource()
を使って音声解析やビジュアライゼーションをしようとしても、ブラウザがセキュリティ上の理由で音声データを取得できず、無音(ゼロ)として扱ってしまうということです。
🛑 なぜ起こるのか?
file:///
で始まるローカルファイルは、「オリジン(出所)」が定義されていないため、CORSポリシーによりアクセスが制限されます。- Web Audio API は、音声データを解析するために「生のデータ」へのアクセスが必要ですが、CORS制限下ではそれができません。
🛠️ 解決方法:
ローカルサーバーを立てる
→ file:///
ではなく http://localhost/...
でアクセスするようにします。
たとえば、Pythonが入っていれば以下で簡易サーバーを起動できます:
cd C:\Users\user\gemini_cli\lipsync
python -m http.server 8000
その後、ブラウザで http://localhost:8000 にアクセス。
ポイントは、ファイルがあるディレクトリに移動して python -m http.server 8000 を実行することです。すると、そのディレクトリにあるファイルは、ローカルサーバー http://localhost:8000 経由でブラウザにアクセスできるようになります。
これで、CORS制限を回避しながら実行できます。
今回、Web Audio API が CORS 制限により音声データを取得できていませんが、
ローカル環境で JavaScript を使って file:///
経由でファイルを読み込む場合にも、
同様の制限が発生します。よくあることなので覚えておくといいでしょう。
まとめ
📝 簡易リップシンクアニメーション制作のまとめ
今回の記事では、無料で使えるAIツールを複数組み合わせて、HTMLベースのリップシンクアニメーションを作成する工程が紹介されていました。以下にポイントを整理します👇
✅ 使用ツールと役割
ツール・技術 | 役割内容 |
---|---|
Google AI Studio (Imagen4) | 元画像を生成(猫漫画など) |
Gemini Image Generation | 口の動きに合わせた画像修正(口を閉じた画像など) |
Gemini CLI | HTML・CSS・JSのコード生成。 |
HTML/JavaScript | アニメーションの実装 |
Web Audio API | 音声解析(CORS制限に注意) |
🔧 技術的な工夫と注意点
- 画像生成のコツ: Run Safety SettingsをOFFにしても厳しい判定があるので、Clear Chatでの再挑戦+プロンプト改善が有効。
- 音声処理: バイブコーディングを活用し、簡易的に口パクを制御。
- CORS制限回避: ローカルサーバー(例:
python -m http.server
)を使ってfile:///
によるエラーを回避。
🎬 最終成果物とリンク
完成したアニメーションはこちら:
🔗 https://django6.kikuichige.com/lipsync/nyanporo/index.html
🎯 この手法の魅力
無料のAIツールを組み合わせることで、誰でも手軽に音声連動アニメーションを作れる時代が来たということを実感できます。Geminiのコード生成、画像編集の精度も活用価値大!
イチゲをOFUSEで応援する(御質問でもOKです)Vプリカでのお支払いがおすすめです。
MENTAやってます(ichige)