Node stream.pipeline to save a Sume MP4 and catch truncation

Stream a finished Sume video to disk with stream.pipeline, then compare bytes written with content-length so a cut-off MP4 never reaches your users.

4 min readSume
All posts

Do not buffer a 30-second video

A completed job from POST /v1/videos is fetched with GET /v1/videos/{id}/content?index=0. Reading that response into memory with res.arrayBuffer() works for a short clip and becomes a problem for longer or higher-resolution output, and Seedance 2.5 and Wan 3.0 reach 30 seconds.

Node's stream.pipeline connects the response body to a file and handles errors and cleanup of the streams in one call, so a failure mid-transfer rejects instead of leaving a dangling handle.

The script

Run with VIDEO_ID and SUME_API_KEY set, on Node 18 or newer, saved as dl.mjs. After the pipeline finishes it compares the file size to the content-length header when the server sent one.

import { createWriteStream } from "node:fs";
import { stat, rm } from "node:fs/promises";
import { Readable } from "node:stream";
import { pipeline } from "node:stream/promises";

const url = "https://api.sume.com/v1/videos/" + process.env.VIDEO_ID + "/content?index=0";
const res = await fetch(url, {
  headers: { Authorization: "Bearer " + process.env.SUME_API_KEY },
});
if (!res.ok) throw new Error("download failed: HTTP " + res.status);

await pipeline(Readable.fromWeb(res.body), createWriteStream("clip.mp4"));
const expected = Number(res.headers.get("content-length") || 0);
const { size } = await stat("clip.mp4");
if (expected && size !== expected) {
  await rm("clip.mp4");
  throw new Error("truncated: " + size + " of " + expected + " bytes");
}
console.log("saved", size, "bytes");

Limits of the check

  • A chunked response has no content-length; then verify with ffprobe before you serve the file.
  • Only download after status completed; before then the route returns an error body, which the res.ok check turns into an exception.
  • On failure, retry the download, not the job: the generation already finished and was billed once.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume