From 7acd79a1e6c2f54379b9406777934a9a44282094 Mon Sep 17 00:00:00 2001 From: Hossein Tork <119336267+Ho3seinTork@users.noreply.github.com> Date: Fri, 31 Jan 2025 19:13:41 +0330 Subject: [PATCH] Update YouTube_playlist_downloader.py --- YouTube_playlist_downloader.py | 226 +++++++++++++++++---------------- 1 file changed, 120 insertions(+), 106 deletions(-) diff --git a/YouTube_playlist_downloader.py b/YouTube_playlist_downloader.py index a43725d..60c4d80 100644 --- a/YouTube_playlist_downloader.py +++ b/YouTube_playlist_downloader.py @@ -1,156 +1,170 @@ import requests import urllib.request from bs4 import BeautifulSoup -import progressbar from time import sleep - - +import asyncio +import aiohttp +import aiofiles +from concurrent.futures import ThreadPoolExecutor +from functools import lru_cache + +# Create a session to reuse connections +session = requests.Session() +MAX_RETRIES = 3 + +# Cache network requests +@lru_cache(maxsize=128) +def cached_request(url): + for attempt in range(MAX_RETRIES): + try: + return session.get(url, timeout=10) + except requests.exceptions.RequestException as e: + if attempt == MAX_RETRIES - 1: + raise e + sleep(1) + +# Function to display simple progress +def simple_progress(current, total): + print(f"Progress: {current}/{total} ({current/total:.1%})") + +# Function to get download links from keepvid.com def keepvid_download_link(start=1 , end=300): - fr = open('links.txt', 'r') - links = fr.read() - links = links.split("\n") - length=len(links) - print("Number of videos in the playlist "+str(length-1)) - fkeep = open('keepvid.txt', 'w') - n=1 - print("Getting download links...") - sleep(0.1) - bar = progressbar.ProgressBar(maxval=length-1, \ - widgets=[progressbar.Bar('=', '[', ']'), ' ', progressbar.Percentage()]) - bar.start() - for link in links: - if n>=start and n<=end: - if link != "": - keepvid_url = 'http://keepvid.com/?url=' + link - href = download_link(keepvid_url) - bar.update(n) - - - fkeep.write(href + "\n") - n+=1 - if n>end: - break - bar.finish() - sleep(1) - fkeep.close() - fr.close() - -def downloading( num, user_input = 0): + # Use context manager for file operations + with open('links.txt', 'r') as fr: + links = fr.read().splitlines() + + length = len(links) + print(f"Number of videos in the playlist {length-1}") + + # Use ThreadPoolExecutor for parallel processing of download links + with ThreadPoolExecutor(max_workers=5) as executor: + valid_links = [link for link in links[start-1:end] if link] + futures = [executor.submit(download_link, f'http://keepvid.com/?url={link}') + for link in valid_links] + + with open('keepvid.txt', 'w') as fkeep: + for i, future in enumerate(futures): + href = future.result() + simple_progress(start + i, length-1) + fkeep.write(f"{href}\n") + +# Function to download videos +def downloading(num, user_input=0): print('Downloading Videos....') - fr_title = open('vid_title.txt', 'r') - titles = fr_title.read() - titles = titles.split("\n") - fr_link = open('keepvid.txt', 'r') - links = fr_link.read() - links = links.split("\n") - max_index = len(links)-1 + + # Read video titles and links from files + with open('vid_title.txt', 'r') as fr_title, open('keepvid.txt', 'r') as fr_link: + titles = fr_title.read().splitlines() + links = fr_link.read().splitlines() + + max_index = len(links) - 1 if user_input == 0: user_input = num + # Use asyncio for concurrent downloads + selected_links = links[num-1:max_index] + selected_titles = titles[user_input-1:user_input-1+len(selected_links)] + + loop = asyncio.get_event_loop() + results = loop.run_until_complete(download_multiple_videos(selected_links, selected_titles)) + + for i, success in enumerate(results): + if success: + print(f"{selected_titles[i]} -- [{num+i}/{max_index}] Downloaded") + else: + print(f"{selected_titles[i]} --Download Failed") - for link in links: - if num < (max_index+1): - if link[num-1] is not "no video found": - download_vid(links[num - 1], titles[user_input - 1]) - print(titles[user_input-1] + " -- ["+str(num)+"/"+str(max_index)+"] Downloaded") - else: - print(titles[user_input-1] + " --Download Failed") - - num += 1 - user_input+=1 - - fr_link.close() - fr_title.close() print("Download Complete.") - -def download_vid(link, title): - name =str(title) + ".mp4" - urllib.request.urlretrieve(link,name) - - +# Function to download a video given a link and title +async def download_vid_async(link, title): + name = f"{title}.mp4" + async with aiohttp.ClientSession() as session: + async with session.get(link) as response: + if response.status == 200: + async with aiofiles.open(name, mode='wb') as f: + await f.write(await response.read()) + return True + return False + +async def download_multiple_videos(links, titles): + tasks = [] + for link, title in zip(links, titles): + if link != "no video found": + task = asyncio.create_task(download_vid_async(link, title)) + tasks.append(task) + return await asyncio.gather(*tasks) + +# Function to get the download link from keepvid.com def download_link(url): source = requests.get(url) plain_text = source.text soup = BeautifulSoup(plain_text, "lxml") a = 0 - c=0 - k=0 + c = 0 + k = 0 + # Find the download link based on quality for row in soup.findAll('td'): - - qulity = str(row.string) - a+=1; - if qulity == '(Max 720p)': - c=((a-1)/4)+1 + quality = str(row.string) + a += 1 + if quality == '(Max 720p)': + c = ((a - 1) / 4) + 1 for link in soup.findAll('a', {'class': 'btn-outline'}): - k+=1 - + k += 1 if k == c: href = link.get('href') - return href - - - break - elif qulity == '480p': + elif quality == '480p': c = ((a - 1) / 4) + 1 for link in soup.findAll('a', {'class': 'btn-outline'}): k += 1 - # print(k) if k == c: href = link.get('href') return href break - - if k > 0 : - + if k > 0: break return 'no video found' +# Function to get all video links from a YouTube playlist def download_link_youtube(url): - source = requests.get(url) - plain_text = source.text - soup = BeautifulSoup(plain_text, "lxml") - fw = open('links.txt', 'w') - for link in soup.findAll('a', {'class': 'playlist-video'}): - href = 'https://www.youtube.com' + link.get('href') - - fw.write(href+"\n") - - fw.close() - fw1 = open('vid_title.txt', 'w') - index=1 - for title in soup.findAll('h4', {'class': 'yt-ui-ellipsis'}): - title_vid = title.string - title_vid=str(index)+"."+title_vid.strip() - fw1.write(title_vid + "\n") - index+=1 - - fw1.close() - - - - - + response = cached_request(url) + soup = BeautifulSoup(response.text, "lxml") + + # Use list comprehensions for better performance + links = ['https://www.youtube.com' + link.get('href') + for link in soup.findAll('a', {'class': 'playlist-video'})] + + titles = [f"{i+1}.{title.string.strip()}" + for i, title in enumerate(soup.findAll('h4', {'class': 'yt-ui-ellipsis'}))] + + # Write files using context managers + with open('links.txt', 'w') as fw: + fw.write('\n'.join(links)) + + with open('vid_title.txt', 'w') as fw1: + fw1.write('\n'.join(titles)) + +# Main script to handle user input and start the download process print("Select your option : \nEnter 1 : To download playlist (Complete) form url\nEnter 2 : To resume your download.\nEnter 3 : For custom download") option = input('Enter your option : ') -if option is '1': +if option == '1': url = input('Enter URL of any video of Youtube Playlist : ') download_link_youtube(url) keepvid_download_link() starting_index = 1 downloading(starting_index) -if option is '2': - starting_index =int( input('Enter the video number to start download from : ')) +elif option == '2': + starting_index = int(input('Enter the video number to start download from : ')) downloading(starting_index) -if option is '3': +elif option == '3': url = input('Enter URL of any video of Youtube Playlist : ') starting_index = int(input('Enter the video number to start download from : ')) ending_index = int(input('Enter the video number to end at : ')) download_link_youtube(url) keepvid_download_link(starting_index, ending_index) - downloading(1,starting_index) + downloading(1, starting_index)