Cómo desactivar el ajuste automático de volumen de micrófono en Skype y GTalk

Haciendo audioconferencia me he encontrado a menudo con que el volumen del micrófono se intenta ajustar automáticamente a un volumen óptimo según el nivel de la señal. Pero cuando hay una pausa en la conversación o se cuela algún ruidito leve, el volumen se sube muchísimo y produce un montón de ruido o satura la línea. Esto hace que esta feature se vuelva inusable, y la quiero desactivar.

Buscando en google he visto que esto no es algo del sistema, sino una función que hacen tanto Skype como Google Talk (uso ambos). Para desactivarlo en Skype basta con ir a Opciones / Dispositivos de sonido y desmarcar la casilla “Permitir que Skype ajuste automáticamente mis niveles de mezclado“.

En Google Talk, en cambio, no hay una opción de interfaz para desactivar esto. Pero se puede hacer editando el siguiente fichero (en Linux):

~/.config/google-googletalkplugin/options

y añadiendo (o modificando) la siguiente línea

audio-flags=1

Si el fichero options no existe, se puede crear nuevo y añadir esa línea dentro.

Fuente de la información: How to stop Google Talk Plugin auto adjust Microphone volume ?!

Hecho esto, ahora para cambiar el nivel del micro habrá que hacerlo a mano desde la herramienta de control de volumen que tenga nuestro sistema.

Anuncios

¿Cómo usar búsqueda de texto en PostgreSQL?

PostgreSQL tiene unas funciones bastante potentes para búsqueda de texto (Full Text Search), es decir, búsqueda de palabras claves estilo Google. Su uso es relativamente complejo, pero hay una buena documentación en la propia web: Chapter 12. Full Text Search.

Sin embargo, he escrito un resumen para gente con prisa, con las opciones más frecuentes. Helo aquí:

¿Qué es un documento?

Es la unidad mínima de búsqueda. En Postgres, un documento es un campo en una fila de una tabla, o quizá una concatenación de varios campos, de una misma tabla o de más de una con un join:

SELECT m.title || ' ' || m.author || ' ' || m.abstract || ' ' || d.body AS document
 FROM messages m, docs d
 WHERE m.id = d.id AND m.id = 12;

Al combinar campos suele ser conveniente usar la función coalesce para convertir los valores NULL en ”, de otro modo si un campo es nulo, el documento entero será nulo, por la propiedad de que tiene el valor NULL de que genera NULL al participar en cualquier expresión.

SELECT coalesce(m.title,'') || ' ' || coalesce(m.author,'') || ' ' ||
 coalesce(m.abstract,'') || ' ' || coalesce(d.body,'') AS document
 FROM messages m, docs d
 WHERE m.id = d.id AND m.id = 12;

¿Qué es un tsvector?

Es un tipo de datos de Postgres, que consiste en una lista de palabras extraidas de un documento. Las palabras están normalizadas, es decir, se eliminan las palabras “stopwords” (artículos, conjunciones, etc.) y los signos de puntuación, y el resto de palabras se reducen a su lexema básico. Finalmente se añade a cada palabra en qué posición o posiciones del documento aparece (también se puede añadir un peso, pero esto no lo veremos aquí).

Existe la función to_tsvector que convierte un string a un tsvector:

SELECT to_tsvector('english', 'a Fat  Cat sat on a mat - it ate a fat rats');
                    to_tsvector
 -----------------------------------------------------
 'ate':9 'cat':3 'fat':2,11 'mat':7 'rat':12 'sat':4

-> se han eliminado a, on, it, –
-> se ha convertido rats a rat
-> se ha convertido todo a minúscula y sin acentos (en este caso sin efecto porque el original no los tenía)

El parámetro ‘english’ indica qué ‘text configuration’ se va a usar para procesar las palabras. Postgres incorpora un montón de plugins con diccionarios, parseadores y otros trucos que procesan texto. Una configuración es una lista de plugins a aplicar, se puede definir con el comando CREATE TEXT SEARCH CONFIGURATION. Un ejemplo en 2.7. Configuration Example.

