Search code examples
bashffmpegvideo-streamingmp4mpeg-dash

MP4 to DASH (bash script)


I have a web site in which users can upload video files. I want to stream all of them using DASH to obtain an adaptive bitrate streaming. So I wrote a bash script (to run by cron) that converts all mp4 files to DASH, but it doesn't work properly: what is wrong?

For example, using the following script, I obtained: https://www.informatica-libera.net/dash_faq/stream.mpd

It validates, but it doesn't play. I tested it on: http://dash-mse-test.appspot.com/dash-player.html?url=https%3A%2F%2Fwww.informatica-libera.net%2Fdash_faq%2Fstream.mpd&autoplay=on&adapt=auto&flavor=

Thank you for any help. The code:

#!/bin/bash

# THIS SCRIPT CONVERTS EVERY MP4 (IN THE CURRENT FOLDER AND SUBFOLDER)
# TO A MULTI-BITRATE VIDEO IN MP4-DASH
# For each file "videoname.mp4" it creates a folder "dash_videoname" 
# containing a dash manifest file "stream.mpd" and subfolders containing 
# video segments.

# mp4dash documentation and download: https://www.bento4.com/developers/dash/

MYDIR=$(dirname $(readlink -f ${BASH_SOURCE[0]}))
SAVEDIR=$(pwd)

# Check programs
if [ -z "$(which ffmpeg)" ]; then
    echo "Error: ffmpeg is not installed"
    exit 1
fi

if [ -z "$(which mp4dash)" ]; then
    echo "Error: mp4dash is not installed"
    exit 1
fi

cd "$MYDIR"

TARGET_FILES=$(find ./ -type f -name "*.mp4")
for f in $TARGET_FILES
do
  f=$(basename "$f") # fullname of the file
  f="${f%.*}" # name without extension

  if [ ! -d "dash_${f}" ]; then
    echo "Converting \"$f\" to multi-bitrate video in MPEG-DASH"

    ffmpeg -y -i "${f}.mp4" -c:a libfdk_aac -ac 2 -ab 128k -c:v libx264 -x264opts 'keyint=24:min-keyint=24:no-scenecut' -b:v 1500k -vf "scale=-2:720" -f mp4 -pass 1 -y /dev/null
    ffmpeg -y -i "${f}.mp4" -c:a libfdk_aac -ac 2 -ab 128k -c:v libx264 -x264opts 'keyint=24:min-keyint=24:no-scenecut' -b:v 1500k -vf "scale=-2:720" -f mp4 -pass 2 "${f}_1500.mp4"

    ffmpeg -y -i "${f}.mp4" -c:a libfdk_aac -ac 2 -ab 128k -c:v libx264 -x264opts 'keyint=24:min-keyint=24:no-scenecut' -b:v 800k -vf "scale=-2:540" -f mp4 -pass 1 -y /dev/null
    ffmpeg -y -i "${f}.mp4" -c:a libfdk_aac -ac 2 -ab 128k -c:v libx264 -x264opts 'keyint=24:min-keyint=24:no-scenecut' -b:v 800k -vf "scale=-2:540" -f mp4 -pass 2 "${f}_800.mp4"

    ffmpeg -y -i "${f}.mp4" -c:a libfdk_aac -ac 2 -ab 128k -c:v libx264 -x264opts 'keyint=24:min-keyint=24:no-scenecut' -b:v 400k -vf "scale=-2:360" -f mp4 -pass 1 -y /dev/null
    ffmpeg -y -i "${f}.mp4" -c:a libfdk_aac -ac 2 -ab 128k -c:v libx264 -x264opts 'keyint=24:min-keyint=24:no-scenecut' -b:v 400k -vf "scale=-2:360" -f mp4 -pass 2 "${f}_400.mp4"

    ffmpeg -y -i "${f}.mp4" -c:a libfdk_aac -ac 2 -ab 128k -c:v libx264 -x264opts 'keyint=24:min-keyint=24:no-scenecut' -b:v 200k -vf "scale=-2:180" -f mp4 -pass 1 -y /dev/null
    ffmpeg -y -i "${f}.mp4" -c:a libfdk_aac -ac 2 -ab 128k -c:v libx264 -x264opts 'keyint=24:min-keyint=24:no-scenecut' -b:v 200k -vf "scale=-2:180" -f mp4 -pass 2 "${f}_200.mp4"

    rm -f ffmpeg*log*

    mp4fragment "${f}_1500.mp4" "${f}_1500_fragmented.mp4"
    mp4fragment "${f}_800.mp4" "${f}_800_fragmented.mp4"
    mp4fragment "${f}_400.mp4" "${f}_400_fragmented.mp4"
    mp4fragment "${f}_200.mp4" "${f}_200_fragmented.mp4"

    rm -f "${f}_1500.mp4" "${f}_800.mp4" "${f}_400.mp4" "${f}_200.mp4"

    mp4dash -v -o "dash_${f}" "${f}_1500_fragmented.mp4" "${f}_800_fragmented.mp4" "${f}_400_fragmented.mp4" "${f}_200_fragmented.mp4"

    rm -f "${f}_1500_fragmented.mp4" "${f}_800_fragmented.mp4" "${f}_400_fragmented.mp4" "${f}_200_fragmented.mp4"

    fi

