danigm's blog

De vez en cuando me da por grabar un vídeo de cómo dibujo con el inkscape y esas cosas, lo acelero, le pongo música y lo cuelgo en youtube. Pues bien voy a explicar cómo hago toda esa mierda, para cuando se me olvide, o por si alguien que esté aburrido lee estoy y le sirve de ayuda.
Grabación del vídeo
Para grabar lo que hago en mi escritorio utilizo una fantabulosa aplicación llamada gtk-recordmydesktop. Esta aplicación me genera un fichero out.ogv.
Conversión a avi y aceleración
El ogv está muy bien, pero si lo quieres subir a youtube o a algún sitio de estos es mejor convertirlo. Yo lo convierto a avi usando mencoder. Además acelero el vídeo para que no sea un tostón de 30min:
mencoder out.ogv -fps 250 -vf scale=640:375 -ovc lavc -o output.avi
Creación de cabecera
Para que el vídeo quede más bonito se le puede añadir una cabecera, una imagen con texto que salga antes del vídeo y que desaparezca con un fundido.
Para ello vamos a utilizar imagemagick, ffmpeg y mencoder.
Primero capturamos el primer frame del vídeo:
ffmpeg -i output.avi -vcodec png -vframes 1 -ss 00:00:00 -an -f rawvideo first.png
Luego con el gimp o con el editor que te de la gana, creamos la imagen de cabecera que vamos a introducir, del mismo tamaño que first.png (640x375). Recomiendo un texto claro y simple sobre fondo negro. Y a esta imagen la llamamos title.png.
Ahora ejecutamos el siguiente script en el mismo directorio donde están las imágenes title.png y first.png.
#!/bin/bash tmpname=tmpcrop.png ext=.png startimage=title.png endimage=first.png qual="100%" percent=100 counter=0 while (( percent > 0 )) do counter=$(($counter+1)) if [ $counter -lt 10 ] then n="0$counter" else n="$counter" fi composite -blend $percent -gravity center $startimage $endimage -quality $qual frame$n$ext percent=$(($percent-2)) done
Este script genera una animación de 50 frames de "fade out" de la imagen de cabecera hacia el primer frame, el fundido.
Ahora para que se pueda ver la cabecera, copiamos la imagen repetidas veces, unas 100 estará bien:
for i in $(python -c "print '\n'.join('%02d'%i for i in range(1,100))"); do cp title.png a$i.png ; done
Ahora borramos title.png y first.png y generamos un .avi a partir de todos los pngs que tenemos:
mencoder mf://*.png -mf fps=25:type=png -ovc lavc -lavcopts vcodec=mpeg4 -oac copy -o title.avi -ffourcc DX50
Todo junto, unir dos .avi
Para unir la cabecera con el vídeo usaremos ffmpeg y cat. Convertimos los dos vídeos a vídeos temporales mpeg:
ffmpeg -i output.avi -sameq main.mpeg ffmpeg -i title.avi -sameq title.mpeg
Y los vídeos mpeg se pueden unir con el comando cat:
cat title.mpeg main.mpeg > video.mpeg
Y lo volvemos a convertir a avi:
ffmpeg -i video.mpeg -sameq video.avi
Vamos a por el sonido
Ahora queremos añadirle una banda sonora, así que buscamos una canción bonita que vaya con nuestro vídeo y que dure algo más. Podemos ver la duración del vídeo reproduciéndolo con mplayer y pulsando oo.
El archivo de sonido se puede retocar con audacity para recortarlo, añadirle efectos de fadein fadeout al principio y al final, etc. Pero aquí voy a explicar como añadir el efecto de fadeout al final con sox, desde la consola.
Fadeout mp3 con sox
Para que quede bien el corte a la canción vamos a añadir el efecto de fundido en el sonido. Supongamos que tenemos la canción elegida y se llama soundtrack.mp3.
Lo primero es convertirla a wav para trabajar sobre ella:
lame --decode soundtrack.mp3 soundtrack.wav
Ahora supongamos que nuestro vídeo dura dos minutos y queremos que el fundido sea en los últimos 5 segundos:
sox soundtrack.wav st.wav fade t 0 120 5
El 120 son los dos minutos (en segundos) y el 5 son los 5 segundos de fundido.
El efecto fade con sox recibe cuatro parámetros:
fade [type] fade-in-length [stop-time [fade-out-length]]
Puedes ver más ejemplos y detalles sobre sox en su manual, man sox.
Ahora convertimos el wav a mp3:
lame st.wav
Añadir banda sonora. Adjuntar audio a video
Para finalizar vamos a unir el mp3 que hemos editado con el vídeo. Para ello vamos a utilizar mencoder:
mencoder video.avi -o final.avi -ovc copy -oac copy -audiofile st.wav.mp3
Y con esto y un bizcocho tendremos un vídeo genial para subir a youtube o a donde haga falta, y todo hecho desde la línea de comandos, por lo que podríamos crearnos un megascript que le metiera una cabecera y unos créditos a nuestros vídeos de manera automatizada.

