UPDATE: in my next Blog there is a better variant of this script!
Working with my
video cutting-plan scripts
I found that I would like
to completely move to ffmpeg
, because OpenShot is
simply too slow on my weak(?) machine
(it takes minutes to move a 30 minutes clip collection 4 seconds to the right, once it even crashed).
What I'm giving up then is smooth transitions between the clips, fades, and text overlays,
as this would require re-encoding and takes a lot of time.
But I don't want to publish a video without title,
thus I tried to create a script that produces a title that I can prepend to
the cuts generated by my cutVideos.sh
script.
It is a simple text, optionally several lines, on a black background.
This Blog contains the source code of titleForVideos.sh.
Everything here refers to MP4 videos, ffmpeg
for Ubuntu LINUX,
and I am not an ffmpeg
expert.
Prerequisites
What we need:
- A text file containing the title text, named title.txt by default. It can contain newlines. Locate it where the videos are, like cuts.txt. This is the best location, so why support others?
- A fixed file name for the resulting video: TITLE.MP4.
The script
cutVideos.sh
can search for that naming convention and integrate the title when found. - A template video that gives some basic properties to use for the title video,
so that it can be prepended to the clips without re-encoding.
What I took from the template is the video-encoding (only
h264
is accepted), the frame-rate, the pixel format, width and height, for audio the audio-encoding and the sample-rate.
Implementation titleForVideos.sh
Following are all script fragments in the order they appear. At the end of the article you can find its complete source. I will explain every fragment in detail. Mind that this is a UNIX shell script and you need CYGWIN to run this on WINDOWS operating systems.
Script Settings
duration=4 # seconds fontcolor=yellow # foreground color fontsize=100 # size of text in pixels titleVideo=TITLE.MP4 # file name of the title video
When you want different title properties,
you can set them on top of the script file (not from outside).
But you shouldn't change TITLE.MP4, because this is a naming convention for cutVideos.sh
.
Argument Checks
[ -z "$1" ] && { echo "SYNTAX: $0 videoDir/[TARGETVIDEO.MP4] [title.txt]" >&2 echo " Creates a $titleVideo video with same properties as videos in given directory." >&2 echo " The text of the title must be in a file title.txt where the videos are." >&2 exit 1 } if [ -d $1 ] then cd $1 || exit 1 videoTemplate=`ls -1 *.mp4 *.MP4 2>/dev/null | head -1` # first found video [ -f "$videoTemplate" ] || { echo "Found no video template in $1" >&2 exit 2 } echo "Using $videoTemplate as video template" >&2 elif [ -f $1 ] then cd `dirname \$1` || exit 2 videoTemplate=`basename \$1` else echo "No video template given: $1" >&2 exit 3 fi titleText=$2 [ -z "$titleText" ] && titleText=title.txt [ -f $titleText ] || { echo "Found no file $titleText containing the title where the video is: `pwd`" >&2 exit 4 }
If the first argument was not given (empty), the script outputs its syntax and exits.
If the first argument is a directory, the script changes to the directory and uses the first found MP4 file as properties template. When the first argument is a file, the script uses that as properties template and changes into its directory. In any other case an error is written to stderr and the script exits.
The second argument is optional and gives the name of the file holding the title text.
It must be located inside the video directory.
When it was not given, title.txt
is assumed.
If it does not exist, an error is written to stderr and the script exits.
Read Video Properties
I want to create a title video with same technical properties as the video I will use the title for, so that I can prepend it without re-encoding.
streamProperty() { # $1 = stream name, $2 = property name ffprobe -v error -of default=noprint_wrappers=1:nokey=1 -select_streams $1 -show_entries stream=$2 $videoTemplate } # get video properties stream=v:0 # first found video codec_name=`streamProperty \$stream codec_name` [ "$codec_name" = "h264" ] || { echo "Due to a missing mapping from codec_name to ffmpeg libXXX only h264 is supported." >&2 exit 5 } rate=`streamProperty \$stream r_frame_rate` pixelFormat=`streamProperty \$stream pix_fmt` width=`streamProperty \$stream width` height=`streamProperty \$stream height` # get audio properties stream=a:0 # first found audio audio_codec=`streamProperty \$stream codec_name` sample_rate=`streamProperty \$stream sample_rate` echo "rate=$rate, size=$width/$height, pix_fmt=$pixelFormat, audio_codec=$audio_codec, sample_rate=$sample_rate"
The shell function streamProperty()
works
with the global variable $videoTemplate
and
two given parameters $1
and $2
, which must be the stream and the property name.
(Mind that you don't declare parameters on shell functions.)
The function holds a ffprobe
command with two placeholders for stream and property-name.
In the moment of function-execution the command is evaluated with given $-parameters.
To provide this function avoids to repeat the (complex) ffprobe
command as many times as you need properties.
The function is then called in a command-substitution with different streams (video, audio) and property names.
This gives the values of the properties, needed to create a compatible title video.
The script exits when the video encoding is different to "h264" because
I have no mapping between codecs and ffmpeg
libs, thus I anticipate MP4.
Finally the properties are written to stderr, so that we can check them for correctness.
They will be used in the subsequent ffmpeg
command.
Title Video Creation
[ -f $titleVideo ] && rm -f $titleVideo ffmpeg \ -f lavfi -i color=c=black:size=$width/$height:rate=$rate -t $duration \ -f lavfi -i anullsrc=sample_rate=$sample_rate:channel_layout=stereo -t $duration \ -vf "drawtext=textfile=$titleText:fontcolor=$fontcolor:fontsize=$fontsize:x=(w-text_w)/2:y=(h-text_h)/2" \ -codec:v libx264 -pix_fmt $pixelFormat -profile:v high \ -codec:a $audio_codec \ -shortest $titleVideo || exit 6
In case the TITLE.MP4 video already exists, it is removed.
The following ffmpeg
command consists of several parts (lines).
The parts are split using a backslash, which is the shell escape for newlines.
The first line creates a black color background with given dimension, frame-rate, and duration in seconds.
The second line creates an empty audio stream with given sample-rate and same duration.
The third line draws the text on center of the screen.
The 4th line defines the video output codec and sets the pixel-format.
The 5th line sets the audio codec.
The last line finally gives the output video file name.
In case the command fails, an error message is written and the script exits negatively.
Complete Source
####################################################### # Creates a title for a video with text in file title.txt. ####################################################### titleVideo=TITLE.MP4 # file name of the title video duration=4 # seconds fontcolor=yellow # foreground color fontsize=100 # size of text [ -z "$1" ] && { echo "SYNTAX: $0 videoDir/[TARGETVIDEO.MP4] [title.txt]" >&2 echo " Creates a $titleVideo video with same properties as videos in given directory." >&2 echo " The text of the title must be in a file title.txt where the videos are." >&2 exit 1 } if [ -d $1 ] then cd $1 || exit 1 videoTemplate=`ls -1 *.mp4 *.MP4 2>/dev/null | head -1` # first found video [ -f "$videoTemplate" ] || { echo "Found no video template in $1" >&2 exit 2 } echo "Using $videoTemplate as video template" >&2 elif [ -f $1 ] then cd `dirname \$1` || exit 2 videoTemplate=`basename \$1` else echo "No video template given: $1" >&2 exit 3 fi titleText=$2 [ -z "$titleText" ] && titleText=title.txt [ -f $titleText ] || { echo "Found no file $titleText containing the title where the video is: `pwd`" >&2 exit 4 } streamProperty() { ffprobe -v error -of default=noprint_wrappers=1:nokey=1 -select_streams $1 -show_entries stream=$2 $videoTemplate" } # get video properties stream=v:0 # first found video codec_name=`streamProperty \$stream codec_name` [ "$codec_name" = "h264" ] || { echo "Due to a missing mapping from codec_name to ffmpeg libXXX only h264 is supported." >&2 exit 5 } rate=`streamProperty \$stream r_frame_rate` pixelFormat=`streamProperty \$stream pix_fmt` width=`streamProperty \$stream width` height=`streamProperty \$stream height` # get audio properties stream=a:0 # first found audio audio_codec=`streamProperty \$stream codec_name` sample_rate=`streamProperty \$stream sample_rate` echo "rate=$rate, size=$width/$height, pix_fmt=$pixelFormat, audio_codec=$audio_codec, sample_rate=$sample_rate" [ -f $titleVideo ] && rm -f $titleVideo ffmpeg \ -f lavfi -i color=c=black:size=$width/$height:rate=$rate -t $duration \ -f lavfi -i anullsrc=sample_rate=$sample_rate:channel_layout=stereo -t $duration \ -vf "drawtext=textfile=$titleText:fontcolor=$fontcolor:fontsize=$fontsize:x=(w-text_w)/2:y=(h-text_h)/2" \ -codec:v libx264 -pix_fmt $pixelFormat -profile:v high \ -codec:a $audio_codec \ -shortest $titleVideo || exit 6 echo "Created $titleVideo in `pwd`"
Keine Kommentare:
Kommentar veröffentlichen