Search Docs
Loading...
Skip to content

To MP4

Export your video compositions as MP4 files with H.264 encoding, progress events, and configurable quality and resolution.

5 mins
estimated time
GitHub

MP4 is the most widely supported video format, using H.264 encoding for efficient compression. CE.SDK renders frames, encodes them with H.264, and muxes audio into an MP4 container. The export runs on the engine’s background worker so the main thread stays responsive.

This guide covers exporting a page to MP4, observing progress, cancelling an in-flight export, configuring resolution and quality, and exporting a partial timeline range.

Export to MP4#

Call engine.block.exportVideo(_:mimeType:options:) with a page block to export it as an MP4 video. The call returns an AsyncThrowingStream<VideoExport, Error> that yields .progress events while encoding and a final .finished(video:) event that carries the encoded Blob.

let videoStream = try await engine.block.exportVideo(page, mimeType: .mp4)
for try await event in videoStream {
if case let .finished(video: blob) = event {
try blob.write(to: exportsDirectory.appendingPathComponent("video.mp4"))
}
}

MIMEType.mp4 is the default — pass it explicitly to make the output format obvious to readers of the call site. Once the stream finishes, write the resulting Blob to disk with Blob.write(to:).

Tracking Export Progress#

.progress events arrive throughout the export with three counters: rendered frames (frames pulled from the scene), encoded frames (frames already pushed through the H.264 encoder), and the total frame count. The encoded count is the most useful signal for a user-facing progress bar because it tracks the slower stage.

let progressStream = try await engine.block.exportVideo(page, mimeType: .mp4)
for try await event in progressStream {
switch event {
case let .progress(rendered, encoded, total):
let percent = total > 0 ? Int(Double(encoded) / Double(total) * 100) : 0
print("Export \(percent)% — encoded \(encoded)/\(total) (rendered \(rendered))")
case let .finished(video: blob):
try blob.write(to: exportsDirectory.appendingPathComponent("progress.mp4"))
}
}

Cancelling an Export#

Wrap the export in a Task so a holder can call cancel() to abort it. Each loop iteration calls try Task.checkCancellation() first, so the loop exits as soon as cancellation is requested and the engine tears down the worker.

let exportTask = Task { () -> Blob in
let stream = try await engine.block.exportVideo(page, mimeType: .mp4)
for try await event in stream {
try Task.checkCancellation()
if case let .finished(video: blob) = event {
return blob
}
}
throw CancellationError()
}
// Call exportTask.cancel() from another task to abort the export.
let exportedBlob = try await exportTask.value
try exportedBlob.write(to: exportsDirectory.appendingPathComponent("cancellable.mp4"))

Hold a reference to the Task value and call cancel() from your UI (for example, when a user taps a Cancel button). The try Task.checkCancellation() call surfaces a CancellationError through exportTask.value instead of returning a partial result.

Configure Video Encoding#

Pass a VideoExportOptions value to control quality, file size, and device compatibility.

Resolution and Framerate#

Set targetWidth, targetHeight, and framerate to control output dimensions and smoothness.

let resolutionOptions = VideoExportOptions(
framerate: 30,
targetWidth: 1920,
targetHeight: 1080,
)
let resolutionStream = try await engine.block.exportVideo(page, mimeType: .mp4, options: resolutionOptions)
for try await event in resolutionStream {
if case let .finished(video: blob) = event {
try blob.write(to: exportsDirectory.appendingPathComponent("video-1080p.mp4"))
}
}

If only one of targetWidth or targetHeight is non-zero, the other is computed to preserve the source aspect ratio. The default framerate is 30 Hz.

H.264 Profile and Quality#

The h264Profile option controls encoding quality and device compatibility:

  • .baseline (66): Maximum compatibility, lower compression.
  • .main (77): Balanced quality and compatibility (default).
  • .extended (88): Extended feature set, less common.
  • .high (100): Best compression, supported on all modern iOS devices.

h264Level accepts the level multiplied by ten — pass 52 for level 5.2. videoBitrate accepts bits per second; 0 (the default) lets the engine pick a value based on resolution and profile.

let qualityOptions = VideoExportOptions(
h264Profile: .high,
h264Level: 52,
videoBitrate: 8_000_000,
)
let qualityStream = try await engine.block.exportVideo(page, mimeType: .mp4, options: qualityOptions)
for try await event in qualityStream {
if case let .finished(video: blob) = event {
try blob.write(to: exportsDirectory.appendingPathComponent("video-high.mp4"))
}
}

The example above sets videoBitrate: 8_000_000 (8 Mbps), a reasonable target for high-quality 1080p H.264 footage. As a rough guide, scale the bitrate with the pixel count and motion complexity: ~5 Mbps for 720p, 8–12 Mbps for 1080p, and 20–40 Mbps for 4K.

Export a Partial Timeline#

Use timeOffset and duration to export a specific segment without modifying the scene. Both values are in seconds, relative to the page’s timeline.

let partialOptions = VideoExportOptions(
timeOffset: 1,
duration: 2,
)
let partialStream = try await engine.block.exportVideo(page, mimeType: .mp4, options: partialOptions)
for try await event in partialStream {
if case let .finished(video: blob) = event {
try blob.write(to: exportsDirectory.appendingPathComponent("video-clip.mp4"))
}
}

A duration of 0 defaults to the full duration of the exported page (the engine does not subtract timeOffset).

All MP4 Export Options#

OptionTypeDefaultDescription
h264ProfileH264Profile.mainEncoder profile: .baseline (66), .main (77), .extended (88), .high (100).
h264LevelInt3252Encoding level (multiply desired level by 10, e.g., 52 for level 5.2).
videoBitrateInt320 (auto)Video bitrate in bits/second. Maximum determined by profile and level.
audioBitrateInt320 (auto)Audio bitrate in bits/second. 0 defaults to 128 kbps for stereo AAC.
framerateFloat30Target framerate in Hz.
targetWidthFloat0Output width in pixels. 0 keeps the page’s natural width.
targetHeightFloat0Output height in pixels. 0 keeps the page’s natural height.
timeOffsetDouble0Start time offset in seconds.
durationDoublepage lengthVideo duration in seconds. 0 defaults to the duration of the exported page.
allowTextOverhangBoolfalseInclude text bounding boxes that account for glyph overhangs.

API Reference#

MethodDescription
engine.block.exportVideo(_:mimeType:options:)Export a page block as MP4 video. Returns an AsyncThrowingStream<VideoExport, Error>.

Next Steps#