Más abajo hacemos un resumen de las opciones de configuración que hay.

Si no se indica la configuración, se toma la que tenga nuestra base de datos por defecto, o si no, la de nuestra instalación de Postgres. En una b.d. puede haber tantas configuraciones como queramos.

¿Qué es un tsquery?

Es otro tipo de datos, que también contiene una lista de palabras normalizadas, esta vez organizadas en forma de expresión booleana, con operadores & (and), | (or) y ! (not), y paréntesis.

También existe una función to_tsquery para convertir un string en un tsquery:

SELECT to_tsquery('english', 'Fat & (Rat | ! Cat)');
         to_tsquery
 ---------------------------
 'fat' & ( 'rat' | !'cat' )

Esta función necesita que el string original tenga un formato estricto. Hay otra función para convertir desde un string arbitrario (por ejemplo, lo que un usuario ha introducido en una caja de búsqueda). Es un modo más básico porque simplemente añade el operador & a todas las palabras.

SELECT plainto_tsquery('english', 'The Fat Rats');
  plainto_tsquery
 -----------------
 'fat' & 'rat'

¿Cómo se usan?

El uso más habitual es como filtro en una cláusula where, mediante el operador @@. Este operador aplica un tsquery contra un tsvector (en cualquier orden) y dice si coincide o no.

SELECT title
 FROM pgweb
 WHERE to_tsvector(body) @@ to_tsquery('friend');

En este ejemplo usamos la configuración por defecto en ambos casos. Otro ejemplo más elaborado:

SELECT title
 FROM pgweb
 WHERE to_tsquery('create & table') @@ to_tsvector(coalesce(title,'') || ' ' ||
                                                   coalesce(body,''))
 ORDER BY last_mod_date DESC
 LIMIT 10;

Esta query devuelve las 10 páginas más recientes que contienen las palabras “create” y “table” en el título o el body. Tal como está, Postgres debe leer todas las filas y convertir los datos a tsvector cada vez. Para acelerar la cosa, lo normal es usar un índice de texto.

¿Cómo se crea un índice de texto?

Hay dos tipos de índice de texto, GIN y GIST. Explicaremos las diferencias más abajo. Ambos se crean igual:

CREATE INDEX pgweb_idx ON pgweb USING gin(to_tsvector('english', body));
CREATE INDEX pgweb_idx ON pgweb USING gist(to_tsvector('english', body));

Importante: en el índice siempre hay que indicar la configuración (en este caso ‘english’). Sólo se usará el índice si en la expresión ts_vector del where hemos indicado explícitamente la configuración, y ésta coincide con la del índice.

En el caso de que nuestra búsqueda de texto use varias columnas, hay dos opciones:

a) Crear un índice compuesto:

CREATE INDEX pgweb_idx ON pgweb USING gin(to_tsvector('english', coalesce(title,'') || 
                                                      ' ' || coalesce(body,'')));

Así la segunda query del ejemplo de arriba usaría este índice, siempre y cuando le añadamos la configuración ‘english’ a la función to_tsvector.

b) Crear un campo agregado de tipo tsvector y copiar ahí los datos ya convertidos:

ALTER TABLE pgweb ADD COLUMN textsearchable_index_col tsvector;
UPDATE pgweb SET textsearchable_index_col =
           to_tsvector('english', coalesce(title,'') || ' ' || coalesce(body,''));

Este campo hay que mantenerlo actualizado bien mediante el código de nuestra aplicación, o creando un trigger de Postgresql usando la función tsvector_update_trigger, como se explica en 12.4.3. Triggers for Automatic Updates.

Luego ya se puede crear el índice y buscar por ese campo:

CREATE INDEX textsearch_idx ON pgweb USING gin(textsearchable_index_col);

SELECT title
 FROM pgweb
 WHERE to_tsquery('create & table') @@ textsearchable_index_col
 ORDER BY last_mod_date DESC
 LIMIT 10;

Las diferencias entre ambos enfoques son:

