Weblogs Código

Picando Código

Guetzli – compresor JPEG software libre de Google

marzo 29, 2017 01:00

Hace unos días, Google publicó un nuevo compresor de imágenes JPEG llamado Guetzli (galletita en suizo alemán). Es software libre publicado bajo la licencia Apache 2.0. En el blog, anunciaban resultados de archivos 35% más chicos que con métodos actuales, permitiendo usarlos en la web para carga más rápido y menos uso de datos.

Para instalarlo, podemos obtener el código desde GitHub, ya sea clonándolo o desde un archivo. En sistemas GNU/Linux (POSIX) tenemos que instalar libpng ( libpng-dev en Debian y Ubuntu, libpng-devel en Fedora y libpng en ArchLinux) y ejecutar make en el directorio de guetzli. Esto genera un ejecutable guetzli en el directorio bin/Release. Podemos agregarlo al path para usarlo cómodamente desde la terminal. También hay instrucciones para Windows y macOS.

Algunas notas importantes de la documentación de Guetzli:

  • Usa mucha memoria. Se deben proveer al menos 300MB de memoria por 1MPix de la imagen de entrada.
  • Usa mucho tiempo de procesamiento. Se debe contar en usar al menos 1 minuto de CPU por 1MPix de imagen de entrada.
  • Asume que la entrada está en el perfil sRGB (TIL) con una gamma de 2.2. Guetzli ignorará cualquier metadato de perfil de color de la imagen.

El binario lee una imagen JPEG o PNG y crea una imagen JPEG optimizada:


guetzli [--quality Q] [--verbose] original.png optimizada.jpg
guetzli [--quality Q] [--verbose] original.jpg optimizada.jpg

Para probarlo saqué una foto de 16.1MP con la cámara de mi teléfono. Al primer intento fallé porque no daba con la memoria suficiente tal como dice la documentación:


Unhandled exception. Most likely insufficient memory available.
Make sure that there is 300MB/MPix of memory available.

16.1 por 300 son 4830MB de memoria ram que debería tener disponible. Así que fui cerrando aplicaciones hasta que free -m diera con los números. Increíble cómo consumimos memoria estos días, pero al ver que tengo más de 100 pestañas abiertas en Firefox, editor de texto, cliente de correo, cliente de chat, un reproductor de música, etc… ejecutándose al mismo tiempo, tiene sentido.

La imagen original es esta (clic para ver la original):

Imagen Original Guetzli

Imagen Original Guetzli

Es la imagen cruda sin comprimir, directo de la cámara, con un peso de 2.9MB. Por lo que comentaba la documentación, la optimización llevaría unos 16 minutos aproximadamente (1 minuto por 1MPix), así que le conté el tiempo y me fui a preparar un café o algo:

$ time guetzli imagen.jpg imagen-optimizada.jpg

real 15m41.561s
user 14m58.576s
sys 0m42.356s

Prácticamente 16 minutos entonces. Y el resultado fue bastante bueno, resultando en esta otra imagen:

Imagen Guetzli Optimizada

Imagen Guetzli Optimizada

El tamaño de esta nueva imagen es de 1.4MB. Mirando muy de cerca las imágenes, realmente no noto una diferencia sustancial en la calidad, pero el tamaño se redujo bastante. Para comparar, optimicé la misma imagen con jpegoptim, un compresor de JPEG que se puede instalar desde los repos de Debian. Para hacer la cosa pareja, comprimí el archivo a la misma cantidad de MB que el creado por guetzli, ya que el comando lo permite:

