Goodreads+webcam+python+zbar == hackfun!

2010-09-01 16:36:47

Me gusta mucho GoodReads una red social para gente que lee libros.

Leo mucho, y me gusta que puedo ver las opiniones de otra gente antes de empezar un libro, y puedo poner comentarios, y que anduve leyendo, y otras cosas.

De hecho, goodreads va a ser una parte importante de un proyecto que estamos empezando con gente de PyAr.

Una cosa que no vengo haciendo es agregar los libros que tengo en goodreads, porque es mucho laburo.

Bueno, ya no tanto!

Así se hace, al estilo hacker...

  1. Instalá zbar
  2. Conseguite una webcam barata
  3. Agarrá un libro
  4. Un programita python de 7 líneas (ver abajo)

Y mirá el video...

¿No es lindo?

El programa:

import os

p=os.popen('/usr/bin/zbarcam','r')
while True:
    code = p.readline()
    print 'Got barcode:', code
    isbn = code.split(':')[1]
    os.system('chromium http://www.goodreads.com/search/search?q=%s'%isbn)

PET: English Translation Issue 1 tiene fecha

2010-08-29 16:58:51

Porque funcionó una vez, hagámoslo de nuevo. Acabo de ponerle una fecha completamente arbitraria y probablemente muy cercana para la salida del primer número en inglés de la revista "PET: Python Entre Todos".

La versión en inglés se llama PET, que significa "Python Entre Todos: English Translation".

Va a tener los mismos contenidos que el primer número en castellano... y va a ser la última que hagamos así.

De ahí en más, las dos versiones van a salir al mismo tiempo, si podemos.

Por lo tanto, va a haber un lapso muy corto entre el primer número en inglés y el segundo (ojalá que menos de un mes).

Así que , no cambien de canal

Soy un nerd, pero mi teléfono es barato

2010-08-23 18:50:02

Tengo un celular nuevo porque el viejo desapareció. Me compré el Samsung Star, que no es un smartphone, sino lo que llaman un featurephone, que parece significar "tiene mucha cosas pero es barato, sólo se programa en Java y no tiene Android".

Es más o menos así:

  • Pantalla táctil, 400x240
  • Falso GPS (te da tu ubicación a unos 400 metros a la redonda, en la ciudad me alcanza).
  • Browser basado en Webkit que anda sorprendentemente bien (viene con un documento que es la LGPL y no se puede borrar :-)
  • Acelerómetro
  • Radio FM
  • Una tarjeta micro-SD de 2GB
  • Cámara de 3.2MP (video a 320x240).

La cámara toma fotos más o menos decentes con buena luz:

Foto0005.jpg

Y tiene algunos features "avanzados" (que quiere decir: mi camarita no los tiene), como detección de sonrisas y foto panorámica:

Foto0018.jpg

Aparte de eso... y, es un teléfono, que se puede decir. Es "barato", lo que quiere decir "sale caro, pero menos que los otros".

Entonces, vamos a aprovecharlo...

Primero: no tengo mi fiel lector de libros electrónicos desde que se murió mi última Clie. Así que busqué software para eso.

Resulta que el mundo de los Midlets, como se llaman las aplicaciones para feature phones, es algo raro, donde las cosas son difíciles de encontrar para los que recién llegan.

Hay una especie de "tienda de apps" en http://getjar.com pero no es exactamente completa, y es difícil encontrar algo.

Después de buscar mucho, encontré un buen (casi digo muy buen) programa llamado Foliant. Esta es la home page, en ruso ... es raro, mi lector de ebooks favorito para palm (Palm Fiction) también tiene un sitio sólo en ruso!

Por supuesto las tipografías son horribles, pero resulta que se pueden convertir TTF usando esto así que ya tengo mi Droid Sans como a mí me gusta.

Otra cosa interesante sobre este celular es que puede reproducir cosas (sí el anterior era tan baja gama que no tenía ni mp3). Pero... no cualquier cosa. Para audio, usás mp3 y listo. Para video... es un poco más complicado.