– El campo agregado ocupa más espacio, ya que hay que duplicar los datos. Además es una solución más complicada por la sincronización entre unos campos y otros.
– Usando el campo agregado no hace falta indicar la configuración, se pueden hacer queries con la config por defecto.
– La velocidad en principio es la misma, pero si usamos un índice GIST, Postgres tiene que recalcular la fórmula en todas las filas encontradas, porque no es un índice determinista (genera falsos positivos), con lo que el índice compuesto es más lento. Con el índice GIN también hay que recalcular si se usan pesos, porque éstos no se guardan en el índice. Pero si no se usan, entonces es más o menos igual de rápida la solución a) que la b).

¿Qué índice debo usar, GIST o GIN?

Los índices GIST usan un hash de longitud fija, que es bastante eficiente en espacio. Pero puede ocurrir que varios documentos generen el mismo hash, por lo que en una búsqueda aparecerán ambos cuando quizá sólo se esté buscando uno de ellos. Por tanto, Postgres recorre todas las filas devueltas por el índice y calcula de nuevo el filtro en memoria para eliminar los sobrantes. Esta operación es lenta, y además, la lectura de las filas de la tabla que no son necesarias es más lenta aún. La probabilidad de conflicto aumenta cuantas más palabras distintas haya en la tabla, por lo que estos índices son buenos cuando los documentos no tienen muchas palabras (por debajo de 10.000). Es útil además definir una buena configuración que elimine todas las palabras posibles y normalice mucho.

Los GIN en cambio, no tienen estas limitaciones, pero ocupan bastante más espacio, y son más lentos de actualizar, aunque son más rápidos de leer. La regla general suele ser usar GIN si los datos cambian poco o si hay muchas palabras distintas, y GIST para datos muy dinámicos pero sin demasiadas palabras, o si el espacio es muy importante.

Hay un análisis más completo en 2.9. GiST and GIN Index Types.

¿Cómo ordenar los resultados por relevancia?

Hay dos funciones predefinidas (ts_rank y ts_rank_cd) que calculan la relevancia de un documento respecto de un tsquery, en función del nº de veces que se encuentra cada término de búsqueda, la posición dentro del documento, etc. Cada una usa un algoritmo diferente, aunque también se pueden definir funciones propias si queremos usar nuestro propio algoritmo. Se usan así:

SELECT title, ts_rank_cd(textsearch, query) AS rank
 FROM apod, to_tsquery('neutrino|(dark & matter)') query
 WHERE query @@ textsearch
 ORDER BY rank DESC
 LIMIT 10;
                 title                         |   rank
 ----------------------------------------------+----------
 Neutrinos in the Sun                          |      3.1
 The Sudbury Neutrino Detector                 |      2.4
 A MACHO View of Galactic Dark Matter          |  2.01317
 Hot Gas and Dark Matter                       |  1.91171
 The Virgo Cluster: Hot Plasma and Dark Matter |  1.90953
 Rafting for Solar Neutrinos                   |      1.9
 NGC 4650A: Strange Galaxy and Dark Matter     |  1.85774
 Hot Gas and Dark Matter                       |   1.6123
 Ice Fishing for Cosmic Neutrinos              |      1.6
 Weak Lensing Distorts the Universe            | 0.818218

Aquí buscamos los 10 documentos con más puntuación al buscar “neutrino” o “materia oscura” en la columna agregada “textsearch” de la tabla apod.

Para ver los distintos algoritmos y las opciones de personalización con pesos y varias formas de normalización, ver 12.3.3. Ranking Search Results.

¿Cómo se usan las configuraciones de búsqueda?

Una configuración es lo que convierte un string general en un tsvector. Está compuesta de un “parser” (que divide el string en tokens) y una lista de “diccionarios”, que procesan los tokens y los convierten en lexemas básicos.

Una instalación de PostgreSQL incluye varias configuraciones por defecto, y se pueden crear configuraciones nuevas en una base de datos. La variable de configuración default_text_search_config (definida en postgresql.conf o con un comando SET) indica la configuración a usar si no se indica una en una llamada a to_tsvector o to_tsquery. Para saber qué configuraciones vienen precargadas, se puede consultar el catálogo pg_catalog con pgAdminIII, por ejemplo.

