Skip to content

Instantly share code, notes, and snippets.

@chan-ume
Last active February 15, 2026 07:28
Show Gist options
  • Select an option

  • Save chan-ume/f3aa0bdaa5e86476c9db3c49ea6e55cc to your computer and use it in GitHub Desktop.

Select an option

Save chan-ume/f3aa0bdaa5e86476c9db3c49ea6e55cc to your computer and use it in GitHub Desktop.
Remotionで画像・動画・音楽素材を使う例
// https://www.randpy.tokyo/entry/remotion-4-assets
import {
AbsoluteFill,
Img,
interpolate,
Sequence,
staticFile,
useCurrentFrame,
useVideoConfig,
} from "remotion";
import { Video } from "@remotion/media";
import { Audio } from "@remotion/media";
import { z } from "zod";
export const assetsDemoSchema = z.object({
imagePath: z.string(),
videoPath: z.string(),
audioPath: z.string(),
});
// --- Phase 1: Img + staticFile + Ken Burns ---
const ImageShowcase: React.FC<{ imagePath: string }> = ({ imagePath }) => {
const frame = useCurrentFrame();
// Ken Burns 効果: ゆっくりズーム
const scale = interpolate(frame, [0, 100], [1, 1.15]);
return (
<AbsoluteFill>
<AbsoluteFill>
<Img
src={staticFile(imagePath)}
style={{
width: "100%",
height: "100%",
objectFit: "cover",
transform: `scale(${scale})`,
}}
/>
</AbsoluteFill>
{/* ラベルオーバーレイ */}
<AbsoluteFill
style={{
justifyContent: "flex-start",
alignItems: "flex-start",
padding: 40,
}}
>
<div
style={{
backgroundColor: "rgba(0, 0, 0, 0.7)",
color: "#4ade80",
fontSize: 28,
fontFamily: "monospace",
padding: "12px 20px",
borderRadius: 8,
}}
>
{"<Img src={staticFile(\"" + imagePath + "\")} />"}
</div>
</AbsoluteFill>
<AbsoluteFill
style={{
justifyContent: "flex-start",
alignItems: "flex-start",
padding: 40,
paddingTop: 100,
}}
>
<div
style={{
backgroundColor: "rgba(0, 0, 0, 0.7)",
color: "#60a5fa",
fontSize: 24,
fontFamily: "monospace",
padding: "8px 16px",
borderRadius: 8,
}}
>
objectFit: &quot;cover&quot; + Ken Burns (scale{" "}
{scale.toFixed(2)})
</div>
</AbsoluteFill>
{/* タイトル */}
<AbsoluteFill
style={{
justifyContent: "flex-end",
alignItems: "center",
paddingBottom: 60,
}}
>
<div
style={{
fontSize: 56,
fontWeight: "bold",
color: "white",
fontFamily: "sans-serif",
textShadow: "0 2px 8px rgba(0,0,0,0.8)",
}}
>
staticFile + Img + Ken Burns
</div>
</AbsoluteFill>
</AbsoluteFill>
);
};
// --- Phase 2: Video ---
const VideoShowcase: React.FC<{ videoPath: string }> = ({ videoPath }) => {
const { fps } = useVideoConfig();
return (
<AbsoluteFill style={{ backgroundColor: "#111" }}>
{/* 動画を中央に表示 */}
<AbsoluteFill
style={{ justifyContent: "center", alignItems: "center" }}
>
<Video
src={staticFile(videoPath)}
style={{
width: 1280,
height: 720,
objectFit: "cover",
borderRadius: 16,
}}
volume={0.5}
/>
</AbsoluteFill>
{/* ラベル */}
<AbsoluteFill
style={{
justifyContent: "flex-start",
alignItems: "flex-start",
padding: 40,
}}
>
<div
style={{
backgroundColor: "rgba(0, 0, 0, 0.8)",
color: "#4ade80",
fontSize: 24,
fontFamily: "monospace",
padding: "10px 16px",
borderRadius: 8,
display: "flex",
flexDirection: "column",
gap: 4,
}}
>
<span>{"<Video src={staticFile(\"" + videoPath + "\")} />"}</span>
<span style={{ color: "#fbbf24" }}>
volume={"{0.5}"} / trimBefore={"{" + (2 * fps) + "}"} /
playbackRate={"{1.5}"}
</span>
</div>
</AbsoluteFill>
{/* タイトル */}
<AbsoluteFill
style={{
justifyContent: "flex-end",
alignItems: "center",
paddingBottom: 40,
}}
>
<div
style={{
fontSize: 56,
fontWeight: "bold",
color: "white",
fontFamily: "sans-serif",
}}
>
Video コンポーネント
</div>
</AbsoluteFill>
</AbsoluteFill>
);
};
// --- Phase 3: Audio + volume callback 可視化 ---
const AudioShowcase: React.FC<{ audioPath: string }> = ({ audioPath }) => {
const frame = useCurrentFrame();
const { fps, durationInFrames } = useVideoConfig();
// volume コールバックと同じ計算を可視化用に再現
const fadeIn = interpolate(frame, [0, fps], [0, 1], {
extrapolateRight: "clamp",
});
const fadeOut = interpolate(
frame,
[durationInFrames - 2 * fps, durationInFrames],
[1, 0],
{ extrapolateLeft: "clamp" },
);
const volume = fadeIn * fadeOut;
// グラフ描画用のポイント
const graphWidth = 800;
const graphHeight = 200;
return (
<AbsoluteFill style={{ backgroundColor: "#0f172a" }}>
{/* 実際の Audio コンポーネント */}
<Audio
src={staticFile(audioPath)}
volume={(f) => {
const fi = interpolate(f, [0, fps], [0, 1], {
extrapolateRight: "clamp",
});
const fo = interpolate(
f,
[durationInFrames - 2 * fps, durationInFrames],
[1, 0],
{ extrapolateLeft: "clamp" },
);
return fi * fo;
}}
/>
{/* タイトル */}
<div
style={{
position: "absolute",
top: 60,
width: "100%",
textAlign: "center",
fontSize: 56,
fontWeight: "bold",
color: "white",
fontFamily: "sans-serif",
}}
>
Audio + volume callback
</div>
{/* volume カーブのグラフ */}
<AbsoluteFill
style={{ justifyContent: "center", alignItems: "center" }}
>
<div style={{ position: "relative", width: graphWidth, height: graphHeight + 60 }}>
{/* グラフ背景 */}
<div
style={{
width: graphWidth,
height: graphHeight,
backgroundColor: "rgba(255,255,255,0.05)",
borderRadius: 8,
border: "1px solid rgba(255,255,255,0.1)",
position: "relative",
overflow: "hidden",
}}
>
{/* 現在位置インジケータ */}
<div
style={{
position: "absolute",
left: (frame / durationInFrames) * graphWidth,
top: 0,
width: 2,
height: graphHeight,
backgroundColor: "#f43f5e",
}}
/>
{/* volume バー */}
<div
style={{
position: "absolute",
bottom: 0,
left: (frame / durationInFrames) * graphWidth - 20,
width: 40,
height: volume * graphHeight,
backgroundColor: "#38bdf8",
borderRadius: "4px 4px 0 0",
opacity: 0.8,
}}
/>
</div>
{/* ラベル */}
<div
style={{
display: "flex",
justifyContent: "space-between",
marginTop: 8,
color: "#64748b",
fontSize: 18,
fontFamily: "monospace",
}}
>
<span>frame 0</span>
<span>fadeIn ↑</span>
<span>volume = 1.0</span>
<span>↓ fadeOut</span>
<span>frame {durationInFrames}</span>
</div>
</div>
</AbsoluteFill>
{/* 現在の volume 値 */}
<div
style={{
position: "absolute",
bottom: 100,
width: "100%",
textAlign: "center",
fontSize: 36,
fontFamily: "monospace",
color: "#38bdf8",
}}
>
volume: {volume.toFixed(2)} = Math.min(fadeIn: {fadeIn.toFixed(2)},
fadeOut: {fadeOut.toFixed(2)}) × fadeOut
</div>
{/* コード表示 */}
<div
style={{
position: "absolute",
bottom: 40,
width: "100%",
textAlign: "center",
fontSize: 22,
fontFamily: "monospace",
color: "#4ade80",
}}
>
{"volume={(f) => interpolate(f, ...) * interpolate(f, ...)}"}
</div>
</AbsoluteFill>
);
};
// --- メインコンポーネント ---
export const AssetsDemo: React.FC<z.infer<typeof assetsDemoSchema>> = ({
imagePath,
videoPath,
audioPath,
}) => {
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={100}>
<ImageShowcase imagePath={imagePath} />
</Sequence>
<Sequence from={100} durationInFrames={100}>
<VideoShowcase videoPath={videoPath} />
</Sequence>
<Sequence from={200} durationInFrames={100}>
<AudioShowcase audioPath={audioPath} />
</Sequence>
</AbsoluteFill>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment