├── README.md ├── requirements.txt ├── app.py ├── test.py ├── utils.py ├── .gitignore └── youtube_downloader_gui.py /README.md: -------------------------------------------------------------------------------- 1 | # Python-YT-video-downloader 2 | Uses pytube and PyQt5 for GUI 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/K-Jadeja/Python-YT-video-downloader/main/requirements.txt -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | from utils import download_audio, download_video 3 | 4 | def main(): 5 | st.title("YouTube Video & Audio Downloader") 6 | st.write("Enter the URL of the YouTube video you want to download.") 7 | 8 | # Input for YouTube URL 9 | youtube_url = st.text_input("YouTube URL", "") 10 | 11 | # Select output format: Audio or Video 12 | download_type = st.radio("Select Download Type", ('Audio (MP3)', 'Video (MP4)')) 13 | 14 | # Input for Output Path 15 | output_path = st.text_input("Output Path", "./downloads") 16 | 17 | # Resolution selector for video 18 | if download_type == 'Video (MP4)': 19 | resolution = st.selectbox("Select Maximum Resolution", ["1080p", "720p", "480p", "360p"]) 20 | 21 | # Download button 22 | if st.button("Download"): 23 | if not youtube_url: 24 | st.error("Please enter a valid YouTube URL.") 25 | else: 26 | st.write("Downloading...") 27 | if download_type == 'Audio (MP3)': 28 | file_path = download_audio(youtube_url, output_path) 29 | else: 30 | file_path = download_video(youtube_url, output_path, max_resolution=resolution) 31 | 32 | if file_path: 33 | st.success(f"Download complete! File saved to: {file_path}") 34 | else: 35 | st.error("An error occurred during the download.") 36 | 37 | if __name__ == "__main__": 38 | main() 39 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | import os 2 | from yt_dlp import YoutubeDL 3 | 4 | def download_audio(youtube_url, output_path="."): 5 | try: 6 | ydl_opts = { 7 | 'format': 'bestaudio/best', 8 | 'outtmpl': os.path.join(output_path, '%(title)s.%(ext)s'), 9 | 'postprocessors': [{ 10 | 'key': 'FFmpegExtractAudio', 11 | 'preferredcodec': 'mp3', 12 | 'preferredquality': '192', 13 | }], 14 | } 15 | 16 | with YoutubeDL(ydl_opts) as ydl: 17 | info_dict = ydl.extract_info(youtube_url, download=True) 18 | print(f"Audio downloaded successfully: {ydl.prepare_filename(info_dict)}") 19 | return ydl.prepare_filename(info_dict) 20 | 21 | except Exception as e: 22 | print(f"An error occurred while downloading audio: {str(e)}") 23 | return None 24 | 25 | def download_video(youtube_url, output_path=".", max_resolution="1080p"): 26 | try: 27 | ydl_opts = { 28 | 'format': f'bestvideo[height<={max_resolution}]+bestaudio/best', 29 | 'outtmpl': os.path.join(output_path, '%(title)s.%(ext)s'), 30 | 'merge_output_format': 'mp4', 31 | } 32 | 33 | with YoutubeDL(ydl_opts) as ydl: 34 | info_dict = ydl.extract_info(youtube_url, download=True) 35 | print(f"Video downloaded successfully: {ydl.prepare_filename(info_dict)}") 36 | return ydl.prepare_filename(info_dict) 37 | 38 | except Exception as e: 39 | print(f"An error occurred while downloading video: {str(e)}") 40 | return None 41 | 42 | if __name__ == "__main__": 43 | # Example usage: 44 | youtube_url = "https://www.youtube.com/watch?v=KhFlD54nQrY" # Replace with your YouTube URL 45 | output_path = "./downloads" # Set your desired output directory 46 | 47 | # Download audio 48 | print("Downloading audio...") 49 | download_audio(youtube_url, output_path) 50 | 51 | # Download video 52 | print("Downloading video...") 53 | download_video(youtube_url, output_path, max_resolution="1080p") 54 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | from yt_dlp import YoutubeDL 3 | 4 | def download_audio(youtube_url, output_path="."): 5 | try: 6 | ydl_opts = { 7 | 'format': 'bestaudio/best', 8 | 'outtmpl': os.path.join(output_path, '%(title)s.%(ext)s'), 9 | 'postprocessors': [{ 10 | 'key': 'FFmpegExtractAudio', 11 | 'preferredcodec': 'mp3', 12 | 'preferredquality': '192', 13 | }], 14 | } 15 | 16 | with YoutubeDL(ydl_opts) as ydl: 17 | info_dict = ydl.extract_info(youtube_url, download=True) 18 | print(f"Audio downloaded successfully: {ydl.prepare_filename(info_dict)}") 19 | return ydl.prepare_filename(info_dict) 20 | 21 | except Exception as e: 22 | print(f"An error occurred while downloading audio: {str(e)}") 23 | return None 24 | 25 | def download_video(youtube_url, output_path=".", max_resolution="1080p"): 26 | try: 27 | ydl_opts = { 28 | 'format': f'bestvideo[height<={max_resolution}]+bestaudio/best', 29 | 'outtmpl': os.path.join(output_path, '%(title)s.%(ext)s'), 30 | 'merge_output_format': 'mp4', 31 | } 32 | 33 | with YoutubeDL(ydl_opts) as ydl: 34 | info_dict = ydl.extract_info(youtube_url, download=True) 35 | print(f"Video downloaded successfully: {ydl.prepare_filename(info_dict)}") 36 | return ydl.prepare_filename(info_dict) 37 | 38 | except Exception as e: 39 | print(f"An error occurred while downloading video: {str(e)}") 40 | return None 41 | 42 | if __name__ == "__main__": 43 | # Example usage: 44 | youtube_url = "https://www.youtube.com/watch?v=KhFlD54nQrY" # Replace with your YouTube URL 45 | output_path = "./downloads" # Set your desired output directory 46 | 47 | # Download audio 48 | print("Downloading audio...") 49 | download_audio(youtube_url, output_path) 50 | 51 | # Download video 52 | print("Downloading video...") 53 | download_video(youtube_url, output_path, max_resolution="1080p") 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 110 | .pdm.toml 111 | .pdm-python 112 | .pdm-build/ 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | # PyCharm 158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 160 | # and can be added to the global gitignore or merged into this file. For a more nuclear 161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 162 | #.idea/ 163 | -------------------------------------------------------------------------------- /youtube_downloader_gui.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton, QLabel, QFileDialog, QProgressBar 4 | from PyQt5.QtCore import Qt, QThread, pyqtSignal 5 | from utils import download_audio, download_video 6 | 7 | class DownloadThread(QThread): 8 | progress = pyqtSignal(int) 9 | finished = pyqtSignal(str) 10 | error = pyqtSignal(str) 11 | 12 | def __init__(self, url, save_path, download_type): 13 | QThread.__init__(self) 14 | self.url = url 15 | self.save_path = save_path 16 | self.download_type = download_type 17 | 18 | def run(self): 19 | try: 20 | if self.download_type == "audio": 21 | result = download_audio(self.url, self.save_path) 22 | else: 23 | result = download_video(self.url, self.save_path) 24 | 25 | if result: 26 | self.finished.emit(result) 27 | else: 28 | self.error.emit("Download failed") 29 | except Exception as e: 30 | self.error.emit(str(e)) 31 | 32 | class YouTubeDownloaderGUI(QWidget): 33 | def __init__(self): 34 | super().__init__() 35 | self.initUI() 36 | 37 | def initUI(self): 38 | layout = QVBoxLayout() 39 | 40 | self.url_input = QLineEdit() 41 | self.url_input.setPlaceholderText("Enter YouTube URL") 42 | layout.addWidget(self.url_input) 43 | 44 | self.path_input = QLineEdit() 45 | self.path_input.setPlaceholderText("Save to...") 46 | layout.addWidget(self.path_input) 47 | 48 | browse_button = QPushButton("Browse") 49 | browse_button.clicked.connect(self.browse_folder) 50 | layout.addWidget(browse_button) 51 | 52 | button_layout = QHBoxLayout() 53 | self.audio_button = QPushButton("Download Audio") 54 | self.audio_button.clicked.connect(lambda: self.download("audio")) 55 | button_layout.addWidget(self.audio_button) 56 | 57 | self.video_button = QPushButton("Download Video") 58 | self.video_button.clicked.connect(lambda: self.download("video")) 59 | button_layout.addWidget(self.video_button) 60 | 61 | layout.addLayout(button_layout) 62 | 63 | self.progress_bar = QProgressBar() 64 | layout.addWidget(self.progress_bar) 65 | 66 | self.status_label = QLabel("") 67 | layout.addWidget(self.status_label) 68 | 69 | self.setLayout(layout) 70 | self.setWindowTitle('YouTube Downloader') 71 | self.setGeometry(300, 300, 400, 200) 72 | 73 | def browse_folder(self): 74 | folder = QFileDialog.getExistingDirectory(self, "Select Directory") 75 | if folder: 76 | self.path_input.setText(folder) 77 | 78 | def download(self, download_type): 79 | url = self.url_input.text() 80 | save_path = self.path_input.text() 81 | 82 | if not url or not save_path: 83 | self.status_label.setText("Please enter URL and save location.") 84 | return 85 | 86 | self.download_thread = DownloadThread(url, save_path, download_type) 87 | self.download_thread.finished.connect(self.download_finished) 88 | self.download_thread.error.connect(self.download_error) 89 | self.download_thread.start() 90 | 91 | self.audio_button.setEnabled(False) 92 | self.video_button.setEnabled(False) 93 | self.status_label.setText("Downloading...") 94 | 95 | def download_finished(self, file_path): 96 | self.status_label.setText(f"Download completed: {file_path}") 97 | self.audio_button.setEnabled(True) 98 | self.video_button.setEnabled(True) 99 | 100 | def download_error(self, error_message): 101 | self.status_label.setText(f"Error: {error_message}") 102 | self.audio_button.setEnabled(True) 103 | self.video_button.setEnabled(True) 104 | 105 | if __name__ == '__main__': 106 | app = QApplication(sys.argv) 107 | ex = YouTubeDownloaderGUI() 108 | ex.show() 109 | sys.exit(app.exec_()) --------------------------------------------------------------------------------