Cut, join, speed up. Exact to the frame.
Make highlights from long recordings, add intros and outros, and smooth the edges of every cut.
Who uses it: Editors in the browser, highlight and clipping tools, course platforms, meeting recorders.
01
Trim and clip
Cuts a part of a video from a start time for a length you choose.
- What it is for
- Highlights, previews and the one answer a viewer needs from a long recording.
- Endpoint and fields
POST /v1/transcodetrim- In ffmpeg
-ss-t
curl "$VIDEXO_API_URL/v1/transcode" \
-H "Authorization: Bearer $VIDEXO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "https://example.com/talk.mp4",
"filename": "talk-clip.mp4",
"trim": {
"start": "00:01:30",
"duration": 30
}
}' import { Videxo } from '@videxo/sdk';
const videxo = new Videxo({ apiKey: process.env.VIDEXO_API_KEY, baseUrl: process.env.VIDEXO_API_URL });
const job = await videxo.transcode({
"input": "https://example.com/talk.mp4",
"filename": "talk-clip.mp4",
"trim": {
"start": "00:01:30",
"duration": 30
}
});
const done = await videxo.jobs.wait(job.id); Install with npm i @videxo/sdk, or send the same request with curl or JavaScript. About the SDK
const response = await fetch(`${process.env.VIDEXO_API_URL}/v1/transcode`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VIDEXO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"input": "https://example.com/talk.mp4",
"filename": "talk-clip.mp4",
"trim": {
"start": "00:01:30",
"duration": 30
}
}),
});
const job = await response.json(); ffmpeg \
-ss 90 -t 30 -i https://example.com/talk.mp4 \
-map 0:v:0 \
-vf "scale='min(trunc(iw/2)*2,min(7680,min(trunc(7680*iw/ih/2)*2,trunc(sqrt(33554432*iw/ih)/2)*2)))':-2" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p \
-map "0:a:0?" \
-c:a aac -b:a 128k -movflags +faststart \
talk-clip.mp4 Output. talk-clip.mp4: the 30 seconds that start at 1:30, re-encoded so the cut lands on the exact frame.
02
Join files
Puts videos one after another, picture and sound, into one file.
- What it is for
- Add an intro or an outro, or stitch the parts of a recording back together.
- Endpoint and fields
POST /v1/concatresize- In ffmpeg
concat
curl "$VIDEXO_API_URL/v1/concat" \
-H "Authorization: Bearer $VIDEXO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"inputs": [
"https://example.com/intro.mp4",
"https://example.com/talk.mp4"
],
"filename": "talk-with-intro.mp4",
"resize": {
"width": 1920,
"height": 1080,
"fit": "pad"
}
}' import { Videxo } from '@videxo/sdk';
const videxo = new Videxo({ apiKey: process.env.VIDEXO_API_KEY, baseUrl: process.env.VIDEXO_API_URL });
const job = await videxo.concat({
"inputs": [
"https://example.com/intro.mp4",
"https://example.com/talk.mp4"
],
"filename": "talk-with-intro.mp4",
"resize": {
"width": 1920,
"height": 1080,
"fit": "pad"
}
});
const done = await videxo.jobs.wait(job.id); Install with npm i @videxo/sdk, or send the same request with curl or JavaScript. About the SDK
const response = await fetch(`${process.env.VIDEXO_API_URL}/v1/concat`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VIDEXO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"inputs": [
"https://example.com/intro.mp4",
"https://example.com/talk.mp4"
],
"filename": "talk-with-intro.mp4",
"resize": {
"width": 1920,
"height": 1080,
"fit": "pad"
}
}),
});
const job = await response.json(); ffmpeg \
-i https://example.com/intro.mp4 \
-i https://example.com/talk.mp4 \
-filter_complex "[0:v]setpts=PTS-STARTPTS,scale=1920:1080:force_original_aspect_ratio=decrease:force_divisible_by=2,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=0x000000,setsar=1,fps=30[o0c0v];[0:a]asetpts=PTS-STARTPTS[o0c0a];[1:v]setpts=PTS-STARTPTS,scale=1920:1080:force_original_aspect_ratio=decrease:force_divisible_by=2,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=0x000000,setsar=1,fps=30[o0c1v];[1:a]asetpts=PTS-STARTPTS[o0c1a];[o0c0v][o0c0a][o0c1v][o0c1a]concat=n=2:v=1:a=1[o0cv][o0ca]" \
-map "[o0cv]" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p \
-map "[o0ca]" \
-c:a aac -b:a 128k -movflags +faststart \
talk-with-intro.mp4 Output. talk-with-intro.mp4: the intro, then the talk, as one 1920 by 1080 video at 30 frames a second with one audio track; a part of another shape gets bars.
03
Slideshows and image to video
Holds still images on screen for a time you set, with a slow zoom or pan if you like, and joins them into one video.
- What it is for
- Property tours and product videos from photos, a cover image with a podcast episode, a video from a single picture.
- Endpoint and fields
POST /v1/slideshowimagesdurationmotionresizeextra_inputsaudio- In ffmpeg
zoompantpadconcat
curl "$VIDEXO_API_URL/v1/slideshow" \
-H "Authorization: Bearer $VIDEXO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"images": [
"https://example.com/photo-1.jpg",
"https://example.com/photo-2.jpg"
],
"duration": 4,
"motion": "zoom-in",
"resize": {
"width": 1280,
"height": 720
},
"extra_inputs": {
"music": "https://example.com/music.mp3"
},
"audio": {
"input": "music"
},
"filename": "slideshow.mp4"
}' import { Videxo } from '@videxo/sdk';
const videxo = new Videxo({ apiKey: process.env.VIDEXO_API_KEY, baseUrl: process.env.VIDEXO_API_URL });
const job = await videxo.slideshow({
"images": [
"https://example.com/photo-1.jpg",
"https://example.com/photo-2.jpg"
],
"duration": 4,
"motion": "zoom-in",
"resize": {
"width": 1280,
"height": 720
},
"extra_inputs": {
"music": "https://example.com/music.mp3"
},
"audio": {
"input": "music"
},
"filename": "slideshow.mp4"
});
const done = await videxo.jobs.wait(job.id); Install with npm i @videxo/sdk, or send the same request with curl or JavaScript. About the SDK
const response = await fetch(`${process.env.VIDEXO_API_URL}/v1/slideshow`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VIDEXO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"images": [
"https://example.com/photo-1.jpg",
"https://example.com/photo-2.jpg"
],
"duration": 4,
"motion": "zoom-in",
"resize": {
"width": 1280,
"height": 720
},
"extra_inputs": {
"music": "https://example.com/music.mp3"
},
"audio": {
"input": "music"
},
"filename": "slideshow.mp4"
}),
});
const job = await response.json(); ffmpeg \
-i https://example.com/photo-1.jpg \
-i https://example.com/photo-2.jpg \
-i https://example.com/music.mp3 \
-filter_complex "[0:v]fps=30:start_time=0:round=up,trim=end_frame=1,scale=2560:1440:force_original_aspect_ratio=increase,crop=2560:1440,zoompan=z=1+0.2*on/119:x=(iw-iw/zoom)/2:y=(ih-ih/zoom)/2:d=120:s=1280x720:fps=30,setsar=1[o0c0v];[1:v]fps=30:start_time=0:round=up,trim=end_frame=1,scale=2560:1440:force_original_aspect_ratio=increase,crop=2560:1440,zoompan=z=1+0.2*on/119:x=(iw-iw/zoom)/2:y=(ih-ih/zoom)/2:d=120:s=1280x720:fps=30,setsar=1[o0c1v];[o0c0v][o0c1v]concat=n=2:v=1:a=0[o0cv]" \
-map "[o0cv]" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p \
-map 2:a:0 \
-af apad \
-c:a aac -b:a 128k -shortest -movflags +faststart \
slideshow.mp4 Output. slideshow.mp4: 8 seconds at 1280 by 720, each photo filling the frame and zooming in slowly, over the music, which ends with the picture.
04
Transitions between parts
Crosses one part of a joined video into the next with a fade, wipe, slide, circle or zoom, picture and sound together.
- What it is for
- A slideshow that flows, a clip that dissolves into the next, without an editor.
- Endpoint and fields
POST /v1/concatresize- In ffmpeg
xfadeacrossfade
curl "$VIDEXO_API_URL/v1/concat" \
-H "Authorization: Bearer $VIDEXO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"inputs": [
{
"source": "https://example.com/talk.mp4",
"start": 10,
"end": 20
},
{
"source": "https://example.com/talk.mp4",
"start": 40,
"end": 50,
"transition": {
"type": "fade",
"duration": 1
}
}
],
"resize": {
"width": 1280,
"height": 720
},
"filename": "highlights.mp4"
}' import { Videxo } from '@videxo/sdk';
const videxo = new Videxo({ apiKey: process.env.VIDEXO_API_KEY, baseUrl: process.env.VIDEXO_API_URL });
const job = await videxo.concat({
"inputs": [
{
"source": "https://example.com/talk.mp4",
"start": 10,
"end": 20
},
{
"source": "https://example.com/talk.mp4",
"start": 40,
"end": 50,
"transition": {
"type": "fade",
"duration": 1
}
}
],
"resize": {
"width": 1280,
"height": 720
},
"filename": "highlights.mp4"
});
const done = await videxo.jobs.wait(job.id); Install with npm i @videxo/sdk, or send the same request with curl or JavaScript. About the SDK
const response = await fetch(`${process.env.VIDEXO_API_URL}/v1/concat`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VIDEXO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"inputs": [
{
"source": "https://example.com/talk.mp4",
"start": 10,
"end": 20
},
{
"source": "https://example.com/talk.mp4",
"start": 40,
"end": 50,
"transition": {
"type": "fade",
"duration": 1
}
}
],
"resize": {
"width": 1280,
"height": 720
},
"filename": "highlights.mp4"
}),
});
const job = await response.json(); ffmpeg \
-ss 10 -t 10 -i https://example.com/talk.mp4 \
-ss 40 -t 10 -i https://example.com/talk.mp4 \
-filter_complex "[0:v]setpts=PTS-STARTPTS,scale=1280:720:force_original_aspect_ratio=decrease:force_divisible_by=2,pad=1280:720:(ow-iw)/2:(oh-ih)/2:color=0x000000,setsar=1,fps=30,format=yuv420p,tpad=stop=-1:stop_mode=clone,trim=end_frame=300[o0c0v];[0:a]asetpts=PTS-STARTPTS,apad=whole_dur=10,atrim=duration=10[o0c0a];[1:v]setpts=PTS-STARTPTS,scale=1280:720:force_original_aspect_ratio=decrease:force_divisible_by=2,pad=1280:720:(ow-iw)/2:(oh-ih)/2:color=0x000000,setsar=1,fps=30,format=yuv420p,tpad=stop=-1:stop_mode=clone,trim=end_frame=300[o0c1v];[1:a]asetpts=PTS-STARTPTS,apad=whole_dur=10,atrim=duration=10[o0c1a];[o0c0v][o0c1v]xfade=transition=fade:duration=1:offset=9[o0cv];[o0c0a][o0c1a]acrossfade=d=1:c1=tri:c2=tri[o0ca]" \
-map "[o0cv]" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p \
-map "[o0ca]" \
-c:a aac -b:a 128k -movflags +faststart \
highlights.mp4 Output. highlights.mp4: 19 seconds at 1280 by 720, the second part fading in over the last second of the first, picture and sound together.
05
Title cards
Adds a solid colour card with text as a part of a joined video, generated with no file to upload.
- What it is for
- An opening title, a chapter card or an end card, in the font and colours of your brand.
- Endpoint and fields
POST /v1/jobsoutputs- In ffmpeg
drawtextconcat
curl "$VIDEXO_API_URL/v1/jobs" \
-H "Authorization: Bearer $VIDEXO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"inputs": {
"talk": "https://example.com/talk.mp4"
},
"outputs": [
{
"type": "video",
"filename": "with-title.mp4",
"resize": {
"width": 1280,
"height": 720
},
"concat": [
{
"color": "#101828",
"duration": 3,
"text": [
{
"text": "Launch day",
"font": "inter-bold",
"size": 72
}
]
},
{
"input": "talk",
"start": 10,
"end": 70
}
]
}
]
}' import { Videxo } from '@videxo/sdk';
const videxo = new Videxo({ apiKey: process.env.VIDEXO_API_KEY, baseUrl: process.env.VIDEXO_API_URL });
const job = await videxo.jobs.create({
"inputs": {
"talk": "https://example.com/talk.mp4"
},
"outputs": [
{
"type": "video",
"filename": "with-title.mp4",
"resize": {
"width": 1280,
"height": 720
},
"concat": [
{
"color": "#101828",
"duration": 3,
"text": [
{
"text": "Launch day",
"font": "inter-bold",
"size": 72
}
]
},
{
"input": "talk",
"start": 10,
"end": 70
}
]
}
]
});
const done = await videxo.jobs.wait(job.id); Install with npm i @videxo/sdk, or send the same request with curl or JavaScript. About the SDK
const response = await fetch(`${process.env.VIDEXO_API_URL}/v1/jobs`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VIDEXO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"inputs": {
"talk": "https://example.com/talk.mp4"
},
"outputs": [
{
"type": "video",
"filename": "with-title.mp4",
"resize": {
"width": 1280,
"height": 720
},
"concat": [
{
"color": "#101828",
"duration": 3,
"text": [
{
"text": "Launch day",
"font": "inter-bold",
"size": 72
}
]
},
{
"input": "talk",
"start": 10,
"end": 70
}
]
}
]
}),
});
const job = await response.json(); ffmpeg \
-f lavfi -i color=c=0x101828:s=1280x720:r=30:d=3 \
-f lavfi -i anullsrc=r=48000:cl=stereo:d=3 \
-ss 10 -t 60 -i https://example.com/talk.mp4 \
-filter_complex "[0:v]trim=end_frame=90,drawtext=expansion=none:fontfile=/opt/videxo/fonts/Inter-Bold.ttf:text=Launch day:fontsize=72:fontcolor=0xFFFFFF:x=(w-tw)/2:y=(h-90)/2:y_align=font[o0c0v];[2:v]setpts=PTS-STARTPTS,scale=1280:720:force_original_aspect_ratio=decrease:force_divisible_by=2,pad=1280:720:(ow-iw)/2:(oh-ih)/2:color=0x000000,setsar=1,fps=30[o0c1v];[2:a]asetpts=PTS-STARTPTS[o0c1a];[o0c0v][1:a][o0c1v][o0c1a]concat=n=2:v=1:a=1[o0cv][o0ca]" \
-map "[o0cv]" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p \
-map "[o0ca]" \
-c:a aac -b:a 128k -movflags +faststart \
with-title.mp4 Output. with-title.mp4: 3 seconds of title, silent, then a minute of the talk with its sound, at 1280 by 720.
06
Speed up or slow down
Changes how fast a video or its sound plays, from a quarter of the speed to four times; the sound keeps its pitch.
- What it is for
- Time-lapses of long recordings, slow motion for a key moment, lectures and podcasts at 1.5 times.
- Endpoint and fields
POST /v1/transcodespeed- In ffmpeg
setptsatempo
curl "$VIDEXO_API_URL/v1/transcode" \
-H "Authorization: Bearer $VIDEXO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "https://example.com/talk.mp4",
"filename": "talk-2x.mp4",
"speed": 2
}' import { Videxo } from '@videxo/sdk';
const videxo = new Videxo({ apiKey: process.env.VIDEXO_API_KEY, baseUrl: process.env.VIDEXO_API_URL });
const job = await videxo.transcode({
"input": "https://example.com/talk.mp4",
"filename": "talk-2x.mp4",
"speed": 2
});
const done = await videxo.jobs.wait(job.id); Install with npm i @videxo/sdk, or send the same request with curl or JavaScript. About the SDK
const response = await fetch(`${process.env.VIDEXO_API_URL}/v1/transcode`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VIDEXO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"input": "https://example.com/talk.mp4",
"filename": "talk-2x.mp4",
"speed": 2
}),
});
const job = await response.json(); ffmpeg \
-i https://example.com/talk.mp4 \
-map 0:v:0 \
-vf "scale='min(trunc(iw/2)*2,min(7680,min(trunc(7680*iw/ih/2)*2,trunc(sqrt(33554432*iw/ih)/2)*2)))':-2,setpts=PTS/2" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p \
-map "0:a:0?" \
-af atempo=2 \
-c:a aac -b:a 128k -movflags +faststart \
talk-2x.mp4 Output. talk-2x.mp4: picture and sound at twice the speed, the sound at its own pitch.
07
Scene detection
Finds where the shots of a video change and lists each scene as JSON: its start, its end and how sharp the change was.
- What it is for
- Cut a long recording into shots, pick a frame per shot, jump between the parts of a video.
- Endpoint and fields
POST /v1/scenesthreshold- In ffmpeg
scdet
curl "$VIDEXO_API_URL/v1/scenes" \
-H "Authorization: Bearer $VIDEXO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "https://example.com/talk.mp4",
"filename": "scenes.json",
"threshold": 12
}' import { Videxo } from '@videxo/sdk';
const videxo = new Videxo({ apiKey: process.env.VIDEXO_API_KEY, baseUrl: process.env.VIDEXO_API_URL });
const job = await videxo.scenes({
"input": "https://example.com/talk.mp4",
"filename": "scenes.json",
"threshold": 12
});
const done = await videxo.jobs.wait(job.id); Install with npm i @videxo/sdk, or send the same request with curl or JavaScript. About the SDK
const response = await fetch(`${process.env.VIDEXO_API_URL}/v1/scenes`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VIDEXO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"input": "https://example.com/talk.mp4",
"filename": "scenes.json",
"threshold": 12
}),
});
const job = await response.json(); ffmpeg \
-i https://example.com/talk.mp4 \
-map 0:v:0 \
-vf scdet=threshold=12,settb=1/1000000,metadata=mode=print:key=lavfi.scd.score:file=scenes.scores.txt \
-f null \
- Output. scenes.json: every scene with its start and end in seconds, and the score from 0 to 100 of the change that starts it.
08
Fade in and out
Fades the picture from and to black and the sound from and to silence.
- What it is for
- Clips that start and end softly instead of cutting in mid-word.
- Endpoint and fields
POST /v1/transcodetrimfade- In ffmpeg
fadeafade
curl "$VIDEXO_API_URL/v1/transcode" \
-H "Authorization: Bearer $VIDEXO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "https://example.com/talk.mp4",
"filename": "talk-faded.mp4",
"trim": {
"start": 90,
"duration": 30
},
"fade": {
"in": 1,
"out": 1
}
}' import { Videxo } from '@videxo/sdk';
const videxo = new Videxo({ apiKey: process.env.VIDEXO_API_KEY, baseUrl: process.env.VIDEXO_API_URL });
const job = await videxo.transcode({
"input": "https://example.com/talk.mp4",
"filename": "talk-faded.mp4",
"trim": {
"start": 90,
"duration": 30
},
"fade": {
"in": 1,
"out": 1
}
});
const done = await videxo.jobs.wait(job.id); Install with npm i @videxo/sdk, or send the same request with curl or JavaScript. About the SDK
const response = await fetch(`${process.env.VIDEXO_API_URL}/v1/transcode`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VIDEXO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"input": "https://example.com/talk.mp4",
"filename": "talk-faded.mp4",
"trim": {
"start": 90,
"duration": 30
},
"fade": {
"in": 1,
"out": 1
}
}),
});
const job = await response.json(); ffmpeg \
-ss 90 -t 30 -i https://example.com/talk.mp4 \
-map 0:v:0 \
-vf "scale='min(trunc(iw/2)*2,min(7680,min(trunc(7680*iw/ih/2)*2,trunc(sqrt(33554432*iw/ih)/2)*2)))':-2,fade=t=in:st=0:d=1:c=black,fade=t=out:st=29:d=1:c=black" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p \
-map "0:a:0?" \
-af afade=t=in:st=0:d=1,afade=t=out:st=29:d=1 \
-c:a aac -b:a 128k -movflags +faststart \
talk-faded.mp4 Output. talk-faded.mp4: a 30 second clip that fades in over its first second and out over its last.
Limits. Set by your plan.
- Joining with concat needs inputs of the same size and frame rate; scale and fps them in the same graph first.
- Re-encoding cuts on the exact frame. With -c copy a cut can only start on a keyframe.
- Inputs are public http or https URLs, or completed private uploads up to 100 MB and your plan’s input limit. Outputs have signed download links. A job names up to 16 input and 16 output files.
| Limit | Pay as you go | Starter | Pro | Scale |
|---|---|---|---|---|
| Longest job | 10 min | 10 min | 30 min | 60 min |
| Inputs per job | 1 GB | 1 GB | 5 GB | 10 GB |
| Outputs per job | 1 GB | 1 GB | 5 GB | 8 GB |
| Outputs expire after | 7 days | 7 days | 30 days | 30 days |
Pay as you go
- Longest job
- 10 min
- Inputs per job
- 1 GB
- Outputs per job
- 1 GB
- Outputs expire after
- 7 days
Starter
- Longest job
- 10 min
- Inputs per job
- 1 GB
- Outputs per job
- 1 GB
- Outputs expire after
- 7 days
Pro
- Longest job
- 30 min
- Inputs per job
- 5 GB
- Outputs per job
- 5 GB
- Outputs expire after
- 30 days
Scale
- Longest job
- 60 min
- Inputs per job
- 10 GB
- Outputs per job
- 8 GB
- Outputs expire after
- 30 days
Soon. Planned, not available yet.
Scene thumbnails and chapters Soon
A thumbnail for each scene of a video, and chapters to match.