[Python] Descargar videos y shorts en Youtube

En este artículo vamos a construir una pequeña aplicación de escritorio en Python capaz de descargar vídeos de Internet mediante una interfaz gráfica sencilla.

El objetivo no es crear simplemente un script que descargue un archivo desde una URL, sino aprovechar varias herramientas y librerías del ecosistema Python para construir una aplicación completa:

  • Interfaz gráfica con Tkinter.
  • Descarga y extracción de información mediante yt-dlp.
  • Obtención y visualización de la miniatura del vídeo.
  • Descarga de vídeo y audio.
  • Conversión y combinación mediante FFmpeg cuando es necesario.
  • Manejo de errores mediante cuadros de diálogo.

⚠️ Aviso importante

Este proyecto tiene una finalidad educativa y experimental. El hecho de que técnicamente sea posible descargar contenido no significa que tengamos automáticamente derecho a hacerlo.

Debes utilizar la herramienta únicamente con contenido que tengas permiso para descargar y siempre respetando los derechos de autor, las condiciones de servicio de la plataforma y la legislación aplicable.

¿Qué vamos a construir?

La aplicación tendrá una interfaz muy sencilla. El usuario introduce una URL, pulsa el botón de descarga y el programa intenta obtener información sobre el contenido antes de comenzar la descarga.

Además, cuando la plataforma proporciona una miniatura, la aplicación la mostrará en pantalla.

El flujo general será:

  1. Introducir una URL.
  2. Obtener información sobre el contenido.
  3. Obtener la miniatura.
  4. Mostrar la miniatura en la interfaz.
  5. Seleccionar el formato disponible.
  6. Descargar el contenido.
  7. Combinar vídeo y audio cuando sea necesario.
  8. Mostrar el resultado al usuario.

¿Qué herramientas vamos a utilizar?

Python

Python será el lenguaje utilizado para construir toda la aplicación.

Una de las ventajas de Python para este tipo de proyectos es que permite combinar fácilmente una interfaz gráfica, peticiones HTTP, procesamiento de archivos y herramientas externas.

Tkinter

Tkinter es la biblioteca gráfica incluida habitualmente con las instalaciones de Python.

Nos permitirá crear:

  • Ventanas.
  • Etiquetas.
  • Campos de texto.
  • Botones.
  • Mensajes de error.
  • Vista previa de imágenes.

yt-dlp

yt-dlp es la pieza encargada de analizar la URL y gestionar la descarga de los diferentes formatos disponibles.

Una de las particularidades de muchas plataformas modernas es que un vídeo no necesariamente se sirve como un único archivo que contenga imagen y sonido.

En determinados casos podemos encontrar:

  • Un flujo de vídeo.
  • Un flujo de audio.
  • Diferentes resoluciones de vídeo.
  • Diferentes formatos de audio.

Por eso una herramienta como yt-dlp resulta mucho más práctica que intentar descargar directamente una URL de archivo.

FFmpeg

FFmpeg es una herramienta fundamental para trabajar con vídeo y audio.

En nuestro caso puede ser necesaria para realizar operaciones como:

  • Combinar vídeo y audio.
  • Convertir formatos.
  • Remultiplexar archivos.
  • Procesar diferentes códecs.

Por ejemplo, si descargamos un flujo de vídeo y otro de audio por separado, FFmpeg puede encargarse de combinarlos en un único archivo final.

Instalación de las dependencias

Antes de ejecutar el programa necesitaremos instalar algunas dependencias.

1. Python

Necesitamos tener instalado Python 3.

Podemos comprobarlo desde una terminal:

python --version

En algunos sistemas puede ser necesario utilizar:

python3 --version

2. yt-dlp

Instalamos yt-dlp mediante pip:

pip install -U yt-dlp

3. Pillow

Utilizaremos Pillow para cargar y mostrar la miniatura descargada.

pip install pillow

4. Requests

