-
Notifications
You must be signed in to change notification settings - Fork 46
/
Copy pathcrossfade-clips
executable file
·149 lines (124 loc) · 5.71 KB
/
crossfade-clips
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#!/bin/sh
#===============================================================================
# crossfade-clips
# cross fade video clips
#===============================================================================
# dependencies:
# ffmpeg file grep
#===============================================================================
# script usage
#===============================================================================
usage()
{
# if argument passed to function echo it
[ -z "${1}" ] || echo "! ${1}"
# display help
echo "\
# ffmpeg cross fade clips
$(basename "$0") -a clip1.(mp4|mkv|mov|m4v) -b clip2.(mp4|mkv|mov|m4v) -d (1|2) -o output.mp4
-a clip1.(mp4|mkv|mov|m4v) : first clip
-b clip2.(mp4|mkv|mov|m4v) : second clip
-d (1|2) : cross fade duration :optional argument # if option not provided defaults to 1 second
-o output.mp4 : optional argument # if option not provided defaults to input-name-xfade-date-time"
exit 2
}
#===============================================================================
# error messages
#===============================================================================
INVALID_OPT_ERR='Invalid option:'
REQ_ARG_ERR='requires an argument'
WRONG_ARGS_ERR='wrong number of arguments passed to script'
#===============================================================================
# check the number of arguments passed to the script
#===============================================================================
[ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}"
#===============================================================================
# getopts check the options passed to the script
#===============================================================================
while getopts ':a:b:d:o:h' opt
do
case ${opt} in
a) clip1="${OPTARG}";;
b) clip2="${OPTARG}";;
d) dur="${OPTARG}";;
o) outfile="${OPTARG}";;
h) usage;;
\?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;;
:) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;;
esac
done
shift $((OPTIND-1))
#===============================================================================
# variables
#===============================================================================
# input, input name and extension
clip1_nopath="${clip1##*/}"
clip1_name="${clip1_nopath%.*}"
# clip durations for fades
clip1_dur=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$clip1" | cut -d\. -f1)
clip2_dur=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$clip2" | cut -d\. -f1)
# clip1 use the bc command to remove 1 second from length of clip for cross fade
clip1_offset=$(echo "${clip1_dur}-1" | bc -l)
clip2_offset=$(echo "${clip2_dur}-1" | bc -l)
# variables
outfile_default="${clip1_name}-xfade-$(date +"%Y-%m-%d-%H-%M-%S").mp4"
duration_default='1'
#===============================================================================
# functions
#===============================================================================
# cross fade video and audio
xfade_va () {
ffmpeg \
-hide_banner \
-stats -v panic \
-i "${clip1}" -i "${clip2}" \
-an -filter_complex \
" [0:v]trim=start=0:end='${clip1_offset}',setpts=PTS-STARTPTS[firstclip];
[1:v]trim=start=${dur:=${duration_default}},setpts=PTS-STARTPTS[secondclip];
[0:v]trim=start='${clip1_offset}':end='${clip1_dur}',setpts=PTS-STARTPTS[fadeoutsrc];
[1:v]trim=start=0:end=${dur:=${duration_default}},setpts=PTS-STARTPTS[fadeinsrc];
[fadeinsrc]format=pix_fmts=yuva420p,
fade=t=in:st=0:d=${dur:=${duration_default}}:alpha=1[fadein];
[fadeoutsrc]format=pix_fmts=yuva420p,
fade=t=out:st=0:d=${dur:=${duration_default}}:alpha=1[fadeout];
[fadein]fifo[fadeinfifo];
[fadeout]fifo[fadeoutfifo];
[fadeoutfifo][fadeinfifo]overlay[crossfade];
[firstclip][crossfade][secondclip]concat=n=3[output];
[0:a] afade=t=in:st=0:d=${dur:=${duration_default}} [audiofadein];
[1:a] afade=t=out:st='${clip2_offset}':d=${dur:=${duration_default}} [audiofadeout];
[audiofadein][audiofadeout] acrossfade=d=${dur:=${duration_default}} [audio]
" \
-map "[output]" -map "[audio]" "${outfile:=${outfile_default}}"
}
# cross fade video
xfade_v () {
ffmpeg \
-hide_banner \
-stats -v panic \
-i "${clip1}" -i "${clip2}" \
-an -filter_complex \
" [0:v]trim=start=0:end='${clip1_offset}',setpts=PTS-STARTPTS[firstclip];
[1:v]trim=start=${dur:=${duration_default}},setpts=PTS-STARTPTS[secondclip];
[0:v]trim=start='${clip1_offset}':end='${clip1_dur}',setpts=PTS-STARTPTS[fadeoutsrc];
[1:v]trim=start=0:end=${dur:=${duration_default}},setpts=PTS-STARTPTS[fadeinsrc];
[fadeinsrc]format=pix_fmts=yuva420p,
fade=t=in:st=0:d=${dur:=${duration_default}}:alpha=1[fadein];
[fadeoutsrc]format=pix_fmts=yuva420p,
fade=t=out:st=0:d=${dur:=${duration_default}}:alpha=1[fadeout];
[fadein]fifo[fadeinfifo];
[fadeout]fifo[fadeoutfifo];
[fadeoutfifo][fadeinfifo]overlay[crossfade];
[firstclip][crossfade][secondclip]concat=n=3[output]
" \
-map "[output]" "${outfile:=${outfile_default}}"
}
# check if video has an audio track
clip1_check="$(ffprobe -i "${clip1}" -show_streams -select_streams a -loglevel error)"
clip2_check="$(ffprobe -i "${clip2}" -show_streams -select_streams a -loglevel error)"
# check if audio_check is null which means the video doesnt have an audio track
if [ -z "${clip1_check}" ] || [ -z "${clip2_check}" ]; then
xfade_v "${clip1}" "${clip2}" # fade video track
else
xfade_va "${clip1}" "${clip2}" # fade video and audio track
fi