> ## Documentation Index
> Fetch the complete documentation index at: https://docs.microsandbox.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Transcode media with FFmpeg

> Prepare FFmpeg once and process untrusted media in an offline worker

<Tooltip tip="This workflow prepares and restores a local disk snapshot, which is not available on microsandbox cloud."><span className="msb-badge-local">Local-only <Icon icon="circle-info" size={11} /></span></Tooltip>

Media parsers are a natural fit for disposable microVMs. This example snapshots a prepared FFmpeg toolchain, copies one input file into a networkless worker, and exports only the transcoded result.

## Transcode media

<Steps>
  <Step title="Prepare FFmpeg">
    Create one script to prepare the reusable toolchain:

    ```sh prepare-ffmpeg.sh theme={null}
    #!/bin/sh
    set -eu

    apt-get update
    DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends ffmpeg
    rm -rf /var/lib/apt/lists/*
    useradd --system --create-home --shell /usr/sbin/nologin media
    mkdir -p /input /out
    chown media:media /out
    chmod 0555 /input
    chmod 0700 /out
    ffmpeg -version | head -1
    ```

    Create the workload script that each worker will run:

    ```sh transcode-video.sh theme={null}
    #!/bin/sh
    set -eu

    test "$(id -u)" -ne 0

    if (printf x >> /input/input.mov) 2>/dev/null; then
      echo "input unexpectedly writable" >&2
      exit 1
    fi

    if getent ahosts example.com >/dev/null 2>&1; then
      echo "network unexpectedly reachable" >&2
      exit 1
    fi

    echo "input read-only; network blocked"
    ffmpeg -nostdin -hide_banner -y \
      -i /input/input.mov \
      -c:v libx264 -preset medium -crf 23 \
      -c:a aac -b:a 128k \
      /out/output.mp4
    ffprobe -v error \
      -show_entries format=duration,size \
      -of default=noprint_wrappers=1 \
      /out/output.mp4
    ```

    Install both scripts in the guest, then run the preparation script as the entrypoint:

    <CodeGroup>
      ```sh macOS & Linux theme={null}
      msb run --name ffmpeg-base --replace \
        --cpus 2 --memory 2G --root-disk 4G \
        --script-path prepare-ffmpeg:./prepare-ffmpeg.sh \
        --script-path transcode-video:./transcode-video.sh \
        --entrypoint prepare-ffmpeg \
        debian:bookworm-slim
      ```

      ```powershell Windows theme={null}
      msb run --name ffmpeg-base --replace `
        --cpus 2 --memory 2G --root-disk 4G `
        --script-path prepare-ffmpeg:./prepare-ffmpeg.sh `
        --script-path transcode-video:./transcode-video.sh `
        --entrypoint prepare-ffmpeg `
        debian:bookworm-slim
      ```
    </CodeGroup>

    Capture the prepared toolchain:

    ```sh theme={null}
    msb snapshot create ffmpeg-tools --from ffmpeg-base --integrity --force
    ```

    Verify the snapshot before using it:

    ```sh theme={null}
    msb snapshot verify ffmpeg-tools
    ```
  </Step>

  <Step title="Start an offline worker">
    <CodeGroup>
      ```sh macOS & Linux theme={null}
      msb run -d --name ffmpeg-worker --replace \
        --from-snapshot ffmpeg-tools \
        --cpus 2 --memory 2G \
        --user media --security restricted \
        --no-net --max-duration 10m \
        -- sh -lc 'exec sleep 10m'
      ```

      ```powershell Windows theme={null}
      msb run -d --name ffmpeg-worker --replace `
        --from-snapshot ffmpeg-tools `
        --cpus 2 --memory 2G `
        --user media --security restricted `
        --no-net --max-duration 10m `
        -- sh -lc 'exec sleep 10m'
      ```
    </CodeGroup>

    Copy the input into the running worker:

    ```sh theme={null}
    msb cp ./input.mov ffmpeg-worker:/input/input.mov
    ```

    Make the copied input read-only:

    ```sh theme={null}
    msb exec --user root ffmpeg-worker -- chmod 0444 /input/input.mov
    ```
  </Step>

  <Step title="Transcode the file">
    Run the bounded transcode as the unprivileged `media` user:

    <CodeGroup>
      ```sh macOS & Linux theme={null}
      msb exec --user media --timeout 9m \
        --rlimit nproc=64 --rlimit nofile=256 --rlimit fsize=1073741824 \
        ffmpeg-worker -- transcode-video
      ```

      ```powershell Windows theme={null}
      msb exec --user media --timeout 9m `
        --rlimit nproc=64 --rlimit nofile=256 --rlimit fsize=1073741824 `
        ffmpeg-worker -- transcode-video
      ```
    </CodeGroup>

    The worker boots before the input is copied because rootfs patches cannot be combined with `--from-snapshot`. The root-owned input directory prevents the unprivileged media process from replacing the copied file, while `/out` is the only workload-owned data directory. The untrusted transcode command sets its own process, file-descriptor, and per-file limits.
  </Step>

  <Step title="Export the result">
    Prepare a fresh artifact directory on the host:

    <CodeGroup>
      ```sh macOS & Linux theme={null}
      export FFMPEG_ARTIFACT_DIR="${FFMPEG_ARTIFACT_DIR:-$PWD/.ffmpeg-artifacts}"

      if [ -e "$FFMPEG_ARTIFACT_DIR" ] || [ -L "$FFMPEG_ARTIFACT_DIR" ]; then
        echo "artifact path already exists: $FFMPEG_ARTIFACT_DIR" >&2
        exit 1
      fi

      mkdir -m 700 "$FFMPEG_ARTIFACT_DIR"
      ```

      ```powershell Windows theme={null}
      if (-not $env:FFMPEG_ARTIFACT_DIR) {
        $env:FFMPEG_ARTIFACT_DIR = Join-Path $PWD '.ffmpeg-artifacts'
      }

      if (Test-Path -LiteralPath $env:FFMPEG_ARTIFACT_DIR) {
        throw "artifact path already exists: $env:FFMPEG_ARTIFACT_DIR"
      }

      New-Item -ItemType Directory -Path $env:FFMPEG_ARTIFACT_DIR | Out-Null
      ```
    </CodeGroup>

    Stop the worker before exporting its output:

    ```sh theme={null}
    msb stop ffmpeg-worker
    ```

    Copy the result to the host:

    <CodeGroup>
      ```sh macOS & Linux theme={null}
      msb cp ffmpeg-worker:/out/output.mp4 "$FFMPEG_ARTIFACT_DIR/output.mp4"
      ```

      ```powershell Windows theme={null}
      $outputPath = Join-Path $env:FFMPEG_ARTIFACT_DIR 'output.mp4'
      msb cp ffmpeg-worker:/out/output.mp4 $outputPath
      ```
    </CodeGroup>

    Validate the exported file:

    <CodeGroup>
      ```sh macOS & Linux theme={null}
      test ! -L "$FFMPEG_ARTIFACT_DIR/output.mp4" &&
        test -f "$FFMPEG_ARTIFACT_DIR/output.mp4" &&
        test -s "$FFMPEG_ARTIFACT_DIR/output.mp4" &&
        test "$(wc -c < "$FFMPEG_ARTIFACT_DIR/output.mp4")" -le 1073741824
      ```

      ```powershell Windows theme={null}
      $output = Get-Item -LiteralPath $outputPath
      $isLink = ($output.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0
      if ($output.PSIsContainer -or $isLink -or $output.Length -eq 0 -or $output.Length -gt 1GB) {
        throw 'output.mp4 failed artifact validation'
      }
      ```
    </CodeGroup>

    Stopping the worker before export removes any process that could race the artifact path. The fresh private directory, regular-file check, and 1 GiB host-side size check keep the exported file within the transcode command's per-file limit.

    The laboratory smoke test generated a two-second H.264 video and `ffprobe` verified its duration and size.
  </Step>

  <Step title="Clean up">
    Remove the prepared sandbox and worker:

    <CodeGroup>
      ```sh macOS & Linux theme={null}
      msb rm -f ffmpeg-base ffmpeg-worker
      rm -f prepare-ffmpeg.sh transcode-video.sh
      ```

      ```powershell Windows theme={null}
      msb rm -f ffmpeg-base ffmpeg-worker
      Remove-Item prepare-ffmpeg.sh, transcode-video.sh
      ```
    </CodeGroup>

    Remove the reusable toolchain snapshot:

    ```sh theme={null}
    msb snapshot rm ffmpeg-tools
    ```

    The checked output remains in the artifact directory configured above until you remove it.
  </Step>
</Steps>

## Bound the workload

* Validate the input size before creating the sandbox.
* Set `--max-duration` according to the longest media you accept.
* Limit concurrent workers at the application layer so users cannot exhaust host CPU.
* Keep `--no-net` unless the worker must retrieve remote media; copy or stream validated input through a controlled host service instead.
* Export only checked regular files from a stopped worker rather than the worker's entire filesystem.
