mirror of
https://github.com/levogevo/ffmpeg-builder.git
synced 2026-01-15 19:06:17 +00:00
efg
This commit is contained in:
@@ -505,7 +505,7 @@ build_libopus() {
|
|||||||
|
|
||||||
### MESON ###
|
### MESON ###
|
||||||
build_libdav1d() {
|
build_libdav1d() {
|
||||||
local enableAsm='true'
|
local enableAsm=true
|
||||||
# arm64 will fail the build at 0 optimization
|
# arm64 will fail the build at 0 optimization
|
||||||
if [[ "${HOSTTYPE}:${OPT}" == "aarch64:0" ]]; then
|
if [[ "${HOSTTYPE}:${OPT}" == "aarch64:0" ]]; then
|
||||||
enableAsm="false"
|
enableAsm="false"
|
||||||
@@ -626,8 +626,7 @@ build_ffmpeg() {
|
|||||||
--enable-nonfree \
|
--enable-nonfree \
|
||||||
--disable-htmlpages \
|
--disable-htmlpages \
|
||||||
--disable-podpages \
|
--disable-podpages \
|
||||||
--disable-txtpages \
|
--disable-txtpages || return 1
|
||||||
--disable-autodetect || return 1
|
|
||||||
ccache make -j"${JOBS}" || return 1
|
ccache make -j"${JOBS}" || return 1
|
||||||
${SUDO_MODIFY} make -j"${JOBS}" install || return 1
|
${SUDO_MODIFY} make -j"${JOBS}" install || return 1
|
||||||
}
|
}
|
||||||
|
|||||||
259
lib/efg.sh
Normal file
259
lib/efg.sh
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
efg_usage() {
|
||||||
|
echo "efg -i input [options]"
|
||||||
|
echo -e "\t[-l NUM] low value (default: ${LOW})"
|
||||||
|
echo -e "\t[-s NUM] step value (default: ${STEP})"
|
||||||
|
echo -e "\t[-h NUM] high value (default: ${HIGH})"
|
||||||
|
echo -e "\t[-p] plot bitrates using gnuplot"
|
||||||
|
echo -e "\n\t[-I] system install at ${EFG_INSTALL_PATH}"
|
||||||
|
echo -e "\t[-U] uninstall from ${EFG_INSTALL_PATH}"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
set_efg_opts() {
|
||||||
|
local opts='pl:s:h:i:IU'
|
||||||
|
local numOpts=${#opts}
|
||||||
|
# default values
|
||||||
|
unset INPUT
|
||||||
|
LOW=0
|
||||||
|
STEP=1
|
||||||
|
HIGH=30
|
||||||
|
PLOT=false
|
||||||
|
EFG_INSTALL_PATH='/usr/local/bin/efg'
|
||||||
|
# only using -I or -U
|
||||||
|
local minOpt=1
|
||||||
|
# using all
|
||||||
|
local maxOpt=${numOpts}
|
||||||
|
test $# -lt ${minOpt} && echo_fail "not enough arguments" && efg_usage && return 1
|
||||||
|
test $# -gt ${maxOpt} && echo_fail "too many arguments" && efg_usage && return 1
|
||||||
|
OPTIND=1
|
||||||
|
while getopts "${opts}" flag; do
|
||||||
|
case "${flag}" in
|
||||||
|
I)
|
||||||
|
echo_warn "attempting install"
|
||||||
|
sudo ln -sf "${SCRIPT_DIR}/efg.sh" \
|
||||||
|
"${EFG_INSTALL_PATH}" || return 1
|
||||||
|
echo_pass "succesfull install"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
U)
|
||||||
|
echo_warn "attempting uninstall"
|
||||||
|
sudo rm "${EFG_INSTALL_PATH}" || return 1
|
||||||
|
echo_pass "succesfull uninstall"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
i)
|
||||||
|
if [[ $# -lt 2 ]]; then
|
||||||
|
echo_fail "wrong arguments given"
|
||||||
|
efg_usage
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
INPUT="${OPTARG}"
|
||||||
|
;;
|
||||||
|
p)
|
||||||
|
PLOT=true
|
||||||
|
;;
|
||||||
|
l)
|
||||||
|
if ! is_positive_integer "${OPTARG}"; then
|
||||||
|
efg_usage
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
LOW="${OPTARG}"
|
||||||
|
;;
|
||||||
|
s)
|
||||||
|
if ! is_positive_integer "${OPTARG}"; then
|
||||||
|
efg_usage
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
STEP="${OPTARG}"
|
||||||
|
;;
|
||||||
|
h)
|
||||||
|
if ! is_positive_integer "${OPTARG}"; then
|
||||||
|
efg_usage
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
HIGH="${OPTARG}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "wrong flags given"
|
||||||
|
efg_usage
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ! -f ${INPUT} ]]; then
|
||||||
|
echo "${INPUT} does not exist"
|
||||||
|
efg_usage
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# set custom EFG_DIR based off of sanitized inputfile
|
||||||
|
local sanitizedInput="$(bash_basename "${INPUT}")"
|
||||||
|
local sanitizeChars=(' ' '@' ':')
|
||||||
|
for char in "${sanitizeChars[@]}"; do
|
||||||
|
sanitizedInput="${sanitizedInput//${char}/}"
|
||||||
|
done
|
||||||
|
EFG_DIR+="-${sanitizedInput}"
|
||||||
|
|
||||||
|
echo_info "estimating film grain for ${INPUT}"
|
||||||
|
echo_info "range: $LOW-$HIGH with $STEP step increments"
|
||||||
|
}
|
||||||
|
|
||||||
|
efg_segment() {
|
||||||
|
# number of segments to split video
|
||||||
|
local segments=30
|
||||||
|
# duration of each segment
|
||||||
|
local segmentTime=3
|
||||||
|
|
||||||
|
# get times to split the input based
|
||||||
|
# off of number of segments
|
||||||
|
local duration
|
||||||
|
duration="$(get_duration "${INPUT}")" || return 1
|
||||||
|
# trim decimal points if any
|
||||||
|
IFS=. read -r duration _ <<<"${duration}"
|
||||||
|
# number of seconds that equal 1 percent of the video
|
||||||
|
local percentTime=$((duration / 100))
|
||||||
|
# percent that each segment takes
|
||||||
|
local percentSegment=$((100 / segments))
|
||||||
|
# number of seconds to increment between segments
|
||||||
|
local timeBetweenSegments=$((percentTime * percentSegment))
|
||||||
|
if [[ ${timeBetweenSegments} -lt ${segmentTime} ]]; then
|
||||||
|
timeBetweenSegments=${segmentTime}
|
||||||
|
fi
|
||||||
|
local segmentBitrates=()
|
||||||
|
|
||||||
|
# clean workspace
|
||||||
|
test -d "${EFG_DIR}" && rm -rf "${EFG_DIR}"
|
||||||
|
mkdir -p "${EFG_DIR}"
|
||||||
|
|
||||||
|
# split up video into segments based on start times
|
||||||
|
for ((time = 0; time < duration; time += timeBetweenSegments)); do
|
||||||
|
local outSegment="${EFG_DIR}/segment-${#segmentBitrates[@]}.mkv"
|
||||||
|
split_video "${INPUT}" "${time}" "${segmentTime}" "${outSegment}" || return 1
|
||||||
|
local segmentBitrate
|
||||||
|
segmentBitrate="$(get_avg_bitrate "${outSegment}")" || return 1
|
||||||
|
segmentBitrates+=("${segmentBitrate}:${outSegment}")
|
||||||
|
done
|
||||||
|
local numSegments="${#segmentBitrates[@]}"
|
||||||
|
|
||||||
|
local removeSegments
|
||||||
|
if [[ ${numSegments} -lt ${ENCODE_SEGMENTS} ]]; then
|
||||||
|
removeSegments=0
|
||||||
|
else
|
||||||
|
removeSegments=$((numSegments - ENCODE_SEGMENTS))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# sort the segments
|
||||||
|
mapfile -t sortedSegments < <(IFS=: bash_sort "${segmentBitrates[@]}")
|
||||||
|
# make sure bitrate for each file is actually increasing
|
||||||
|
local prevBitrate=0
|
||||||
|
# remove all but the highest bitrate segments
|
||||||
|
for segment in "${sortedSegments[@]}"; do
|
||||||
|
test ${removeSegments} -eq 0 && break
|
||||||
|
local file currBitrate
|
||||||
|
IFS=: read -r _ file <<<"${segment}"
|
||||||
|
currBitrate="$(get_avg_bitrate "${file}")" || return 1
|
||||||
|
|
||||||
|
if [[ ${currBitrate} -lt ${prevBitrate} ]]; then
|
||||||
|
echo_fail "${file} is not a higher bitrate than previous"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
prevBitrate=${currBitrate}
|
||||||
|
|
||||||
|
rm "${file}" || return 1
|
||||||
|
removeSegments=$((removeSegments - 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
efg_encode() {
|
||||||
|
echo -n >"${GRAIN_LOG}"
|
||||||
|
for vid in "${EFG_DIR}/"*.mkv; do
|
||||||
|
echo "file: ${vid}" >>"${GRAIN_LOG}"
|
||||||
|
for ((grain = LOW; grain <= HIGH; grain += STEP)); do
|
||||||
|
local file="$(bash_basename "${vid}")"
|
||||||
|
local out="${EFG_DIR}/grain-${grain}-${file}"
|
||||||
|
encode -P 10 -g ${grain} -i "${vid}" "${out}"
|
||||||
|
echo -e "\tgrain: ${grain}, bitrate: $(get_avg_bitrate "${out}")" >>"${GRAIN_LOG}"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
less "${GRAIN_LOG}"
|
||||||
|
}
|
||||||
|
|
||||||
|
efg_plot() {
|
||||||
|
declare -A normalizedBitrateSums=()
|
||||||
|
local referenceBitrate=''
|
||||||
|
local setNewReference=''
|
||||||
|
|
||||||
|
while read -r line; do
|
||||||
|
local noWhite="${line// /}"
|
||||||
|
# new file, reset logic
|
||||||
|
if line_starts_with "${noWhite}" 'file:'; then
|
||||||
|
setNewReference=true
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
IFS=',' read -r grainText bitrateText <<<"${noWhite}"
|
||||||
|
IFS=':' read -r _ grain <<<"${grainText}"
|
||||||
|
IFS=':' read -r _ bitrate <<<"${bitrateText}"
|
||||||
|
if [[ ${setNewReference} == 'true' ]]; then
|
||||||
|
referenceBitrate="${bitrate}"
|
||||||
|
setNewReference=false
|
||||||
|
fi
|
||||||
|
# bash doesn't support floats, so scale up by 10000
|
||||||
|
local normBitrate=$((bitrate * 10000 / referenceBitrate))
|
||||||
|
local currSumBitrate=${normalizedBitrateSums[${grain}]}
|
||||||
|
normalizedBitrateSums[${grain}]=$((normBitrate + currSumBitrate))
|
||||||
|
setNewReference=false
|
||||||
|
done <"${GRAIN_LOG}"
|
||||||
|
|
||||||
|
# create grain:average plot file
|
||||||
|
local plotFile="${EFG_DIR}/plot.dat"
|
||||||
|
echo -n >"${plotFile}"
|
||||||
|
for ((grain = LOW; grain <= HIGH; grain += STEP)); do
|
||||||
|
local sum=${normalizedBitrateSums[${grain}]}
|
||||||
|
local avg=$((sum / ENCODE_SEGMENTS))
|
||||||
|
echo -e "${grain}\t${avg}" >>"${plotFile}"
|
||||||
|
done
|
||||||
|
|
||||||
|
# plot data
|
||||||
|
bash -c 'echo $COLUMNS $LINES' >/dev/null 2>&1
|
||||||
|
gnuplot -p -e "\
|
||||||
|
set terminal dumb size ${COLUMNS}, ${LINES}; \
|
||||||
|
set autoscale; \
|
||||||
|
set style line 1 \
|
||||||
|
linecolor rgb '#0060ad' \
|
||||||
|
linetype 1 linewidth 2 \
|
||||||
|
pointtype 7 pointsize 1.5; \
|
||||||
|
plot \"${plotFile}\" with linespoints linestyle 1" | less
|
||||||
|
echo_info "grain log: ${GRAIN_LOG}"
|
||||||
|
}
|
||||||
|
|
||||||
|
FB_FUNC_NAMES+=('efg')
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
FB_FUNC_DESCS['efg']='estimate the film grain of a given file'
|
||||||
|
efg() {
|
||||||
|
EFG_DIR="${TMP_DIR}/efg"
|
||||||
|
# encode N highest-bitrate segments
|
||||||
|
ENCODE_SEGMENTS=5
|
||||||
|
|
||||||
|
set_efg_opts "$@" || return 1
|
||||||
|
test -d "${EFG_DIR}" || mkdir "${EFG_DIR}"
|
||||||
|
|
||||||
|
GRAIN_LOG="${EFG_DIR}/${LOW}-${STEP}-${HIGH}-grains.txt"
|
||||||
|
|
||||||
|
if [[ ${PLOT} == 'true' && -f ${GRAIN_LOG} ]]; then
|
||||||
|
efg_plot
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
efg_segment || return 1
|
||||||
|
efg_encode || return 1
|
||||||
|
|
||||||
|
if [[ ${PLOT} == 'true' && -f ${GRAIN_LOG} ]]; then
|
||||||
|
efg_plot || return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
@@ -22,19 +22,19 @@ set_audio_bitrate() {
|
|||||||
local numChannels codec
|
local numChannels codec
|
||||||
numChannels="$(get_num_audio_channels "${file}" "${stream}")" || return 1
|
numChannels="$(get_num_audio_channels "${file}" "${stream}")" || return 1
|
||||||
local channelBitrate=$((numChannels * 64))
|
local channelBitrate=$((numChannels * 64))
|
||||||
codec="$(get_stream_codec "${file}" "a:${stream}")" || return 1
|
codec="$(get_stream_codec "${file}" "${stream}")" || return 1
|
||||||
if [[ ${codec} == 'opus' ]]; then
|
if [[ ${codec} == 'opus' ]]; then
|
||||||
bitrate+=(
|
bitrate+=(
|
||||||
"-c:a:${stream}"
|
"-c:${stream}"
|
||||||
"copy"
|
"copy"
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
bitrate+=(
|
bitrate+=(
|
||||||
"-filter:a:${stream}"
|
"-filter:${stream}"
|
||||||
"aformat=channel_layouts=7.1|5.1|stereo|mono"
|
"aformat=channel_layouts=7.1|5.1|stereo|mono"
|
||||||
"-c:a:${stream}"
|
"-c:${stream}"
|
||||||
"libopus"
|
"libopus"
|
||||||
"-b:a:${stream}"
|
"-b:${stream}"
|
||||||
"${channelBitrate}k"
|
"${channelBitrate}k"
|
||||||
)
|
)
|
||||||
fi
|
fi
|
||||||
@@ -95,7 +95,7 @@ audio_enc_version() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
encode_usage() {
|
encode_usage() {
|
||||||
echo "$(bash_basename "$0") -i input [options] output"
|
echo "encode -i input [options] output"
|
||||||
echo -e "\t[-P NUM] set preset (default: ${PRESET})"
|
echo -e "\t[-P NUM] set preset (default: ${PRESET})"
|
||||||
echo -e "\t[-C NUM] set CRF (default: ${CRF})"
|
echo -e "\t[-C NUM] set CRF (default: ${CRF})"
|
||||||
echo -e "\t[-g NUM] set film grain for encode"
|
echo -e "\t[-g NUM] set film grain for encode"
|
||||||
@@ -105,22 +105,23 @@ encode_usage() {
|
|||||||
echo -e "\t[-v] Print relevant version info"
|
echo -e "\t[-v] Print relevant version info"
|
||||||
echo -e "\t[-s] use same container as input, default is mkv"
|
echo -e "\t[-s] use same container as input, default is mkv"
|
||||||
echo -e "\n\t[output] if unset, defaults to ${HOME}/"
|
echo -e "\n\t[output] if unset, defaults to ${HOME}/"
|
||||||
echo -e "\n\t[-I] Install this as /usr/local/bin/encode"
|
echo -e "\n\t[-I] system install at ${ENCODE_INSTALL_PATH}"
|
||||||
echo -e "\t[-U] Uninstall this from /usr/local/bin/encode"
|
echo -e "\t[-U] uninstall from ${ENCODE_INSTALL_PATH}"
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
set_encode_opts() {
|
set_encode_opts() {
|
||||||
local opts='vi:pcsdg:P:C:IU'
|
local opts='vi:pcsdg:P:C:IU'
|
||||||
local numOpts="${#opts}"
|
local numOpts=${#opts}
|
||||||
# default values
|
# default values
|
||||||
PRESET=3
|
PRESET=3
|
||||||
CRF=25
|
CRF=25
|
||||||
GRAIN=""
|
GRAIN=""
|
||||||
CROP='false'
|
CROP=false
|
||||||
PRINT_OUT='false'
|
PRINT_OUT=false
|
||||||
DISABLE_DV='false'
|
DISABLE_DV=false
|
||||||
local SAME_CONTAINER="false"
|
ENCODE_INSTALL_PATH='/usr/local/bin/encode'
|
||||||
|
local sameContainer="false"
|
||||||
# only using -I/U
|
# only using -I/U
|
||||||
local minOpt=1
|
local minOpt=1
|
||||||
# using all + output name
|
# using all + output name
|
||||||
@@ -128,18 +129,19 @@ set_encode_opts() {
|
|||||||
test $# -lt ${minOpt} && echo_fail "not enough arguments" && encode_usage && return 1
|
test $# -lt ${minOpt} && echo_fail "not enough arguments" && encode_usage && return 1
|
||||||
test $# -gt ${maxOpt} && echo_fail "too many arguments" && encode_usage && return 1
|
test $# -gt ${maxOpt} && echo_fail "too many arguments" && encode_usage && return 1
|
||||||
local optsUsed=0
|
local optsUsed=0
|
||||||
|
OPTIND=1
|
||||||
while getopts "${opts}" flag; do
|
while getopts "${opts}" flag; do
|
||||||
case "${flag}" in
|
case "${flag}" in
|
||||||
I)
|
I)
|
||||||
echo_warn "attempting install"
|
echo_warn "attempting install"
|
||||||
sudo ln -sf "${SCRIPT_DIR}/encode.sh" \
|
sudo ln -sf "${SCRIPT_DIR}/encode.sh" \
|
||||||
/usr/local/bin/encode || return 1
|
"${ENCODE_INSTALL_PATH}" || return 1
|
||||||
echo_pass "succesfull install"
|
echo_pass "succesfull install"
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
U)
|
U)
|
||||||
echo_warn "attempting uninstall"
|
echo_warn "attempting uninstall"
|
||||||
sudo rm /usr/local/bin/encode || return 1
|
sudo rm "${ENCODE_INSTALL_PATH}" || return 1
|
||||||
echo_pass "succesfull uninstall"
|
echo_pass "succesfull uninstall"
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
@@ -160,11 +162,11 @@ set_encode_opts() {
|
|||||||
optsUsed=$((optsUsed + 2))
|
optsUsed=$((optsUsed + 2))
|
||||||
;;
|
;;
|
||||||
p)
|
p)
|
||||||
PRINT_OUT='true'
|
PRINT_OUT=true
|
||||||
optsUsed=$((optsUsed + 1))
|
optsUsed=$((optsUsed + 1))
|
||||||
;;
|
;;
|
||||||
c)
|
c)
|
||||||
CROP='true'
|
CROP=true
|
||||||
optsUsed=$((optsUsed + 1))
|
optsUsed=$((optsUsed + 1))
|
||||||
;;
|
;;
|
||||||
d)
|
d)
|
||||||
@@ -172,7 +174,7 @@ set_encode_opts() {
|
|||||||
optsUsed=$((optsUsed + 1))
|
optsUsed=$((optsUsed + 1))
|
||||||
;;
|
;;
|
||||||
s)
|
s)
|
||||||
SAME_CONTAINER='true'
|
sameContainer=true
|
||||||
optsUsed=$((optsUsed + 1))
|
optsUsed=$((optsUsed + 1))
|
||||||
;;
|
;;
|
||||||
g)
|
g)
|
||||||
@@ -217,7 +219,7 @@ set_encode_opts() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# use same container for output
|
# use same container for output
|
||||||
if [[ $SAME_CONTAINER == "true" ]]; then
|
if [[ $sameContainer == "true" ]]; then
|
||||||
local fileFormat
|
local fileFormat
|
||||||
fileFormat="$(get_file_format "${INPUT}")" || return 1
|
fileFormat="$(get_file_format "${INPUT}")" || return 1
|
||||||
FILE_EXT=''
|
FILE_EXT=''
|
||||||
@@ -235,6 +237,12 @@ set_encode_opts() {
|
|||||||
OUTPUT="${OUTPUT%.*}"
|
OUTPUT="${OUTPUT%.*}"
|
||||||
OUTPUT+=".${FILE_EXT}"
|
OUTPUT+=".${FILE_EXT}"
|
||||||
|
|
||||||
|
if [[ ! -f ${INPUT} ]]; then
|
||||||
|
echo "${INPUT} does not exist"
|
||||||
|
efg_usage
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo_info "INPUT: ${INPUT}"
|
echo_info "INPUT: ${INPUT}"
|
||||||
echo_info "GRAIN: ${GRAIN}"
|
echo_info "GRAIN: ${GRAIN}"
|
||||||
|
|||||||
@@ -9,6 +9,33 @@ get_duration() {
|
|||||||
"${file}"
|
"${file}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_avg_bitrate() {
|
||||||
|
local file="$1"
|
||||||
|
ffprobe \
|
||||||
|
-v error \
|
||||||
|
-select_streams v:0 \
|
||||||
|
-show_entries format=bit_rate \
|
||||||
|
-of default=noprint_wrappers=1:nokey=1 \
|
||||||
|
"${file}"
|
||||||
|
}
|
||||||
|
|
||||||
|
split_video() {
|
||||||
|
local file="$1"
|
||||||
|
local start="$2"
|
||||||
|
local time="$3"
|
||||||
|
local out="$4"
|
||||||
|
ffmpeg \
|
||||||
|
-ss "${start}" \
|
||||||
|
-i "${file}" \
|
||||||
|
-hide_banner \
|
||||||
|
-loglevel error \
|
||||||
|
-t "${time}" \
|
||||||
|
-map 0:v \
|
||||||
|
-reset_timestamps 1 \
|
||||||
|
-c copy \
|
||||||
|
"${out}"
|
||||||
|
}
|
||||||
|
|
||||||
get_crop() {
|
get_crop() {
|
||||||
local file="$1"
|
local file="$1"
|
||||||
local duration
|
local duration
|
||||||
|
|||||||
21
lib/utils.sh
21
lib/utils.sh
@@ -202,3 +202,24 @@ is_positive_integer() {
|
|||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bash_sort() {
|
||||||
|
local arr=("$@")
|
||||||
|
local n=${#arr[@]}
|
||||||
|
local i j val1 val2
|
||||||
|
|
||||||
|
# Bubble sort, numeric comparison
|
||||||
|
for ((i = 0; i < n; i++)); do
|
||||||
|
for ((j = 0; j < n - i - 1; j++)); do
|
||||||
|
read -r val1 _ <<<"${arr[j]}"
|
||||||
|
read -r val2 _ <<<"${arr[j + 1]}"
|
||||||
|
if (("${val1}" > "${val2}")); then
|
||||||
|
local tmp=${arr[j]}
|
||||||
|
arr[j]=${arr[j + 1]}
|
||||||
|
arr[j + 1]=$tmp
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '%s\n' "${arr[@]}"
|
||||||
|
}
|
||||||
|
|||||||
1
scripts/efg.sh
Symbolic link
1
scripts/efg.sh
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
entry.sh
|
||||||
Reference in New Issue
Block a user