0

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

  • 1
    What you do here is to run ffmpeg with your settings, then on the second invocation (i) reconvert everything including a (big) performance and some quality loss and (ii) overwriting all your parameters from the first run with the default ones. First: Just join your second ffmpeg invocation with the first one. There really is no need to run ffmpeg twice. Second: If you think about performance over absolute quality, you might have a look at the hardware acceleration of Intel CPUs (QSV / VAAPI). There you get the encoding + scaling for free with some quality loss regarding `libx264`. – Suuuehgi Nov 16 '20 at 11:20
  • 1
    **P.s.** You use lot's of very special arguments. I recommend sticking to ffmpeg's defaults unless you really need otherwise. – Suuuehgi Nov 16 '20 at 11:24
  • Thank you, I will look at hardware acceleration for performance. But what I thought was every time FFmpeg run a simple command such as `ffmpeg -i input.mov -s 640x320 -c:v libx264 -c:a copy output.mp4` it decodes, resizes then encodes a video. For 4 times resizing it does 3 time extra decodes which were I thought leads to performance lose as my understanding of FFmpeg. Also, I tried FFmpeg map, complex filtering command but there was not a success. Then I think there are people who know how to do in an optimal way, or is it only way how FFmpeg works? – Saidamir Botirov Nov 16 '20 at 12:24
  • And about special arguments, I was going to stick to ffmpeg's default but on some articles on the internet authors recommend to use them on streaming video – Saidamir Botirov Nov 16 '20 at 12:29
  • There are a lot of different (specialized) use-cases and bad examples on the internet. I just want to say: Be careful with arguments that you don't fully understand. I added a longer answer below. – Suuuehgi Nov 16 '20 at 15:55

1 Answers1

1

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.

Suuuehgi
  • 4,547
  • 3
  • 27
  • 32
  • One more question, do setting order matter in an FFmpeg command? – Saidamir Botirov Nov 17 '20 at 07:17
  • 1
    There are two types of options: Global options (e.g. `-loglevel error`) and input / output options. Global options come first, then everything else are input / output options. "[All (those) options apply ONLY to the next input or output file and are reset between files.](https://ffmpeg.org/ffmpeg.html#Description)" The order among them afaik does not matter. "file" can by any format here, e.g. a stream. The separator is `-i`: Everything (except global options) before are input options, everything behind is either an output option XOR an input option regarding the next `-i`. – Suuuehgi Nov 17 '20 at 08:34