Poco a poco, día a día, nos van quitando cosas por las que alguien luchó para que tuvieramos. Yo (y supongo que mi greneración) he crecido en un mundo de abundancia, de derechos, donde no he tenido que trabajar hasta los veintipico años, donde no he tenido que compartir cama con ningún hermano, e incluso he tenido habitación propia. Pensamos que siempre ha sido así, que la guardia civil ha tratado a la gente con respeto y nunca ha pegado una paliza a alguien por estar en el lugar equivocado y preguntar después.
Todo eso es muy bonito, y es nuestro mundo, lo que hemos conocido. En el colegio hemos estudiado historia. Pero la historia que se estudia es impersonal, son guerras, reyes, conflictos y eventos que quedan registrados en los periodicos, pero no sabemos cómo vivieron nuestros padres, cómo vivieron nuestros abuelos. Por conseguir qué derechos lucharon, no tenemos ni idea de cómo vivian y nos dedicamos a decir que antes se vivía mejor.
Escuchando a los mayores, pidiendo que nos cuenten historias, aprenderemos de dónde venimos, y qué es lo que tenemos, por qué han luchado y qué es lo que deberíamos defender con nuestra vida porque muchos murieron para el mundo fuera hoy un poco mejor y a nosotros nos arrebatan poco a poco, día a día, sin que nos demos cuenta, los derechos fundamentales.
Miro la sociedad de hoy en día y veo un problema grande, están separando al pueblo. Nos están volviendo a unos contra otros, desconfiados, todos son el enemigo, el mundo es muy peligroso, no salgas de tu casa, no hables con extraños, todo el mundo quiere engañarte. El hombre como individuo no tiene ningún poder contra nada, es facilmente silenciable y se puede comprar con dinero. Sin embargo, cuando nos reunimos, cuando nos asociamos, cuando no somos un hombre, sino un grupo de personas, el poder es infinito, es imparable, no se puede silenciar a un grupo, ni se puede comprar.
Es por eso que debemos hablar, asociarnos, formar grupos de gente con los mismos intereses para luchar contra el "poder".
Muchas veces he pensado que yo era la única persona que pensaba que las consas estaban mal, y seguramente a muchísima gente se le habrá pasado por la cabeza, pero al pensar que estamos solos no podemos hacer nada, sin embargo si escuchas un poco, si cuentas tus pensamientos, te das cuenta de que mucha más gente comparte lo mismo que tú y entonces sentirás esa sensación de poder, de valor, que da la "manada", el saber que tus palabras están respaldadas por más personas.
Aprende de tus mayores, organízate para defender tus derechos, porque firmes un contrato no eres esclavo de nadie, es simplemente un acuerdo de prestación de servicios. El paro no es el problema, el problema es el hambre.
Nunca he pasado hambre, nunca he temido por mi vida, así que no me hables de crisis ni del fin del mundo, que todavía queda mucho para eso.

