From 729f0718ff6ac831e91279b77046e4f6b9438b65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kleinb=C3=B6lting?= Date: Thu, 24 Nov 2022 13:02:25 +0100 Subject: [PATCH] fix: key frame extraction (DEV-1513) (#2300) Fixes bugs which prevent the key frame extraction and matrix file creation of the two files: https://github.com/dasch-swiss/4124-xmlperformance-testdata/blob/main/bitstreams/0.1.mp4 https://github.com/dasch-swiss/4124-xmlperformance-testdata/blob/main/bitstreams/100.mp4 Both files suffer from two different bugs: Fixes processing of very short moving images (0.1.mp4) The file is too short for any frame to be extracted with the previous implementation which assumed at least five keyframes to be extracted. Change the script to start extracting keyframes at time unit 0, i.e. the beginning of the file and use the first extract frame as the start of the matrix instead of a later possibly non existing one. Fix rounding error in calculation of number of matrix files to created (100.mp4) For some reason the calculation of the number of matrix files was rounded from 0.8... to 2 where the next bigger integer should be 1 fixes issue DEV-1513 Additional changes: * Add support for debugging output to export-moving-image-frames.sh Introduce $DEBUG for the export script as a way to turn on debug out * Mount scripts folder into sipi for local dev purposed * Declare function with function keyword * Formatting - use spaces instead of tabs akin to scala source code * Fix mimetype check being skipped/failing Failed with + file -b --mime-type ./1.mp4 -ne video/mp4 Usage: file [-bcCdEhikLlNnprsSvzZ0] [--apple] [--extension] [--mime-encoding] [--mime-type] [-e ] [-F ] [-f ] [-m ] [-P ] ... file -C [-m ] file [--help] * Use double quotes in order to prevent globbing and word splitting * Remove unused code: function collect, unused num variable,unused framestart variable * Fix warning for read without -r https://github.com/koalaman/shellcheck/wiki/SC2162 * Simplify calculation and rename number_of_frame_files --- docker-compose.yml | 1 + sipi/scripts/export-moving-image-frames.sh | 182 +++++++++------------ 2 files changed, 82 insertions(+), 101 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 15f62854bd..c318b6ccb8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -33,6 +33,7 @@ services: - ${LOCAL_HOME}/sipi/config:/sipi/config:delegated - ${LOCAL_HOME}/sipi/images:/sipi/images:delegated - ${LOCAL_HOME}/sipi/server:/sipi/server:delegated + - ${LOCAL_HOME}/sipi/scripts:/sipi/scripts:delegated networks: - knora-net environment: diff --git a/sipi/scripts/export-moving-image-frames.sh b/sipi/scripts/export-moving-image-frames.sh index cd339647a7..049747c8c6 100755 --- a/sipi/scripts/export-moving-image-frames.sh +++ b/sipi/scripts/export-moving-image-frames.sh @@ -7,78 +7,57 @@ # - max. 36 frames per matrix # - one matrix file corresponds to 6 minutes of film -set -e - -dir=$(pwd) -sep='---------------------------------' +if [ "${DEBUG:false}" == true ]; then + set -ex +else + set -e +fi -die() { - echo >&2 "$@" - exit 1 +function printSeparator() { + echo "---------------------------------" } -usage() { - echo "usage: -i [infile]>" - echo ${sep} - echo "-i = Inpute file" - echo ${sep} +function die() { + echo >&2 "$@" + exit 1 } -collect() { - film='' - cnt=1 - #for b in `ls`; do - for file in *.$1; do - [ -f ${file} ] - name=$2 - - for c in ${cnt}; do - if [ "$film" == '' ] || [ "$film" == 'VIDEO_TS.VOB' ]; then - film=${file} - else - case $3 in - true) film="$film|$file" ;; - esac - fi - done - cnt=$(expr ${cnt} + 1) - done - needle="|" - num=$(grep -o "$needle" <<<"$film" | wc -l) - num=$(expr ${num} + 1) +function usage() { + echo "usage: -i [infile]>" + printSeparator + echo "-i = Inpute file" + printSeparator } # default variables +# current directory +dir=$(pwd) +# name of the input file for which the keyframes should be extracted infile='' # alternative to get some information from the source file: # ffprobe -v quiet -print_format json -show_format -show_streams ~/sourceFile.mov while getopts ':i:' OPTION; do - case ${OPTION} in - i) infile=$OPTARG ;; - *) echo 'Unknown Parameter' ;; - esac + case ${OPTION} in + i) infile=$OPTARG ;; + *) echo 'Unknown Parameter' ;; + esac done if [ $# -eq 0 ]; then - usage - die '' + usage + die '' fi -# check the arguments: if they exist -num='^[0-9]+$' - -# infile must be given and has to be an mp4 +# infile must be given and has to be an mp4 by checking if mime-type is video/mp4 if [ -z "${infile}" ]; then - # must be given - usage - die "ERROR: The Input File (-i) is missing" + # must be given + usage + die "ERROR: The Input File (-i) is missing" fi - -# check if mime-type is video/mp4 -if file -b --mime-type "${infile}" -ne "video/mp4"; then - die "ERROR: The Input File (-i) is not mp4" +if [ "$(file -b --mime-type "${infile}")" != "video/mp4" ]; then + die "ERROR: The Input File (-i) is not mp4" fi # get the right output path, the file and the filename (without extension) @@ -86,19 +65,17 @@ dir=$(dirname "${infile}") file=$(basename "${infile}") name="${file%.*}" -cd $dir +cd "${dir}" -# -- # store the frames and matrix files in specific folder; # folder has same unique name as the uploaded file -mkdir -p $name -cd $name +mkdir -p "${name}" +cd "${name}" -# -- # read frame rate from input file -framerate=$(ffprobe -v 0 -of csv=p=0 -select_streams v:0 -show_entries stream=r_frame_rate ../"${file}") +frame_rate=$(ffprobe -v 0 -of csv=p=0 -select_streams v:0 -show_entries stream=r_frame_rate ../"${file}") -IFS="/" read -a array <<<"$framerate" +IFS="/" read -ra array <<<"$frame_rate" numerator="${array[0]}" denumerator="${array[1]}" @@ -110,35 +87,35 @@ aspect=$(ffprobe -v error -select_streams v:0 -show_entries stream=display_aspec # if aspect ratio does not exist in video file metadata if [ ! -z {$aspect} ]; then - # get aspect ratio from video dimension (width and height) - aspectW=$(ffprobe -v error -select_streams v:0 -show_entries stream=width -of csv=s=x:p=0 ../"${file}") - aspectH=$(ffprobe -v error -select_streams v:0 -show_entries stream=height -of csv=s=x:p=0 ../"${file}") -else - IFS=':' read -a array <<<"$aspect" - aspectW="${array[0]}" - aspectH="${array[1]}" + # get aspect ratio from video dimension (width and height) + aspectW=$(ffprobe -v error -select_streams v:0 -show_entries stream=width -of csv=s=x:p=0 ../"${file}") + aspectH=$(ffprobe -v error -select_streams v:0 -show_entries stream=height -of csv=s=x:p=0 ../"${file}") +else + IFS=':' read -a array <<<"$aspect" + aspectW="${array[0]}" + aspectH="${array[1]}" fi # -- -# framesize +# calculate frame width, height and size framewidth='256' frameheight=$(($framewidth * $aspectH / $aspectW)) framesize=${framewidth}'x'${frameheight} -echo ${sep} +# -- +printSeparator echo 'Start with frame export' # check the outpath for the frames; # if exists, delete it and create new if [ -d "frames" ]; then - rm -rf ./frames + rm -rf ./frames fi mkdir -p 'frames' -framestart=$(echo ${start} + 1 | bc) -ffmpeg -i ../${file} -an -ss 1 -f image2 -s ${framesize} -vf framestep=${fps} frames/${name}'_f_%d.jpg' 2>&1 +ffmpeg -i ../"${file}" -an -ss 0 -f image2 -s ${framesize} -vf framestep="${fps}" frames/"${name}"'_f_%d.jpg' 2>&1 echo 'Done' -echo ${sep} +printSeparator # -- # create the matrix files @@ -147,15 +124,14 @@ echo 'Start with creating matrix file' # change directory frames cd frames # Get the number of files: one image = one second of movie -# numfiles is equivalent to the movie duration (in seconds) -numfiles=(*) -numfiles=${#numfiles[@]} +# number_of_frame_files is equivalent to the movie duration (in seconds) +readonly number_of_frame_files=$(find . -type f | wc -l) image="${name}_f_1.jpg" # check if file exists if [[ ! -f "${image}" ]]; then - die "File not found: ${image}" + die "File not found: ${image}" fi # grab the identify string, make sure it succeeded @@ -179,38 +155,42 @@ frame_height=$(echo "scale=0; ${matrix_height}/6" | bc) frame_size="${frame_width}x${frame_height}" # get every 10th image; start with the image number 5; # how many matrixes will it produce? -calcmatrix=$(echo "scale=4; (${numfiles}/10)/36" | bc) +calcmatrix=$(echo "scale=4; (${number_of_frame_files}/10)/36" | bc) +# Round the exact fraction to the to the next highest integer +# for the number of matrix files to create 'nummatrix' +readonly interim=$(echo "${calcmatrix}+1"|bc) +readonly nummatrix=${interim%.*} + echo '#Matrix (calculated) '${calcmatrix} -nummatrix=$(echo ${calcmatrix} | awk '{printf("%.0f\n",$1 + 0.75)}') echo '#Matrix (rounded) '${nummatrix} t=0 while [ ${t} -lt ${nummatrix} ]; do - echo 'Matrix nr '${t} - firstframe=$(echo "scale=0; ${t}*360+5" | bc) - MATRIX=$(find *_${firstframe}.jpg)' ' - - c=1 - while [ ${c} -lt 36 ]; do - sec=$(echo "scale=0; ${firstframe}+(${c}*10)" | bc) - if [ $sec -lt $numfiles ]; then - img="${name}_f_${sec}.jpg" - if [ -f ${img} ]; then - MATRIX+=${img}' ' - fi - fi - - let c=c+1 - done - - # here we make the montage of every matrix file - # montage -size 320x180 DB_4_5.jpg DB_4_15.jpg DB_4_25.jpg DB_4_35.jpg -geometry +0+0 montage1.jpg - # $(echo "montage -size ${matrix_size} ${MATRIX} -tile 6x6 -geometry +0+0 -resize ${frame_size} ../matrix/${name}'_m_'${t}'.jpg'") - montage -size ${matrix_size} ${MATRIX} -tile 6x6 -geometry +0+0 -resize ${frame_size} ../${name}'_m_'${t}'.jpg' 2>&1 - - let t=t+1 + echo 'Matrix nr '${t} + firstframe=$(echo "scale=0; ${t}*360+1" | bc) + MATRIX=$(find *_${firstframe}.jpg)' ' + + c=1 + while [ ${c} -lt 36 ]; do + sec=$(echo "scale=0; ${firstframe}+(${c}*10)" | bc) + if [ $sec -lt $number_of_frame_files ]; then + img="${name}_f_${sec}.jpg" + if [ -f ${img} ]; then + MATRIX+=${img}' ' + fi + fi + + let c=c+1 + done + + # here we make the montage of every matrix file + # montage -size 320x180 DB_4_5.jpg DB_4_15.jpg DB_4_25.jpg DB_4_35.jpg -geometry +0+0 montage1.jpg + # $(echo "montage -size ${matrix_size} ${MATRIX} -tile 6x6 -geometry +0+0 -resize ${frame_size} ../matrix/${name}'_m_'${t}'.jpg'") + montage -size ${matrix_size} ${MATRIX} -tile 6x6 -geometry +0+0 -resize ${frame_size} ../${name}'_m_'${t}'.jpg' 2>&1 + + let t=t+1 done echo 'Done' -echo ${sep} +printSeparator