Acá está la versión corta:

  1. Convertí usando Handbrake, video MP4 ffmpeg, audio AAC.
  2. Usá la extensión .mp4, no se aviva que .m4v es video.
  3. El video tiene que ser como máximo 320x240.

El punto 3 es un problema. Por ejemplo, que hacemos con un video pantalla ancha, ponele 640x272, una relación 2.35:1?

Lo obvio es limitar el ancho a 320, y te da un video de 320x136.

Bueno, no. Lo que hay que hacer es encontrar el alto correcto, respetando la relación ancho/alto para un ancho de 400 pixels. En este caso, es 400x170.

Pero no podés usar un video de 400x170! Por eso usás 320x170, y al reproducir le decís al teléfono que "llene la pantalla" ignorando el aspect ratio. Listo: 400x170 y la forma correcta.

La diferencia? 320x130 tiene sólo 41600 pixels, 320x170 tiene 54400. O sea, 30% mejor imagen.

Sí, es diminuta (3 pulgadas) pero se ve bastante bien, y dependiendo del tipo de video, funciona.

NOTA: Foliant es mucho mejor de lo que parecía, una vez que bajé la versión para Samsung de la página. Es pantalla completa (sin soft buttons) y la pantalla rota automáticamente con el acelerómetro. Es muy cómodo de usar y la interfaz es muy bonita.

España es mucho más grande de lo que pensás!

2010-08-19 23:02:05

Lo vi hoy en las noticias (diario Tiempo Argentino, perdón por la foto ilegible):

Foto0012.jpg

Dice: "En España no llega a 340 mil clientes mensuales (0,006% del total)".

Esto es sobre usuarios de celulares. Entonces, cuantos celulares hay en España?

De acuerdo al diario, 340000 son el 0,006% ... entonces hay 5 666 666 667 (aprox.) Por lo tanto España tiene aproximadamente un celular para cada hombre mujer y niño del mundo.

Cosas que aprendí publicando una revista

2010-08-13 18:46:22

Hoy a las 00:00:00 GMT-3 PET: Python entre todos salió, a tiempo (arbitrario pero forzado) y en presupuesto ($0).

¿Entonces, que aprendí? ¡Varias cosas!

  • Lo único que se necesita para publicar es tiempo y contenido.

  • El tiempo se puede convertir en contenido, pero si escribís todo solo es un blog, no una revista. Por suerte PET atrajo buenos colaboradores.

  • Si querés diseño utilitario, rst2pdf funciona.

  • En algunos sentidos funciona mejor que otras herramientas.

    • Puedo sacar una versión corregida de los PDFs en 5 minutos para todos los diseños. ¿Cuánto me llevaria usando Scribus u otros DTP? En una revista donde las cosas deben ser correctas eso es importante.
    • Las tablas de contenido (TOC) son mejores que la mayoría de las revistas en PDF amateur. La que está en el texto es clickeable, y la del PDF es perfecta.

    Los números de página en la TOC del PDF están bien (no, la tapa no es la página 1)

    • Estoy produciendo 6 versiones en PDF: A4 (BN, color), A5(BN, color) y librito(BN, color) y podría agregar otra en minutos.
  • ¡Aprendí que es PDF Imposition!

Vamos a explicar esa:

Suponete que queres imprimir un librito, y tenés 32 páginas de contenido. ¿Como lo hacés?

La forma más fácil es imprimirlo a dos paginas por carilla en papel A4, así podés apilar las hojas, doblarlas por la mitad, abrocharlas, y te da un lindo librito A5.

El problema es que el orden de las páginas es difícil. Por ejemplo, para un librito de 4 páginas, hay que imprimir una hoja A4 con las páginas 4-1 de un lado y las 2-3 del otro.

Para 8 páginas, es 8-1,2-7,3-6,4-5.

Por suerte hay una forma de hacerlo automáticamente:

