mirror of
https://github.com/wimpysworld/stream-sprout
synced 2026-03-14 14:45:50 +01:00
406 lines
22 KiB
Bash
Executable file
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
|