También utilizaremos Requests para descargar la imagen de la miniatura:

pip install requests

Podemos instalar todas las dependencias Python de una sola vez:

pip install -U yt-dlp pillow requests

¿Por qué necesitamos FFmpeg?

Esta es probablemente la parte que más confusión genera cuando comenzamos a utilizar yt-dlp.

Imaginemos que queremos descargar un vídeo en una resolución determinada. La plataforma puede proporcionar un flujo de vídeo de alta calidad y, por separado, un flujo de audio.

En ese caso tenemos algo parecido a esto:

VIDEO
   +
AUDIO
   ↓
FFmpeg
   ↓
ARCHIVO FINAL MP4

Por eso nuestro programa puede necesitar FFmpeg aunque el código Python no lo utilice directamente para descargar los datos.

yt-dlp se encarga de gestionar la descarga y FFmpeg puede encargarse del procesamiento posterior.

Comprobar si FFmpeg está instalado

Podemos comprobarlo desde la terminal:

ffmpeg -version

Si el sistema reconoce el comando, FFmpeg está disponible.

Si aparece un mensaje indicando que el comando no existe, tendremos que instalarlo y asegurarnos de que su ejecutable esté disponible en el PATH del sistema.

La aplicación gráfica

Vamos ahora con el programa completo.

La aplicación tendrá un campo donde introducir la URL, un botón para iniciar la descarga y un espacio donde mostrar la miniatura obtenida.

import tkinter as tk
from tkinter import messagebox
from PIL import Image, ImageTk
import yt_dlp
import requests
from io import BytesIO


def descargar_video():
    url = url_entry.get().strip()

    if not url:
        messagebox.showerror(
            "Error",
            "Por favor, introduce una URL válida."
        )
        return

    opciones = {
        "format": "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]",
        "outtmpl": "%(title)s.%(ext)s",
        "merge_output_format": "mp4",
        "quiet": False,
    }

    try:
        # -------------------------------------------------
        # Obtener información del contenido
        # -------------------------------------------------

        with yt_dlp.YoutubeDL() as ydl:
            info = ydl.extract_info(url, download=False)

        titulo = info.get("title", "Sin título")
        thumbnail_url = info.get("thumbnail")

        titulo_label.config(
            text=f"Título:\n{titulo}"
        )

        # -------------------------------------------------
        # Descargar y mostrar miniatura
        # -------------------------------------------------

        if thumbnail_url:
            response = requests.get(
                thumbnail_url,
                timeout=10
            )
            response.raise_for_status()

            img = Image.open(
                BytesIO(response.content)
            )

            img.thumbnail((400, 250))

            img_tk = ImageTk.PhotoImage(img)

            thumbnail_label.config(
                image=img_tk
            )

            thumbnail_label.image = img_tk

        else:
            thumbnail_label.config(
                image=""
            )

        # -------------------------------------------------
        # Descargar
        # -------------------------------------------------

        estado_label.config(
            text="Descargando..."
        )

        root.update_idletasks()

        with yt_dlp.YoutubeDL(opciones) as ydl:
            ydl.download([url])

        estado_label.config(
            text="Descarga completada correctamente."
        )

        messagebox.showinfo(
            "Éxito",
            "✅ La descarga se ha completado correctamente."
        )

    except Exception as e:

        estado_label.config(
            text="Error durante la descarga."
        )

        messagebox.showerror(
            "Error",
            f"❌ Se produjo un error:\n\n{e}"
        )


# ---------------------------------------------------------
# Ventana principal
# ---------------------------------------------------------

root = tk.Tk()

root.title(
    "Descargador multimedia con Python"
)

root.geometry(
    "700x700"
)

root.minsize(
    600,
    600
)


# ---------------------------------------------------------
# Título
# ---------------------------------------------------------

titulo_principal = tk.Label(
    root,
    text="🎬 Descargador multimedia",
    font=("Arial", 24, "bold")
)

