Advertisement
xosski

YouTube preview and downloader

Dec 4th, 2024
22
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.88 KB | None | 0 0
  1. import tkinter as tk
  2. from tkinter import ttk, filedialog
  3. from pathlib import Path
  4. from PIL import Image, ImageTk
  5. import urllib.request
  6. import io
  7. import yt_dlp
  8. import re
  9. import os
  10. import time
  11. import threading
  12.  
  13. class YouTubeGUI:
  14. def __init__(self, root):
  15. self.root = root
  16. self.root.title("YouTube Downloader")
  17.  
  18. # Main frames setup
  19. url_frame = ttk.LabelFrame(root, text="Link", padding="5")
  20. url_frame.pack(fill="x", padx=5, pady=5)
  21.  
  22. self.url_entry = ttk.Entry(url_frame)
  23. self.url_entry.pack(fill="x", padx=5, pady=5)
  24. self.url_entry.bind('<KeyRelease>', self.on_url_change)
  25. self.url_entry.bind('<<Paste>>', lambda e: self.root.after(10, self.update_thumbnail))
  26.  
  27. self.thumbnail_label = ttk.Label(url_frame)
  28. self.thumbnail_label.pack(pady=5)
  29.  
  30. # Options setup
  31. options_frame = ttk.LabelFrame(root, text="Options", padding="5")
  32. options_frame.pack(fill="x", padx=5, pady=5)
  33.  
  34. ttk.Label(options_frame, text="Type:").pack(anchor="w")
  35. self.type_var = tk.StringVar(value="Single Video")
  36. type_combo = ttk.Combobox(options_frame, textvariable=self.type_var)
  37. type_combo['values'] = ['Single Video', 'Playlist']
  38. type_combo.pack(fill="x", padx=5, pady=5)
  39.  
  40. ttk.Label(options_frame, text="Quality:").pack(anchor="w")
  41. self.format_var = tk.StringVar(value="Highest")
  42. format_combo = ttk.Combobox(options_frame, textvariable=self.format_var)
  43. format_combo['values'] = ['Highest', '1080p', '720p', '480p', '360p', 'Audio Only']
  44. format_combo.pack(fill="x", padx=5, pady=5)
  45.  
  46. ttk.Label(options_frame, text="Save Location:").pack(anchor="w")
  47. self.output_frame = ttk.Frame(options_frame)
  48. self.output_frame.pack(fill="x", padx=5, pady=5)
  49.  
  50. self.output_var = tk.StringVar(value=str(Path.home() / "Downloads"))
  51. self.output_entry = ttk.Entry(self.output_frame, textvariable=self.output_var)
  52. self.output_entry.pack(side="left", fill="x", expand=True)
  53.  
  54. self.browse_btn = ttk.Button(self.output_frame, text="Browse", command=self.browse_output)
  55. self.browse_btn.pack(side="right", padx=5)
  56.  
  57. # Download button and progress
  58. self.download_btn = ttk.Button(root, text="Download", command=self.download)
  59. self.download_btn.pack(fill="x", padx=5, pady=5)
  60.  
  61. self.status_var = tk.StringVar()
  62. self.status_label = ttk.Label(root, textvariable=self.status_var, wraplength=350)
  63. self.status_label.pack(fill="x", padx=5, pady=5)
  64.  
  65. # Progress bar
  66. self.progress = ttk.Progressbar(root, mode='determinate')
  67. self.progress.pack(fill="x", padx=5, pady=5)
  68.  
  69. self.download_start_time = None
  70. self.current_filesize = 0
  71.  
  72. def on_url_change(self, event):
  73. self.update_thumbnail()
  74.  
  75. def update_thumbnail(self):
  76. url = self.url_entry.get().strip()
  77. if not url:
  78. return
  79. try:
  80. video_id = self.extract_video_id(url)
  81. if video_id:
  82. self.set_thumbnail(video_id)
  83. except Exception as e:
  84. print(f"Thumbnail error: {e}")
  85.  
  86. def set_thumbnail(self, video_id):
  87. thumb_url = f"https://img.youtube.com/vi/{video_id}/mqdefault.jpg"
  88. try:
  89. with urllib.request.urlopen(thumb_url) as u:
  90. raw_data = u.read()
  91. image = Image.open(io.BytesIO(raw_data))
  92. image.thumbnail((320, 180))
  93. photo = ImageTk.PhotoImage(image)
  94. self.thumbnail_label.configure(image=photo)
  95. self.thumbnail_label.image = photo
  96. except Exception as e:
  97. print(f"Thumbnail load error: {e}")
  98.  
  99. def extract_video_id(self, url):
  100. youtube_regex = r'(?:youtube\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})'
  101. match = re.search(youtube_regex, url)
  102. return match.group(1) if match else None
  103.  
  104. def browse_output(self):
  105. directory = filedialog.askdirectory(initialdir=self.output_var.get())
  106. if directory:
  107. self.output_var.set(directory)
  108.  
  109. def get_format_string(self, quality):
  110. format_map = {
  111. 'Highest': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best',
  112. '1080p': 'bestvideo[height<=1080][ext=mp4]+bestaudio[ext=m4a]/best[height<=1080][ext=mp4]/best',
  113. '720p': 'bestvideo[height<=720][ext=mp4]+bestaudio[ext=m4a]/best[height<=720][ext=mp4]/best',
  114. '480p': 'bestvideo[height<=480][ext=mp4]+bestaudio[ext=m4a]/best[height<=480][ext=mp4]/best',
  115. '360p': 'bestvideo[height<=360][ext=mp4]+bestaudio[ext=m4a]/best[height<=360][ext=mp4]/best',
  116. }
  117. return format_map.get(quality, 'best')
  118.  
  119. def format_size(self, bytes):
  120. try:
  121. for unit in ['B', 'KB', 'MB', 'GB']:
  122. if bytes < 1024:
  123. return f"{bytes:.2f} {unit}"
  124. bytes /= 1024
  125. return f"{bytes:.2f} TB"
  126. except Exception as e:
  127. return "Error in size format"
  128.  
  129. def format_time(self, seconds):
  130. try:
  131. if seconds < 60:
  132. return f"{seconds:.0f}s"
  133. minutes, seconds = divmod(seconds, 60)
  134. if minutes < 60:
  135. return f"{minutes:.0f}m {seconds:.0f}s"
  136. hours, minutes = divmod(minutes, 60)
  137. return f"{hours:.0f}h {minutes:.0f}m {seconds:.0f}s"
  138. except Exception as e:
  139. return "Error in time format"
  140.  
  141. def progress_hook(self, d):
  142. if d['status'] == 'downloading':
  143. try:
  144. # Calculate progress percentage
  145. downloaded = d.get('downloaded_bytes', 0)
  146. total = d.get('total_bytes') or d.get('total_bytes_estimate', 0)
  147.  
  148. if total > 0:
  149. percentage = (downloaded / total) * 100
  150. self.progress['value'] = percentage
  151.  
  152. # Calculate speed and ETA
  153. if not self.download_start_time:
  154. self.download_start_time = time.time()
  155. self.current_filesize = total
  156.  
  157. elapsed_time = time.time() - self.download_start_time
  158. speed = downloaded / elapsed_time
  159. remaining_bytes = total - downloaded
  160. eta = remaining_bytes / speed if speed > 0 else 0
  161.  
  162. # Format status message
  163. status = (f"Downloading... {percentage:.1f}% "
  164. f"({self.format_size(downloaded)}/{self.format_size(total)}) "
  165. f"at {self.format_size(speed)}/s "
  166. f"ETA: {self.format_time(eta)}")
  167.  
  168. self.status_var.set(status)
  169. except Exception as e:
  170. self.status_var.set("Downloading...")
  171. self.root.after(100, self.root.update)
  172.  
  173. elif d['status'] == 'finished':
  174. self.download_start_time = None
  175. self.current_filesize = 0
  176. self.progress['value'] = 100
  177. self.status_var.set("Download completed!")
  178.  
  179. def download(self):
  180. url = self.url_entry.get().strip()
  181. if not url:
  182. self.status_var.set("Please enter a URL")
  183. return
  184.  
  185. quality = self.format_var.get()
  186. output_path = self.output_var.get()
  187. is_playlist = self.type_var.get() == "Playlist"
  188.  
  189. ydl_opts = {
  190. 'format': self.get_format_string(quality),
  191. 'outtmpl': f'{output_path}/%(title)s.%(ext)s',
  192. 'progress_hooks': [self.progress_hook],
  193. }
  194.  
  195. if is_playlist:
  196. ydl_opts['yes_playlist'] = True
  197. else:
  198. ydl_opts['noplaylist'] = True
  199.  
  200. if quality == 'Audio Only':
  201. ydl_opts.update({
  202. 'format': 'bestaudio/best',
  203. 'postprocessors': [{
  204. 'key': 'FFmpegExtractAudio',
  205. 'preferredcodec': 'mp3',
  206. }],
  207. })
  208.  
  209. # Run the download in a separate thread to avoid UI blocking
  210. def download_thread():
  211. try:
  212. self.progress['value'] = 0
  213. self.download_start_time = None
  214. self.status_var.set("Starting download...")
  215. self.root.update()
  216.  
  217. with yt_dlp.YoutubeDL(ydl_opts) as ydl:
  218. ydl.download([url])
  219.  
  220. except Exception as e:
  221. self.status_var.set(f"Error: {str(e)}")
  222. self.progress['value'] = 0
  223.  
  224. # Start the download in a new thread
  225. threading.Thread(target=download_thread, daemon=True).start()
  226.  
  227. if __name__ == "__main__":
  228. root = tk.Tk()
  229. app = YouTubeGUI(root)
  230. root.mainloop()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement