#!/bin/sh
# shellcheck disable=SC2086

# screencast - POSIX-compliant shell script to record a X11 desktop
#
# Copyright (c) 2015-2020 Daniel Bermond < gmail.com: danielbermond >
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

#########################################
#           general settings            #
#########################################

# program settings
screencast_version='v1.6.0'
screencast_website='https://github.com/dbermond/screencast/'

# system related settings
queue_size='3096'            # ffmpeg thread queue size
ffplay_volume='35'           # ffplay playback volume (0-100)
expire_time_short='1700'     # expire time for short notifications
expire_time_normal='3000'    # expire time for normal notifications
expire_time_long='5000'      # expire time for long notifications
savedir="$(pwd)"             # path to save output files
tmpdir="${XDG_CACHE_HOME:-"${HOME}/.cache"}/screencast" # path to save temporary files
error_icon='dialog-error'
record_icon='media-record'
encode_icon='media-playback-stop'
finish_icon_generic='video-x-generic'
finish_icon_oxygen='/usr/share/icons/oxygen/base/128x128/actions/dialog-ok-apply.png'
finish_sound='/usr/share/sounds/freedesktop/stereo/complete.oga'

# control settings (controls various aspects)
saving_output='true'         # if user is saving an output video (true, false)
recording_audio='true'       # if user is recording audio (true, false)
select_region='false'        # mouse screen region selection (true, false)
auto_filename='false'        # auto choose output filename based on date/time
keep_video='false'           # keep live streaming or the tmp video (true, false)
fade='none'                  # fade effect (in, out, both, none)
volume_increase='false'      # volume increase effect (true, false)
watermark='false'            # watermark effect (true, false)
pngoptimizer='none'          # png (watermark) optimizer (truepng, pingo, optipng, opt-png, none)
webcam_overlay='false'       # webcam overlay effect (true, false)
streaming='false'            # live streaming (true, false)
one_step='false'             # one step process (true, false)
one_step_lossless='false'    # one step process without encoding the lossless video (true, false)
fixed_length='0'             # fixed length video in seconds (0 disable)
notifications='true'         # desktop notifications (true, false)
audio_input_setted='false'   # if audio input    was setted by cmd line with -i
audio_encoder_setted='false' # if audio encoder  was setted by cmd line with -a
display_setted='false'       # if display        was setted by cmd line with -d
border_setted='false'        # if border         was setted by cmd line with -b
video_encoder_setted='false' # if video encoder  was setted by cmd line with -v
vaapi_device_setted='false'  # if a DRM node     was setted by cmd line with -A
video_size_setted='false'    # if video size     was setted by cmd line with -s
video_posi_setted='false'    # if video position was setted by cmd line with -p
video_rate_setted='false'    # if video rate/fps was setted by cmd line with -r
format_setted='false'        # if video format   was setted by cmd line with -f
fade_setted='false'          # if fade effect    was setted by cmd line with -e
volume_factor_setted='false' # if volume factor  was setted by cmd line with -m
wmark_size_setted='false'    # if wmark size     was setted by cmd line with -z
wmark_posi_setted='false'    # if wmark position was setted by cmd line with -k
wmark_font_setted='false'    # if wmark font     was setted by cmd line with -c
webcam_input_setted='false'  # if webcam input   was setted by cmd line with -I
webcam_size_setted='false'   # if webcam size    was setted by cmd line with -Z
webcam_posi_setted='false'   # if wcam position  was setted by cmd line with -P
webcam_rate_setted='false'   # if webcam fps     was setted by cmd line with -R
one_step_setted='false'      # if one step       was setted by cmd line with -1
fixed_length_setted='false'  # if fixed length   was setted by cmd line with -x
pngoptimizer_setted='false'  # if png optimizer  was setted by cmd line with -g
outputdir_setted='false'     # if output dir     was setted by cmd line with -o
tmpdir_setted='false'        # if tmp dir        was setted by cmd line with -t

# audio settings
audio_input='default'        # audio input
audio_encoder='aac'          # audio encoder
volume_factor='1.0'          # volume increase effect factor (0.0/1.0 disable)
audio_channel_layout='-channel_layout stereo'
audio_input_options="-f alsa -thread_queue_size ${queue_size} -sample_rate 48000 -channels 2 ${audio_channel_layout}"

# video settings
video_encoder='x264'               # video encoder
pixel_format='yuv420p'             # pixel format
format='mp4'                       # container format (file extension) for output
video_position='0,0' #200,234      # X and Y screen coordinates to record video from
video_size='640x480'               # video size (resolution)
video_rate='25'                    # video framerate (fps)
corner_padding='10'                # video corner padding (for watermark and webcam effects)
vaapi_device='/dev/dri/renderD128' # DRM render node (vaapi device) (for vaapi video encoders)
display=':0.0'                     # display (and screen) number(s) ($DISPLAY environment variable)
border='2'                         # tickness of the screen region border delimiter (0 to disable border)
border_options="-show_region 1 -region_border ${border}"
video_input_options="-f x11grab -thread_queue_size ${queue_size} -probesize 20M"

# metadata settings
metadata="comment=$(printf '%s\n%s' "Created with screencast ${screencast_version}" "${screencast_website}")"

# default options for later comparison (to print informative messages)
audio_encoder_default="$audio_encoder"
video_encoder_default="$video_encoder"
format_default="$format"

#########################################
#       container format settings       #
#########################################

# supported container formats (one per line for accurate grepping and easy deletion)
supported_formats_all="$(cat <<- __EOF__
		mp4
		mov
		mkv
		webm
		ogg
		ogv
		flv
		nut
		wmv
		asf
		avi
__EOF__
)"
supported_formats_lossless="$(cat <<- __EOF__
		matroska
		nut
__EOF__
)"

# container formats that, depending on the selected audio/video encoder, may be unplayable in some players
possible_unplayable_formats="$(cat <<- __EOF__
		mp4
		mov
__EOF__
)"

# lossless container format settings functions
# description:
#   make checks and settings for container format to store lossless video
#   (for the 1st step, lossless recording)
# arguments: none
# return value: not relevant
# return code (status):
#   0 - the detected ffmpeg build has support for the tested container format
#   1 - the detected ffmpeg build has no support for the tested container format
# sets special variable: $rec_extension - file extension (container format) of the lossless video
# note:
#   the program will exit with error if the tested container format is not supported
#   by the detected ffmpeg build
lossless_format_settings_matroska() {
    if check_component  matroska       muxer &&
       check_component 'matroska,webm' demuxer
    then
        rec_extension='mkv'
    else
        return 1
    fi
}

lossless_format_settings_nut() {
    if check_component nut muxer &&
       check_component nut demuxer
    then
        rec_extension='nut'
    else
        return 1
    fi
}

# format settings functions
# description: make checks and settings for the selected container format
# arguments: none
# return value: none
# return code (status): not relevant
# sets special variables:
#   $supported_audiocodecs - audio encoders supported by the selected container format (one per line)
#   $supported_videocodecs - video encoders supported by the selected container format (one per line)
# note:
#   the program will exit with error if the selected container format is not supported
#   by the detected ffmpeg build
format_settings_mp4() {
    supported_audiocodecs="$(cat <<- __EOF__
		$audiocodecs_aac
		$audiocodecs_opus
		$audiocodecs_vorbis
		$audiocodecs_mp3
__EOF__
)"
    supported_videocodecs="$(cat <<- __EOF__
		$videocodecs_h264
		$videocodecs_hevc
		$videocodecs_vp9
		$videocodecs_av1
__EOF__
)"
    possible_unplayable_videocodecs="$(cat <<- __EOF__
		$videocodecs_vp9
__EOF__
)"
    
    # check if the detected ffmpeg build has support for the mp4/mov muxer
    check_component "$format" muxer || component_error "$format" muxer true
    
    # move the moov atom to the beginning of the file
    if [ "$streaming"  = 'true' ]
    then
        [ "$saving_output" = 'true' ] && tee_faststart='[movflags=+faststart]'
    else
        ff_faststart='-movflags +faststart'
    fi
}

format_settings_mov() {
    format_settings_mp4 "$@"
    
    supported_audiocodecs="$(cat <<- __EOF__
		$audiocodecs_aac
		$audiocodecs_vorbis
		$audiocodecs_mp3
		$audiocodecs_wma
__EOF__
)"
    supported_videocodecs="$(cat <<- __EOF__
		$videocodecs_h264
		$videocodecs_hevc
		$videocodecs_theora
		$videocodecs_wmv
__EOF__
)"
    possible_unplayable_audiocodecs="$(cat <<- __EOF__
		$audiocodecs_vorbis
		$audiocodecs_wma
__EOF__
)"
    possible_unplayable_videocodecs="$(cat <<- __EOF__
		$videocodecs_theora
		$videocodecs_wmv
__EOF__
)"
}

format_settings_mkv() {
    # note: mkv container formats supports all valid audio and video encoders for this program
    supported_audiocodecs="$supported_audiocodecs_all"
    supported_videocodecs="$supported_videocodecs_all"
    
    # check if the detected ffmpeg build has support for the matroska (mkv) muxer
    if [ "$rec_extension" != "$format" ]
    then
        check_component matroska muxer || component_error matroska muxer true
    fi
}

format_settings_webm() {
    supported_audiocodecs="$(cat <<- __EOF__
		$audiocodecs_opus
		$audiocodecs_vorbis
__EOF__
)"
    supported_videocodecs="$(cat <<- __EOF__
		$videocodecs_vp8
		$videocodecs_vp9
		$videocodecs_av1
__EOF__
)"
    
    # check if the detected ffmpeg build has support for the webm muxer
    check_component "$format" muxer || component_error "$format" muxer true
    
    # auto choose audio/video encoder if needed
    if [ "$audio_encoder_setted" = 'false' ]
    then
        audio_encoder="$(printf '%s' "$audiocodecs_opus" | head -n1)"
        audio_outstr='(auto chosen)'
    fi
    
    if [ "$video_encoder_setted" = 'false' ]
    then
        video_encoder="$(printf '%s' "$videocodecs_vp9" | head -n1)"
        video_outstr='(auto chosen)'
    fi
}

format_settings_ogg() {
    supported_audiocodecs="$(cat <<- __EOF__
		$audiocodecs_opus
		$audiocodecs_vorbis
__EOF__
)"
    supported_videocodecs="$(cat <<- __EOF__
		$videocodecs_vp8
		$videocodecs_theora
__EOF__
)"
    
    # check if the detected ffmpeg build has support for the ogg/ogv muxer
    check_component "$format" muxer || component_error "$format" muxer true
    
    # auto choose audio/video encoder if needed
    if [ "$audio_encoder_setted" = 'false' ]
    then
        audio_encoder="$(printf '%s' "$audiocodecs_vorbis" | head -n1)"
        audio_outstr='(auto chosen)'
    fi
    
    if [ "$video_encoder_setted" = 'false' ]
    then
        video_encoder="$(printf '%s' "$videocodecs_theora" | head -n1)"
        video_outstr='(auto chosen)'
    fi
}

format_settings_ogv() {
    format_settings_ogg "$@"
}

format_settings_flv() {
    supported_audiocodecs="$(cat <<- __EOF__
		$audiocodecs_aac
		$audiocodecs_mp3
__EOF__
)"
    supported_videocodecs="$(cat <<- __EOF__
		$videocodecs_h264
__EOF__
)"
    
    # check if the detected ffmpeg build has support for the flv muxer only if recording offline
    # (flv muxer support in ffmpeg is already checked during the live streaming checks)
    if [ "$streaming" = 'false' ]
    then
        check_component "$format" muxer || component_error "$format" muxer true
    fi
}

format_settings_nut() {
    # note: nut container formats supports all valid audio and video encoders for this program
    supported_audiocodecs="$supported_audiocodecs_all"
    supported_videocodecs="$supported_videocodecs_all"
    
    # check if the detected ffmpeg build has support for the nut muxer
    if [ "$rec_extension" != "$format" ]
    then
        check_component "$format" muxer || component_error "$format" muxer true
    fi
}

format_settings_wmv() {
    supported_audiocodecs="$(cat <<- __EOF__
		$audiocodecs_aac
		$audiocodecs_vorbis
		$audiocodecs_mp3
		$audiocodecs_wma
__EOF__
)"
    supported_videocodecs="$(cat <<- __EOF__
		$videocodecs_h264
		$videocodecs_vp8
		$videocodecs_vp9
		$videocodecs_theora
		$videocodecs_wmv
		$videocodecs_av1
__EOF__
)"
    
    # check if the detected ffmpeg build has support for the asf muxer
    # note: asf muxer is used for wmv (and wma) container format
    check_component asf muxer || component_error asf muxer true
}

format_settings_asf() {
    format_settings_wmv "$@"
}

format_settings_avi() {
    # note: avi container formats supports all valid video encoders for this program
    supported_audiocodecs="$(cat <<- __EOF__
		$audiocodecs_aac
		$audiocodecs_vorbis
		$audiocodecs_mp3
		$audiocodecs_wma
__EOF__
)"
    supported_videocodecs="$supported_videocodecs_all"
    
    # check if the detected ffmpeg build has support for the avi muxer
    check_component "$format" muxer || component_error "$format" muxer true
}

#########################################
#         audio encoder settings        #
#########################################

# supported audio encoders (one per line for accurate grepping and easy deletion)
supported_audiocodecs_all="$(cat <<- __EOF__
		aac
		opus
		vorbis
		mp3lame
		shine
		wma
		none
__EOF__
)"
audiocodecs_aac="$(cat <<- __EOF__
		aac
__EOF__
)"
audiocodecs_opus="$(cat <<- __EOF__
		opus
__EOF__
)"
audiocodecs_vorbis="$(cat <<- __EOF__
		vorbis
__EOF__
)"
audiocodecs_mp3="$(cat <<- __EOF__
		mp3lame
		shine
__EOF__
)"
audiocodecs_wma="$(cat <<- __EOF__
		wma
__EOF__
)"
supported_audiocodecs_lossless="$(cat <<- __EOF__
		pcm_s16le
		flac
__EOF__
)"
# lossless audio encoder settings functions
# description:
#   make checks and settings for lossless audio encoder (for the 1st step, lossless recording)
# arguments: none
# return value: not relevant
# return code (status):
#   0 - the detected ffmpeg build has support for the tested lossless audio_encoder
#   1 - the detected ffmpeg build has no support for the tested lossless audio encoder
# sets special variable: $audio_record_codec - ffmpeg lossless audio codec option and settings
# note:
#   the program will exit with error if the selected lossless audio encoder is not supported
#   by the detected ffmpeg build
lossless_audiocodec_settings_pcm_s16le() {
    if check_component pcm_s16le encoder &&
       check_component pcm_s16le decoder
    then
        audio_record_codec='-codec:a pcm_s16le -ar 48000 -ac 2'
    else
        return 1
    fi
}

lossless_audiocodec_settings_flac() {
    if check_component flac encoder &&
       check_component flac decoder
    then
        audio_record_codec='-codec:a flac -b:a 320k -ar 48000 -ac 2'
    else
        return 1
    fi
}

# audio encoder settings functions
# description: make checks and settings for the selected audio encoder
# arguments: none
# return value: none
# return code (status): not relevant
# sets special variable: $audio_encode_codec - ffmpeg audio codec option and settings
# note:
#   the program will exit with error if the selected audio encoder is not supported
#   by the detected ffmpeg build
audiocodec_settings_aac() {
    if check_component libfdk_aac encoder
    then
        if [ "$streaming" = 'true' ]
        then
            audio_encode_codec='-codec:a libfdk_aac -b:a  96k -ar 44100 -ac 2'
        else
            audio_encode_codec='-codec:a libfdk_aac -b:a 128k -ar 44100 -ac 2'
        fi
    else
        check_component aac encoder || component_error 'libfdk_aac or aac' 'audio encoder' true
        
        if [ "$streaming" = 'true' ]
        then
            audio_encode_codec='-codec:a aac -b:a  96k -ar 44100 -ac 2'
        else
            audio_encode_codec='-codec:a aac -b:a 128k -ar 44100 -ac 2'
        fi
    fi
}

audiocodec_settings_vorbis() {
    check_component libvorbis encoder || component_error libvorbis 'audio encoder' true
    audio_encode_codec='-codec:a libvorbis -qscale:a 4 -ar 44100 -ac 2'
}

audiocodec_settings_opus() {
    check_component libopus encoder || component_error libopus 'audio encoder' true
    audio_encode_codec='-codec:a libopus -b:a 128k -ar 48000 -ac 2'
}

audiocodec_settings_mp3lame() {
    check_component libmp3lame encoder || component_error libmp3lame 'audio encoder' true
    
    if [ "$streaming" = 'true' ]
    then
        audio_encode_codec='-codec:a libmp3lame -b:a  96k -ar 44100 -ac 2'
    else
        audio_encode_codec='-codec:a libmp3lame -b:a 128k -ar 44100 -ac 2'
    fi
}

audiocodec_settings_shine() {
    check_component libshine encoder || component_error libshine 'audio encoder' true
    
    if [ "$streaming" = 'true' ]
    then
        audio_encode_codec='-codec:a libshine -b:a  96k -ar 44100 -ac 2'
    else
        audio_encode_codec='-codec:a libshine -b:a 128k -ar 44100 -ac 2'
    fi
}

audiocodec_settings_wma() {
    check_component wmav2 encoder || component_error wmav2 'audio encoder' true
    audio_encode_codec='-codec:a wmav2 -b:a 128k -ar 44100 -ac 2'
}

#########################################
#         video encoder settings        #
#########################################

# supported video encoders (one per line for accurate grepping and easy deletion)
supported_videocodecs_all="$(cat <<- __EOF__
		x264
		h264_nvenc
		h264_vaapi
		h264_qsv
		x265
		kvazaar
		svt_hevc
		hevc_nvenc
		hevc_vaapi
		hevc_qsv
		vp8
		vp8_vaapi
		vp9
		svt_vp9
		vp9_vaapi
		theora
		wmv
		aom_av1
		svt_av1
		rav1e
		none
__EOF__
)"
supported_videocodecs_software="$(cat <<- __EOF__
		x264
		x265
		kvazaar
		svt_hevc
		vp8
		vp9
		svt_vp9
		theora
		wmv
		aom_av1
		svt_av1
		rav1e
__EOF__
)"
videocodecs_vaapi="$(cat <<- __EOF__
		h264_vaapi
		hevc_vaapi
		vp8_vaapi
		vp9_vaapi
__EOF__
)"
videocodecs_qsv="$(cat <<- __EOF__
		h264_qsv
		hevc_vaapi
__EOF__
)"
videocodecs_h264="$(cat <<- __EOF__
		x264
		h264_nvenc
		h264_vaapi
		h264_qsv
__EOF__
)"
videocodecs_hevc="$(cat <<- __EOF__
		x265
		kvazaar
		svt_hevc
		hevc_nvenc
		hevc_vaapi
		hevc_qsv
__EOF__
)"
videocodecs_vp8="$(cat <<- __EOF__
		vp8
		vp8_vaapi
__EOF__
)"
videocodecs_vp9="$(cat <<- __EOF__
		vp9
		svt_vp9
		vp9_vaapi
__EOF__
)"
videocodecs_theora="$(cat <<- __EOF__
		theora
__EOF__
)"
videocodecs_wmv="$(cat <<- __EOF__
		wmv
__EOF__
)"
videocodecs_av1="$(cat <<- __EOF__
		aom_av1
		svt_av1
		rav1e
__EOF__
)"
videocodecs_av1_slow="$(cat <<- __EOF__
		aom_av1
		rav1e
__EOF__
)"
videocodecs_svt="$(cat <<- __EOF__
		svt_av1
		svt_hevc
		svt_vp9
__EOF__
)"
supported_videocodecs_lossless="$(cat <<- __EOF__
		ffv1
		ffvhuff
		huffyuv
__EOF__
)"
largefile_videocodecs_lossless="$(cat <<- __EOF__
		ffvhuff
		huffyuv
__EOF__
)"

# description: defines video encoders to be used with dimension_msg()
# arguments: none
# return value: none
# return code (status): not relevant
# sets special variables:
#   $msg_speedloss_videocodecs -
#     video encoders that can lead to speedloss with non multiple of 8 dimensions (one per line)
#   $msg_requirement_videocodecs - video encoders requires multiple of 8 dimensions (one per line)
get_videocodecs_for_nonmulti8_msg() {
    msg_speedloss_videocodecs="$(del_multiline "$supported_videocodecs_all" "$(cat <<- __EOF__
		$videocodecs_hevc
		$videocodecs_svt
__EOF__
)")"
    msg_requirement_videocodecs="$(cat <<- __EOF__
		$videocodecs_hevc
		$videocodecs_svt
__EOF__
)"
}

# lossless video encoder settings functions
# description:
#   make checks and settings for lossless video encoder (for the 1st step, lossless recording)
# arguments: none
# return value: not relevant
# return code (status):
#   0 - the detected ffmpeg build has support for the tested lossless video_encoder
#   1 - the detected ffmpeg build has no support for the tested lossless video encoder
# sets special variable: $video_record_codec - ffmpeg lossless video codec option and settings
# note:
#   the program will exit with error if the selected lossless video encoder is not supported
#   by the detected ffmpeg build
lossless_videocodec_settings_ffv1() {
    if check_component ffv1 encoder &&
       check_component ffv1 decoder
    then
        video_record_codec='ffv1 -level 3 -slicecrc 1'
    else
        return 1
    fi
}

lossless_videocodec_settings_ffvhuff() {
    if check_component ffvhuff encoder &&
       check_component ffvhuff decoder
    then
        video_record_codec='ffvhuff'
    else
        return 1
    fi
}

lossless_videocodec_settings_huffyuv() {
    if check_component huffyuv encoder &&
       check_component huffyuv decoder
    then
        video_record_codec='huffyuv'
    else
        return 1
    fi
}

# video encoder settings functions
# description: make checks and settings for the selected video encoder
# arguments: none
# return value: none
# return code (status): not relevant
# sets special variable: $video_encode_codec - ffmpeg video codec settings
# note:
#   the program will exit with error if the selected video encoder is not supported
#   by the detected ffmpeg build
videocodec_settings_x264() {
    check_component libx264 encoder || component_error libx264 'video encoder' true
    
    if [ "$streaming" = 'true' ]
    then
        video_encode_codec='libx264 -crf 30 -preset veryfast'
    else
        if [ "$one_step" = 'true' ]
        then
            video_encode_codec='libx264 -crf 21 -preset ultrafast'
        else
            video_encode_codec='libx264 -crf 21 -preset veryslow'
        fi
    fi
}

videocodec_settings_h264_nvenc() {
    check_component h264_nvenc encoder || component_error h264_nvenc 'video encoder' true
    video_encode_codec='h264_nvenc -qmin 10 -qmax 42 -preset slow -cq 10 -bf 2 -g 150'
}

videocodec_settings_h264_vaapi() {
    check_component h264_vaapi encoder || component_error h264_vaapi 'video encoder' true
    check_vaapi_device
    video_encode_codec='h264_vaapi -qp 18'
}

videocodec_settings_h264_qsv() {
    check_component h264_qsv encoder || component_error h264_qsv 'video encoder' true
    video_encode_codec='h264_qsv -preset:v medium -rdo 1 -qscale:v 5 -look_ahead 0'
}

videocodec_settings_x265() {
    check_component libx265 encoder || component_error libx265 'video encoder' true
    
    if [ "$one_step" = 'true' ]
    then
        video_encode_codec='libx265 -crf 25 -preset ultrafast'
    else
        video_encode_codec='libx265 -crf 25 -preset veryslow'
    fi
}

videocodec_settings_kvazaar() {
    check_component libkvazaar encoder || component_error libkvazaar 'video encoder' true
    
    if [ "$one_step" = 'true' ]
    then
        video_encode_codec='libkvazaar -kvazaar-params preset=ultrafast'
    else
        video_encode_codec='libkvazaar -kvazaar-params preset=veryslow'
    fi
}

videocodec_settings_svt_hevc() {
    check_component libsvt_hevc encoder || component_error libsvt_hevc 'video encoder' true
    video_encode_codec='libsvt_hevc -rc vbr -tune sq'
}

videocodec_settings_hevc_nvenc() {
    check_component hevc_nvenc encoder || component_error hevc_nvenc 'video encoder' true
    video_encode_codec='hevc_nvenc -preset slow'
}

videocodec_settings_hevc_vaapi() {
    check_component hevc_vaapi encoder || component_error hevc_vaapi 'video encoder' true
    check_vaapi_device
    video_encode_codec='hevc_vaapi -qp 22'
}

videocodec_settings_hevc_qsv() {
    check_component hevc_qsv encoder || component_error hevc_qsv 'video encoder' true
    video_encode_codec='hevc_qsv -load_plugin hevc_hw -preset:v medium -rdo 1 -qscale:v 5 -look_ahead 0'
}

videocodec_settings_vp8() {
    check_component libvpx encoder || component_error libvpx 'video encoder' true
    video_encode_codec='libvpx -crf 8 -b:v 2M'
}

videocodec_settings_vp8_vaapi() {
    check_component vp8_vaapi encoder || component_error vp8_vaapi 'video encoder' true
    check_vaapi_device
    video_encode_codec='vp8_vaapi -global_quality 30'
}

videocodec_settings_vp9() {
    check_component libvpx-vp9 encoder || component_error libvpx-vp9 'video encoder' true
    video_encode_codec='libvpx-vp9 -crf 30 -b:v 0'
}

videocodec_settings_svt_vp9() {
    check_component libsvt_vp9 encoder || component_error libsvt_vp9 'video encoder' true
    video_encode_codec='libsvt_vp9 -qp 29'
}

videocodec_settings_vp9_vaapi() {
    check_component vp9_vaapi encoder || component_error vp9_vaapi 'video encoder' true
    check_vaapi_device
    video_encode_codec='vp9_vaapi -global_quality 80'
}

videocodec_settings_theora() {
    check_component libtheora encoder || component_error libtheora 'video encoder' true
    video_encode_codec='libtheora -qscale:v 5'
}

videocodec_settings_wmv() {
    check_component wmv2 encoder || component_error wmv2 'video encoder' true
    video_encode_codec='wmv2 -qscale:v 3'
}

videocodec_settings_aom_av1() {
    check_component libaom-av1 encoder || component_error libaom-av1 'video encoder' true
    video_encode_codec='libaom-av1 -crf 27 -b:v 0 -strict experimental'
}

videocodec_settings_svt_av1() {
    check_component libsvtav1 encoder || component_error libsvtav1 'video encoder' true
    video_encode_codec='libsvtav1 -qp 36'
}

videocodec_settings_rav1e() {
    check_component librav1e encoder || component_error librav1e 'video encoder' true
    video_encode_codec='librav1e -qp 90 -speed 5 -rav1e-params low_latency=true'
}

#########################################
#            effects settings           #
#########################################

# watermark settings
watermark_font='DejaVu-Sans'
watermark_size='256x36'
watermark_position='bottomright'
    # good absolute position values for hd720p (1280x720) with default watermark size:
    # 970,10  - top right corner
    # 10,10   - top left  corner
    # 970,688 - bottom right corner
    # 10,688  - bottom left  corner
    # 550,350 - centralized

# webcam settings
webcam_input_options="-f video4linux2 -thread_queue_size ${queue_size} -probesize 10M"
webcam_size='320x240'
webcam_input='/dev/video0'
webcam_position='topright'

# effect settings functions
# description: make settings for the selected effect(s)
# arguments: none
# return value: none
# return code (status): not relevant
# sets special variables: various, depending on the effect
get_supported_fade() {
    supported_fade="$(cat <<- __EOF__
		none
		in
		out
		both
__EOF__
)"
}

get_supported_pngoptmz() {
    supported_pngoptmz="$(cat <<- __EOF__
		none
		optipng
		oxipng
		opt-png
		truepng
		pingo
__EOF__
)"
}

get_fade_settings() {
    fade_color='black'      # color to be used by the video fade effect
    fade_length='0.6'       # length (in seconds) of video fade effect itself
    fade_solid_length='0.1' # solid color length (in seconds) of video fade effect
}

get_supported_streaming_settings() {
    # container formats that supports the audio/video encoders of the allowed flv muxers
    supported_streaming_formats="$(cat <<- __EOF__
		mp4
		mov
		mkv
		flv
		nut
		wmv
		asf
		avi
__EOF__
)"
    
    # flv muxer restrictions
    supported_streaming_audiocodecs="$(cat <<- __EOF__
		$audiocodecs_aac
		$audiocodecs_mp3
__EOF__
)"
    supported_streaming_videocodecs="$(cat <<- __EOF__
		$videocodecs_h264
__EOF__
)"
}

get_pngoptmz_settings_truepng() {
    pngoptmz_settings='-o max'
}

get_pngoptmz_settings_pingo() {
    pngoptmz_settings='-s8'
}

get_pngoptmz_settings_optipng() {
    pngoptmz_settings='-o 7'
}

get_pngoptmz_settings_oxipng() {
    pngoptmz_settings='-o 6 --strip safe'
}

get_pngoptmz_settings_advdef() {
    advdef_settings='-z4i10'
}

#########################################
#           get command line            #
#########################################

