Remotionで不動産物件動画を自動生成する実践ガイド
Remotionで不動産物件動画を自動生成する実践ガイド
不動産業界における動画コンテンツの重要性は、この数年で急速に高まっている。ポータルサイトで物件を検索するユーザーの行動を見ると、写真だけの掲載より動画付き物件の問い合わせ率が有意に高い傾向が一貫して観測されており、InstagramやTikTokのアルゴリズムが静止画よりも動画コンテンツをリーチ面で優遇していることも業界内では広く認識されている。
しかし、中規模の不動産会社が管理する物件は常時50件、大手エージェンシーともなれば数百件に及ぶ。物件ごとに住所・価格・間取り・写真・周辺環境データが異なる中、すべての物件に対して個別に動画を制作するのは現実的ではない。制作コストを抑えようとすると、競合と見分けのつかないテンプレート動画になり、ブランドの差別化が損なわれる。
Remotionはこのジレンマを根本から解決する。Remotionにおいてビデオはコードで書かれたReactコンポーネントであり、プロップス(props)経由でデータを受け取る。物件データのJSONを流し込むだけで、同一のテンプレートから物件ごとに異なる動画が自動的に生成される。テンプレートの制作は一度だけ。あとはデータが動画になる。
アーキテクチャの概要:JSONからInstagram Stories動画まで
全体の処理フローはシンプルだ。
CRM / MLS / スプレッドシート
↓
JSON形式に正規化(物件1件 = 1オブジェクト)
↓
renderMedia() を物件数分ループ実行
↓
各物件のMP4ファイルが out/ フォルダに出力
まず物件データのJSON構造を定義する。この構造がそのままRemotionコンポジションのpropsの型定義になる。
{
"listings": [
{
"id": "prop-001",
"address": "東京都渋谷区代々木2-15-8",
"price": 68000000,
"priceLabel": "6,800万円",
"layout": "3LDK",
"floorArea": 78.4,
"buildYear": 2018,
"floor": "12階 / 25階建",
"photos": [
"https://cdn.example.com/prop-001/exterior.jpg",
"https://cdn.example.com/prop-001/living.jpg",
"https://cdn.example.com/prop-001/kitchen.jpg"
],
"features": ["南向きバルコニー", "宅配ボックス", "ディスポーザー"],
"neighborhood": "代々木",
"walkMinutes": 7,
"nearestStation": "代々木駅",
"agentName": "田中 恵",
"agencyName": "アペックス不動産"
}
]
}
この構造を設計する際のポイントは、表示用の文字列(priceLabel)と計算用の数値(price)を分離することだ。価格アニメーションのカウントアップには数値型が必要だが、最終的に画面に表示する際は「6,800万円」のようなフォーマット済みの文字列を使いたい場面もある。両方を持っておくことで柔軟性が増す。
コンポジションの構成:9秒・9:16縦型
Instagram StoriesおよびReels向けの基本構成は1080×1920ピクセル、30fps、270フレーム(9秒)だ。
import { Composition } from 'remotion';
import { PropertyListingVideo } from './PropertyListingVideo';
export const RemotionRoot = () => (
<Composition
id="PropertyListing"
component={PropertyListingVideo}
durationInFrames={270}
fps={30}
width={1080}
height={1920}
defaultProps={{
address: '東京都渋谷区代々木2-15-8',
price: 68000000,
priceLabel: '6,800万円',
layout: '3LDK',
floorArea: 78.4,
photos: [],
features: [],
nearestStation: '代々木駅',
walkMinutes: 7,
agentName: '田中 恵',
agencyName: 'アペックス不動産',
}}
/>
);
タイムラインの設計は以下のように区切ると自然なリズムが生まれる。
| フレーム範囲 | 内容 |
|---|---|
| 0–60 | 外観写真 + 住所テキストのフェードイン |
| 60–120 | 内覧写真カルーセル(2〜3枚) |
| 120–180 | 価格・間取りスペックカード |
| 180–240 | セールスポイントのアニメーション箇条書き |
| 240–270 | 最寄り駅情報 + 担当エージェントCTA |
価格アニメーション:スプリングで数字を「生かす」
価格のカウントアップアニメーションは、物件動画の中で最も視聴者の関心を引く演出のひとつだ。ただし実装を誤ると安っぽく見える。ポイントはスプリング(spring)アニメーションのdampingを適切に設定し、目標値を自然に通り越してから収束する動きを避けることだ。
import { useCurrentFrame, spring, interpolate, useVideoConfig } from 'remotion';
type AnimatedPriceProps = {
targetPrice: number;
displayLabel: string;
startFrame: number;
};
export const AnimatedPrice: React.FC<AnimatedPriceProps> = ({
targetPrice,
displayLabel,
startFrame,
}) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const progress = spring({
frame: frame - startFrame,
fps,
config: { damping: 20, stiffness: 70, mass: 0.9 },
});
const displayValue = Math.round(interpolate(progress, [0, 1], [0, targetPrice]));
// 万円単位に変換して表示
const displayText = displayValue >= 10000
? `${(displayValue / 10000).toFixed(0)}万円`
: `${displayValue.toLocaleString()}円`;
return (
<div style={{
fontSize: 80,
fontWeight: 700,
color: '#FFFFFF',
fontFamily: '"Yu Gothic", "Hiragino Kaku Gothic ProN", "Noto Sans JP", sans-serif',
textShadow: '0 2px 16px rgba(0,0,0,0.6)',
letterSpacing: '-0.01em',
}}>
{displayText}
</div>
);
};
damping: 20という値は、物件価格のような「信頼感」を演出したいコンテキストに適している。ゲーミフィケーション的なポップな動きではなく、重厚感のある収束を実現できる。
写真カルーセルとケン・バーンズ効果
各写真に微妙なパン&ズーム(ケン・バーンズ効果)を加えると、静止画でも映像的なリッチさが生まれる。
type KenBurnsImageProps = {
src: string;
startFrame: number;
durationFrames: number;
direction: 'zoom-in' | 'zoom-out' | 'pan-left' | 'pan-right';
};
export const KenBurnsImage: React.FC<KenBurnsImageProps> = ({
src,
startFrame,
durationFrames,
direction,
}) => {
const frame = useCurrentFrame();
const progress = (frame - startFrame) / durationFrames;
const clampedProgress = Math.min(Math.max(progress, 0), 1);
const scale = direction === 'zoom-in'
? 1 + clampedProgress * 0.08
: 1.08 - clampedProgress * 0.08;
const translateX = direction === 'pan-left'
? -clampedProgress * 40
: direction === 'pan-right'
? clampedProgress * 40
: 0;
return (
<Img
src={src}
style={{
width: '100%',
height: '100%',
objectFit: 'cover',
transform: `scale(${scale}) translateX(${translateX}px)`,
}}
/>
);
};
連続する写真でdirectionを交互に変える(zoom-in → pan-left → zoom-out)と、単調さを避けた自然なリズムが生まれる。スケール変化は5〜10%の範囲に抑えること。それ以上だと視聴者が「拡大されている」と意識してしまい、逆効果になる。
物件スペックカードのスタガードアニメーション
間取り・専有面積・最寄り駅といったスペック情報は、カードを少しずつ遅延させてスライドインさせると読みやすく、かつ映像的な奥行きが出る。
const SPECS = [
{ label: '間取り', value: layout },
{ label: '専有面積', value: `${floorArea}㎡` },
{ label: '最寄り駅', value: `${nearestStation} 徒歩${walkMinutes}分` },
];
{SPECS.map((spec, i) => {
const delay = i * 10;
const opacity = interpolate(
frame - startFrame - delay,
[0, 18],
[0, 1],
{ extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }
);
const translateY = interpolate(
frame - startFrame - delay,
[0, 18],
[28, 0],
{ extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }
);
return (
<div key={spec.label} style={{
opacity,
transform: `translateY(${translateY}px)`,
backgroundColor: 'rgba(255,255,255,0.1)',
backdropFilter: 'blur(8px)',
borderRadius: 12,
padding: '16px 24px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}>
<span style={{
fontSize: 26,
color: 'rgba(255,255,255,0.7)',
fontFamily: '"Yu Gothic", "Hiragino Kaku Gothic ProN", "Noto Sans JP", sans-serif',
}}>
{spec.label}
</span>
<span style={{
fontSize: 28,
fontWeight: 700,
color: '#FFFFFF',
fontFamily: '"Yu Gothic", "Hiragino Kaku Gothic ProN", "Noto Sans JP", sans-serif',
}}>
{spec.value}
</span>
</div>
);
})}
50件以上の一括レンダリングスクリプト
バッチレンダリングはRemotionが最も得意とする領域だ。以下のスクリプトを実行するだけで、全物件の動画が自動生成される。
import { renderMedia, selectComposition } from '@remotion/renderer';
import path from 'path';
import fs from 'fs';
import listings from './data/listings.json';
const BUNDLE_URL = './out/bundle';
const OUTPUT_DIR = './out/videos';
async function renderAllProperties() {
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
console.log(`${listings.length}件の物件動画をレンダリング開始...`);
// 5件ずつ並列処理(メモリ圧迫を防ぐ)
const BATCH_SIZE = 5;
for (let i = 0; i < listings.length; i += BATCH_SIZE) {
const batch = listings.slice(i, i + BATCH_SIZE);
await Promise.all(batch.map(async (listing) => {
const composition = await selectComposition({
serveUrl: BUNDLE_URL,
id: 'PropertyListing',
inputProps: listing,
});
const outputPath = path.join(OUTPUT_DIR, `${listing.id}.mp4`);
await renderMedia({
composition,
serveUrl: BUNDLE_URL,
codec: 'h264',
outputLocation: outputPath,
inputProps: listing,
concurrency: 2,
});
console.log(`完了: ${listing.address} → ${outputPath}`);
}));
}
console.log('全物件のレンダリング完了。');
}
renderAllProperties().catch(console.error);
M3チップ搭載のMacBook Proで9秒の1080×1920動画をレンダリングする場合、1件あたり約8〜12秒かかる。50件であれば8〜10分で全件完了する計算だ。さらに高速化が必要であれば、Remotion Lambdaを使ってAWS上でクラウド並列処理することで、同じ50件を2分以内に処理できる。
差分更新:価格変更時の効率的な再レンダリング
不動産ポータルでは価格改定が頻繁に発生する。50件すべてを毎回再レンダリングするのは非効率だ。物件データにタイムスタンプを付加し、前回レンダリング時からデータが変化した物件のみを再処理する差分レンダリングを実装すると、運用コストを大幅に削減できる。
// 前回のレンダリング記録を保持するJSONファイル
const RENDER_LOG_PATH = './out/render-log.json';
type RenderLog = Record<string, { renderedAt: string; checksum: string }>;
function computeChecksum(listing: ListingProps): string {
const crypto = require('crypto');
return crypto
.createHash('md5')
.update(JSON.stringify(listing))
.digest('hex');
}
async function renderChangedListings(listings: ListingProps[]) {
const log: RenderLog = fs.existsSync(RENDER_LOG_PATH)
? JSON.parse(fs.readFileSync(RENDER_LOG_PATH, 'utf-8'))
: {};
const changed = listings.filter((listing) => {
const prev = log[listing.id];
return !prev || prev.checksum !== computeChecksum(listing);
});
console.log(`変更あり: ${changed.length}件 / 全${listings.length}件`);
// changed のみをレンダリング...
}
このパターンを使うと、毎晩の定期実行バッチでも変更のあった物件だけが再レンダリングされ、コンピューティングコストと時間の双方を節約できる。
RenderCompテンプレートで即時スタート
不動産物件動画テンプレートをゼロから構築すると、アニメーション曲線の調整、フォントサイズのレスポンシブ対応、写真が少ない場合のフォールバック処理など、細部の作り込みに相当の時間がかかる。
RenderComp では、このガイドで解説したJSON構造に直接対応した不動産向けRemotionテンプレートを提供している。価格アニメーション、ケン・バーンズ効果付き写真カルーセル、スペックカード、エージェントCTAがすべて実装済みで、データを差し込むだけでレンダリングを開始できる。
FAQ
Q: 物件写真がMLS CDNのURL形式の場合、ローカルにダウンロードする必要がありますか? A: RemotionのImgコンポーネントはリモートURLを直接使用できます。ただしバッチレンダリング時は、事前にローカルへダウンロードしておくことを強く推奨します。ネットワーク遅延がレンダリング時間に積み重なるため、50件の処理で数分の差が生まれることがあります。また、MLSのCDNによってはレンダリング時の認証ヘッダー挿入が難しいケースもあります。
Q: 写真が3枚しかない物件と10枚ある物件で、同じテンプレートを使えますか?
A: 可能です。カルーセルの表示枚数と各フォトシーンの尺をpropsから動的に計算するよう実装してください。写真が少ない場合はブランドカラーのグラデーションプレースホルダーでパディングするのが一般的な解決策です。写真配列の長さで総フレーム数を可変にすることも、calculateMetadataを使えば実現できます。
Q: 音楽(BGM)を付けることはできますか? A: できます。RemotionのAudioコンポーネントにローカルの音声ファイルを指定するだけです。物件動画ではSNSのオートプレイがミュートで始まる点を考慮し、音楽なしでも情報が伝わるデザインを優先した上で、BGMは20〜30%程度の音量で最後にフェードアウトさせる構成が無難です。ロイヤリティフリーの楽曲を使用してください。
Q: 横型(16:9)版も同時に出力できますか?
A: 可能です。formatプロップスでstory(9:16)、landscape(16:9)、square(1:1)を切り替えるコンポジションを作り、同じ物件データから3フォーマットを同時出力するバッチスクリプトを組むことができます。アニメーションのロジックは共通で、コンテナサイズとテキスト配置のみを変更する設計が保守しやすい。
Q: Remotion Lambdaを使う場合、AWSのコストはどれくらいになりますか? A: 9秒の1080×1920動画50件をLambdaでレンダリングした場合、通常のRemotion Lambda料金(Lambda実行時間+S3転送)で概算500〜1,500円程度になります(リージョンや設定によって変動)。ローカルレンダリングが難しい本番自動化フローや、1時間以内の緊急対応が必要な場合にLambdaを選択するのが合理的です。
Q: 一棟マンション全室の動画を一気に作ることはできますか? A: 技術的にはまったく同じアプローチで実現できます。JSONに100室分のデータを用意し、バッチスクリプトを実行するだけです。部屋番号・階数・向き・価格をpropsとして渡せば、各部屋に最適化されたユニークな動画が自動生成されます。