1. Instalá podofo 3. Conseguite booklet-A4.plan (ver abajo) 2. Corré esto:

podofoimpose mis-paginas-A5.pdf mi-librito.pdf booklet-A4.plan lua

booklet-A4.plan es esto:

---Generic Booklet (A4)
---
---It is said generic as it will try to determine
---automatically how to fit the booklet onto A4
---paper sheets, scaling pages if necessary.
---it is well suited for office documents for
---which you do not care too much about resulting
---imposition artefacts since it manages to save
---paper!
---
-- print("Booklet")
-- We output an A4 booklet
PageWidth = 595.27559
PageHeight = 841.88976

print("PageCount",PageCount)

-- We assume that H > W
-- Argh, we now can do better since we have "if" ;-)
-- Scale = PageHeight / (2*SourceWidth)
if(SourceWidth <= SourceHeight)
then
--  If you A5 pages are not really A5, uncomment the next line
--  Scale = PageHeight / (2*SourceWidth)
    Scale = 1
    rot = 90
        xof = SourceHeight
        yofRA = 0
        yofRB = SourceWidth
        yofVA = 0
        yofVB = SourceWidth
else
--  If you A5 pages are not really A5, uncomment the next line
--  Scale = PageHeight / (2*SourceHeight)
    Scale = 1
    rot = 0
        xof = 0;
        yofRA = 0
        yofRB = SourceHeight
        yofVA = SourceHeight
        yofVB = 0
end

do
    rest = PageCount % 4
    totp = PageCount
    if rest ~= 0
        then
        totp = totp + ( 4 - rest)
        end
    inc = 0
    count = 0
    imax = totp/4
    while count < imax
        do
--          We assume that podofoimpose will discard invalid records
--          such as those with source page greater than PageCount
--          print(totp, inc, rot, xof,yofRA, yofRA, yofVA, yofVB)
-- Recto
        PushRecord(totp - inc , inc + 1 , rot, xof , yofRA)
        PushRecord(inc + 1 , inc + 1 , rot, xof , yofRB)
-- Verso
        PushRecord(inc + 2 , inc + 2 , rot, xof , yofVA)
        PushRecord(totp-(inc + 1) , inc + 2 , rot, xof, yofVB)

        count = count + 1
        inc = inc + 2
        end
end

El código está tomado de acá: http://www.oep-h.com/impose/

Y listo, tenés un PDF todo revuelto con las páginas en exactamente el orden correcto (y páginas en blanco agregadas en el lugar necesario).

¡Vengan a verme a Bahía Blanca este fin de semana!

2010-08-10 16:57:47

Voy a dar una charla en las Jornadas del Sur en Bahía Blanca este fin de semana (Agosto 14/15 y 16).

Para variar, no voy a dar una de mis charlas viejas, sino una completamente nueva, estreno nunca taxi, que se llama "El Amateur", y probablemente una lightning talk o algo así.

La oferta habitual de cerveza gratis no va a ser posible esta vez, asi que: si mencionás este blog en la sesión de preguntas y respuestas te ganas... caramelos gratis!

Después de eso, voy a estar en FM La Tribu el sábado 21 de agosto en las Charlas Abiertas 2010 hablando de cosas como virtualenv, tox, nose y otros temas relacionados a testing.

Sale o sale: PET - Python Entre Todos

2010-08-09 15:57:27

Editado

Saqué la cuenta regresiva porque estaba equivocada (problema de timezone). Para una que funciona, vean http://revista.python.org.ar

Me han metido en OPM (Otro Proyecto Más) y este es el primer número en la historia de PET - Python Entre Todos una revista online de python.

Esto es un emergente de PyAr la comunidad de Python Argentina, y tal vez nunca tenga un segundo número así que atentos al primero ;-)

Vengo haciendo de webmaster, edición y producción del PDF junto con Emiliano Dalla Verde Marcozzi que ayuda por todas partes y consiguió los artículos.