# description: get command line arguments and adjust related variables
# arguments:
#   the positional parameters passed with double quotes ("$@")
# return value: none
# return code (status): not relevant
# sets special variables: $shift_count - how many shifts were executed
get_cmd_line() {
    shift_count='0' # counts how many 'shift' commands were executed
    
    while :
    do
        # since this is a very long case statement only the first block will
        # be commented. almost all other blocks follows the same sequence/logic
        # and comments will be inserted only for what is different.
        case "$1" in
            # short option and long option without '='
            -s|--size)
                # search for an argument
                if [ -n "$2" ]
                then
                    # errors out if no argument was entered after the option
                    # (will check for a leading '-' in the next parameter,
                    #  meaning that another option was found)
                    if printf '%.1s\n' "$2" | grep -q '-'
                    then
                        command_error '--size (-s)'
                    else
                        video_size="$2"
                        video_size_setted='true'
                        shift && shift_count="$((shift_count + 1))"
                    fi
                # errors out if no argument is found
                else
                    command_error '--size (-s)'
                fi
                ;;
            # long option with '='
            --size=?*)
                video_size="${1#*=}" # assign value after '='
                video_size_setted='true'
                ;;
            # errors out if a long option with '=' has nothing following '='
            --size=)
                command_error '--size (-s)'
                ;;
            
            -p|--position)
                if [ -n "$2" ]
                then
                    if printf '%.1s\n' "$2" | grep -q '-'
                    then
                        command_error '--position (-p)'
                    else
                        video_position="$2"
                        video_posi_setted='true'
                        shift && shift_count="$((shift_count + 1))"
                    fi
                else
                    command_error '--position (-p)'
                fi
                ;;
            --position=?*)
                video_position="${1#*=}"
                video_posi_setted='true'
                ;;
            --position=)
                command_error '--position (-p)'
                ;;
            
            -d|--display)
                if [ -n "$2" ]
                then
                    if printf '%.1s\n' "$2" | grep -q '-'
                    then
                        command_error '--display (-d)'
                    else
                        display="$2"
                        display_setted='true'
                        shift && shift_count="$((shift_count + 1))"
                    fi
                else
                    command_error '--display (-d)'
                fi
                ;;
            --display=?*)
                display="${1#*=}"
                display_setted='true'
                ;;
            --display=)
                command_error '--display (-d)'
                ;;
            
            -b|--border)
                if [ -n "$2" ]
                then
                    if printf '%.1s\n' "$2" | grep -q '-'
                    then
                        command_error '--border (-b)'
                    else
                        border="$2"
                        border_setted='true'
                        shift && shift_count="$((shift_count + 1))"
                    fi
                else
                    command_error '--border (-b)'
                fi
                ;;
            --border=?*)
                border="${1#*=}"
                border_setted='true'
                ;;
            --border=)
                command_error '--border (-b)'
                ;;
            
            -S|--select-region) # option without argument
                select_region='true'
                ;;
            
            -r|--fps)
                if [ -n "$2" ]
                then
                    if printf '%.1s\n' "$2" | grep -q '-'
                    then
                        command_error '--fps (-r)'
                    else
                        video_rate="$2"
                        video_rate_setted='true'
                        shift && shift_count="$((shift_count + 1))"
                    fi
                else
                    command_error '--fps (-r)'
                fi
                ;;
            --fps=?*)
                video_rate="${1#*=}"
                video_rate_setted='true'
                ;;
            --fps=)
                command_error '--fps (-r)'
                ;;
            
            -f|--format)
                if [ -n "$2" ]
                then
                    if printf '%.1s\n' "$2" | grep -q '-'
                    then
                        command_error '--format (-f)'
                    else
                        format="$2"
                        format_setted='true'
                        shift && shift_count="$((shift_count + 1))"
                    fi
                else
                    command_error '--format (-f)'
                fi
                ;;
            --format=?*)
                format="${1#*=}"
                format_setted='true'
                ;;
            --format=)
                command_error '--format (-f)'
                ;;
            
            -i|--audio-input)
                if [ -n "$2" ]
                then
                    if printf '%.1s\n' "$2" | grep -q '-'
                    then
                        command_error '--audio-input (-i)'
                    else
                        audio_input="${2}"
                        audio_input_setted='true'
                        shift && shift_count="$((shift_count + 1))"
                    fi
                else
                    command_error '--audio-input (-i)'
                fi
                ;;
            --audio-input=?*)
                audio_input="${1#*=}"
                audio_input_setted='true'
                ;;
            --audio-input=)
                command_error '--audio-input (-i)'
                ;;
            
            -a|--audio-encoder)
                if [ -n "$2" ]
                then
                    if printf '%.1s\n' "$2" | grep -q '-'
                    then
                        command_error '--audio-encoder (-a)'
                    else
                        audio_encoder="$2"
                        audio_encoder_setted='true'
                        shift && shift_count="$((shift_count + 1))"
                    fi
                else
                    command_error '--audio-encoder (-a)'
                fi
                ;;
            --audio-encoder=?*)
                audio_encoder="${1#*=}"
                audio_encoder_setted='true'
                ;;
            --audio-encoder=)
                command_error '--audio-encoder (-a)'
                ;;
            
            -v|--video-encoder)
                if [ -n "$2" ]
                then
                    if printf '%.1s\n' "$2" | grep -q '-'
                    then
                        command_error '--video-encoder (-v)'
                    else
                        video_encoder="$2"
                        video_encoder_setted='true'
                        shift && shift_count="$((shift_count + 1))"
                    fi
                else
                    command_error '--video-encoder (-v)'
                fi
                ;;
            --video-encoder=?*)
                video_encoder="${1#*=}"
                video_encoder_setted='true'
                ;;
            --video-encoder=)
                command_error '--video-encoder (-v)'
                ;;
            
            -A|--vaapi-device)
                if [ -n "$2" ]
                then
                    if printf '%.1s\n' "$2" | grep -q '-'
                    then
                        command_error '--vaapi-device (-A)'
                    else
                        vaapi_device="$2"
                        vaapi_device_setted='true'
                        shift && shift_count="$((shift_count + 1))"
                    fi
                else
                    command_error '--vaapi-device (-A)'
                fi
                ;;
            --vaapi-device=?*)
                vaapi_device="${1#*=}"
                vaapi_device_setted='true'
                ;;
            --vaapi-device=)
                command_error '--vaapi-device (-A)'
                ;;
            
            -e|--fade)
                if [ -n "$2" ]
                then
                    if printf '%.1s\n' "$2" | grep -q '-'
                    then
                        command_error '--fade (-e)'
                    else
                        fade="$2"
                        fade_setted='true'
                        shift && shift_count="$((shift_count + 1))"
                    fi
                else
                    command_error '--fade (-e)'
                fi
                ;;
            --fade=?*)
                fade="${1#*=}"
                fade_setted='true'
                ;;
            --fade=)
                command_error '--fade (-e)'
                ;;
            
            -m|--volume-factor)
                if [ -n "$2" ]
                then
                    if printf '%.1s\n' "$2" | grep -q '-'
                    then
                        command_error '--volume-factor (-u)'
                    else
                        volume_factor="$2"
                        volume_factor_setted='true'
                        shift && shift_count="$((shift_count + 1))"
                    fi
                else
                    command_error '--volume-factor (-m)'
                fi
                ;;
            --volume-factor=?*)
                volume_factor="${1#*=}"
                volume_factor_setted='true'
                ;;
            --volume-factor=)
                command_error '--volume-factor (-m)'
                ;;
            
            -w|--watermark)
                if [ -n "$2" ]
                then
                    if printf '%.1s\n' "$2" | grep -q '-'
                    then
                        command_error '--watermark (-w)'
                    else
                        watermark_text="$2"
                        watermark='true'
                        shift && shift_count="$((shift_count + 1))"
                    fi
                else
                    command_error '--watermark (-w)'
                fi
                ;;
            --watermark=?*)
                watermark_text="${1#*=}"
                watermark='true'
                ;;
            --watermark=)
                command_error '--watermark (-w)'
                ;;
            
            -z|--wmark-size)
                if [ -n "$2" ]
                then
                    if printf '%.1s\n' "$2" | grep -q '-'
                    then
                        command_error '--wmark-size (-z)'
                    else
                        watermark_size="$2"
                        wmark_size_setted='true'
                        shift && shift_count="$((shift_count + 1))"
                    fi
                else
                    command_error '--wmark-size (-z)'
                fi
                ;;
            --wmark-size=?*)
                watermark_size="${1#*=}"
                wmark_size_setted='true'
                ;;
            --wmark-size=)
                command_error '--wmark-size (-z)'
                ;;
            
            -k|--wmark-position)
                if [ -n "$2" ]
                then
                    if printf '%.1s\n' "$2" | grep -q '-'
                    then
                        command_error '--wmark-position (-k)'
                    else
                        watermark_position="$2"
                        wmark_posi_setted='true'
                        shift && shift_count="$((shift_count + 1))"
                    fi
                else
                    command_error '--wmark-position (-k)'
                fi
                ;;
            --wmark-position=?*)
                watermark_position="${1#*=}"
                wmark_posi_setted='true'
                ;;
            --wmark-position=)
                command_error '--wmark-position (-k)'
                ;;
            
            -c|--wmark-font)
                if [ -n "$2" ]
                then
                    if printf '%.1s\n' "$2" | grep -q '-'
                    then
                        command_error '--wmark-font (-c)'
                    else
                        watermark_font="$2"
                        wmark_font_setted='true'
                        shift && shift_count="$((shift_count + 1))"
                    fi
                else
                    command_error '--wmark-font (-c)'
                fi
                ;;
            --wmark-font=?*)
                watermark_font="${1#*=}"
                wmark_font_setted='true'
                ;;
            --wmark-font=)
                command_error '--wmark-font (-c)'
                ;;
            
            -W|--webcam) # option without argument
                webcam_overlay='true'
                ;;
            
            -I|--webcam-input)
                if [ -n "$2" ]
                then
                    if printf '%.1s\n' "$2" | grep -q '-'
                    then
                        command_error '--webcam-input (-I)'
                    else
                        webcam_input="${2}"
                        webcam_input_setted='true'
                        shift && shift_count="$((shift_count + 1))"
                    fi
                else
                    command_error '--webcam-input (-I)'
                fi
                ;;
            --webcam-input=?*)
                webcam_input="${1#*=}"
                webcam_input_setted='true'
                ;;
            --webcam-input=)
                command_error '--webcam-input (-I)'
                ;;
            
            -Z|--webcam-size)
                if [ -n "$2" ]
                then
                    if printf '%.1s\n' "$2" | grep -q '-'
                    then
                        command_error '--webcam-size (-Z)'
                    else
                        webcam_size="$2"
                        webcam_size_setted='true'
                        shift && shift_count="$((shift_count + 1))"
                    fi
                else
                    command_error '--webcam-size (-Z)'
                fi
                ;;
            --webcam-size=?*)
                webcam_size="${1#*=}"
                webcam_size_setted='true'
                ;;
            --webcam-size=)
                command_error '--webcam-size (-Z)'
                ;;
            
            -P|--webcam-position)
                if [ -n "$2" ]
                then
                    if printf '%.1s\n' "$2" | grep -q '-'
                    then
                        command_error '--webcam-position (-P)'
                    else
                        webcam_position="$2"
                        webcam_posi_setted='true'
                        shift && shift_count="$((shift_count + 1))"
                    fi
                else
                    command_error '--webcam-position (-P)'
                fi
                ;;
            --webcam-position=?*)
                webcam_position="${1#*=}"
                webcam_posi_setted='true'
                ;;
            --webcam-position=)
                command_error '--webcam-position (-P)'
                ;;
            
            -R|--webcam-fps)
                if [ -n "$2" ]
                then
                    if printf '%.1s\n' "$2" | grep -q '-'
                    then
                        command_error '--webcam-fps (-R)'
                    else
                        webcam_rate="$2"
                        webcam_rate_setted='true'
                        shift && shift_count="$((shift_count + 1))"
                    fi
                else
                    command_error '--webcam-fps (-R)'
                fi
                ;;
            --webcam-fps=?*)
                webcam_rate="${1#*=}"
                webcam_rate_setted='true'
                ;;
            --webcam-fps=)
                command_error '--webcam-fps (-R)'
                ;;
            
            -L|--live-streaming)
                if [ -n "$2" ]
                then
                    if printf '%.1s\n' "$2" | grep -q '-'
                    then
                        command_error '--live-streaming (-L)'
                    else
                        streaming_url="$2"
                        streaming='true'
                        shift && shift_count="$((shift_count + 1))"
                    fi
                else
                    command_error '--live-streaming (-L)'
                fi
                ;;
            --live-streaming=?*)
                streaming_url="${1#*=}"
                streaming='true'
                ;;
            --live-streaming=)
                command_error '--live-streaming (-L)'
                ;;
            
            -x|--fixed)
                if [ -n "$2" ]
                then
                    if printf '%.1s\n' "$2" | grep -q '-'
                    then
                        command_error '--fixed (-x)'
                    else
                        fixed_length="$2"
                        fixed_length_setted='true'
                        shift && shift_count="$((shift_count + 1))"
                    fi
                else
                    command_error '--fixed (-x)'
                fi
                ;;
            --fixed=?*)
                fixed_length="${1#*=}"
                fixed_length_setted='true'
                ;;
            --fixed=)
                command_error '--fixed (-x)'
                ;;
            
            -1|--one-step)  # option without argument
                one_step='true'
                one_step_setted='true'
                ;;
            
            -n|--no-notifications)  # option without argument
                notifications='false'
                ;;
            
            -g|--png-optimizer)
                if [ -n "$2" ]
                then
                    if printf '%.1s\n' "$2" | grep -q '-'
                    then
                        command_error '--png-optimizer (-g)'
                    else
                        pngoptimizer="$2"
                        pngoptimizer_setted='true'
                        shift && shift_count="$((shift_count + 1))"
                    fi
                else
                    command_error '--png-optimizer (-g)'
                fi
                ;;
            --png-optimizer=?*)
                pngoptimizer="${1#*=}"
                pngoptimizer_setted='true'
                ;;
            --png-optimizer=)
                command_error '--png-optimizer (-g)'
                ;;
            
            -o|--output-dir)
                if [ -n "$2" ]
                then
                    if printf '%.1s\n' "$2" | grep -q '-'
                    then
                        command_error '--output-dir (-o)'
                    else
                        savedir="${2%/}" # remove ending '/' if present
                        outputdir_setted='true'
                        shift && shift_count="$((shift_count + 1))"
                    fi
                else
                    command_error '--output-dir (-o)'
                fi
                ;;
            --output-dir=?*)
                savedir="${1#*=}"
                savedir="${savedir%/}" # remove ending '/' if present
                outputdir_setted='true'
                ;;
            --output-dir=)
                command_error '--output-dir (-o)'
                ;;
            
            -t|--tmp-dir)
                if [ -n "$2" ]
                then
                    if printf '%.1s\n' "$2" | grep -q '-'
                    then
                        command_error '--tmp-dir (-t)'
                    else
                        tmpdir="${2%/}" # remove ending '/' if present
                        tmpdir_setted='true'
                        shift && shift_count="$((shift_count + 1))"
                    fi
                else
                    command_error '--tmp-dir (-t)'
                fi
                ;;
            --tmp-dir=?*)
                tmpdir="${1#*=}"
                tmpdir="${tmpdir%/}" # remove ending '/' if present
                tmpdir_setted='true'
                ;;
            --tmp-dir=)
                command_error '--tmp-dir (-t)'
                ;;
            
            -K|--keep) # option without argument
                keep_video='true'
                ;;
            
            -u|--auto-filename) # option without argument
                auto_filename='true'
                ;;
            
            -l|--list)  # option without argument
                list_setted='true'
                show_list "$@"
                exit 0
                ;;
            
            -h|-\?|--help)  # option without argument
                help_setted='true'
                show_help "$@"
                exit 0
                ;;
            
            -V|--version)  # option without argument
                version_setted='true'
                show_header "$@"
                exit 0
                ;;
            
            --)   # check for the end of options marker '--'
                shift && shift_count="$((shift_count + 1))"
                break
                ;;
            
            -?*=?*) # unknown option with '=', handle argument also with '='
                exit_program "unknown option '${1%%=*}'"
                ;;
            
            -?*=) # unknown option with '='
                exit_program "unknown option '${1%=*}'"
                ;;
            
            -?*)
                exit_program "unknown option '${1}'"
                ;;
            
            *)    # no more options left
                break
        esac
        shift && shift_count="$((shift_count + 1))"
    done
}

#########################################
#          command line checks          #
#########################################

