157 lines
5.1 KiB
Bash
Executable File
157 lines
5.1 KiB
Bash
Executable File
#!/bin/sh
|
|
# Don't link mrsh to sh, since it does not allow line continuations in here-documents.
|
|
# Don't use tcsh, it doesn't allow multiple lines in braced or parenthesized blocks.
|
|
# Use dash, yash, bash, posh, ksh, mksh, zsh, pbosh
|
|
|
|
# Run entire script in subshell to prevent variable pollution
|
|
(
|
|
# https://unix.stackexchange.com/a/188365
|
|
# If and only if bash is in POSIX-mode, which can be forced by setting the
|
|
# POSIXLY_CORRECT variable (to any value), then the special built-ins, see
|
|
# https://www.gnu.org/software/bash/manual/html_node/Special-Builtins.html,
|
|
# are found BEFORE functions during lookup. `unset' is one of these special
|
|
# built-ins. However, when using `shopt -s expand_aliases', alias expansion in
|
|
# non-interactive shells is supported, so aliasing unset is a problem.
|
|
# This can be circumvented by escaping any of the letters of the command, usually the first.
|
|
#
|
|
# The COMMANDS_ variable contains the commands, space delimited (therefore the IFS),
|
|
# which are supposed to be "made safe".
|
|
IFS="
|
|
"
|
|
POSIXLY_CORRECT='1'
|
|
COMMANDS_='builtin unalias unset read printf command exit type . tr mkdir wc sed grep xargs ffmpeg jq wget'
|
|
# shellcheck disable=SC2086
|
|
\unset -f -- ${COMMANDS_-} 2>/dev/null
|
|
# shellcheck disable=SC2086
|
|
\unalias -- ${COMMANDS_-} 2>/dev/null || true
|
|
|
|
command -v -- wget >/dev/null 2>&1 || {
|
|
printf '\033[31m%s\033[m\n' "Please install wget"
|
|
exit 1
|
|
}
|
|
|
|
command -v -- jq >/dev/null 2>&1 || {
|
|
printf '\033[31m%s\033[m\n' "Please install jq"
|
|
exit 1
|
|
}
|
|
|
|
command -v -- ffmpeg >/dev/null 2>&1 || {
|
|
printf '\033[31m%s\033[m\n' "Please install ffmpeg"
|
|
exit 1
|
|
}
|
|
|
|
# .env contains a line like
|
|
# BASE64_BEARER_TOKEN='<literal base64 encoded string>'
|
|
# Retrieve that base64 token from a request from the browser
|
|
# network tab network tabfrom a logged in MS Stream (Classic) tab
|
|
. './.env'
|
|
|
|
OUTDIR='./videos'
|
|
|
|
read -r API_URL <<- EOM
|
|
https://euwe-1.api.microsoftstream.com/api/videos?\
|
|
\$top=4\
|
|
&\
|
|
\$skip=0\
|
|
&\
|
|
\$orderby=metrics/trendingScore asc\
|
|
&\
|
|
\$filter=\
|
|
published \
|
|
and \
|
|
(\
|
|
(\
|
|
state eq 'Completed' and contentSource ne 'livestream'\
|
|
) \
|
|
or \
|
|
(\
|
|
contentSource eq 'livestream' \
|
|
and not \
|
|
(\
|
|
state eq 'Completed' \
|
|
and not \
|
|
liveEvent/LiveEventOptions/OnDemandOptions/EnablePlayback\
|
|
)\
|
|
)\
|
|
)\
|
|
&\
|
|
api-version=1.4-private
|
|
EOM
|
|
|
|
API_URL="$(
|
|
printf '%s' "${API_URL-}" |
|
|
tr -d -- '\t'
|
|
)"
|
|
|
|
mkdir -p -- "${OUTDIR-}" || {
|
|
printf '\033[31m%s\033[m\n' 'Aborting'
|
|
exit 2
|
|
}
|
|
|
|
# '>' without quotes is delimiter, since it is one of the few characters disallowed in a URL. Title must not include this character
|
|
# https://www.ietf.org/rfc/rfc2396.txt
|
|
# 2.4.3. Excluded US-ASCII Characters
|
|
m3u8_manifest_urls_and_metadata="$(
|
|
wget --no-verbose \
|
|
--quiet \
|
|
--output-document - \
|
|
--header="authorization: Bearer ${BASE64_BEARER_TOKEN-}" \
|
|
-- "${API_URL-}" |
|
|
jq --raw-output \
|
|
'.value[] | .name + ">" + .media.duration[2:] + ">" + (.playbackUrls | map(select(.mimeType == "application/vnd.apple.mpegurl")) | .[].playbackUrl)'
|
|
)"
|
|
|
|
[ -z "${m3u8_manifest_urls_and_metadata-}" ] && {
|
|
printf '\033[31m%s\033[m\n' 'Error GETting manifest urls (response empty) or Bearer token invalid/expired. Exiting.'
|
|
exit 3
|
|
}
|
|
|
|
idx='1'
|
|
total="$(printf '%s\n' "${m3u8_manifest_urls_and_metadata-}" | wc -l)"
|
|
|
|
IFS='
|
|
'
|
|
for m3u8_manifest_url_and_metadata in ${m3u8_manifest_urls_and_metadata-}
|
|
do
|
|
IFS='>' read -r title length m3u8_manifest_url <<- EOM
|
|
${m3u8_manifest_url_and_metadata-}
|
|
EOM
|
|
|
|
length="$(printf '%s' "${length-}" | sed -e 's/\..*S/S/g' -e 's/M/:/g' -e 's/H/:/g')"
|
|
|
|
filepath="${OUTDIR-}/${title-}.mp4"
|
|
|
|
printf '\033[32m%s\033[m\n' "Download '${filepath-}' (${idx-}/${total-})"
|
|
|
|
ffmpeg -hide_banner \
|
|
-loglevel error \
|
|
-headers "authorization: Bearer ${BASE64_BEARER_TOKEN-}" \
|
|
-i "${m3u8_manifest_url-}" \
|
|
-progress - \
|
|
-c copy \
|
|
"${filepath-}" |
|
|
grep --line-buffered 'out_time=' |
|
|
sed -u -e 's/out_time=//g' -e 's/\..*//g' -e 's/^-//g' |
|
|
xargs -I{} printf '%s\r' "{} of ${length-} "
|
|
|
|
printf '\n\033[33m%s\033[m\n' "Done"
|
|
|
|
idx="$(( idx + 1 ))"
|
|
done
|
|
|
|
unset -v POSIXLY_CORRECT \
|
|
idx \
|
|
total \
|
|
API_URL \
|
|
filepath \
|
|
m3u8_manifest_url \
|
|
m3u8_highest_res_url \
|
|
m3u8_manifest_url_and_metadata \
|
|
m3u8_manifest_urls_and_metadata \
|
|
OUTDIR \
|
|
BASE64_BEARER_TOKEN
|
|
|
|
IFS="
|
|
"
|
|
)
|