Apuntes mientras aprendo sobre software y computadoras.

programación

Cortar automáticamente la claqueta de un video con Python

En este apunte práctico voy a estar investigando una forma de cortar automáticamente la claqueta de un video con Python.

Puede que esto no sea demasiado práctico realmente, pero investigar un poco el asunto va a ayudarnos para aprender varias cosas.

Y solo a modo de auto publicidad, ya en otro momento utilicé FFmpeg para editar automáticamente un video por medio de los timecodes.

¿Cuál es la idea?

Durante la grabación de un video, por ejemplo para una película o un videoclip, se marca el inicio de cada plano utilizando una claqueta.

Es esta una buena oportunidad para agregar…

¿Qué es una claqueta?

Una claqueta es básicamente ese pedazo de madera (o plástico) en el que se anotan varios datos del plano (número de toma, escena…) y que se cierra con un golpe antes de empezar lo que se esta grabando.

Se trata de un dispositivo muy famoso, no me voy a dedicar a describirlo. Lo importante es su función: marcar un punto en el que luego va a poder sincronizarse el audio de la toma.

La claqueta no solamente entrega información visual. Digamos que se esta utilizando una cámara (que cuenta con su propio micrófono) y una grabadora de sonido independiente para capturar el audio con mayor calidad.

Intentar sincronizar estas dos cosas sería muy difícil sin una ayuda. Cuando la claqueta se golpea, genera una marca muy fácil de reconocer en la onda de audio. Utilizando esta marca se puede ordenar el material, para que la imagen y el sonido vayan al mismo tiempo durante el montaje audiovisual.

Entonces ahora si, vuelvo con…

¿Cuál es la idea? (Toma dos)

Bien, ya conozco que la claqueta deja una marca sonora muy significativa en la onda de audio.

Esta marca se ve realmente como una montaña, un pico que satura el micrófono y que para los efectos de este texto voy a imaginar al rededor de -1 decibelios.

Imaginemos que tenemos el video en la linea de tiempo del editor.

Por una parte todo lo que se encuentra en el archivo desde que se encendió la cámara hasta que se golpeo la claqueta es material que no corresponde a la escena. Y lo que sigue a partir de ese punto hasta que se corta el plano, es parte de lo que se va a terminar viendo en pantalla.

La idea es crear un programa que reconozca automáticamente esta marca en el audio, y que nos diga el fotograma más aproximado al que se encuentra este sonido.

Luego voy a poder usar esta señalización para poder borrar todo lo que se encuentra antes de la claqueta, y por añadidura voy a poder quedarme con lo que necesito del plano para continuar editando.

La principal ventaja de la idea:

La principal ventaja de todo esto es que puede automatizarse. Digamos que cuento con muchos planos de video, y a todos esos archivos tengo que retirarles los primeros segundos.

Utilizando este código voy a poder remover todas las partes que contienen la claqueta de una manera muy rápida.

Y por lo demás, incluso si no hago ningún corte, voy a conocer el exacto fotograma en el que se encuentra la claqueta para poder encontrarlo más fácil si hago una planilla de datos. Puedo incluso usar esta técnica para obtener el timecode de la claqueta, y conocer por tiempo el lugar que tengo que hacer la sincronización.

La mayor desventaja:

Aunque no es necesario usar este código para hacer cortes, si puede usarse para cortar el material.

Lo bueno es que el resultado va a ser un nuevo video, con la misma calidad del original.

Sin embargo hay que recordar que la claqueta se utiliza con un propósito muy bien definido. Tratar de removería automáticamente puede dificultar en el futuro la sincronización de la imagen con el audio.

Por supuesto, también se podría extender la funcionalidad del código para cortar los archivos originales de audio y también los archivos de video. En una situación ideal las dos cosas se cortan en el exacto momento, y luego pueden unidos sin problemas al hacer el montaje.

Pero eso es solamente en un caso ideal, también puede resultar que haya pequeñas discrepancias en el reconocimiento de la marca. Si no se hace con cuidado se puede terminar perdiendo más tiempo del que podríamos haber ahorrado.

El código

Comparto primero el código, por si te interesa empezar a usarlo o a investigarlo. Luego voy a tratar de explicarlo, al menos superficialmente.

import os
import numpy as np
import librosa
from moviepy.editor import VideoFileClip
import subprocess


def process_video(video_path):
    # carga el video
    video = VideoFileClip(video_path)

    # Extrae el audio del video
    audio_path = 'temp_audio.wav'
    video.audio.write_audiofile(audio_path)

    # Carga el audio usando librosa
    y, sr = librosa.load(audio_path, sr=None)

    # Encuentra el primer frame más cercano a -1 dB
    frame_threshold = -1  # en dB
    frame_rate = video.fps
    first_frame = None

    # Calcula el nivel de sonido en dB
    sound_levels = 20 * np.log10(np.abs(y) / np.max(np.abs(y)))

    # Encuentra el indice más cercano del frame encontrado
    closest_index = np.argmin(np.abs(sound_levels - frame_threshold))

    first_frame = int(closest_index * frame_rate / sr)

    # Remueve el archivo de audio que se generó antes luego de encontrar el first_frame
    os.remove(audio_path)

    return first_frame


results = []

# Ejecutar la función anterior en carpeta especificada, modificar el "folder_path" por el camino absoluto al directorio

folder_path = '/directorio/buscado'
video_files = [f for f in os.listdir(folder_path) if f.endswith('.mp4')]