# description:
#   check validity of command line options and arguments (aslo set some variables)
#   (will exit with error if any inconsistency is found)
# arguments:
#   the remainder positional parameters passed with double quotes ("$@")
# return value: none
# return code (status): not relevant
check_cmd_line() {
    if [ "$display_setted" = 'true' ]
    then
        if ! printf '%s' "$display" | grep -Eq '^:[0-9]+(\.[0-9]+)?$'
        then
            exit_program "'${display}' is not a valid display value"
        fi
    fi
    
    if [ "$select_region" = 'true' ]
    then
        # do not allow to use -S/--select-region with -s/--size or -p/--position
        [ "$video_size_setted" = 'true' ] && exit_program '--select-region (-S) option cannot be used with --size (-s) option'
        [ "$video_posi_setted" = 'true' ] && exit_program '--select-region (-S) option cannot be used with --position (-p) option'
    else
        # preparations for check_dimension() and check_screen()
        video_position_x="$(printf '%s' "$video_position" | awk -F',' '{ print $1 }')"
        video_position_y="$(printf '%s' "$video_position" | awk -F',' '{ print $2 }')"
        video_width="$(     printf '%s' "$video_size"     | awk -F'x' '{ print $1 }')"
        video_height="$(    printf '%s' "$video_size"     | awk -F'x' '{ print $2 }')"
        
        if [ "$video_size_setted" = 'true' ]
        then
            # check for a valid video size format (NxN) (-s/--size)
            printf '%s' "$video_size" | grep -Eq '^[0-9]+x[0-9]+$' || exit_program "'${video_size}' is not a valid video size format"
            
            # check if video width and height are a multiple of 8
            check_dimension "$video_width"  || exit_program "$(dimension_msg 'width')"
            check_dimension "$video_height" || exit_program "$(dimension_msg 'height')"
        fi
        
        if [ "$video_posi_setted" = 'true' ]
        then
            # check for a valid screen position format to record (N,N) (-p/--position)
            printf '%s' "$video_position" | grep -Eq '^[0-9]+,[0-9]+$' || exit_program "'${video_position}' is not a valid screen position format"
        fi
        
        check_screen
    fi
    
    if [ "$border_setted" = 'true' ]
    then
        if printf '%s' "$border" | grep -Eq '^[0-9]+$'
        then
            # set video border if chosen by user (a value different than '0')
            if [ "$border" -gt '0' ]
            then
                if [ "$border" -le '128' ]
                then
                    border_options="-show_region 1 -region_border ${border}"
                else
                    exit_program "video border '${border}' is out of range (allowed values: 0 - 128)"
                fi
            else
                border_options='-show_region 0'
            fi
        else
            exit_program "'${border}' is not a valid number for video border delimiter (allowed values: 0 - 128)"
        fi
    fi
    
    if [ "$video_rate_setted" = 'true' ]
    then
        # check if the entered framerate (fps) is a valid integer number (-r/--fps)
        # (when live streaming, the video framerate (fps) cannot be a floating point number
        #  because it will be multiplied by two to obtain the gop value. This multiplication
        #  will be done using the shell simple aritmetic operator, which does not support floats.
        #  This will make bc to be required only when using fade effect.)
        if [ "$streaming" = 'true' ]
        then
            if ! printf '%s' "$video_rate" | grep -Eq '^[0-9]+$'
            then
                if printf '%s' "$video_rate" | grep -Eq '^[0-9]+(\.[0-9]+)?$'
                then
                    exit_program 'video framerate (fps) cannot be a floating point value when live streaming'
                else
                    exit_program "'${video_rate}' is not a valid video framerate (fps) format"
                fi
            fi
        
        # check if the entered framerate (fps) is a valid integer/float number (-r/--fps)
        else
            printf '%s' "$video_rate" | grep -Eq '^[0-9]+(\.[0-9]+)?$' ||
                exit_program "'${video_rate}' is not a valid video framerate (fps) format"
        fi
    fi
    
    # check if user entered a valid audio encoder
    if [ "$audio_encoder_setted" = 'true' ]
    then
        if ! printf '%s' "$supported_audiocodecs_all" | grep -q "^${audio_encoder}$"
        then
            exit_program "'${audio_encoder}' is not a valid audio encoder for this program"
        fi
    fi
    
    # check if user entered a valid video encoder
    if [ "$video_encoder_setted" = 'true' ]
    then
        if ! printf '%s' "$supported_videocodecs_all" | grep -q "^${video_encoder}$"
        then
            exit_program "'${video_encoder}' is not a valid video encoder for this program"
        fi
    fi
    
    # check if user is recording audio (if not, modify the corresponding control variable)
    if [ "$audio_input" = 'none' ]
    then
        if [ "$audio_encoder_setted" = 'true' ]
        then
            exit_program "the audio encoder cannot be setted with '-a' option when using '-i none'"
        fi
        
        recording_audio='false'
    fi
    
    # check if user is doing a one step process without encoding the lossless video
    if [ "$video_encoder" = 'none' ] &&
       {
           [ "$audio_encoder"   = 'none'  ] ||
           [ "$recording_audio" = 'false' ];
       }
    then
        video_outstr='(lossless video)'
        [ "$recording_audio" = 'true' ] && audio_outstr='(lossless audio)'
        
        one_step_lossless='true'
        [ "$one_step" != 'true' ] && one_step='true' # implies one step process
    fi
    
    # check if user is saving an output video (if not, modify the corresponding control variable)
    [ "$streaming" = 'true' ] && [ "$keep_video" = 'false' ] && saving_output='false'
    
    # do not allow to use -K/--keep with -1/--one-step
    [ "$one_step" = 'true' ] && [ "$keep_video" = 'true' ] &&
        exit_program "'-K' is set but there is no temporary video to keep when using --one-step"
    
    # check if user entered a valid fade effect (-e/--fade)
    if [ "$fade_setted" = 'true' ]
    then
        get_supported_fade
        
        if printf '%s' "$supported_fade" | grep -q "^${fade}$"
        then
            # do not allow to use fade effect when using a one step process (-1/--one-step)
            [ "$one_step" = 'true' ] && [ "$fade" != 'none' ] && exit_program 'fade effect (-e) cannot be used with --one-step'
        else
            exit_program "'${fade}' is not a valid fade effect for this program"
        fi
    fi
    
    # checks and settings based on usage of the '-u' option
    if [ "$auto_filename" = 'true' ]
    then
        # do not allow to use '-u' when not saving the live streaming
        [ "$saving_output" = 'false' ] && exit_program "'-u' is set but not saving the live streaming ('-K' not set)"
        
        # if '-u' is set, don't allow to set an output filename in command line
        [ "$#" -gt '0' ] && exit_program '--auto-filename (-u) does not allow to set an output filename'
        
        # show full path when using dot folder hardlinks in -o/--output-dir
        if [ "$outputdir_setted" = 'true' ]
        then
            # output dir in format '../myvideo.mp4' or '../path/to/myvideo.mp4' (parent dir as double dot folder hardlink)
            if printf '%s' "$savedir" | grep -Eq '^\.\.[/]?.*'
            then
                savedir="$(printf '%s' "$savedir" | sed "s|^\\.\\.|$(dirname "$(pwd)")|")"
                savedir="${savedir%/}" # remove ending '/' if present
                
            # output dir in format './myvideo.mp4' or './path/to/myvideo.mp4' (current dir as single dot folder hardlink)
            elif printf '%s' "$savedir" | grep -Eq '^\.[/]?.*'
            then
                savedir="$(printf '%s' "$savedir" | sed "s|^\\.|$(pwd)|")"
                savedir="${savedir%/}" # remove ending '/' if present
            fi
        fi
        
        current_time="$(date +%Y-%m-%d_%H.%M.%S)" # get current time for placing on filename
        
        # set the output filename
        if [ "$streaming" = 'true' ]
        then
            output_file="screencast-livestreaming-${current_time}.${format}"
        else
            if [ "$one_step_lossless" = 'true' ] && [ "$format_setted" = 'false' ]
            then
                format='mkv'
                format_outstr='(auto chosen)'
            fi
            
            output_file="screencast-${current_time}.${format}"
        fi
    else
        # check if user have not entered an output filename after the options (only if saving the output video)
        [ "$#" -eq '0' ] && [ "$saving_output" = 'true' ] && exit_program "you must enter an output filename with extension (or use '-u')"
        
        # do not allow to enter an output filename when not saving the live streaming
        [ "$#" -eq '1' ] && [ "$saving_output" = 'false' ] &&
            exit_program "output filename was specified but but not saving the live streaming ('-K' not set)"
        
        # do not allow to enter anything after the output filename
        [ "$#" -gt '1' ] && exit_program 'please do not enter anything after the output filename'
        
        # do not allow to use -f/--format when specifying an output filename
        if [ "$format_setted" = 'true' ] &&
           {
               [ "$streaming" = 'false' ] ||
               {
                   [ "$streaming"  = 'true'  ] &&
                   [ "$keep_video" = 'false' ];
               } ;
           }
        then
            exit_program "--format (-f) option can be used only with --auto-filename (-u)
                      (when not using '-u', set format in the output filename)"
        fi
        
        # do not allow to use -o/--output-dir when specifying an output filename
        if [ "$outputdir_setted" = 'true' ] &&
           {
               [ "$streaming" = 'false' ] ||
               {
                   [ "$streaming"  = 'true'  ] &&
                   [ "$keep_video" = 'false' ];
               } ;
           }
        then
            exit_program "--output-dir (-o) option can be used only with --auto-filename (-u)
                      (when not using '-u', set output directory with the output filename)"
        fi
        
        # output dir in format '../myvideo.mp4' or '../path/to/myvideo.mp4' (parent dir as double dot folder hardlink)
        if printf '%s' "$1" | grep -q '^\.\./.*'
        then
            savedir="$(printf '%s' "$(dirname "$1")" | sed "s|^\\.\\.|$(dirname "$(pwd)")|")"
            
        # output dir in format './myvideo.mp4' or './path/to/myvideo.mp4' (current dir as single dot folder hardlink)
        elif printf '%s' "$1" | grep -q '^\./.*'
        then
            savedir="$(printf '%s' "$(dirname "$1")" | sed "s|^\\.|$(pwd)|")"
            
        # any other output dir format, no need of special handling
        else
            savedir="$(dirname "$1")"
        fi
        
        # set the output filename and get the container format (only if saving the output video)
        if [ "$saving_output" = 'true' ]
        then
            output_file="$(basename "$1")" # set the output filename
            format="${output_file##*.}"    # set container format (output filename extension)
        fi
        
    fi # end: else clause of: if [ "$auto_filename" = 'true' ]
    
    # show full path when using dot folder hardlinks in -t/--tmp-dir
    if [ "$tmpdir_setted" = 'true' ]
    then
        # tmpdir in format '../' or '../path/to/' (parent dir as double dot folder hardlink)
        if printf '%s' "$tmpdir" | grep -Eq '^\.\.[/]?.*'
        then
            tmpdir="$(printf '%s' "$tmpdir" | sed "s|^\\.\\.|$(dirname "$(pwd)")|")"
            tmpdir="${tmpdir%/}" # remove ending '/' if present
            
        # tmpdir in format './' or './path/to/' (current dir as single dot folder hardlink)
        elif printf '%s' "$tmpdir" | grep -Eq '^\.[/]?.*'
        then
            tmpdir="$(printf '%s' "$tmpdir" | sed "s|^\\.|$(pwd)|")"
            tmpdir="${tmpdir%/}" # remove ending '/' if present
        fi
    fi
    
    # check if user entered a valid container format
    if [ "$format_setted" = 'true' ] ||
       {
           [ "$auto_filename" = 'false' ] &&
           [ "$saving_output" = 'true'  ];
       }
    then
        if ! printf '%s' "$supported_formats_all" | grep -q "^${format}$"
        then
           exit_program "'${format}' is not a valid container format for this program"
        fi
    fi
    
    # check for valid live streaming settings
    if [ "$streaming" = 'true' ]
    then
        get_supported_streaming_settings
        
        # check if the detected ffmpeg build has support for flv muxer (used for live streaming)
        check_component flv muxer || component_error flv muxer false
        
        # do not allow live streaming without recording audio (YouTube seems to refuse it)
        [ "$recording_audio" = 'false' ] && exit_program 'live streaming cannot be sent without audio'
        
        # do not allow to use fade effect with live streaming (-L/--live-streaming)
        [ "$fade" != 'none' ] && exit_program 'fade effect (-e) cannot be used with live streaming (-L)'
        
        # check for an invalid container format if saving the live stream
        # (supported audio/video encoders in the flv muxer that have restrictions in specific container formats)
        if [ "$saving_output" = 'true' ]
        then
            if ! printf '%s' "$supported_streaming_formats" | grep -q "^${format}$"
            then
                exit_program "live streaming cannot be saved to the '${format}' container format"
            fi
        fi
        
        # check for an invalid audio encoder for live streaming (flv muxer restrictions)
        if ! printf '%s' "$supported_streaming_audiocodecs" | grep -q "^${audio_encoder}$"
        then
            exit_program "live streaming cannot be made with the '${audio_encoder}' audio encoder"
        fi
        
        # check for an invalid video encoder for live streaming (flv muxer restrictions)
        if ! printf '%s' "$supported_streaming_videocodecs" | grep -q "^${video_encoder}$"
        then
            exit_program "live streaming cannot be made with the '${video_encoder}' video encoder"
        fi
    fi
    
    # do not allow to use '-a none' with an '-v' argument different than 'none'
    if [ "$audio_encoder" = 'none' ] && [ "$video_encoder" != 'none' ]
    then
        msg="'-a none' cannot be used with an '-v' argument different than 'none'"
        
        if [ "$video_encoder_setted" = 'false' ]
        then
            msg="${msg}
                      (did you forget to select the video encoder?)"
        fi
        
        exit_program "$msg"
    fi
    
    # do not allow to use '-v none' with an '-a' argument different than 'none'
    if [ "$video_encoder" = 'none' ] && [ "$audio_encoder" != 'none' ] && [ "$recording_audio" = 'true' ]
    then
        msg="'-v none' cannot be used with an '-a' argument different than 'none'"
        
        if [ "$audio_encoder_setted" = 'false' ]
        then
            msg="${msg}
                      (did you forget to select the audio encoder?)"
        fi
        
        exit_program "$msg"
    fi

    # checks and settings for formats and audio/video encoders when saving the output video
    if [ "$saving_output" = 'true' ]
    then
        # execute container formats checks and settings
        "format_settings_${format}"
        
        # check if user entered and invalid combination of audio encoder and container format
        if [ "$format_setted" = 'true' ] || [ "$audio_encoder_setted" = 'true' ]
        then
            if ! printf '%s' "$supported_audiocodecs" | grep -q "^${audio_encoder}$"
            then
                msg="container format '${format}' does not support '${audio_encoder}' audio encoder"
                
                if [ "$auto_filename" = 'true' ] && [ "$format_setted" = 'false' ]
                then
                    msg="${msg}
                      (did you forget to select the container format?)"
                fi
                
                show_settings
                exit_program "$msg"
            fi
            
            # special condition check
            # opus + mp4: requires ffmpeg 4.3 or greater (or git master N-97089-gca7a192d10 or greater)
            if printf '%s' "$audiocodecs_opus" | grep -q "^${audio_encoder}$" && [ "$format" = 'mp4' ]
            then
                if ! check_minimum_ffmpeg_version '4.3' '97089'
                then
                    msg="'opus' audio encoder in 'mp4' container format is not allowed with your ffmpeg
                      it's needed ffmpeg 4.3 or greater (or git master N-97089-gca7a192d10 or greater)"
                    
                    ffmpeg_version_error "$msg"
                fi
            fi
        fi
        
        # check if user entered and invalid combination of video encoder and container format
        if [ "$format_setted" = 'true' ] || [ "$video_encoder_setted" = 'true' ]
        then
            if ! printf '%s' "$supported_videocodecs" | grep -q "^${video_encoder}$"
            then
                msg="container format '${format}' does not support '${video_encoder}' video encoder"
                
                if [ "$auto_filename" = 'true' ] && [ "$format_setted" = 'false' ]
                then
                    msg="${msg}
                      (did you forget to select the container format?)"
                fi
                
                show_settings
                exit_program "$msg"
            fi
            
            # special condition checks
            # vp9 + mp4: requires ffmpeg 3.4 or greater (or git master N-86119-g5ff31babfc or greater)
            if printf '%s' "$videocodecs_vp9" | grep -q "^${video_encoder}$" && [ "$format" = 'mp4' ]
            then
                if ! check_minimum_ffmpeg_version '3.4' '86119'
                then
                    msg="support for 'vp9' video encoder in 'mp4' container format in your ffmpeg build is experimental
                      it's needed ffmpeg 3.4 or greater (or git master N-86119-g5ff31babfc or greater)"
                    
                    ffmpeg_version_error "$msg"
                fi
            
            # av1 + webm: requires ffmpeg 4.1 or greater (or git master N-91995-gcbe5c7ef38 or greater)
            elif printf '%s' "$videocodecs_av1" | grep -q "^${video_encoder}$" && [ "$format" = 'webm' ]
            then
                if ! check_minimum_ffmpeg_version '4.1' '91995'
                then
                    msg="your ffmpeg build does not support 'av1' video encoder in 'webm' container format
                      it's needed ffmpeg 4.1 or greater (or git master N-91995-gcbe5c7ef38 or greater)"
                    
                    ffmpeg_version_error "$msg"
                fi
            fi
            
        fi # end: [ "$format_setted" = 'true' ] || [ "$video_encoder_setted" = 'true' ]
        
    fi # end: [ "$saving_output" = 'true' ]
    
    # check for the lossless ffmpeg components when needed
    if [ "$streaming" = 'false' ] &&
       {
           [ "$one_step" = 'false' ] ||
           [ "$one_step_lossless" = 'true' ];
       }
    then
        check_lossless_component format
        check_lossless_component videocodec
    fi
    
    # audio input checks and adjustments
    if [ "$recording_audio" = 'true' ]
    then
        # check if the detected ffmpeg build has support for a lossless audio encoder and decoder for recording
        check_lossless_component audiocodec
        
        # check for ALSA support in ffmpeg
        if check_component alsa demuxer
        then
            # check if user entered a valid ALSA input device name
            if [ "$audio_input_setted" = 'true' ]
            then
                case "$audio_input" in
                    pulse|default)
                        :
                        ;;
                    # do not allow 'null' ALSA input device
                    null)
                        exit_program "the use of 'null' ALSA input device causes problems and is not allowed"
                        ;;
                    *)
                        if command -v arecord >/dev/null 2>&1
                        then
                            if ! check_alsa_long_name && ! check_alsa_short_name
                            then
                                exit_program "'${audio_input}' is not a valid ALSA input device name on this system"
                            fi
                        else
                            exit_program "'arecord' was not found
                      please install 'arecord' (alsa-utils)"
                        fi
                        ;;
                esac
            fi
            
        # check for PulseAudio support in ffmpeg (fallback mode if ALSA is not available)
        elif check_component pulse demuxer
        then
            # check for PulseAudio support in ffmpeg
            if check_component pulse demuxer
            then
                audio_input_options="$(printf '%s' "$audio_input_options" | sed 's/alsa/pulse/')"
                
                print_warn 'the detected ffmpeg build has no ALSA support, falling back to PulseAudio backend'
            fi
            
            if [ "$audio_input_setted" = 'true' ]
            then
                # check if user entered a valid PulseAudio input source
                case "$audio_input" in
                    default)
                        :
                        ;;
                    *)
                        if command -v pactl >/dev/null 2>&1
                        then
                            if ! printf '%s' "$(pactl list sources |
                                                 grep '[Nn]ame:'   |
                                                 grep 'input'      |
                                                 sed  's/[[:space:]]*[Nn]ame:[[:space:]]//;s/^<//;s/>$//'
                                               )" | grep -q "^${audio_input}$"
                            then
                                exit_program "'${audio_input}' is not a valid PulseAudio input source on this system"
                            fi
                        else
                            exit_program "'pactl' was not found after falling back to PulseAudio backend
                      please install 'pactl'"
                        fi
                        ;;
                esac
            else
                print_warn "auto setting audio input device to 'default'"
            fi
        
        # no ALSA ou PulseAudio support available in ffmpeg
        else
            component_error 'ALSA or PulseAudio' backend false
            
        fi # end: else clause of: if check_component alsa demuxer
        
        audio_input="-i ${audio_input}" # add ffmpeg '-i' option for audio input
        
    fi # end: [ "$recording_audio" = 'true' ]
    
    # execute audio encoder checks and settings (only if recording audio)
    if [ "$recording_audio" = 'true' ]
    then
        [ "$audio_encoder" != 'none' ] && "audiocodec_settings_${audio_encoder}"
    
    # adjust settings if user is not recording audio (video without audio stream)
    else
        unset -v audio_input
        unset -v audio_channel_layout
        unset -v audio_input_options
        unset -v audio_encoder
        audio_record_codec='-an'
        audio_encode_codec='-an'
    fi
    
    # execute video encoder checks and settings
    [ "$video_encoder" != 'none' ] && "videocodec_settings_${video_encoder}"
    
    # do not allow to use slow video encoders in a one step process (-1/--one-step)
    # (aom_av1 is still experimental and very slow in ffmpeg)
    if printf '%s' "$videocodecs_av1_slow" | grep -q "^${video_encoder}$"
    then
        exit_program "'${video_encoder}' cannot be used for a one step process (very slow in FFmpeg)"
    fi
    
    # do not allow to use -A/--vaapi-device without setting a vaapi video encoder
    if ! printf '%s' "$videocodecs_vaapi" | grep -q "^${video_encoder}$"
    then
        if [ "$vaapi_device_setted" = 'true' ]
        then
            exit_program '--vaapi-device (-A) option can be used only when a VAAPI video encoder is selected'
        fi
        unset -v vaapi_device
    fi
    
    # do not allow to use -m option when -i is setted to 'none'
    if [ "$recording_audio" = 'false' ] && [ "$volume_factor_setted" = 'true' ]
    then
        exit_program "--volume-factor (-m) option cannot be used when '-i' is setted to 'none'"
    fi
    
    # check if the entered volume factor is a valid integer/float number (-m)
    if [ "$volume_factor_setted" = 'true' ]
    then
        if printf '%s' "$volume_factor" | grep -Eq '^[0-9]+(\.[0-9]+)?$'
        then
            # enable volume increase effect if chosen by user
            # (a value different than '1.0' or '0.0')
            if ! {
                     printf '%s' "$volume_factor" | grep -Eq '^[0]+(\.[0]+)?$' ||
                     printf '%s' "$volume_factor" | grep -Eq '^[1]+(\.[0]+)?$' ;
                 }
            then
                volume_increase='true'
            fi
        else
            exit_program "'${volume_factor}' is not a valid number for volume increase effect"
        fi
    fi
    
    # watermark checks and settings
    if [ "$watermark" = 'true' ]
    then
        # check for a valid watermark size format (NxN) (-z/--wmark-size)
        printf '%s' "$watermark_size" | grep -Eq '^[0-9]+x[0-9]+$' || exit_program "'${watermark_size}' is not a valid watermark size format"
        
        # check for a valid watermark position format ('N,N' or 'PRE') (-k/--wmark-position)
        if printf '%s' "$watermark_position" | grep -Eq '^[0-9]+,[0-9]+$'
        then
            # translate watermark position to what is really used in ffmpeg command
            watermark_position="$(printf '%s' "$watermark_position" | tr ',' ':')"
        else
            # check for a valid watermark special position value
            if check_special_position "$watermark_position"
            then
                # translate watermark position to what is really used in ffmpeg command
                watermark_corner="$watermark_position" # save for comparing watermark and webcam overlay corners
                watermark_position="$special_position"
                unset -v special_position
                
                # translate a possible alias to the full corner position name (for comparing with webcam overlay corner)
                case "$watermark_corner" in
                    tr)
                        watermark_corner='topright'
                        ;;
                    br)
                        watermark_corner='bottomright'
                        ;;
                    tl)
                        watermark_corner='topleft'
                        ;;
                    bl)
                        watermark_corner='bottomleft'
                        ;;
                esac
            else
                exit_program "'${watermark_position}' is not a valid watermark position format"
            fi
        fi
    
    # do not allow to use -z, -k, -c or -g options without -w option
    else
        msg='option can be used only with --watermark (-w) option'
        [ "$wmark_size_setted"   = 'true' ] && exit_program "--wmark-size (-z) ${msg}"
        [ "$wmark_posi_setted"   = 'true' ] && exit_program "--wmark-position (-k) ${msg}"
        [ "$wmark_font_setted"   = 'true' ] && exit_program "--wmark-font (-c) ${msg}"
        [ "$pngoptimizer_setted" = 'true' ] && exit_program "--png-optimizer (-g) ${msg}"
        unset -v msg
    fi
    
    # webcam overlay checks and settings
    if [ "$webcam_overlay" = 'true' ]
    then
        # check for a valid webcam input device
        [ -c "$webcam_input" ] || exit_program "'${webcam_input}' is not a valid webcam input device on this system"
        
        # check for a valid webcam size format (NxN) (-Z/--webcam-size)
        printf '%s' "$webcam_size" | grep -Eq '^[0-9]+x[0-9]+$' || exit_program "'${webcam_size}' is not a valid webcam size format"
        
        # check for a valid webcam position format ('N,N' or 'PRE') (-P/--webcam-position)
        if printf '%s' "$webcam_position" | grep -Eq '^[0-9]+,[0-9]+$'
        then
            # translate webcam position to what is really used in ffmpeg command
            webcam_position="$(printf '%s' "$webcam_position" | tr ',' ':')"
        else
            # check for a valid webcam special position value
            if check_special_position "$webcam_position"
            then
                # translate webcam position to what is really used in ffmpeg command
                webcam_corner="$webcam_position" # save for comparing watermark and webcam overlay corners
                webcam_position="$special_position"
                unset -v special_position
                
                # translate a possible alias to the full corner position name (for comparing with watermark corner)
                case "$webcam_corner" in
                    tr)
                        webcam_corner='topright'
                        ;;
                    br)
                        webcam_corner='bottomright'
                        ;;
                    tl)
                        webcam_corner='topleft'
                        ;;
                    bl)
                        webcam_corner='bottomleft'
                        ;;
                esac
            else
                exit_program "'${webcam_position}' is not a valid webcam position format"
            fi
        fi
    
    # do not allow to use -Z, -I, or -P options without -W option
    else
        msg='option can be used only with --webcam (-W) option'
        [ "$webcam_size_setted"  = 'true' ] && exit_program "--webcam-size (-Z) ${msg}"
        [ "$webcam_input_setted" = 'true' ] && exit_program "--webcam-input (-I) ${msg}"
        [ "$webcam_posi_setted"  = 'true' ] && exit_program "--webcam-position (-P) ${msg}"
        [ "$webcam_rate_setted"  = 'true' ] && exit_program "--webcam-fps (-R) ${msg}"
        unset -v msg
    fi
    
    # do not allow watermark and webcam overlay to be placed in the same video corner (only works with predefined special values)
    [ "$watermark" = 'true' ] && [ "$webcam_overlay" = 'true' ] && [ "$watermark_corner" = "$webcam_corner" ] &&
        exit_program "watermark and webcam overlay cannot be placed in the same '$webcam_corner' video corner"
    
    # check if the entered fixed video length is a valid integer/float number (-x)
    if [ "$fixed_length_setted" = 'true' ]
    then
        if printf '%s' "$fixed_length" | grep -Eq '^[0-9]+(\.[0-9]+)?$'
        then
            # enable fixed video length if chosen by user (a value different than '0')
            printf '%s' "$fixed_length" | grep -Eq '^[0]+(\.[0]+)?$' || ff_fixed_length_options="-t ${fixed_length}"
        else
            exit_program "'${fixed_length}' is not a valid number for fixed video length"
        fi
    fi
}