El primer número son unas 45 páginas A5 (o 23 A4) en letra chica así que no es exactamente enorme, pero tiene media docena de artículos apuntados a distintos niveles de programador python.

¡Está siendo divertida de hacer, ojalá sea bien recibida!

Extensiones para rst2pdf: Fácil y poderoso

2010-08-05 18:55:47

Casi todo lo que escribo en mi trabajo (y mi libro) lo escribo usando restructured text. Y cuando quiero algo "para imprimir", tiendo a usar mi propia herramienta, rst2pdf.

Es popular, por lejos mi programa más usado, pero pocos saben que es también fácil de extender (¡saludos a Patrick Maupin, que escribió esa parte!). Y no sólo eso, sino que se puede hacer que haga cosas bastante asombrosas con un poco de esfuerzo.

Para demostrarlo, creemos los títulos de sección más impresionantes del mundo y sus alrededores (a ver como hacen esto con LaTeX ;-)

Primero: definamos el problema

Los títulos que puede producir rst2pdf son aburridos. Si apretás todos los botones y tirás de todas las palancas, podés llegar a producir un título en Comic Sans, alineado derecha en letras rosas con fondo palta y borde rojo.

Y hasta ahí llega la personalización que podés hacer usando stylesheets. Normalmente eso es suficiente porque rst2pdf no está pensado para folletería (aunque alguna se ha hecho).

El problema real es que si te ponés en diseñador gráfico con rst2pdf, perdés la estructura del documento porque no estás siendo semántico.

Segundo: definir la meta

Uiero hacer encabezados como este:

fancytitles1

La imagen está sacada de la biblioteca del congreso de EEUU con un poco de (mal) trabajo de gimp para dejar el espacio libre a la izquierda, y el título se agregó con Inkscape.

¿Se puede hacer eso con rst2pdf? No, ni cerca. No sin programar. ¡Así que programemos una extensión que te permita crear cualquier encabezado que vos quieras dentro de los límites de Inkscape!

Primero creamos un template SVG para los encabezados (Es un poco grande porque tiene la imagen adentro).

Tercero: el flowable encabezado-imagen

Suponete que tenés una imagen del encabezado exactamente como esa de arriba. ¿Como lo dibujás en un PDF? En reportlab se hace usando flowables que son elementos que componen la historia que es tu documento. Esos flowables se acomodan en páginas, y eso es tu PDF.

Si estás haciendo un título, hay una cosa más, necesitás crear un bookmark para que aparezca en la tabla de contenidos del PDF.

Este es un flowable que hace eso. Está hecho pegando pedazos de cosas de rst2pdf y es una cruza maligna entre Heading y MyImage:

class FancyHeading(MyImage):
  '''This is a cross between the Heading flowable, that adds outline
  entries so you have a PDF TOC, and MyImage, that draws images'''

  def __init__(self, *args, **kwargs):
      # The inicialization is taken from rst2pdf.flowables.Heading
      self.stext = kwargs.pop('text')
      # Cleanup title text
      self.stext = re.sub(r'<[^>]*?>', '', unescape(self.stext))
      self.stext = self.stext.strip()

      # Stuff needed for the outline entry
      self.snum = kwargs.pop('snum')
      self.level = kwargs.pop('level')
      self.parent_id= kwargs.pop('parent_id')


      MyImage.__init__(self, *args, **kwargs)

  def drawOn(self,canv,x,y,_sW):

      ## These two lines are magic.
      #if isinstance(self.parent_id, tuple):
          #self.parent_id=self.parent_id[0]

      # Add outline entry. This is copied from rst2pdf.flowables.heading
      canv.bookmarkHorizontal(self.parent_id,0,0+self.image.height)

      if canv.firstSect:
          canv.sectName = self.stext
          canv.firstSect=False
          if self.snum is not None:
              canv.sectNum = self.snum
          else:
              canv.sectNum = ""

      canv.addOutlineEntry(self.stext.encode('utf-8','replace'),
                                self.parent_id.encode('utf-8','replace'),
                                int(self.level), False)

      # And let MyImage do all the drawing
      MyImage.drawOn(self,canv,x,y,_sW)