for video_file in video_files:
    video_path = os.path.join(folder_path, video_file)
    first_frame = process_video(video_path)
    results.append((video_file, first_frame))

# Se imprimen en la terminal los resultados de la lista "Results"
print("Results:", results)

# Función para cortar cada video en la carpeta con ffmpeg


def cut_video_from_frame(video_file, start_frame, output_folder, fps):
    input_path = os.path.join(folder_path, video_file)
    output_path = os.path.join(output_folder, f"cut_{video_file}")
    start_time = start_frame / fps
# El corte es en base al frame encontrado antes.
    command = f"ffmpeg -i {input_path} -ss {start_time} -c copy {output_path}"
    subprocess.run(command, shell=True, check=True)


# Es posible especificar otro directorio de salida, modificando la variable "output_folder"
output_folder = '/especificar/directorio/salida'

# Se ejecuta la nueva función "cut_video_from_frame"
for result in results:
    video_file, first_frame = result
    video_path = os.path.join(folder_path, video_file)
    video = VideoFileClip(video_path)
    cut_video_from_frame(video_file, first_frame, output_folder, video.fps)

# Se avisa en la terminal que el programa terminó
print("Programa terminado")

Cómo funciona:

El código tiene básicamente cuatro partes.

Primero se llaman a las librerías necesarias:

import os
import numpy as np
import librosa
from moviepy.editor import VideoFileClip
import subprocess

Antes de utilizar el código es necesario instalarlas en el equipo, para no recibir ningún tipo de error al ejecutarlo.

En la segunda parte, creamos una función que va a extraer el audio del video.

def process_video(video_path):
    # carga el video
    video = VideoFileClip(video_path)

    # Extrae el audio del video
    audio_path = 'temp_audio.wav'
    video.audio.write_audiofile(audio_path)

    # Carga el audio usando librosa
    y, sr = librosa.load(audio_path, sr=None)

    # Encuentra el primer frame más cercano a -1 dB
    frame_threshold = -1  # en dB
    frame_rate = video.fps
    first_frame = None

    # Calcula el nivel de sonido en dB
    sound_levels = 20 * np.log10(np.abs(y) / np.max(np.abs(y)))

    # Encuentra el indice más cercano del frame encontrado
    closest_index = np.argmin(np.abs(sound_levels - frame_threshold))

    first_frame = int(closest_index * frame_rate / sr)

    # Remueve el archivo de audio que se generó antes luego de encontrar el first_frame
    os.remove(audio_path)

    return first_frame

Luego de extraer el audio, le ponemos un limite a la búsqueda (frame_threshold = -1). Elegí el -1 db porque el golpe de la claqueta puede llegar hasta ahí o incluso superarlo. Al ser un golpe tan marcado, se diferencia mucho con el ruido de fondo de la escena.

La función guarda el resultado en una lista (results = []) y lo guarda en forma de tupla, primero el nombre del video y luego el nombre del primer frame (cuadro) más cercano al pico en la waveform.

Lo que sigue en la tercera parte es ejecutar la función anterior, que repasa todos los archivos de la carpeta donde estamos utilizando el programa.

folder_path = '/directorio/buscado'
video_files = [f for f in os.listdir(folder_path) if f.endswith('.mp4')]

for video_file in video_files:
    video_path = os.path.join(folder_path, video_file)
    first_frame = process_video(video_path)
    results.append((video_file, first_frame))

En este caso revisa todos los archivos terminados con la extensión “.mp4”, pero pueden revisarse otro tipo de documentos al mismo tiempo. También se podría cambiar el archivo de audio a otro formato, de ser necesario.

Y ya en la cuarta parte, en el final, creamos una nueva función para cortar el video con FFmpeg.

def cut_video_from_frame(video_file, start_frame, output_folder, fps):
    input_path = os.path.join(folder_path, video_file)
    output_path = os.path.join(output_folder, f"cut_{video_file}")
    start_time = start_frame / fps
# El corte es en base al frame encontrado antes.
    command = f"ffmpeg -i {input_path} -ss {start_time} -c copy {output_path}"
    subprocess.run(command, shell=True, check=True)

Utilizando un comando de Ffmpeg, lo que resulta es que combinamos el nombre del video con el frame con la marca de la claqueta. Gracias a esto podemos cortar el audiovisual y obtener un nuevo archivo, que solo contiene el material que necesitamos para editar.

Esto es, con mucha suerte, porque puede ser que Librosa (la librería que el programa usa para analizar el audio) no encuentre exactamente el fotograma que necesitamos. Las razones pueden ser muchas, desde la calidad del material hasta que el golpe de la claqueta es muy suave y no llega a los -1 dB por ejemplo.

Conclusión

Con esto tenemos al menos una forma de cortar automáticamente la claqueta de un video con Python.

La base de este programa puede ser expandida para conseguir hacer otras cosas. Una vez que conocemos el cuadro o el timecode que buscamos, podemos usar esa información para generar otras cosas con el material.

Y no podemos olvidar que utilizando inteligencia artificial el código puede ser modificado o expandido en nuevas direcciones.

De cualquier manera, me gustaría conocer tu opinión o consejos. Y si encontrás algún error el material, por favor avisame para que pueda repararlo.

La seguimos en el próximo apunte.

Recursos

Manual de la librería Librosa (en inglés)
Documentación de MoviePy (en inglés)

Leave a Reply