done

cd "$SAVEDIR"

Solution

  • I solved so (however it's not fully compatible with every modern browser, for example on my Linux version of Firefox the audio doesn't play):

    #!/bin/bash
    
    # THIS SCRIPT CONVERTS EVERY MP4 (IN THE CURRENT FOLDER AND SUBFOLDER) TO A MULTI-BITRATE VIDEO IN MP4-DASH
    # For each file "videoname.mp4" it creates a folder "dash_videoname" containing a dash manifest file "stream.mpd" and subfolders containing video segments.
    
    # Validation tool:
    # http://dashif.org/conformance.html
    
    # Documentation:
    # https://tdngan.wordpress.com/2016/11/17/how-to-encode-multi-bitrate-videos-in-mpeg-dash-for-mse-based-media-players/
    
    # Remember to add the following mime-types (uncommented) to .htaccess:
    # AddType video/mp4 m4s
    # AddType application/dash+xml mpd
    
    # DASH-264 JavaScript Reference Client
    # https://github.com/Dash-Industry-Forum/dash.js
    # https://github.com/Dash-Industry-Forum/dash.js/wiki
    
    MYDIR=$(dirname $(readlink -f ${BASH_SOURCE[0]}))
    SAVEDIR=$(pwd)
    
    # Check programs
    if [ -z "$(which ffmpeg)" ]; then
        echo "Error: ffmpeg is not installed"
        exit 1
    fi
    
    if [ -z "$(which MP4Box)" ]; then
        echo "Error: MP4Box is not installed"
        exit 1
    fi
    
    cd "$MYDIR"
    
    TARGET_FILES=$(find ./ -type f -name "*.mp4")
    for f in $TARGET_FILES
    do
      f=$(basename "$f") # fullname of the file
      f="${f%.*}" # name without extension
    
      if [ ! -d "dash_${f}" ]; then
        echo "Converting \"$f\" to multi-bitrate video in MPEG-DASH"
    
        ffmpeg -y -i "${f}.mp4" -c:a libfdk_aac -ac 2 -ab 128k -vn "${f}_audio.m4a"
    
        ffmpeg -y -i "${f}.mp4" -an -c:v libx264 -x264opts 'keyint=24:min-keyint=24:no-scenecut' -b:v 1500k -vf "scale=-2:720" -f mp4 -pass 1 -y /dev/null
        ffmpeg -y -i "${f}.mp4" -an -c:v libx264 -x264opts 'keyint=24:min-keyint=24:no-scenecut' -b:v 1500k -vf "scale=-2:720" -f mp4 -pass 2 "${f}_1500.mp4"
    
        ffmpeg -y -i "${f}.mp4" -an -c:v libx264 -x264opts 'keyint=24:min-keyint=24:no-scenecut' -b:v 800k -vf "scale=-2:540" -f mp4 -pass 1 -y /dev/null
        ffmpeg -y -i "${f}.mp4" -an -c:v libx264 -x264opts 'keyint=24:min-keyint=24:no-scenecut' -b:v 800k -vf "scale=-2:540" -f mp4 -pass 2 "${f}_800.mp4"
    
        ffmpeg -y -i "${f}.mp4" -an -c:v libx264 -x264opts 'keyint=24:min-keyint=24:no-scenecut' -b:v 400k -vf "scale=-2:360" -f mp4 -pass 1 -y /dev/null
        ffmpeg -y -i "${f}.mp4" -an -c:v libx264 -x264opts 'keyint=24:min-keyint=24:no-scenecut' -b:v 400k -vf "scale=-2:360" -f mp4 -pass 2 "${f}_400.mp4"
    
        ffmpeg -y -i "${f}.mp4" -an -c:v libx264 -x264opts 'keyint=24:min-keyint=24:no-scenecut' -b:v 200k -vf "scale=-2:180" -f mp4 -pass 1 -y /dev/null
        ffmpeg -y -i "${f}.mp4" -an -c:v libx264 -x264opts 'keyint=24:min-keyint=24:no-scenecut' -b:v 200k -vf "scale=-2:180" -f mp4 -pass 2 "${f}_200.mp4"
    
        rm -f ffmpeg*log*
    
        MP4Box -dash 2000 -rap -frag-rap -profile onDemand "${f}_1500.mp4" "${f}_800.mp4" "${f}_400.mp4" "${f}_200.mp4" "${f}_audio.m4a" -out "${f}_MP4.mpd"
    
    
        rm  "${f}_1500.mp4" "${f}_800.mp4" "${f}_400.mp4" "${f}_200.mp4" "${f}_audio.m4a"
    
        fi
    
    done
    
    cd "$SAVEDIR"
    

    I also tried VP9 instead of h.264, but also in this case there isn't compatibility with all browser (in my Linux distro, it plays correctly only on Firefox, while it doesn't play at all on Chrome):

    #!/bin/bash
    MYDIR=$(dirname $(readlink -f ${BASH_SOURCE[0]}))
    SAVEDIR=$(pwd)
    
    # Controlla che i programmi richiesti siano installati
    if [ -z "$(which ffmpeg)" ]; then
        echo "Errore: ffmpeg non e' installato"
        exit 1
    fi
    
    cd "$MYDIR"
    
    TARGET_FILES=$(find ./ -type f -name "*.mp4")
    for f in $TARGET_FILES
    do
      f=$(basename "$f") # memorizza il nome completo del file
      f="${f%.*}" # toglie l'estensione
      if [ ! -f "${f}.mpd" ]; then
        echo "Converto il file \"$f\" in Adaptive WebM using DASH"
        echo "Riferimenti: http://wiki.webmproject.org/adaptive-streaming/instructions-to-playback-adaptive-webm-using-dash"
    
        # http://wiki.webmproject.org/adaptive-streaming/instructions-to-playback-adaptive-webm-using-dash
    
        VP9_DASH_PARAMS="-tile-columns 4 -frame-parallel 1"
    
        ffmpeg -i "${f}.mp4" -c:v libvpx-vp9 -vf scale=-1:90 -b:v 250k -keyint_min 150 -g 150 ${VP9_DASH_PARAMS} -an -f webm -dash 1 -pass 1 -y /dev/null
        ffmpeg -i "${f}.mp4" -c:v libvpx-vp9 -vf scale=-1:90 -b:v 250k -keyint_min 150 -g 150 ${VP9_DASH_PARAMS} -an -f webm -dash 1 -pass 2 "${f}_160px_250k.webm"
    
        ffmpeg -i "${f}.mp4" -c:v libvpx-vp9 -vf scale=-1:180 -b:v 500k -keyint_min 150 -g 150 ${VP9_DASH_PARAMS} -an -f webm -dash 1 -pass 1 -y /dev/null
        ffmpeg -i "${f}.mp4" -c:v libvpx-vp9 -vf scale=-1:180 -b:v 500k -keyint_min 150 -g 150 ${VP9_DASH_PARAMS} -an -f webm -dash 1 -pass 2 "${f}_320px_500k.webm"
    
        ffmpeg -i "${f}.mp4" -c:v libvpx-vp9 -vf scale=-1:360 -b:v 750k -keyint_min 150 -g 150 ${VP9_DASH_PARAMS} -an -f webm -dash 1 -pass 1 -y /dev/null
        ffmpeg -i "${f}.mp4" -c:v libvpx-vp9 -vf scale=-1:360 -b:v 750k -keyint_min 150 -g 150 ${VP9_DASH_PARAMS} -an -f webm -dash 1 -pass 2 "${f}_640px_750k.webm"
    
        ffmpeg -i "${f}.mp4" -c:v libvpx-vp9 -vf scale=-1:360 -b:v 1000k -keyint_min 150 -g 150 ${VP9_DASH_PARAMS} -an -f webm -dash 1 -pass 1 -y /dev/null
        ffmpeg -i "${f}.mp4" -c:v libvpx-vp9 -vf scale=-1:360 -b:v 1000k -keyint_min 150 -g 150 ${VP9_DASH_PARAMS} -an -f webm -dash 1 -pass 2 "${f}_640px_1000k.webm"
    
        ffmpeg -i "${f}.mp4" -c:v libvpx-vp9 -vf scale=-1:720 -b:v 1500k -keyint_min 150 -g 150 ${VP9_DASH_PARAMS} -an -f webm -dash 1 -pass 1 -y /dev/null
        ffmpeg -i "${f}.mp4" -c:v libvpx-vp9 -vf scale=-1:720 -b:v 1500k -keyint_min 150 -g 150 ${VP9_DASH_PARAMS} -an -f webm -dash 1 -pass 2 "${f}_1280px_1500k.webm"
    
        ffmpeg -i "${f}.mp4" -c:a libvorbis -b:a 128k -vn -f webm -dash 1 "${f}_audio_128k.webm"
    
    
        rm -f ffmpeg*.log
    
        ffmpeg \
        -f webm_dash_manifest -i "${f}_160px_250k.webm" \
        -f webm_dash_manifest -i "${f}_320px_500k.webm" \
        -f webm_dash_manifest -i "${f}_640px_750k.webm" \
        -f webm_dash_manifest -i "${f}_640px_1000k.webm" \
        -f webm_dash_manifest -i "${f}_1280px_1500k.webm" \
        -f webm_dash_manifest -i "${f}_audio_128k.webm" \
        -c copy -map 0 -map 1 -map 2 -map 3 -map 4 -map 5 \
        -f webm_dash_manifest \
        -adaptation_sets "id=0,streams=0,1,2,3,4 id=1,streams=5" \
        "${f}.mpd"
    
        fi
    
    done
    
    cd "$SAVEDIR"
    

    I didn't find a way to serve audio/video content to all browsers. I've done my tests so:

    <!DOCTYPE html>
    <html>
    <head>
    <script src="http://cdn.dashjs.org/latest/dash.all.min.js"></script>
    <style>
        video {
           width: 640px;
           height: 360px;
        }
    </style>
    </head>
    <body>
    <div>
           <video data-dashjs-player autoplay src="test.mpd" controls></video>
       </div>
    </body>
    </html>