Experimento: Creando un nuevo effecto de imagen para el secuenciador de vídeo de Blender (VSE) con código fuente

Publicado el 13 marzo 2017 por Gaspar Fernández Moreno @gaspar_fm


Lo que pongo aquí es solo un experimento. Como dije en mi anterior post, me encanta Blender para hacer montajes de vídeo y para mí, es el mejor editor de vídeo disponible para GNU/Linux. Aunque tiene un pequeño defecto, bueno, muy grande... apenas tiene efectos para los vídeos, por lo que si quieres algún efecto que no sea un crossfade, una corrección de color, o un blur, vas a tener que hacerlo de otra forma y eso implica exportar vídeo y luego importarlo cuando esté hecho. Con el gran inconveniente de que si el efecto no ha quedado exactamente como quieres, si de verdad te quedan ganas para modificarlo tienes que repetir el proceso.

Me gustan los efectos, y quiero hacer pruebas, porque no había mirado mucho el código fuente de Blender, me parece una bestia, tiene muchas cosas, y partes que se han quedado un poco viejas, como es el VSE (Video Sequence Editor), el módulo de Blender que más utilizo. Así que, ¿por qué no empezar a crear algunos efectos?

Construyendo Blender

Lo malo de todo esto, es que al contrario que con programas privativos, Blender no soporta plugins para efectos. Lo hizo en el pasado, pero por razones que no llego a comprender (supongo que relacionadas con el mantenimiento, con que nadie quiso hacerse cargo del módulo y con que tal vez les reportaban muchos fallos sobre efectos cuando el fallo no estaba en Blender, sino en el que realizaba el propio efecto). Lo malo, es que si queremos incluir un nuevo efecto en Blender, debemos recompilar nuestra versión de Blender. No tarda mucho, pero tiene algunas dependencias algo pesadas. Una vez que lo has hecho, las siguientes compilaciones no tardarán mucho, ya que sólo compila el código nuevo (lo que hacemos nosotros).

Bueno, he dicho que Blender no tiene API... ¡claro que tiene! Lo malo es que es una API en Python, y personalmente (que lo he probado), efectos que renderizan a 25fps hechos en C, van a 0.3fps hechos en Python. Es cierto que no he utilizado ninguna de las fantásticas bibliotecas de imagen digital disponibles para Python, pero de todas formas hay que hacer que el vídeo se exporte a Python, y sólo lo he conseguido hacer con alguna guarrada que me da vergüenza mencionar aquí...