Crear una configuración nueva

Lo normal será crearla copiando otra existente y luego modificándola, por ejemplo:

CREATE TEXT SEARCH CONFIGURATION public.pg ( COPY = pg_catalog.english );

Pero se puede crear desde cero con

CREATE TEXT SEARCH CONFIGURATION public.pg ( PARSER = "default" );

Usar un parser

PostgreSQL trae un parser por defecto que suele ser suficiente. Analiza un string y lo divide en una lista de tokens de distintos tipos, que se pueden ver en 12.5. Parsers.

Usar diccionarios

Un diccionario recibe un token y lo convierte en un lexema final, o en otro token, aplicando una transformación, o bien lo descarta (si por ejemplo es una stop word). Para añadirlo a una configuración hay que decirle a qué tipos de tokens se va a aplicar:

ALTER TEXT SEARCH CONFIGURATION pg
  ALTER MAPPING FOR asciiword, asciihword, hword_asciipart, word, hword, hword_part
  WITH pg_dict, english_ispell, english_stem;

Este comando hace que a todos los tokens de tipo asciiword, asciihword, etc. se les apliquen los diccionarios pg_dict, english_ispell y english_stem, en ese orden. Los diccionarios se van aplicando hasta que alguno reconoce el token y devuelve un lexema o lo descarta, en cuyos casos se detiene la búsqueda.

Para eliminar diccionarios:

ALTER TEXT SEARCH CONFIGURATION pg
    DROP MAPPING FOR email, url, url_path, sfloat, float;

Esto hace que a los tokens de tipo email, url, etc. no se les aplica ningún diccionario, y por tanto son eliminados en el tsvector.

Crear un diccionario nuevo

Para crear un diccionario se usa el comando CREATE TEXT SEARCH DICTIONARY. Hay que indicarle una template y uno o más ficheros de configuración. Las templates están en el directorio contrib de Postgres, y los ficheros están en $SHAREDIR (ejecutar pg_config --sharedir para saber dónde está). Cada template tiene un formato distinto. Se pueden ver todos los templates incluidos en PostgreSQL en 12.6 Dictionaries.

  • Template “simple”. Mira si el token es una stopword; si lo es, lo descarta y si no, lo convierte a minúsculas.
  • Template “synonym”. Busca el token en una lista de sinónimos, y si lo encuentra, devuelve el sinónimo.
  • Template “thesaurus”. Similar a synonym pero más potente (busca frases, y reemplaza en función de si una palabra está junto a otras concretas o no).
  • Template “ispell”. Usa un fichero de diccionario “morfológico” para normalizar formas lingüisticas (plurales, género, tiempos de los verbos, etc.).
  • Template “snowball”. El mismo efecto que ispell, pero en vez de basarse en un diccionario usa algoritmos basados en reglas y expresiones regulares.
  • Template “unaccent”. Usa un fichero de mapeo para convertir todas las letras con acentos en una sin acentos.

Esta última template no está en el PostgreSQL por defecto, se añade instalando la extensión “unaccent” (en Ubuntu está incluida en apt-get install postgresql-contrib). Para activarla en nuestra instalación de PostgreSQL hay que escribir:

psql -c "create extension unaccent;" template1

Al hacerlo, se añade también una función “unaccent()” que se puede usar en sentencias SQL. Tanto la template como el diccionario no se crean en pg_catalog sino en el schema public. Más información en F.44. unaccent.

Un ejemplo. Para crear un diccionario de sinónimos basado en el fichero $SHAREDIR/tsearch_data/pg_dict.syn (que convierte los strings “postgresql”, “postgres” y “pgsql” en “pg”), ejecutar:

CREATE TEXT SEARCH DICTIONARY pg_dict (
    TEMPLATE = synonym,
    SYNONYMS = pg_dict
);

Con esto ya se puede incluir en la configuración nueva, tal como se veía arriba en “crear una configuración nueva”.

Instalar PIL en un virtualenv de python, y que importe correctamente las librerías de jpeg, png, etc.

