├── requirements.txt ├── init.py ├── README.md ├── LICENSE └── main.py /requirements.txt: -------------------------------------------------------------------------------- 1 | pytube3==9.6.4 2 | streamlit==0.64.0 -------------------------------------------------------------------------------- /init.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | 4 | if not os.path.isdir('Downloads'): 5 | os.mkdir('Downloads') 6 | subprocess.run('streamlit run main.py', shell=True) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YouTube Downloader 2 | A web based python application to download youtube videos 3 | 4 | 5 | ## Using the application 6 | * Clone GitHub repository 7 | * Download required dependencies: ```$ pip install -r requirements.txt``` (If there is an issue when installing streamlit, try switching to Python 3.7) 8 | * [Install ffmpeg and add it to your path](https://www.wikihow.com/Install-FFmpeg-on-Windows) 9 | * Run `init.py` 10 | 11 | (If you get a cipher error, that is an error with pytube that should be fixable with [this](https://github.com/nficano/pytube/pull/643/files)) 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 000Nobody 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import requests 3 | import os 4 | import re 5 | import pytube 6 | import subprocess 7 | from pytube import YouTube 8 | import streamlit as st 9 | from PIL import Image 10 | from io import BytesIO 11 | 12 | def getVideo(url): #Check to ensure that the video can be found 13 | global video_found, video 14 | try: 15 | video = YouTube(url) 16 | video_found = True 17 | except pytube.exceptions.RegexMatchError: 18 | st.error('Invalid URL.') 19 | video_found = False 20 | except pytube.exceptions.VideoUnavailable: 21 | st.error('This video is unavailable') 22 | video_found = False 23 | return video 24 | 25 | def loadThumbnail(image_url): 26 | response = requests.get(image_url) 27 | img = Image.open(BytesIO(response.content)) 28 | return img 29 | 30 | @st.cache 31 | def getStats(video): # Return the formated video stats 32 | header = (f'**{video.title}**' 33 | + f' *By: {video.author}*') 34 | thumbnail = loadThumbnail(video.thumbnail_url) 35 | info = (f'Length: **{datetime.timedelta(seconds = video.length)}** \n' 36 | + f'Views: **{video.views:,}**') 37 | return header, thumbnail, info 38 | 39 | st.title('YouTube Downloader') 40 | 41 | url = st.text_input('Enter the URL of the YouTube video') 42 | 43 | if url: 44 | video = getVideo(url) 45 | if video_found: 46 | header, thumbnail, info = getStats(video) 47 | st.header(header) 48 | st.image(thumbnail, width = 750) 49 | st.write(info) 50 | download_type = st.radio( 51 | 'Select the type of download you would like', [ 52 | 'Video and Audio (.mkv)', 53 | 'Audio Only (.mp3)', 54 | 'Video Only (.mp4)'] 55 | ) 56 | 57 | if download_type == 'Video and Audio (.mkv)': 58 | video_stream = video.streams.filter(type = 'video', subtype = 'mp4').order_by(attribute_name = 'resolution').last() 59 | audio_stream = video.streams.get_audio_only() 60 | filesize = round((video_stream.filesize + audio_stream.filesize)/1000000, 2) 61 | if st.button(f'Download (~{filesize} MB)'): 62 | # To get the highest resolution, the audio and video streams must be installed seperate as .mp4s, 63 | # so the audio track must be converted to an mp3, then merged with the video, then the other files must be deleted 64 | with st.spinner( 65 | f'Downloading {video.title}... ***Please wait to open any files until the download has finished***' 66 | ): 67 | video_stream.download(filename = 'video-track') 68 | audio_stream.download(filename = 'audio-track') 69 | convert_mp3 = 'ffmpeg -i audio-track.mp4 audio-track.mp3' 70 | subprocess.run(convert_mp3, shell = True) 71 | os.remove('audio-track.mp4') 72 | formatted_title = re.sub("[^0-9a-zA-Z]+", "-", video.title) 73 | merge_audio_video = ( 74 | 'ffmpeg -y -i audio-track.mp3 ' 75 | '-r 30 -i video-track.mp4 ' 76 | '-filter:a aresample=async=1 -c:a flac -c:v ' 77 | f'copy Downloads/{formatted_title}.mkv' 78 | ) 79 | subprocess.run(merge_audio_video, shell = True) 80 | os.remove('audio-track.mp3') 81 | os.remove('video-track.mp4') 82 | st.success(f'Finished Downloading {video.title}!') 83 | 84 | if download_type == 'Audio Only (.mp3)': 85 | stream = video.streams.get_audio_only() 86 | filesize = round(stream.filesize/1000000, 2) 87 | if st.button(f'Download (~{filesize} MB)'): 88 | with st.spinner( 89 | f'Downloading {video.title}... ***Please wait to open any files until the download has finished***' 90 | ): 91 | stream.download(filename = 'audio') 92 | convert_mp3 = f'ffmpeg -i audio.mp4 Downloads/{re.sub("[^0-9a-zA-Z]+", "-", video.title)}.mp3' 93 | subprocess.run(convert_mp3, shell = True) 94 | os.remove('Downloads/audio.mp4') 95 | st.success(f'Finished Downloading {video.title}!') 96 | 97 | if download_type == 'Video Only (.mp4)': 98 | stream = video.streams.filter(type = 'video', subtype = 'mp4').order_by(attribute_name = 'resolution').last() 99 | filesize = round(stream.filesize/1000000, 2) 100 | if st.button(f'Download (~{filesize} MB)'): 101 | with st.spinner( 102 | f'Downloading {video.title}... ***Please wait to open any files until the download has finished***' 103 | ): 104 | stream.download(filename = video.title + ' Video Only', output_path = 'Downloads') 105 | st.success(f'Finished Downloading {video.title}!') 106 | --------------------------------------------------------------------------------