Lo primero es decargarte el código fuente de la página principal o del git oficial de Blender (git://git.blender.org/blender.git). Si vas a compartir lo que haces con la comunidad, te recomiendo utilizar git para poder enviar parches y demás.

El caso es que si queremos compilar Blender, necesitamos un entorno de compilación GCC, y las siguientes bibliotecas con sus cabeceras para programación: python, numpy, libx11, libjpeg, libpng, boost, ffmpeg, libopencolorio, libilm, libopenexr y algunas más. Las encuentras todas en blender/build_files/build_environment/install_deps.sh

Antes de meterme con código

Si echas un vistazo por encima al código parece muchísimo, ¡ pero no es tanto ! Como es un programa tan grande, en muchos archivos, para que sepamos qué tenemos que tocar. Como no puedo decir la línea exacta ya que este programa se modifica a menudo he decidido poner unas cuantas líneas, o funciones completas a modo de referencia. Cuando lo que nos afecta es lo referente a CUSTOM_EFFECT.

¡Empezamos con la aventura!

Botón en la interfaz

Lo primero, es que desde la interfaz podamos añadir un nuevo efecto. Que cuando estemos en el secuenciador, pulsando Shift+A -> Effect aparezca nuestro efecto en el menú, para eso, tenemos que editar

Para eso, editamos el archivo source/blender/makesdna/DNA_sequence_types.h y añadimos una nueva constante con nuestro effecto. Mi efecto se llamará CUSTOMEFFECT, además, incrementamos SEQ_TYPE_MAX. En mi caso, la cosa queda más o menos así (cerca de la línea 500, en la versión 2.78c):

Nuestro efecto, tendrá el índice 42, aunque no nos importa mucho la posición que ocupe. Ahora vamos al archivo source/blender/editors/space_sequencer/sequencer_edit.c, metemos el mismo efecto:

Dentro de cada elemento del array tendremos:

  • Índice, para lo que cogemos un valor de nuestro enum de antes. Será de uso interno,
  • String clave, será una cadena en clave, que manejaremos desde Python para referirnos al efecto.
  • El 0 siguiente indica que no hay icono asociado, por ahora no tenemos iconos
  • Luego el nombre del efecto, para mostrarlo
  • Por último, la descripción del efecto

Ahora en el archivo source/blender/makesrna/intern/rna_sequencer_api.c:

Seguimos con la interfaz de Python. Para que el elemento aparezca en el menú debemos incluir lo siguiente en el archivo release/scripts/startup/bl_ui/space_sequencer.py (añadir la última línea, más o menos alrededor de la línea 380, blender 2.78c):

Ahora definimos el color del elemento una vez lo pongamos en la línea de tiempo. Para ello en source/blender/editors/space_sequencer/sequencer_draw.c vamos a la función color3ubv_from_seq() donde, dentro del case, dejamos algo como esto (es aproximado, podemos definirle el color si queremos, pero yo me he basado en el color de otros elementos parecidos):

Si compilamos y ejecutamos Blender, seguramente nuestro efecto aparezca y lo podremos añadir, pero no hará nada, sólo soltar errores por la consola.

Definiendo algunas propiedades

Debemos definir antes de continuar algunas propiedades básicas del strip. En source/blender/makesrna/intern/rna_sequencer.c haremos varias modificaciones. Primero, en la función rna_def_sequence() (alrededor de la línea 1380), volvemos a meter nuestro efecto:

Luego en la línea 2390 más o menos modificamos la estructura def_effects añadiendo la definición de nuestro nuevo efecto:

Donde, el 1 que acompaña la declaración del efecto es el número de entradas que necesitamos. Al aplicarse sobre otro strip, necesitamos un 1 aquí. Si fuera un generador, sería 0 y una transición tendría 2.

Ahora, junto con los demás efectos (línea 2100 más o menos), creamos la función rna_def_customeffect donde definiremos los parámetros que se podrán tocar de nuestro efecto, que luego tendremos que vincular con Python, que no se nos olvide:

Y añadimos nuestro efecto a la función rna_sequence_refine():

Por último en source/blender/makesrna/RNA_access.h metemos esta línea, más o menos en la 280:

Insertamos las propiedades de nuestro efecto

Por ahora sólo estoy metiendo una, y no hace nada, sólo estar ahí, pero me sirve como plantilla para futuros efectos. Antes de que se nos olvide, vamos a añadirla desde la interfaz de Python. En el archivo en el archivo release/scripts/startup/bl_ui/space_sequencer.py en la clase CLASS_PT_EFFECT(), al final del todo añadimos nuestro efecto:

Ahora declaramos las propiedades de nuestro efecto. Para ello en el archivo source/blender/makesdna/DNA_sequence_types.h insertamos lo sigueinte (cerca de la 290, junto con los demás efectos, ya los veréis):

Mi propiedad se llamaba property, porque soy muy original, pero aquí pondremos las que necesitemos. Podremos poner arrays de char para cadenas de caracteres, char, short para flags, enteros y float, por ejemplo, los colores van en float. Podríamos poner otros tipos de variable, pero no he probado... no soy tan valiente.

Para que las propiedades de nuestro efecto se guarden cuando salvamos el archivo .blend hacemos, editamos source/blender/blenloader/intern/writefile.c añadiendo la estructura de nuestro efecto, alrededor de la límea 2660:

Definiciones de nuestro efecto

Ya va quedando menos, tenemos esto casi configurado, sólo quedan algunos callbacks con las llamadas que hacen los propios filtros, vamos, ahora es cuando nuestro filtro hace algo, aunque es necesario definirlo para que el programa no explote:

En el archivo source/blender/blenkernel/intern/seqeffects.c encontramos el código de estos efectos. Lo primero será introducir las definiciones de nuestro efecto en la función get_sequence_effect_impl() en la línea 3315 o así, buscamos los efectos del switch y ponemos el nuestro al final:

Aquí definiremos:

  • rval.init : función que inicializa nuestro efecto
  • rval.num_inputs : número de strips de entrada que necesita nuestro strip. Los efectos necesitan estas sobre un strip, las transiciones sobre dos, los generadores no necesitan strips.
  • rval.free : Qué hacemos cuando se quita nuestro efecto (para liberar memoria)
  • rval.copy : Qué hacer cuando copian nuestro efecto.
  • rval.early_out : A veces, podemos saber si es necesario o no aplicar el efecto antes de ponernos a procesar. Aquí veremos si procesamos, si devolvemos el valor de la entrada, o no devolvemos nada
  • rval.execute : NUESTRO EFECTO !!!
  • rval.supports_mask : ¿Soporta máscaras? (es un booleano, no un callback
  • rval.load : Qué hacer cuando se acaba de cargar el efecto?
  • rval.init_execution : Este callback se ejecutará antes de hacer el render de nuestro efecto.
  • rva.multithreaded : El efecto soporta threads. Y en lugar de llamar a rval.execute se llamará a rval.execute_slice, por lo que si hacemos nuestro efecto multihilo tendremos que hacerlo independiente de las líneas de origen y fin que nos pasen (Blender ya se encarga de crear hilos y de todo lo demás, nosotros sólo tenemos que definir lo que hace cada uno).

Encontraremos toda la información en source/blender/blenkernel/BKE_sequencer.h

Como todo lo que hemos puesto son callbacks, tendremos que definirlos, voy a poner todo el código seguido, pero si queréis tenerlo ordenado buscad funciones parecidas de otros efectos y poned el vuestro por ahí:

¡Y listo! Si compilamos Blender ya tenemos nuestro efecto.

En la función do_custom_effect_effect(), tenemos los siguientes parámetros para la entrada:

  • const SeqRenderData *context : El contexto, aquí tenemos acceso a muchísimas cosas, demasiado para enumerarlo aquí, pero accedemos a la estructura Main que contiene casi todo, podemos acceder a la escena, elementos guardados y miles de cosas más.
  • Sequence *seq : Toda la información del strip actual
  • float cfra: Fotograma actual, puede tener un error de +-0.5 frames.
  • float facf0: Cuando estamos operando con contenido entrelazado y estamos animando un efecto, qué porcentaje de efecto recae sobre el campo 0?
  • float facf1: lo mismo pero para el campo 1.
  • ImBuf *ibuf1, ImBuf *ibuf2, ImBuf *ibuf3: Son las imágenes asociadas al efecto, dependiendo del efecto, habrá varias imágenes implicadas en él.

Y... como extra, si no estamos utilizando una variable, pero la queremos dejar ahí, para que el compilador no se queje podemos envolverla con la macro UNUSED()

La comunidad Blender

En https://developer.blender.org/ encontramos un sistema en el que se ponen en común bugs y pequeñas características (si quieres colaborar en algo grande, tienes que contactar con ellos). Y actualmente el secuenciador lo tienen un poco abandonado, de todas formas, hay una parte importante de la comunidad que sigue ahí, enviando parches y mejoras.

También podría interesarte...