titulo_principal.pack(
    pady=(30, 10)
)


descripcion = tk.Label(
    root,
    text="Introduce una URL para analizar el contenido",
    font=("Arial", 12)
)

descripcion.pack(
    pady=(0, 20)
)


# ---------------------------------------------------------
# Campo URL
# ---------------------------------------------------------

url_label = tk.Label(
    root,
    text="URL:",
    font=("Arial", 11, "bold")
)

url_label.pack(
    pady=(10, 5)
)


url_entry = tk.Entry(
    root,
    width=70,
    font=("Arial", 11)
)

url_entry.pack(
    padx=30,
    pady=5
)


# ---------------------------------------------------------
# Botón
# ---------------------------------------------------------

download_button = tk.Button(
    root,
    text="⬇ Descargar",
    command=descargar_video,
    font=("Arial", 13, "bold"),
    padx=30,
    pady=10,
    cursor="hand2"
)

download_button.pack(
    pady=20
)


# ---------------------------------------------------------
# Información del contenido
# ---------------------------------------------------------

titulo_label = tk.Label(
    root,
    text="",
    font=("Arial", 11),
    wraplength=600
)

titulo_label.pack(
    pady=10
)


# ---------------------------------------------------------
# Miniatura
# ---------------------------------------------------------

thumbnail_label = tk.Label(
    root
)

thumbnail_label.pack(
    pady=20
)


# ---------------------------------------------------------
# Estado
# ---------------------------------------------------------

estado_label = tk.Label(
    root,
    text="Esperando una URL...",
    font=("Arial", 11)
)

estado_label.pack(
    pady=20
)


# ---------------------------------------------------------
# Ejecutar aplicación
# ---------------------------------------------------------

root.mainloop()

Analizando el código

Vamos a dividir el programa en varias partes para entender qué está ocurriendo.

Obtener la URL

url = url_entry.get().strip()

Esta línea obtiene el contenido escrito por el usuario en el campo de texto.

Utilizamos strip() para eliminar espacios innecesarios al principio y al final.

Obtener información sin descargar todavía

with yt_dlp.YoutubeDL() as ydl:
    info = ydl.extract_info(url, download=False)

Esta parte es especialmente interesante.

Estamos pidiendo a yt-dlp que analice la URL pero sin iniciar todavía la descarga.

El resultado es un diccionario de información que puede contener datos como:

  • Título.
  • Duración.
  • Autor o canal.
  • Miniatura.
  • Formatos disponibles.
  • Resoluciones.
  • Códecs.
  • Información adicional proporcionada por la plataforma.

Por ejemplo:

titulo = info.get("title")
thumbnail_url = info.get("thumbnail")

Con esto podemos obtener el título y la URL de la miniatura.

Mostrar la miniatura

La miniatura se descarga utilizando Requests:

response = requests.get(
    thumbnail_url,
    timeout=10
)

Después convertimos los bytes recibidos en una imagen utilizando Pillow:

img = Image.open(
    BytesIO(response.content)
)

Y finalmente la convertimos al formato que Tkinter necesita:

img_tk = ImageTk.PhotoImage(img)

Un detalle importante cuando trabajamos con imágenes en Tkinter es mantener una referencia al objeto. Por eso hacemos:

thumbnail_label.image = img_tk

Si no mantenemos esa referencia, Python puede eliminar el objeto de imagen y la interfaz podría dejar de mostrarlo correctamente.

Seleccionar el formato

La configuración utilizada en el ejemplo es:

opciones = {
    "format": "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]",
    "outtmpl": "%(title)s.%(ext)s",
    "merge_output_format": "mp4",
    "quiet": False,
}

La opción format indica a yt-dlp que intente utilizar un flujo de vídeo MP4 junto con un flujo de audio M4A y, si esa combinación no está disponible, que busque una alternativa MP4.

Por su parte:

"outtmpl": "%(title)s.%(ext)s"

