Skip to content

Instantly share code, notes, and snippets.

@anguyen8
Last active November 26, 2024 16:21
Show Gist options
  • Save anguyen8/d0630b6aef6c1cd79b9a1341e88a573e to your computer and use it in GitHub Desktop.
Save anguyen8/d0630b6aef6c1cd79b9a1341e88a573e to your computer and use it in GitHub Desktop.
#!/bin/bash
# Anh Nguyen <anh.ng8@gmail.com>
# 2016-04-30
# MIT License
# This script takes in same-size images from a folder and make a crossfade video from the images using ffmpeg.
# Make sure you have ffmpeg installed before running.
# The output command looks something like the below, but for as many images as you have in the folder.
# See the answer by LordNeckbeard at:
# http://superuser.com/questions/833232/create-video-with-5-images-with-fadein-out-effect-in-ffmpeg/1071748#1071748
#
#
# ffmpeg \
# -loop 1 -t 1 -i 001.png \
# -loop 1 -t 1 -i 002.png \
# -loop 1 -t 1 -i 003.png \
# -loop 1 -t 1 -i 004.png \
# -loop 1 -t 1 -i 005.png \
# -filter_complex \
# "[1:v][0:v]blend=all_expr='A*(if(gte(T,0.5),1,T/0.5))+B*(1-(if(gte(T,0.5),1,T/0.5)))'[b1v]; \
# [2:v][1:v]blend=all_expr='A*(if(gte(T,0.5),1,T/0.5))+B*(1-(if(gte(T,0.5),1,T/0.5)))'[b2v]; \
# [3:v][2:v]blend=all_expr='A*(if(gte(T,0.5),1,T/0.5))+B*(1-(if(gte(T,0.5),1,T/0.5)))'[b3v]; \
# [4:v][3:v]blend=all_expr='A*(if(gte(T,0.5),1,T/0.5))+B*(1-(if(gte(T,0.5),1,T/0.5)))'[b4v]; \
# [0:v][b1v][1:v][b2v][2:v][b3v][3:v][b4v][4:v]concat=n=9:v=1:a=0,format=yuv420p[v]" -map "[v]" out.mp4
#----------------------------------------------------------------
# SETTINGS
input_dir="/path/to/your/folder" # Replace this by a path to your folder /path/to/your/folder
n_files=10 # Replace this by a number of images
files=`ls ${input_dir}/*.jpg | head -${n_files}` # Change the file type to the correct type of your images
output_file="video.mp4" # Name of output video
crossfade=0.9 # Crossfade duration between two images
#----------------------------------------------------------------
# Making an ffmpeg script...
input=""
filters=""
output="[0:v]"
i=0
for f in ${files}; do
input+=" -loop 1 -t 1 -i $f"
next=$((i+1))
if [ "${i}" -ne "$((n_files-1))" ]; then
filters+=" [${next}:v][${i}:v]blend=all_expr='A*(if(gte(T,${crossfade}),1,T/${crossfade}))+B*(1-(if(gte(T,${crossfade}),1,T/${crossfade})))'[b${next}v];"
fi
if [ "${i}" -gt "0" ]; then
output+="[b${i}v][${i}:v]"
fi
i=$((i+1))
done
output+="concat=n=$((i * 2 - 1)):v=1:a=0,format=yuv420p[v]\" -map \"[v]\" ${output_file}"
script="ffmpeg ${input} -filter_complex \"${filters} ${output}"
echo ${script}
# Run it
eval "${script}"
@ZuzooVn
Copy link

ZuzooVn commented Jul 15, 2017

@anguyen8: can you add more animation for ffmpeg slideshow?

@spiralofhope
Copy link

The URL can be shortened:

http://superuser.com/questions/833232/create-video-with-5-images-with-fadein-out-effect-in-ffmpeg/1071748#1071748
=>
http://superuser.com/questions/833232#1071748

@gcrowder1
Copy link

Thank you friends, your script successfully solved my problem. If the image size is different, an error will occur. But the problem is not big, I have processed the image to the same size. Thanks again

@shakram02
Copy link

Thank you so much, worked like a charm 🙏.
I converted it to python if anyone is interested, here

@Butateng
Copy link

Hey guys, I just wanna asked if this is feasible with ffmpeg. https://trello-attachments.s3.amazonaws.com/545a93cff0b222f456c5ad82/5d8cfc0d75e80e63dae2fe6a/5a523ed328f24b0454c5c56cd0ee68e6/23442279.mp4

