🚧 The Shelby Explorer is currently in beta and is under active development

Shelby SymbolMedia Kit

Preparing Media

Transcode video for optimal streaming with the Media Prepare SDK


Introduction

Transcode your video into a streaming-friendly format before uploading to Shelby. The Media Prepare SDK handles adaptive bitrate encoding, segmentation, and manifest generation.

Workflow diagram showing Install SDK, Create Plan, Execute, and Output Files steps
Install SDK
Create Plan
Execute
Output Files

Why Prepare Video?

Raw video files aren't optimized for streaming. Transcoding creates:

  • Multiple quality levels - 1080p, 720p, 480p variants for adaptive bitrate
  • Segmented files - Small chunks that load progressively
  • Manifest files - HLS playlists that index everything for players
input.mp4Source VideoFFmpeg1080p5 Mbps · 1920×1080720p3 Mbps · 1280×720480p1.2 Mbps · 854×480InputTranscodeAdaptive Bitrate Output

Prerequisites

FFmpeg Installation

Install FFmpeg 7.0 or later:

# macOS
brew install ffmpeg

# Ubuntu/Debian
sudo apt install ffmpeg

# Windows (via Chocolatey)
choco install ffmpeg

Verify your installation:

ffmpeg -version
# Should show version 7.0 or higher

FFmpeg 7.0+ is required for CMAF output. The SDK will validate your installation automatically.

Transcoding Workflow

Install the SDK

npm install @shelby-protocol/media-prepare

Create a transcoding plan

Use CmafPlanBuilder to configure your transcoding settings:

transcode.ts
import {
  CmafPlanBuilder,
  videoLadderPresets,
} from "@shelby-protocol/media-prepare/core";

const plan = new CmafPlanBuilder()
  .withInput("input.mp4")
  .withOutputDir("output")
  .withVideoLadder(videoLadderPresets.vodHd_1080p)
  .withVideoCodec({ kind: "x264", preset: "medium" })
  .addAudioTrack({
    language: "eng",
    bitrateBps: 128_000,
    default: true,
  })
  .withSegmentDuration(4)
  .withHlsOutput()
  .build();

The vodHd_1080p preset creates three quality rungs:

RungResolutionBitrate
1080p1920x10805 Mbps
720p1280x7203 Mbps
480p854x4801.2 Mbps

Execute the plan

transcode.ts
import { NodeCmafPlanExecutor } from "@shelby-protocol/media-prepare/node";

const executor = new NodeCmafPlanExecutor();
await executor.execute(plan);

console.log("Transcoding complete!");

Check the output

After transcoding completes, you'll have a directory ready for upload:

output/master.m3u8← Main1080p/playlist.m3u8, segment-*.m4s720p/playlist.m3u8, segment-*.m4saudio-eng/playlist.m3u8, segment-*.m4s

The master.m3u8 file is the entry point that players use to select the appropriate quality level based on network conditions.

Configuration Options

Video Codecs

Choose the codec that fits your needs:

// H.264 - Maximum compatibility
builder.withVideoCodec({ kind: "x264", preset: "medium", profile: "high" });

// H.265/HEVC - Better compression, slower encoding
builder.withVideoCodec({ kind: "x265", preset: "medium", profile: "main" });

// Copy - No re-encoding (use when source is already optimized)
builder.withVideoCodec({ kind: "copy" });

Encoding Presets

Balance speed vs quality with presets (fastest to slowest):

ultrafastsuperfastveryfastfasterfastmediumslowslowerveryslow

Use medium for a good balance. Use slow or slower for final production encodes when time isn't critical.

Custom Bitrate Ladders

Build ladders for specific requirements:

const customLadder = [
  { width: 1920, height: 1080, bitrateBps: 6_000_000, name: "1080p" },
  { width: 1280, height: 720, bitrateBps: 3_000_000, name: "720p" },
  { width: 854, height: 480, bitrateBps: 1_500_000, name: "480p" },
  { width: 640, height: 360, bitrateBps: 800_000, name: "360p" },
];

builder.withVideoLadder(customLadder);

Executor Options

Configure the executor for your environment:

const executor = new NodeCmafPlanExecutor({
  ffprobe: true, // Auto-detect source properties (default: true)
  shaka: true, // Use Shaka Packager for DASH + DRM (default: true)
  verbose: true, // Stream FFmpeg logs to stdout
});

Complete Example

transcode.ts
import * as fs from "node:fs/promises";
import {
  CmafPlanBuilder,
  videoLadderPresets,
} from "@shelby-protocol/media-prepare/core";
import { NodeCmafPlanExecutor } from "@shelby-protocol/media-prepare/node";

async function transcodeVideo(inputPath: string, outputDir: string) {
  // Clean output directory
  await fs.rm(outputDir, { recursive: true, force: true });

  // Build plan
  const plan = new CmafPlanBuilder()
    .withInput(inputPath)
    .withOutputDir(outputDir)
    .withVideoLadder(videoLadderPresets.vodHd_1080p)
    .withVideoCodec({ kind: "x264", preset: "medium", profile: "high" })
    .addAudioTrack({
      language: "eng",
      bitrateBps: 128_000,
      default: true,
    })
    .withSegmentDuration(4)
    .withHlsOutput()
    .force()
    .build();

  // Execute
  const executor = new NodeCmafPlanExecutor({
    ffprobe: true,
    verbose: true,
  });

  console.log("Starting transcoding...");
  await executor.execute(plan);
  console.log("Transcoding complete!");

  // List output files
  const files = await fs.readdir(outputDir, { recursive: true });
  console.log("Output files:", files);
}

transcodeVideo("input.mp4", "output");

Next Steps


On this page