$ jpegoptim --size=1433.6k imagen.jpg
imagen.jpg 3006x5344 24bit N Exif  [OK] 2854419 --> 1426035 bytes (50.04%), optimized.`

En este resultado sí noté una diferencia (haciendo mucho zoom):

Imagen jpegoptim

Imagen jpegoptim

A modo de conclusión, es una buena herramienta de Google y estas pruebas súper básicas y para nada profesionales confirman los niveles de compresión y la poca pérdida de calidad que promete Guetzli. Para un caso como este blog, donde la calidad de las imágenes no es tan importante, probablemente no lo use por un tema de tiempos más que nada. Pero imagino que para un blog de fotografía profesional, o donde las imágenes tengan un mayor protagonismo, sería útil. Ni que hablar para sitios con un tráfico importante, cada optimización sirve, y tener un script que optimice cada imagen sería beneficioso.

Pueden ver el repositorio de código en GitHub o el anuncio original de Google en su blog de Investigación.

» Leer más, comentarios, etc...

Variable not found

Adivina, adivinanza: ¿por qué no compila este código?

marzo 29, 2017 09:00

Desarrollador volviéndose loco
Imagen original de Pixabay.
La llegada del próximo April Fool's day, algo parecido a lo que por aquí conocemos como el día de los Inocentes, me ha recordado una curiosidad con la que me topé hace algún tiempo que, si tenéis un poco de maldad reprimida, podéis utilizar para gastar una broma a algún compañero desarrollador y echar unas risas.

La historia consiste en abusar del amplio conjunto de caracteres soportado por UTF, sustituyendo el punto y coma de finalización de una línea de código (";") por el símbolo de interrogación griego (";", Unicode 037E), indistinguibles a simple vista, como en la siguiente línea:
public void HelloWorld()
{
Console.WriteLine("Hello world!");
}
Esto dará lugar a un extraño error sintáctico compilación que, situado en un lugar estratégico, hará que más de uno tenga que devanarse los sesos para descubrir dónde está el problema:
1>------ Build started: Project: TestApp, Configuration: Debug Any CPU ------
1>C:\Projects\TestApp\Program.cs(9,46,9,47): error CS1002: ; expected
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

Hey, pero por si luego quieren matar a alguien, os adjunto un link a la fuente original, que mía no es la ocurrencia ;D
Replace a semicolon with a greek question mark

Publicado en Variable not found.

» Leer más, comentarios, etc...

Adrianistán

Rust en 5 minutos – #PicnicCode2017

marzo 28, 2017 09:34

El pasado 17 de marzo fue el Picnic Code en la Universidad de Valladolid. En el evento, organizado por el GUI y Cylicon Valley, tuve el honor de dar una Lightning Talk. Se trata de una charla de 5 minutos muy rápidos para exponer una idea. Mi Lightning Talk titulada Rust en 5 minutos iba dirigida a enseñar, sin entrar en muchos detalles, aquellas características que hacen de Rust un lenguaje seguro. No estaba nervioso hasta que subí al escenario… ¡y entonces ya empecé a estarlo! Hay algunos fallos frutos de los nervios y las diapositivas… bueno, podían haber funcionado mejor.

En cualquier caso, estáis invitados a ver Rust en 5 minutos.

La entrada Rust en 5 minutos – #PicnicCode2017 aparece primero en Blog - Adrianistan.eu.

» Leer más, comentarios, etc...

Poesía Binaria

Aplicaciones web sin servidor, o casi (arquitecturas serverless)

marzo 27, 2017 06:06


¿Cómo es esto posible? Si tenemos una cosa clara en el mundo de Internet es que si queremos tener en pie un servicio, éste debe estar alojado en una máquina que esté conectada a Internet, con energía y que nos asegure en cierta forma que está levantada. Estos servidores pueden ser de diversos tipos según las necesidades de la aplicación: web, base de datos, autentificación, tareas, logs, notificaciones, etc. Así que, ¿qué es esto de aplicaciones sin servidor? ¿o serverless?

¿Sin servidor? Serverless?

En realidad, este término no tiene que ver con que exista físicamente un servidor o no, sino con el hecho de delegar su administración a un tercero. Al mismo tiempo, nosotros no pedimos un servidor, pedimos un servicio Por lo que este término se aplica siempre que no tengamos que administrar el servidor que ejecuta un servicio, es más, en muchos casos, no sabemos sus características ni tenemos acceso a él más allá del servicio que ejecuta. Es más, el proveedor nos da un punto de entrada para poder llamar al servicio, y lo que haya por detrás será todo un misterio. Ellos nos garantizan el servicio encargándose de todo.

Esto pueden ser aplicaciones en la nube (ay la nube, ese gran desconocido, literalmente), como bases de datos, servidores de autentificación, incluso APIs para nuestras aplicaciones. Esto se utiliza sobre todo para aplicaciones móviles, en las que disponemos de un frontend y por detrás se hacen llamadas a servicios web que a su vez pueden hacer llamadas a otros servicios, todos remotos. Esto se suele llamar BaaS (Backend as a Service).

Por otro lado, algo que está muy de moda últimamente es la ejecución de funciones como servicio o FaaS (Functions as a Service), de hecho casi siempre que escuchemos algo sobre Serverless se referirán a esto y no es más que el alojamiento y ejecución de código por un tercero en contenedores de cómputo descentralizados y administrados por un tercero. Estos terceros suelen ser IBM, Google, Microsoft, Amazon, etc En definitiva, nuestro código no sabemos dónde se aloja ni dónde se ejecuta, sólo sabemos cómo llamarlo y cómo ver la respuesta de la ejecución. Es más, dependiendo del uso que hagamos de esta tecnología puede que haya muchas máquinas ejecutando nuestro código a la vez, sirviendo respuestas para nuestras aplicaciones… y todo sin tener que montar ni contratar un servidor.

Como hemos podido ver, estas soluciones son administradas, es decir, tampoco necesitamos que nadie se tome molestas para encargarse de la seguridad del servidor, balanceo de carga, actualizaciones de software, direccionamiento, almacenamiento, ni nada. Sólo debemos encargarnos de subir nuestro código y listo. Pero claro, eso de Serverless, seguro que se le ocurrió a uno de marketing porque suena muy bien. Está claro que servidor sí que hay, pero bueno, no tenemos que prestarle mucha atención.

¿Para qué quiero yo esto?

El objetivo, además de que es algo muy moderno, por un lado es que se reducen costes. Aunque hay que mirarlo con lupa y estudiar casos concretos. Es decir, para una empresa, el tiempo de un empleado supone dinero y el tiempo que el empleado invierte en tareas de mantenimiento de un servidor, significa una inversión económica. Además, tener una máquina contratada para realizar un servicio determinado es también una inversión económica, generalmente en forma de alquiler mensual y esto cubre momentos en los que se está utilizando el servicio y momentos en los que no hay nada utilizándose. Y normalmente, las empresas que facilitan este tipo de servicios cobran por el tiempo de uso de los servicios.

Por otro lado, nos garantiza la escalabilidad de los servicios (escalabilidad? Introducción a la escalabilidad de aplicaciones web. Técnicas, opciones, seguridad y consejos.). Ya que, en el caso de FaaS, sólo ejecutamos funciones, estas funciones podrán conectar con bases de datos y utilizar servicios externos, pero gozan de independencia de la máquina donde se ejecutan por lo que podremos llamar al servicio las veces que necesitemos, simultáneamente y no debemos tener ningún problema en la disponibilidad (no es plan de hacermos un DDOS tampoco). En el caso de BaaS también estamos llamando a servicios independientes por lo que el escalado no es un problema.
El caso es que el tema del escalado, que bien nos puede suponer un quebradero de cabeza de esta forma es transparente a nosotros y lo harán los servicios de manera natural.

Paranoico de mí

Pero claro, estamos dependiendo de un tercero, o de varios terceros, no tenemos control sobre la infraestructura, en muchos casos ni podemos visitarla, internamente no sabemos cómo funciona (más allá de lo que nos cuentan), puede que hasta desconozcamos la versión exacta del software que ejecutan estos contenedores. Aunque es verdad que las empresas muestran gran transparencia en sus operaciones no es lo mismo que si lo hicieras tú.
Siguiendo con la dependencia, la supervivencia de nuestras aplicaciones depende por completo de los servicios del tercero que contratamos. Eso quiere decir que si la empresa cambia sus condiciones (empeorándolas) tenemos un problema, si hacen una actualización de software y nuestro código no es compatible, tenemos otro problema gordo, si la empresa cierra (recordemos que todos los imperios han caído tarde o temprano), tenemos un problema más gordo aún y cambiar de empresa puede ser otro problema si no lo abordamos desde el principio.

No es lo mismo contratar un VPS con un sistema operativo donde podemos copiar los archivos de nuestra aplicación y desplegar (más o menos) que si tenemos que realizar cambios en nuestra aplicación para hacer llamadas a los servicios que hemos cambiado de empresa. Normalmente cada una tiene sus APIs para garantizar los accesos a sus servicios, autentificación de las llamadas, disparadores, etc. Aunque todos suelen tener partes comunes, también tienen sus particularidades.

¿Para qué lo podemos usar?

Lo primero, para tener todos los bloques de nuestra aplicación de Internet separados y bien diferenciados, lo que nos ayudará a crecer más rápidamente y como dijimos antes, la escalabilidad será algo natural.

Aunque ahora me voy a centrar un poco más en FaaS. Es decir, lo que haremos con este tipo de servicio es simplemente la ejecución de código. Una función, por ejemplo hecha en Python que tiene que ejecutarse en alguna ocasión. ¿Cuándo? Generalmente estas funciones se ejecutarán cuando se dispare un evento del tipo: petición web, entrada de datos en base de datos, inserción de mensaje en un log, creación de una notificación, entrada de un usuario determinado, nuevos archivos en servidores de almacenamiento y mucho más.

Normalmente nuestra aplicación tendrá un servidor web de entrada, que sí lo gestionamos nosotros (ese es nuestro VPS, o nuestros servidores balanceados), pero ahí vamos a incluir el código más básico posible: generación de páginas a partir de las plantillas, obtención sencilla de datos, control de sesiones, y poco más.

Por ejemplo, si tenemos en base de datos toda la información de ventas anual y queremos crear una gráfica resumen con todo y algunos datos estadísticos, seguramente si hemos vendido mucho debemos hacer cientos de cálculos y podemos encargárselos a otro liberando algo de carga en nuestro servidor, aunque el la entrada/salida de nuestra base de datos va a tener impacto igual, pero los cálculos y el trabajo de memoria y CPU para extraer medias, periodos, tendencias y demás se lo lleva otro. Todo esto lo podemos extender a todas aquellas tareas que sean un poco más pesadas de la cuenta. A lo mejor no tardan mucho tiempo, pero involucran sobre todo CPU y memoria y puede perjudicar el rendimiento global de nuestra aplicación. Estas tareas pueden ser: grandes importaciones/exportaciones de datos, generación de imágenes o miniaturas, generación de vídeos, etc.

En el lado del programador

El número de lenguajes es limitado y lo pone el proveedor del servicio. Contamos con lenguajes más o menos famosos y útiles como Python o NodeJs, en algunos sistemas podremos ejecutar Java o C#. Es normal que todos los lenguajes no estén soportados, porque hay que preparar los entornos de ejecución para que sea seguro utilizarlos y, supongo que con el tiempo se podrá ampliar el número de lenguajes soportados. Esto también puede causar dependencias, ya que puede que un proveedor soporte un lenguaje y otro no.

Por el lado de los desarrolladores, tenemos que contar con herramientas que nos permitan desarrollar el código de estos servicios de forma sencilla. Como la forma más común y sencilla de disparar los eventos es con peticiones HTTP. Actualmente hay sistemas que nos permiten crear un pequeño servidor local que atiende estos tipos de peticiones y ejecuta el código seleccionado. Luego, cuando pasemos el código a nuestro servidor sólo tendremos que cambiar el punto de acceso al servicio (que no será local, será el que nos dé nuestro proveedor). Y todo porque desarrollar desplegando todo el rato es muy feo.

Es conveniente también automatizar los despliegues. Y tenemos herramientas para ello, pero dependen del proveedor que cojamos. Sólo tendremos que crear un script o un archivo de instrucciones de despliegue y ya podremos usarlo para desplegar. Lo bueno es que esta tecnología nos permite crear tantos eventos como queramos, y tenemos la posibilidad de tener varios entornos, testing y producción por ejemplo, y sólo tenemos que cambiar el punto de entrada.

Tenemos que tener en cuenta que las funciones que ejecutamos deben estar lo más optimizadas posible, porque nos cobrarán por tiempo de ejecución, por lo tanto, las funciones deben estar bien definidas, tener un único objetivo y evitar entrar en bucles infinitos o esperas innecesarias. Eso sí, estas funciones podrán disparar eventos que ejecuten otras funciones en el futuro, así que tendremos que tener un poco de cuidado para que nuestras funciones no se pisen las unas a las otras…

Resumiendo

Para no agobiarnos mucho, con esta tecnología, definiremos una serie de eventos que, cuando se disparen, ejecutarán un fragmento de código en un ambiente de ejecución o contenedor controlado. Este código se ejecutará y el contenedor se destruirá. Si la función tiene que ejecutarse muchas veces, incluso simultáneamente, el proveedor se encarga del escalado/encolado/etc.

Estas funciones pueden conectar con otros servicios de base de datos, almacenamiento u otros tipos de servidores, incluso disparar la ejecución de otras funciones.

En próximos posts voy a contaros mi experiencia trabajando con Amazon Lambda

Referencias

No te pierdas estos sitios con interesantes lecturas sobre arquitectura Serverless:

The post Aplicaciones web sin servidor, o casi (arquitecturas serverless) appeared first on Poesía Binaria.

» Leer más, comentarios, etc...

Picando Código

Grupo de Usuarios de Unity en Uruguay – Evento especial Jueves 30 de marzo

marzo 27, 2017 01:00

El próximo jueves 30 de marzo a partir de las 17:30 se reúne el Grupo de Usuarios Unity de Uruguay:

Unity

Carl Callewaert, director global de evangelismo de Unity, estará compartiendo información emocionante sobre las características más nuevas de Unity 5.6 y características para venir en el futuro.

PREMIO – ¡Un afortunado asistente ganará una suscripción de Unity Pro de 12 meses o un acceso de 30 días al Unity Certfied Developer Courseware!

Al día siguiente del evento, va a haber un evento para certificarse como desarrollador Unity, examen mediante: http://bit.ly/CRTMV317

Fecha: Jueves 30 de marzo – 17:30 horas
Lugar: Centro de Conferencias de la Intendencia de Mondevideo
Salón Dorado, Piso 1½ Puerta 1 555, Av. 18 de Julio 1360
Montevideo, Uruguay

Más información e inscripciones:
https://www.eventbrite.com/e/unity-user-group-uruguay-tickets-33031282455

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 275

marzo 27, 2017 06:55

Enlaces interesantesAhí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

.NET/.NET Core

ASP.NET/ASP.NET Core

Azure / Cloud

Conceptos/Patrones/Buenas prácticas

Data

Html/Css/Javascript

Visual Studio/Complementos/Herramientas

Cross-platform

Otros

Y para terminar, ahí va un chiste de esos que sólo entendemos los desarrolladores ;D

chiste

Publicado en Variable not found

» Leer más, comentarios, etc...

Bitácora de Javier Gutiérrez Chamorro (Guti)

Sieve en PowerBASIC Console Compiler 6

marzo 26, 2017 07:18

Tras el anuncio de que Powerbasic es ahora gratuito, y después de los últimos artículos que publiqué relativos a Ejemplo con PB/Forms y a Ejemplo con PB/CC, he estado siguiendo activamente la situación de Powerbasic, para ver cómo evoluciona, esperemos que de manera feliz, y no a mucho tardar, tengamos un Powerbasic 11 con soporte […]

La entrada Sieve en PowerBASIC Console Compiler 6 aparece primero en Bitácora de Javier Gutiérrez Chamorro (Guti).

» Leer más, comentarios, etc...

Blog Bitix

Ejemplo de JNI, usar código en C desde Java

marzo 25, 2017 09:05

Para tareas muy específicas que requieran alto rendimiento, baja latencia, tiempo real o haya restricciones de tiempo el lenguaje Java y la JVM pueden mostrar algunas limitaciones obligando a escribir alguna sección crítica de un programa en un lenguaje nativo como C o C++. Para hacer posible la integración entre Java y C existe en Java la API JNI. En este artículo mostraré como realizar un programa Java que emite el mensaje Hola Mundo desde una biblioteca compartida en C y usando JNI.

Java
SVG
Linux

Nunca hasta ahora había tenido necesidad de crear un programa que no estuviese completamente escrito en el lenguaje Java. La API de Java ofrece multitud de clases para cualquier funcionalidad que necesitemos desde estructuras de datos hasta algoritmos de búsqueda o criptografía. También porque el rendimiento de un programa en Java es suficiente y similar a un programa equivalente escrito en C o C++ gracias a las optimizaciones que implementa la máquina virtual de Java o JVM aún siendo los programas Java compilados a una representación intermedia de bytecode independiente de la arquitectura de procesador y sistema operativo en archivos de extensión class y posteriormente interpretados y traducidos a la arquitectura de ejecución, lo que le proporciona a Java la conocida frase “Write once, run anywhere”.

Sin embargo, en casos que se necesita un alto rendimiento para tareas muy específicas o evitar las imposiciones de la máquina virtual como las paradas que realiza para el recolector de basura una solución es escribir esa funcionalidad crítica en lenguaje C, C++ e incluso en Go. El caso de necesidad que me he encontrado es acceder a un sensor de temperatura DHT11 del kit de iniciación a la electrónica para la Raspberry Pi para leer de él la temperatura y humedad. La forma que tiene el sensor DHT11 de proporcionar los datos tiene restricciones de tiempo, cuando se le requieren los valores envía 80 bits de datos donde un pulso de 27μs significa un 0 y un pulso de más de ese tiempo hasta 70μs significa un 1. Estas restricciones de tiempo del sensor y el hecho de que es en una modesta en potencia Raspberry Pi 1 donde lo usaré hace que Java no sea capaz de leer correctamente los valores del sensor.

Acceder desde Java a código nativo en C requiere usar Java Native Interface o por sus siglas JNI. Lo primero que hay que realizar es crear una clase que declare los métodos que serán implementados de forma nativa declarando estos métodos usando la palabra reservada native y que serán enlazados por la JVM cargando una librería compartida con System.loadLibrary(). Creada la clase Java se ha de generar el archivo de cabecera .h propia del lenguaje C con el programa de utilidad del JDK javah. Con el archivo de cabecera se implementa la función y se crea una librería compartida en GNU/Linux usando el compilador gcc. Con la librería compartida se puede iniciar el programa Java. Si la biblioteca compartida no se encuentra se lanzará una excepción del tipo UnsatisfiedLinkError.

Excepción UnsatisfiedLinkError cuando no se encuentra la librería de código nativo

Algunas otras necesidades para hacer uso de JNI son:

  • Acceder a características dependientes de la plataforma necesitadas por la aplicación que no están soportadas en la librería estándar de Java.
  • Ya hay una librería escrita en otro lenguaje y se quiere hacer accesible a código Java a través de JNI.
  • Se quiere implementar una pequeña parte de código crítico en un lenguaje de bajo nivel como ensamblador.

Desde los métodos de código nativo se puede:

  • Crear, inspeccionar y actualizar objetos Java (incluyendo arrays y strings).
  • Llamar a métodos Java.
  • Capturar y lanzar excepciones.
  • Cargar y obtener información de clases.
  • Realizar validación de tipos en tiempo de ejecución.

Los comandos para generar el archivo de cabecera de C y compilarlo con el código nativo en una librería compartida con gcc son:

La cabecera usa varias definiciones de tipos definidas en los archivos jni.h y el archivo que variará según el sistema operativo jni_md.h. En la estructura JNIEnv con múltiples funciones de integración en C y Java, también varias definiciones de los tipos Java para usarlos en C como jobject, jstring, jint, jboolean, jlong, jdouble, jchar, etc.

El programa que emite el mensaje Hello World! desde código nativo en C debe cargar y enlazar la librería de código nativo con el código de la clase Java. Esto se muestra en el bloque de inicialización static de la clase, en este caso usándo el método System.load(), la librería de código nativo de extensión .so en GNU/Linux como en este caso al construirse el proyecto se incluye en el archivo .jar del artefacto resultante se extráe al directorio temporal y se carga desde esa ubicación temporal. En el programa se llama al método print implementado en código nativo y en el código C se usa la función printf de la librería stdio para emitir el mensaje:

La librería compartida para un sistema amd64 la he compilado en mi equipo de escritorio y para la versión arm en la Raspberry Pi e incluido en el directorio src/main/resources de código fuente del ejemplo.

Mensaje en la terminal emitido desde código nativo en un sistema amd64 y ARM

Ente ejemplo usa Java 8 y requiere instalar el compilador gcc para compilar la librería con código nativo. Gradle ofrece soporte para compilar código nativo con su plugin, sin embargo, he preferido usar y conocer los comandos javah y gcc sin usar Gradle. En el siguiente artículo mostraré el ejemplo del sensor DHT11 usando JNI y código nativo en C llamando a métodos de un objeto Java desde código C.

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando el comando ./gradlew executeJniHelloWorldLocal.

» Leer más, comentarios, etc...

Bitácora de Javier Gutiérrez Chamorro (Guti)

Faltas de ortografía en la AEPD

marzo 24, 2017 12:16

Hace no mucho os hablaba de los defectos de forma en el enunciado de la Práctica fundamentos de la programación de la UPF. El caso de hoy, me indigna aún más, pues proviene de la Agencia Española de Protección de Datos (AEPD). Se localiza en la Guía Documento Seguridad, ni más ni menos que la […]

La entrada Faltas de ortografía en la AEPD aparece primero en Bitácora de Javier Gutiérrez Chamorro (Guti).

» Leer más, comentarios, etc...

Juanjo Navarro

Acceso a servicios locales en un VPS

marzo 21, 2017 08:03

Ahora que estoy usando un VPS, una de las cosas que hago es acceder a distintas herramientas administrativas que para mayor seguridad no quiero que estén disponibles de modo abierto en Internet. Ejemplos de este tipo de herramientas son un phpMyAdmin (para administrar la bbdd) o paneles de monitorización del rendimiento de la máquina. Aunque muchas de ellas permiten restringir su uso mediante usuario y contraseña, creo que es mejor no exponerlas a un acceso público.

Lo que hago es configurar estas herramientas para que escuchen sólo en el interface localhost (127.0.0.1) y después utilizo el acceso ssh para acceder a ellas. Según el software que se utilice, esto se hace de distintas maneras:

Apache

En el fichero del VirtualHost, configurar la IP desde la que se escucha. Ejemplo:

<VirtualHost 127.0.0.1:8080>
...
</VirtualHost>

NGINX

Igualmente se puede configurar al definir el servidor:

server  {
    listen 127.0.0.1:8080
    ...
}

Docker

Docker abre los puertos que se le configuran como públicos, incluso aunque tú no los abras en el firewall de linux, por lo tanto es importante indicar igualmente el host cuando se lanza. Ejemplo de lanzar un phpMyAdmin que sólo escucha en el interface local:

docker run --name phpmyadmin -d --link mysql_db_server:db -p 127.0.0.1:8080:80 phpmyadmin/phpmyadmin

Acceso al servicio

Ahora, cuando queramos acceder a los servicios, lo que haremos será abrir un ssh al servidor y abrir un proxy SOCKS dinámico. La forma de hacer esto depende de el cliente ssh que se esté usando. En el caso de putty, lo que se debe hacer es crear un tunel dinámico en el puerto que se quiera (en el siguiente ejemplo, en el puerto 1080, que por otro lado es el puerto oficial de un proxy SOCKS). No olvidar pulsar sobre el botón “Add” para que realmente añada el tunel.

Putty Puerto SOCKS

En el caso de un cliente normal de ssh de línea de comando hay que utilizar el siguiente formato:

ssh -D 1080 user@host

(sustituir user y host por los datos adecuados del servidor)

Una vez hecho esto, configuramos el navegador para que utilice un proxy de tipo SOCKS4, poniendo localhost o 127.0.0.1 como IP del proxy y 1080 como el puerto del mismo. A partir de este momento cualquier acceso en el navegador se hará a través del proxy. Por lo tanto podremos acceder a nuestro servicio sin más que acceder a http://localhost:8080.

Proxy en Windows

Interface de phpMyAdmin

Durante todo el tiempo que queramos acceder al servicio, se debe mantener la sesión ssh abierta. Una vez que terminemos de acceder, simplemente cambiamos la configuración del navegador para que no utilice el proxy SOCKS4.

Haciéndolo más fácil con SwitchyOmega y nip.io

Un problema de este tipo de acceso es que hay que estar continuamente cambiando la configuración del proxy del navegador, además de que mientras el proxy está activado todo el tráfico se ruta a través del VPS, lo cual puede no interesarnos.

La solución es utilizar una extensión como SwitchyOmega. Esta extensión para el navegador Chrome (existen alternativas para otros navegadores) permite configurar un proxy que sólo se utiliza para determinadas urls. Ahora lo único que necesitamos es tener un dominio específico para nuestro servicio local, que configuraremos en SwitchyOmega para que utilice el proxy SOCKS4, lo cual podemos lograr mediante el servicio nip.io

nip.io es un servicio público que nos permite acceder a una máquina con diferentes nombres de dominio. Ejemplo:

127.0.0.1.nip.io mapea a 127.0.0.1
app.127.0.0.1.nip.io mapea a 127.0.0.1

…y así cualquier cosa que queramos poner delante de 127.0.0.1.nip.io. Configuraremos SwitchyOmega para que utilice el proxy SOCKS4 cuando accedamos a (por ejemplo) http://local.127.0.0.1.nip.io:

SwitchyOmega

De esta manera, podremos acceder a nuestro servicio mediante, por ejemplo, http://local.127.0.0.1.nip.io:8080, mientras que para el resto de urls el navegador no utilizará el proxy SOCKS que hemos abierto.

» Leer más, comentarios, etc...

xailer.info

Novedades de Xailer 5 (III)

marzo 21, 2017 01:13

Hola a todos,

Hoy os comento otra mejora del IDE que entiendo que es bastante útil y consiste en la posibilidad de realizar búsquedas incrementales en los propios resultados de búsqueda ofrecidos por el IDE cuando se realiza una búsqueda masiva. En la nueva versión de Xailer cuando se realiza una búsqueda masiva podréis ver un nuevo icono en la venta de mensajes:

Búsqueda en búsqueda

Búsqueda en búsqueda


Cuando pulsemos este nuevo botón podremos realizar una búsqueda incremental sobre los resultados de búsqueda obtenidos. El sistema de búsqueda es incremental y empieza a actuar en cuanto estamos más de medio segundo sin introducir ningún carácter. Espero que os guste.
Resultados de búsqueda en búsqueda

Resultados de búsqueda en búsqueda


¡Ya queda menos para la salida de Xailer 5!

Los siguientes artículos serán mucho más importantes e interesantes; abordaremos los diez nuevos controles que incluye Xailer 5 y la nueva herramienta de control de versiones local que además es totalmente compatible con el uso de control de versiones externo, como SVN o CVS.

Un saludo a todos.

» Leer más, comentarios, etc...

Variable not found

Flushing asíncrono en ASP.NET Core MVC

marzo 21, 2017 08:22

ASP.NET Core MVCSabemos que mientras se renderiza una vista Razor, por defecto el framework MVC va almacenando el resultado en memoria, y sólo al finalizar es cuando comienza a retornarlo al lado cliente.

Por ejemplo, en la ejecución del siguiente código Razor, el usuario que solicitó la página no vería absolutamente nada durante 10 segundos, y de pronto le llegaría el resultado completo:
@using System.Threading.Tasks
@{
Layout = null;
}
<html>
<head>
<title>Hello world!</title>
</head>
<body>
<h1>Let's go</>
<ul>
@for (int i = 0; i < 10; i++)
{
await Task.Delay(1000);
<li>@i</li>
}
</ul>
</body>
</html>
Nota: Observad que para hacer el retardo, en lugar del típico Thread.Sleep() he utilizado Task.Delay() sólo para recordaros que en ASP.NET Core las vistas se renderizan/ejecutan en un contexto asíncrono y, por tanto, podemos utilizar en su interior llamadas con await como hacemos en otros puntos del código.


Aunque en muchos escenarios el comportamiento por defecto de buffering de la salida es válido, hay veces en las que nos interesaría ir enviando al lado cliente las porciones de página que ya han sido generadas. Esto puede reducir el consumo de memoria en el servidor, disminuir el tiempo de espera hasta el primer byte percibido desde el cliente y, como consecuencia, mejorar el rendimiento de nuestra aplicación web.

ASP.NET Core ofrece la posibilidad de "flushear" el contenido en los momentos que nos interese mediante el método FlushAsync() disponible en la clase RazorPage de la que heredan normalmente nuestras vistas. En el momento en que lo invoquemos, todo el contenido renderizado hasta el momento será enviado al cliente a través del canal de salida y eliminado del buffer.

Así, si en el código anterior introducimos llamadas a este método en cada iteración del bucle, el resultado que obtendremos es el mostrado en la animación de abajo, donde se observa que los resultados son enviados al cliente conforme van siendo generados, en lugar de tener que esperar al proceso completo de la vista:
@using System.Threading.Tasks
@{
Layout = null;
}
<html>
<head>
<title>Hello world!</title>
</head>
<body>
<h1>Let's go</h1>
@await FlushAsync()
<ul>
@for (int i = 0; i < 10; i++)
{
await Task.Delay(1000);
<li>@i</li>
await FlushAsync();
}
</ul>
</body>
</html>

Resultado enviándose al cliente de forma progresiva

¿Mola, eh? Pero, ojo, antes de lanzarnos a utilizarlo siempre en nuestras aplicaciones, hay que tener en cuenta que no se trata de la panacea, tiene sus limitaciones. Por ejemplo, no podemos utilizar FlushAsync() en el interior de una página que utilice un Layout debido al extraño orden en el que se ejecutan estos elementos, o tampoco podemos usarlo en el interior de tag helpers.

Publicado en Variable not found.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 274

marzo 20, 2017 12:22

Enlaces interesantesAhí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

.NET/.NET Core

ASP.NET/ASP.NET Core

Azure / Cloud

Conceptos/Patrones/Buenas prácticas

Data

Html/Css/Javascript

Visual Studio 2017 posterVisual Studio/Complementos/Herramientas

Otros

Y por último, ahí va un regalo para los que no sabéis en qué consiste y cómo se calcula la complejidad ciclomática, de la mano de Yerai Darias (@ydarias):

Complejidad ciclomática, by Yerai Darias
(Ver publicación original en Twitter)

Publicado en Variable not found

» Leer más, comentarios, etc...

Blog Bitix

Introducción y ejemplo de cluster de contenedores con Docker Swarm

marzo 19, 2017 11:00

Las funcionalidades de Docker Swarm están incorporadas en Docker para gestionar clusters de nodos con contenedores de los servicios que deseemos. En artículo comentaré algunas de las propiedades de networkning distribuido incorporado en Docker, como crear un cluster de nodos Docker usando VirtualBox con máquinas virtuales para simular múltiples máquinas junto con como lanzar un servicio en el cluster que en este caso consistirá en un servidor web nginx.

Docker

En artículos anteriores de la serie sobre Docker comentaba varias de las herramientas de Docker como Docker Compose, Dockerfile o Docker Machine con ejemplos de como usarlo en local. Una de las herramientas que me quedaba por investigar era Docker Swarm para crear clusters de nodos para contenedores Docker en un entorno de producción. A partir de la versión 1.12 de Docker se han incorporado varias características a Docker para usaar contenedores de forma distribuida y que a pesar de la complejidad subjacente que debe haber es realmente simple usarlo.

Una de las características es el networking que hace trasnparente la comunicación en red distribuida que se hace entre los nodos y los contenedores de esos nodos. Además permite crear redes por software para que los contenedores conectados a esas redes se comuniquen de forma privada. Otra característica interesante de Docker Swarm es que se encarga de monitorizar el estado de los servicios recreando contendores si alguno deja de funcionar. También a través del denominado routing mesh da igual al nodo del cluster por el que se acceda y da igual en que nodo esté el contenedor que Docker Swarm con esta propiedad se encargará de hacer llegar la petición al contenedor. Además, a lo que en Docker Swarm se denomina servicio se realiza balanceo de carga entre la instancias del mismo que haya en el cluster y al servicio se le asigna un DNS y dirección IP por el que puede ser accedido por otros servicios.

En el siguiente ejemplo para crear el cluster de nodos Docker usaré Docker Machine para crear las máquinas de los nodos en máquinas virtuales de VirtualBox aunque su funcionamiento es similar si usásemos la nube de Amazon EC2, Digital Ocean u otros.

El siguiente script crea primeramente varios nodos cada uno en una máquina virtual, luego establece el nodo 01 como manager y los nodos 02 y 03 como workers usando un token para unirlos al cluster según su rol. Los nodos manager se encargan de mantener el estado del cluster y los que a través de ellos los comandos de los servicios deben ser lanzados, en un entorno de producción posiblemente tendríamos 3 nodos manager para soportar tolerancia a fallos. Finalmente, se obtiene lista los nodos del cluster. El comando docker-machine env node-01 permite establecer el entorno contra el que el comando docker lanzará las operaciones como si de la máquina local se tratase.

Una vez creado los nodos es cuando podemos empezar a crear servicios en el cluster. Los servicios son una definición de los contenedores de Docker que queremos que el cluster ejecute. En el ejemplo definiré el servicio de un servidor web nginx, primeramente crearé una red por software en el cluster a la que los servicios pueden conectarse que en el ejemplo (aunque para este no es necesario) utilizaré para hacer una consulta DNS con la herramienta drill para ver el nombre de dominio y dirección IP que asigna Docker Swarm al servicio del servidor web. Con docker service create se crean los servicios, algunos de los parámetros del comando son el nombre del servicio que queremos asignarle, los puertos que expone en este caso el 80 y 443 en el host para que sea accesible desde fuera del cluster, la redes a las que está conectado y finalmente la imagen del contenedor del servicio que en este caso será la versión de nginx con Alpine para Docker. Se pueden listar los servicios que contiene el cluster con docker service ls y los procesos de cada nodo donde podemos ver en que nodos se está ejecutando los contenedores con docker ps.

Una de las propiedades interesantes del networking de Docker Swarm es que ofrece incorporado balanceo de carga, esto es, si el servicio de nginx del ejemplo estuviese formado por dos instancias las peticiones se distribuirían entre las instancias usando el método round-robin. Otra característica interesante si se observa el ejemplo con detalle es que da igual el nodo al que hagamos la petición que la respuesta se obtendrá igualmente, esto es, aunque la petición se haga al nodo 01 y realmente el contenedor del servidor nginx se esté ejecutando en el nodo 02 la petición se realizará correctamente gracias al routing mesh del neworking de Docker Swarm, esto es gracias a que cada servicio tiene asignada una dirección IP, como se ha visto anteriormente en la salida del comando drill.

En este vídeo de asciinema se ve en funcionamiento todos los anteriores comandos. Y en la aplicación de VirtualBox estarán las máquinas virtuales de cada uno de los nodos que crea el ejemplo. En el vídeo se aprecia que el servicio de nginx se está ejecutando en el nodo 02 cuando se listan los procesos de Docker de cada nodo con docker ps, nótese sin embargo que al hacer un petición HTTP a cualquiera de los nodos se devuelve la página de inicio de nginx ya que gracias al routing mesh de Docker Swarm la petición se redirige de forma transparente para el cliente y el servicio al nodo donde realmente se está ejecutando el contenedor de nginx.

<noscript><a href="https://asciinema.org/a/107868" target="_blank"><img src="https://asciinema.org/a/107868.png" width="734" /></a></noscript> Introducción y ejemplo de cluster de contenedores con Docker Swarm
Máquinas virtuales de los nodos del cluster de Docker Swarm

Los comandos para eliminar un servicio del cluster y eliminar completamente el cluster son los siguientes.

Un libro que me ha gustado mucho y que recomiendo leer sobre Docker Swarm es The Devops 2.1 Toolkit que lo explica detalladamente y todo el libro está orientado a como usarlo en un entorno de producción. Un libro más introductorio que también he leído y que está bastante bien es Docker in Action.

Finalmente, quizás si estás usando GNU/Linux y VirtualBox como yo al crear los nodos con el comando docker-machine te produzca el siguiente error (quizá se corrija en futuras versiones de Docker o VirtualBox).

La solución que he encontrado para que funcione es asignar una dirección IP al adaptador puente solo-anfitrión y levantar la interfaz que usa Docker para comunicarse con las máquinas virtuales previamente a crear el nodo. En Arch Linux con los siguientes comandos.

Se puede definir un conjunto de servicios como una unidad en un archivo en stacks de forma similar a como es posible hacer con Docker Compose cosa que mostraré en otro artículo.

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub.

» Leer más, comentarios, etc...

Blog Bitix

Controlar un display LCD 1602 para mostrar texto con la Raspberry Pi y Java

marzo 19, 2017 01:00

Raspberry Pi
Java

Uno de los motivos por los que compré el kit de iniciación a la electrónica para la Raspberry Pi, además de cacharrear un poco, era en concreto controlar el display LCD de 16 columnas y 2 filas. En el kit el display viene con un adaptador con el bus de comunicación I2C. El display se puede usar sin este bus pero requiere utilizar muchos más pines GPIO de datos de los limitados 17 que ofrece la Raspberry Pi 1 y los 26 de las Raspberry Pi B+, 2 y 3. Controlar el display con I2C requiere únicamente 2 pines, por contra sin usar I2C requiere un número significativamente mayor 4 u 8 pines.

El display 1602 con su adaptador para el bus I2C que viene con el kit ya incorporado en la parte trasera es el siguiente.

Display LCD 1602 y adaptador bus I2C

El esquema de conexionado para controlar el display requiere usar los pines de la Raspberry Pi SDA y SDL además de un pin para proporcionar un voltaje de 5V y otro pin para la tierra. El pin SDA es el número 2 según la numeración de pines de la Raspberry Pi y el SDL es el 5. El pin SDA es utilizado en el bus I2C para transmitir los datos y el SDL para la señal de reloj o sincronización. Utilizando la placa de extensión wiringPi de 26 pines los pines SDA y SDL se encuentran identificados por su nombre y el de la placa de extensión de 40 pines que viene con el kit de iniciación también, deberemos identificar estos pines y realizar las conexiones adecuadamente.

Cableado en la breadboard

Hay que emplear varios cables macho-macho y hembra-hembra para conectar a los pines del adaptador I2C del display a los pines del voltaje de 5V, tierra, SDA y SDL de la placa de pruebas sin soldadura.

Unión cables macho-hembra

El siguiente paso será activar el bus I2C en la Raspberry Pi que por defecto está desactivado. Esto requiere añadir unos parámetros en la configuración de arranque y cargar unos módulos del kernel que finalmente crearán un dispositivo tal que en /dev/i2c-0 o /dev/i2c-0. Si instalamos el paquete i2c-tools podremos detectar el display en el bus I2C, en la captura de pantalla en la dirección 27 que hay que usar al construir la instancia del controlador del display. Estos cambios en la configuración de inicio requieren reiniciar la Raspberry Pi. En un sistema con la distribución Arch Linux ARM los cambios son los siguientes.

Detectción del display 1602 en el bus I2C

Según la especificación del display 1602 este componente soporta varios comandos para controlarlo, algunos son para limpiar el texto, cambiar la dirección de escritura, añadir caracteres personalizados y emitir texto en la línea o posición del display que queramos. No es simple el controlar el display a bajo nivel ya que hay que trabajar en momentos con binario y usar bytes, por ello para el ejemplo usaré la librería diozero que ya trae una implementación de controlador con funciones de alto nivel I2CLcd que en versiones más recientes de la librería ha sido renombrada a HD44780Lcd mucho más cómoda que enviar bytes a bajo nivel al bus I2C, el código fuente de la clase HD44780Lcd está disponible y podemos verlo si hay curiosidad.

En mi caso con la Raspberry Pi 1 he tenido que utilizar la versión 0.9 de la librería diozero porque la 0.8 me generaba un stacktrace de una excepción java.lang.UnsupportedOperationException. Obtener esta versión de la librería como aún era de desarrollo y no estaba publicada en Maven Central la he descargado de un google drive que ha creado el autor y usado en Gradle como una dependencia del sistema de ficheros. Como librería subyacente de diozero para controlar los pines GPIO he usado pigpio.

En el ejemplo mostraré un texto en cada una de las lineas del display y usaré una de las funciones del para mostrar caracteres personalizados con los que es posible crear emojis o caracteres nuevos. El controlador de diozero ya contiene una buena colección de caracteres personalizados que definen el patrón de 5x8 puntos que siguen, los nombres de estos caracteres personalizados están en la clase interna Characters de HD44780Lcd aunque también podemos definir nuevos. El ejemplo es el siguiente donde se muestra el uso de los métodos setText y setCharacter, también el constructor donde hay que indicar la dirección asignada al dispositivo en el bus I2C que siendo la 27 corresponde con el valor definido en una constante. Pero también hay otros métodos como clear, cursorOff y cursorOn para apagar y encender el cursor, displayOff displayOn para apgar y encender el display y createChar para crear nuevos caracteres definidos como una array de 8 posiciones donde cada byte indica los pixeles encendidos de cada fila del caracter de 5x8 y con setCharacter para emitir uno de los 8 posibles que se pueden usar al mismo tiempo. Además de estos también hay otros pocos métodos más relacionados con el cursor.

Mensaje en LCD 1602

Pudiendo mostrar mensajes en display es posible mostrar cualquier información que un programa sea capaz de capturar como temperatura y humedad del correspondiente sensor en el mismo kit, estado de un pulsador, espacio disponible en el disco del sistema, y memoria libre, uptime del sistema, fecha y hora, … cualquier cosa que se nos ocurra.

El ejemplo parece simple, y el programa Java lo es, pero requiere conocer varias cosas que en internet está dispersas como activar el bus I2C o conocer la librería diozero para controlar el display que simplifica enormemente el código y nos evita comunicarnos a más bajo nivel con el display, realizar las conexiones eléctricas también requiere algo de conocimiento. Averiguar todo esto me costó una buena cantidad de tiempo.

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando el comando ./gradlew executeLcd.

» Leer más, comentarios, etc...

Poesía Binaria

Cómo recuperar pestañas perdidas en Google Chrome

marzo 16, 2017 08:03

En Internet hay dos tipos de personas: esos que como mucho tienen tres pestañas abiertas a la vez, y parece que les cuesta dinero tener pestañas abiertas. Y los que, como yo, abrimos una pestaña, la dejamos pa’ luego, descubrimos algo interesante y lo abrimos, o incluso mientras buscamos información abrimos todas las entradas del buscador en pestañas para ir mirándolas poco a poco. Total, al final te juntas (sin exagerar) con unas 500 pestañas. Afortunadamente, los navegadores modernos gestionan esto de forma más o menos eficiente.

Acumulando pestañas

Por si las moscas, ya que Chrome/Chromium tiene todo abierto a la vez y muchas veces el sistema no se suspende automáticamente porque alguna página lo prohíbe, yo utilizo la extensión The Great Suspender que se encarga de suspender pestañas que no uso tras un tiempo sin hacerles caso. Esto es sólo para liberar memoria y recursos del ordenador.

Reabriendo pestañas

De la misma manera que se puede abrir una pestaña cerrada recientemente haciendo click con el botón derecho en la pestaña y luego “Volver a abrir pestaña cerrada” o pulsando Control + Mayúscula + T podemos abrir todas las pestañas que teníamos en la ventana cuando la cerramos. Por lo que si hemos cerrado accidentalmente el navegador podemos volver a tener todo abierto con esta combinación de teclas.

Chrome también nos permite guardar en marcadores todas las pestañas que tenemos abiertas con (Control + Mayúscula + D), que nos puede venir muy bien.

Que automáticamente restaure las pestañas

Si entramos en la configuración de Chrome, veremos en las primeras opciones cómo queremos que el navegador se presente ante nosotros, en Iniciar Sesión / Al abrir el navegador… podemos marcar “Abrir todo como estaba antes de cerrar”, tal y como aparece en la foto. Esto puede variar ligeramente con la versión.

Pero a veces hay problemas…

Pero a veces podemos tener serios problemas cuando tienes muchas pestañas abiertas, se cierra la ventana por algún problema (o porque tuviste un accidente, que un error humano no lo podemos descartar) y cuando arrancamos de nuevo el navegador no se restauran las pestañas. Cuando ya no sabes ni qué tenías abierto pero que seguro que era interesante y tarde o temprano echarías en falta (y que no la encuentras en el historial porque tienes demasiadas cosas). Aquella pestaña que abriste hace meses y no le has hecho ni caso pero ahora se vuelve una gran pérdida en tu corazón.

Todavía no está todo perdido. ¡No hagas nada! ¡No toques nada!. Este navegador guarda una copia de la sesión anterior en los archivos de usuario. Para ello, dependiendo de tu sistema operativo encontrarás los archivos en un sitio u otro. Más o menos:

  • GNU/Linux: /home/usuario/.config/{chrome}/Default ({chrome} lo pongo entre llaves porque puede ser “chrome” / “chromium” o “google chrome”
  • Mac OS X: Users/usuario/Library/Application Support/Google/Chrome/Default
  • Windows 7, 8.1 y 10: C:\Users\\AppData\Local\Google\Chrome\User Data\Default

Allí encontramos algunos archivos:

  • Current Session
  • Current Tabs
  • Last Session
  • Last Tabs

Estos archivos están en formato binario, aunque las direcciones de las pestañas abiertas se pueden leer perfectamente, si tienes GNU/Linux o cualquier Unix, puedes utilizar strings para ver su contenido (seguro que para Windows encontramos también alguna herramienta):

strings Current\ Session
http://www.bodurov.com/JsonFormatter/
https://colorlib.com/polygon/metis/

Pero estos ficheros también podemos copiarlos fuera (por seguridad, ya lo veremos), o machacar Current Session con Last Session y Current Tabs con Last Tabs. Para asegurarnos de que todo va bien, es recomendable, antes de cerrar el navegador hacer una copia de seguridad de todos:

cp Current\ Session{,.backup}
cp Current\ Tabs{,.backup}
cp Last\ Session{,.backup}
cp Last\ Tabs{,.backup}

Ahora tendremos Current Session y Current Session.backup , Current Tabs y Current Tabs.backup y así con los demás. Ahora, cerramos el navegador, que seguramente reescriba los archivos (pero no las copias de seguridad que hemos hecho).
Ahora copiamos Last Session.backup en Last Session (y en Current Session también si queremos) y hacemos lo mismo con Last Tabs.backup:

cp Last\ Session.backup Current\ Session
cp Last\ Session.backup Last\ Session
cp Last\ Tabs.backup Current\ Tabs
cp Last\ Tabs.backup Last\ Tabs

Ahora podemos volver a abrir el navegador normalmente y si es necesario restaurar las pestañas. ¡Espero que las hayas podido recuperar!

Copias de seguridad periódicas de las pestañas

Si dejas abierto mucho tiempo el navegador y tienes largas sesiones de navegación que pueden durar incluso días. No estaría mal tener una copia de seguridad periódica de estas pestañas. Que se realice automáticamente cada cierto tiempo, para ello puedes hacer un cron con este script:

1
2
3
4
5
#!/bin/bash

cd /home/usuario/.config/chromium/Default/
tar cvjpf /home/usuario/Chromebackups/chrometabs_20170302_22_32_52.tar.bz2 "Current Session" "Current Tabs" "Last Session" "Last Tabs"
find -mtime +30 -exec rm {} +

Cambiando los directorios de usuario y de chrome y ejecutándolo un par de veces al día podemos tener por seguro que no perderemos muchas pestañas. Con el script anterior estaremos guardando un mes de pestañas en el tiempo (la última línea de find se encarga de borrar los que sean muy antiguos).

The post Cómo recuperar pestañas perdidas en Google Chrome appeared first on Poesía Binaria.

» Leer más, comentarios, etc...

Juanjo Navarro

Rendimiento de un VPS en Scaleway

marzo 16, 2017 01:09

Scaleway es una empresa de hosting francesa (creo que filial de online.net) de la que se oyó hablar hace unos meses porque ofrecen servidores reales (no virtuales) con microprocesador ARM. Básicamente son pequeños ordenadores (equivalentes a un Raspberry Pi) con 2GB de RAM por 2,99€ al mes (más impuestos). Estuve probando uno hace un tiempo (permiten usarlos por horas) pero decidí no usarlo para el hosting de mi web. Me echó para atrás el poco rendimiento de un chip ARM (aunque sean 4 cores) y sobre todo porque a nivel de software te obliga a instalar versiones especiales de todo (SO, aplicaciones) compiladas para micro ARM (o compilarlas tú mismo).

Un tiempo después sacaron servidores VPS más clásicos, virtualizados sobre micros x86 64bit. El más pequeño de ellos (también 2,99€ más impuestos) ofrece 2GB de RAM al mismo precio que DigitalOcean ofrece sólo 512Mb.

Buscando información sobre el rendimiento de estos VPS en Internet los resultados no son muy buenos, principalmente por dos razones:

  1. Los micros, a pesar de ser x86 64bit, son de una arquitectura antigua, mucho más lenta que los micros actuales.
  2. Los discos duros, aunque son SSD, están montados sobre un interface de red, no directamente.

La teoría es que a igualdad de recursos un VPS de DigitalOcean o Linode tendrá mucho mejor rendimiento, pero el precio de un VPS de 2GB en DigitalOcean cuadruplica el de Scaleway. Podría contratar un VPS con menos memoria, pero creo que para un hosting es más importante la cantidad de memoria que la velocidad del micro, y 2Gb es la cantidad de memoria mínima para no tener que estar configurado de modo especial apache, etc.

En cualquier caso, como una cosa es la teoría y otra la práctica, he hecho una pequeña prueba de carga. He instalado un Textpattern (mi gestor de contenidos favorito) sobre Docker. He utilizado un contenedor para apache y otro para la bbdd mysql. Estos son los tiempos de acceso a la página de inicio utilizando la herramienta ab de apache, 100 peticiones con 10 peticiones concurrentes, en el VPS de Scaleway:

Concurrency Level:      10
Time taken for tests:   5.955 seconds
Complete requests:      100
Failed requests:        0
Write errors:           0
Total transferred:      2718734 bytes
HTML transferred:       2699134 bytes
Requests per second:    16.79 [#/sec] (mean)
Time per request:       595.534 [ms] (mean)
Time per request:       59.553 [ms] (mean, across all concurrent requests)
Transfer rate:          445.82 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       38   43   3.6     43      61
Processing:   335  520  77.2    529     698
Waiting:      248  476  78.2    481     632
Total:        380  563  77.2    573     743

Percentage of the requests served within a certain time (ms)
  50%    573
  66%    588
  75%    614
  80%    625
  90%    664
  95%    687
  98%    715
  99%    743
 100%    743 (longest request)

Y ahora en DigitalOcean, con un VPS también de 2Gb, desplegando los mismos dos contenedores Docker:

Concurrency Level:      10
Time taken for tests:   5.508 seconds
Complete requests:      100
Failed requests:        0
Write errors:           0
Total transferred:      2718610 bytes
HTML transferred:       2699010 bytes
Requests per second:    18.15 [#/sec] (mean)
Time per request:       550.831 [ms] (mean)
Time per request:       55.083 [ms] (mean, across all concurrent requests)
Transfer rate:          481.98 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       44   51   6.4     50      82
Processing:   202  458  71.1    464     583
Waiting:      101  301 106.8    292     512
Total:        254  510  70.7    519     630

Percentage of the requests served within a certain time (ms)
  50%    519
  66%    535
  75%    553
  80%    560
  90%    577
  95%    604
  98%    627
  99%    630
 100%    630 (longest request)

Como se puede ver, los rendimientos son muy parecidos (la media en Scaleway es 573ms frente a 519ms en DigitalOcean). Un 10% mejores en DigitalOcean en esta prueba, pero recordemos que su precio se cuadruplica. Esto además es con el VPS de 2Gb de DigitalOcean (que tiene dos cores). Si utilizamos el VPS de 512Mb o 1Gb (que sólo tienen un core) los tiempos en DigitalOcean son mucho peores. Esto es lógico, dado el nivel de concurrencia los tiempos entre ambos sólo se mantienen cuando hacemos una sola petición concurrente.

Por cierto, esta prueba con Docker me ha permitido ver la ventaja de usarlo para la infraestructura: Una vez montado todo en el primer VPS, llevarlo al segundo simplemente fue hacer copia de los contenedores (con sus volúmenes) y copiarlos a DigitalOcean. Hacer un docker-compose up -d y automáticamente tener una copia exacta funcionando.

» Leer más, comentarios, etc...

Juanjo Navarro

Montando tu servidor en un VPS con Docker: Introducción

marzo 14, 2017 12:23

Últimamente he estado montando mis páginas web en un servidor VPS.

Mi intención es dedicar una serie de artículos a esto en las próximas semanas, pero antes quiero dejar escritas las motivaciones para hacerlo y algunas ideas previas.

¿Por qué montar un VPS?

  1. Me gusta “jugar” con servidores. Esta es la verdadera razón. He tenido mis páginas en un hosting compartido desde hace mucho, y es obvio que no tener que ocuparte tú del mantenimiento de los servidores es una ventaja, pero varias veces había tenido la tentación de montar un VPS y al final me he decidido.
  2. He encontrado un VPS de muy buen precio. En ocasiones he arrancado un VPS en DigitalOcean durante horas o días, para hacer alguna prueba, pero el precio que quería pagar sólo me permitía acceder al Droplet más pequeño, y me daba la sensación de que con sólo 512Mb de RAM se podía quedar corto. Ahora he encontrado otro que por el mismo precio o menos me proporciona 2Gb de RAM (más sobre esto próximamente)
  3. Me permite acceder a funcionalidades extra. Por ejemplo: Let’s Encrypt. Con la web moviéndose hacía https (y con un servicio como Let’s Encrypt, que nos ofrece certificados gratuitamente) quería sumarme a esta tendencia, pasando mis webs a https. Algunos proveedores de hosting compartido ya ofrecen soporte para Let’s Encrypt, pero el que yo usaba no lo hacía. Puestos a cambiar de hosting compartido, he decidido el do it yourself de un VPS.

¿Por qué utilizar Docker?

  1. Por probar una nueva tecnología. Nuevamente, no hay que buscar más razones que las ganas de “jugar” con una nueva tecnología. He leído sobre Docker, he hecho algunas pruebas, pero no hay nada como tener que pelearte de verdad con una tecnología para aprender sobre ella.
  2. Quiero poder cambiar rápidamente de VPS. Es obvio que el VPS barato me puede salir rana. Aunque he hecho unas cuantas pruebas y todo parece funcionar bien, quiero tener un plan B, poder pasarme a otro VPS rápidamente en el caso de que el rendimiento no sea el esperado. Montando todo con Docker (servidores de BBDD, servidores web, etc) puedo simplemente desplegar los contenedores en otro VPS y automáticamente tener todo funcionando sin más que apuntar los DNS a la nueva IP.

» Leer más, comentarios, etc...

Variable not found

¿Quién libera las dependencias cuando finaliza el proceso de una petición en ASP.NET Core?

marzo 14, 2017 08:17

ASP.NET CoreEl sistema de inyección de dependencias de ASP.NET Core es un potente mecanismo que viene de serie en el framework y, que de hecho, es uno de los principales pilares en los que se basa su funcionamiento.

Probablemente sabréis (y si no, podéis echar un vistazo a este post) que existen diversas fórmulas para añadir servicios o componentes al contenedor de dependencias, y una de ellas es la denominada "Scoped". Estos componentes quedan registrados asociados a un ciclo de vida específico (ServiceLifetime.Scoped), que indica que cada vez que haya que se solicite un objeto de este tipo se retornará la misma instancia dentro el ámbito actual, que normalmente coincide con el proceso de una petición específica.

O en otras palabras, dentro de la misma petición, la instancia del componente será compartida por todos los objetos que dependan de él. Además, al finalizar el proceso de la petición, el framework invocará su método Dispose() (si existe, claro), permitiendo liberar los recursos que haya utilizado. Por ejemplo, suele ser una buena práctica emplear esta técnica para reutilizar un contexto de datos entre los servicios que participan en el proceso de una petición que accede a una base de datos, y asegurar su liberación final para no dejar conexiones abiertas.

Pero este post no va de ver cómo registrar estos componentes o de cómo utilizarlos en nuestras aplicaciones, sino de responder una simple curiosidad: ¿quién se encarga de crear este scope cuando comienza una petición y de liberarlo cuando finaliza? Ya, sé que no tiene mucha utilidad práctica en el día a día, pero es una buena excusa para echar un vistazo y entender cómo funcionan las cosas por dentro ;)

<Disclaimer>Todo lo que comentaremos a continuación son detalles de la implementación actual del framework y son válidos en la versión 1.1 de ASP.NET Core, pero podrían cambiar en futuras versiones.</Disclaimer>

Como sabéis, el método Configure() de la clase de inicialización de ASP.NET Core recibe una instancia de IApplicationBuilder que representa el pipeline de nuestra aplicación. Durante el proceso de configuración, utilizaremos esta instancia para añadir los middlewares que necesitemos en nuestra aplicación.

Pero si alguna vez habéis introducido un breakpoint en este método y habéis analizado el contenido que recibimos aquí, veréis que enmascarado tras el interfaz IApplicationBuilder se encuentra un objeto ApplicationBuilder que, a diferencia de lo que podríamos esperar, viene con el pipeline ya poblado con algunos middlewares, como se muestra en la siguiente captura de pantalla:

Instancia de IApplicationBuilder recibida en el método Configure()
De los tres middlewares incluidos por defecto, los dos últimos tienen que ver con la integración con IIS, pero el que nos interesa especialmente es el primero de ellos, que si escarbamos un poco podemos ver que se trata de un objeto RequestServicesContainerMiddleware.

Este middleware, definido de forma interna en el paquete Microsoft.AspNetCore.Hosting, está posicionado justo a la entrada del pipeline y básicamente se encarga de crear un scope justo cuando llega una petición, ceder el control al resto del pipeline y, finalmente, liberarlo junto con los objetos asociados al mismo cuando vuelve a retomar el control, es decir, cuando el proceso de la petición ha finalizado completamente:
public async Task Invoke(HttpContext httpContext)
{
[...]
using (var feature = new RequestServicesFeature(_scopeFactory))
{
try
{
httpContext.Features.Set<IServiceProvidersFeature>(feature);
await _next.Invoke(httpContext);
}
finally
{
[...]
}
}
}
La clase RequestServicesFeature que se crea en el código anterior es la que realmente implementa la instanciación del scope, y es introducida en las features disponibles en el contexto HTTP como IServiceProvidersFeature para que esté disponible durante el proceso de la petición para actuar como service provider. Es decir, todas las instancias  creadas como ServiceLifetime.Scoped serán asociadas a este proveedor de servicios.

Cuando la petición ha sido ya procesada por el resto del pipeline y el control retorna al código de este middleware, la finalización del bloque using hace que se libere el objeto RequestServicesFeature y con él los objetos scoped que hayan sido creados en su ámbito. Sencillo, ¿verdad?

En conclusión, la respuesta a la pregunta que nos hacíamos al principio es bastante simple, e incluso previsible. La creación y liberación del scope que marca la vida de las dependencias durante el proceso de una petición se realiza desde un middleware ubicado justo en la puerta del pipeline. Esta posición privilegiada le permite crear un service provider específico para la petición, que actuará como scope, y liberarlo cuando el proceso de ésta haya finalizado, junto con todos los objetos que hayan sido creados en el mismo.

Publicado en Variable not found.

» Leer más, comentarios, etc...

Poesía Binaria

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

marzo 13, 2017 10:02


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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
enum {
    SEQ_TYPE_IMAGE       = 0,
    SEQ_TYPE_META        = 1,
    SEQ_TYPE_SCENE       = 2,
    SEQ_TYPE_MOVIE       = 3,
    SEQ_TYPE_SOUND_RAM   = 4,
    SEQ_TYPE_SOUND_HD    = 5,
    SEQ_TYPE_MOVIECLIP   = 6,
    SEQ_TYPE_MASK        = 7,

    SEQ_TYPE_EFFECT      = 8,
    SEQ_TYPE_CROSS       = 8,
    SEQ_TYPE_ADD         = 9,
    SEQ_TYPE_SUB         = 10,
    SEQ_TYPE_ALPHAOVER   = 11,
    SEQ_TYPE_ALPHAUNDER  = 12,
    SEQ_TYPE_GAMCROSS    = 13,
    SEQ_TYPE_MUL         = 14,
    SEQ_TYPE_OVERDROP    = 15,
    /* SEQ_TYPE_PLUGIN      = 24, */ /* Deprecated */
    SEQ_TYPE_WIPE        = 25,
    SEQ_TYPE_GLOW        = 26,
    SEQ_TYPE_TRANSFORM   = 27,
    SEQ_TYPE_COLOR       = 28,
    SEQ_TYPE_SPEED       = 29,
    SEQ_TYPE_MULTICAM    = 30,
    SEQ_TYPE_ADJUSTMENT  = 31,
    SEQ_TYPE_GAUSSIAN_BLUR = 40,
    SEQ_TYPE_TEXT = 41,
    SEQ_TYPE_CUSTOMEFFECT = 42,

    SEQ_TYPE_MAX  = 42
};

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
EnumPropertyItem sequencer_prop_effect_types[] = {
    {SEQ_TYPE_CROSS, "CROSS", 0, "Crossfade", "Crossfade effect strip type"},
    {SEQ_TYPE_ADD, "ADD", 0, "Add", "Add effect strip type"},
    {SEQ_TYPE_SUB, "SUBTRACT", 0, "Subtract", "Subtract effect strip type"},
    {SEQ_TYPE_ALPHAOVER, "ALPHA_OVER", 0, "Alpha Over", "Alpha Over effect strip type"},
    {SEQ_TYPE_ALPHAUNDER, "ALPHA_UNDER", 0, "Alpha Under", "Alpha Under effect strip type"},
    {SEQ_TYPE_GAMCROSS, "GAMMA_CROSS", 0, "Gamma Cross", "Gamma Cross effect strip type"},
    {SEQ_TYPE_MUL, "MULTIPLY", 0, "Multiply", "Multiply effect strip type"},
    {SEQ_TYPE_OVERDROP, "OVER_DROP", 0, "Alpha Over Drop", "Alpha Over Drop effect strip type"},
    {SEQ_TYPE_WIPE, "WIPE", 0, "Wipe", "Wipe effect strip type"},
    {SEQ_TYPE_GLOW, "GLOW", 0, "Glow", "Glow effect strip type"},
    {SEQ_TYPE_TRANSFORM, "TRANSFORM", 0, "Transform", "Transform effect strip type"},
    {SEQ_TYPE_COLOR, "COLOR", 0, "Color", "Color effect strip type"},
    {SEQ_TYPE_SPEED, "SPEED", 0, "Speed", "Color effect strip type"},
    {SEQ_TYPE_MULTICAM, "MULTICAM", 0, "Multicam Selector", ""},
    {SEQ_TYPE_ADJUSTMENT, "ADJUSTMENT", 0, "Adjustment Layer", ""},
    {SEQ_TYPE_GAUSSIAN_BLUR, "GAUSSIAN_BLUR", 0, "Gaussian Blur", ""},
    {SEQ_TYPE_TEXT, "TEXT", 0, "Text", ""},
    {SEQ_TYPE_CUSTOMEFFECT, "CUSTOMEFFECT", 0, "Custom Effect", "Create a strip with customized effect" },
    {0, NULL, 0, NULL, NULL}
};

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void RNA_api_sequences(BlenderRNA *brna, PropertyRNA *cprop)
{
    StructRNA *srna;
    PropertyRNA *parm;
    FunctionRNA *func;

    static EnumPropertyItem seq_effect_items[] = {
        {SEQ_TYPE_CROSS, "CROSS", 0, "Cross", ""},
        {SEQ_TYPE_ADD, "ADD", 0, "Add", ""},
        {SEQ_TYPE_SUB, "SUBTRACT", 0, "Subtract", ""},
        {SEQ_TYPE_ALPHAOVER, "ALPHA_OVER", 0, "Alpha Over", ""},
        {SEQ_TYPE_ALPHAUNDER, "ALPHA_UNDER", 0, "Alpha Under", ""},
        {SEQ_TYPE_GAMCROSS, "GAMMA_CROSS", 0, "Gamma Cross", ""},
        {SEQ_TYPE_MUL, "MULTIPLY", 0, "Multiply", ""},
        {SEQ_TYPE_OVERDROP, "OVER_DROP", 0, "Over Drop", ""},
        {SEQ_TYPE_WIPE, "WIPE", 0, "Wipe", ""},
        {SEQ_TYPE_GLOW, "GLOW", 0, "Glow", ""},
        {SEQ_TYPE_TRANSFORM, "TRANSFORM", 0, "Transform", ""},
        {SEQ_TYPE_COLOR, "COLOR", 0, "Color", ""},
        {SEQ_TYPE_SPEED, "SPEED", 0, "Speed", ""},
        {SEQ_TYPE_MULTICAM, "MULTICAM", 0, "Multicam Selector", ""},
        {SEQ_TYPE_ADJUSTMENT, "ADJUSTMENT", 0, "Adjustment Layer", ""},
        {SEQ_TYPE_GAUSSIAN_BLUR, "GAUSSIAN_BLUR", 0, "Gaussian Blur", ""},
        {SEQ_TYPE_TEXT, "TEXT", 0, "Text", ""},
        {SEQ_TYPE_CUSTOMEFFECT, "CUSTOM_EFFECT", 0, "Custom Effect", "" },
        {0, NULL, 0, NULL, NULL}
    };

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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class SEQUENCER_MT_add_effect(Menu):
    bl_label = "Effect Strip..."

    def draw(self, context):
        layout = self.layout

        layout.operator_context = 'INVOKE_REGION_WIN'

        layout.operator("sequencer.effect_strip_add", text="Add").type = 'ADD'
        layout.operator("sequencer.effect_strip_add", text="Subtract").type = 'SUBTRACT'
        layout.operator("sequencer.effect_strip_add", text="Alpha Over").type = 'ALPHA_OVER'
        layout.operator("sequencer.effect_strip_add", text="Alpha Under").type = 'ALPHA_UNDER'
        layout.operator("sequencer.effect_strip_add", text="Cross").type = 'CROSS'
        layout.operator("sequencer.effect_strip_add", text="Gamma Cross").type = 'GAMMA_CROSS'
        layout.operator("sequencer.effect_strip_add", text="Gaussian Blur").type = 'GAUSSIAN_BLUR'
        layout.operator("sequencer.effect_strip_add", text="Multiply").type = 'MULTIPLY'
        layout.operator("sequencer.effect_strip_add", text="Over Drop").type = 'OVER_DROP'
        layout.operator("sequencer.effect_strip_add", text="Wipe").type = 'WIPE'
        layout.operator("sequencer.effect_strip_add", text="Glow").type = 'GLOW'
        layout.operator("sequencer.effect_strip_add", text="Text").type = 'TEXT'
        layout.operator("sequencer.effect_strip_add", text="Transform").type = 'TRANSFORM'
        layout.operator("sequencer.effect_strip_add", text="Color").type = 'COLOR'
        layout.operator("sequencer.effect_strip_add", text="Speed Control").type = 'SPEED'
        layout.operator("sequencer.effect_strip_add", text="Multicam Selector").type = 'MULTICAM'
        layout.operator("sequencer.effect_strip_add", text="Adjustment Layer").type = 'ADJUSTMENT'
        layout.operator("sequencer.effect_strip_add", text="Custom Effect").type = 'CUSTOM_EFFECT'

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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
        /* effects */
        case SEQ_TYPE_TRANSFORM:
        case SEQ_TYPE_SPEED:
        case SEQ_TYPE_ADD:
        case SEQ_TYPE_SUB:
        case SEQ_TYPE_MUL:
        case SEQ_TYPE_ALPHAOVER:
        case SEQ_TYPE_ALPHAUNDER:
        case SEQ_TYPE_OVERDROP:
        case SEQ_TYPE_GLOW:
        case SEQ_TYPE_MULTICAM:
        case SEQ_TYPE_ADJUSTMENT:
        case SEQ_TYPE_GAUSSIAN_BLUR:
        case SEQ_TYPE_CUSTOMEFFECT:
            UI_GetThemeColor3ubv(TH_SEQ_EFFECT, col);

            /* slightly offset hue to distinguish different effects */
            if      (seq->type == SEQ_TYPE_ADD)           rgb_byte_set_hue_float_offset(col, 0.04);
            else if (seq->type == SEQ_TYPE_SUB)           rgb_byte_set_hue_float_offset(col, 0.08);
            else if (seq->type == SEQ_TYPE_MUL)           rgb_byte_set_hue_float_offset(col, 0.12);
            else if (seq->type == SEQ_TYPE_ALPHAOVER)     rgb_byte_set_hue_float_offset(col, 0.16);
            else if (seq->type == SEQ_TYPE_ALPHAUNDER)    rgb_byte_set_hue_float_offset(col, 0.20);
            else if (seq->type == SEQ_TYPE_OVERDROP)      rgb_byte_set_hue_float_offset(col, 0.24);
            else if (seq->type == SEQ_TYPE_GLOW)          rgb_byte_set_hue_float_offset(col, 0.28);
            else if (seq->type == SEQ_TYPE_TRANSFORM)     rgb_byte_set_hue_float_offset(col, 0.36);
            else if (seq->type == SEQ_TYPE_MULTICAM)      rgb_byte_set_hue_float_offset(col, 0.32);
            else if (seq->type == SEQ_TYPE_ADJUSTMENT)    rgb_byte_set_hue_float_offset(col, 0.40);
            else if (seq->type == SEQ_TYPE_GAUSSIAN_BLUR) rgb_byte_set_hue_float_offset(col, 0.42);
            else if (seq->type == SEQ_TYPE_CUSTOMEFFECT)  rgb_byte_set_hue_float_offset(col, 0.52);
            break;

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
static void rna_def_sequence(BlenderRNA *brna)
{
    StructRNA *srna;
    PropertyRNA *prop;

    static const EnumPropertyItem seq_type_items[] = {
        {SEQ_TYPE_IMAGE, "IMAGE", 0, "Image", ""},
        {SEQ_TYPE_META, "META", 0, "Meta", ""},
        {SEQ_TYPE_SCENE, "SCENE", 0, "Scene", ""},
        {SEQ_TYPE_MOVIE, "MOVIE", 0, "Movie", ""},
        {SEQ_TYPE_MOVIECLIP, "MOVIECLIP", 0, "Clip", ""},
        {SEQ_TYPE_MASK, "MASK", 0, "Mask", ""},
        {SEQ_TYPE_SOUND_RAM, "SOUND", 0, "Sound", ""},
        {SEQ_TYPE_CROSS, "CROSS", 0, "Cross", ""},
        {SEQ_TYPE_ADD, "ADD", 0, "Add", ""},
        {SEQ_TYPE_SUB, "SUBTRACT", 0, "Subtract", ""},
        {SEQ_TYPE_ALPHAOVER, "ALPHA_OVER", 0, "Alpha Over", ""},
        {SEQ_TYPE_ALPHAUNDER, "ALPHA_UNDER", 0, "Alpha Under", ""},
        {SEQ_TYPE_GAMCROSS, "GAMMA_CROSS", 0, "Gamma Cross", ""},
        {SEQ_TYPE_MUL, "MULTIPLY", 0, "Multiply", ""},
        {SEQ_TYPE_OVERDROP, "OVER_DROP", 0, "Over Drop", ""},
        {SEQ_TYPE_WIPE, "WIPE", 0, "Wipe", ""},
        {SEQ_TYPE_GLOW, "GLOW", 0, "Glow", ""},
        {SEQ_TYPE_TRANSFORM, "TRANSFORM", 0, "Transform", ""},
        {SEQ_TYPE_COLOR, "COLOR", 0, "Color", ""},
        {SEQ_TYPE_SPEED, "SPEED", 0, "Speed", ""},
        {SEQ_TYPE_MULTICAM, "MULTICAM", 0, "Multicam Selector", ""},
        {SEQ_TYPE_ADJUSTMENT, "ADJUSTMENT", 0, "Adjustment Layer", ""},
        {SEQ_TYPE_GAUSSIAN_BLUR, "GAUSSIAN_BLUR", 0, "Gaussian Blur", ""},
        {SEQ_TYPE_TEXT, "TEXT", 0, "Text", ""},
        {SEQ_TYPE_CUSTOMEFFECT, "CUSTOM_EFFECT", 0, "Custom Effect", "" },             
        {0, NULL, 0, NULL, NULL}
    };

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
static EffectInfo def_effects[] = {
    {"AddSequence", "Add Sequence", "Add Sequence", NULL, 2},
    {"AdjustmentSequence", "Adjustment Layer Sequence",
     "Sequence strip to perform filter adjustments to layers below", rna_def_input, 0},
    {"AlphaOverSequence", "Alpha Over Sequence", "Alpha Over Sequence", NULL, 2},
    {"AlphaUnderSequence", "Alpha Under Sequence", "Alpha Under Sequence", NULL, 2},
    {"ColorSequence", "Color Sequence",
     "Sequence strip creating an image filled with a single color", rna_def_solid_color, 0},
    {"CrossSequence", "Cross Sequence", "Cross Sequence", NULL, 2},
    {"GammaCrossSequence", "Gamma Cross Sequence", "Gamma Cross Sequence", NULL, 2},
    {"GlowSequence", "Glow Sequence", "Sequence strip creating a glow effect", rna_def_glow, 1},
    {"MulticamSequence", "Multicam Select Sequence", "Sequence strip to perform multicam editing",
     rna_def_multicam, 0},
    {"MultiplySequence", "Multiply Sequence", "Multiply Sequence", NULL, 2},
    {"OverDropSequence", "Over Drop Sequence", "Over Drop Sequence", NULL, 2},
    {"SpeedControlSequence", "SpeedControl Sequence",
     "Sequence strip to control the speed of other strips", rna_def_speed_control, 1},
    {"SubtractSequence", "Subtract Sequence", "Subtract Sequence", NULL, 2},
    {"TransformSequence", "Transform Sequence",
     "Sequence strip applying affine transformations to other strips", rna_def_transform, 1},
    {"WipeSequence", "Wipe Sequence", "Sequence strip creating a wipe transition",
     rna_def_wipe, 2},
    {"GaussianBlurSequence", "Gaussian Blur Sequence", "Sequence strip creating a gaussian blur",
     rna_def_gaussian_blur, 1},
    {"TextSequence", "Text Sequence", "Sequence strip creating text",
     rna_def_text, 0},
    {"CustomEffectSequence", "Custom Effect Sequence", "Sequence strip creating custom effects",
     rna_def_custom_effect, 1},
    {"", "", "", NULL, 0}
};

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:

1
2
3
4
5
6
7
8
9
10
static void rna_def_custom_effect(StructRNA *srna)
{
    PropertyRNA *prop;

    RNA_def_struct_sdna_from(srna, "CustomEffectVars", "effectdata");
    prop = RNA_def_property(srna, "property", PROP_FLOAT, PROP_UNSIGNED);
    RNA_def_property_ui_text(prop, "One property", "One float prop for practising");
    RNA_def_property_ui_range(prop, 0.0f, FLT_MAX, 1, -1);
    RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_Sequence_update");
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
static StructRNA *rna_Sequence_refine(struct PointerRNA *ptr)
{
    Sequence *seq = (Sequence *)ptr->data;

    switch (seq->type) {
        case SEQ_TYPE_IMAGE:
            return &RNA_ImageSequence;
        case SEQ_TYPE_META:
            return &RNA_MetaSequence;
        case SEQ_TYPE_SCENE:
            return &RNA_SceneSequence;
        case SEQ_TYPE_MOVIE:
            return &RNA_MovieSequence;
        case SEQ_TYPE_MOVIECLIP:
            return &RNA_MovieClipSequence;
        case SEQ_TYPE_MASK:
            return &RNA_MaskSequence;
        case SEQ_TYPE_SOUND_RAM:
            return &RNA_SoundSequence;
        case SEQ_TYPE_CROSS:
            return &RNA_CrossSequence;
        case SEQ_TYPE_ADD:
            return &RNA_AddSequence;
        case SEQ_TYPE_SUB:
            return &RNA_SubtractSequence;
        case SEQ_TYPE_ALPHAOVER:
            return &RNA_AlphaOverSequence;
        case SEQ_TYPE_ALPHAUNDER:
            return &RNA_AlphaUnderSequence;
        case SEQ_TYPE_GAMCROSS:
            return &RNA_GammaCrossSequence;
        case SEQ_TYPE_MUL:
            return &RNA_MultiplySequence;
        case SEQ_TYPE_OVERDROP:
            return &RNA_OverDropSequence;
        case SEQ_TYPE_MULTICAM:
            return &RNA_MulticamSequence;
        case SEQ_TYPE_ADJUSTMENT:
            return &RNA_AdjustmentSequence;
        case SEQ_TYPE_WIPE:
            return &RNA_WipeSequence;
        case SEQ_TYPE_GLOW:
            return &RNA_GlowSequence;
        case SEQ_TYPE_TRANSFORM:
            return &RNA_TransformSequence;
        case SEQ_TYPE_COLOR:
            return &RNA_ColorSequence;
        case SEQ_TYPE_SPEED:
            return &RNA_SpeedControlSequence;
        case SEQ_TYPE_GAUSSIAN_BLUR:
            return &RNA_GaussianBlurSequence;
        case SEQ_TYPE_TEXT:
            return &RNA_TextSequence;
        case SEQ_TYPE_CUSTOM_EFFECT:
            return &RNA_CustomEffectSequence;
        default:
            return &RNA_Sequence;
    }
}

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

1
extern StructRNA RNA_CustomEffectSequence;

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:

1
2
3
4
5
6
7
8
9
10
11
12
         col = layout.column(align=True)
        if strip.type == 'SPEED':
            col.prop(strip, "multiply_speed")
        elif strip.type in {'CROSS', 'GAMMA_CROSS', 'WIPE', 'ALPHA_OVER', 'ALPHA_UNDER', 'OVER_DROP'}:
            col.prop(strip, "use_default_fade", "Default fade")
            if not strip.use_default_fade:
                col.prop(strip, "effect_fader", text="Effect fader")
        elif strip.type == 'GAUSSIAN_BLUR':
            col.prop(strip, "size_x")
            col.prop(strip, "size_y")
        elif strip.type == 'CUSTOM_EFFECT':
            col.prop(strip, 'property')

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):

1
2
3
typedef struct CustomEffectVars {
    float property;
} CustomEffectVars;

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
            SEQ_BEGIN(ed, seq)
            {
                if (seq->strip && seq->strip->done == 0) {
                    /* write strip with 'done' at 0 because readfile */

                    if (seq->effectdata) {
                        switch (seq->type) {
                            case SEQ_TYPE_COLOR:
                                writestruct(wd, DATA, SolidColorVars, 1, seq->effectdata);
                                break;
                            case SEQ_TYPE_SPEED:
                                writestruct(wd, DATA, SpeedControlVars, 1, seq->effectdata);
                                break;
                            case SEQ_TYPE_WIPE:
                                writestruct(wd, DATA, WipeVars, 1, seq->effectdata);
                                break;
                            case SEQ_TYPE_GLOW:
                                writestruct(wd, DATA, GlowVars, 1, seq->effectdata);
                                break;
                            case SEQ_TYPE_TRANSFORM:
                                writestruct(wd, DATA, TransformVars, 1, seq->effectdata);
                                break;
                            case SEQ_TYPE_GAUSSIAN_BLUR:
                                writestruct(wd, DATA, GaussianBlurVars, 1, seq->effectdata);
                                break;
                            case SEQ_TYPE_CUSTOMEFFECT:
                                writestruct(wd, DATA, CustomEffectVars, 1, seq->effectdata);
                                break;
                            case SEQ_TYPE_TEXT:
                                writestruct(wd, DATA, TextVars, 1, seq->effectdata);
                                break;
                        }
                    }

                    writestruct(wd, DATA, Stereo3dFormat, 1, seq->stereo3d_format);

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
        case SEQ_TYPE_ADJUSTMENT:
            rval.supports_mask = true;
            rval.num_inputs = num_inputs_adjustment;
            rval.early_out = early_out_adjustment;
            rval.execute = do_adjustment;
            break;
        case SEQ_TYPE_GAUSSIAN_BLUR:
            rval.init = init_gaussian_blur_effect;
            rval.num_inputs = num_inputs_gaussian_blur;
            rval.free = free_gaussian_blur_effect;
            rval.copy = copy_gaussian_blur_effect;
            rval.early_out = early_out_gaussian_blur;
            rval.execute = do_gaussian_blur_effect;
            break;
        case SEQ_TYPE_TEXT:
            rval.num_inputs = num_inputs_text;
            rval.init = init_text_effect;
            rval.free = free_effect_default;
            rval.copy = copy_effect_default;
            rval.early_out = early_out_text;
            rval.execute = do_text_effect;
            break;
        case SEQ_TYPE_CUSTOMEFFECT:
            rval.init = init_custom_effect_effect;
            rval.num_inputs = num_inputs_custom_effect;
            rval.free = free_custom_effect_effect;
            rval.copy = copy_custom_effect_effect;
            rval.early_out = early_out_custom_effect;
            rval.execute = do_custom_effect_effect;
            break;

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í:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/*********************** Custom Effect *************************/
static void init_custom_effect_effect(Sequence *seq)
{
    if (seq->effectdata)
        MEM_freeN(seq->effectdata);

    seq->effectdata = MEM_callocN(sizeof(CustomEffectVars), "customeffectvars");
}

static int num_inputs_custom_effect(void)
{
    return 1;
}

static void free_custom_effect_effect(Sequence *seq)
{
    if (seq->effectdata)
        MEM_freeN(seq->effectdata);

    seq->effectdata = NULL;
}

static void copy_custom_effect_effect(Sequence *dst, Sequence *src)
{
    dst->effectdata = MEM_dupallocN(src->effectdata);
}

static int early_out_custom_effect(Sequence *seq, float UNUSED(facf0), float UNUSED(facf1))
{

    /* source/blender/blenkernel/BKE_sequencer.h */
    /* #define EARLY_NO_INPUT      -1 */
    /* #define EARLY_DO_EFFECT     0 */
    /* #define EARLY_USE_INPUT_1   1 */
    /* #define EARLY_USE_INPUT_2   2 */
   
    CustomEffectVars *UNUSED(data) = seq->effectdata;
    /* I will always do my effect */
    /* if (data->property == 0) { */
    /*  return EARLY_USE_INPUT_1; */
    /* } */
    return EARLY_DO_EFFECT;
}

// NUESTRO EFECTO !!!!!!!!!!!!!
static ImBuf *do_custom_effect_effect(const SeqRenderData *context, Sequence *seq, float UNUSED(cfra), float UNUSED(facf0), float UNUSED(facf1),
                                     ImBuf *ibuf1, ImBuf *ibuf2, ImBuf *ibuf3)
{
    ImBuf *out = prepare_effect_imbufs(context, ibuf1, ibuf2, ibuf3);

    CustomEffectVars *cv = (CustomEffectVars *)seq->effectdata;

    unsigned char *rect, *orig;
    int x= context->rectx;
    int y= context->recty;
    float prop = cv->property;

    if (out->rect) {
        orig = (unsigned char *)ibuf1->rect;
        rect = (unsigned char *)out->rect;
        while (y--) {
            x = context->rectx;
            while (x--) {
                rect[0] = (orig[0]+ prop*orig[1])/(1+prop);
                rect[1] = (orig[1]+ prop*orig[2])/(1+prop);
                rect[2] = (orig[2]+ prop*orig[1])/(1+prop);
                rect[3] = 220;
                rect += 4;
                orig += 4;
            }
        }
    }
    else if (out->rect_float) {
        orig = (unsigned char *)ibuf1->rect_float;
        rect = (unsigned char *)out->rect_float;
        while (y--) {
            x = context->rectx;
            while (x--) {
                rect[0] = (orig[0]+ prop*orig[1])/(1+prop);
                rect[1] = (orig[1]+ prop*orig[2])/(1+prop);
                rect[2] = (orig[2]+ prop*orig[1])/(1+prop);
                rect[3] = 0.5;
                rect += 4;
                orig += 4;
               
            }
        }
    }
    return out;
}

¡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.

The post Experimento: Creando un nuevo efecto de imagen para el secuenciador de vídeo de Blender (VSE) con código fuente appeared first on Poesía Binaria.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 273

marzo 13, 2017 08:41

Enlaces interesantesAquí tenéis una nueva colección de enlaces interesantes, con especial atención al lanzamiento de Visual Studio 2017 :)

Por cierto, a partir de ahora voy a utilizar otro criterio de categorización, uniendo .NET y .NET Core en una única categoría, y haciendo lo mismo con ASP.NET y ASP.NET Core, porque cada vez iba teniendo menos sentido mantener esta separación.

Como siempre, espero que estos links os resulten interesantes. :-)

.NET/.NET Core

ASP.NET/ASP.NET Core

Azure / Cloud

Conceptos/Patrones/Buenas prácticas

Data

Html/Css/Javascript

Visual Studio/Complementos/Herramientas

Cross-platform

Otros


Publicado en Variable not found

» Leer más, comentarios, etc...

Blog Bitix

Ejemplo encender y apagar diodo LED con la Raspberry Pi en Java

marzo 11, 2017 11:15

Raspberry Pi
Java

El primer ejemplo que haré de un programa Java que usa los pines GPIO para realizar algo con el kit de inicialización a la electrónica con la Raspberry Pi consiste en un pequeño programa Java que hace parpadear un diodo LED. La librería Diozero ofrece a los programas Java el acceso a los pines GPIO de la diferentes versiones de la Raspberry Pi desde la 1 (rev 1 y rev 2) pasando por los modelos B+, 2 y 3. Otra librería que se puede usar con el lenguaje de programación Java es Pi4J aunque personalmente Diozero me ha gustado más por ser de más alto nivel.

Algunas de las características que ofrece la librería Diozero son:

Una de las primeras cosas a conocer es como se numeran los pines en la Raspberry Pi ya que hay varias nomenclaturas (header, wiringPi y Broadcom) y que nomenclatura utiliza la librería Diozero. También deberemos tener en cuenta el modelo de la Raspberry Pi que poseamos ya que según el modelo hay pequeñas diferencias en algunos pines. Además si usamos una placa de extensión para pruebas sin sodadura como la wiringPi deberemos identificarlos por su nombre. Yo que poseo una de las primeras Raspberry Pi (la 1, rev1) el correspondiente su esquema de pines Raspberry Pi 1 (rev. 1) es el del enlace. En ese esquema se define que el pin número 12 según el conteo del header corresponde a GPIO 18 según la nomenclatura Broadcom y la librería Diozero y al GPIO 1 en la librería Pi4J y en la placa de extensión wiringPi.

Placas modelos Raspberry Pi 1 B y 3 B
Breadboard y placa extesión GPIO wiringPi de 26 pines y 40 pines
Esquema del cableado

Como uso la placa de extensión wiringPi para conectar los pines de la Raspberry Pi a otra placa para hacer pruebas sin soldadura también conocidas como breadboard resultará que en el programa Java al usar Diozero uso la nomenclatura Broadcom para identificar los pines pero al conectar los cables en la placa de pruebas uso la nomenclatura de wiringPi.

Para el ejemplo utilizaré la placa de extensión sin soldadura, una resistencia de 200 ohmios (dadas sus bandas de colores rojo, negro, marrón y dorado) y un diodo LED además de un par de cables macho-macho para realizar las conexiones electrónicas entre el GPIO 18 (según la nomenclatura de la librería Diozero y Broadcom, 12 según la nomenclatura del header y 1 según la de wiringPi) y la resistencia además de entre el diodo y la línea de tierra. Los diodos LED poseen una orientación y hay que conectar la resistencia con el polo positivo del diodo LED, el polo positivo del diodo LED identifica porque es la patita larga y el negativo con tierra es la patita corta. Si realizamos la conexión al revés solo pasará que el diodo no se enciende pero no lo estropeará, la resistencia si es necesaria para no hacer que pase por el diodo una intensidad que lo estropee como se explica en ¿Qué resistencia ooner a un LED?.

Diodo blanco y resistencia de 200 ohmios

Las resistencias poseen cuatro bandas de colores que indican el valor en ohmios de esa resistencia, la tabla de colores es el siguiente:

Código de colores de las resistencias

El programa Java para hacer parpadear el diodo LED con la librería Diozero con el proveedor pigpio. El ejemplo consiste en activar y apagar el pin sucesivamente en un bucle y usar el método Thread.sleep para que pase unos segundos entre uno y otro y nos de tiempo a ver el encendido y apagado. El nada complejo programa Java para controlar el diodo y un vídeo de su funcionamiento están a continuación.

En un artículo anterior comento como disponer de un entorno para desarrollar, desplegar las librerías jar en la Raspberry Pi y como ejecutar los ejemplos desde la línea de comandos usando una combinación de herramientas de SSH, rsync y Ansible.

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando el comando ./gradlew executePinBlink.

» Leer más, comentarios, etc...

Poesía Binaria

Edición de vídeo en GNU/Linux con software libre, ¿Qué características necesito para editar vídeo?

marzo 08, 2017 11:21

El momento ha llegado, tras tantos años sin pensar en ella. He vuelto a encontrármela, ahí de pie, delante de mí. Su nombre es Pandora y con una risa maliciosa viene a este humilde blog. A llenar nuestras almas de oscuridad aumentando el contraste y corrigiendo el color.

Aunque poco a poco he estado soltando pinceladas en el tiempo sobre imagen digital en GNU/Linux, vídeo digital, integración de algunas técnicas y demás. Pero creo que ha llegado el momento de hacer algo más grande y preparar una referencia mayor sobre software libre y edición de vídeo.

Mi experiencia personal y privativa

Hace muchos años que me adentré en el mundo de la edición de vídeo por ordenador. Como muchos de nosotros, empecé haciendo pequeñas animaciones en un modesto equipo del siglo pasado, sólo por hobbie y por hacer algo con mi tiempo (en aquella época en la que aún podía gozar de algo de tiempo libre). El programa que solía utilizar era Autodesk Animator. Estaba muy limitado, pero podías hacer muchas cosas. Era muy intuitivo.

Con el tiempo, fui conociendo otros programas de edición de vídeo, hasta que gracias a un amigo conocí el paquete de Ulead Videostudio. Esta persona se compró una tarjeta capturadora en aquella época en que las tarjetas de este tipo ocupaban todo el ancho de la torre y costaban un ojo de la cara y medio riñón… pero venían con mucho software para utilizar. Unos años más tarde fueron bajando de precio, aunque empezaron a ser un poco peores en la captura. Como los ordenadores de aquella época no eran tan rápidos, estas tarjetas solían llevar un chip compresor/descompresor MPEG integrado por lo que a medida que capturabas vídeo lo ibas recibiendo comprimido y todo… y como en todo, un mejor compresor generaba vídeos de menor tamaño con mayor calidad, y dado que el ancho de banda del bus es limitado llegaba un punto que una tarjeta capturadora barata no podía obtener mucha calidad. Con el tiempo terminé comprando una tarjeta de estas, de las baratas, no de las profesionales, aunque dentro de la gama de las baratas me fue muy bien.

Unos años más adelante, con capturadora nueva, no tenía cámara de vídeo y me pasé mucho tiempo pidiendo cámaras de vídeo. Si algún amigo tenía una cámara de vídeo, allá que iba yo cuando tenía algún proyecto para grabar. Me lo pasaba en grande montando historias para hacer pequeñas grabaciones en mi adolescencia. ¿Algunos ejemplos? (He perdido mucho material, tenía muchas cosas grabadas en CD y esos CDs cambiaron de plano existencial, fue una gran pérdida, no es lo mejor, pero sí lo que pude rescatar y lo que Youtube no me ha quitado por tema de licencias).

He perdido muchísimo material. Además, en 2003 hice mi paso definitivo a GNU/Linux. Aunque tenía la espinita del vídeo digital, me pudo más el hecho de dejar Windows de lado y llegó un punto en el que no arrancaba Windows para nada por lo que GNU/Linux pasó a ser el único sistema operativo de mi ordenador, y lo ha sido hasta el día de hoy.

Bueno, antes de nada decir que hasta 2003 más o menos, los programas de edición de vídeo para Windows tampoco destacaban por su estabilidad. Ya fuera por el propio sistema operativo, el uso intensivo de la memoria, los codecs y la complejidad de los sistemas, era común encontrarse con algún pantallazo azul de vez en cuando, o que el propio programa en el que estabas editando vídeo en el momento de más concentración se cierra inesperadamente dejándote todo el trabajo a medias. O, como me pasó con Huyendo del destino, el ordenador se reinició y cuando todo arrancó vi que los principales archivos del proyecto estaban corruptos (era mucho más largo y abandoné el proyecto).

Lo sé, había editores de vídeo para GNU/Linux

Soy consciente de ello, es más, de vez en cuando abría alguno e intentaba hacerme con él, aunque el problema, como en Windows era la estabilidad, aunque en este sistema era aún peor. Por un lado, los programas de edición no eran lo suficientemente maduros, casi todos acababan de nacer (incluyo como mucho hasta 2004, avisadme si falta alguno):

  • KDEnlive nació en 2002
  • Cinelerra nace en 2002
  • PiTiVi nace en 2003
  • Blender se libera en 2002, aunque no editaba vídeos
  • Shotcut nace en 2004
  • LiVES nace en 2003

Y es normal que tratándose de software libre, en este sentido estos desarrollos fueran bastante lentos. Además estábamos en una época cambiante, con respecto a codificación de vídeo (MPEG-4 estaba pisando fuerte, había nuevas formas de codificar audio y surgían formatos propios de Microsoft o Apple a los que había que dar soporte. También estaba entrando el DVD y los usuarios de GNU/Linux teníamos derecho a utilizar el DVD). Además, las cámaras empezaban a utilizar el IEEE1394 que era más rápido que USB para transmisión de datos… En definitiva, los desarrollos de codificación y soporte cambiaban muy a menudo, y quienes desarrollaban estos programas de vídeo al no estar amparados por ninguna empresa, encima recién nacidos los proyectos, era normal que no fuera la cosa tan estable como debería… y ya por no meternos en el tema de tarjetas gráficas.

El caso es que para 2006 pude volver a hacer algún montaje sencillo, con una estabilidad que más o menos me permitía no arrancarme el pelo de desesperación. En este caso con KDEnlive, un editor que he utilizado mucho y durante muchas horas, que me ha dado alegrías y penas y del que alguna vez he hecho algo en este blog.

También quiero dejar claro que, la industria del cine, según dice empieza a utilizar GNU/Linux en el año 2002 o incluso antes. Empresas como Industrial Light and Magic, Rhythm and Hues, Dreamworks, WetaDigital o Pixar dicen que utilizan GNU/Linux en algunos de sus procesos, eso sí con herramientas propias y/o privativas.

Estado del arte

Pero llega 2017. Y, sobre todo me animo a hacer esta serie de posts el hecho de que KDEnlive, después de algo más de dos años sin usarlo, en diciembre de 2016, lo he notado más estable que nunca, pude hacer un vídeo sin que me diera guerra el programa, incluso abrí un montaje hecho hace casi 5 años y no tuve ningún problema, bueno sólo un filtro que no estaba, pero era culpa mía que no lo instalé.
Por otro lado, Blender, que, aunque me gusta, tengo que decir que no siempre las versiones estables son estables, ni están libres de fallos tontos (los fallos ocurren, lo sabemos, pero hay algunos más tontos que otros, y a veces la mezcla de dos imágenes no funciona bien, o se ve claramente que se desborda un número y no se ha controlado un valor (lo siento, mi faceta de programador siempre está alerta). Pero abrí Blender 2.78 y me dio muy buena sensación, iba muy ligero y no se quejó para un pequeño montaje (cosa que antes sí que hacía).

Así que, quiero empezar este año a hacer un pequeño análisis y empezar a realizar tutoriales sobre edición de vídeo digital en GNU/Linux, efectos especiales, composición, etc. Es cierto que el software privativo en este sentido está a muchos años de distancia, pero vamos por buen camino, incluso hay cosas que podremos hacer con software libre y no con software privativo, aunque nos toque tener que programarlas.

Un pequeño vídeo

Digo edición de vídeo, pero no profundizo

Es un tema complejo y amplio que he metido en edición de vídeo, aunque sé que hay muchos subtemas que debemos tratar. Una cosa es el montaje, y otra la composición (aunque hay herramientas que las integran). Por otro lado tenemos la conversión, recorte, etalonaje (palabro de la vieja escuela, relativo a la corrección y tratamiento del color), análisis espacio-temporal, screencasting, stop-motion, edición y renderizado 3D, edición de audio y de imagen fija (sí, utilizaremos GIMP para preparar cosas), y mucho más. Tenemos para largo.
Y todo eso sin entrar en temas de producción donde encontramos software para creación de guiones, presupuestos, gestión de proyectos, storyboarding y muchísimo más.

¿Qué le pido a un editor de vídeo?

Hablo aquí de editores de vídeo no lineales, de los de toda la vida, para hacer montajes, en los que cogemos vídeos de origen, aplicamos un efecto y obtenemos un vídeo resultante.

Me quiero centrar en tres características:

Edición multicapa

Esto es, que podamos superponer varias capas de vídeo o imagen unas encima de otras para obtener una imagen resultante mezcla de todas. Podremos aplicar filtros a algunas imágenes intermedias, introducir transparencias, mover capas dentro de la imagen y mucho más.
Casi todos los editores suelen tener esta característica, mejor o peor, sólo quiero quitar del medio los programas que no valen para este tipo de edición, los que dan muchos problemas al realizarlo. No está bien que el día que necesitas 5 capas para montar un vídeo el programa empiece a ir extremadamente lento.

Proxys

Ahora editamos en resoluciones muy grandes. Es más, en el año 2000 se editaban algunas películas en 8K (y eso que ahora estamos con la novedad de las pantallas 4K). Eso sí mientras estamos editando, durante casi todo el proceso no nos importa tener las imágenes a la mayor resolución. Para conocer cambios de escena, hacer muchas tareas de corrección de color o mezclado no necesitamos las imágenes en alta resolución.
Así que un buen programa de edición de vídeo, aunque ocupe algo más de disco duro, y tarde un poco más al principio, nos permitirá generar vídeos en resoluciones más pequeñas y con algoritmos de fácil descompresión con el objetivo de facilitarnos el trabajo.
Imaginemos que mi cámara digital comprime los vídeos en MPEG-4 AVC (H.264, mal empezamos si queremos ser libres, pero bueno). El formato crea archivos de pequeño tamaño con nuestro vídeo, lo malo es que necesitamos un uso intensivo de CPU, incluso es más fácil reproducir un vídeo que extraer un fotograma suelto, y cuando estamos editando casi siempre estaremos extrayendo esos fotogramas por separado para realizar todo el trabajo de las capas. Muchas veces estos proxys suelen hacerse en MPEG-2, que a estas alturas se descomprime de forma muy sencilla, o en imágenes sueltas, JPEG, TIFF, PNG…). En definitiva, a lo mejor perdemos mucho tiempo en este trabajo. Además, si estamos trabajando en FullHD (1920×1080), seguramente la vista previa esté a un cuarto de resolución (960×540) o menos, por lo que, para crear las vistas previas, no merece la pena hacer todo el proceso en resolución completa, podemos coger la imagen a 960×540 y trabajar con ella, el resultado será el mismo y se harán un 25% de operaciones, lo que nos permitirá hacer cosas más complejas.

Eso sí, cuando vayamos a hacer el render definitivo, el programa cogerá los archivos originales. En hacer el render, no nos podemos precipitar. Es verdad que podremos optimizar y hay programas más rápidos que otros, pero lo realmente importante, creo que es el proceso de edición, el render siempre puede tirarse un ordenador varios días encendido, o podemos hacerlo en un servidor.

Máscaras

Esto es más de composición, pero me encanta que los editores tengan la posibilidad de incluir máscaras rápidas para hacer pequeños efectos rápidos. Con estas máscaras podremos dibujar zonas de la imagen que serán relevantes para nosotros descartando lo demás, que luego podremos superponer y hacer muchas tareas con ellas. Las máscaras deberían poder animarse, con lo que crearemos efectos de rotoscopía. Creo que hablaré sobre esto en un futuro post. Es un tema que me encanta.

¿Algo más?

Hay muchas más cosas que un editor debe tener, pero empecemos por estas tres. Por supuesto, deberíamos poder aplicar filtros de color, y corregirlo de forma precisa, seleccionar áreas que queremos con diferente color… aplicar efectos de barrido, ajustar velocidad de vídeos y muchas más cosas a las que me gustaría dedicarle algunos futuros posts y vídeos.

Programas para abrir boca

Sigue este enlace, donde veremos una colección de programas y recursos con los que editar vídeo y audio en GNU/Linux. El listado irá creciendo con enlaces a manuales, tutoriales y guías poco a poco.

Y tú, ¿qué software usas?

¿Qué software utilizas tú para tus montajes de vídeo? ¿Qué características son las imprescindibles para ti?

The post Edición de vídeo en GNU/Linux con software libre, ¿Qué características necesito para editar vídeo? appeared first on Poesía Binaria.

» Leer más, comentarios, etc...

Picando Código

Riff Studio – Aplicación Android para practicar música

marzo 07, 2017 01:00

Riff StudioSin duda las mejores aplicaciones son las que surgen a partir de una necesidad. Es el caso de Riff Studio, una aplicación para dispositivos Android hecha por y para músicos desarrollada por Bruno Azzinari. Su objetivo es facilitarnos practicar canciones.

Básicamente tenemos que armar una lista de temas agregándolos desde nuestro dispositivo. Desde ahí podemos reproducirlos y cambiarles el tono y velocidad.

Podemos bajarle la velocidad a una canción para practicarla e ir aumentando de a poco a medida que vaya saliendo mejor. Para práctica más intensa podemos incluso subirle la velocidad a una canción. También cambiar el tono si usamos una afinación distinta o para usa un registro distinto si estamos cantando.

Todos estos cambios se pueden hacer en tiempo real afectando a la reproducción en el momento y son independientes. El tono se puede cambiar en semitonos y la velocidad en porcentaje de la original.

Una de las características interesantes que se agregó en la última actualización es la de bucle. Podemos definir un segmento de la canción que queremos que se repita en un bucle. Esto es súper útil y personalmente lo he usado para sacar algún punteo complicado en la guitarra. Al poder repetir el mismo segmento y a una velocidad más lenta, no se nos escapa ni una nota.

Podemos exportar en formato mp3 la canción modificada para reproducirla desde cualquier otro reproductor digital y no depender del programa.

La interfaz es muy sencilla y cómoda de usar. El desarrollador está abierto a críticas, sugerencias y feedback en general, y lo pueden contactar en el mail brazzilabs@gmail.com con ideas.

RiffStudio en el Google Play Store

[See image gallery at picandocodigo.net]

» Leer más, comentarios, etc...

Bitácora de Javier Gutiérrez Chamorro (Guti)

La evolución de FileOptimizer

marzo 06, 2017 10:22

En FileOptimizer y los logotipos más populares, vimos las mejoras reales que obtiene FileOptimizer. Además, con cierta regularidad, os voy manteniendo al día sobre su evolución, y os voy contando algunos secretillos. En el artículo de hoy, vamos a ver como ha mejorado el grado de optimización que consigue FileOptimizer. Aunque la primera versión fue […]

La entrada La evolución de FileOptimizer aparece primero en Bitácora de Javier Gutiérrez Chamorro (Guti).

» Leer más, comentarios, etc...

Meta-Info

¿Que es?

Planeta Código es un agregador de weblogs sobre programación y desarrollo en castellano. Si eres lector te permite seguirlos de modo cómodo en esta misma página o mediante el fichero de subscripción.

rss subscripción

Sponsors

Puedes utilizar las siguientes imagenes para enlazar PlanetaCodigo:
planetacodigo

planetacodigo

Si tienes un weblog de programación y quieres ser añadido aquí, envíame un email solicitándolo.

Idea: Juanjo Navarro

Diseño: Albin