PIL es una librería bastante útil en muchos casos. Se apoya en varias librerías externas para manejar diferentes formatos de imagen:

  • libjpeg para manejar imágenes JPEG (en Ubuntu 11.04 aptitude install libjpeg62 libjpeg62-dev).
  • zlib para manejar imágenes PNG (en Ubuntu 11.04 aptitude install zlib1g zlib1g-dev).
  • etc.

La manera más lógica de instalarlo debería ser instalar primero esas dependencias en el sistema, como explica aquí: http://www.eddiewelker.com/2010/03/31/installing-pil-virtualenv-ubuntu/, y luego instalar PIL con “pip install PIL”. Es necesario instalar la versión “-dev” para que incluya las cabeceras C. En una distribución tipo Debian sería:

$ sudo apt-get install libjpeg62 libjpeg62-dev
$ sudo apt-get install zlib1g-dev
$ sudo apt-get install libfreetype6 libfreetype6-dev

El problema es que el instalador tiene un algoritmo bastante cutre para localizar estas librerías en el sistema, y a menudo no las encuentra. Por ejemplo en Arch las encuentra bien, pero en Ubuntu no. Mi problema ahora mismo es instalarlo dentro de un virtualenv con pip install. El comando pip se descarga correctamente la librería y la compila bien, pero al no encontrar esas dependencias, me sale el siguiente mensaje:

--------------------------------------------------------------------
*** TKINTER support not available
*** JPEG support not available
*** ZLIB (PNG/ZIP) support not available
*** FREETYPE2 support not available
*** LITTLECMS support not available
--------------------------------------------------------------------

He visto tres soluciones, de más complicada a menos:

    1. Descargar PIL de http://effbot.org/downloads/Imaging-1.1.7.tar.gz e instalarlo manualmente con python setup.py install. Si sigue sin encontrar las librerías, trucar el setup como se explica aquí: http://effbot.org/zone/pil-decoder-jpeg-not-available.htm.(esta no la recomiendo normalmente)
    2. Instalar PIL con pip pero diciéndole que no lo compile, y luego editar el setup y compilarlo a mano, tal como explica aquí: http://ubuntuforums.org/showpost.php?p=10804763&postcount=2.(esta es la más recomendable)
       1/ Find the path where libjpeg.so is installed, with
          dpkg -L libjpeg62
          dpkg -L zlib1g
          dpkg -L libfreetype6
       2/ Call 'pip install -I pil --no-install' to download and unpack the PIL source
          into your build directory;
       3/ Get into your build directory (<virtualenv_path>/build/pil) and edit setup.py;
       4/ Find a line that says 'add_directory(library_dirs, "/usr/lib")' (line 214 here)
       5/ Add the line 'add_directory(library_dirs, "<library_path>")' afterwards;
       6/ Call 'pip install -I pil --no-download' to finish the installation.
    3. Instalar PIL globalmente en el sistema con aptitude install python-imaging (al ser un paquete de Ubuntu ya sí que resuelve todas las dependencias) y luego copiarlo o enlazarlo al virtualenv, como explica éste: http://menudoproblema.es/blog/entries/2011/04/26/soporte-para-jpeg-zlib-y-freetype2-en-pil/.(esta es una opción rápida si no funciona la anterior)
      $ cd /path/to/virtualenv
      $ ln -s /usr/lib/python2.7/dist-packages/PIL/ lib/python2.7/site-packages/

Desplegar Django bajo Apache con WSGI en un virtualenv, *y que funcione*

El despliegue de Django es una de las pocas cosas claramente mejorables de este estupendo framework. Suele dar más de un dolor de cabeza porque es un poco lío establecer correctamente las variables de entorno. Un caso concreto que me ha dado bastantes problemas es desplegarlo con mod_wsgi de Apache, pero estando el Django dentro de un python con virtualenv.

En la documentación de Django explican el caso general de usar Django con Apache y mod_wsgi, incluyendo enlaces a otros documentos más extensos. Pero no explican el caso del virtualenv. Buscando en la web he encontrado muchos tutoriales pero ninguno me terminaba de aclarar. El problema es cómo decirle a mod_wsgi que coja el python del virtualenv en vez del global del sistema. Este tutorial es el que parecía más prometedor. Básicamente dice que hay que añadir el path del site-packages de nuestro virtualenv con una llamada site.addsitedir() y definir el path de python añadiendo WSGIPythonHome al fichero de configuración de Apache.