¿Y cómo le decimos a rst2pdf que use eso en vez de un Heading común? Pisando la clase TitleHandler. Acá es donde entra la magia de las extensiones.

Si se define, en una extensión, una clase como esta:

class FancyTitleHandler(genelements.HandleParagraph, docutils.nodes.title):

Entonces esa clase va a ser responsable de todos los nodos de clase docutils.nodes.title. En este caso, tan solo copié rst2pdf.genelements.HandleTitle y cambié el resultado de los encabezados nivel 1 para que use un FancyHeading en vez de un Heading... y eso es todo.

class FancyTitleHandler(genelements.HandleParagraph, docutils.nodes.title):
  '''
  This class will handle title nodes.

  It takes a "titletemplate.svg", replaces TITLEGOESHERE with
  the actual title text, and draws that using the FancyHeading flowable
  (see below).

  Since this class is defined in an extension, it
  effectively replaces rst2pdf.genelements.HandleTitle.
  '''

  def gather_elements(self, client, node, style):
      # This method is copied from the HandleTitle class
      # in rst2pdf.genelements.

      # Special cases: (Not sure this is right ;-)
      if isinstance(node.parent, docutils.nodes.document):
          #node.elements = [Paragraph(client.gen_pdftext(node),
                                      #client.styles['title'])]
          # The visible output is now done by the cover template
          node.elements = []
          client.doc_title = node.rawsource
          client.doc_title_clean = node.astext().strip()
      elif isinstance(node.parent, docutils.nodes.topic):
          node.elements = [Paragraph(client.gen_pdftext(node),
                                      client.styles['topic-title'])]
      elif isinstance(node.parent, docutils.nodes.Admonition):
          node.elements = [Paragraph(client.gen_pdftext(node),
                                      client.styles['admonition-title'])]
      elif isinstance(node.parent, docutils.nodes.table):
          node.elements = [Paragraph(client.gen_pdftext(node),
                                      client.styles['table-title'])]
      elif isinstance(node.parent, docutils.nodes.sidebar):
          node.elements = [Paragraph(client.gen_pdftext(node),
                                      client.styles['sidebar-title'])]
      else:
          # Section/Subsection/etc.
          text = client.gen_pdftext(node)
          fch = node.children[0]
          if isinstance(fch, docutils.nodes.generated) and \
              fch['classes'] == ['sectnum']:
              snum = fch.astext()
          else:
              snum = None
          key = node.get('refid')
          maxdepth=4
          if reportlab.Version > '2.1':
              maxdepth=6

          # The parent ID is the refid + an ID to make it unique for Sphinx
          parent_id=(node.parent.get('ids', [None]) or [None])[0]+u'-'+unicode(id(node))
          if client.depth > 1:
              node.elements = [ Heading(text,
                      client.styles['heading%d'%min(client.depth, maxdepth)],
                      level=client.depth-1,
                      parent_id=parent_id,
                      node=node,
                      )]
          else: # This is an important title, do our magic ;-)
              # Hack the title template SVG
              tfile = open('titletemplate.svg')
              tdata = tfile.read()
              tfile.close()
              tfile = tempfile.NamedTemporaryFile(dir='.', delete=False, suffix='.svg')
              tfname = tfile.name
              tfile.write(tdata.replace('TITLEGOESHERE', text))
              tfile.close()

              # Now tfname contains a SVG with the right title.
              # Make rst2pdf delete it later.
              client.to_unlink.append(tfname)

              e = FancyHeading(tfname, width=700, height=100,
                  client=client, snum=snum, parent_id=parent_id,
                  text=text, level=client.depth-1)

              node.elements = [e]

          if client.depth <= client.breaklevel:
              node.elements.insert(0, MyPageBreak(breakTo=client.breakside))
      return node.elements

