314 lines
9.7 KiB
Bash
314 lines
9.7 KiB
Bash
|
#!/bin/bash -e
|
||
|
|
||
|
# a
|
||
|
# .og
|
||
|
# ...f = OGG/FLAC
|
||
|
# ...o = OGG/Opus
|
||
|
# ...v = OGG/Vorbis
|
||
|
# .mpe = MP3
|
||
|
# .a
|
||
|
# ..lc = LC-AAC
|
||
|
# ..he = AAC-HE
|
||
|
# ..hp = AAC-HE v2 (PS)
|
||
|
# v
|
||
|
# .avc = H.264
|
||
|
# .hvc = H.265
|
||
|
# .vp
|
||
|
# ...g = VP-7
|
||
|
# ...h = VP-8
|
||
|
# s
|
||
|
# .wvt = WebVTT subtitles
|
||
|
|
||
|
ffmpeg_version="$(ffmpeg -version | awk '{print $3}' | head -n1)"
|
||
|
|
||
|
# function version_gt() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" != "$1"; }
|
||
|
# function version_le() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" == "$1"; }
|
||
|
# function version_lt() { test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" != "$1"; }
|
||
|
# function version_ge() { test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" == "$1"; }
|
||
|
|
||
|
function contains_audio() {
|
||
|
case "$1" in
|
||
|
a*|*_a*)
|
||
|
true
|
||
|
;;
|
||
|
*)
|
||
|
false
|
||
|
;;
|
||
|
esac
|
||
|
}
|
||
|
|
||
|
function contains_video() {
|
||
|
case "$1" in
|
||
|
v*|*_v*)
|
||
|
true
|
||
|
;;
|
||
|
*)
|
||
|
false
|
||
|
;;
|
||
|
esac
|
||
|
}
|
||
|
|
||
|
function contains_subtitles() {
|
||
|
case "$1" in
|
||
|
s*|*_s*)
|
||
|
true
|
||
|
;;
|
||
|
*)
|
||
|
false
|
||
|
;;
|
||
|
esac
|
||
|
}
|
||
|
|
||
|
function is_hls_compatible() {
|
||
|
result="1"
|
||
|
for i in ${1//_/ }; do
|
||
|
case "$i" in
|
||
|
ampe|aalc|aah[ep]|vavc)
|
||
|
;;
|
||
|
aogo)
|
||
|
# opus, ffmpeg can code this but no player supports it and it can even confuse bitrade adaption
|
||
|
result="0"
|
||
|
;;
|
||
|
*)
|
||
|
result="0"
|
||
|
;;
|
||
|
esac
|
||
|
done
|
||
|
if [ "$result" -eq 1 ]; then true; else false; fi
|
||
|
}
|
||
|
|
||
|
function is_webm_dash_compatible() {
|
||
|
result="1"
|
||
|
for i in ${1//_/ }; do
|
||
|
case "$i" in
|
||
|
aog[ov]|vvp[gh]|svvt)
|
||
|
;;
|
||
|
*)
|
||
|
result="0"
|
||
|
;;
|
||
|
esac
|
||
|
done
|
||
|
if [ "$result" -eq 1 ]; then true; else false; fi
|
||
|
}
|
||
|
|
||
|
function parse_bitrate() {
|
||
|
echo "$1" |\
|
||
|
sed \
|
||
|
-e 's/g/\*1000000000/gi' \
|
||
|
-e 's/m/\*1000000/gi' \
|
||
|
-e 's/k/\*1000/gi' |\
|
||
|
bc
|
||
|
}
|
||
|
|
||
|
source_url="http://publish.streaminginter.net:61120/${CHANNEL_ID}/master_signal"
|
||
|
if [ -n "${INPUT_URL}" ]; then
|
||
|
source_url="${INPUT_URL}"
|
||
|
fi
|
||
|
echo "* Source URL: ${source_url}"
|
||
|
|
||
|
ffmpeg_log_level="${LOG_LEVEL:-warning}"
|
||
|
|
||
|
ffmpeg_args=()
|
||
|
ffmpeg_args+=(-loglevel "${ffmpeg_log_level}")
|
||
|
ffmpeg_args+=(-i "${source_url}")
|
||
|
if [ -n "${FFMPEG_ARGS}" ]; then
|
||
|
ffmpeg_args+=(${FFMPEG_ARGS})
|
||
|
fi
|
||
|
|
||
|
hls_args=""
|
||
|
if [ -n "${HLS_ARGS}" ]; then
|
||
|
hls_args+=":${HLS_ARGS}"
|
||
|
fi
|
||
|
|
||
|
hls_args="${hls_args}:use_localtime=1"
|
||
|
hls_args="${hls_args}:use_localtime_mkdir=1"
|
||
|
# Reference: https://developer.apple.com/library/content/documentation/General/Reference/HLSAuthoringSpec/Requirements.html
|
||
|
# - 8.4. -> program_data_time
|
||
|
# - 8.13. -> discont_start
|
||
|
hls_args="${hls_args}:hls_flags=discont_start+append_list+omit_endlist+temp_file+delete_segments+program_date_time"
|
||
|
hls_args="${hls_args}:hls_init_time=${HLS_INIT_TIME:-0}"
|
||
|
hls_args="${hls_args}:hls_time=${HLS_TIME:-6}"
|
||
|
hls_args="${hls_args}:hls_list_size=${HLS_LIST_SIZE:-10}"
|
||
|
hls_args="${hls_args}:hls_start_number_source=generic"
|
||
|
hls_args="${hls_args}:hls_ts_options=mpegts_service_type=digital_radio\\\\:mpegts_copyts=1"
|
||
|
|
||
|
# if version_gt ${ffmpeg_version} 3.3.3; then
|
||
|
hls_args="${hls_args}:hls_segment_type=${HLS_SEGMENT_TYPE:-mpegts}"
|
||
|
# else
|
||
|
# echo "* HLS_SEGMENT_TYPE forced to mpegts, currently installed ffmpge $ffmpeg_version does not support hls_segment_type"
|
||
|
# fi
|
||
|
|
||
|
# if version_gt ${ffmpeg_version} 3.3.3; then
|
||
|
if [ ! -z "${HLS_ENCRYPT}" ] && [ "${HLS_ENCRYPT}" -ne 0 ]; then
|
||
|
echo "* Will generate encrypted HLS chunks"
|
||
|
hls_args="${hls_args}:hls_enc=1"
|
||
|
else
|
||
|
echo "* Will NOT generate encrypted HLS chunks"
|
||
|
fi
|
||
|
# else
|
||
|
# echo "* HLS_ENCRYPT forced to 0, currently installed ffmpeg $ffmpeg_version does not support hls_enc"
|
||
|
# fi
|
||
|
|
||
|
webm_chunk_args=""
|
||
|
if [ -n "${WEBM_CHUNK_ARGS}" ]; then
|
||
|
webm_chunk_args+=":${WEBM_CHUNK_ARGS}"
|
||
|
fi
|
||
|
webm_chunk_duration="${WEBM_CHUNK_DURATION:-5000}"
|
||
|
webm_chunk_start_index="${WEBM_CHUNK_START_INDEX:-0}"
|
||
|
webm_chunk_args="${webm_chunk_args}:audio_chunk_duration=${webm_chunk_duration}"
|
||
|
webm_chunk_args="${webm_chunk_args}:chunk_start_index=${webm_chunk_start_index}"
|
||
|
|
||
|
webm_manifest_input_args=()
|
||
|
webm_manifest_output_args=()
|
||
|
webm_manifest_adaptation_sets=""
|
||
|
wemb_manifest_mpds=()
|
||
|
webm_manifest_next_adaptationset_index=0
|
||
|
webm_manifest_next_stream_index=0
|
||
|
|
||
|
function make_stream() {
|
||
|
local codec_shorthand=$1
|
||
|
local codec_id=$2
|
||
|
local codec_options_="$3"
|
||
|
local codec_options=(${codec_options_})
|
||
|
local bitrates_=$4[@]
|
||
|
local bitrates=("${!bitrates_}")
|
||
|
|
||
|
if [ -z "${bitrates}" ]; then
|
||
|
return
|
||
|
fi
|
||
|
|
||
|
echo "* Bitrates for ${codec_shorthand}: ${bitrates[*]}" >&2
|
||
|
|
||
|
old_webm_manifest_next_stream_index=${webm_manifest_next_stream_index}
|
||
|
|
||
|
for bitrate in ${bitrates}; do
|
||
|
parsed_bitrate="$(parse_bitrate "$bitrate")"
|
||
|
output=""
|
||
|
|
||
|
# HLS
|
||
|
if is_hls_compatible "${codec_shorthand}"; then
|
||
|
# if [ -n "$output" ]; then
|
||
|
# # append to tee output name
|
||
|
# output+="|"
|
||
|
# fi
|
||
|
|
||
|
hls_m3u8="hls_${codec_shorthand}${parsed_bitrate}.m3u8"
|
||
|
hls_chk="hls_${codec_shorthand}${parsed_bitrate}/c%s.ts"
|
||
|
|
||
|
mkdir -p "$(dirname "${hls_m3u8}")" "$(dirname "${hls_chk}")"
|
||
|
output+="[f=hls${hls_args}:hls_segment_filename=${hls_chk}]${hls_m3u8}"
|
||
|
|
||
|
# Add format to HLS index
|
||
|
cat <<EOF >>hls.m3u8
|
||
|
#EXT-X-STREAM-INF:PROGRAM-ID=1,AVERAGE-BANDWIDTH=$(echo "${parsed_bitrate}*1.1" | bc),BANDWIDTH=$(echo "${parsed_bitrate} * 1.1" | bc),CODECS="$codec_id"
|
||
|
${hls_m3u8}
|
||
|
EOF
|
||
|
fi
|
||
|
|
||
|
# WebM-DASH
|
||
|
if is_webm_dash_compatible "${codec_shorthand}"; then
|
||
|
if [ -n "$output" ]; then
|
||
|
# append to tee output name
|
||
|
output+="|"
|
||
|
fi
|
||
|
|
||
|
dash_prefix="dash_${codec_shorthand}${parsed_bitrate}"
|
||
|
dash_hdr="${dash_prefix}.hdr"
|
||
|
dash_chk="${dash_prefix}_%d.chk"
|
||
|
|
||
|
rm -f "${dash_hdr}" *.chk
|
||
|
|
||
|
mkdir -p "$(dirname "${dash_hdr}")" "$(dirname "${dash_chk}")"
|
||
|
|
||
|
webm_manifest_mpds+=("${dash_hdr}")
|
||
|
extra_args=""
|
||
|
|
||
|
output+="[f=webm_chunk${webm_chunk_args}${extra_args}:header=${dash_hdr}]${dash_chk}"
|
||
|
|
||
|
# Temporary file to store bandwidth for final manifest
|
||
|
webm_manifest_input_args+=(
|
||
|
-y -f webm_dash_manifest
|
||
|
-live 1
|
||
|
-bandwidth "${parsed_bitrate}"
|
||
|
-i "${dash_hdr}"
|
||
|
)
|
||
|
if [ "${old_webm_manifest_next_stream_index}" = "${webm_manifest_next_stream_index}" ]; then
|
||
|
if [ -n "${webm_manifest_adaptation_sets}" ]; then
|
||
|
webm_manifest_adaptation_sets="${webm_manifest_adaptation_sets%,} "
|
||
|
fi
|
||
|
webm_manifest_adaptation_sets+="id=${webm_manifest_next_adaptationset_index},streams="
|
||
|
webm_manifest_next_adaptationset_index=$((${webm_manifest_next_adaptationset_index}+1))
|
||
|
fi
|
||
|
webm_manifest_adaptation_sets+="${webm_manifest_next_stream_index},"
|
||
|
webm_manifest_output_args+=(-map ${webm_manifest_next_stream_index})
|
||
|
webm_manifest_next_stream_index=$((webm_manifest_next_stream_index+1))
|
||
|
fi
|
||
|
|
||
|
ffmpeg_args+=(
|
||
|
"${codec_options[@]}"
|
||
|
-b:a "${parsed_bitrate}"
|
||
|
-map_metadata 0
|
||
|
-metadata "service_provider=streaminginter.net"
|
||
|
-metadata "service_name=${CHANNEL_NAME:-Livestream}"
|
||
|
-y -f tee
|
||
|
)
|
||
|
if contains_audio "${codec_shorthand}"; then
|
||
|
ffmpeg_args+=(-map 0:a)
|
||
|
fi
|
||
|
if contains_video "${codec_shorthand}"; then
|
||
|
ffmpeg_args+=(-map 0:v)
|
||
|
fi
|
||
|
ffmpeg_args+=("${output}")
|
||
|
done
|
||
|
}
|
||
|
|
||
|
make_webm_dash_manifest() {
|
||
|
ffmpeg_args_2=(
|
||
|
-loglevel "${ffmpeg_log_level}"
|
||
|
"${webm_manifest_input_args[@]}"
|
||
|
-c copy
|
||
|
"${webm_manifest_output_args[@]}"
|
||
|
-y -f webm_dash_manifest -live 1
|
||
|
-adaptation_sets "${webm_manifest_adaptation_sets}"
|
||
|
-chunk_start_index "${webm_chunk_start_index}"
|
||
|
-chunk_duration_ms "${webm_chunk_duration}"
|
||
|
-time_shift_buffer_depth 7200
|
||
|
-minimum_update_period 7200
|
||
|
)
|
||
|
if [ -n "${UTC_TIMING_URL}" ]; then
|
||
|
ffmpeg_args_2+=(-utc_timing_url "${UTC_TIMING_URL}")
|
||
|
fi
|
||
|
ffmpeg_args_2+=(dash.mpd)
|
||
|
|
||
|
set -x
|
||
|
ffmpeg "${ffmpeg_args_2[@]}"
|
||
|
}
|
||
|
|
||
|
find -name '*.mpd' -or -name '*.chk' -or -name '*.m3u8' -delete
|
||
|
|
||
|
echo "#EXTM3U" >hls.m3u8
|
||
|
|
||
|
make_stream aogo opus "-c:a libopus -compression_level 10" OUTPUT_OPUS_BITRATES
|
||
|
make_stream aogv vorbis "-c:a libvorbis" OUTPUT_VORBIS_BITRATES
|
||
|
make_stream aalc mp4a.40.2 "-c:a libfdk_aac -cutoff 20000" OUTPUT_AAC_BITRATES
|
||
|
make_stream aahe mp4a.40.5 "-c:a libfdk_aac -profile:a aac_he" OUTPUT_AAC_HE_BITRATES
|
||
|
make_stream aahp mp4a.40.29 "-c:a libfdk_aac -profile:a aac_he_v2" OUTPUT_AAC_HEV2_BITRATES
|
||
|
make_stream ampe mp4a.40.34 "-c:a libmp3lame" OUTPUT_MP3_BITRATES
|
||
|
|
||
|
# WebM DASH master manifest generation
|
||
|
(
|
||
|
echo "Waiting for DASH manifests to be generated..."
|
||
|
for mpd in "${webm_manifest_mpds[@]}"; do
|
||
|
echo "Waiting for $mpd..."
|
||
|
while [ ! -f "$mpd" ]; do sleep 1; done
|
||
|
done
|
||
|
echo "Generating WebM DASH manifest..."
|
||
|
make_webm_dash_manifest
|
||
|
echo "WebM DASH manifest generated."
|
||
|
) &
|
||
|
|
||
|
# Delete old WebM chunks regularly
|
||
|
watch -t -n 30 'find -name "*.chk" -mmin 120 -delete' &
|
||
|
|
||
|
echo "Starting ffmpeg..."
|
||
|
ffmpeg "${ffmpeg_args[@]}"
|