比特之外

Remotion 教程 04:把音频、转场和运动模糊放进时间轴

前面几篇讲的是 Remotion 的基础:入口、帧、时间轴和动画。现在开始接触更像真实视频的部分:素材、声音、场景切换和运动质感。

这一篇聚焦三个能力:

  • Audio:把音频素材放进视频
  • TransitionSeries:让两个场景之间有转场
  • Trail:给快速运动元素加尾迹

为什么这一章重要

只会让一个方块移动,还不能算视频生成。真实视频通常至少有三类内容:

  • 画面素材:图片、视频、图形、产品卡片
  • 声音素材:口播、音乐、音效
  • 场景结构:从片头到正文,从一个镜头切到另一个镜头

Remotion 的优势是把这些内容都放进同一条 frame 时间轴。

把音频放进时间轴

音频文件应该放在 public/ 目录下,例如:

public/audio/talking-demo.wav

在组件里用 staticFile() 引用:

<Audio src={staticFile("audio/talking-demo.wav")} volume={0.25} />

volume={0.25} 表示把音量降到 25%。做课程示例或预览时,这样可以避免音频过响。

注意:音频进入时间轴后,并不会自动和字幕或画面同步。你仍然需要用 frame 或字幕时间戳来安排画面节奏。

用 Trail 做运动尾迹

快速运动的物体如果完全锐利,画面会显得很硬。@remotion/motion-blur 里的 Trail 可以复制多层延迟画面,形成尾迹。

<Trail layers={9} lagInFrames={2} trailOpacity={0.18}>
  <div style={{ left: x }} />
</Trail>

三个参数的意思是:

  • layers:尾迹层数,越多越明显
  • lagInFrames:每层尾迹延迟多少帧
  • trailOpacity:尾迹透明度

真正控制运动的仍然是 frame:

const x = interpolate(frame, [0, 60], [-140, 620], {
  extrapolateLeft: "clamp",
  extrapolateRight: "clamp",
});

Trail 只是让这个运动更有视觉质感。

用 TransitionSeries 做场景切换

TransitionSeries 用来把多个场景接起来,并在中间插入转场:

<TransitionSeries>
  <TransitionSeries.Sequence durationInFrames={82}>
    <ProductScene />
  </TransitionSeries.Sequence>
  <TransitionSeries.Transition
    presentation={slide({ direction: "from-right" })}
    timing={linearTiming({ durationInFrames: 24 })}
  />
  <TransitionSeries.Sequence durationInFrames={82}>
    <ResultScene />
  </TransitionSeries.Sequence>
</TransitionSeries>

这段代码表达的是:

  1. 第一个场景播放 82 帧。
  2. 用 24 帧做 slide 转场。
  3. 第二个场景播放 82 帧。

presentation 决定转场长什么样。 timing 决定转场持续多久。

30fps 下,24 帧大约是 0.8 秒。

完整最小示例

下面是一个两场景视频:第一个场景播放音频并展示带尾迹的运动方块,第二个场景通过 slide 转场进入。

import React from "react";
import { Trail } from "@remotion/motion-blur";
import { linearTiming, TransitionSeries } from "@remotion/transitions";
import { slide } from "@remotion/transitions/slide";
import {
  AbsoluteFill,
  Audio,
  interpolate,
  staticFile,
  useCurrentFrame,
} from "remotion";
 
const ProductScene: React.FC = () => {
  const frame = useCurrentFrame();
  const x = interpolate(frame, [0, 60], [-140, 620], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });
 
  return (
    <AbsoluteFill style={{ backgroundColor: "#f8fafc", padding: 96 }}>
      <Audio src={staticFile("audio/talking-demo.wav")} volume={0.25} />
      <h1 style={{ margin: 0, fontSize: 72 }}>真实素材进入时间轴</h1>
 
      <div
        style={{
          marginTop: 120,
          width: 620,
          height: 340,
          borderRadius: 8,
          backgroundColor: "#dbeafe",
          position: "relative",
          overflow: "hidden",
        }}
      >
        <Trail layers={9} lagInFrames={2} trailOpacity={0.18}>
          <div
            style={{
              position: "absolute",
              top: 110,
              left: x,
              width: 120,
              height: 120,
              borderRadius: 8,
              backgroundColor: "#2563eb",
            }}
          />
        </Trail>
      </div>
    </AbsoluteFill>
  );
};
 
const ResultScene: React.FC = () => {
  return (
    <AbsoluteFill
      style={{
        backgroundColor: "#312e81",
        color: "white",
        padding: 96,
      }}
    >
      <h1 style={{ margin: 0, fontSize: 72 }}>场景切换也由帧控制</h1>
      <p style={{ marginTop: 80, fontSize: 38 }}>
        TransitionSeries 把两个场景接起来。
      </p>
    </AbsoluteFill>
  );
};
 
export const MediaDemo: React.FC = () => {
  return (
    <TransitionSeries>
      <TransitionSeries.Sequence durationInFrames={82}>
        <ProductScene />
      </TransitionSeries.Sequence>
      <TransitionSeries.Transition
        presentation={slide({ direction: "from-right" })}
        timing={linearTiming({ durationInFrames: 24 })}
      />
      <TransitionSeries.Sequence durationInFrames={82}>
        <ResultScene />
      </TransitionSeries.Sequence>
    </TransitionSeries>
  );
};

三个能力的分工

不要把 media、transitions、motion-blur 混在一起理解:

media:素材怎么进视频
transitions:场景怎么切换
motion-blur:运动怎么更自然

在真实项目里,常见组合是:

素材场景 + 转场 + 局部运动质感

你不需要给所有东西都加运动模糊,也不需要每个场景都用很夸张的转场。它们应该服务内容节奏。

示例项目位置

这篇文章对应示例项目里的:

Composition: LessonMediaTransitions
文件: src/lessons/MediaTransitions.tsx
素材: public/audio/talking-demo.wav

运行示例项目后,可以试三个改动:

  1. volume={0.25} 改成 volume={0.1}
  2. linearTiming({ durationInFrames: 24 }) 改成 12 或 36。
  3. Traillayers 从 9 改成 3,再改成 14。

下一篇继续沿着“声音和文字”往前走,讲口播字幕、词级高亮和 TTS/Whisper 数据。