I've been googling around finding on how to have smooth easing and effects on the video link above. I am really new to ffmpeg, please please yung help will be really meant a lot. Cheers!

@MrMegamind
Copy link

Guys thank you so much, can I attach bg voice or I have to attach later in an other command?

@rossomaltese
Copy link

Thanks @anguyen8 for the script, it worked like a charm!
I post a little patch I did to manage some ffmpeg parameters, remove some useless brackets and refactor the main loop a little bit; I mainly did it to better understand the ffmpeg use, so all the kudos go to you! ;-)
With this patch, the script uses all the .jpg files in the current directory.
NOTE: all the images must have the same size.

--- make_crossfade_ffmpeg_video_from_images.sh.orig	2022-10-18 11:11:33.851470982 +0200
+++ make_crossfade_ffmpeg_video_from_images.sh	2022-10-18 11:40:52.239914716 +0200
@@ -4,7 +4,7 @@
 # 2016-04-30
 # MIT License
 
-# This script takes in images from a folder and make a crossfade video from the images using ffmpeg.
+# This script takes in all .jpg from current folder and make a crossfade video from the images using ffmpeg.
 # Make sure you have ffmpeg installed before running.
 
 # The output command looks something like the below, but for as many images as you have in the folder.
@@ -27,40 +27,42 @@
 
 #----------------------------------------------------------------
 # SETTINGS
