From an ffmpeg Command to a Playable HLS URL in One Request
Two things shipped for people who arrive with an ffmpeg command and a video URL: a converter that turns the command into a Transcodely job request in your browser and names every flag it cannot carry, and a quickstart where one request with managed: true returns a hosted, playable HLS ladder. No bucket, no origin, nothing to configure first.
Most people who evaluate a transcoding API already have a working ffmpeg command. It is in a Makefile, a Lambda, a cron job, or a shell history, and it encodes exactly what they need. The question they are actually asking is not “can this service encode video” but “what does my command look like as a request, and what do I lose in translation.”
Two things shipped in September to answer that directly.
Paste the command, get the request
transcodely.com/tools/ffmpeg-to-transcodely takes the ffmpeg command you run today and writes the equivalent CreateJobRequest as JSON, curl, JavaScript or Python. It runs entirely in your browser; no command is uploaded, logged or sent to the API.
Take a typical HLS ladder command:
ffmpeg -i https://cdn.example.com/talk.mp4 \
-c:v libx264 -crf 21 -preset medium \
-vf scale=1920:1080 -r 30 \
-f hls -hls_time 6 -hls_segment_type fmp4 -hls_playlist_type vod \
out/master.m3u8The converter reads the input URL, the codec, the CRF, the scale filter, the frame rate, and the HLS muxer options, and produces an output with type: "hls", an H.264 variant at 1080p and 30 fps with CRF 21 in its codec options, segments.duration of 6, hls.segment_format of fmp4 and hls.playlist_type of vod. -hls_time becomes a segment duration and -hls_segment_type becomes a segment format because packaging on Transcodely is done by Shaka Packager after the encode, not by ffmpeg’s muxer. It also tells you what it approximated: -preset medium has no direct equivalent, since ffmpeg’s ten preset rungs map onto three quality tiers, so the nearest tier is used and the note says so.
The more useful half of the page is the list of what it could not carry over, and why. A converter that silently drops flags would hand you a request that encodes something other than what your command encodes. This one refuses to guess:
- Muxer flags with no equivalent (
-hls_flags,-var_stream_map) are listed as unmapped rather than approximated. - Audio is not configured inline. An output carries no audio codec or bitrate field: MP4, MOV and HLS get AAC, WebM gets Opus, and anything else is set on a preset.
-andoes map, todisable_audio. - Filters beyond
scaleare refused, not guessed. Watermarks and burned-in subtitles are their own request fields with their own shapes, so a-vfchain carryingdrawtextoroverlayis reported as unmapped. - Inert fields are not written. The encoder reads
crfandprofilefrom the codec options and nothing else, so-maxrate,-bufsize,-tuneand-levelare reported as unmapped instead of being written to a field that would be ignored. Two-pass bitrate commands are the case where this matters most. - CRF has a narrower accepted band than ffmpeg. H.264 takes 15 to 35, H.265 18 to 35, VP9 15 to 50, AV1 20 to 55. A value outside the band is clamped and the change is stated. 10-bit H.264 is refused outright.
- A codec has to fit its container. VP9 in MP4, H.264 in WebM, AV1 in MOV and anything but H.264 on MPEG-TS segments are refused at job creation, so the converter refuses them on the page instead of handing you a request that cannot run.
- Every output names a resolution. There is no source-passthrough member on the resolution list, so a command that never resizes still has to pick a rung. The converter assumes 1080p and says so.
None of that is hidden in a tooltip. The page prints the limits up front, because the person evaluating the API deserves to know what the API does not do before they know what it does.
The request the converter produces is one you can send. Which brings up the other half.
One request, one playable URL
The quickstart used to begin the way most storage-first APIs do: create an origin, put credentials on it, decide on output paths, then submit a job and go and look in the bucket. That is the right shape for a production pipeline, where you want outputs in storage you own at paths you chose. It is the wrong shape for the first ten minutes.
The quickstart now begins with one request:
curl -X POST https://api.transcodely.com/transcodely.v1.JobService/Create \
-H "Authorization: Bearer $TRANSCODELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input_url": "https://www.transcodely.com/samples/transcodely-sample-30s.mp4",
"managed": true,
"outputs": [
{
"type": "hls",
"video": [
{ "codec": "h264", "resolution": "1080p", "quality": "standard" },
{ "codec": "h264", "resolution": "720p", "quality": "standard" },
{ "codec": "h264", "resolution": "480p", "quality": "standard" }
]
}
]
}'managed: true means “deliver the output to Transcodely hosting”. The outputs land in managed storage, a hosted video record is created alongside the job, and when the job reads completed each output carries a signed_url: a ready-to-play HLS manifest served over the CDN, signed and time-limited. The input points at a public 30-second sample clip we host, so the request runs before you own any storage at all. Copy, paste, play.
The first managed request on an app takes a few seconds longer than the rest, because hosting is provisioned on first use: the app’s managed storage and its CDN zone are created then. Every later request skips that step. If provisioning fails, the call returns hosting_provisioning_failed and creates nothing, so you retry rather than wonder.
The same three SDKs carry the same request, in TypeScript, Python and Go, and the quickstart shows all four side by side.
What this is not
Managed delivery is the fast path, not the only path. The reason Transcodely exists is the other one: outputs written into your own S3, GCS or R2 bucket at paths you choose, with signed URLs and your own DRM keys gating playback. The quickstart keeps that as the second block, right after the first job completes, and the storage setup guide covers origins in full. If your users upload to a bucket already, an ingest rule turns each upload into a job with no request from you at all.
And the converter is not an ffmpeg runner. Services exist that will execute an ffmpeg argument string on their machines for you, and if you want your exact command executed unchanged, they are the honest answer. What the converter gives you is the structured request that a managed pipeline can price at create, verify at completion, and report on field by field, together with an explicit account of what was lost getting there.
Try both
Paste your command at /tools/ffmpeg-to-transcodely. If it converts cleanly, put a key in the header and send it. If the converter says a flag is unmapped, the message tells you whether the API has another way to express it or whether it is a thing the API does not do, and both answers are worth having before you write any code.
Try it on your own footage
Upload a clip, pick a ladder, and read the manifest that comes back. Test encodes up to 30 seconds are free.