remotion-dev/remotion · error

The time stretcher has already been finalized.

Error message

The time stretcher has already been finalized.

What it means

finalize() on StreamingPitchShifter flushes remaining audio and marks the instance finished. Calling finalize twice would produce duplicated tail padding/invalid state, so the second call throws this error.

Source

Thrown at packages/media/src/audio/pitch-shift.ts:323

		this.outputLength -= finalizedLength;
		this.synthesisPosition -= finalizedLength;

		const inputFramesToDiscard = Math.max(
			0,
			Math.floor(this.analysisPosition) - this.searchRadius,
		);
		for (let channel = 0; channel < this.numberOfChannels; channel++) {
			this.input[channel].copyWithin(0, inputFramesToDiscard, this.inputLength);
		}

		this.inputLength -= inputFramesToDiscard;
		this.analysisPosition -= inputFramesToDiscard;
		return result;
	}

	public finalize() {
		if (this.finalized) {
			throw new Error('The time stretcher has already been finalized.');
		}

		this.finalized = true;
		const targetLength = Math.round(this.totalInputFrames * this.factor);
		const padding = makePlanarAudio(
			this.numberOfChannels,
			this.windowSize + this.searchRadius * 2,
		);
		this.input = ensurePlanarCapacity({
			buffers: this.input,
			requiredLength: this.inputLength + padding[0].length,
		});
		for (let channel = 0; channel < this.numberOfChannels; channel++) {
			this.input[channel].set(padding[channel], this.inputLength);
		}

		this.inputLength += padding[0].length;
		this.process();

View on GitHub (pinned to a6a7485a9a)

Solutions

  1. Call finalize exactly once per StreamingPitchShifter instance; guard with a boolean flag in caller code
  2. Remove duplicate flush calls (e.g. from both catch/finally and normal completion)
  3. On retry, create a new shifter instead of re-finalizing the old one

Example fix

// before
shifter.finalize(); cleanup(); // cleanup also calls shifter.finalize()
// after
shifter.finalize(); cleanup(); // cleanup: if (!finalized) shifter.finalize();
Defensive patterns

Strategy: validation

Validate before calling

let finalized = false;
function finalizeOnce(s: StreamingPitchShifter) {
  if (!finalized) { s.finalize(); finalized = true; }
}

Type guard

const canFinalize = (s: StreamingPitchShifter): boolean => !s.finalized;

Try / catch

try {
  shifter.finalize();
} catch (e) {
  if (e instanceof Error && e.message.includes('already been finalized')) {
    // already flushed — safe to ignore
  } else throw e;
}

Prevention

When it happens

Trigger: Calling shifter.finalize() more than once on the same instance — e.g. flush() invoked on both segment end and stream end, or finalize in both an error-cleanup path and the normal completion path.

Common situations: Double flush in streaming pipelines (error handler + finally block); reusing an iterator's internal shifter after segment boundary flush; retry logic calling finalize again after a partial failure.

Related errors


AI-assisted analysis of remotion-dev/remotion@a6a7485a9a (2026-09-02). Data as JSON: /api/errors/d7e35b3b392a4a30. Report an issue: GitHub.