Skip to content

<Audio> at composition root resolves to duration=Infinity in ffmpeg filter graph #4

Open

Description

@thecodacus

Summary

A bare <Audio src={...} /> placed at the root of a <Composition> (no endAt prop, no wrapping <Sequence>) causes the renderer to emit duration=Infinity into the ffmpeg atrim filter, which crashes the render.

Reproduction

// MyChapter.tsx
import { CanvasElement, Audio, staticFile } from '@rendiv/core';

export const CHAPTER_DURATION = 3814;

export const MyChapter = () => (
  <CanvasElement id="MyChapter">
    <Audio src={staticFile('audio/chapter.mp3')} />
    {/* ...other content sized to CHAPTER_DURATION... */}
  </CanvasElement>
);

// index.tsx
<Composition
  id=\"MyChapter\"
  component={MyChapter}
  durationInFrames={CHAPTER_DURATION}
  fps={30}
  width={1920}
  height={1080}
/>

Run:

rendiv render src/index.tsx MyChapter out/MyChapter.mp4

Actual

FFmpeg exited with code 234:
[Parsed_atrim_0 @ 0x...] Unable to parse option value \"Infinity\" as duration
Error applying option 'duration' to filter 'atrim': Invalid argument
Failed to set value '[1:a]atrim=start=0.000000:duration=Infinity,asetpts=PTS-STARTPTS[aout]'
  for option 'filter_complex': Invalid argument

Expected

The renderer should treat `` without an explicit `endAt` as bounded by the parent composition's `durationInFrames` — same behavior as Remotion.

Workaround

Explicitly pass `endAt`:
```tsx
<Audio src={staticFile('audio/chapter.mp3')} endAt={CHAPTER_DURATION} />
```

This works, but every chapter master in the project needed the same one-line fix, and the failure mode (`duration=Infinity` from ffmpeg) was very far from the cause.

Suggested fixes (any one would help; all three would be ideal)

  1. Default `endAt` to composition duration. When `` has no `endAt` and no wrapping ``, treat it as `endAt = compositionDurationInFrames`.

  2. Guard the filter-graph emitter. Before serializing `atrim=duration=${value}`, `assert(Number.isFinite(value))` and throw with a useful message, e.g.:

    `Audio src="..." resolved to infinite duration. Pass endAt={N} or wrap in .`

  3. Mount-time lint. Walk the composition tree once and warn for any `` / `

Environment

  • `@rendiv/core`: ^0.1.0
  • `@rendiv/cli`: ^0.1.0
  • ffmpeg: 7.0.2-static
  • OS: Debian inside Docker container (headless)

Activity

  1. elwizard33 commented on Nov 2, 2026

    @elwizard33

    The same duration=Infinity failure also happens when <Audio> is inside a <Sequence> that has from but no durationInFrames, which is a common way to place narration clips:

    const Comp = () => (
      <>
        <Fill style={{ background: '#123' }} />
        <Sequence from={15}>
          <Audio src={staticFile('a.mp3')} />
        </Sequence>
      </>
    );
    // <Composition id="Comp" component={Comp} durationInFrames={90} fps={30} width={320} height={180} />

    On master (6d6aca2), rendering with renderFrames() + stitchFramesToVideo() registers the source as { startAtFrame: 15, durationInFrames: Infinity }, and FFmpeg fails with Unable to parse option value "Infinity" as duration. This happens after every frame has already been rendered.

    The root cause is the same in both cases: SequenceContext defaults to durationInFrames: Infinity, a <Sequence> without durationInFrames keeps Infinity, and Audio, Video and OffthreadVideo register that value as is.

    I opened #25, which clamps the registered duration to compositionDuration - startAtFrame in all three components (your suggested fix 1), with tests for both the root-level case and the Sequence case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions