【FFmpeg】一篇文章快速上手 FFmpeg

FFmpeg

1. 环境搭建

git clone https://github.com/tanersener/ffmpeg-video-slideshow-scripts.git

为了方便,这里直接使用了conda环境(py10),使用如下代码下载ffmpeg:

conda install -c conda-forge ffmpeg

至此,环境搭建已完成。可以查看ffmpeg的版本等信息。

2. 基本代码部分

2.1 超参数设置

WIDTH=576
HEIGHT=1024
FPS=30
TRANSITION_DURATION=1
IMAGE_DURATION=2

2.2 数据加载

# FILES=`find ../media/*.jpg | sort -r`             # USE ALL IMAGES UNDER THE media FOLDER SORTED
# FILES=('../media/1.jpg' '../media/2.jpg')         # USE ONLY THESE IMAGE FILES
FILES=`find ../media/*.jpg`                         # USE ALL IMAGES UNDER THE media FOLDER

2.3 部分参数

TRANSITION_FRAME_COUNT=$(( TRANSITION_DURATION*FPS ))
IMAGE_FRAME_COUNT=$(( IMAGE_DURATION*FPS ))
TOTAL_DURATION=$(( (IMAGE_DURATION+TRANSITION_DURATION)*IMAGE_COUNT - TRANSITION_DURATION ))
TOTAL_FRAME_COUNT=$(( TOTAL_DURATION*FPS ))

2.4 输入流具体操作处理

for IMAGE in ${
    
    FILES[@]}; do
    FULL_SCRIPT+="-loop 1 -i '${IMAGE}' "
done
......
常见的操作
[${c}:v]  # 对某个进行输入流进行操作

setpts=PTS-STARTPTS  # 时间戳从0开始

scale=w='if(gte(iw/ih,${WIDTH}/${HEIGHT}),min(iw,${WIDTH}),-1)':h='if(gte(iw/ih,${WIDTH}/${HEIGHT}),-1,min(ih,${HEIGHT}))'  # 进行缩放,宽高比大于或等于目标宽高比 ${WIDTH}/${HEIGHT},则将宽度调整为 ${WIDTH},高度根据宽高比进行自适应;否则,将高度调整为 ${HEIGHT},宽度根据宽高比进行自适应。

scale=trunc(iw/2)*2:trunc(ih/2)*2  # 将图像的宽度和高度调整为偶数值

pad=width=${WIDTH}:height=${HEIGHT}:x=(${WIDTH}-iw)/2:y=(${HEIGHT}-ih)/2:color=#00000000  # 填充达到指定的宽高(${WIDTH}和${HEIGHT}),填充颜色为透明黑色(#00000000)

setsar=sar=1/1  # 设置样纵横比为1:1,确保输出图像的纵横比与原始图像一致

crop=${WIDTH}:${HEIGHT}  # 一个视频处理操作,用于裁剪视频帧的尺寸。

concat=n=3:v=1:a=0  # 连接三个视频片段,只包含视频流而不包含音频流

scale=${WIDTH}*5:-1  # 将连接后的视频流缩放为 ${WIDTH}*5 的宽度,高度根据比例自动调整

zoompan=z='min(pzoom+0.001*${ZOOM_SPEED},2)':d=1:${POSITION_FORMULA}:fps=${FPS}:s=${WIDTH}x${HEIGHT}  # min(pzoom+0.001*${ZOOM_SPEED},2) 控制了缩放的速度;${ZOOM_SPEED} 是之前定义的缩放速度变量

[stream$((c+1))]  # 得到经过缩放/填充的图像流

2.5 输入流拼接

for (( c=1; c<${
    
    IMAGE_COUNT}; c++ ))
do
    FULL_SCRIPT+="[stream${c}overlaid][stream$((c+1))blended]"
done

3. 图像操作

将图像流通过map直接打印出jpg/png文件。

模板格式如下:

ffmpeg -i 'the path of images' -filter_complex "the operations of ffmpeg" -map "streamout" output1.jpg

例如:

ffmpeg -i '../images/aaa.jpg' -filter_complex "scale=w='if(gte(iw/ih,576/1024),-1,576)':h='if(gte(iw/ih,576/1024),1024,-1)',crop=576:1024,setsar=sar=1/1,fps=30,format=rgba,split=2[stream1out1][stream1out2]" -map "[stream1out1]" output1.jpg -map "[stream1out2]" output2.jpg

4. 视频操作(以blurred_background.sh为例)

# 5张图片为例,总时长 5*2+5-1=14s
WIDTH=1280
HEIGHT=720
FPS=30
TRANSITION_DURATION=1
IMAGE_DURATION=2
# FILE OPTIONS
# FILES=`find ../media/*.jpg | sort -r`             # USE ALL IMAGES UNDER THE media FOLDER SORTED
# FILES=('../media/1.jpg' '../media/2.jpg')         # USE ONLY THESE IMAGE FILES
FILES=`find ../media/*.jpg`                         # USE ALL IMAGES UNDER THE media FOLDER

let IMAGE_COUNT=0
for IMAGE in ${
    
    FILES[@]}; do (( IMAGE_COUNT+=1 )); done

if [[ ${
    
    IMAGE_COUNT} -lt 2 ]]; then
    echo "Error: media folder should contain at least two images"
    exit 1;
fi
# 输出 图像数量/过渡时间/总时间
TRANSITION_FRAME_COUNT=$(( TRANSITION_DURATION*FPS ))
IMAGE_FRAME_COUNT=$(( IMAGE_DURATION*FPS ))
TOTAL_DURATION=$(( (IMAGE_DURATION+TRANSITION_DURATION)*IMAGE_COUNT - TRANSITION_DURATION ))
TOTAL_FRAME_COUNT=$(( TOTAL_DURATION*FPS ))

echo -e "\nVideo Slideshow Info\n------------------------\nImage count: ${
    
    IMAGE_COUNT}\nDimension: ${
    
    WIDTH}x${
    
    HEIGHT}\nFPS: ${
    
    FPS}\nImage duration: ${
    
    IMAGE_DURATION} s\n\
Transition duration: ${
    
    TRANSITION_DURATION} s\nTotal duration: ${
    
    TOTAL_DURATION} s\n"

# SECONDS 是一个内置的 Bash 变量
START_TIME=$SECONDS
所有的命令都是追加到 FULL_SCRIPT 中。
# 1. START COMMAND
FULL_SCRIPT="conda run -n py10 ffmpeg -y "  # 需要修改为自己的环境
# 2. ADD INPUTS
# -loop 1 表示将图像文件循环播放
# -i '${IMAGE}' 参数指定输入的图像文件路径
for IMAGE in ${
    
    FILES[@]}; do
    FULL_SCRIPT+="-loop 1 -i '${IMAGE}' "
done

-filter_complex 参数来处理多个输入流并生成一个或多个输出流的过程。
在这个脚本中,-filter_complex 参数后面的内容将用于定义一系列的滤镜操作,包括图像的缩放、裁剪、设置帧率等

# 3. START FILTER COMPLEX
FULL_SCRIPT+="-filter_complex \""

在循环内部,${c}:v 表示当前图像的视频流索引。然后应用一系列滤镜操作来对该输入流(每个图像都为一个数入流)进行处理,包括图像的缩放、设置像素宽高比、转换为 RGBA 格式、应用模糊效果和设置帧率。
具体的滤镜操作如下:

  • scale= W I D T H x {WIDTH}x WIDTHx{HEIGHT}:将图像缩放到指定的宽度和高度。
  • setsar=sar=1/1:设置像素宽高比为 1:1,以避免图像变形。
  • format=rgba:将图像格式转换为 RGBA 格式,以支持透明通道。
  • boxblur=100:应用模糊效果,使用模糊半径为 100。
  • setsar=sar=1/1:再次设置像素宽高比为 1:1。
  • fps= F P S :设置帧率为指定的 F P S 值。处理完成后,将该输入流命名为 s t r e a m {FPS}:设置帧率为指定的 FPS 值。 处理完成后,将该输入流命名为 stream FPS:设置帧率为指定的FPS值。处理完成后,将该输入流命名为stream((c+1))blurred唯一标识,其中 (c+1) 表示当前图像的索引加1,以便命名不重复。
# 4. PREPARE BLURRED INPUTS
for (( c=0; c<${
    
    IMAGE_COUNT}; c++ ))