Ahora que estoy trabajando con django estoy viendo lo potente que es, pero su sintaxis y forma de hacer las cosas no me acaba de convencer del todo comparandolo con web.py.
Una de las cosas que me gustan mucho de web.py es la forma de definir las vistas, que consiste en una clase donde defines el método GET y el método POST (no son obligatorios) y estos métodos son llamados según sea la petición http. Esto es muy util a la hora de hacer vistas de formularios, que si reciben la petición por GET muestran el formulario y si la reciben por POST hacen lo que sea con los datos, y en django suele haber un if request.method == 'POST' que guarrea la vista completamente.
Por eso hoy me he puesto y he hecho una pequeña prueba de concepto para poder permitir una definición similar en django, y así poder tener una mejor reestructuración del código de las vistas.
Lo he puesto en django snippets, pero también lo voy a poner aquí y lo comento:
from django.http import HttpResponse as response from django.http import HttpResponseNotAllowed class ViewClass: def __call__(self, request, *args, **kwargs): self.request = request methods = ['POST', 'GET'] self.methods = [method for method in dir(self)\ if callable(getattr(self, method)) and method in methods] if request.method in self.methods: view = getattr(self, request.method) return view(*args, **kwargs) else: return HttpResponseNotAllowed(self.methods) class IndexView(ViewClass): def GET(self): return response("all ok %s" % self.request.method) def POST(self): return response("all ok %s" % self.request.method) index = IndexView()
¿Qué es lo que he hecho? He definido mi vista IndexView como una clase, que hereda de "ViewClass" (ahí está la mágia), donde me he definido mis dos métodos, GET y POST, igual que si lo hubiera hecho con web.py, suponiendo que GET se llamará cuando la petición sea GET y POST cuando sea POST, y el request en lugar de recibirlo como parámetro es un atributo de la clase, así que accedo a él con self.request.
Luego defino la vista como index = IndexView(), una instancia de la clase IndexView.
ViewClass es la clase de la que heredamos y la que implementa el método __call__. En python, si un objeto tiene el método __call__ es callable, y se puede llamar igual que una funcion. Es más, en python, las funciones son objetos que tienen definido el método __call__. Por tanto, como django espera que la vista sea una función, no una clase, django va a llamar al método call y le va a pasar los argumentos de la vista.
En el método __call__ lo que se hace es mirar si el tipo de petición HTTP está implementado en la clase y es uno de los válidos (methods) y si existe en la clase un método con ese nombre, lo llama pasandole los argumentos y poniendo previamente self.request = request. En caso de que el método no esté implementado devuelve responsenotallowed.
Para terminar, quiero comentar que otra de las cosas que no me gusta de django es el sistema de templates, con tantas llaves y porcentages por ahí, me parece mucho más elegante templetor, pero también es verdad que los templates de django son muy potentes y se pueden hacer muchas cosas.


En los aeropuertos se están poniendo ya muy cabrones, que te hagan pasar por un control, vale, por un detector de metales, vale, que te quiten las botellas de agua y los batidos de chocolate ... vale, que te cacheen .......... vale, que te pregunten de dónde vienes y a dónde vas y para qué ... .. .. .. .. .. vale, que te miren con un escaner que te desnuda ..... .. .. .... .. . vale, que te metan el dedo en el culo por si llevas una bomba. .. .. ... .. ..... . .. ... vale. ¿Hasta dónde vamos a llegar? Bueno, cada cual tendrá sus límites.

Ya estamos llegando al final del año 2009, y mi avatar está ya un poco anticuado, así que me he puesto a dibujar un nuevo avatar para el nuevo año y este ha sido el resultado:

Aquí está el video del proceso con musiquita y todo:
La canción es "Cerca de Shibuya" de "La casa azul".
Este dibujo está realizado en un "asus eee901" con Archlinux con el trackpad usando inkscape 0.46, grabado con gtk-recordmydesktop y editado con mencoder. El audio ha sido editado con audacity.

El otro día leí en reddit que emacs se pasaba a bazaar y en los comentarios leí que bazaar no es lo mejor del mundo. Yo ya lo sabía, y después de haber tratado durante algún tiempo con bazaar me decidí a cambiar todos mis repositorios a git.
¿Cómo hacer eso? pues fué más fácil de lo esperado, con el plugin de bazaar fastimport y el comando git import. Se instala fácilmente descargandolo "bzr branch lp:bzr-fastimport fastimport" y copiando el directorio a "~/.bazaar/plugins/". Ahora deberíamos tener accesible el comando bzr fast-export.
En mi servidor tengo todas mis ramas dentro de un directorio llamado branchs y quería crear un directorio llamado gits con los mismos proyectos pero portados. Para ello me he creado un script que se encarga de hacer todo lo necesario:
#!/bin/bash root=$PWD cd $root/branchs for i in * do newdir=$root/gits/$i.git mkdir $newdir cd $newdir git init --bare bzr fast-export $root/branchs/$i | git fast-import git update-server-info done
Esto exporta todos los repositorios de bazaar a git.
Y para mostrar todas las ramas de git que tengo me he instalado gitPHP, porque intenté configurar gitweb, pero con lighttpd no he podido hacer funcionar este porque es un script perl, he intentado seguir las instrucciones para configurar gitweb con lighttpd, pero no me muestra la página, me da el fichero para descargar.
Sobre la decisión de usar git en lugar de bazaar, es una decisión totalmente arbitraria, con poquísimo fundamento, bien podría haber sido mercurial o lo que sea, pero git está siendo utilizado por grandes proyectos como gnome o kde y se supone que es el más potente, así que por qué no.

Hoy edulix me ha pedido que le haga un logo para su nuevo proyecto en el CUSL4. El proyecto en concreto se llama RoboDo, y pretende acercar la potencia de los scripts a usuarios no programadores, presentando una interfaz gráfica y una manerá más o menos simple para generar "scripts" que actúen con el escritorio Plasma KDE :P
El proyecto se describe así:
RoboDo es una aplicación de KDE que permite crear flujos de trabajo gráficamente para automatizar tareas repetitivas.
Y este es el logo que he hecho:


