🤖 Descargador de YouTube 2.0: Guía Definitiva de tu App Ciberpunk 💻
¡Hola! Has creado una aplicación increíblemente sofisticada. Con la incorporación de hilos, una barra de progreso y una interfaz de usuario con un estilo de terminal retro, este proyecto merece una publicación completa y visualmente atractiva.
A continuación, te presento el manual completo, en formato Markdown, que incluye tu código exacto y una explicación exhaustiva de cada una de sus nuevas características, desde la instalación hasta su uso.
Requisitos Esenciales 🛠️
- Un sistema operativo Linux (la guía se basa en distribuciones como Ubuntu/Debian).
- Una terminal.
- El archivo
cc_icon.png
en el mismo directorio de tu script, si quieres que la aplicación muestre el ícono.
Paso 1: Preparando tu Entorno Python 🐍
Si ya tienes Python y pip
instalados, puedes saltarte este paso. Si no, sigue estas instrucciones para prepararte.
Actualización del Sistema
Siempre es una buena práctica comenzar con una actualización de tus paquetes.
sudo apt-get update
sudo apt-get upgrade
Instalación de Python y pip
Estos comandos instalarán tanto el intérprete de Python como su gestor de paquetes.
sudo apt-get install python3
sudo apt-get install python3-pip
Para verificar que todo está en orden, ejecuta estos comandos y asegúrate de que se muestre un número de versión.
python3 --version
pip3 --version
Paso 2: Configuración e Instalación de Librerías 📦
Ahora que tu entorno base está listo, es vital usar un entorno virtual (venv
) para aislar tu proyecto y mantenerlo ordenado.
Creando y Activando el Entorno Virtual
Crea una carpeta para tu proyecto, navega a ella y activa el entorno virtual.
mkdir descargador_yt_ciberpunk
cd descargador_yt_ciberpunk
python3 -m venv venv
source venv/bin/activate
Instalando las Bibliotecas Adicionales
Tu aplicación usa varias bibliotecas que no vienen por defecto. Con el entorno virtual activo, instala todas las dependencias con un solo comando.
pip install pytubefix
pytubefix
es la biblioteca principal para la descarga. Los otros módulos que utilizas, como tkinter
, ttk
, threading
y os
, son parte de la librería estándar de Python y no necesitan instalación adicional.
Paso 3: ¡El Código Completo! Tu App Ciberpunk en Acción 💻
A continuación, se presenta el código completo. Es una obra de ingeniería que combina lógica robusta con una interfaz de usuario avanzada. Copia y pega el código exactamente como está en un archivo llamado descargador.py
.
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
from pytubefix import YouTube
from pytubefix.exceptions import RegexMatchError
import threading
import re
import os
import webbrowser
--- Lógica de la Aplicación ---
def es_url_youtube(url):
"""Valida si la cadena proporcionada es una URL de YouTube."""
patron = r"^(https?://)?(www.)?(youtube.com|youtu.be)/.+$"
return re.match(patron, url) is not None
def progreso_descarga(stream, chunk, bytes_remaining):
"""Función para actualizar la barra de progreso."""
total_size = stream.filesize
bytes_descargados = total_size - bytes_remaining
porcentaje = (bytes_descargados / total_size) * 100
barra_progreso['value'] = porcentaje
ventana.update_idletasks()
def descarga_completada(stream, file_path):
"""Función que se llama cuando la descarga se completa."""
estado.set("¡Descarga completada!")
messagebox.showinfo("Éxito", f"Descarga completada en: {file_path}")
boton_descargar.config(state=tk.NORMAL)
boton_abrir_carpeta.config(state=tk.NORMAL)
ruta_descarga_final = os.path.dirname(file_path)
boton_abrir_carpeta.config(command=lambda: os.startfile(ruta_descarga_final) if os.name == 'nt' else os.system(f'open "{ruta_descarga_final}"') if os.name == 'darwin' else os.system(f'xdg-open "{ruta_descarga_final}"'))
barra_progreso['value'] = 0
def descargar_video():
url = entrada_url.get()
carpeta = ruta_descarga.get()
descargar_audio_solo = opcion_descarga.get() == "audio"
if not url:
messagebox.showwarning("Advertencia", "Por favor, ingresa una URL de YouTube.")
return
if not es_url_youtube(url):
estado.set("URL inválida.")
messagebox.showerror("Error", "La URL ingresada no es una URL válida de YouTube.")
return
try:
boton_descargar.config(state=tk.DISABLED)
boton_abrir_carpeta.config(state=tk.DISABLED)
estado.set("Validando URL...")
yt = YouTube(url, on_progress_callback=progreso_descarga, on_complete_callback=descarga_completada)
estado.set(f"Descargando: {yt.title} ({'Audio' if descargar_audio_solo else 'Video'})")
if descargar_audio_solo:
audio = yt.streams.filter(only_audio=True).order_by('abr').desc().first()
if audio:
audio.download(output_path=carpeta, filename=f"{yt.title}.mp3")
else:
raise Exception("No se encontraron streams de audio disponibles.")
else:
video = yt.streams.get_highest_resolution()
if video:
video.download(output_path=carpeta)
else:
raise Exception("No se encontraron streams de video de alta resolución disponibles.")
except RegexMatchError:
estado.set("Error: URL de YouTube no válida.")
messagebox.showerror("Error", "La URL proporcionada no parece ser una URL válida de YouTube.")
boton_descargar.config(state=tk.NORMAL)
boton_abrir_carpeta.config(state=tk.DISABLED)
barra_progreso['value'] = 0
except Exception as e:
estado.set(f"Error en la descarga: {e}")
messagebox.showerror("Error", str(e))
boton_descargar.config(state=tk.NORMAL)
boton_abrir_carpeta.config(state=tk.DISABLED)
barra_progreso['value'] = 0
def elegir_carpeta():
carpeta = filedialog.askdirectory()
if carpeta:
ruta_descarga.set(carpeta)
def iniciar_descarga():
hilo = threading.Thread(target=descargar_video)
hilo.start()
def abrir_enlace_acerca_de():
webbrowser.open_new_tab("https://shalom-now.blogspot.com/")
--- Lógica de la Animación de Escritura ---
palabras_animacion = ["FAST", "EASY", "FREE", "MP3"]
palabra_idx = 0
letra_idx = 0
cursor_visible = True
def animar_escritura():
global palabra_idx, letra_idx
palabra_actual = palabras_animacion[palabra_idx]
if letra_idx < len(palabra_actual):
texto_dinamico.set(palabra_actual[:letra_idx + 1])
letra_idx += 1
ventana.after(150, animar_escritura)
else:
ventana.after(1500, animar_borrado)
def animar_borrado():
global palabra_idx, letra_idx
if letra_idx > 0:
texto_dinamico.set(palabras_animacion[palabra_idx][:letra_idx - 1])
letra_idx -= 1
ventana.after(100, animar_borrado)
else:
palabra_idx = (palabra_idx + 1) % len(palabras_animacion)
ventana.after(500, animar_escritura)
def animar_cursor():
global cursor_visible
if cursor_visible:
etiqueta_cursor.config(text="")
else:
etiqueta_cursor.config(text="|")
cursor_visible = not cursor_visible
ventana.after(500, animar_cursor)
--- Configuración de la Interfaz Gráfica ---
ventana = tk.Tk()
ventana.title("Descargador de YouTube")
ventana.geometry("800x600")
ventana.resizable(True, True)
ventana.configure(bg="#000000")
Cargar icono de Creative Commons
try:
icono_cc_img = tk.PhotoImage(file="cc_icon.png")
ventana.iconphoto(True, icono_cc_img)
except tk.TclError:
pass
Variables de Tkinter (movidas después de crear la ventana)
ruta_descarga = tk.StringVar()
estado = tk.StringVar(value="Esperando URL…")
opcion_descarga = tk.StringVar(value="video")
texto_dinamico = tk.StringVar(value="")
Estilo
estilo = ttk.Style()
estilo.theme_use("clam")
estilo.configure("TLabel", font=("Courier", 11), background="#000000", foreground="#00ff00")
estilo.configure("TEntry", font=("Courier", 11), fieldbackground="#0d0d0d", foreground="#00ff00", borderwidth=1, relief="solid")
estilo.configure("TButton", font=("Courier", 11), padding=6, borderwidth=1, relief="solid", background="#00ff00", foreground="#000000")
estilo.map("TButton", background=[('active', '#009900')])
Estilo para la barra de progreso
estilo.configure("Custom.Horizontal.TProgressbar", thickness=25, troughcolor="#0d0d0d", background="#00ff00")
estilo.configure("TRadiobutton", font=("Courier", 10), background="#000000", foreground="#00ff00")
estilo.configure("TLabelframe", background="#000000", borderwidth=1, relief="solid")
estilo.configure("TLabelframe.Label", font=("Courier", 11), background="#000000", foreground="#00ff00")
estilo.configure("Header.TLabel", font=("Courier", 24, "bold"), background="#000000", foreground="#00ff00")
Widgets usando grid layout
frame_header = tk.Frame(ventana, bg="#000000")
frame_header.grid(row=0, column=0, columnspan=3, padx=20, pady=(20, 10), sticky="ew")
frame_header.columnconfigure(0, weight=1)
etiqueta_estatica = tk.Label(frame_header, text="DESCARGADOR YT ", font=("Courier", 12), bg="#000000", fg="#00ff00", justify="right")
etiqueta_estatica.pack(side=tk.LEFT)
etiqueta_dinamica = tk.Label(frame_header, textvariable=texto_dinamico, font=("Courier", 12), bg="#000000", fg="#00ff00")
etiqueta_dinamica.pack(side=tk.LEFT)
etiqueta_cursor = tk.Label(frame_header, text="|", font=("Courier", 12), bg="#000000", fg="#00ff00")
etiqueta_cursor.pack(side=tk.LEFT)
animar_escritura()
animar_cursor()
ttk.Label(ventana, text="URL del video de YouTube:").grid(row=1, column=0, padx=20, pady=(10, 5), sticky="w")
entrada_url = ttk.Entry(ventana)
entrada_url.grid(row=1, column=1, columnspan=2, padx=20, pady=(10, 5), sticky="ew")
frame_opciones = ttk.LabelFrame(ventana, text="Opciones de Descarga")
frame_opciones.grid(row=2, column=0, columnspan=3, padx=20, pady=10, sticky="ew")
frame_opciones.columnconfigure(0, weight=1)
radio_video = ttk.Radiobutton(frame_opciones, text="Video (Máxima Calidad)", variable=opcion_descarga, value="video")
radio_video.grid(row=0, column=0, padx=20, pady=5, sticky="w")
radio_audio = ttk.Radiobutton(frame_opciones, text="Solo Audio (Máxima Calidad)", variable=opcion_descarga, value="audio")
radio_audio.grid(row=1, column=0, padx=20, pady=5, sticky="w")
ttk.Label(ventana, text="Carpeta de Descarga:").grid(row=3, column=0, padx=20, pady=(10, 5), sticky="w")
entrada_carpeta = ttk.Entry(ventana, textvariable=ruta_descarga, state="readonly")
entrada_carpeta.grid(row=3, column=1, padx=20, pady=(10, 5), sticky="ew")
boton_elegir_carpeta = ttk.Button(ventana, text="Elegir…", command=elegir_carpeta)
boton_elegir_carpeta.grid(row=3, column=2, padx=(0, 20), pady=(10, 5), sticky="w")
boton_descargar = ttk.Button(ventana, text="Descargar", command=iniciar_descarga)
boton_descargar.grid(row=4, column=0, columnspan=3, padx=20, pady=(20, 10), sticky="ew")
barra_progreso = ttk.Progressbar(ventana, orient=tk.HORIZONTAL, mode='determinate', style="Custom.Horizontal.TProgressbar")
barra_progreso.grid(row=5, column=0, columnspan=3, padx=20, pady=10, sticky="ew")
ttk.Label(ventana, textvariable=estado, foreground="#ff00ff").grid(row=6, column=0, columnspan=3, padx=20, pady=5, sticky="w")
boton_abrir_carpeta = ttk.Button(ventana, text="Abrir Carpeta", state=tk.DISABLED)
boton_abrir_carpeta.grid(row=7, column=0, columnspan=3, padx=20, pady=(10, 20), sticky="ew")
frame_acerca_de = ttk.LabelFrame(ventana, text="Acerca de")
frame_acerca_de.grid(row=8, column=0, columnspan=3, padx=20, pady=(20, 10), sticky="ew")
etiqueta_acerca_de = ttk.Label(frame_acerca_de, text="Este programa permite descargar videos o solo el audio de videos de YouTube. La descarga se realizará siempre con la máxima calidad disponible para la opción seleccionada.")
etiqueta_acerca_de.grid(row=0, column=0, padx=10, pady=5, sticky="w")
enlace_acerca_de = ttk.Label(frame_acerca_de, text="Más información", cursor="hand2", foreground="#00ff00")
enlace_acerca_de.grid(row=1, column=0, padx=10, pady=5, sticky="w")
enlace_acerca_de.bind("", lambda e: abrir_enlace_acerca_de())
ventana.columnconfigure(1, weight=1)
frame_opciones.columnconfigure(0, weight=1)
frame_acerca_de.columnconfigure(0, weight=1)
ventana.mainloop()