youtube_tool.py
1 ''' 2 QuickSave allows you to download videos from YotuTube 3 Copyright (C) 2025 Andrés Chaparro 4 5 This program is free software: you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation, either version 3 of the License, or 8 (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program. If not, see <https://www.gnu.org/licenses/>. 17 ''' 18 19 import sys 20 from kivy.clock import Clock 21 22 # To displays messages 23 from modules.utils.show_snackbar import ShowSnackbar 24 25 ''' 26 To run the task of downloading 27 videos asynchronously 28 ''' 29 from threading import Thread 30 31 # To check the platform 32 from kivy import platform 33 34 ''' 35 This is the most important 36 dependency that allows you 37 to download the videos 38 ''' 39 import yt_dlp 40 41 ''' 42 To vibrate when the 43 download is complete 44 ''' 45 from modules.utils.vibrator_tool import VibratorTool 46 47 ''' 48 This class handles errors that 49 may occur in yt_dlp, if this is 50 not done an excepcion wiil be 51 throen 52 ''' 53 class CustomLogger: 54 def debug(self, msg): 55 pass 56 def warning(self, msg): 57 pass 58 def error(self, msg): 59 print(msg, file=sys.stderr) 60 61 class YoutubeTool(): 62 def __init__(self, url): 63 ''' 64 Removes spaces at the 65 beginning and end of 66 the URL 67 ''' 68 self.url = str(url.replace(" ", "")) 69 print(f"url: {self.url}") 70 71 def download(self, format_id, output_path="/storage/emulated/0/Download/"): 72 ''' 73 This method is responsible for 74 downloading the video in the 75 selected path and id 76 ''' 77 if not platform == 'android': 78 output_path = "." # output manager to the screen 79 ''' 80 yt_dlp settings 81 ''' 82 ydl_opts = { 83 'external_downloader': 'aria2c', 84 'format': format_id, # Usar el ID del formato 85 'outtmpl': f"{output_path}/%(title)s.%(ext)s", 86 'quiet': False, # Mostrar progreso 87 'logger': CustomLogger(), 88 } 89 90 ''' 91 Create a thread to download the video 92 ''' 93 94 thread = object 95 with yt_dlp.YoutubeDL(ydl_opts) as ydl: 96 thread = Thread(target=ydl.download, args=([self.url],)) 97 thread.daemon = True 98 thread.start() 99 100 ShowSnackbar.show("¡La descarga ha iniciado!") 101 102 ''' 103 Create a thread to check 104 if the download thread has finished 105 ''' 106 107 thread_find_out = Thread(target=self.get_state, args=([thread])) 108 thread_find_out.daemon = True 109 thread_find_out.start() 110 111 def get_state(self, thread): 112 ''' 113 This method checks if 114 the download thread has already finished 115 and if so, it displays a message and vibrates 116 ''' 117 band = True 118 while(band): 119 if not thread.is_alive(): 120 ''' 121 Note: Other threads cannot update 122 the kivy interface; you must use 123 kivy's clock to schedule method 124 execution on the main kivy thread. 125 ''' 126 Clock.schedule_once(self.show_message) 127 band = False 128 129 def show_message(self, *args): 130 ''' 131 Displays a message and vibrates 132 ''' 133 ShowSnackbar.show("La descarga ha terminado") 134 VibratorTool.vibrate_in_time(1) 135 136 def get_metadata(self): 137 ''' 138 This method is responsible for 139 obtaining the video data 140 without downloading it 141 ''' 142 ydl_opts = { 143 'external_downloader': 'aria2c', 144 'quiet': False, # Evita logs innecesarios 145 'no_warnings': False, # Omite advertencias 146 'logger': CustomLogger(), 147 } 148 149 with yt_dlp.YoutubeDL(ydl_opts) as ydl: 150 print(f"url: {self.url}") 151 # Extract metadata without downloading the video 152 info = object 153 try: 154 info = ydl.extract_info(self.url, download=False) 155 except Exception as e: 156 print(f"Error extracting data: {e}") 157 158 try: 159 # basic data 160 metadata = { 161 'title': info.get('title', 'Sin título'), 162 'duration': info.get('duration', 'Desconocido'), 163 'uploader': info.get('uploader', 'Desconocido'), 164 'view_count': info.get('view_count', 0), 165 'formats': [], 166 } 167 168 # Processes every available format 169 for fmt in info.get('formats', []): 170 format_info = { 171 'format_id': fmt.get('format_id'), 172 'ext': fmt.get('ext'), 173 'resolution': fmt.get('resolution', 'audio'), 174 'filesize': fmt.get('filesize', 'Desconocido'), # En bytes 175 'quality': fmt.get('quality'), 176 } 177 metadata['formats'].append(format_info) 178 179 ''' 180 Returns the metadata as a list of dictionaries 181 ''' 182 return metadata 183 184 except Exception as e: 185 print(f"Error getting metadata: {e}") 186 return None 187 188 189 190 191 192 193 194