#########################################
#                system                 #
#########################################

# description: execute cleanup routines after program termination
# arguments: none
# return value: none
# return code (status): not relevant
cleanup() {
    # delete temporary PNG (waterwark) image
    [ "$watermark" = 'true' ] && rm -f "${tmpdir}/screencast-tmpimage-${rndstr_png}.png"
    
    if [ "$streaming" = 'false' ] && [ "$one_step" = 'false' ] && [ -n "$ff_output" ]
    then
        # rename temporary (lossless) video to a better name if user selected to keep it (--keep/-K)
        # and move it to $savedir
        if [ "$keep_video" = 'true' ]
        then
            if [ "$auto_filename" = 'true' ]
            then
                tmpvideo="${tmpdir}/screencast-lossless-${rndstr_video}.${rec_extension}"
                tmpvideo_newname="${savedir}/screencast-lossless-${current_time}.${rec_extension}"
            else
                tmpvideo="${tmpdir}/screencast-lossless-${rndstr_video}.${rec_extension}"
                tmpvideo_newname="${savedir}/${output_file%.*}-lossless.${rec_extension}"
            fi
            
            if [ -f "$tmpvideo" ]
            then
                filesize="$(du -k "$tmpvideo" | awk '{ print $1 }')"
                
                if [ "$filesize" -eq '0' ]
                then
                    # delete zero-sized tmpvideo file in case some unexpected error occurred
                    rm -f "$tmpvideo"
                else
                    mv -f "$tmpvideo" "$tmpvideo_newname"
                fi
            fi
        # delete temporary (lossless) video if not saving it
        else
            rm -f "${tmpdir}/screencast-lossless-${rndstr_video}.${rec_extension}"
        fi
    fi
    
    # delete zero-sized output file in case some unexpected error occurred
    if [ -f "${savedir}/${output_file}" ]
    then
        filesize="$(du -k "${savedir}/${output_file}" | awk '{ print $1 }')"
        
        [ "$filesize" -eq '0' ] && rm -f "${savedir}/${output_file}"
    fi
}

# description: check if required programs are installed
# arguments: none
# return value: not relevant
# return code (status): not relevant
check_requirements() {
    for requirement in notify-send ffmpeg xdpyinfo slop ffprobe \
                       convert magick "$pngoptimizer" advdef
    do
        # skip disabled components (unnecessary checks)
        if {
               [ "$requirement"   = 'slop'  ] &&
               [ "$select_region" = 'false' ];
           } ||
           {
               [ "$requirement" = 'ffprobe' ] &&
               [ "$fade" = 'none' ];
           } ||
           {
               {
                   [ "$requirement" = 'convert' ] ||
                   [ "$requirement" = 'magick'  ];
               } &&
               [ "$watermark" = 'false' ];
           } ||
           {
               [ "$requirement"  = "$pngoptimizer" ] &&
               [ "$pngoptimizer" = 'none'          ];
           } ||
           {
               [ "$requirement"  = 'advdef' ] &&
               [ "$pngoptimizer" = "none"   ];
           } ||
           {
               [ "$requirement"   = 'notify-send' ] &&
               [ "$notifications" = 'false'       ];
           }
        then
            continue
        fi
        
        case "$requirement" in
            notify-send)
                unset -v request_string
                installname="${requirement} (libnotify) (or use '-n')"
                ;;
            ffmpeg)
                unset -v request_string
                installname='ffmpeg (version git master preferred)'
                ;;
            ffprobe)
                request_string='video fade effect was requested but '
                installname='ffprobe (ffmpeg) (version git master preferred)'
                ;;
            convert|magick)
                request_string='text watermark was requested but '
                installname='ImageMagick (IM7 preferred)'
                ;;
            slop)
                request_string='screen region selection was requested but '
                installname="$requirement"
                ;;
            "$pngoptimizer")
                request_string='png optimization was requested but '
                installname="$requirement"
                ;;
            advdef)
                request_string='png optimization was requested but '
                installname="${requirement} (advancecomp)"
                ;;
            *)
                unset -v request_string
                installname="$requirement"
                ;;
        esac
        
        if ! command -v "$requirement" >/dev/null 2>&1
        then
            if [ "$requirement"  = 'magick' ]
            then
                # in this case IM6 was found because 'convert' goes first
                magick() {
                    convert "$@"
                }
                continue
            else
                msg="${request_string}'${requirement}' was not found"
                print_error "$msg"
                printf '%s%s\n' '                      ' \
                          "please install ${installname}" >&2
                [ "$requirement" != 'notify-send' ] &&
                    notify 'critical' "$expire_time_long" "$error_icon" "$msg"
                exit 1
            fi
        fi
    done
    
    unset -v requirement
    unset -v installname
}

# description:
#   remove two or more consecutive spaces from a string, making them to be a single space
# arguments:
#   $1 - the string to modify
# return value: a modified string
# return code (status): not relevant
remove_spaces() {
    printf '%s' "$1" | sed 's/[[:space:]]\{1,\}/ /g'
}

# description: delete multiple lines from a variable
# arguments:
#   $1 - variable to delete the multiple lines from
#   $2 - the multiple lines to be deleted
# return value: the variable at $1 with the multiple line deleted
# return code (status): not relevant
del_multiline() {
    var="$1"
    
    while read -r line
    do
        var="$(printf '%s' "$var" | sed "/^${line}$/d")"
    done <<- __EOF__
		$(printf '%s' "$2")
__EOF__
    
    printf '%s\n' "$var"
    
    unset -v line
    unset -v var
}

# description: check if specified DRM render node (vaapi device) exists (-A/--vaapi-device)
# arguments: none
# return value: not relevant
# return code (status):
#   0 - a valid DRM render node (vaapi device) exists
# note:
#   it will make the program exit with error if an invalid DRM render node
#   (vaapi device) was selected
check_vaapi_device() {
    [ -c "$vaapi_device" ] || exit_program "'${vaapi_device}' is not a valid DRM render node (VAAPI device) on this system"
}

# description: check for a valid ALSA input device long name
# arguments: none
# return value: none
# return code (status):
#   0 - a valid ALSA input device long name was entered
#   1 - an invalid ALSA input device long name was entered
# note: it will make the program exit with error user selects the 'null' input device
check_alsa_long_name() {
    arecord="$(arecord -L)"
    
    # check if user has entered allowed variations of long device name
    if ! printf '%s' "$arecord" | grep -q  "^${audio_input}$"  &&
       ! printf '%s' "$arecord" | grep -Eq "^${audio_input}:?" &&
       ! printf '%s' "$arecord" | grep -Eq "^${audio_input%:*}:(CARD=)?${audio_input#*:}"
    then
        return 1
    fi
}

# description: check for a valid ALSA input device short name
# arguments: none
# return value: none
# return code (status):
#   0 - a valid ALSA input device short name was entered
#   1 - an invalid ALSA input device short name was entered
check_alsa_short_name() {
    arecord="$(arecord -l)"
    
    # format: [plug]hw:card
    if printf '%s' "$audio_input" | grep -Eq '^(plug)?+hw:[0-9]+$'
    then
        alsa_card="$(printf '%s' "$audio_input" | sed 's/.*hw://')"
        
        if ! printf '%s' "$arecord" | grep -q "card[[:space:]]${alsa_card}:"
        then
            return 1
        fi
        
    # format: [plug]hw:card,device
    elif printf '%s' "$audio_input" | grep -Eq '^(plug)?+hw:[0-9],[0-9]+$'
    then
        alsa_card="$(  printf '%s' "$audio_input" | sed 's/.*hw://;s/,.*//')"
        alsa_device="$(printf '%s' "$audio_input" | sed 's/.*hw://;s/.*,//')"
        
        if ! printf '%s' "$arecord" | grep "card[[:space:]]${alsa_card}:" | grep -q "device[[:space:]]${alsa_device}:"
        then
            return 1
        fi
        
    # format: [plug]hw:card,device,subdevice
    elif printf '%s' "$audio_input" | grep -Eq '^(plug)?+hw:[0-9],[0-9]+,[0-9]+$'
    then
        alsa_card="$(     printf '%s' "$audio_input" | sed 's/.*hw://;s/,.*//')"
        alsa_device="$(   printf '%s' "$audio_input" | sed 's/.*hw://;s/[0-9]\{1,\},//;s/,.*//')"
        alsa_subdevice="$(printf '%s' "$audio_input" | sed 's/.*hw://;s/[0-9]\{1,\},//;s/.*,//')"
        
        if printf '%s' "$arecord" | grep "card[[:space:]]${alsa_card}:" | grep -q "device[[:space:]]${alsa_device}:"
        then
            # find line number of specified card and device
            card_line="$(printf '%s' "$arecord" | sed -n "/card[[:space:]]${alsa_card}:.*device[[:space:]]${alsa_device}:.*/=")"
            
            # find how many subdevices this card/device have
            # ($card_line + 1 to match Subdevices: 1/1, Subdevices: 2/2, ...)
            subdevices="$(printf '%s' "$arecord" | sed -n "$((card_line + 1))p" | sed 's@.*/@@')"
            
            [ "$subdevices" = '1' ] && subdevices='0' # no need to extend line range if there is only one subdevice
            
            # grep for subdevice number in specific line range after $card_line
            if ! printf '%s' "$arecord" | sed -n "$((card_line + 2)),$((card_line + 2 + subdevices))p" | grep -q "Subdevice[[:space:]]#${alsa_subdevice}:"
            then
                return 1
            fi
        else
            return 1
        fi
    else
        return 1
    fi
}

# description:
#   check for valid output and tmp directories (-o and -t)
#   (will exit with error if any problem is encountered)
# arguments:
#   $1 - the directory to check
# return value: not relevant
# return code (status): not relevant
check_dir() {
    # check if the entered $savedir/$tmpdir already exists
    if [ -e "$1" ]
    then
        # check if the entered $savedir/$tmpdir is a directory
        if [ -d "$1" ]
        then
            # check if the entered $savedir/$tmpdir has write permission
            if [ ! -w "$1" ]
            then
                case "$1" in
                    "$savedir")
                        msg='output'
                        ;;
                    "$tmpdir")
                        msg='temporary files'
                        ;;
                esac
                exit_program "no write permission for ${msg} directory '${1}'"
            fi
        else
            exit_program "'${1}' is not a directory"
        fi
    # create the entered $savedir/$tmpdir if it does not exists
    else
        [ "$1" = "$tmpdir" ] && mode='-m 700'
        
        # shellcheck disable=SC2174
        if ! mkdir -p $mode "$1"
        then
            case "$1" in
                "$savedir")
                    msg='output'
                    ;;
                "$tmpdir")
                    msg='temporary files'
                    ;;
            esac
            exit_program "failed to create ${msg} directory '${1}'"
        fi
    fi
}

# description: generate a random string
# arguments:
#   $1 - desired string length
# return value: a random string
# return code (status): not relevant
randomstr() {
    if [ -c '/dev/urandom' ]
    then
        LC_CTYPE='C' tr -dc '[:alnum:]' < /dev/urandom 2>/dev/null | head -c"$1"
        
    elif command -v shuf >/dev/null 2>&1
    then
        alphanum="a b c d e f g h i j k l m n o p q r s t u v w x y z \
                  A B C D E F G H Y J K L M N O P Q R S T U V W X Y Z \
                  0 1 2 3 4 5 6 7 8 9"
        shuf -ez -n"$1" $alphanum
        unset -v alphanum
        
    elif command -v openssl >/dev/null 2>&1
    then
        openssl rand -hex "$1" | cut -c-"$1"
        
    elif command -v pwgen >/dev/null 2>&1
    then
        pwgen -cns "$1" 1

    else
        print_good 'generating random string with legacy method' >&2
        rnd="$(awk 'BEGIN { srand(); printf "%d\n", (rand() * 10^8) }')"
        
        while [ "$(printf '%s' "$rnd" | wc -m)" -lt "$1" ]
        do
            sleep 1
            rnd="${rnd}$(awk 'BEGIN { srand(); printf "%d\n", (rand() * 10^8) }')"
        done
        
        printf '%s' "$rnd" | cut -c-"$1"
        unset -v rnd
    fi
}

#########################################
#               messages                #
#########################################

# print functions
# description: print formated messages
# arguments:
#   $1 - message to print
# return value: the message to print
# return code (status): not relevant
print_good() {
    printf '%s\n' "${msg_header}${color_off} ${1}"
}

print_info() {
    printf '%s\n' "${msg_header}${color_bold} info:${color_off} ${1}"
}

print_warn() {
    printf '%s\n' "${msg_header}${color_yellow} warning:${color_off} ${1}"
}

print_error() {
    printf '%s\n' "${msg_header}${color_red} error:${color_off} ${1}" >&2
}

# description: show a desktop notification if setted to do so
# arguments:
#   $1 - urgency level (low, normal, critial)
#   $2 - duration in milliseconds
#   $3 - icon name
#   $4 - text message
# return value: none
# return code (status): not relevant
notify() {
    if [ "$notifications" = 'true' ]
    then
        notify-send --urgency="$1" --expire-time="$2" --icon="$3" \
            screencast "$(remove_spaces "$4")"
    fi
}

# description: print message and show notification when finished
# arguments: none
# return value: the printed message
# return code (status): not relevant
finish() {
    print_good 'finish'
    
    if [ -f "$finish_icon_oxygen" ]
    then
        finish_icon="$finish_icon_oxygen"
    else
        finish_icon="$finish_icon_generic"
    fi
    
    notify 'normal' "$expire_time_normal" "$finish_icon" 'finish'
    
    # play a sound after finish if requirements are present
    if [ "$notifications" = 'true' ] && [ -f "$finish_sound" ] && command -v ffplay >/dev/null 2>&1
    then
        ffplay -v quiet -nodisp -autoexit -volume "$ffplay_volume" "$finish_sound" >/dev/null 2>&1 &
    fi
    
    unset -v finish_icon
}

#########################################
#            error messages             #
#########################################

# description:
#   print an error message regarding invalid command
#   line arguments, show notification and exit with error
# arguments:
#   $1 - command line option name (e.g.: "--fade (-e)")
# return value: not relevant
# return code (status): not relevant
command_error() {
    exit_program "${1} option requires an argument"
}

# description:
#   print an error message to stderr, a desktop notification
#   (if it is enabled) and exit with error
# arguments:
#   $1 - error message to print/notificate
# return value: not relevant
# return code (status): not relevant
exit_program() {
    print_error "$1"
    notify 'critical' "$expire_time_long" "$error_icon" "error: ${1}"
    exit 1
}

# description:
#   print an error message and show an error notification
#   about a not found ffmpeg component and exit the program
# arguments:
#   $1 - the not found ffmpeg component
#   $2 - ffmpeg component type ([audio/video] encoder, [audio/video] decoder, muxer, demuxer)
#   $3 - show suggestion to try a different component (true, false)
# return value: not relevant
# return code (status): not relevant
component_error() {
    print_error "the detected ffmpeg build has no support for '${1}' ${2}"
    printf '%s%s\n'   '                      ' \
                      "please install a ffmpeg build with support for '${1}' ${2}" >&2
                      
    notify 'critical' "$expire_time_long" "$error_icon" \
           "error: the detected ffmpeg build has no support for '${1}' ${2}"
    
    if [ "$3" =  'true' ] && printf '%s' "$2" | grep -q '.*encoder$'
    then
        printf '%s%s\n'   '                      ' \
                          "(or try a different ${2})" >&2
                          
    elif [ "$3" =  'true' ] && printf '%s' "$2" | grep -q 'muxer'
    then
        printf '%s%s\n'   '                      ' \
                          "(or try a different ${2}/format)" >&2
    fi
    
    exit 1
}

# description:
#   exit the program with the proper message/notifications if the detected ffmpeg version is unsupported
# arguments:
#   $1 - error message to print/notificate
# return value: not relevant
# return code (status): not relevant
ffmpeg_version_error() {
    msg="$1"
    
    if [ "$auto_filename" = 'true' ] && [ "$format_setted" = 'false' ]
    then
        msg="${msg}
                      (did you forget to select the container format?)"
    fi
    
    show_settings
    exit_program "$msg"
}

#########################################
#               show info               #
#########################################

# description:
#   format the supported componenets for use with show_list() in -l/--list option
# arguments:
#   $1 - variable with all supported components
# return value: the supported componenets formated for use with show_list() in -l/--list option
# return code (status): not relevant
get_list_format() {
    count='1'
    chars='25'
    
    while read -r item
    do
        if [ "$count" -eq "$(printf '%s' "$1" | wc -w)" ]
        then
            ending_chars='0'
            unset -v separator
        else
            ending_chars='2'
            separator=', '
        fi
        
        chars="$((chars + "$(printf '%s' "$item" | wc -m)" + ending_chars))"
        
        if [ "$chars" -gt '80' ]
        then
            chars="$((25 + "$(printf '%s' "$item" | wc -m)" + ending_chars))"
            formated_components="$(printf '%s\n%s%s' "$formated_components" \
                '                         ' "${item}${separator}")"
        else
            formated_components="${formated_components}${item}${separator}"
        fi
        
        count="$((count + 1))"
    done <<- __EOF__
		$(printf '%s\n' "$1")
__EOF__
    
    printf '%s' "$formated_components"
    
    unset -v count
    unset -v chars
    unset -v item
    unset -v ending_chars
    unset -v separator
    unset -v formated_components
}

# description: show program header
# arguments:
#   $1 - the positional parameters passed with double quotes ("$@")
# return value: the program header
# return code (status): not relevant
show_header() {
    # give error if --version/-V is not the unique command line option
    if [ "$version_setted" = 'true' ]
    then
        if [ "$shift_count" -ne '0' ] || [ "$#" -ne '1' ]
        then
            exit_program '--version (-V) option can be used only alone'
        fi
    fi
    
    cat <<- __EOF__
	screencast ${screencast_version} - Copyright (c) 2015-$(date +%Y) Daniel Bermond
	Command line interface to record a X11 desktop
	${screencast_website}
__EOF__
}

# description: show help
# arguments:
#   $1 - the positional parameters passed with double quotes ("$@")
# return value: the program help screen
# return code (status): not relevant
show_help() {
    # give error if --help/-h/-? is not the unique command line option
    if [ "$help_setted" = 'true' ]
    then
        if [ "$shift_count" -ne '0' ] || [ "$#" -ne '1' ]
        then
            exit_program '--help (-h) option can be used only alone'
        fi
    fi
    
    show_header "$@"
    
    cat <<- __EOF__
	
	Usage: screencast [options] <output>
	                  [options] -u
	                  [options] -L <URL>
	Options:
	  -s, --size=NxN            video size (resolution) [${video_size}]
	  -p, --position=N,N        recording position (screen XY topleft offsets) [${video_position}]
	  -d, --display=:N[.N]      X server display (and screen) number(s) [${display}]
	  -b, --border              tickness of the screen region border (0 disable) [${border}]
	  -S, --select-region       select with mouse the screen region to record
	  -r, --fps=N               video framerate (fps) [${video_rate}]
	  -f, --format=TYPE         container format (to use with -u) [${format}]
	  -i, --audio-input=NAME    audio input device [${audio_input}]
	  -a, --audio-encoder=NAME  audio encoder [${audio_encoder}]
	  -v, --video-encoder=NAME  video encoder [${video_encoder}]
	  -A, --vaapi-device=NODE   DRM render node [${vaapi_device}]
	  -e, --fade=TYPE           video fade effect [${fade}]
	  -m, --volume-factor=N     volume increase effect factor (1.0 disable) [${volume_factor}]
	  -w, --watermark=TEXT      enable and set text watermark effect [disabled]
	  -z, --wmark-size=NxN      watermark image size (resolution) [${watermark_size}]
	  -k, --wmark-position=N,N  watermark position (video XY topleft offsets,
	      --wmark-position=PRE    or a predefined special value) [${watermark_position}]
	  -c, --wmark-font=NAME     watermark font [${watermark_font}]
	  -W, --webcam              enable webcam overlay effect [disabled]
	  -I, --webcam-input=DEV    webcam input device [${webcam_input}]
	  -Z, --webcam-size=NxN     webcam video size (resolution) [${webcam_size}]
	  -P, --webcam-position=N,N webcam position (video XY topleft offsets,
	      --webcam-position=PRE   or a predefined special value) [${webcam_position}]
	  -R, --webcam-fps=N        webcam framerate (fps) [device default]
	  -L, --live-streaming=URL  enable live streaming, setting url to URL [disabled]
	  -1, --one-step            one step (record and encode at same time) [disabled]
	  -x, --fixed=N             fixed video length for N seconds (0 disable) [${fixed_length}]
	  -n, --no-notifications    disable desktop and sound notifications
	  -g, --png-optimizer=NAME  use png (watermark) optimizer and advdef [${pngoptimizer}]
	  -o, --output-dir=DIR      save videos to DIR (to use with -u) [local dir]
	  -t, --tmp-dir=DIR         use DIR for temporary files [${tmpdir}]
	  -K, --keep                keep the temporary video or the live streaming
	  -u, --auto-filename       auto choose output filename based on date/time
	  -l, --list                list arguments supported by these options
	  -h, --help                this help screen
	  -V, --version             version information
	
	For further help run 'man screencast'
__EOF__
}

# description: print a list of arguments supported by this program
# arguments:
#   $1 - the positional parameters passed with double quotes ("$@")
# return value: the described list
# return code (status): not relevant
show_list() {
    # give error if --list/-l is not the unique command line option
    if [ "$list_setted" = 'true' ]
    then
        if [ "$shift_count" -ne '0' ] || [ "$#" -ne '1' ]
        then
            exit_program '--list (-l) option can be used only alone'
        fi
    fi
    
    show_header "$@"
    get_supported_fade
    get_supported_pngoptmz
    
    list_wmark_position='topleft, topright, bottomleft, bottomright
                         (or the respective aliases tl, tr, bl, br)'
                         
    list_webcam_position="$list_wmark_position"
    
    cat <<- __EOF__
	
	Supported arguments:
	  -f, --format           $(get_list_format "$supported_formats_all")
	
	  -a, --audio-encoder    $(get_list_format "$supported_audiocodecs_all")
	
	  -v, --video-encoder    $(get_list_format "$supported_videocodecs_all")
	
	  -e, --fade             $(get_list_format "$supported_fade")
	
	  -k, --wmark-position   $(printf '%s' "$list_wmark_position")
	
	  -P, --webcam-position  $(printf '%s' "$list_webcam_position")
	
	  -g, --png-optimizer    $(get_list_format "$supported_pngoptmz")
	
	  note: selecting vorbis or opus audio encoders actually uses the higher
	        quality libvorbis and libopus encoders respectively.
	
	  note: the container formats mkv and nut support a combination of all audio
	        and video encoders. Restrictions apply to other container formats.
__EOF__
    
    unset -v list_wmark_position
    unset -v list_webcam_position
}

# description: show information about some program settings
# arguments: none
# return value: program settings information
# return code (status): not relevant
show_settings() {
    [ "$video_encoder" = "$video_encoder_default" ] && [ "$video_encoder_setted" = 'false' ] && video_outstr='(default)'
    [ "$audio_encoder" = "$audio_encoder_default" ] && [ "$audio_encoder_setted" = 'false' ] && audio_outstr='(default)'
    [ "$format"        = "$format_default"        ] && [ "$format_setted"        = 'false' ] &&
        [ "$auto_filename" = 'true'  ] && format_outstr='(default)'
    
    if printf '%s' "$videocodecs_vaapi" | grep -q "^${video_encoder}$"
    then
        video_outstr="(${vaapi_device})"
    fi
    
    [ "$fade" != 'none' ] && effects="fade-${fade}"
    
    if [ "$watermark" = 'true' ]
    then
        effects="${effects:+"${effects}, watermark"}"
        effects="${effects:-watermark}"
    fi
    
    if [ "$webcam_overlay" = 'true' ]
    then
        effects="${effects:+"${effects}, webcam overlay"}"
        effects="${effects:-webcam overlay}"
    fi
    
    if [ "$volume_increase" = 'true' ]
    then
        effects="${effects:+"${effects}, volume (${volume_factor})"}"
        effects="${effects:-"volume (${volume_factor})"}"
    fi
    
    effects="${effects:-none}"
    
    print_good "${color_bold}video encoder   :${color_off} ${video_encoder} ${video_outstr}"
    print_good "${color_bold}audio encoder   :${color_off} ${audio_encoder:-"no audio input"} ${audio_outstr}"
    
    [ "$saving_output" = 'true' ] && print_good "${color_bold}container format:${color_off} ${format} ${format_outstr}"
    
    print_good "${color_bold}effects         :${color_off} ${effects}"
}