indica que el nombre del archivo se construirá utilizando el título del contenido.

Y:

"merge_output_format": "mp4"

indica que, cuando sea necesario combinar los flujos descargados, el resultado se genere como MP4.

¿Qué ocurre cuando vídeo y audio están separados?

Este es uno de los conceptos más importantes del artículo.

Supongamos que la plataforma ofrece:

Vídeo:
1920 × 1080
H.264

Audio:
AAC
128 kbps

No necesariamente existe un único archivo que contenga ambos flujos.

El programa puede descargar ambos componentes y posteriormente utilizar FFmpeg:

      ┌─────────────┐
      │   VÍDEO     │
      └──────┬──────┘
             │
             │
             ▼
         ┌───────┐
         │       │
         │ FFmpeg│
         │       │
         └───┬───┘
             │
             ▲
             │
      ┌──────┴──────┐
      │    AUDIO    │
      └─────────────┘

             ↓

        ARCHIVO FINAL

Esto explica por qué FFmpeg puede ser una dependencia importante para obtener determinados formatos y calidades.

¿Y si no tenemos FFmpeg?

Aquí tenemos que hacer una distinción importante.

Si el formato elegido ya contiene vídeo y audio en un único archivo compatible, podemos descargarlo sin necesidad de realizar una combinación posterior.

Sin embargo, si queremos utilizar formatos donde el vídeo y el audio se ofrecen por separado, FFmpeg puede ser necesario para obtener un único archivo final con ambos componentes.

Por tanto, no es correcto afirmar que cualquier descarga funcionará perfectamente sin FFmpeg. Dependerá del formato disponible y de lo que solicitemos a yt-dlp.

Versión sencilla sin depender de FFmpeg

Si nuestra prioridad es evitar la combinación de flujos, podemos solicitar un formato que ya contenga vídeo y audio conjuntamente.

Por ejemplo:

opciones = {
    "format": "best[ext=mp4]/best",
    "outtmpl": "%(title)s.%(ext)s",
}

Esta configuración intenta seleccionar un formato progresivo que ya contenga ambos componentes.

La contrapartida es que podemos perder calidad o resolución respecto a los formatos separados disponibles en la plataforma.

Es decir:

Configuración Ventaja Desventaja
Vídeo + audio separados Mayor variedad y calidad Puede necesitar FFmpeg
Formato combinado Más sencillo Puede tener menor calidad

Mejorando la aplicación

El ejemplo anterior es intencionadamente sencillo, pero a partir de aquí podemos convertirlo en una aplicación bastante más completa.

Por ejemplo, podríamos añadir:

  • Selector de carpeta de destino.
  • Barra de progreso.
  • Velocidad de descarga.
  • Tiempo restante estimado.
  • Tamaño del archivo.
  • Selección de resolución.
  • Selección de formato.
  • Descarga de subtítulos cuando estén disponibles.
  • Descarga de miniaturas.
  • Historial de descargas.
  • Cancelación de una descarga.
  • Registro de errores.
  • Selección de una carpeta de salida.

El siguiente paso: utilizar callbacks de progreso

Una aplicación gráfica no debería quedarse aparentemente congelada mientras se está realizando una descarga.

Una mejora interesante consiste en utilizar los hooks de progreso de yt-dlp para actualizar una barra de progreso en Tkinter.

La idea sería recibir información periódicamente:

def progreso(d):
    if d["status"] == "downloading":
        porcentaje = d.get("_percent_str", "")
        velocidad = d.get("_speed_str", "")
        eta = d.get("_eta")

        print(
            f"{porcentaje} | "
            f"{velocidad} | "
            f"ETA: {eta}"
        )

Después podemos utilizar esa información para actualizar nuestra interfaz.

Un detalle importante: no bloquear la interfaz

Otro aspecto que merece atención es que una descarga puede tardar bastante tiempo.