-input_dir="/path/to/your/folder"  # Replace this by a path to your folder /path/to/your/folder
-n_files=10                        # Replace this by a number of images
-files=`ls ${input_dir}/*.jpg | head -${n_files}`  # Change the file type to the correct type of your images
-output_file="video.mp4"           # Name of output video
-crossfade=0.9                     # Crossfade duration between two images
+if (($# != 3)); then
+  echo "usage: $0 time[s] crossfade[s] output"
+  exit 1
+fi
+files=(*.jpg)                     # Change the file type to the correct type of your images
+output_file="$3"                  # Name of output video
+crossfade=$2                      # Crossfade duration between two images
+time=$1
+
+if [[ -e $output_file ]]; then
+  echo "$0: file $output_file already exists." >&2
+  exit 2
+fi
 #----------------------------------------------------------------
 
 # Making an ffmpeg script...
-input=""
-filters=""
-output="[0:v]"
-
-i=0
-
-for f in ${files}; do
-  input+=" -loop 1 -t 1 -i $f"
-
-  next=$((i+1))
-  if [ "${i}" -ne "$((n_files-1))" ]; then
-    filters+=" [${next}:v][${i}:v]blend=all_expr='A*(if(gte(T,${crossfade}),1,T/${crossfade}))+B*(1-(if(gte(T,${crossfade}),1,T/${crossfade})))'[b${next}v];"
-  fi
-
-  if [ "${i}" -gt "0" ]; then
-    output+="[b${i}v][${i}:v]"
-  fi
-
-  i=$((i+1))
+input="-hide_banner"
+for ((i = 0; i < ${#files[*]}; ++i)); do
+  input+=" -loop 1 -t $time -i "\'"${files[i]}"\'
 done
 
-output+="concat=n=$((i * 2 - 1)):v=1:a=0,format=yuv420p[v]\" -map \"[v]\" ${output_file}"
+filters=`mktemp`
+expr="if(gte(T,$crossfade),1,T/$crossfade)"
+output="[0:v]"
+{
+  for ((i = 1; i < ${#files[*]}; ++i)); do
+    echo "[$i:v][$((i-1)):v]blend=all_expr='A*($expr)+B*(1-($expr))'[b${i}v];"
+    output+="[b${i}v][$i:v]"
+  done
+  echo "$output"
+  echo "concat=n=$((${#files[*]} * 2 - 1)):v=1:a=0,format=yuv420p[v]"
+} >> $filters
 
-script="ffmpeg ${input} -filter_complex \"${filters} ${output}"
+script="ffmpeg $input -filter_complex_script $filters -map '[v]' "\'"$output_file"\'
 
-echo ${script}
+echo "$script"
 
 # Run it
-eval "${script}"
\ No newline at end of file
+eval "$script"

@anguyen8
Copy link
Author

Thanks @rossomaltese for sharing with us your updated/improved version!! :)

@martin3000
Copy link

martin3000 commented Jan 27, 2023

I have 30 jpgs and the script eats up all of the 8 GB system memory and then the machine crashes.
So I do

script="systemd-run --user --scope -p MemoryMax=2G -p MemorySwapMax=2G ffmpeg  ...

@HemangiVekaria
Copy link

Hey guys, I just wanna asked if this is feasible with ffmpeg. https://trello-attachments.s3.amazonaws.com/545a93cff0b222f456c5ad82/5d8cfc0d75e80e63dae2fe6a/5a523ed328f24b0454c5c56cd0ee68e6/23442279.mp4

I've been googling around finding on how to have smooth easing and effects on the video link above. I am really new to ffmpeg, please please yung help will be really meant a lot. Cheers!

Hello, Did you get any solution for this? Able to do animation like this?

@iRGBit
Copy link

iRGBit commented Mar 17, 2024

thanks for the work @anguyen8 and @rossomaltese
i have been trying both your versions and have ended up with 0MB .mp4s unfortunately... although a simple slideshow with ffmpeg like
ffmpeg -framerate 1 -pattern_type glob -i '*.jpg' -c:v libx264 -r 30 -pix_fmt yuv420p output.mp4
worked perfectly...
any hints/ideas where the error could lie? :/

@rossomaltese
Copy link

thanks for the work @anguyen8 and @rossomaltese i have been trying both your versions and have ended up with 0MB .mp4s unfortunately... although a simple slideshow with ffmpeg like ffmpeg -framerate 1 -pattern_type glob -i '*.jpg' -c:v libx264 -r 30 -pix_fmt yuv420p output.mp4 worked perfectly... any hints/ideas where the error could lie? :/

I retried executing the updated version and worked for me.
Two hints:

  1. pictures must have the same dimensions (width and height)
  2. there must not be blanks in the files name (or double quote each occurrence of variable files in the script).

Also, please take care of @martin3000 comment.

@rossomaltese
Copy link

For the ones who asked for adding a soundtrack, I did using something like that:

ffmpeg -i output_from_the_script.mp4 -itsoffset 00:00:17 -i soundtrack.mp3 -c copy -map 0:v:0 -map 1:a:0 output_with_sound.mp4

-itsoffset is the time at which soundtrack must start playing.

HTH

@martin3000
Copy link

If you have some hundred images, your memory will exhaust. I created a version which combines every 2 images and at the end, the resulting videos are combined into one:

#!/bin/bash

# make a crossfade transition for every 2 images
# jm schlatter 2024

# parms
if (($# != 3))
then
  echo "usage: $0 time[s] crossfade[s] outputvideoname"
  exit 1
else
  time=$1
  crossfade=$2                      # Crossfade duration between two images
  output_file="$3"                  # Name of output video
fi

if (( $(echo "$time <= 0" | bc -l) )); then
  time=1
fi

files=(*.jpg)                     # Change the file type to the type of your images

for ((i = 1; i < ${#files[*]}; ++i)); do
    ffmpeg -hide_banner -loop 1 -t $time -i "${files[i-1]}" -loop 1 -t $time -i "${files[i]}" -filter_complex \
    "[1:v][0:v]blend=all_expr='A*(if(gte(T,$crossfade),1,T/$crossfade))+B*(1-(if(gte(T,$crossfade),1,T/$crossfade)))'[b1v];\
     [0:v][b1v][1:v] \
     concat=n=3:v=1:a=0,format=yuv420p[v]"\
     -map '[v]' /tmp/output_$i.mp4
done

ffmpeg -f concat -safe 0 -i <(for f in $('ls' -1rt /tmp/output_*.mp4); do echo "file '$f'"; done) -c copy "$output_file.mp4"

@DavidOliver
Copy link

@DavidOliver: I would appreciate if you could post how you did this in G'MIC. As far as I can tell it is not easy to do. I tried posting a question to their Google Group.

With crossfade; with morph.

Sorry for the somewhat delayed reply.

@froh
Copy link

froh commented Sep 17, 2024

Hi Anguyen, FYI I've added parameters for the "same input size" problem, lowered memory requirements, a longer lead-in and last frame (parameterized, too), it's my my fork of the gist.

thanks for sharing this you#ve saved me lots of time :-)

@anguyen8
Copy link
Author

anguyen8 commented Sep 20, 2024

That's wonderful! Thank you for sharing your ver. @froh ! ❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment