├── LICENSE ├── README.md └── hls /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hls_downloader 2 | 3 | A posix compliant highly fast and efficient Asynchronous stable m3u8 links parallel downloader that uses shell jobs for controlling parallel download... 4 | 5 | ``` 6 | Usage: 7 | hls [ -o ] [ -r | -f | -n ] [ ] 8 | hls -h 9 | 10 | Options: 11 | -h show helptext 12 | -o filename (default : video) 13 | -r select highest resolution automatically 14 | -n set maximum number of connections (default : 36) 15 | -f skip ffmpeg file conversion (used to enable the video file to run on any video player) 16 | -s subtitles url or path 17 | 18 | Note: if subtitles url is passed using [-s] along with skip ffmpeg [-f] then the script will download subtitle file with same name instead of burning it in video file 19 | ``` 20 | 21 | # Increase/Decrease Parallel Downloads.. 22 | 23 | Currently its set to 36 in [line 33](https://github.com/CoolnsX/hls_downloader/blob/main/hls#L33) in script 24 | You can Increase/Decrease it by using ```-n ``` 25 | 26 | ``` 27 | Internet Speed = 12 MByte per seconds.. 28 | 36 (Internet Speed * 3).. 29 | ``` 30 | NOTE :- Increasing the number will make the download faster but less stable and Decreasing the number will make download slower but stable.. Decrease the number if the script is hanging out during download process.. - This applies if you use curl for downloading (I know you have reasons for not having aria2 installed), It doesn't affect in aria2 case and leaving it default will be recommended. :) 31 | 32 | # Dependency 33 | 34 | - aria2 for downloading pieces (curl as fallback, if aria2 not found) 35 | - ffmpeg for conversion 36 | - openssl for decrypting streams 37 | -------------------------------------------------------------------------------- /hls: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #initializing.. 4 | 5 | help_text () { 6 | while IFS= read -r line; do 7 | printf "%s\n" "$line" 8 | done <<-EOF 9 | Usage: 10 | ${0##*/} [ -o ] [ -r | -f | -n ] [ ] 11 | ${0##*/} -h 12 | 13 | Options: 14 | -h show helptext 15 | -o filename (default : video) 16 | -r select highest resolution automatically 17 | -n set maximum number of connections (default : 36) 18 | -f skip ffmpeg file conversion (used to enable the video file to run on any video player) 19 | -s subtitles url (will be saved as same name as the video file) 20 | EOF 21 | } 22 | 23 | download(){ 24 | printf "" > "$failed" 25 | for i in $1; do 26 | 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 ✓ %s / %s done" "$i" "$range" || printf "%s\n" "$i" >> "$failed" & 27 | jobs -p > "$jobdir" 28 | while [ "$(wc -l "$jobdir")" -ge "$n" ];do jobs > "$jobdir";sleep 0.05;done 29 | done 30 | wait 31 | } 32 | 33 | n=36 #no. of parallel download or connections 34 | file="video" #default filename 35 | tmpdir="${XDG_CACHE_HOME:-$HOME/.cache}/hls-temp" 36 | jobdir="${XDG_CACHE_HOME:-$HOME/.cache}/hls-jobs" 37 | failed="${XDG_CACHE_HOME:-$HOME/.cache}/hls-fail" 38 | 39 | while getopts 'o:rfhn:s:' OPT; do 40 | case $OPT in 41 | o) file=$OPTARG ;; 42 | n) n=$OPTARG ;; 43 | f) skip_ffmpeg=1;; 44 | r) skip_res=1;; 45 | s) subs=$OPTARG;; 46 | *) 47 | help_text 48 | exit 0 49 | ;; 50 | esac 51 | done 52 | shift $((OPTIND - 1)) 53 | 54 | [ -z "$*" ] && printf "\033[1;34mEnter link >\033[0m " && read -r link || link=$* 55 | trap "killall curl;rm -rdf '$tmpdir' '$jobdir';exit 0" INT HUP 56 | printf "\033[2K\r\033[1;36mFetching resolutions.." 57 | m3u8_data=$(curl -s "$link" | sed '/#EXT-X-I-FRAME-STREAM-INF/d') 58 | res_list=$(printf "%s" "$m3u8_data" | sed -nE 's_.*RESOLUTION=.*x([^,]*).*_\1_p') 59 | if [ -n "$res_list" ];then 60 | highest_res=$(printf "%s" "$res_list" | sort -nr | head -1) 61 | [ -z "$skip_res" ] && printf "\033[2K\r\033[1;33mRESOLUTIONS >>\n\033[0m%s\n\033[1;34mType ur preferred resolution (default: %s) > " "$res_list" "$highest_res" && read -r sel_res || printf "\033[2K\r\033[1;36mSelecting highest resolution.." 62 | [ -z "$sel_res" ] && sel_res=$highest_res 63 | unset highest_res res_list 64 | url=$(printf "%s" "$m3u8_data" | sed -n "/x$sel_res/{n;p;}" | tr -d '\r') 65 | #check whether the m3u8_data contains uri that starts from http 66 | printf "%s" "$url" | grep -q "http" || relative_url=$(printf "%s" "$link" | sed 's|[^/]*$||') 67 | printf "\033[2K\r\033[1;36mFetching Metadata.." 68 | url="${relative_url}$url" 69 | resp="$(curl -s "$url")" 70 | else 71 | url=$link 72 | resp=$m3u8_data 73 | fi 74 | [ -d "$tmpdir" ] || mkdir -p "$tmpdir" 75 | #extract key uri and iv uri from encrypted stream if exists.. 76 | key_uri="$(printf "%s" "$resp" | sed -nE 's/^#EXT-X-KEY.*URI="([^"]*)"/\1/p')" 77 | [ -z "$key_uri" ] || iv_uri="$(printf "%s" "$resp" | sed -nE 's/^#EXT-X-IV.*URI="([^"]*)"/\1/p')" 78 | data="$(printf "%s" "$resp" | sed '/#/d')" 79 | printf "%s" "$data" | grep -q "http" && relative_url='' || relative_url=$(printf "%s" "$url" | sed 's|[^/]*$||') 80 | range=$(printf "%s\n" "$data" | wc -l) 81 | 82 | #for encrypted stream only 83 | if [ -n "$key_uri" ];then 84 | #extract key from uri.. 85 | key=$(curl -s "$key_uri" | od -A n -t x1 | tr -d ' |\n') 86 | #iv from uri 87 | [ -z "$iv_uri" ] && iv=$(openssl rand -hex 16) || iv=$(curl -s "$iv_uri" | od -A n -t x1 | tr -d ' |\n') 88 | fi 89 | 90 | printf "\033[2K\r\033[1;35mpieces : %s\n\033[1;33mDownloading.." "$range" 91 | #downloading .ts data asynchronously 92 | if command -v aria2c >>/dev/null;then 93 | printf '%s' "$data" | nl -n'rz' | sed -E "s|^([0-9]*)[[:space:]]*(.*)|${relative_url}\2\n\tout=\1.ts|g" | aria2c --no-conf=true --enable-rpc=false -x16 -s16 -j "$n" -k'1M' -d "$tmpdir" -i - --download-result=hide --summary-interval=0 94 | else 95 | download "$(seq "$range")" 96 | #redownloading failed pieces 97 | download "$(cat "$failed")" 98 | fi 99 | #downloading subtitles if uri passed using -s option 100 | [ -z "$subs" ] || curl -s "$subs" -o "$file.srt" & 101 | 102 | #concatenating all .ts file in one file.. 103 | if [ -n "$key_uri" ];then 104 | #decrypting while concatenating.. 105 | printf "\033[2K\r\033[1;36m Decrypting and Concatenating pieces into single file.." 106 | for i in "$tmpdir"/*;do 107 | openssl aes-128-cbc -d -K "$key" -iv "$iv" -nopad >> "$file.ts" < "$i" 108 | done 109 | else 110 | printf "\033[2K\r\033[1;36m Concatenating pieces into single file..\n" 111 | cat "$tmpdir"/* | ffmpeg -loglevel error -stats -i - -c copy "$file.mp4" -y 112 | skip_ffmpeg=1 113 | fi 114 | 115 | rm -rdf "$tmpdir" "$jobdir" "$failed" 116 | #conversion of allts file to mp4 video using ffmpeg.. 117 | if [ -z "$skip_ffmpeg" ];then 118 | printf "\033[2K\r\033[1;36mEncoding file to mp4 video..\n\033[0m" 119 | ffmpeg -i "$file.ts" -loglevel error -stats -c copy "$file.mp4" 120 | else 121 | [ -f "$file.ts" ] && mv "$file.ts" "$file.mp4" 122 | fi 123 | 124 | #cleanup.. 125 | rm -f "$file".ts 126 | printf "\033[2K\r\033[1;36m Done!!" 127 | --------------------------------------------------------------------------------