How to optimize encoding and packaging videos using ffmpeg and shaka-packager - bash

I'm trying to encode and package uploaded videos for an LMS website where video size may differ. How can I write a sh script that converts and packages the given video based on its size (For ex. if the given video resolution is bigger than 720p and less than 1080p FFmpeg should convert videos in 2 sizes [360p, 720p] then shaka-packager should package them).
So far I have this script assuming that input video resolution is 1080p (or 1080p <= size < 4k)
#!/bin/sh
pwd
URL="$1"
ID="$2"
FOLDER="$3"
if [ -z "$URL" ];then
echo "Must input a file"
$SHELL
exit
fi
DIR="$FOLDER/$ID"
OUTDIR="$DIR/cmaf"
mkdir -p -v $DIR
mkdir -p -v $OUTDIR
GOP_SIZE=50
FPS=25
CRF=28
INPUT="$DIR/input"
wget -c -O $INPUT $URL &&
if [ ! -f $FILE ]; then
echo "$FILE does not exists"
$SHELL
exit
fi
ffmpeg -i $INPUT -y \
-threads 1 \
-c:v libx264 -crf $CRF -profile:v high -pix_fmt yuv420p \
-keyint_min $GOP_SIZE -g $GOP_SIZE -sc_threshold 0 \
-color_primaries 1 -color_trc 1 -colorspace 1 -movflags +faststart \
-c:a aac -b:a 128k -ar 44100 \
-r $FPS \
"$DIR/input.mp4" &&
ffmpeg -i "$DIR/input.mp4" -y \
-threads 1 \
-vn -acodec copy "$DIR/a.mp4" \
-vf scale=640:360 -an "$DIR/360p.mp4" \
-vf scale=1280:720 -an "$DIR/720p.mp4" \
-vf scale=1920:1080 -an "$DIR/1080p.mp4" &&
rm -R $OUTDIR
packager \
in="$DIR/a.mp4",stream=audio,output="$OUTDIR/a.mp4",drm_label=AUDIO \
in="$DIR/360p.mp4",stream=video,output="$OUTDIR/360p.mp4",drm_label=SD \
in="$DIR/720p.mp4",stream=video,output="$OUTDIR/720p.mp4",drm_label=HD \
in="$DIR/1080p.mp4",stream=video,output="$OUTDIR/1080p.mp4",drm_label=HD \
--enable_raw_key_encryption \
--keys label=AUDIO:key_id=f3c5e0761e6654b28f8049c778b23947:key=a4637a153a443df9eed0593043db7517,label=SD:key_id=abba277e8bcf552bbd2e86a434a9a5d7:key=69eaa807a6763af979e8d1940fb88397,label=HD:key_id=6d76f25cb17f5e76b8eaef6b7f582d87:key=cb541784c99737aef4fff74500c12ea7 \
--pssh 000000377073776800000000EDEF8BA979D64ACEA3C877DCD51D21ED00000071220F7465737420636F6E74656E74206967 \
--mpd_output "$OUTDIR/h264.mpd" \
--hls_master_playlist_output "$OUTDIR/h264_master.m3u8"
The above script first downloads a video by a given URL then converts it to appropriate video format before resizing and packaging. I assumed if I convert the video before scaling would be more performant than every time converting and resizing it. Also, I assumed if I resize to all resolutions in one command it would be much faster, but I think that is not how FFmpeg works. I'm stack in the world of FFmpeg not knowing how to write sh(or bash) script better, cleaner and dynamic for encoding and packaging videos for online streaming. I think there are others with the same problem or the same case. So any help, fix and recommendation is appreciated

For the sake of clarity, I stripped some arguments from your commands (yuv420p and -profile:v high are defaults, not changing frame-rate)
ffmpeg -i <input> -y \
-c:v libx264 -crf 28 -g 50 \
-c:a aac -b:a 128k -ar 44100 \
-movflags +faststart \
<output> &&
ffmpeg -i <output> -y \
-vn -c:a copy "$DIR/a.mp4" \
-vf scale=640:360 -an "$DIR/360p.mp4" \
-vf scale=1280:720 -an "$DIR/720p.mp4" \
-vf scale=1920:1080 -an "$DIR/1080p.mp4"
The first run will decode your input and re-encode it using libx264 with quality-target 28 and a keyframe every 50 frames.
The second instance will decode it again, guessing an encoder by the .mp4 extension -- defaulting to libx264 --, and re-encodes everything three times by using the default values -g 250 -crf 23 (I'm not sure about -movflags +faststart).
So you are (1) overwriting your settings from the first-run, (2) having an additional decode process and (3) having a certain quality loss due to multiple lossy encodings.
What you want is to combine these into one invocation:
ffmpeg -i <input> -y \
-vn -c:a aac -b:a 128k -ar 44100 "$DIR/a.mp4" \
-c:v libx264 -crf 28 -g 50 -s 640x360 -movflags +faststart -an "$DIR/360p.mp4" \
-c:v libx264 -crf 28 -g 50 -s 1280x720 -movflags +faststart -an "$DIR/720p.mp4" \
-c:v libx264 -crf 28 -g 50 -s 19201080 -movflags +faststart -an "$DIR/1080p.mp4"
Additionally, I would stay away from special arguments unless you really know what and why you are choosing them.
P.s.
This is a command that runs with 15 % CPU utilization on my laptop.
ffmpeg \
-hwaccel qsv -c:v h264_qsv -i 'rtsp://109.98.78.106' \
-an -c:v h264_qsv -global_quality 30 -vf "scale_qsv=h=360:w=-1" "/tmp/360p.mp4" \
-an -c:v h264_qsv -global_quality 30 -vf "scale_qsv=h=720:w=-1" "/tmp/720p.mp4" \
-an -c:v h264_qsv -global_quality 30 -vf "scale_qsv=h=1080:w=-1" "/tmp/1080p.mp4"
It might have some color and / or quality issues but this is a performance trade-off.

Related

Why does the mp4 from ffmpeg freeze during the last 3 seconds?

I'm trying to generate a perfectly looping mp4 from three inputs:
A background png image
An image sequence of transparent png images with the number of particles increasing
Another image sequence of transparent png images with the number of particles decreasing
I'm currently trying to achieve this with two commands (I have to use 'overlay' twice). The problem is that after the second command the video (test2.mp4) freezes for the last 3 seconds. Why does it happen? ARe there any other commands I could try to use?
First command:
ffmpeg -framerate 30 \
-pattern_type glob -i 'images/increase/*.png' \
-framerate 30 \
-i screens/Background.png \
-i audio/50-White-Noise-10min.mp3 \
-filter_complex "[1:v][0:v] overlay" \
-preset slow -c:a copy -shortest -c:v libx264 -pix_fmt yuv420p test.mp4
Second command:
ffmpeg -framerate 30 \
-pattern_type glob -i 'images/decrease/*.png' \
-i test.mp4 \
-filter_complex "[1:v][0:v] overlay" \
-preset slow -c:a copy -shortest -c:v libx264 -pix_fmt yuv420p test2.mp4
The solution was to do what Rajib commented: chain the overlay filters and do it all in one command:
ffmpeg -framerate 30 \
-i screens/Background.png \
-framerate 30 \
-pattern_type glob -i 'images/increase/*.png' \
-framerate 30 \
-pattern_type glob -i 'images/decrease/*.png' \
-i audio/50-White-Noise-10min.mp3 \
-filter_complex "[0][1] overlay[out],[out][2] overlay" \
-c:a copy -shortest -c:v libx264 -pix_fmt yuvj420p loop.mp4

what filters affect ffmpeg encoding speed

What are the options in this command that would cause my encoding speed to be 0.999x instead of 1.0x or higher?
ffmpeg -y \
-loop 1 -framerate 30 -re \
-i ./1280x720.jpg \
-stream_loop -1 -re \
-i ./audio.mp3 \
-vcodec libx264 -pix_fmt yuv420p \
-b:v 2500k -maxrate 2500k -bufsize 10000k \
-preset slow -tune stillimage \
-b:a 128k -ar 44100 -ac 2 -acodec aac \
-af "dynaudnorm=f=150:g=15" \
-g 60 \
-f flv tmp.flv
I am trying to figure out why would this only be encoding at 0.999x speed, is there anything that I could do to speed this up? 2 pass encoding? I cannot understand why the encoding speed is so slow?
Also please not i've tried present from slow - ultrafast, the encoding speed stays relatively unchanged.
The -re is the rate-limiting factor. It only feeds input in real-time so the encoder can't progress any faster.
Remove the -re before the inputs. Needed only when trying to simulate a real-time input or streaming to an output that expects its input in real-time.

ffmpeg youtube livestream stops after a while

I'll update this question
ffmpeg -version
ffmpeg -version
ffmpeg version 4.3.1-4ubuntu1 Copyright (c) 2000-2020 the FFmpeg developers
built with gcc 10 (Ubuntu 10.2.0-9ubuntu2)
I run this command to use ffmpeg to stream to youtube ;
ffmpeg -y -threads 12 \
-loop 1 -framerate 30 -re \
-i ./1280x720.jpg \
-i ./audio.mp3 \
-video_size 1280x720 \
-vcodec libx264 -pix_fmt yuv420p \
-b:v 4500k -maxrate 5500k -bufsize 22000k \
-preset ultrafast -crf 23 -tune stillimage \
-b:a 128k -ar 44100 -ac 2 -acodec aac \
-filter_complex "dynaudnorm=f=150:g=15" \
-r 30 -g 60 \
-f flv rtmp://a.rtmp.youtube.com/live2/xxxx 2>&1 | tee _LOG
The stream is excellent for 45-53 minutes then i'll get an error like this from ffmpeg:
[flv # 0x56077027cd80] Delay between the first packet and last packet in the muxing queue is 10034000 > 10000000: forcing output
then youtube starts to say, no data being received and the stream will end, which it does.
This is the full log: http://0x0.st/-zUH.txt
Your MP3 duration is 00:49:57.42 so the stream messes up after it ends. Loop the audio with -stream_loop -1 and add -re for real-time reading of the input:
ffmpeg -y \
-loop 1 -framerate 30 -re -i ./1280x720.jpg \
-re -stream_loop -1 -i ./audio.mp3 \
-c:v libx264 -pix_fmt yuv420p \
-b:v 4500k -maxrate 5500k -bufsize 22000k \
-preset ultrafast -tune stillimage \
-b:a 128k -ar 44100 -ac 2 -c:a aac \
-filter_complex "dynaudnorm=f=150:g=15" \
-g 60 -f flv rtmp://a.rtmp.youtube.com/live2/xxxx
Alternatively, remove -re -stream_loop -1 and add the output option -shortest if you want the stream to end when the audio ends.
Unrelated changes:
No need to set -threads. Let ffmpeg auto choose.
-video_size 1280x720 is an input option for certain demuxers and does nothing in your command. Removed. Your input is already 1280x720 anyway: otherwise, see Resizing videos with ffmpeg to fit a specific size.
-b:v and -crf are mutually exclusive. In your case -b:v is being ignored. For streaming you probably want to use -b:v. Removed -crf.
You already set the frame rate with -framerate 30 so -r 30 is not needed. Removed.
Recommend using the slowest -preset that still encodes fast enough.

ffmpeg combine audio mix code into complex concate script

I got currently 2 different ffmpeg scripts which I want to combine. I do not have good ffmpeg experience and those codes are mostly googel code so please be patient with me
The first code is concating 3 videos:
ffmpeg -y -i "$vid1" -i "$fp" -i "$vid1" -filter_complex \
"[0:v]scale=$cResolution:force_original_aspect_ratio=decrease,pad=$cResolution:(ow-iw)/2:(oh-ih)/2,setsar=1,fps=30,format=yuv420p[v0]; \
[1:v]scale=$cResolution:force_original_aspect_ratio=decrease,pad=$cResolution:(ow-iw)/2:(oh-ih)/2,setsar=1,fps=30,format=yuv420p[v1]; \
[2:v]scale=$cResolution:force_original_aspect_ratio=decrease,pad=$cResolution:(ow-iw)/2:(oh-ih)/2,setsar=1,fps=30,format=yuv420p[v2]; \
[0:a]aformat=sample_rates=48000:channel_layouts=stereo[a0]; \
[1:a]aformat=sample_rates=48000:channel_layouts=stereo[a1]; \
[2:a]aformat=sample_rates=48000:channel_layouts=stereo[a2]; \
[v0][a0][v1][a1][v2][a2]concat=n=3:v=1:a=1[v][a]; \
[v]drawtext=text='example..':y=h-line_h-$h3:x=w/30*mod(t\,20):enable='gt(mod(t,$dr2),$Introdr_rounded)'[v]; \
[v]drawtext=text='example..':y=h-line_h-$hcentral:x=w/20*mod(t\,100):enable='gt(mod(t,$dr2),$Introdr_rounded)'[v]; \
[v]drawtext=text='example..':y=h-line_h-23:x=w/30*mod(t\,20):enable='gt(mod(t,$dr2),$Introdr_rounded)'[v]" \
-map "[v]" -map "[a]" -c:v libx264 -crf 22 -preset veryfast -c:a aac -movflags +faststart "$fp_dest"
The second code is overlay a background mp3 in endless loop to the created video from above. Its important to know that this code does overlap the audio of the video and does not replace it. In future I will lower the volume of the mp3 files to work as background music
ffmpeg -y -i "$fp_dest" -filter_complex "amovie=$audio:loop=0,asetpts=N/SR/TB[aud];[0:a][aud]amix[a]" -map 0:v -map '[a]' -c:v copy -c:a aac -b:a 256k -shortest ./test.mp4
So currently I got 2 steps which I want to combine into 1 step. Can you please help me to include the second code into the first one without change any logic of the code?
Use amix to mix the music and the concated audio. stream_loop is applied to the music to loop it.
ffmpeg -y -i "$vid1" -i "$fp" -i "$vid1" -stream_loop -1 -i "$audio" -filter_complex \
"[0:v]scale=$cResolution:force_original_aspect_ratio=decrease,pad=$cResolution:(ow-iw)/2:(oh-ih)/2,setsar=1,fps=30,format=yuv420p[v0]; \
[1:v]scale=$cResolution:force_original_aspect_ratio=decrease,pad=$cResolution:(ow-iw)/2:(oh-ih)/2,setsar=1,fps=30,format=yuv420p[v1]; \
[2:v]scale=$cResolution:force_original_aspect_ratio=decrease,pad=$cResolution:(ow-iw)/2:(oh-ih)/2,setsar=1,fps=30,format=yuv420p[v2]; \
[0:a]aformat=sample_rates=48000:channel_layouts=stereo[a0]; \
[1:a]aformat=sample_rates=48000:channel_layouts=stereo[a1]; \
[2:a]aformat=sample_rates=48000:channel_layouts=stereo[a2]; \
[v0][a0][v1][a1][v2][a2]concat=n=3:v=1:a=1[v][a]; \
[a][3]amix=duration=first[a]; \
[v]drawtext=text='example..':y=h-line_h-$h3:x=w/30*mod(t\,20):enable='gt(mod(t,$dr2),$Introdr_rounded)'[v]; \
[v]drawtext=text='example..':y=h-line_h-$hcentral:x=w/20*mod(t\,100):enable='gt(mod(t,$dr2),$Introdr_rounded)'[v]; \
[v]drawtext=text='example..':y=h-line_h-23:x=w/30*mod(t\,20):enable='gt(mod(t,$dr2),$Introdr_rounded)'[v]" \
-map "[v]" -map "[a]" -c:v libx264 -crf 22 -preset veryfast -c:a aac -b:a 256k -movflags +faststart "$fp_dest"

ffmpeg two-pass in ts stream production

How can I make a two-pass convert while using .ts chunks output?
I use the following command inside bash script for chunks generation (I think all variables are clear enough for understanding):
ffmpeg -i $1 -threads 1 -b:v ${selected_bitrate} -b:a ${audio_bitrate} -s ${selected_width}x${selected_height} -r ${framerate} -preset fast -level ${level} -vcodec libx264 -f ssegment -segment_list b${selected_bitrate}.m3u8 -segment_time 9 -force_key_frames "expr:gte(t,n_forced*9)" -y b${selected_bitrate}_%05d.ts
I want to try two-pass because I need to match desired bitrate more accurate. Right now when I use for example 200k bitrate for video stream, it results ~380k in ts chunks (of course without audio).
Just call the libx264 with "-pass 1" like the following:
ffmpeg -i $1 -threads 1 -ar -b:v ${selected_bitrate} -s ${selected_width}x${selected_height} -r ${framerate} -preset fast -level ${level} -vcodec libx264 -pass 1 -f null -
Then repeat your command with "-pass 2":
ffmpeg -i $1 -threads 1 -b:v ${selected_bitrate} -b:a ${audio_bitrate} -s ${selected_width}x${selected_height} -r ${framerate} -preset fast -level ${level} -vcodec libx264 -pass 2 -f ssegment -segment_list b${selected_bitrate}.m3u8 -segment_time 9 -force_key_frames "expr:gte(t,n_forced*9)" -y b${selected_bitrate}_%05d.ts
That should give you what you want.

Resources