do
    FULL_SCRIPT+="[${c}:v]scale=${WIDTH}x${HEIGHT},setsar=sar=1/1,format=rgba,boxblur=100,setsar=sar=1/1,fps=${FPS}[stream$((c+1))blurred];"
done

循环遍历了每个图像输入流,并为每个输入流执行以下操作:

  1. 缩放操作:使用 scale 过滤器将图像的宽度和高度调整为指定的目标尺寸 W I D T H x {WIDTH}x WIDTHx{HEIGHT}。这里使用了条件表达式来确定缩放的方式,以确保图像在不变形的情况下适应目标尺寸。如果图像的宽高比大于或等于目标宽高比 W I D T H / {WIDTH}/ WIDTH/{HEIGHT},则将宽度调整为 ${WIDTH},高度根据宽高比进行自适应;否则,将高度调整为 ${HEIGHT},宽度根据宽高比进行自适应。
  2. 再次缩放操作:为了确保缩放后的图像尺寸是偶数,使用 scale 过滤器将宽度和高度取整为最接近的偶数。
  3. 设置像素宽高比:使用 setsar 过滤器将图像的像素宽高比设置为 1:1,以避免在后续处理步骤中出现不正常的拉伸或挤压。
  4. 格式转换:使用 format 过滤器将图像的像素格式转换为 RGBA 格式,以便在后续的处理和合成中能够正确地处理图像的透明度。
    转换后的图像输入流被命名为 [stream$((c+1))raw],其中 $((c+1)) 是对循环索引变量 c 进行递增操作得到的序号。这些准备好的输入流将在后续的处理步骤中使用。
# 5. PREPARE INPUTS
for (( c=0; c<${
    
    IMAGE_COUNT}; c++ ))
do
    FULL_SCRIPT+="[${c}:v]scale=w='if(gte(iw/ih,${WIDTH}/${HEIGHT}),min(iw,${WIDTH}),-1)':h='if(gte(iw/ih,${WIDTH}/${HEIGHT}),-1,min(ih,${HEIGHT}))',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1,format=rgba[stream$((c+1))raw];"
done

在这一部分,对预处理的模糊图像和缩放后的图像进行叠加操作。
循环遍历了每个图像的模糊图像和缩放后的图像,并使用 overlay 过滤器将它们叠加在一起。叠加操作的参数包括:

  • overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2:将模糊图像叠加在缩放后的图像上,使它们在画面中居中对齐。
  • format=rgb:将叠加后的图像像素格式设置为 RGB 格式。
    叠加后的图像被命名为 [stream c o u t 1 ] ,表示第 c 个图像叠加后的输出。此外,还使用了 s p l i t 过滤器将每个图像的输出流分割成两个流 [ s t r e a m {c}out1],表示第 c 个图像叠加后的输出。此外,还使用了 split 过滤器将每个图像的输出流分割成两个流 [stream cout1],表示第c个图像叠加后的输出。此外,还使用了split过滤器将每个图像的输出流分割成两个流[stream{c}out1] 和 [stream c o u t 2 ] ,以便在后续的处理步骤中使用。通过这个循环,每个图像都产生了两个输出流,其中 [ s t r e a m {c}out2],以便在后续的处理步骤中使用。 通过这个循环,每个图像都产生了两个输出流,其中 [stream cout2],以便在后续的处理步骤中使用。通过这个循环,每个图像都产生了两个输出流,其中[stream{c}out1] 是叠加后的图像,[stream${c}out2] 是原始的缩放图像。
# 6. OVERLAY BLURRED AND SCALED INPUTS
for (( c=1; c<=${
    
    IMAGE_COUNT}; c++ ))
do
    FULL_SCRIPT+="[stream${c}blurred][stream${c}raw]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2:format=rgb,setpts=PTS-STARTPTS,split=2[stream${c}out1][stream${c}out2];"
done

在这一部分,对叠加后的图像进行填充操作。
循环遍历每个图像的叠加后的输出流,并使用 pad 过滤器对图像进行填充。填充操作的参数包括:

  • width= W I D T H : h e i g h t = {WIDTH}:height= WIDTH:height={HEIGHT}:设置填充后图像的宽度和高度为指定的值。
  • x=( W I D T H − i w ) / 2 : y = ( {WIDTH}-iw)/2:y=( WIDTHiw)/2:y=({HEIGHT}-ih)/2:计算填充的位置,使图像在画面中居中对齐。
  • trim=duration=${IMAGE_DURATION}:设置图像的持续时间为 IMAGE_DURATION,即图像显示的时间长度。
  • select=lte(n,${IMAGE_FRAME_COUNT}):选择图像的帧数范围,保证图像只在指定的帧数范围内显示。
    根据图像的位置和数量,还进行了不同的判断和处理:
  • 如果是第一个图像,并且图像数量大于 1,则另外生成一个输出流 [stream${c}ending],用于表示过渡效果的结束部分。
  • 如果是最后一个图像,则将过渡效果的起始部分合并到该图像的输出流 [stream${c}starting] 中。
  • 如果是中间的图像,则将过渡效果的起始部分和结束部分分割成两个输出流 [stream c s t a r t i n g ] 和 [ s t r e a m {c}starting] 和 [stream cstarting][stream{c}ending]。
    通过这个循环,每个图像的叠加输出流都经过填充操作,并生成了相应的输出流,用于表示图像的叠加效果和过渡效果。
# 7. APPLY PADDING
for (( c=1; c<=${
    
    IMAGE_COUNT}; c++ ))
do
    FULL_SCRIPT+="[stream${c}out1]pad=width=${WIDTH}:height=${HEIGHT}:x=(${WIDTH}-iw)/2:y=(${HEIGHT}-ih)/2,trim=duration=${IMAGE_DURATION},select=lte(n\,${IMAGE_FRAME_COUNT})[stream${c}overlaid];"
    if [[ ${
    
    c} -eq 1 ]]; then
        if  [[ ${
    
    IMAGE_COUNT} -gt 1 ]]; then
            FULL_SCRIPT+="[stream${c}out2]pad=width=${WIDTH}:height=${HEIGHT}:x=(${WIDTH}-iw)/2:y=(${HEIGHT}-ih)/2,trim=duration=${TRANSITION_DURATION},select=lte(n\,${TRANSITION_FRAME_COUNT})[stream${c}ending];"
        fi
    elif [[ ${
    
    c} -lt ${
    
    IMAGE_COUNT} ]]; then
        FULL_SCRIPT+="[stream${c}out2]pad=width=${WIDTH}:height=${HEIGHT}:x=(${WIDTH}-iw)/2:y=(${HEIGHT}-ih)/2,trim=duration=${TRANSITION_DURATION},select=lte(n\,${TRANSITION_FRAME_COUNT}),split=2[stream${c}starting][stream${c}ending];"
    elif [[ ${
    
    c} -eq ${
    
    IMAGE_COUNT} ]]; then
        FULL_SCRIPT+="[stream${c}out2]pad=width=${WIDTH}:height=${HEIGHT}:x=(${WIDTH}-iw)/2:y=(${HEIGHT}-ih)/2,trim=duration=${TRANSITION_DURATION},select=lte(n\,${TRANSITION_FRAME_COUNT})[stream${c}starting];"
    fi
done

在这一部分,创建过渡帧(transition frames)。
循环遍历每对相邻的图像,创建过渡效果的帧。对于每对相邻的图像,包括当前图像和下一个图像:

  • 使用 blend 过滤器对两个图像进行混合操作,生成过渡帧。混合操作的参数包括:
    • all_expr=‘A*(if(gte(T, T R A N S I T I O N D U R A T I O N ) , {TRANSITION_DURATION}), TRANSITIONDURATION),{TRANSITION_DURATION},T/ T R A N S I T I O N D U R A T I O N ) ) + B ∗ ( 1 − ( i f ( g t e ( T , {TRANSITION_DURATION}))+B*(1-(if(gte(T, TRANSITIONDURATION))+B(1(if(gte(T,{TRANSITION_DURATION}), T R A N S I T I O N D U R A T I O N , T / {TRANSITION_DURATION},T/ TRANSITIONDURATION,T/{TRANSITION_DURATION})))’:通过表达式混合两个输入图像,其中 A 表示当前图像,B 表示下一个图像。根据时间 T 的变化,控制两个图像的权重,从而实现过渡效果。
    • select=lte(n,${TRANSITION_FRAME_COUNT}):选择过渡帧的帧数范围,保证过渡帧只在指定的帧数范围内生成。
      通过这个循环,为每对相邻的图像创建了过渡帧的输出流,用于表示两个图像之间的过渡效果。
# 8. CREATE TRANSITION FRAMES
for (( c=1; c<${
    
    IMAGE_COUNT}; c++ ))
do
    FULL_SCRIPT+="[stream$((c+1))starting][stream${c}ending]blend=all_expr='A*(if(gte(T,${TRANSITION_DURATION}),${TRANSITION_DURATION},T/${TRANSITION_DURATION}))+B*(1-(if(gte(T,${TRANSITION_DURATION}),${TRANSITION_DURATION},T/${TRANSITION_DURATION})))',select=lte(n\,${TRANSITION_FRAME_COUNT})[stream$((c+1))blended];"
done

在这一部分,开始进行视频片段的拼接。
循环遍历每对相邻的图像,将前一个图像的叠加输出流(stream c o v e r l a i d )和当前图像的过渡帧输出流( s t r e a m {c}overlaid)和当前图像的过渡帧输出流(stream coverlaid)和当前图像的过渡帧输出流(stream((c+1))blended)连接起来,用于拼接视频片段。
通过这个循环,为每对相邻的图像创建了连接它们的输入流,用于拼接它们之间的视频片段。

# 9. BEGIN CONCAT
for (( c=1; c<${
    
    IMAGE_COUNT}; c++ ))
do
    FULL_SCRIPT+="[stream${c}overlaid][stream$((c+1))blended]"
done

在这一部分,完成了视频片段的拼接。
将最后一个图像的叠加输出流(stream${IMAGE_COUNT}overlaid)与之前所有连接过渡帧的输出流连接起来,用于拼接所有的视频片段。
通过这个步骤,生成了最终的视频流(video),其中包含了所有图像的叠加和过渡效果。

# 10. END CONCAT
FULL_SCRIPT+="[stream${IMAGE_COUNT}overlaid]concat=n=$((2*IMAGE_COUNT-1)):v=1:a=0,format=yuv420p[video]\""

在这最后一部分,将视频流(video)映射到输出文件,并指定了输出文件的格式和编码参数。
将输出文件的路径设置为…/advanced_blurred_background.mp4,并使用libx264编码器进行视频编码。
其他参数如下:

  • -vsync 2:设置音视频同步模式为"passthrough"。
  • -async 1:将音频同步模式设置为"audio first",确保音频和视频同步。
  • -rc-lookahead 0:禁用码率控制中的预测。
  • -g 0:禁用GOP(Group of Pictures)大小的限制。
  • -profile:v main -level 42:设置视频编码的配置文件和级别。
  • -c:v libx264:指定视频编码器为libx264。
  • -r F P S :设置输出视频的帧率为 {FPS}:设置输出视频的帧率为 FPS:设置输出视频的帧率为{FPS}(可选,根据需要添加)。
    最终,FULL_SCRIPT包含了完整的FFmpeg命令,用于生成最终的视频文件。
# 11. END
# FULL_SCRIPT+=" -map [video] -vsync 2 -async 1 -rc-lookahead 0 -g 0 -profile:v main -level 42 -c:v libx264 -r ${FPS} ../advanced_blurred_background.mp4"
FULL_SCRIPT+=" -map [video] -vsync 2 -async 1 -rc-lookahead 0 -g 0 -profile:v main -level 42 -c:v libx264 ../advanced_blurred_background.mp4"

#FULL_SCRIPT+=" -map [video] -async 1 -rc-lookahead 0 -g 0 -profile:v main -level 42 -c:v libx264 -r ${FPS} ../advanced_blurred_background.mp4"

eval ${
    
    FULL_SCRIPT}

ELAPSED_TIME=$(($SECONDS - $START_TIME))

echo -e '\nSlideshow created in '$ELAPSED_TIME' seconds\n'

unset $IFS

猜你喜欢

转载自blog.csdn.net/qq_44824148/article/details/128842259
今日推荐