# description: show warnings that should appear right after show_settings()
# arguments: none
# return value: program early warnings
# return code (status): not relevant
show_warnings() {
    # warn: user chosen a possible unplayable combinantion of audio/video encoder and container format
    if printf '%s' "$possible_unplayable_formats" | grep -q "^${format}$"
    then
        msg="on '${format}' container format may not be playable in some players"
        
        if printf '%s' "$possible_unplayable_audiocodecs" | grep -q "^${audio_encoder}$"
        then
            print_warn "'${audio_encoder}' audio encoder ${msg}"
        fi
        
        if printf '%s' "$possible_unplayable_videocodecs" | grep -q "^${video_encoder}$"
        then
            print_warn "'${video_encoder}' video encoder ${msg}"
        fi
        
        unset -v msg
    fi
    
    if [ "$auto_filename" = 'false' ]
    then
        # warn: output file already exists
        [ -f "${savedir}/${output_file}" ] &&
            print_warn "output file '${output_file}' already exists, overwriting without prompt"
            
        # warn: lossless output file already exists
        lossless_video="${output_file%.*}-lossless.${rec_extension}"
        
        [ "$streaming"  = 'false' ] && [ "$one_step" = 'false'  ] &&
        [ "$keep_video" = 'true'  ] && [ -f "${tmpdir}/${lossless_video}" ] &&
            print_warn "lossless output file '${lossless_video}' already exists, overwriting without prompt"
            
        unset -v lossless_video
    fi
    
    if [ "$streaming" = 'true' ]
    then
        # warn: a possible output file already exists (if user specifies a local file in -L/--live-streaming option)
        [ -f "$streaming_url" ] && print_warn "output file '${streaming_url}' already exists, overwriting without prompt"
        
        # warn: using a software-based video encoder in live streaming
        if printf '%s' "$supported_videocodecs_software" | grep -q "^${video_encoder}$"
        then
            print_warn 'using a software-based video encoder in live streaming is not recommended
                        (can cause buffer problems that may lead to packet loss)'
        fi
    else
        # warn: using a software-based video encoders in a one step process
        if [ "$one_step" = 'true' ]
        then
            if printf '%s' "$supported_videocodecs_software" | grep -q "^${video_encoder}$"
            then
                print_warn 'using a software-based video encoder in a one step process is not recommended
                        (can cause buffer problems that may lead to packet loss)'
                
            elif [ "$one_step_lossless" = 'true' ]
            then
                print_warn 'the recorded lossless video will not be encoded (file size will be large)'
                
                if [ "$one_step_setted" = 'false' ]
                then
                    print_warn 'one step process is auto chosen due to no encoding the lossless video'
                fi
            fi
        fi
    fi
}

#########################################
#                screen                 #
#########################################

# description:
#   create an error/warning message to use with a given inappropriate video dimension
# arguments:
#   $1 - a string denoting the given dimension (width/height)
# return value: a string with the created message
# return code (status): not relevant
dimension_msg() {
    get_videocodecs_for_nonmulti8_msg
    
    if printf '%s' "$msg_speedloss_videocodecs" | grep -q "^${video_encoder}$"
    then
        msg='(this can lead to a speedloss)'
        
    elif printf '%s' "$msg_requirement_videocodecs" | grep -q "^${video_encoder}$"
    then
        msg="(${video_encoder} requirement)"
    fi
    
    case "$1" in
        width)
            printf '%s' "video width '${video_width}' is not a multiple of 8 ${msg}"
            ;;
        height)
            printf '%s' "video height '${video_height}' is not a multiple of 8 ${msg}"
            ;;
        # no need to exit since it's usually called from a subshell
        *)
            printf '%s' "dimension_msg(): invalid \$1 '${1}'"
            ;;
    esac
    
    unset -v msg
}

# description: check if a given dimension is a multiple of 8
# arguments:
#   $1 - the given dimension (the actual variable value)
# return value: none
# return code (status):
#   0 - the dimension is a multiple of 8
#   1 - the dimension is not a multiple of 8
check_dimension() {
    # the dimension will be a multiple of 8 if the remainder is 0
    [ "$(("$1" % 8))" = '0' ]
}

# description:
#   change a given dimension to the immediately higher number that is a multiple of 8
# arguments:
#   $1 - a string denoting the given dimension (width/height)
# return value = not relevant
# return code (status): not relevant
adjust_dimension() {
    dimension="$1"
    
    # get the dimension value denoted by the string in $dimension
    case "$dimension" in
        width)
            dimension_value="$video_width"
            ;;
        height)
            dimension_value="$video_height"
            ;;
        *)
            exit_program "adjust_dimension(): invalid dimension '${dimension}'"
            ;;
    esac
    
    # obtain the next multiple of 8 number
    remainder="$((dimension_value % 8))"
    to_reach="$((8 - remainder))"
    new_dimension_value="$((dimension_value + to_reach))"
    
    print_warn "$(dimension_msg "$dimension") and was changed to '${new_dimension_value}'"
    
    # change the given dimension to the new value
    case "$dimension" in
        width)
            video_width="$new_dimension_value"
            ;;
        height)
            video_height="$new_dimension_value"
            ;;
    esac
    
    unset -v dimension
    unset -v dimension_value
    unset -v remainder
    unset -v to_reach
    unset -v new_dimension_value
}

# description:
#   check for valid video size and position in relation to the current screen size
#   (will exit with error if any problem is encountered)
# arguments: none
# return value: not relevant
# return code (status): not relevant
# note: needs $video_width, $video_position_x, $video_height and $video_position_y
check_screen() {
    screen_size="$(xdpyinfo -display "$display" | awk 'FNR == 1 { next } /dimensions/ { print $2 }')"
    screen_width="$( printf '%s' "$screen_size" | awk -F'x' '{ print $1 }')"
    screen_height="$(printf '%s' "$screen_size" | awk -F'x' '{ print $2 }')"
    
    if [ "$((video_width + video_position_x))" -gt "$screen_width"  ]
    then
        exit_program 'recording area is out of screen bounds
                      (video width + position X is greater than the current screen width)'
    fi
    
    if [ "$((video_height + video_position_y))" -gt "$screen_height" ]
    then
        exit_program 'recording area is out of screen bounds
                      (video height + position Y is greater than the current screen height)'
    fi
    
    unset -v screen_size
    unset -v screen_width
    unset -v screen_height
}

# description:
#   select with mouse the screen region to record
#   (will exit with error if region selection is canceled)
# arguments: none
# return value: not relevant
# return code (status): not relevant
# note: sets $video_size and $video_position
get_region() {
    print_good 'please select with mouse a screen region to record...'
    print_info 'single click to select a window, click and drag to select a region'
    print_info 'use arrow keys to fine tune when dragging, right click or any other keystroke to cancel'
    
    screen_region="$(slop -o -f '%x %y %w %h')" || exit_program 'screen region was not selected'
    
    if ! printf '%s' "$screen_region" | grep -Eq '^([0-9]+[[:space:]]){3}[0-9]+$'
    then
        exit_program 'slop returned wrong values'
    fi
    
    video_position_x="$(printf '%s' "$screen_region" | awk '{ print $1 }')"
    video_position_y="$(printf '%s' "$screen_region" | awk '{ print $2 }')"
    video_width="$(     printf '%s' "$screen_region" | awk '{ print $3 }')"
    video_height="$(    printf '%s' "$screen_region" | awk '{ print $4 }')"
    
    # change video width and height if not a multiple of 8
    check_dimension "$video_width"  || adjust_dimension 'width'
    check_dimension "$video_height" || adjust_dimension 'height'
    
    video_size="${video_width}x${video_height}"
    video_position="${video_position_x},${video_position_y}"
    
    unset -v screen_region
}

#########################################
#                effects                #
#########################################

# description: check for a valid special position string
# arguments:
#   $1 - the string to check
# return value: not relevant
# return code (status):
#   0 - a valid special positon string was passed
#   1 - an invalid special positon string was passed
# sets special variables: $special_position - the value to which the string translates
check_special_position() {
    case "$1" in
        topright|tr)
            special_position="main_w-overlay_w-${corner_padding}:${corner_padding}"
            ;;
        bottomright|br)
            special_position="main_w-overlay_w-${corner_padding}:main_h-overlay_h-${corner_padding}"
            ;;
        topleft|tl)
            special_position="${corner_padding}:${corner_padding}"
            ;;
        bottomleft|bl)
            special_position="${corner_padding}:main_h-overlay_h-${corner_padding}"
            ;;
        *)
            return 1
            ;;
    esac
}

# description:
#   check for a valid png optimizer (-g/--png-optimizer)
#   (will exit with error if an invalid png optimizer is chosen)
# arguments: none
# return value: not relevant
# return code (status): 0 - a valid png optimizer was selected
# note1: it will make the program exit with error if an invalid png optimizer was selected
# note2:
#   this png optimizer check should be on check_cmd_line(), but it is implemented
#   as a separate function to allow it to be executed before check_requirements()
check_pngoptimizer() {
    if [ "$pngoptimizer_setted" = 'true' ]
    then
        get_supported_pngoptmz
        
        if ! printf '%s' "$supported_pngoptmz" | grep -q "^${pngoptimizer}$"
        then
            exit_program "'${pngoptimizer}' is not a valid PNG optimizer for this program"
        fi
    fi
}

# description: optimize the png (watermark) image
# arguments: none
# return value: not relevant
# return code (status): not relevant
# note: needs $wmark_image
optimize_png() {
    print_good 'optimizing watermark image'
    
    [ "$pngoptimizer" != 'opt-png' ] && "get_pngoptmz_settings_${pngoptimizer}"
    
    "$pngoptimizer" $pngoptmz_settings "$wmark_image"
    
    # use advdef to optimize PNG even more
    get_pngoptmz_settings_advdef
    advdef $advdef_settings "$wmark_image"
}

# description: create a text watermark and set watermark options to be passed to ffmpeg command
# arguments: none
# return value: not relevant
# return code (status):
#   0 - text watermark image was successfully created
#   1 - failed to create text watermark image
# sets special variables: $watermark_vfilter - ffmpeg video filter options for watermark
create_watermark() {
    print_good 'generating watermark image'
    rndstr_png="$(randomstr '12')" # random string for tmp png filename
    wmark_image="${tmpdir}/screencast-tmpimage-${rndstr_png}.png"
    
    # get font pointsize
    if watermark_pointsize="$(magick \
                                  -size "$watermark_size" \
                                  -font "$watermark_font" \
                                  label:"$watermark_text" \
                                  -format '%[label:pointsize]' \
                                  info:)"
    then
        # check if font pointsize was correctly obtained (integer/float number)
        if printf '%s' "$watermark_pointsize" | grep -Eq '^[0-9]+(\.[0-9]+)?$'
        then
            # generate the watermark
            if magick \
                   -size "$watermark_size" \
                   -font "$watermark_font" \
                   -pointsize "$watermark_pointsize" \
                   -gravity center \
                   \( \
                       xc:grey30 \
                       -draw "fill gray70  text 0,0  '${watermark_text}'" \
                   \) \
                   \( \
                       xc:black \
                       -draw "fill white  text  1,1  '${watermark_text}'  \
                                          text  0,0  '${watermark_text}'  \
                              fill black  text -1,-1 '${watermark_text}'" \
                       -alpha Off \
                   \) \
                   -alpha Off \
                   -compose copy-opacity \
                   -composite \
                   -trim \
                   +repage \
                   "$wmark_image"
            then
                # check if the generated watermark file is truly a PNG image
                if file "$wmark_image" | grep -q 'PNG image data'
                then
                    # optimize PNG image if chosen by user (-g/--png-optimizer)
                    [ "$pngoptimizer" != 'none' ] && optimize_png
                    
                    return 0
                else
                    print_warn 'the generated watermark is not a PNG file (watermarking aborted)' >&2
                    return 1
                fi # end: if file
            else
                print_warn 'failed to create watermark image (watermarking aborted)' >&2
                return 1
            fi # end: if magick
        else
            print_warn 'the obtained watermark font pointsize is not a valid integer/float number (watermarking aborted)' >&2
            return 1
        fi # end: if printf
    else
        print_warn 'failed to obtain watermark font pointsize (watermarking aborted)' >&2
        return 1
    fi # end: if watermark_pointsize=
}

# description: sets video fade options to be passed to ffmpeg command
# arguments: none
# return value: none
# return code (status): not relevant
# sets special variables: $fade_options - ffmpeg fade options
videofade() {
    get_fade_settings
    
    # get recorded video length in seconds
    video_length="$(ffprobe \
                        -i "${tmpdir}/screencast-lossless-${rndstr_video}.${rec_extension}" \
                        -show_entries format='duration' \
                        -v quiet \
                        -of csv='p=0')"
    
    # set start time of fade-out in seconds
    total_fadeout="$(awk "BEGIN { OFMT=\"%.2f\"; print ${fade_length}  + ${fade_solid_length} }")"
    fadeout_start="$(awk "BEGIN { OFMT=\"%.2f\"; print ${video_length} - ${total_fadeout} }")"
    
    # build ffmpeg fade in/out options
    fadein="fade=type=in:start_time=${fade_solid_length}:duration=${fade_length}:color=${fade_color}"
    fadeout="fade=type=out:start_time=${fadeout_start}:duration=${fade_length}:color=${fade_color}"
    
    # check the chosen fade type and set ffmpeg fade options if necessary
    case "$fade" in
        in)
            fade_options="$fadein"
            ;;
        out)
            fade_options="$fadeout"
            ;;
        both)
            fade_options="${fadein},${fadeout}"
            ;;
    esac
    
    unset -v video_length
    unset -v total_fadeout
    unset -v fadeout_start
    unset -v fadein
    unset -v fadeout
}

#########################################
#              set configs              #
#########################################

# description: set needed options for live streaming (-L/--live-streaming)
# arguments: none
# return value: none
# return code (status): not relevant
set_live() {
    # set ffmpeg '-maxrate' and '-bufsize' options
    if   [ "$video_height" -gt '2160' ]
    then
         if [ "$video_rate" -ge '60' ]
         then
             video_encode_codec="${video_encode_codec} -maxrate 40M -bufsize 80M"
         else
             video_encode_codec="${video_encode_codec} -maxrate 30M -bufsize 60M"
         fi
         
    elif [ "$video_height" -eq '2160' ]
    then
         if [ "$video_rate" -ge '60' ]
         then
             video_encode_codec="${video_encode_codec} -maxrate 22M -bufsize 44M"
         else
             video_encode_codec="${video_encode_codec} -maxrate 15M -bufsize 30M"
         fi
         
    elif [ "$video_height" -eq '1440' ] ||
         {
             [ "$video_height" -lt '2160' ] &&
             [ "$video_height" -gt '1440' ];
         }
    then
         if [ "$video_rate" -ge '60' ]
         then
             video_encode_codec="${video_encode_codec} -maxrate 10M -bufsize 20M"
         else
             video_encode_codec="${video_encode_codec} -maxrate 7M -bufsize 14M"
         fi
         
    elif [ "$video_height" -eq '1080' ] ||
         {
             [ "$video_height" -lt '1440' ] &&
             [ "$video_height" -gt '1080' ];
         }
    then
         if [ "$video_rate" -ge '60' ]
         then
             video_encode_codec="${video_encode_codec} -maxrate 5M -bufsize 10M"
         else
             video_encode_codec="${video_encode_codec} -maxrate 4M -bufsize 8M"
         fi
         
    elif [ "$video_height" -eq '720' ] ||
         {
             [ "$video_height" -lt '1080' ] &&
             [ "$video_height" -gt '720'  ];
         }
    then
         if [ "$video_rate" -ge '60' ]
         then
             video_encode_codec="${video_encode_codec} -maxrate 3M -bufsize 6M"
         else
             video_encode_codec="${video_encode_codec} -maxrate 2M -bufsize 4M"
         fi
         
    elif [ "$video_height" -eq '480' ] ||
         {
             [ "$video_height" -lt '720' ] &&
             [ "$video_height" -gt '480' ];
         }
    then
         video_encode_codec="${video_encode_codec} -maxrate 1M -bufsize 2M"
         
    elif [ "$video_height" -eq '360' ] ||
         {
             [ "$video_height" -lt '480' ] &&
             [ "$video_height" -gt '360' ];
         }
    then
         video_encode_codec="${video_encode_codec} -maxrate 500k -bufsize 1000k"
         
    elif [ "$video_height" -eq '240' ] ||
         {
             [ "$video_height" -lt '360' ] &&
             [ "$video_height" -gt '240' ];
         }
    then
         video_encode_codec="${video_encode_codec} -maxrate 400k -bufsize 800k"
         
    else
         video_encode_codec="${video_encode_codec} -maxrate 2M -bufsize 4M"
    fi
    
    video_encode_codec="${video_encode_codec} -g $((video_rate * 2))" # set gop size
}

# description: enable watermark effect if chosen by user (-w/--watermark)
# arguments: none
# return value: none
# return code (status): not relevant
set_watermark() {
    if [ "$watermark" = 'true' ]
    then
        check_dir "$tmpdir"
        
        if create_watermark
        then
            ff_watermark_options="-framerate ${video_rate} -thread_queue_size ${queue_size} -i ${wmark_image}"
            
            watermark_vfilter="overlay=${watermark_position}"
            
            ff_vfilter_option='-filter_complex'
            ff_vfilter_settings="${ff_vfilter_settings:+"${ff_vfilter_settings},${watermark_vfilter}"}"
            ff_vfilter_settings="${ff_vfilter_settings:-"$watermark_vfilter"}"
        fi
    fi
}