El otro día un amigo me preguntó como hacer consultas a una base de datos mysql desde python y le comenté que con sqlalchemy se podía hacer. Yo ya había hecho algunas cosas con sqlalchemy, pero no había tratado con bases de datos ya creadas, sino que siempre las creaba con sqlalchemy.
He estado mirando un poco en la web de sqlalchemy y me he encontrado SqlSoup (SqlAlchemy).
SqlSoup mapea una base de datos ya creada a objetos python, así que no es necesario crear las clases o declarar las tablas en código python, se infieren directamente de la base de datos ya creada.
Veamos como funciona con un simple ejemplo con sqlite.
-
Creamos la base de datos sqlite para las pruebas
- $ sqlite3 testdb.sqlite
- sqlite> create table users (name varchar(50), email varchar(100), primary key (name));
- sqlite> create table posts (id integer not_null auto_increment, user_id varchar(50),
- ...> text varchar, primary key (id), foreign key (user_id) references users(name));
-
Aquí un script de prueba para ejecutar
- # <a href="http://www.sqlalchemy.org/docs/05/reference/ext/sqlsoup.html<br />
- #" title="http://www.sqlalchemy.org/docs/05/reference/ext/sqlsoup.html<br />
- #">http://www.sqlalchemy.org/docs/05/reference/ext/sqlsoup.html<br />
- #</a> Mapeando una base de datos ya creada para usar desde python de
- # manera simple y rapida.
- # creamos la base de datos con sqlite3
- # sqlite3 testdb.sqlite
- # sqlite> create table users (name varchar(50), email varchar(100), primary key (name));
- # sqlite> create table posts (id integer not_null auto_increment, user_id varchar(50),
- # ...> text varchar, primary key (id), foreign key (user_id) references users(name));
- from sqlalchemy.ext.sqlsoup import SqlSoup, Session
- from sqlalchemy import or_, and_, desc
- #db = SqlSoup('mysql://scott:tiger@localhost/mydatabase')
- db = SqlSoup('sqlite:///testdb.sqlite')
- def add_users():
- user = db.users.insert(name='danigm', email='dani@exp.com')
- user2 = db.users.insert(name='dani', email='danigm@exp.com')
- user3 = db.users.insert(name='pepe', email='pepe@exp.com')
- user4 = db.users.insert(name='juan', email='juan@exp.com')
- db.flush()
- Session.commit()
- def add_posts():
- post1 = db.posts.insert(id=1, user_id='danigm', text='comentario1')
- post2 = db.posts.insert(id=2, user_id='danigm', text='comentario2')
- post3 = db.posts.insert(id=3, user_id='pepe', text='comentario3')
- post4 = db.posts.insert(id=4, user_id='juan', text='comentario4')
- db.flush()
- Session.commit()
- try:
- add_users()
- except:
- Session.rollback()
- try:
- add_posts()
- except:
- Session.rollback()
- print db.users.filter(db.users.name=='danigm').first().name
- where = or_(db.users.name=='danigm',
- db.users.email=='danigm@exp.com')
- users = db.users.filter(where).order_by(desc(db.users.name)).all()
- print users
- user = users[0]
- for post in db.posts.filter(db.posts.user_id == user.name):
- print user.name, post.text
-
Veamoslo paso a paso
- db = SqlSoup('sqlite:///testdb.sqlite')
- user = db.users.insert(name='danigm', email='dani@danigm.net')
- db.flush()
- Session.commit()
- print db.users.filter(db.users.name=='danigm').first().name
- where = or_(db.users.name=='danigm',
- db.users.email=='danigm@exp.com')
- users = db.users.filter(where).order_by(desc(db.users.name)).all()
- print users
- user = users[0]
- for post in db.posts.filter(db.posts.user_id == user.name):
- print user.name, post.text
Con esta simple línea tenemos el objeto db que está conectado con la base de datos y ya podemos empezar a hacer consultas u otras cosas.
Podemos acceder a las tablas por su nombre directamente, db.users, db.posts y estos objetos tienen métodos para insertar, borrar o filtrar de tal forma que podamos hacer la consulta deseada, en este ejemplo añadimos un usuario. Hay que tener en cuenta que para que los cambios se vean reflejados hay que ejecutar el commit().
se puede filtrar como en cualquier objeto de sqlite, en este ejemplo filtramos por nombre de usuario, nos quedamos con el primero y mostramos el nombre del mismo.
Y una vez dominado el uso de sqlalchemy se pueden hacer filtros más complejos con otras directivas. combinando filtros y usando toda la potencia que te da sqlalchemy.
Con este ejemplo vemos que acceder a bases de datos desde python es muy fácil con sqlalchemy y además puedes hacerlo independiente del motor de base de datos gracias a que sqlalchemy soporta mysql, postgresql, sqlite, oracle, firebird, MS-SQL, MSAccess.

Los libros de Terry Pratchett esconden muchísimas perlas, aquí dejo una que acabo de leer:
Al fin y al cabo, nunca se puede hacer planes para cada eventualidad, porque eso requeriría saber lo que va a ocurrir, y si uno supiera lo que va a ocurrir, probablemente podría asegurarse de que no ocurriera, o por lo menos de que le ocurriera a otro. Así que el patricio nunca hacía planes. Los planes a menudo se convertían en un estorbo.