Si ejecutamos directamente una operación pesada desde el hilo principal de Tkinter, la ventana puede quedarse congelada mientras se descarga el archivo.

Por eso, en una aplicación más avanzada sería recomendable utilizar un hilo de trabajo y dejar que Tkinter se encargue del hilo principal de la interfaz.

La arquitectura podría ser:

┌─────────────────────────┐
│       Tkinter GUI       │
│                         │
│ URL / Botones / Estado  │
└────────────┬────────────┘
             │
             │ inicia tarea
             ▼
┌─────────────────────────┐
│     Hilo de trabajo     │
│                         │
│        yt-dlp           │
└────────────┬────────────┘
             │
             ▼
┌─────────────────────────┐
│         FFmpeg          │
│   si resulta necesario  │
└─────────────────────────┘

De esta forma podemos mantener la interfaz responsive mientras la descarga continúa en segundo plano.

Errores habituales

“No module named yt_dlp”

Significa que la librería no está instalada en el entorno Python que estamos utilizando.

pip install -U yt-dlp

“No module named PIL”

Tenemos que instalar Pillow:

pip install pillow

“ffmpeg not found”

El sistema no encuentra el ejecutable de FFmpeg.

Debemos instalar FFmpeg y asegurarnos de que el directorio que contiene ffmpeg esté incluido en el PATH.

La descarga funciona pero no hay sonido

Puede ocurrir cuando se ha descargado únicamente el flujo de vídeo.

En ese caso debemos utilizar una selección de formato que incluya audio o permitir que yt-dlp combine ambos flujos utilizando FFmpeg.

La aplicación muestra un error de la plataforma

Las plataformas cambian periódicamente sus sistemas internos. Por ello, una herramienta como yt-dlp necesita actualizarse con frecuencia.

Podemos actualizarla con:

pip install -U yt-dlp

Esto es especialmente importante cuando una plataforma ha cambiado su forma de servir contenido y una versión antigua de la herramienta deja de funcionar correctamente.

¿Qué hemos aprendido?

Aunque el programa parece sencillo, detrás tenemos varios conceptos interesantes de programación.

  • Creación de interfaces gráficas con Tkinter.
  • Uso de librerías externas mediante pip.
  • Petición de recursos mediante HTTP.
  • Procesamiento de imágenes con Pillow.
  • Obtención de metadatos.
  • Descarga y procesamiento multimedia.
  • Integración de herramientas externas como FFmpeg.
  • Manejo de excepciones.
  • Arquitectura basada en tareas en segundo plano.

Conclusión

Este pequeño proyecto demuestra perfectamente cómo podemos utilizar Python para construir una aplicación de escritorio real combinando diferentes tecnologías.

Tkinter se encarga de la interfaz, yt-dlp de analizar y gestionar las descargas, Requests nos permite obtener la miniatura y Pillow se encarga de procesarla.

Cuando necesitamos combinar vídeo y audio, entra en juego FFmpeg.

Lo interesante no es únicamente descargar un vídeo. El verdadero valor educativo del proyecto está en comprender cómo podemos conectar diferentes herramientas y librerías para crear una aplicación completa.

Y a partir de este ejemplo podemos seguir evolucionándola hasta convertirla en una aplicación mucho más sofisticada: selección de calidad, barra de progreso, gestión de varias descargas, historial, selección de carpeta, cancelación de tareas y muchas otras funcionalidades.

En definitiva, un proyecto pequeño, pero con bastantes conceptos interesantes de Python escondidos detrás.


Nota: Las plataformas de vídeo pueden modificar sus sistemas, formatos y condiciones de servicio. Si el código deja de funcionar con una determinada plataforma, lo primero que debemos comprobar es que estamos utilizando una versión actualizada de las dependencias y que el contenido puede descargarse legítimamente.

Visitas: 6

Website |  + posts

Automation & Data Specialist | Web Development | Data Processing & Integration

Deja un comentario