diff --git a/downloadBash.sh b/downloadBash.sh new file mode 100755 index 0000000..7dd37b0 --- /dev/null +++ b/downloadBash.sh @@ -0,0 +1,102 @@ +#!/bin/bash + +# .env contains a line like +# BASE64_BEARER_TOKEN='' +# Retrieve that base64 token from a request from the browser +# network tab network tabfrom a logged in MS Stream (Classic) tab +source .env + +OUTDIR="./videos" + +read -r -d '' API_URL <<- 'EOM' +https://euwe-1.api.microsoftstream.com/api/videos? +$top=100 +& +$skip=0 +& +$orderby=metrics/trendingScore desc +& +$filter= + published{SPACE} + and{SPACE} + ( + ( + state eq 'Completed' and contentSource ne 'livestream' + ){SPACE} + or{SPACE} + ( + contentSource eq 'livestream'{SPACE} + and not + ( + state eq 'Completed'{SPACE} + and not + liveEvent/LiveEventOptions/OnDemandOptions/EnablePlayback + ) + ) + ) +& +api-version=1.4-private +EOM + +API_URL="$( + printf '%s' "${API_URL}" | + sed 's/{SPACE}/ /g' | + tr -d '\n' +)" + +mkdir -p "${OUTDIR}" || { printf '\033[31m%s\033[m\n' 'Aborting'; exit; } + +# '>' without quotes is delimiter, since it's one of the few chars disallowed in a URL. Title must not include this char +# https://www.ietf.org/rfc/rfc2396.txt +# 2.4.3. Excluded US-ASCII Characters +m3u8_manifest_urls_and_metadata=$( + wget --no-verbose --quiet -O- --header="authorization: Bearer ${BASE64_BEARER_TOKEN}" "${API_URL}" | + jq -r '.value[] | .name + ">" + .media.duration[2:] + ">" + (.playbackUrls | map(select(.mimeType == "application/vnd.apple.mpegurl")) | .[].playbackUrl)' +) + +if [ -z "${m3u8_manifest_urls_and_metadata}" ]; then + printf '\033[31m%s\033[m\n' 'Error GETting manifest urls (response empty) or Bearer token invalid/expired. Exiting.' + exit +fi + +idx=1 +total="$(printf '%s\n' "${m3u8_manifest_urls_and_metadata}" | wc -l)" + +IFS=$'\n' +for m3u8_manifest_url_and_metadata in ${m3u8_manifest_urls_and_metadata}; do + IFS='>' read -ra META <<< "${m3u8_manifest_url_and_metadata}" + + title="${META[0]}" + length="${META[1]}" + length="$(echo "${length}" | sed -e 's/\..*S/S/g' -e 's/M/:/g' -e 's/H/:/g')" + m3u8_manifest_url="${META[2]}" + + m3u8_highest_res_url="$( + curl --silent "${m3u8_manifest_url}" | + tail -n1 | + rev | + cut -c3- | + rev | + cut -d'&' -f2 | + cut -c13- | + sed 's/keyframes/fragments/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_highest_res_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 "{} of ${length} \r" + + printf '\n\033[33m%s\033[m\n' "Done" + + idx=$(( idx + 1 )) +done diff --git a/downloadPOSIX.sh b/downloadPOSIX.sh new file mode 100755 index 0000000..94838ad --- /dev/null +++ b/downloadPOSIX.sh @@ -0,0 +1,108 @@ +#!/bin/sh + +# .env contains a line like +# BASE64_BEARER_TOKEN='' +# 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 -d '' API_URL <<- 'EOM' +https://euwe-1.api.microsoftstream.com/api/videos? +$top=100 +& +$skip=0 +& +$orderby=metrics/trendingScore desc +& +$filter= + published{SPACE} + and{SPACE} + ( + ( + state eq 'Completed' and contentSource ne 'livestream' + ){SPACE} + or{SPACE} + ( + contentSource eq 'livestream'{SPACE} + and not + ( + state eq 'Completed'{SPACE} + and not + liveEvent/LiveEventOptions/OnDemandOptions/EnablePlayback + ) + ) + ) +& +api-version=1.4-private +EOM + +API_URL="$( + printf '%s' "${API_URL}" | + sed 's/{SPACE}/ /g' | + tr -d '\n' +)" + +mkdir -p -- "${OUTDIR}" || { + printf '\033[31m%s\033[m\n' 'Aborting' + exit +} + +# '>' without quotes is delimiter, since it's one of the few chars disallowed in a URL. Title must not include this char +# https://www.ietf.org/rfc/rfc2396.txt +# 2.4.3. Excluded US-ASCII Characters +m3u8_manifest_urls_and_metadata="$( + wget --no-verbose --quiet -O- --header="authorization: Bearer ${BASE64_BEARER_TOKEN}" -- "${API_URL}" | + jq -r '.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 +} + +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 -ra META <<- EOM + ${m3u8_manifest_url_and_metadata} + EOM + + title="${META[0]}" + length="${META[1]}" + length="$(printf '%s' "${length}" | sed -e 's/\..*S/S/g' -e 's/M/:/g' -e 's/H/:/g')" + m3u8_manifest_url="${META[2]}" + + m3u8_highest_res_url="$( + curl --silent -- "${m3u8_manifest_url}" | + tail -n1 | + rev | + cut -c3- | + rev | + cut -d'&' -f2 | + cut -c13- | + sed 's/keyframes/fragments/g' + )" + + filepath="${OUTDIR}/${title}.mp4" + + printf '\033[32m%s\033[m\n' "Download '${filepath}' (${idx}/${total})" + + false && ffmpeg -hide_banner \ + -loglevel error \ + -headers "authorization: Bearer ${BASE64_BEARER_TOKEN}" \ + -i "${m3u8_highest_res_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 "{} of ${length} \r" + + printf '\n\033[33m%s\033[m\n' "Done" + + idx="$(( idx + 1 ))" +done