# description: enable webcam overlay effect if chosen by user (-W/--webcam)
# arguments: none
# return value: none
# return code (status): not relevant
set_webcam() {
    if [ "$webcam_overlay" = 'true' ]
    then
        check_component video4linux2,v4l2 demuxer || component_error video4linux2,v4l2 demuxer false
        
        [ "$webcam_rate_setted" = 'true' ] && ff_webcam_options="-framerate ${webcam_rate}"
        
        ff_webcam_options="${webcam_input_options} ${ff_webcam_options} -video_size ${webcam_size} -i ${webcam_input}"
        
        ff_vfilter_option='-filter_complex'
        ff_vfilter_settings="${ff_vfilter_settings:+"${ff_vfilter_settings},overlay=${webcam_position}:format=auto"}"
        ff_vfilter_settings="${ff_vfilter_settings:-"overlay=${webcam_position}:format=auto"}"
    fi
}

# description:
#   enable volume increase effect if chosen by user (-m/--volume-factor)
#   (to enable: a value different than '1.0' or '0.0')
# arguments: none
# return value: none
# return code (status): not relevant
set_volume() {
    [ "$volume_increase" = 'true' ] && ff_volume_options="-af volume=${volume_factor}"
}

# description: fix 'pass duration too large' messages in ffmpeg
# arguments: none
# return value: none
# return code (status): not relevant
fix_pass_duration() {
    [ "$webcam_overlay" = 'false' ] && [ "$watermark" = 'false' ] && ff_vfilter_option='-vf'
    ff_vfilter_settings="${ff_vfilter_settings:+"${ff_vfilter_settings},fps=${video_rate}"}"
    ff_vfilter_settings="${ff_vfilter_settings:-"fps=${video_rate}"}"
}

# description:
#   set needed options for vaapi and qsv hardware accelerated video encoders
#   (-v/--video-encoder) if they are chosen by the user
# arguments: none
# return value: none
# return code (status): not relevant
set_vaapi_qsv() {
    if printf '%s' "$videocodecs_vaapi" | grep -q "^${video_encoder}$"
    then
        if [ "$one_step" = 'true' ]
        then
            [ "$webcam_overlay" = 'false' ] && [ "$watermark" = 'false' ] && [ "$fade" = 'none'  ] && ff_vfilter_option='-vf'
        else
            [ "$streaming"      = 'false' ] && [ "$watermark" = 'false' ] && [ "$fade" = 'none'  ] && ff_vfilter_option='-vf'
        fi
        
        ff_vfilter_settings="${ff_vfilter_settings:+"${ff_vfilter_settings},format=nv12,hwupload"}"
        ff_vfilter_settings="${ff_vfilter_settings:-format=nv12,hwupload}"
        
        ff_vaapi_options="-vaapi_device ${vaapi_device}"
        pixel_format='vaapi_vld'
    
    elif printf '%s' "$videocodecs_qsv" | grep -q "^${video_encoder}$"
    then
        pixel_format='nv12'
    fi
}

#########################################
#                ffmpeg                 #
#########################################

# description:
#   check if at least one supported lossless component of the given type is
#   available in the detected ffmpeg build
# arguments:
#   $1 - component type (format, audiocodec, videocodec)
# return value: none
# return code (status): not relevant
# note1:
#   it will automatically fallback to the next supported lossless component of the
#   given type that is found
# note2:
#   program will exit with error if no supported lossless components of the given type
#   is available in ffmpeg
check_lossless_component() {
    case "$1" in
        format)
            component_list="$supported_formats_lossless"
            component_generic_name='muxer/demuxer'
            ;;
        audiocodec)
            component_list="$supported_audiocodecs_lossless"
            component_generic_name='audio encoder/decoder'
            ;;
        videocodec)
            component_list="$supported_videocodecs_lossless"
            component_generic_name='video encoder/decoder'
            ;;
        *)
            exit_program "check_lossless_component(): invalid component type '${1}'"
            ;;
    esac
    
    first_component="$( printf '%s' "$component_list" | head -n 1)"
    second_component="$(printf '%s' "$component_list" | sed  -n '2p')"
    last_component="$(  printf '%s' "$component_list" | tail -n 1)"
        
    for component in $component_list
    do
        if "lossless_${1}_settings_${component}"
        then
            if [ "$component" = "$first_component" ]
            then
                break
            else
                if [ "$component" = "$second_component" ]
                then
                    previous_components="$(printf '%s' "$previous_components" | sed "s/,[[:space:]]$//")"
                    
                elif [ "$component" = "$last_component" ]
                then
                    previous_components="$(printf '%s' "$previous_components" | sed 's/,[[:space:]]$//')"
                    last_word="$(          printf '%s' "$previous_components" | awk '{ print $NF }')"
                    previous_components="$(printf '%s' "$previous_components" | sed "s/,[[:space:]]${last_word}$/ or ${last_word}/")"
                fi
                
                print_warn "no '${previous_components}' ${component_generic_name} support in ffmpeg"
                print_warn "falling back to '${component}' ${component_generic_name} for lossless recording"
                
                if [ "$1" = 'videocodec' ] &&
                   printf '%s' "$largefile_videocodecs_lossless" | grep -q "^${component}$"
                then
                    print_warn "'${component}' encoder will produce an extra-large temporary video, change the tmp direcotry if needed"
                fi
                
                break
            fi
        else
            if [ "$component" = "$last_component" ]
            then
                previous_components="$(printf '%s' "$previous_components" | sed "s/,[[:space:]]$/ or ${component}/")"
                component_error "$previous_components" "$component_generic_name" false
            else
                previous_components="${previous_components}${component}, "
                continue
            fi
        fi
    done
    
    unset -v component_list
    unset -v component_generic_name
    unset -v first_component
    unset -v second_component
    unset -v last_component
    unset -v component
    unset -v previous_components
}

# description: check if the detected ffmpeg build has support for a given component
# arguments:
#   $1 - component name
#   $2 - component type (encoder, decoder, muxer, demuxer)
# return value: not relevant
# reutrn code (status):
#   0 - ffmpeg build has support for the desired component
#   1 - ffmpeg build has no support for the desired component
# note1: needs $ffmpeg_codecs for encoders and decoders - ffmpeg_codecs="$(ffmpeg -codecs -v quiet)"
# note2: needs $ffmpeg_formats for muxers and demuxers - ffmpeg_formats="$(ffmpeg -formats -v quiet)"
check_component() {
    case "$2" in
        encoder)
            if ! printf '%s' "$ffmpeg_codecs" | grep -q "(encoders:.*${1}" &&
               ! printf '%s' "$ffmpeg_codecs" | grep -q "^[[:space:]].E.\\{4\\}[[:space:]]${1}[[:space:]]"
            then
                return 1
            fi
            ;;
        decoder)
            if ! printf '%s' "$ffmpeg_codecs" | grep -q "(decoders:.*${1}" &&
               ! printf '%s' "$ffmpeg_codecs" | grep -q "^[[:space:]]D.\\{5\\}[[:space:]]${1}[[:space:]]"
            then
                return 1
            fi
            ;;
        muxer)
            if ! printf '%s' "$ffmpeg_formats" | grep -q "^[[:space:]].E[[:space:]]${1}[[:space:]]"
            then
                return 1
            fi
            ;;
        demuxer)
            if ! printf '%s' "$ffmpeg_formats" | grep -q "^[[:space:]]D.[[:space:]]${1}[[:space:]]"
            then
                return 1
            fi
            ;;
        *)
            exit_program "check_component(): invalid component type '${2}'"
            ;;
    esac
}

# description:
#   check if the version of the detected ffmpeg build is at least at a minium required
#   version (checks if ffmpeg version is greater of equal than a specified one; checks
#   for both release and git versions)
# arguments:
#   $1 - ffmpeg release version (example: 4.4)
#   $2 - ffmpeg internal git revision (example: 98615)
# return value: not relevant
# reutrn code (status):
#   0 - ffmpeg version is greater or equal than the specified one
#   1 - ffmpeg version is not greater or equal than the specified one
check_minimum_ffmpeg_version() {
    if [ -z "$1" ] || [ -z "$2" ]
    then
        exit_program 'check_minimum_ffmpeg_version(): invalid usage'
    fi
    
    ffmpeg_version="$(ffmpeg -version | awk '/Copyright/ { print $3 }')"
    
    if printf '%s' "$ffmpeg_version" | grep -Eq '^[0-9]+\..*'
    then
        if [ "$(printf '%s\n' "$1" "$ffmpeg_version" | sort | head -n1)" != "$1" ]
        then
            return 1
        fi
    elif printf '%s' "$ffmpeg_version" | grep -Eq '^N-[0-9]+-[[:alnum:]]+$'
    then
        if [ "$(printf '%s' "$ffmpeg_version" | awk -F'-' '{ print $2 }')" -lt "$2" ]
        then
            return 1
        fi
    else
        exit_program "check_minimum_ffmpeg_version(): unrecognized ffmpeg version format '${ffmpeg_version}'"
    fi
}

# description: execute ffmpeg command according to predefined variables
# arguments: none
# return value: not relevant
# return code (status):
#   the ffmpeg return status, usually:
#   0 - ffmpeg command executed successfully (ffmpeg normal exit)
#   1 - ffmpeg command failed (ffmpeg error)
run_ffmpeg() {
    ffmpeg \
        $ff_audio_options \
        $ff_vaapi_options \
        $ff_video_options \
        $ff_webcam_options \
        $ff_watermark_options \
        $ff_vfilter_option $ff_vfilter_settings \
        $ff_volume_options \
        $ff_flag_global_header \
        $ff_audio_codec \
        -codec:v $ff_video_codec \
        $ff_fixed_length_options \
        $ff_pixfmt_options \
        $ff_faststart \
        $ff_map \
        -metadata "$metadata" \
        -y \
        $ff_output
}

#########################################
#                record                 #
#########################################

# description: record and live stream
# arguments: none
# return value: none
# return code (status): not relevant
# note: it will make the program exit with error if a recording error occurs
live_streaming() {
    set_live
    set_watermark
    set_vaapi_qsv
    set_volume

    ff_audio_codec="$audio_encode_codec"
    ff_video_codec="$video_encode_codec"
    ff_pixfmt_options="-pix_fmt ${pixel_format}"
    
    print_good 'live streaming'
    notify 'normal' "$expire_time_short" "$record_icon" 'live streaming...'
    
    # do the live stream and save the recorded content to an output file
    if [ "$saving_output" = 'true' ]
    then
        check_dir "$savedir"
        
        ff_flag_global_header='-flags +global_header'
        ff_map='-map 1:v -map 0:a'
        ff_output="-f tee ${tee_faststart}${savedir}/${output_file}|[f=flv]${streaming_url}"
        
    # live streaming only, do not save the recorded content to an output file
    else
        ff_output="-f flv ${streaming_url}"
    fi
    
    if run_ffmpeg
    then
        finish
    else
        exit_program 'recording error!'
    fi
}

# description:
#   record offline (without live streaming) using one step
#   (recording and encoding at the same time).
# arguments: none
# return value: none
# return code (status): not relevant
# note: it will make the program exit with error if a recording error occurs
record_offline_one_step() {
    set_watermark
    set_vaapi_qsv
    set_volume
    check_dir "$savedir"
    
    if [ "$one_step_lossless" = 'true' ]
    then
        ff_audio_codec="$audio_record_codec"
        ff_video_codec="$video_record_codec"
    else
        ff_audio_codec="$audio_encode_codec"
        ff_video_codec="$video_encode_codec"
        ff_pixfmt_options="-pix_fmt ${pixel_format}"
    fi
    
    ff_output="${savedir}/${output_file}"
    
    print_good 'recording (one step process)'
    notify 'normal' "$expire_time_short" "$record_icon" 'recording (one step)...'
    
    # record screen and encode in one step
    if run_ffmpeg
    then
        finish
    else
        exit_program 'recording error!'
    fi
}

# description:
#   record offline (without live streaming) using two steps
#   (1st step: lossless recording. 2nd step: encoding).
# arguments: none
# return value: none
# return code (status): not relevant
# note: it will make the program exit with error if a recording or encoding error occurs
record_offline_two_steps() {
    [ "$webcam_overlay" = 'false' ] && [ "$watermark" = 'true' ] && ff_vfilter_option='-vf'
    
    check_dir "$savedir"
    check_dir "$tmpdir"
    
    rndstr_video="$(randomstr '12')" # random string for tmp video filename
    
    ff_audio_codec="$audio_record_codec"
    ff_video_codec="$video_record_codec"
    ff_output="${tmpdir}/screencast-lossless-${rndstr_video}.${rec_extension}"
    
    print_good 'recording'
    notify 'normal' "$expire_time_short" "$record_icon" 'recording...'
    
    # record screen to a lossless video
    if run_ffmpeg
    then
        unset -v ff_vfilter_option
        unset -v ff_vfilter_settings
        unset -v ff_webcam_options
        
        set_watermark
        
        # enable fade effect if chosen by user (-e/--fade)
        if [ "$fade" != 'none' ]
        then
            videofade
            [ "$watermark" = 'false' ] && ff_vfilter_option='-vf'
            ff_vfilter_settings="${ff_vfilter_settings:+"${ff_vfilter_settings},${fade_options}"}"
            ff_vfilter_settings="${ff_vfilter_settings:-"${fade_options}"}"
        fi
        
        set_vaapi_qsv
        set_volume
        
        ff_audio_options="${audio_channel_layout}"
        ff_video_options="-i ${tmpdir}/screencast-lossless-${rndstr_video}.${rec_extension}"
        ff_audio_codec="$audio_encode_codec"
        ff_video_codec="$video_encode_codec"
        ff_pixfmt_options="-pix_fmt ${pixel_format}"
        ff_map='-map 0'
        ff_output="${savedir}/${output_file}"
        
        print_good 'encoding'
        notify 'normal' "$expire_time_normal" "$encode_icon" 'encoding...'
        
        # encode the recorded lossless video file
        if run_ffmpeg
        then
            finish
        else
            exit_program 'encoding error!'
        fi
    else
        exit_program 'recording error!'
    fi
}

#########################################
#            program start              #
#########################################

trap 'cleanup' EXIT HUP INT QUIT ABRT TERM # signal handling

# check for color output support
if [ -n "$TERM" ] &&
   [ "$TERM" != 'dumb'    ] &&
   [ "$TERM" != 'unknown' ] &&
   command -v tput >/dev/null 2>&1
then
    color_off="$(tput sgr0)"
    color_bold="${color_off}$(tput bold)"
    color_blue="${color_bold}$(tput setaf 4)"
    color_yellow="${color_bold}$(tput setaf 3)"
    color_red="${color_bold}$(tput setaf 1)"
fi

# message header (colored, will fallback to non-colored if no color support)
msg_header="${color_blue}[ ${color_bold}screencast${color_blue} ]"

# enable some options if the executing shell is zsh
if [ -n "$ZSH_VERSION" ]
then
    command -v setopt >/dev/null 2>&1 || exit_program 'script appears to be running in zsh but setopt was not found'
    setopt SH_WORD_SPLIT # enable variable word splitting
fi

get_cmd_line "$@"
shift "$shift_count" # destroy all arguments except a possible output filename

show_header
print_good 'initializing'

# check if a X session is running
[ -z "$DISPLAY" ] && exit_program 'it seems that a X session is not running'

check_pngoptimizer
check_requirements

# prepartions for various checks: get the components supported by the detected ffmpeg build
ffmpeg_formats="$(ffmpeg -formats -v quiet)" # muxers   and demuxers (formats)
ffmpeg_codecs="$( ffmpeg -codecs  -v quiet)" # encoders and decoders

# check if the detected ffmpeg build has support for basic screen recording format
check_component x11grab demuxer || component_error x11grab demuxer false

check_cmd_line "$@"
show_settings
show_warnings

# select with mouse the screen region to record if chosen by user
[ "$select_region" = 'true' ] && get_region && check_screen

# common settings for all recording ways
set_webcam
fix_pass_duration
ff_audio_options="${audio_input_options} ${audio_input}"
ff_video_options="${video_input_options} ${border_options} -framerate ${video_rate} -video_size ${video_size} -i ${display}+${video_position}"

# do a live streaming if chosen by user (-L/--live-streaming)
if [ "$streaming" = 'true' ]
then
    live_streaming

# record offline if live streaming is not chosen by user
else
    if [ "$one_step" = 'true' ]
    then
        record_offline_one_step
    else
        record_offline_two_steps
    fi
fi
