stream-sprout/stream-sprout
2024-07-26 07:31:47 +01:00

406 lines
22 KiB
Bash
Executable file

#!/usr/bin/env bash
# shellcheck disable=SC2154
# Disable echo of control characters like ^C
stty -echoctl
readonly STREAM_SPROUT_YAML="stream-sprout.yaml"
readonly VERSION="0.1.5"
function cleanup() {
echo -e " \e[31m\U26D4\e[0m Control-C"
sleep 0.25
if kill -0 "${FFMPEG_PID}" 2>/dev/null; then
echo -e " \e[31m\U1F480\e[0m FFmpeg process (${FFMPEG_PID}) has been terminated"
kill "${FFMPEG_PID}"
else
echo -e " \e[31m\U23F9\e[0m FFmpeg process (${FFMPEG_PID}) has ended"
fi
rename_archive
exit
}
# Function to display help
function show_help() {
echo "Restream a video source to multiple destinations such as Twitch, YouTube, Owncast and Peertube."
echo ""
echo "Usage: $(basename "${0}") [options]"
echo ""
echo "Options:"
echo " --config <path> Specify a custom config file path."
echo " --info Show system information; useful when filing bug reports."
echo " --version Show version information."
echo " --help Display this help message."
}
function show_info() {
local CONTAINER_ENV
local CONTAINER_RUNTIME
local CONTAINER_RUNTIMES=("docker" "lxc" "podman")
local OS_KERNEL
local PRETTY_NAME
OS_KERNEL=$(uname -s)
if [ "${OS_KERNEL}" == "Darwin" ]; then
# Get macOS product name and version using swvers
if [ -x "$(command -v sw_vers)" ]; then
PRETTY_NAME="$(sw_vers -productName) $(sw_vers -productVersion)"
else
PRETTY_NAME="macOS"
fi
elif [ -e /etc/os-release ]; then
PRETTY_NAME=$(grep PRETTY_NAME /etc/os-release | cut -d'"' -f2)
else
PRETTY_NAME="Unknown OS"
fi
echo -e "Operating System : ${PRETTY_NAME}"
# Check for container environment
if [ "${OS_KERNEL}" == "Linux" ]; then
if [ -n "${SNAP}" ]; then
CONTAINER_ENV="Yes"
CONTAINER_RUNTIME="snapd"
else
for runtime in "${CONTAINER_RUNTIMES[@]}"; do
if grep -qa ":/${runtime}/" /proc/1/cgroup; then
CONTAINER_ENV="Yes"
CONTAINER_RUNTIME="${runtime}"
break
else
CONTAINER_ENV="No"
CONTAINER_RUNTIME="Unknown"
fi
done
fi
echo -e "Containerized : ${CONTAINER_ENV}"
if [ "${CONTAINER_ENV,,}" == "yes" ]; then
echo -e "Container Runtime: ${CONTAINER_RUNTIME}"
fi
fi
echo -e "Stream Sprout : ${VERSION}"
echo -e "awk : $(awk --version | head -n 1)"
echo -e "bash : $(bash --version | head -n 1)"
echo -e "ffmpeg : $(ffmpeg -version | head -n 1)"
}
function show_version() {
echo -e "\e[92mStream Sprout\e[0m ${VERSION} using FFmpeg ${FFMPEG_VER}"
}
# https://stackoverflow.com/questions/5014632/how-can-i-parse-a-yaml-file-from-a-linux-shell-script
function parse_yaml() {
local prefix="${2}"
local s=""
local w=""
local fs=""
s='[[:space:]]*'
w='[a-zA-Z0-9_]*'
fs=$'\034'
sed -ne "s|^\(${s}\):|\1|" \
-e 's|`||g;s|\$||g;' \
-e "s|~|${HOME}|g;" \
-e "s|^\(${s}\)\(${w}\)${s}:${s}[\"']\(.*\)[\"']$s\$|\1${fs}\2${fs}\3|p" \
-e "s|^\(${s}\)\(${w}\)${s}:${s}\(.*\)${s}\$|\1${fs}\2${fs}\3|p" "${1}" |
awk -F"${fs}" '{
indent = length($1)/2;
vname[indent] = $2;
for (i in vname) {if (i > indent) {delete vname[i]}}
if (length($3) > 0) {
vn=""; for (i=0; i<indent; i++) {vn=(vn)(vname[i])("_")}
gsub(/^[ \t]+/, "", $3); gsub(/[ \t]+$/, "", $3);
gsub(/\s*#.*$/, "", $3);
printf("%s%s%s=\"%s\"\n", "'"${prefix}"'",vn, $2, $3);
}
}'
}
function rename_archive() {
local STAMP=""
# If there is a stream file, then rename it to the current date and time
if [ -e "${sprout_server_archive_path}/${sprout_server_archive_temp}" ]; then
STAMP=$(date +%Y%m%d_%H%M%S)
echo -e " \U1F500 ${sprout_server_archive_path}/${sprout_server_archive_temp}"
echo -e " \U21AA ${sprout_server_archive_path}/stream-sprout-${STAMP}.mkv"
mv "${sprout_server_archive_path}/${sprout_server_archive_temp}" "${sprout_server_archive_path}/stream-sprout-${STAMP}.mkv"
fi
}
function add_archive() {
sprout_server_archive_temp="stream-temp-$(date +%s%N).mkv"
# Check if recording is enabled in the YAML configuration
if [[ "${sprout_server_archive_stream,,}" == "true" || "${sprout_server_archive_stream}" == "1" ]]; then
if [ -z "${sprout_server_archive_path}" ]; then
sprout_server_archive_path="$(dirname "${PWD}")"
else
mkdir -p "${sprout_server_archive_path}" 2>/dev/null
fi
echo -e " \e[34m\U1F4BE\e[0m ${sprout_server_archive_path}/${sprout_server_archive_temp}"
if [ -n "${STREAM_TEE}" ]; then
STREAM_TEE+="|"
fi
STREAM_TEE+="[f=matroska]${sprout_server_archive_path}/${sprout_server_archive_temp}"
fi
}
function add_service() {
local URI="${1}"
if [ -n "${URI}" ]; then
if [ -n "${STREAM_TEE}" ]; then
STREAM_TEE+="|"
fi
# Using the onfail option will allow the other streams to continue if one fails.
STREAM_TEE+="[f=flv:onfail=ignore]${URI}"
fi
}
function get_stream_tee() {
local SERVICE_ENABLED=""
local SERVICE_KEY=""
local SERVICE_NAME=""
local SERVICE_RTMP=""
local URI=""
STREAM_TEE=""
# Iterate over all the sprout_services variables
for var in "${!sprout_services@}"; do
# Check the variable matches the pattern: sprout_services_*_enabled
if [[ "${var}" =~ ^sprout_services_.*_enabled$ ]]; then
# Derive the service name
# - First remove `sprout_services_` prefix from the beginning of the value stored in the variable $var.
# - Next remove the suffix `_enabled` from the end of the SERVICE_NAME variable's value.
SERVICE_NAME="${var#sprout_services_}"
SERVICE_NAME="${SERVICE_NAME%_enabled}"
# Get the value of the variable $var
SERVICE_ENABLED="${!var}"
if [[ "${SERVICE_ENABLED,,}" == "true" || "${SERVICE_ENABLED}" == "1" ]]; then
echo -e " \e[35m\U1F4E1\e[0m ${SERVICE_NAME}"
# TODO: This assumes that the RTMP URL and key are set in the YAML file.
# Construct the variable name
SERVICE_RTMP="sprout_services_${SERVICE_NAME}_rtmp_server"
SERVICE_KEY="sprout_services_${SERVICE_NAME}_key"
# Use indirect expansion to get the value
# By concatenating these two indirectly referenced values, URI
# is set to the full URI needed for streaming. For instance, if
# SERVICE_RTMP points to a variable holding rtmp://example.com/live
# and SERVICE_KEY points to a variable holding abcd1234, then URI
# would be set to rtmp://example.com/live/abcd1234.
URI="${!SERVICE_RTMP}/${!SERVICE_KEY}"
if [[ ! "${URI}" =~ ^rtmp://.* ]]; then
echo -e " \e[31m\U1F6AB\e[0m ${SERVICE_NAME} is not a valid RTMP service URL"
continue
fi
add_service "${URI}"
fi
fi
done
add_archive
}
function get_server_url() {
local asterisks=""
local key_length=0
# Check if the sprout_server_url is set and display a deprecation notice if it is
if [ -n "${sprout_server_url}" ]; then
echo -e " \e[31m\U1F6AB\e[0m server:"
echo -e " ╰─url: in the YAML is deprecated. Please configure ip: and port: instead."
exit 1
fi
# Validate the sprout_server_ip is valid
if [[ ! "${sprout_server_ip}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo -e " \e[33m\U26A0\e[0m server:"
echo -e " ╰─ip: in the YAML is not valid. Falling back to '127.0.0.1'."
sprout_server_ip="127.0.0.1"
fi
# Validate the sprout_server_port is valid
if [[ ! "${sprout_server_port}" =~ ^[0-9]+$ ]] || [[ "${sprout_server_port}" -lt 1024 ]] || [[ "${sprout_server_port}" -gt 65535 ]]; then
echo -e " \e[33m\U26A0\e[0m server:"
echo -e " ╰─port: in the YAML is not valid. Must be between 1024 and 65535. Falling back to '1935'."
sprout_server_port="1935"
fi
# Check that sprout_server_app is not empty
if [ -z "${sprout_server_app}" ]; then
echo -e " \e[33m\U26A0\e[0m server:"
echo -e " ╰─app: is not configured in the YAML. Falling back to 'sprout'."
sprout_server_app="sprout"
fi
# Check that sprout_server_key is not empty
if [ -z "${sprout_server_key}" ]; then
echo -e " \e[33m\U26A0\e[0m server:"
echo -e " ╰─key: is not configured in the YAML. \e[1;97mYour Stream Sprout server is unprotected.\e[0m"
fi
sprout_server_url="rtmp://${sprout_server_ip}:${sprout_server_port}/${sprout_server_app}"
if [ -n "${sprout_server_key}" ]; then
# Calculate the length of sprout_server_key
key_length=${#sprout_server_key}
# Create a string of asterisks equal to the length of sprout_server_key
asterisks=$(printf "%*s" "${key_length}" "" | tr ' ' '*')
echo -e " \e[36m\U1F310\e[0m ${sprout_server_url}/${asterisks}"
# Append the sprout_server_key to the sprout_server_url
sprout_server_url+="/${sprout_server_key}"
else
echo -e " \e[36m\U1F310\e[0m ${sprout_server_url}"
fi
}
function stream_details() {
local AUDIO=""
local VIDEO=""
local AUDIO_CODEC=""
local AUDIO_BITRATE=""
local AUDIO_FREQ=""
local AUDIO_CHANNELS=""
local VIDEO_CODEC=""
local VIDEO_FPS=""
local VIDEO_RES=""
local VIDEO_BITRATE=""
AUDIO="$(grep "Audio:" "${FFMPEG_LOG}" | head -n 1)"
VIDEO="$(grep "Video:" "${FFMPEG_LOG}" | head -n 1)"
# Correcting the parsing to accurately extract the required information
AUDIO_CODEC=$(echo "${AUDIO}" | awk -F', ' '{print $1}' | awk '{print $4 " " $5}')
AUDIO_FREQ=$(echo "${AUDIO}" | awk -F', ' '{print $2}' | awk '{print $1 " " $2}')
AUDIO_CHANNELS=$(echo "${AUDIO}" | awk -F', ' '{print $3}' | awk '{print $1}')
AUDIO_BITRATE=$(echo "${AUDIO}" | awk -F', ' '{print $5}' | awk '{print $1 " " $2}')
VIDEO_CODEC=$(echo "${VIDEO}" | awk -F', ' '{print $1}' | awk '{print $4 " " $5}')
VIDEO_FPS=$(echo "${VIDEO}" | awk -F', ' '{print $7}' | awk '{print $1 " " $2}')
VIDEO_RES=$(echo "${VIDEO}" | awk -F', ' '{print $5}' | awk '{print $1}')
VIDEO_BITRATE=$(echo "${VIDEO}" | awk -F', ' '{print $6}' | awk '{print $1 " " $2}')
echo -e " \e[32m\U1F441\e[0m FFmpeg detected a new stream"
echo -e " ├─ Audio: ${AUDIO_FREQ} ${AUDIO_CODEC} in ${AUDIO_CHANNELS^} ~${AUDIO_BITRATE}"
echo -e " ╰─ Video: ${VIDEO_RES} ${VIDEO_CODEC} at ${VIDEO_FPS} ~${VIDEO_BITRATE}"
}
function banner() {
echo -e $'\E[38;2;254;75;55m \E[39m\E[38;2;254;64;66m_\E[39m\E[38;2;254;54;77m_\E[39m\E[38;2;252;44;89m_\E[39m\E[38;2;249;35;101m_\E[39m\E[38;2;244;27;114m_\E[39m\E[38;2;238;20;126m \E[39m\E[38;2;232;14;138m_\E[39m\E[38;2;224;9;151m \E[39m\E[38;2;215;5;163m \E[39m\E[38;2;206;3;175m \E[39m\E[38;2;195;2;187m \E[39m\E[38;2;184;2;198m \E[39m\E[38;2;173;3;208m \E[39m\E[38;2;161;6;217m \E[39m\E[38;2;148;10;226m \E[39m\E[38;2;136;15;233m \E[39m\E[38;2;124;21;240m \E[39m\E[38;2;111;28;245m \E[39m\E[38;2;99;36;249m \E[39m\E[38;2;87;46;252m \E[39m\E[38;2;75;56;254m \E[39m\E[38;2;64;66;254m \E[39m\E[38;2;53;78;254m \E[39m\E[38;2;43;90;252m \E[39m\E[38;2;34;102;248m \E[39m\E[38;2;26;115;244m \E[39m\E[38;2;19;127;238m \E[39m\E[38;2;13;139;231m \E[39m\E[38;2;9;151;224m \E[39m\E[38;2;5;164;215m \E[39m\E[38;2;3;176;205m \E[39m\E[38;2;2;187;195m_\E[39m\E[38;2;2;198;184m_\E[39m\E[38;2;3;208;172m_\E[39m\E[38;2;6;218;160m_\E[39m\E[38;2;10;226;147m_\E[39m\E[38;2;15;234;135m \E[39m\E[38;2;21;240;123m \E[39m\E[38;2;29;245;110m \E[39m\E[38;2;37;250;98m \E[39m\E[38;2;46;252;86m \E[39m\E[38;2;56;254;74m \E[39m\E[38;2;67;254;63m \E[39m\E[38;2;78;254;52m \E[39m\E[38;2;90;251;43m \E[39m\E[38;2;103;248;34m \E[39m\E[38;2;115;244;26m \E[39m\E[38;2;128;238;19m \E[39m\E[38;2;140;231;13m \E[39m\E[38;2;152;223;8m \E[39m\E[38;2;164;214;5m \E[39m\E[38;2;176;205;3m \E[39m\E[38;2;188;194;2m \E[39m\E[38;2;199;183;2m_\E[39m\E[38;2;209;171;3m \E[39m\E[38;2;218;159;6m \E[39m\E[38;2;227;147;10m \E[39m\E[38;2;234;134;15m\E[39m'
echo -e $'\E[38;2;254;64;66m|\E[39m\E[38;2;254;54;77m \E[39m\E[38;2;252;44;89m \E[39m\E[38;2;249;35;101m \E[39m\E[38;2;244;27;114m_\E[39m\E[38;2;238;20;126m_\E[39m\E[38;2;232;14;138m|\E[39m\E[38;2;224;9;151m \E[39m\E[38;2;215;5;163m|\E[39m\E[38;2;206;3;175m_\E[39m\E[38;2;195;2;187m \E[39m\E[38;2;184;2;198m_\E[39m\E[38;2;173;3;208m_\E[39m\E[38;2;161;6;217m_\E[39m\E[38;2;148;10;226m \E[39m\E[38;2;136;15;233m_\E[39m\E[38;2;124;21;240m_\E[39m\E[38;2;111;28;245m_\E[39m\E[38;2;99;36;249m \E[39m\E[38;2;87;46;252m_\E[39m\E[38;2;75;56;254m_\E[39m\E[38;2;64;66;254m_\E[39m\E[38;2;53;78;254m \E[39m\E[38;2;43;90;252m_\E[39m\E[38;2;34;102;248m_\E[39m\E[38;2;26;115;244m_\E[39m\E[38;2;19;127;238m_\E[39m\E[38;2;13;139;231m_\E[39m\E[38;2;9;151;224m \E[39m\E[38;2;5;164;215m \E[39m\E[38;2;3;176;205m \E[39m\E[38;2;2;187;195m|\E[39m\E[38;2;2;198;184m \E[39m\E[38;2;3;208;172m \E[39m\E[38;2;6;218;160m \E[39m\E[38;2;10;226;147m_\E[39m\E[38;2;15;234;135m_\E[39m\E[38;2;21;240;123m|\E[39m\E[38;2;29;245;110m_\E[39m\E[38;2;37;250;98m_\E[39m\E[38;2;46;252;86m_\E[39m\E[38;2;56;254;74m \E[39m\E[38;2;67;254;63m_\E[39m\E[38;2;78;254;52m_\E[39m\E[38;2;90;251;43m_\E[39m\E[38;2;103;248;34m \E[39m\E[38;2;115;244;26m_\E[39m\E[38;2;128;238;19m_\E[39m\E[38;2;140;231;13m_\E[39m\E[38;2;152;223;8m \E[39m\E[38;2;164;214;5m_\E[39m\E[38;2;176;205;3m \E[39m\E[38;2;188;194;2m_\E[39m\E[38;2;199;183;2m|\E[39m\E[38;2;209;171;3m \E[39m\E[38;2;218;159;6m|\E[39m\E[38;2;227;147;10m_\E[39m\E[38;2;234;134;15m \E[39m\E[38;2;240;122;22m\E[39m'
echo -e $'\E[38;2;254;54;77m|\E[39m\E[38;2;252;44;89m_\E[39m\E[38;2;249;35;101m_\E[39m\E[38;2;244;27;114m \E[39m\E[38;2;238;20;126m \E[39m\E[38;2;232;14;138m \E[39m\E[38;2;224;9;151m|\E[39m\E[38;2;215;5;163m \E[39m\E[38;2;206;3;175m \E[39m\E[38;2;195;2;187m_\E[39m\E[38;2;184;2;198m|\E[39m\E[38;2;173;3;208m \E[39m\E[38;2;161;6;217m \E[39m\E[38;2;148;10;226m_\E[39m\E[38;2;136;15;233m|\E[39m\E[38;2;124;21;240m \E[39m\E[38;2;111;28;245m-\E[39m\E[38;2;99;36;249m_\E[39m\E[38;2;87;46;252m|\E[39m\E[38;2;75;56;254m \E[39m\E[38;2;64;66;254m.\E[39m\E[38;2;53;78;254m\'\E[39m\E[38;2;43;90;252m|\E[39m\E[38;2;34;102;248m \E[39m\E[38;2;26;115;244m \E[39m\E[38;2;19;127;238m \E[39m\E[38;2;13;139;231m \E[39m\E[38;2;9;151;224m \E[39m\E[38;2;5;164;215m|\E[39m\E[38;2;3;176;205m \E[39m\E[38;2;2;187;195m \E[39m\E[38;2;2;198;184m|\E[39m\E[38;2;3;208;172m_\E[39m\E[38;2;6;218;160m_\E[39m\E[38;2;10;226;147m \E[39m\E[38;2;15;234;135m \E[39m\E[38;2;21;240;123m \E[39m\E[38;2;29;245;110m|\E[39m\E[38;2;37;250;98m \E[39m\E[38;2;46;252;86m.\E[39m\E[38;2;56;254;74m \E[39m\E[38;2;67;254;63m|\E[39m\E[38;2;78;254;52m \E[39m\E[38;2;90;251;43m \E[39m\E[38;2;103;248;34m_\E[39m\E[38;2;115;244;26m|\E[39m\E[38;2;128;238;19m \E[39m\E[38;2;140;231;13m.\E[39m\E[38;2;152;223;8m \E[39m\E[38;2;164;214;5m|\E[39m\E[38;2;176;205;3m \E[39m\E[38;2;188;194;2m|\E[39m\E[38;2;199;183;2m \E[39m\E[38;2;209;171;3m|\E[39m\E[38;2;218;159;6m \E[39m\E[38;2;227;147;10m \E[39m\E[38;2;234;134;15m_\E[39m\E[38;2;240;122;22m|\E[39m\E[38;2;246;110;29m\E[39m'
echo -e $'\E[38;2;252;44;89m|\E[39m\E[38;2;249;35;101m_\E[39m\E[38;2;244;27;114m_\E[39m\E[38;2;238;20;126m_\E[39m\E[38;2;232;14;138m_\E[39m\E[38;2;224;9;151m_\E[39m\E[38;2;215;5;163m|\E[39m\E[38;2;206;3;175m_\E[39m\E[38;2;195;2;187m|\E[39m\E[38;2;184;2;198m \E[39m\E[38;2;173;3;208m|\E[39m\E[38;2;161;6;217m_\E[39m\E[38;2;148;10;226m|\E[39m\E[38;2;136;15;233m \E[39m\E[38;2;124;21;240m|\E[39m\E[38;2;111;28;245m_\E[39m\E[38;2;99;36;249m_\E[39m\E[38;2;87;46;252m_\E[39m\E[38;2;75;56;254m|\E[39m\E[38;2;64;66;254m_\E[39m\E[38;2;53;78;254m_\E[39m\E[38;2;43;90;252m,\E[39m\E[38;2;34;102;248m|\E[39m\E[38;2;26;115;244m_\E[39m\E[38;2;19;127;238m|\E[39m\E[38;2;13;139;231m_\E[39m\E[38;2;9;151;224m|\E[39m\E[38;2;5;164;215m_\E[39m\E[38;2;3;176;205m|\E[39m\E[38;2;2;187;195m \E[39m\E[38;2;2;198;184m \E[39m\E[38;2;3;208;172m|\E[39m\E[38;2;6;218;160m_\E[39m\E[38;2;10;226;147m_\E[39m\E[38;2;15;234;135m_\E[39m\E[38;2;21;240;123m_\E[39m\E[38;2;29;245;110m_\E[39m\E[38;2;37;250;98m|\E[39m\E[38;2;46;252;86m \E[39m\E[38;2;56;254;74m \E[39m\E[38;2;67;254;63m_\E[39m\E[38;2;78;254;52m|\E[39m\E[38;2;90;251;43m_\E[39m\E[38;2;103;248;34m|\E[39m\E[38;2;115;244;26m \E[39m\E[38;2;128;238;19m|\E[39m\E[38;2;140;231;13m_\E[39m\E[38;2;152;223;8m_\E[39m\E[38;2;164;214;5m_\E[39m\E[38;2;176;205;3m|\E[39m\E[38;2;188;194;2m_\E[39m\E[38;2;199;183;2m_\E[39m\E[38;2;209;171;3m_\E[39m\E[38;2;218;159;6m|\E[39m\E[38;2;227;147;10m_\E[39m\E[38;2;234;134;15m|\E[39m\E[38;2;240;122;22m \E[39m\E[38;2;246;110;29m \E[39m\E[38;2;250;97;37m\E[39m'
echo -e $'\E[38;2;249;35;101m \E[39m\E[38;2;244;27;114m \E[39m\E[38;2;238;20;126m \E[39m\E[38;2;232;14;138m \E[39m\E[38;2;224;9;151m \E[39m\E[38;2;215;5;163m \E[39m\E[38;2;206;3;175m \E[39m\E[38;2;195;2;187m \E[39m\E[38;2;184;2;198m \E[39m\E[38;2;173;3;208m \E[39m\E[38;2;161;6;217m \E[39m\E[38;2;148;10;226m \E[39m\E[38;2;136;15;233m \E[39m\E[38;2;124;21;240m \E[39m\E[38;2;111;28;245m \E[39m\E[38;2;99;36;249m \E[39m\E[38;2;87;46;252m \E[39m\E[38;2;75;56;254m \E[39m\E[38;2;64;66;254m \E[39m\E[38;2;53;78;254m \E[39m\E[38;2;43;90;252m \E[39m\E[38;2;34;102;248m \E[39m\E[38;2;26;115;244m \E[39m\E[38;2;19;127;238m \E[39m\E[38;2;13;139;231m \E[39m\E[38;2;9;151;224m \E[39m\E[38;2;5;164;215m \E[39m\E[38;2;3;176;205m \E[39m\E[38;2;2;187;195m \E[39m\E[38;2;2;198;184m \E[39m\E[38;2;3;208;172m \E[39m\E[38;2;6;218;160m \E[39m\E[38;2;10;226;147m \E[39m\E[38;2;15;234;135m \E[39m\E[38;2;21;240;123m \E[39m\E[38;2;29;245;110m \E[39m\E[38;2;37;250;98m \E[39m\E[38;2;46;252;86m|\E[39m\E[38;2;56;254;74m_\E[39m\E[38;2;67;254;63m|\E[39m\E[38;2;78;254;52m \E[39m\E[38;2;90;251;43m \E[39m\E[38;2;103;248;34m \E[39m\E[38;2;115;244;26m \E[39m\E[38;2;128;238;19m \E[39m\E[38;2;140;231;13m \E[39m\E[38;2;152;223;8m \E[39m\E[38;2;164;214;5m \E[39m\E[38;2;176;205;3m \E[39m\E[38;2;188;194;2m \E[39m\E[38;2;199;183;2m \E[39m\E[38;2;209;171;3m \E[39m\E[38;2;218;159;6m \E[39m\E[38;2;227;147;10m \E[39m\E[38;2;234;134;15m \E[39m\E[38;2;240;122;22m \E[39m\E[38;2;246;110;29m \E[39m\E[38;2;250;97;37m \E[39m\E[38;2;253;85;47m\E[39m'
}
if ((BASH_VERSINFO[0] < 5)); then
echo -e " \e[31m\U1F6AB\e[0m bash 5.0 or newer is required to run this script. You have ${BASH_VERSION}"
exit 1
fi
# Check that ffmpeg are available on the PATH
if ! command -v ffmpeg &> /dev/null; then
echo -e " \e[31m\U1F6AB\e[0m ffmpeg is not installed. Exiting."
exit 1
fi
FFMPEG_VER="$(ffmpeg -version | head -n 1 | cut -d' ' -f3)"
# Parse command line arguments
while [[ "$#" -gt 0 ]]; do
case "${1}" in
--config)
STREAM_SPROUT_CONFIG="${2}"
shift
if [ ! -f "${STREAM_SPROUT_CONFIG}" ]; then
echo -e " \e[31m\U1F6AB\e[0m ${STREAM_SPROUT_CONFIG} was not found. Exiting."
exit 1
fi;;
--info)
show_info
exit 0;;
--version)
show_version
exit 0;;
--help)
show_help
exit 0;;
*)
echo "Unknown option: ${1}"
show_help
exit 1;;
esac
shift
done
# Check if a custom config path was not provided
if [ -z "${STREAM_SPROUT_CONFIG}" ]; then
# Check in the current working directory
if [ -f "./${STREAM_SPROUT_YAML}" ]; then
STREAM_SPROUT_CONFIG="./${STREAM_SPROUT_YAML}"
# Check in the user's home directory, considering XDG on Linux and compatibility with macOS
elif [ -f "${XDG_CONFIG_HOME:-${HOME}/.config}/${STREAM_SPROUT_YAML}" ]; then
STREAM_SPROUT_CONFIG="${XDG_CONFIG_HOME:-${HOME}/.config}/${STREAM_SPROUT_YAML}"
# Check in /etc
elif [ -f "/etc/${STREAM_SPROUT_YAML}" ]; then
STREAM_SPROUT_CONFIG="/etc/${STREAM_SPROUT_YAML}"
else
echo -e " \e[31m\U1F6AB\e[0m ${STREAM_SPROUT_YAML} was not found. Exiting."
exit 1
fi
fi
banner
# trap relevant signals and call cleanup()
trap cleanup INT QUIT TERM
while true; do
eval "$(parse_yaml "${STREAM_SPROUT_CONFIG}" sprout_)"
show_version
echo -e " \U2699 ${STREAM_SPROUT_CONFIG}"
get_server_url
get_stream_tee
FFMPEG_LOG=$(mktemp /tmp/stream-sprout.XXXXXX.log)
ffmpeg \
-hide_banner \
-flags +global_header \
-fflags nobuffer \
-listen 1 -i "${sprout_server_url}" \
-flvflags no_duration_filesize \
-c:v copy -c:a copy -map 0 \
-movflags +faststart \
-f tee -use_fifo 1 "${STREAM_TEE}" >"${FFMPEG_LOG}" 2>&1 &
# Capture the PID of the ffmpeg process
FFMPEG_PID=$!
echo -e " \U2B07 FFmpeg process (${FFMPEG_PID}) logging to ${FFMPEG_LOG}"
COUNTER=0
# 0 for standing-by
# 1 for streaming
STREAMING_STATUS=0
# Monitor the FFmpeg process
while sleep 1; do
STAMP="[$(date +%H:%M:%S)]"
if ! kill -0 "${FFMPEG_PID}" 2>/dev/null; then
echo -e " \e[31m\U23F9\e[0m FFmpeg has stopped"
break
else
if grep "Input #0, flv, from 'rtmp://" "${FFMPEG_LOG}" > /dev/null; then
NEW_STATUS=1
else
NEW_STATUS=0
fi
# Check if status changed or if it's time to log the status again
if [ ${NEW_STATUS} -ne ${STREAMING_STATUS} ] || (( COUNTER % 30 == 0 )); then
# If the status has changed, then show the details
if [ ${NEW_STATUS} -ne ${STREAMING_STATUS} ]; then
stream_details
fi
if [ ${NEW_STATUS} -eq 1 ]; then
echo -e " \e[32m\U25B6\e[0m FFmpeg is streaming ${STAMP}"
else
echo -e " \e[33m\U23F8\e[0m FFmpeg is standing-by ${STAMP}"
fi
# Update the current status
STREAMING_STATUS=${NEW_STATUS}
fi
((COUNTER++))
fi
done
rename_archive
echo
unset sprout_server_url
done