Sin embargo, la parte del site.addsitedir() sí funciona, pero el WSGPythonHome no. Después de mucho probar, descubrí que mod_wsgi necesita que el python del virtualenv esté en el path del sistema, si no, no lo encuentra aunque pongamos el WSGIPythonHome apuntando a él. Así que la solución es añadir /mi/path/virtualenv/bin al path, por ejemplo en el script  que lanza apache (normalmente será /etc/init.d/apache o /etc/init.d/httpd). Ojo con esto, que puede afectar a otros proyectos que usen python en el mismo apache.

Y observar una cosa importante: WSGI usa el mismo python para todos los virtual hosts, con lo cual se pierde una de las razones principales para usar virtualenv. Ahora esto sólo servirá si queremos aislar nuestro django de otros proyectos python que haya en el sistema, pero que no usen mod_wsgi.

Y otra cosa más: el mod_wsgi lanza el servidor en un directorio cualquiera, que no tiene por qué ser la raíz de Django. Para que encuentre las cosas, hay dos opciones: una es poner todos los paths absolutos en el settings.py, y la otra es añadir al principio del script django.wsgi las siguientes líneas:

import os
os.chdir(‘/mi/path/virtualenv/mi_proyecto/src’)

En este otro documento explica con bastante más detalle el comportamiento del wsgi script con Django.

¿Qué pasa con el botón de submit si envío un form HTML con la tecla Intro?

Nos enfrentamos a un bug ciertamente extraño. Tenemos dos forms en los que hacemos lo mismo: hay un <input type=”submit” name=”button_send” value=”Aceptar”>, y en el script que recibe el form estamos preguntando en los datos de GET o POST si viene un campo llamado “button_send”.

En Firefox y otros no hay ningún problema… pero al llegar a Explorer comienzan (qué raro) los comportamientos extraños. En uno de los forms, al pulsar Intro mientras el foco está en uno de los campos que no es el botón, el formulario se envía pero el “button_send” NO APARECE en los datos recibidos. Lo impresionante del asunto es que en el otro form sí que aparece incluso pulsando Intro, y ambos forms tienen la misma estructura, sólo se diferencian en los campos de datos que contienen.

Tras un buen rato de comernos el coco y flipar bastante, llegamos a la solución leyendo este texto (hay que ir a archive.org porque el original se borró):

http://web.archive.org/web/20060518010241/ppewww.ph.gla.ac.uk/~flavell/www/formquestion.html

There seems to be some confusion/disagreement among browser developers as to whether a Submit control is a “successful control” (in the terms of e.g the HTML4 specification) when a form is submitted by means of Enter.

MSIE4’s behaviour seemed odd. If there was only a single text-input field, then hitting Enter with the focus on the text-input field would submit the form without sending the name/value pair associated with a submit control. Surprisingly, however, if there were two text-input fields, then hitting Enter when the focus was on either of those fields would result in the form being submitted together with the name/value pair of the submit control (or of the first submit control if there were more than one). This seemed illogical and inconsistent.

Básicamente, el comportamiento de Explorer (comenzando por IE4 pero se ha mantenido en los siguientes, al menos hasta el 8 ) es que si el form tiene exactamente 1 <input type=”text”>, entonces el botón de submit no se envía; pero si tiene más de uno, sí.

El resto de navegadores se comportan de maneras variadas ante este hecho, aunque ninguna tan barroca como esta. La causa es que el comportamiento de enviar un form con Intro no está modelado en la especificación del W3C.

¿Solución?

Si nuestro form tiene varios botones, el script debe contemplar un caso por defecto para cuando no se reciba ninguno.

Si el form es modo GET y estábamos leyendo el botón para distinguir si el usuario ha enviado el formulario o sólo leído la página que lo contiene, hay que cambiar de método, es mejor usar un <input type=”hidden”> y leer éste en lugar del botón.