La extensión está en SVN y se puede probar así:

[fancytitles]$ rst2pdf -e fancytitles -e inkscape demo.txt -b1

Hay que habilitar la extensión Inkscape para que los SVG se vean lo mejor posible. Y esta es la salida:

fancytitles2

Se puede cambiar cualquier elemento de la salida. Eso es ser extensible :-)

Por esto es que Qt y PyQt valen la pena

2010-07-24 20:37:48

Alejandro Dolina alguna vez escribió (si no me traiciona una memoria de hace unos 25 años) sobre una mesa redonda cuyo tema de discusión era "¿Qué es el tango?", y como después de dos horas hablando sobre la naturaleza, características e historia del tango uno de los miembros se paró, agarró un bandoneón, tocó "El apache argentino" y se fue sin decir una palabra.

¿Por qué están buenos Qt y PyQt?

Widget reproductor de audio:

# -*- coding: utf-8 -*-

import sys, os
from PyQt4 import QtCore, QtGui, uic
from PyQt4.phonon import Phonon
import icons_rc

class AudioPlayer(QtGui.QWidget):
    def __init__(self, url, parent = None):

        self.url = url

        QtGui.QWidget.__init__(self, parent)
        self.setSizePolicy(QtGui.QSizePolicy.Expanding,
            QtGui.QSizePolicy.Preferred)


        self.player = Phonon.createPlayer(Phonon.MusicCategory,
            Phonon.MediaSource(url))
        self.player.setTickInterval(100)
        self.player.tick.connect(self.tock)

        self.play_pause = QtGui.QPushButton(self)
        self.play_pause.setIcon(QtGui.QIcon(':/icons/player_play.svg'))
        self.play_pause.clicked.connect(self.playClicked)
        self.player.stateChanged.connect(self.stateChanged)

        self.slider = Phonon.SeekSlider(self.player , self)

        self.status = QtGui.QLabel(self)
        self.status.setAlignment(QtCore.Qt.AlignRight |
            QtCore.Qt.AlignVCenter)

        self.download = QtGui.QPushButton("Download", self)
        self.download.clicked.connect(self.fetch)

        layout = QtGui.QHBoxLayout(self)
        layout.addWidget(self.play_pause)
        layout.addWidget(self.slider)
        layout.addWidget(self.status)
        layout.addWidget(self.download)

    def playClicked(self):
        if self.player.state() == Phonon.PlayingState:
            self.player.pause()
        else:
            self.player.play()

    def stateChanged(self, new, old):
        if new == Phonon.PlayingState:
            self.play_pause.setIcon(QtGui.QIcon(':/icons/player_pause.svg'))
        else:
            self.play_pause.setIcon(QtGui.QIcon(':/icons/player_play.svg'))

    def tock(self, time):
        time = time/1000
        h = time/3600
        m = (time-3600*h) / 60
        s = (time-3600*h-m*60)
        self.status.setText('%02d:%02d:%02d'%(h,m,s))

    def fetch(self):
        print 'Should download %s'%self.url

def main():
    app = QtGui.QApplication(sys.argv)
    window=AudioPlayer(sys.argv[1])
    window.show()
    # It's exec_ because exec is a reserved word in Python
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

Widget reproductor de video:

import sys, os
from PyQt4 import QtCore, QtGui, uic
from PyQt4.phonon import Phonon
import icons_rc

