|
| 1 | +#DownTube is a Youtube Video/Audio downloader script written by XZANATOL |
| 2 | +#https://www.github.com/XZANATOL |
| 3 | +#source code can be found on https://github.com/avinashkranjan/Amazing-Python-Scripts |
| 4 | +from pytube.cli import on_progress |
| 5 | +from pytube import YouTube, Playlist |
| 6 | +from optparse import OptionParser |
| 7 | +import sys |
| 8 | +import os |
| 9 | +import re |
| 10 | + |
| 11 | +#Help menu |
| 12 | +usage = """ |
| 13 | +<Script> [Options] |
| 14 | +
|
| 15 | +[Options]: |
| 16 | + -h, --help show this help message and exit. |
| 17 | + -a, --audio-only Flag to download only the audio source (True/Flase). |
| 18 | + -p, --playlist Playlist flag if the provided link is a playlist not a single video. |
| 19 | + -u, --url Parameter used to add Youtube link. |
| 20 | + -f, --file Parameter used to add file that contains some Youtube links. |
| 21 | +
|
| 22 | +Notes: |
| 23 | +1) You can't pass both -f and -u at the same time. |
| 24 | +2) If a file that exists has the same name of a file to be downloaded, the current file WILL NOT be overwritten. |
| 25 | +""" |
| 26 | + |
| 27 | +#load args |
| 28 | +parser = OptionParser() |
| 29 | +parser.add_option("-a", "--audio-only", action="store_true", dest="only_audio", help="Flag to download only the audio source (True/Flase).") |
| 30 | +parser.add_option("-p", "--playlist", action="store_true", dest="playlist", help="Playlist flag is the provided link is a playlist not a single video.") |
| 31 | +parser.add_option("-u", "--url", dest="url", help="Parameter used to add Youtube link.") |
| 32 | +parser.add_option("-f", "--file", dest="file", help="Parameter used to add file that contains some Youtube links.") |
| 33 | + |
| 34 | +pattern = r'res="([0-9]+)p"' #used for checking available resolutions |
| 35 | + |
| 36 | + |
| 37 | +def choice_single_link(links): |
| 38 | + """Walkthorugh algorithm if -p/--playlist flag is False""" |
| 39 | + try: |
| 40 | + links = YouTube(links, on_progress_callback=on_progress) |
| 41 | + except: |
| 42 | + raise "Can't verify link, check internet connectivity/provided link." |
| 43 | + |
| 44 | + if only_audio: #if -a/--audio-only flag is True |
| 45 | + count = audio_download([links]) #function accepts a list of urls |
| 46 | + else: |
| 47 | + count = is_vid([links]) #function accepts a list of urls |
| 48 | + |
| 49 | + return count |
| 50 | + |
| 51 | + |
| 52 | +def choice_playlist(links): |
| 53 | + """Walkthorugh algorithm if -p/--playlist flag is True""" |
| 54 | + try: |
| 55 | + links = Playlist(links) |
| 56 | + except: |
| 57 | + raise "Can't verify playlist, check internet connectivity/provided link." |
| 58 | + |
| 59 | + if only_audio: #if -a/--audio-only flag is True |
| 60 | + count = audio_download(links.videos) |
| 61 | + else: |
| 62 | + count = is_vid(links.videos) |
| 63 | + |
| 64 | + return count |
| 65 | + |
| 66 | + |
| 67 | +def file_handler(path): |
| 68 | + """Reads file that contains Youtube links and downloads them""" |
| 69 | + try: |
| 70 | + with open(path, "r") as file: |
| 71 | + i=0 #counter for items |
| 72 | + for line in file.readlines(): |
| 73 | + if not "youtube" in line or not line.rstrip("\n"): |
| 74 | + continue |
| 75 | + choice_single_link(line.rstrip("\n")) |
| 76 | + i+=1 |
| 77 | + return i |
| 78 | + except: |
| 79 | + raise "Can't open file, check provided path/read permissions." |
| 80 | + |
| 81 | + |
| 82 | +def is_vid(lst): |
| 83 | + """Filtering function for video downloading""" |
| 84 | + #Instead of filtring the video on each approach (playlist or single_vid or file scraping), |
| 85 | + #This function takes care of the video filtering for all approaches, |
| 86 | + #Just feed her a list of streams and watch the magic. :D |
| 87 | + |
| 88 | + #this loop to check the available resolutions for 1 vid (one will apply for all) |
| 89 | + resolutions_mp4 = [] |
| 90 | + resolutions_webm = [] |
| 91 | + for i in lst: |
| 92 | + mp4_res = i.streams.filter(progressive=True, file_extension="mp4") |
| 93 | + for res in mp4_res: |
| 94 | + resolutions_mp4.append(re.search(pattern, str(res))[1]) |
| 95 | + |
| 96 | + webm_res = i.streams.filter(progressive=True, file_extension="webm") |
| 97 | + for res in webm_res: |
| 98 | + resolutions_webm.append(re.search(pattern, str(res))[1]) |
| 99 | + break |
| 100 | + |
| 101 | + print("Select one of the available resolutions:") |
| 102 | + print("mp4:", resolutions_mp4) |
| 103 | + print("webm:", resolutions_webm) |
| 104 | + ext, res = input("[extension] [resolution] > ").split(" ") |
| 105 | + |
| 106 | + #check input |
| 107 | + if not res in resolutions_mp4+resolutions_webm or not ext in ["mp4", "webm"]: |
| 108 | + raise "Invalid Input..." |
| 109 | + |
| 110 | + return video_download(lst, ext, res) |
| 111 | + |
| 112 | + |
| 113 | +def audio_download(objct): #objct is a list of urls |
| 114 | + """Function that downloads provided streams as audios""" |
| 115 | + i=0 #counter for items |
| 116 | + for aud in objct: |
| 117 | + print("Downloading: " + aud.title) |
| 118 | + aud.register_on_progress_callback(on_progress) #show progress bar |
| 119 | + try: |
| 120 | + aud.streams.filter(type="audio").order_by("abr").desc().first().download() |
| 121 | + i+=1 |
| 122 | + except: |
| 123 | + pass |
| 124 | + print() #add a blank line to seperate intersecting progress bars |
| 125 | + |
| 126 | + return i |
| 127 | + |
| 128 | + |
| 129 | +def video_download(objct, ext, res): #objct is a list of urls |
| 130 | + """Function that downloads provided streams as videos""" |
| 131 | + i=0 #counter for items |
| 132 | + for vid in objct: |
| 133 | + print("Downloading: " + vid.title) |
| 134 | + vid.register_on_progress_callback(on_progress) #show progress bar |
| 135 | + try: |
| 136 | + stream = vid.streams.filter(progressive=True, type="video", resolution=res+"p", file_extension=ext).order_by("abr").desc() |
| 137 | + |
| 138 | + if len(stream)==0: #That if condition is for in case any videos in the playlist doesn't offer the same stream resolution (common in Mix playlists) |
| 139 | + print("Couldn't find available resolution for the video, Downloading with the best available one") |
| 140 | + stream = vid.streams.filter(progressive=True, type="video", file_extension=ext).order_by("resolution").desc().order_by("abr").desc() |
| 141 | + |
| 142 | + stream.first().download() |
| 143 | + i+=1 |
| 144 | + except: |
| 145 | + pass |
| 146 | + print() #add a blank line to seperate intersecting progress bars |
| 147 | + |
| 148 | + return i |
| 149 | + |
| 150 | + |
| 151 | +def check_Download_folder(): |
| 152 | + """Checks if Donwloads folder exists.. If not, then it will create one.""" |
| 153 | + if os.path.exists("Downloads/"): |
| 154 | + os.chdir("Downloads/") |
| 155 | + else: |
| 156 | + try: |
| 157 | + os.mkdir("Downloads") |
| 158 | + except: |
| 159 | + raise "Couldn't create 'Downloads' folder, Check write permissions" |
| 160 | + os.chdir("Downloads/") |
| 161 | + |
| 162 | + |
| 163 | +#Start checkpoint |
| 164 | +if __name__ == "__main__": |
| 165 | + (options, args) = parser.parse_args() |
| 166 | + |
| 167 | + #flags |
| 168 | + only_audio = options.only_audio |
| 169 | + playlist = options.playlist |
| 170 | + link = options.url |
| 171 | + file = options.file |
| 172 | + |
| 173 | + #validate arguments |
| 174 | + if not bool(link) ^ bool(file): #xor gate |
| 175 | + print(usage) |
| 176 | + sys.exit() |
| 177 | + |
| 178 | + #prepare Downloads directory |
| 179 | + check_Download_folder() |
| 180 | + |
| 181 | + if link: |
| 182 | + if playlist: |
| 183 | + count = choice_playlist(link) |
| 184 | + else: |
| 185 | + count = choice_single_link(link) |
| 186 | + else: |
| 187 | + count = file_handler(file) |
| 188 | + |
| 189 | + #print a small report |
| 190 | + print("\n[+]Downloaded {} items".format(count)) |
0 commit comments