Skip to content

video/transcode@1

One video file in, one 8-bit 4:2:0 H.265 (the default) or AV1 MP4 out, encoded by ffmpeg with libx265 or SVT-AV1 on 4 CPUs.

One video file that ffmpeg can read (JPEG XL excepted), of up to 60 s and 1,800 frames (60 s at 30 fps, or 30 s at 60 fps), with frames from 64x64 to 1280x720 in either orientation. The first video stream that is not a cover picture and the first audio stream are encoded; other streams are dropped. The worker reads the file before encoding it: frames are counted from the packets rather than taken from the header, and a phone’s portrait clip is measured as it is shown. An input over a bound fails with the reason; it is never cut.

No option makes a unit cost more than the default, which is what the price is measured on: a preset is the default or a faster one, a crf the default or a higher one, and height only scales down.

Field Required Meaning
url yes an http or https address the worker downloads the video from, or the key of a file your org uploaded (unitfabric upload, or POST /v1/uploads with "kind": "input"), which the node fetches before the worker begins the unit, so the worker reads it as a file under /in; anything else is refused when you submit
codec no h265 (the default) or av1
preset no for h265: medium (the default), fast, faster, veryfast, superfast or ultrafast; for av1: 8 (the default) up to 13
crf no for h265: 28 (the default) to 51; for av1: 35 (the default) to 63
height no scale down to this even height, from 64 up to the input’s own height and at most 720, keeping the aspect ratio and a width of at least 64
unit_id no your name for the unit: 1 to 64 letters, digits, ., _ or -, starting with a letter or digit; its results are stored under it. By default units are numbered by line: 000000, 000001, …

clips.jsonl:

{"url": "https://example.com/clips/take-01.mov"}
{"url": "https://example.com/clips/take-02.mov", "codec": "av1"}
{"unit_id": "teaser", "url": "https://example.com/clips/teaser.mp4", "height": 480, "preset": "fast"}

job.yaml:

name: october clips
template: video/transcode@1
input: clips.jsonl

output.mp4 for each unit: the video in the chosen codec (H.265 tagged hvc1, which Apple players expect), every source frame once, and the audio as 128 kb/s AAC when the input has audio. Each unit’s directory also holds done.json: {"unit_id": ..., "artifacts": ["output.mp4"], "seconds": ...}. A unit that failed carries its reason (unitfabric units <job id> --status failed).

  • 4 CPUs, 4 GB of memory and 20 GB of scratch disk; no GPU needed.
  • 600 s per unit. A unit still running then is stopped and tried again, up to the job’s attempts (unit.max_attempts: 3 by default, 1 to 10), and a unit that never completes is not billed. An input over a bound fails each attempt the same way.
  • A URL the worker downloads is at most 4 GiB and must come within 300 s (half the unit’s time), with no 60 s stretch without data, through our gateway, which allows ports 80 and 443 only. An uploaded file is fetched by the node itself before the unit begins, not through the gateway, within 600 s that are not the unit’s, and is held to the same 4 GiB.
  • Checkpointing is off: a unit cut short by a node failure starts again from the beginning, and is billed once.
  • A manifest line is at most 65,536 characters and a manifest at most 32 MiB.

Price per unit: see pricing.

The price is measured on a full-size unit: the first 60 s (1,800 frames of 1280x720 at 30 fps) of an action-camera clip of a paragliding take-off, by Joxemai on Wikimedia Commons (CC BY-SA 4.0), re-encoded to H.264 and AAC the way uploads usually arrive. It sits at the costly end of real content: six other 60 s cuts made the same way (three of an animated film, three of spacecraft cabin footage) took 0.7 to 1.1 times as long. On 4 CPUs of an Intel Core i9-14900K a bench unit took 19.7 to 27.7 s in H.265 and 22.0 to 29.5 s in AV1 (the high ends while the host was busy with other work), 125 to 163 units an hour for the even mix of the two that the benchmark runs, under a twentieth of the time limit.