#!/bin/sh #initializing.. help_text () { while IFS= read -r line; do printf "%s\n" "$line" done <<-EOF Usage: ${0##*/} [ -o ] [ -r | -f | -n ] [ ] ${0##*/} -h Options: -h show helptext -o filename (default : video) -r select highest resolution automatically -n set maximum number of connections (default : 36) -f skip ffmpeg file conversion (used to enable the video file to run on any video player) -s subtitles url (will be saved as same name as the video file) EOF } download(){ printf "" > $failed for i in $1; do curl --max-time 30 -s "${relative_url}$(printf "%s" "$data" | sed -n "${i}p")" > "$tmpdir/$(printf "%05d" "$i").ts" && printf "\033[2K\r\033[1;32m ✓ $i / $range done" || printf "$i\n" >> $failed & jobs -p > "$jobdir" while [ "$(cat "$jobdir" | wc -l)" -ge $n ];do jobs > "$jobdir";sleep 0.05;done done wait } n=36 #no. of parallel download or connections file="video" #default filename tmpdir="${XDG_CACHE_HOME:-$HOME/.cache}/hls-temp" jobdir="${XDG_CACHE_HOME:-$HOME/.cache}/hls-jobs" failed="${XDG_CACHE_HOME:-$HOME/.cache}/hls-fail" while getopts 'o:rfhn:s:' OPT; do case $OPT in o) file=$OPTARG ;; n) n=$OPTARG ;; f) skip_ffmpeg=1;; r) skip_res=1;; s) subs=$OPTARG;; *|h) help_text exit 0 ;; esac done shift $((OPTIND - 1)) [ -z "$*" ] && printf "\033[1;34mEnter link >\033[0m " && read -r link || link=$* trap "killall curl;rm -rdf $tmpdir $jobdir;exit 0" INT HUP printf "\033[2K\r\033[1;36mFetching resolutions.." m3u8_data=$(curl -s "$link") res_list=$(printf "%s" "$m3u8_data" | sed -nE 's_.*RESOLUTION=.*x([^,]*).*_\1_p') if [ -n "$res_list" ];then highest_res=$(printf "$res_list" | sort -nr | head -1) [ -z "$skip_res" ] && printf "\033[2K\r\033[1;33mRESOLUTIONS >>\n\033[0m$res_list\n\033[1;34mType ur preferred resolution (default: $highest_res) > " && read -r sel_res || printf "\033[2K\r\033[1;36mSelecting highest resolution.." [ -z "$sel_res" ] && sel_res=$highest_res unset highest_res res_list url=$(printf "%s" "$m3u8_data" | sed -n "/x$sel_res/{n;p;}" | tr -d '\r') #check whether the m3u8_data contains uri that starts from http printf "%s" "$m3u8_data" | grep -q "http" || relative_url=$(printf "%s" "$link" | sed 's_[^/]*$__') printf "\033[2K\r\033[1;36mFetching Metadata.." url="${relative_url}$url" resp="$(curl -s "$url")" else url=$link resp=$m3u8_data fi [ -d "$tmpdir" ] || mkdir -p "$tmpdir" #extract key uri and iv uri from encrypted stream if exists.. key_uri="$(printf "%s" "$resp" | sed -nE 's/^#EXT-X-KEY.*URI="([^"]*)"/\1/p')" [ -z "$key_uri" ] || iv_uri="$(printf "%s" "$resp" | sed -nE 's/^#EXT-X-IV.*URI="([^"]*)"/\1/p')" data="$(printf "%s" "$resp" | sed '/#/d')" printf "%s" "$data" | grep -q "http" && relative_url='' || relative_url=$(printf "%s" "$url" | sed 's_[^/]*$__') range=$(printf "%s\n" "$data" | wc -l) #for encrypted stream only if [ -n "$key_uri" ];then #extract key from uri.. key=$(curl -s "$key_uri" | od -A n -t x1 | tr -d ' |\n') #iv from uri [ -z "$iv_uri" ] && iv=$(openssl rand -hex 16) || iv=$(curl -s "$iv_uri" | od -A n -t x1 | tr -d ' |\n') fi printf "\033[2K\r\033[1;35mpieces : $range\n\033[1;33mDownloading.." #downloading .ts data asynchronously download "$(seq $range)" #redownloading failed pieces download "$(cat $failed)" #downloading subtitles if uri passed using -s option [ -z "$subs" ] || curl -s "$subs" -o "$file.srt" & #concatenating all .ts file in one file.. if [ -n "$key_uri" ];then #decrypting while concatenating.. printf "\033[2K\r\033[1;36m Decrypting and Concatenating pieces into single file.." for i in "$tmpdir"/*;do openssl aes-128-cbc -d -K "$key" -iv "$iv" -nopad >> "$file.ts" < "$i" done else printf "\033[2K\r\033[1;36m Concatenating pieces into single file.." cat "$tmpdir"/* >> "$file.ts" fi rm -rdf $tmpdir $jobdir $failed #conversion of allts file to mp4 video using ffmpeg.. if [ -z "$skip_ffmpeg" ];then printf "\033[2K\r\033[1;36mEncoding file to mp4 video..\n\033[0m" ffmpeg -i "$file.ts" -loglevel error -stats -c copy "$file.mp4" else mv "$file.ts" "$file.mp4" fi #cleanup.. rm -f "$file".ts printf "\033[2K\r\033[1;36m Done!!"