Last active
February 22, 2025 08:29
-
-
Save SahandMalaei/6f74adc57f8c5a7fdf963f349c3ad3f4 to your computer and use it in GitHub Desktop.
Python script to download videos from YouTube
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import tkinter as tk | |
| from tkinter import filedialog | |
| import yt_dlp | |
| import ffmpeg | |
| import os | |
| # Mapping resolutions based on the larger dimension (either width or height) | |
| RESOLUTION_CATEGORIES = { | |
| "2160p": 3840, # 2160p for 3840+ width or height | |
| "1440p": 2560, # 1440p for 2560+ width or height | |
| "1080p": 1920, # 1080p for 1920+ width or height | |
| "720p": 1280, # 720p for 1280+ width or height | |
| "480p": 854 # 480p for 854+ width or height | |
| } | |
| def categorize_resolution(width, height): | |
| """Categorizes a resolution into a standard group (e.g., 1080p) based on the larger dimension.""" | |
| max_dimension = max(width, height) | |
| for category, min_dimension in RESOLUTION_CATEGORIES.items(): | |
| if max_dimension >= min_dimension: | |
| return category | |
| return None # Ignore resolutions that don’t fit | |
| def download_video(): | |
| url = input("Enter YouTube video URL: ") | |
| ydl_opts = { | |
| "quiet": True, | |
| "listformats": True | |
| } | |
| with yt_dlp.YoutubeDL(ydl_opts) as ydl: | |
| info = ydl.extract_info(url, download=False) | |
| # Organizing video formats | |
| video_formats = {} | |
| for fmt in info.get("formats", []): | |
| width, height = fmt.get("width", 0), fmt.get("height", 0) | |
| fps = fmt.get("fps", 0) # Frames per second | |
| bitrate = fmt.get("tbr", 0) # Total bitrate | |
| if width and height: | |
| matched_res = categorize_resolution(width, height) | |
| if matched_res and fmt.get("vcodec") != "none" and fmt.get("acodec") == "none": | |
| # Select the highest FPS and bitrate available for each resolution category | |
| if matched_res not in video_formats or (fps > video_formats[matched_res]["fps"]) or (fps == video_formats[matched_res]["fps"] and bitrate > video_formats[matched_res]["bitrate"]): | |
| video_formats[matched_res] = {"format_id": fmt["format_id"], "fps": fps, "bitrate": bitrate} | |
| # Show available resolutions | |
| available_resolutions = sorted(video_formats.keys(), reverse=True) | |
| if not available_resolutions: | |
| print("No suitable video formats found.") | |
| return | |
| print("\nAvailable video resolutions:") | |
| for i, res in enumerate(available_resolutions): | |
| print(f"{i + 1}. {res} (Best FPS & Quality Available)") | |
| choice = int(input("\nEnter the number of the desired video quality: ")) - 1 | |
| selected_resolution = available_resolutions[choice] | |
| selected_video_format = video_formats[selected_resolution]["format_id"] | |
| print (f"Selected stream ID: {selected_video_format}") | |
| # Find highest quality audio format | |
| best_audio_format = None | |
| best_audio_bitrate = 0 | |
| for fmt in info.get("formats", []): | |
| if fmt.get("acodec") != "none" and fmt.get("vcodec") == "none": | |
| bitrate = fmt.get("abr", 0) # Ensure None is treated as 0 | |
| if bitrate and bitrate > best_audio_bitrate: | |
| best_audio_bitrate = bitrate | |
| best_audio_format = fmt["format_id"] | |
| if not best_audio_format: | |
| print("No suitable audio format found.") | |
| return | |
| # File dialog for saving | |
| root = tk.Tk() | |
| root.withdraw() | |
| root.attributes('-topmost', True) # Bring to front | |
| root.update() # Update window state | |
| save_path = filedialog.asksaveasfilename(defaultextension=".mp4", filetypes=[("MP4 files", "*.mp4")], parent=root) | |
| if not save_path: | |
| print("Download canceled.") | |
| return | |
| temp_video = save_path + "_video.mp4" | |
| temp_audio = save_path + "_audio.mp4" | |
| # Download video and audio separately | |
| print(f"Downloading video at {selected_resolution} (Highest FPS & Quality)...") | |
| ydl_opts_video = {"format": selected_video_format, "outtmpl": temp_video} | |
| with yt_dlp.YoutubeDL(ydl_opts_video) as ydl: | |
| ydl.download([url]) | |
| print("Downloading highest quality audio...") | |
| ydl_opts_audio = {"format": best_audio_format, "outtmpl": temp_audio} | |
| with yt_dlp.YoutubeDL(ydl_opts_audio) as ydl: | |
| ydl.download([url]) | |
| # Merge video and audio using FFmpeg with multiple threads | |
| print("Merging video and audio (using multiple CPU threads)...") | |
| ( | |
| ffmpeg | |
| .input(temp_video) | |
| .output(ffmpeg.input(temp_audio), save_path, vcodec="copy", acodec="aac", threads=0) # Auto-detect CPU threads | |
| .run(overwrite_output=True) | |
| ) | |
| # Clean up temporary files | |
| os.remove(temp_video) | |
| os.remove(temp_audio) | |
| print("Download complete! Video saved at:", save_path) | |
| if __name__ == "__main__": | |
| download_video() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment