DocsGuides
Slideshows and image to video
Turn still images into a video, each held for a time, with a slow zoom or pan and music.
POST /v1/slideshow shows its images one after another as one video. One image makes an image to
video.
The request#
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"
}'Copy failed. The text is selected; copy it with your keyboard.
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();Copy failed. The text is selected; copy it with your keyboard.
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.mp4Copy failed. The text is selected; copy it with your keyboard.
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.
duration is how long each image stays on screen, 3 seconds unless you say otherwise, from 0.1 to 300.
motion is a slow move over that time: zoom-in, zoom-out, pan-left, pan-right, pan-up or
pan-down. An image takes a JPEG, PNG, WebP or BMP file, or the first frame of a video or GIF.
The frame is 1920 by 1080 unless resize or a preset sets another, and a picture of another shape
gets bars. With motion, or "fit": "cover", it fills the frame and its edges are cut.
Each image its own way#
An image can set its own duration and motion, and take up to 4
text layers, timed from the start of that image:
curl "$VIDEXO_API_URL/v1/slideshow" \
-H "Authorization: Bearer $VIDEXO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"images": [
{
"source": "https://example.com/cover.jpg",
"duration": 2,
"text": [
{
"text": "Spring collection",
"size": 96
}
]
},
"https://example.com/look-1.jpg",
{
"source": "https://example.com/look-2.jpg",
"motion": "pan-right"
}
],
"duration": 4,
"motion": "zoom-in",
"resize": {
"width": 1080,
"height": 1080,
"fit": "cover"
},
"filename": "spring.mp4"
}'Copy failed. The text is selected; copy it with your keyboard.
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": [
{
"source": "https://example.com/cover.jpg",
"duration": 2,
"text": [
{
"text": "Spring collection",
"size": 96
}
]
},
"https://example.com/look-1.jpg",
{
"source": "https://example.com/look-2.jpg",
"motion": "pan-right"
}
],
"duration": 4,
"motion": "zoom-in",
"resize": {
"width": 1080,
"height": 1080,
"fit": "cover"
},
"filename": "spring.mp4"
}),
});
const job = await response.json();Copy failed. The text is selected; copy it with your keyboard.
ffmpeg \
-i https://example.com/cover.jpg \
-i https://example.com/look-1.jpg \
-i https://example.com/look-2.jpg \
-filter_complex "[0:v]fps=30:start_time=0:round=up,trim=end_frame=1,scale=2160:2160:force_original_aspect_ratio=increase,crop=2160:2160,zoompan=z=1+0.2*on/59:x=(iw-iw/zoom)/2:y=(ih-ih/zoom)/2:d=60:s=1080x1080:fps=30,setsar=1,drawtext=expansion=none:fontfile=/opt/videxo/fonts/Inter-Regular.ttf:text=Spring collection:fontsize=96:fontcolor=0xFFFFFF:x=(w-tw)/2:y=(h-120)/2:y_align=font[o0c0v];[1:v]fps=30:start_time=0:round=up,trim=end_frame=1,scale=2160:2160:force_original_aspect_ratio=increase,crop=2160:2160,zoompan=z=1+0.2*on/119:x=(iw-iw/zoom)/2:y=(ih-ih/zoom)/2:d=120:s=1080x1080:fps=30,setsar=1[o0c1v];[2:v]fps=30:start_time=0:round=up,trim=end_frame=1,scale=2160:2160:force_original_aspect_ratio=increase,crop=2160:2160,zoompan=z=1.2:x=(iw-iw/zoom)*on/119:y=(ih-ih/zoom)/2:d=120:s=1080x1080:fps=30,setsar=1[o0c2v];[o0c0v][o0c1v][o0c2v]concat=n=3:v=1:a=0[o0cv]" \
-map "[o0cv]" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -an \
-movflags +faststart \
spring.mp4Copy failed. The text is selected; copy it with your keyboard.
Transitions#
transition crosses each image into the next, so "transition": { "type": "fade", "duration": 1 }
on four images of 4 seconds makes 13 seconds. An image may set its own; the first has nothing to
cross from. Join files lists the types.
Sound#
A slideshow has no sound of its own. To add music, name it in extra_inputs and take it with
audio.input. The video ends with its last image: the music is cut there, or padded with silence
when it is shorter. fade fades the picture and the music together.
Images between clips#
On POST /v1/jobs, a concat part may be an image next to parts of videos. The image is silent
while the clips keep their sound:
curl "$VIDEXO_API_URL/v1/jobs" \
-H "Authorization: Bearer $VIDEXO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"inputs": {
"logo": "https://example.com/logo.png",
"talk": "https://example.com/talk.mp4"
},
"outputs": [
{
"type": "video",
"concat": [
{
"image": "logo",
"duration": 3
},
"talk"
],
"resize": {
"width": 1920,
"height": 1080,
"fit": "pad"
}
}
]
}'Copy failed. The text is selected; copy it with your keyboard.
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": {
"logo": "https://example.com/logo.png",
"talk": "https://example.com/talk.mp4"
},
"outputs": [
{
"type": "video",
"concat": [
{
"image": "logo",
"duration": 3
},
"talk"
],
"resize": {
"width": 1920,
"height": 1080,
"fit": "pad"
}
}
]
}),
});
const job = await response.json();Copy failed. The text is selected; copy it with your keyboard.
ffmpeg \
-i https://example.com/logo.png \
-f lavfi -i anullsrc=r=48000:cl=stereo:d=3 \
-i https://example.com/talk.mp4 \
-filter_complex "[0:v]fps=30:start_time=0:round=up,trim=end_frame=1,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,tpad=stop_mode=clone:stop_duration=2.966667[o0c0v];[2: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];[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 \
main.mp4Copy failed. The text is selected; copy it with your keyboard.