class VideoPlayer(QtGui.QWidget):
    def __init__(self, url, parent = None):

        self.url = url

        QtGui.QWidget.__init__(self, parent)
        self.setSizePolicy(QtGui.QSizePolicy.Expanding,
            QtGui.QSizePolicy.Preferred)


        self.player = Phonon.VideoPlayer(Phonon.VideoCategory,self)
        self.player.load(Phonon.MediaSource(self.url))
        self.player.mediaObject().setTickInterval(100)
        self.player.mediaObject().tick.connect(self.tock)

        self.play_pause = QtGui.QPushButton(self)
        self.play_pause.setIcon(QtGui.QIcon(':/icons/player_play.svg'))
        self.play_pause.clicked.connect(self.playClicked)
        self.player.mediaObject().stateChanged.connect(self.stateChanged)

        self.slider = Phonon.SeekSlider(self.player.mediaObject() , self)

        self.status = QtGui.QLabel(self)
        self.status.setAlignment(QtCore.Qt.AlignRight |
            QtCore.Qt.AlignVCenter)

        self.download = QtGui.QPushButton("Download", self)
        self.download.clicked.connect(self.fetch)
        topLayout = QtGui.QVBoxLayout(self)
        topLayout.addWidget(self.player)
        layout = QtGui.QHBoxLayout(self)
        layout.addWidget(self.play_pause)
        layout.addWidget(self.slider)
        layout.addWidget(self.status)
        layout.addWidget(self.download)
        topLayout.addLayout(layout)
        self.setLayout(topLayout)

    def playClicked(self):
        if self.player.mediaObject().state() == Phonon.PlayingState:
            self.player.pause()
        else:
            self.player.play()

    def stateChanged(self, new, old):
        if new == Phonon.PlayingState:
            self.play_pause.setIcon(QtGui.QIcon(':/icons/player_pause.svg'))
        else:
            self.play_pause.setIcon(QtGui.QIcon(':/icons/player_play.svg'))

    def tock(self, time):
        time = time/1000
        h = time/3600
        m = (time-3600*h) / 60
        s = (time-3600*h-m*60)
        self.status.setText('%02d:%02d:%02d'%(h,m,s))

    def fetch(self):
        print 'Should download %s'%self.url

def main():
    app = QtGui.QApplication(sys.argv)
    window=VideoPlayer(sys.argv[1])
    window.show()
    # It's exec_ because exec is a reserved word in Python
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

...

Aplicaciones de escritorio y nubes (con video)

2010-07-23 02:50:30

Me gusta hacer aplicaciones de escritorio. Eso significa que soy miembro de una raza en extinción, ya que las aplicaciones web nos van a hacer obsoletos el martes que viene, pero me gusta hacerlas.

Lo malo es, por supuesto, que a veces es mucho más conveniente usar una aplicación web. Por ejemplo, he abandonado a mi propio bebé (uRSSus) porque google reader es más fácil y conveniente.

Pero entonces pensé... ¿qué me molesta de uRSSus? ¡Y son bastantes cosas!

  1. No está en todas las computadoras que uso. Eso quiere decir que jamás podré usarla de forma exclusiva.
  2. Es bastante inútil sin una conexión a Internet (pero también lo es google reader).
  3. Como no la puedo usar exclusivamente, termino con feeds en uRSSus que no están en google reader y viceversa.
  4. Es lentísima.

Entonces decidí ver que puedo hacer al respecto sin abandonar el lado bueno de uRSSus:

  1. Me gusta más que una aplicación web, porque es de escritorio.
  2. Hace cosas como abrir el sitio en vez de mostrar el post del feed (bueno para feeds de contenido parcial)
  3. La hice yo (sí, eso es un feature para mí. Me gusta tener programas que yo hice)

Entonces, este intento de reescribir el lector RSS de escritorio produjo esto:

Como se puede ver en el video, este lector sincroniza la lista de suscripciones con google. También eventualmente sincronizará posts leídos/no leídos.

Sigue pudiendo abrir sitios completos en vez de posts, tiene/tendrá un muy buen modo offline (páginas completas capturadas como imágenes, por ejemplo), y... es muy muy rápido.

Es mucho más rápido que google reader en chromium, y muchísimo más rápido que uRSSus. Eso es porque está mejor el código, así que probablemente significa que antes tenía muerte cerebral y he experimentado una leve mejoría.

El código no es apto para publicación (por ejemplo, el schema de la base de datos va a cambiar) pero se puede probar: http://code.google.com/p/kakawana/source/checkout


View My Stats