Publicaciones sobre kde

2012-02-24 02:38

PyQt Quickie: parsear línea de comandos

Si estás escribiendo una aplicación con PyQt y querés soportar opciones en la línea de comandos, seguro hacés algo así:

opt_parser = OptionParser()
opt_parser.add_option("-q", dest="quickly", action="store_true",
    help="Do it quickly (default=False)")
(options, args) = opt_parser.parse_args(sys.argv)
app = QApplication(sys.argv)
:
:
:

O tal vez incluso QApplication([]). Bueno, eso está mal. Y está mal en casi todos los tutoriales, también. ¿Porqué? Porque Qt (y por lo tanto PyQt) soporta un montón de opciones útiles. Al hacerlo como en ese primer listado, si le pasás "-style=oxygen" o lo que sea, va a pasar alguna de estas cosas:

  1. OptParser te va a decir que es una opción inválida y abortar
  2. Vas a ignorar la opción y no vas a hacer nada útil con ella
  3. Vas a tener tu propia opción -style y vas a hacer dos cosas

Ninguna de esas opciones es la idea. La manera correcta de hacerlo es ésta:

opt_parser = OptionParser()
opt_parser.add_option("-q", dest="quickly", action="store_true",
    help="Do it quickly (default=False)")
app = QApplication(sys.argv)
(options, args) = opt_parser.parse_args(app.arguments())
:
:
:

De esta manera, le das a PyQt la oportunidad de procesar las opciones que reconoce y después, vos manejás el resto, porque a app.arguments() ya le sacaron todas las opciones de Qt.

El lado malo es que --help va a ser mas lento, porque tiene que instanciar QApplication al divino botón, y vas a tener opciones no documentadas. Soluciones para ambos problemas se dejan como ejercicio.

2011-07-24 21:20

Shipping your PyQt app for windows

Disculpen, solo en inglés, no me da el cuerpo para ponerme a traducir :-(


I have written about this in the past, with the general conclusion being "it's a pain in the ass".

So, now, here is how it's done.

  1. Start with a working PyQt application. In this example, I will use devicenzo.py mostly because:
    1. It is a working PyQt application.
    2. It uses a big chunk of PyQt
    3. It's easy to test
  2. Now you need a setup.py. Here's one that works, with extensive commments.
# We will be using py2exe to build the binaries.
# You may use other tools, but I know this one.

from distutils.core import setup
import py2exe

# Now you need to pass arguments to setup
# windows is a list of scripts that have their own UI and
# thus don't need to run in a console.

setup(windows=['devicenzo.py'],
      options={

# And now, configure py2exe by passing more options;

          'py2exe': {

# This is magic: if you don't add these, your .exe may
# or may not work on older/newer versions of windows.

              "dll_excludes": [
                  "MSVCP90.dll",
                  "MSWSOCK.dll",
                  "mswsock.dll",
                  "powrprof.dll",
                  ],

# Py2exe will not figure out that you need these on its own.
# You may need one, the other, or both.

              'includes': [
                  'sip',
                  'PyQt4.QtNetwork',
                  ],

# Optional: make one big exe with everything in it, or
# a folder with many things in it. Your choice
#             'bundle_files': 1,
          }
      },

# Qt's dynamically loaded plugins and py2exe really don't
# get along.

data_files = [
            ('phonon_backend', [
                'C:\Python27\Lib\site-packages\PyQt4\plugins\phonon_backend\phonon_ds94.dll'
                ]),
            ('imageplugins', [
            'c:\Python27\lib\site-packages\PyQt4\plugins\imageformats\qgif4.dll',
            'c:\Python27\lib\site-packages\PyQt4\plugins\imageformats\qjpeg4.dll',
            'c:\Python27\lib\site-packages\PyQt4\plugins\imageformats\qsvg4.dll',
            ]),
],

# If you choose the bundle above, you may want to use this, too.
#     zipfile=None,
)
  1. Run python setup.py py2exe and get a dist folder full of binary goodness.

And that's it. Except of course, that's not it.

What this will do is create a binary set, either a folder full of things, or a single EXE file. And that's not enough. You have to consider at least the following:

  1. Put everything in resource files: images, qss files, icons, etc. Every file your app needs? Put it in a resource file and load it from there. That way you don't have to care about them if you go the "one exe" road.
  2. Compile .ui files to .py (same reason)
  3. Figure out if you use Qt's plugins, and make them work. This includes: using Phonon, using QtSQL, and using any image formats other than PNG.

After you have that, are you done? NO!

Your windows user will want an installer. I am not going to go into details, but I had a good time using BitRock's InstallBuilder for Qt. It's a nice tool, and it works. That's a lot in this field.

But is that all? NO!

You have to take care of the Visual Studio Runtime. My suggestion? Get a copy of the 1.1MB vcredist_x86.exe (not the larger one, the 1.1MB one), and either tell people to install it manually, or add it to your installer. You are legally allowed (AFAIK) to redistribute that thing as a whole. But not what's in it (unless you have a VS license).

And we are done? NO!

Once you run your app "installed", if it ever prints anything to stderr, you will get either a dialog telling you it did, or worse (if you are in aything newer than XP), a dialog telling you it can't write to a log file, and the app will never work again.

This is because py2exe catches stderr and tries to save it on a logfile. Which it tries to create in the same folder as the binary. Which is usually not allowed because of permissions.

Solution? Your app should never write to stderr. Write an excepthook and catch that. And then remove stderr or replace it with a log file, or something. Just don't let py2exe do it, because the way py2exe does it is broken.

And is that it?

Well, basically yes. Of course you should get 4 or 5 different versions of windows to test it on, but you are pretty much free to ship your app as you wish. Oh, mind you, don't upload it to downloads.com because they will wrap your installer in a larger one that installs bloatware and crap.

So, there you go.

2011-03-05 23:46

De Vicenzo: un mini browser más copado

Parece que hubieran sido solo unos días desde que empecé este proyecto. Ah, epa, sí, fué hace unos días nomás!

Si no querés leer eso de nuevo, la idea es ver cuánto código falta para convertir el motor WebKit de Qt en un browser "en serio".

Para ello, me puse una meta completamente arbitraria de 128 líneas de código. En este momento lo declaro feature-complete (pero buggy).

Los nuevos features son:

  • Tabbed browsing (se puede agregar/sacar tabs)
  • Bookmarks (se pueden agregar/sacar y elegir de una lista)

Esto es lo que ya funcionaba:

  • Zoom in (Ctrl++)
  • Zoom out (Ctrl+-)
  • Reset Zoom (Ctrl+=)
  • Buscar (Ctrl+F)
  • Esconder búsqueda (Esc)
  • Botones de atrás/adelante y recargar
  • Entrada de URL que coincide con la página + autocompletado desde la historia + arregla la URL puesta a mano (agrega http://, esas cosas)
  • Plugins (incluído flash, que hay que bajar aparte ;-)
  • El título de la ventana muestra el título de la página (sin propaganda del browser)
  • Barra de progreso para la carga de la página
  • Barra de estado que muestra el destino de los links cuando pasas el mouse
  • Toma una URL en la línea de comando (o abre http://python.org
  • Multiplataforma (funciona donde funciona QtWebKit)

Y cuanto código es eso? 87 LINEAS.

O si preferís la versión que cumple con la PEP8: 115 LINEAS.

Me atajo antes que alguien lo diga: sí, el motor de rendering y el toolkit son enormes. Lo que escribí es el "chrome" alrededor de eso, igual que hacen Arora, Rekonq, Galeon, Epiphany, y muchos otros browsers.

Es un chrome simple y minimalista, pero funciona bastante bien, creo yo.

Aquí está el demo (buggy):

Mas o menos hace lo que esperaba que se puediera lograr, pero le faltan arreglos.

Para ver el código, vayan a su home page: http://devicenzo.googlecode.com

2011-02-28 21:10

¿Cuanto browser entra en 128 líneas de código?

Hoy, charlando en IRC, traté de encontrar un browser de 42 líneas que escribí hace un tiempo. Lamentablemente el pastebin en que lo posteé estaba muerto, así que aprendí una lección: No es buena idea confiar en un pastebin como repositorio de código.

Lo que me gustaba de ese browser de 42 líneas era que no era el ejemplo típico, donde meten una vista de Webkit en una ventana, cargan una página y te tratan de convencer de que son unos bananas. Esa versión son 7 líneas:

import sys
from PyQt4 import QtGui,QtCore,QtWebKit
app=QtGui.QApplication(sys.argv)
wb=QtWebKit.QWebView()
wb.setUrl(QtCore.QUrl('http://www.python.org'))
wb.show()
sys.exit(app.exec_())

O 6 si lo soportara un poco más feo.

¡Pero igual, el de 42 se veía útil!

This 42-line web browser, courtesy of #python and #qt -- http... on Twitpic

Esos botones que se ven funcionaban correctamente, habilitando y deshabilitandose en el momento correcto, la entrada de URL cambiaba cuando hacías click en un link, y otras cositas así.

Ahí decidí empezar un pequeño proyecto intermitente de code golf: meter el mejor browser que pueda en 128 líneas de código (sin contar comentarios ni blancos), usando solo PyQt4.

Eso tiene un propósito útil: siempre sospeché que si uno asume PyQt como parte del sistema base, la mayoría de las aplicaciones entrarían en diskettes. Esta entra unas 500 veces en uno de 1.44MB (¡así que podés usar los de 360 de commodore sin duplidisk!)

hasta ahora van 50 líneas, y tiene los siguientes features:

  • Zoom in (Ctrl++)
  • Zoom out (Ctrl+-)
  • Reset Zoom (Ctrl+=)
  • Buscar (Ctrl+F)
  • Esconder búsqueda (Esc)
  • Botones de atrás/adelante y recargar
  • Entrada de URL que coincide con la página + autocompletado desde la historia + arregla la URL puesta a mano (agrega http://, esas cosas)
  • Plugins (incluído flash, que hay que bajar aparte ;-)
  • El título de la ventana muestra el título de la página (sin propaganda del browser)
  • Barra de progreso para la carga de la página
  • Barra de estado que muestra el destino de los links cuando pasas el mouse
  • Toma una URL en la línea de comando (o abre http://python.org
  • Multiplataforma (funciona donde funciona QtWebKit)

Faltan tabs y soporte de proxy. Espero que lleven unas 40 líneas más, pero creo que ya es el más capaz de todos estos browsers de ejemplo.

El código... no es tan terrible. Uso muchos lambdas, y los argumentos keyword de PyQt para conectar señales, que hacen que algunas líneas sean muy largas, pero no muy difíciles. Se podría achicar bastante todavía!

Aquí está en acción:

Y aquí está el código:

#!/usr/bin/env python
"A web browser that will never exceed 128 lines of code. (not counting blanks)"

import sys
from PyQt4 import QtGui,QtCore,QtWebKit

class MainWindow(QtGui.QMainWindow):
    def __init__(self, url):
        QtGui.QMainWindow.__init__(self)
        self.sb=self.statusBar()

        self.pbar = QtGui.QProgressBar()
        self.pbar.setMaximumWidth(120)
        self.wb=QtWebKit.QWebView(loadProgress = self.pbar.setValue, loadFinished = self.pbar.hide, loadStarted = self.pbar.show, titleChanged = self.setWindowTitle)
        self.setCentralWidget(self.wb)

        self.tb=self.addToolBar("Main Toolbar")
        for a in (QtWebKit.QWebPage.Back, QtWebKit.QWebPage.Forward, QtWebKit.QWebPage.Reload):
            self.tb.addAction(self.wb.pageAction(a))

        self.url = QtGui.QLineEdit(returnPressed = lambda:self.wb.setUrl(QtCore.QUrl.fromUserInput(self.url.text())))
        self.tb.addWidget(self.url)

        self.wb.urlChanged.connect(lambda u: self.url.setText(u.toString()))
        self.wb.urlChanged.connect(lambda: self.url.setCompleter(QtGui.QCompleter(QtCore.QStringList([QtCore.QString(i.url().toString()) for i in self.wb.history().items()]), caseSensitivity = QtCore.Qt.CaseInsensitive)))

        self.wb.statusBarMessage.connect(self.sb.showMessage)
        self.wb.page().linkHovered.connect(lambda l: self.sb.showMessage(l, 3000))

        self.search = QtGui.QLineEdit(returnPressed = lambda: self.wb.findText(self.search.text()))
        self.search.hide()
        self.showSearch = QtGui.QShortcut("Ctrl+F", self, activated = lambda: (self.search.show() , self.search.setFocus()))
        self.hideSearch = QtGui.QShortcut("Esc", self, activated = lambda: (self.search.hide(), self.wb.setFocus()))

        self.quit = QtGui.QShortcut("Ctrl+Q", self, activated = self.close)
        self.zoomIn = QtGui.QShortcut("Ctrl++", self, activated = lambda: self.wb.setZoomFactor(self.wb.zoomFactor()+.2))
        self.zoomOut = QtGui.QShortcut("Ctrl+-", self, activated = lambda: self.wb.setZoomFactor(self.wb.zoomFactor()-.2))
        self.zoomOne = QtGui.QShortcut("Ctrl+=", self, activated = lambda: self.wb.setZoomFactor(1))
        self.wb.settings().setAttribute(QtWebKit.QWebSettings.PluginsEnabled, True)

        self.sb.addPermanentWidget(self.search)
        self.sb.addPermanentWidget(self.pbar)
        self.wb.load(url)


if __name__ == "__main__":
    app=QtGui.QApplication(sys.argv)
    if len(sys.argv) > 1:
        url = QtCore.QUrl.fromUserInput(sys.argv[1])
    else:
        url = QtCore.QUrl('http://www.python.org')
    wb=MainWindow(url)
    wb.show()
    sys.exit(app.exec_())

2010-09-14 12:56

Diálogo de progreso muy pythónico

A veces ves un pedazo de código y está bueno. Acá hay un ejempo que encontré mientras preparaba la charla "Import Antigravity" para el PyDay Buenos Aires: el módulo progressbar.

Este es un ejemplo que te muestra suficiente para usar progressbar:

progress = ProgressBar()
for i in progress(range(80)):
    time.sleep(0.01)

Sí, eso es todo, tenés una linda barra de progreso ASCII que cruza la terminal, soporta que la cambies de tamaño y se mueve mientras iterás de 0 a 79.

El módulo progressbar incluso tiene cosas mejores como ETA o velocidades de transferencia, y todo es así de fácil.

¿Ese código... no está bueno? ¿Querés una barra de progreso para ese loop? ¡Lo "envolvés" y listo! Y por supuesto, como yo programo con PyQt, quiero que PyQt tenga algo igual de bueno.

Así se ve el resultado:

progress

Esto lo podés hacer con cualquier toolkit, y probablemente deberías. Tiene un feature extra: podés interrumpir la iteración, y este es el (poco) código:

# -*- coding: utf-8 -*-
import sys, time
from PyQt4 import QtCore, QtGui

def progress(data, *args):
    it=iter(data)
    widget = QtGui.QProgressDialog(*args+(0,it.__length_hint__()))
    c=0
    for v in it:
        QtCore.QCoreApplication.instance().processEvents()
        if widget.wasCanceled():
            raise StopIteration
        c+=1
        widget.setValue(c)
        yield(v)

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)

    # Do something slow
    for x in progress(xrange(50),"Show Progress", "Stop the madness!"):
        time.sleep(.2)

Diviértanse!

2010-09-09 13:50

Making deployment of desktop Python apps trivial: an idea

Perdón pero hoy no me da el cuero para traducirlo.

Dice google que dice esto.

Proprietor and printer in front of Schwartz Print Shop in Minneapolis

Here's what I'm thinking: how hard could it be to make PyQt app deployment absolutely easy? Well, I am guessing: not very hard.

Here's the trick: see what works in the real world, and adopt it.

Question: what has deployed billions of apps and has its users happy? Answer: phones app stores.

Question: how do they work? Answer: well, that's not that short, so let's start explaining.

As I see it, a reasonable app store has the following components:

A Stable Deployment Target

You can't deploy from the store if you don't know what you are deploying into. If the target platform is shaky, you just can't know how to deploy without user assistance, and we are trying to make this easy for the user, which means that's not acceptable.

So, what's a stable deployment target we can provide?

  • PyQt (so we can deploy GUIs to all major desktop platforms)
  • Python standard library
  • Selected modules

What can be (and should be) bundled with the app?

  • Pure python modules
  • Artwork and other resources

What may be bundled:

  • Python modules written in C/C++, but you then have to redo the app for each platform, and that kinda sucks.

Deployment Services

  • Apps should be able to check if there is a new version of them in the store, to ask for upgrades.
  • Apps should be added by the deployment platform nicely into the host system's menus, desktop, etc.

Monetization Services

  • Some way to charge for apps. Even for open source apps, you could ask for U$S0.99 if you install them through the store. Optional, of course, and up to the app owner.
  • Ad platform? There must be a good one for desktop apps somewhere?

The Store Itself

  • A website that downloads a "package" associated with a local deployment application.
  • A app store app. Install things not via web, but via a desktop application.

I don't expect a functional version of this would take me more than a week working fulltime to implement. Of course then there are all sorts of usability, looks, etc. things to consider.

And... I am going to do something I very rarely do. I am going to ask for money.

As an experiment, I have setup a project at http://www.indiegogo.com/Qt-Shop and set a funding goal of U$S 600.

There you can fund me. I promise that if the project is totally funded, I will deliver. If it isn't, I may deliver anyway. I would prefer to have the money though.

The platform would be released under GPLv2 or later.

2010-07-24 20:37

Por esto es que Qt y PyQt valen la pena

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

...

2010-07-23 02:50

Aplicaciones de escritorio y nubes (con video)

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

2010-07-16 19:58

Capturing a webpage as an image using Pyhon and Qt

Para un proyectito que tengo en carpeta quería poder ver páginas web offline. Entonces empecé a pensar cómo hacerlo, y todas las soluciones que se me ocurrían eran poco prácticas.

Entonces googleé y me encontré con CutyCapt que usa Qt y WebKit para convertir páginas web en imágenes. ¡Me sirve!

Como quiero usarlo desde una aplicación PyQt, tiene sentido hacer lo mismo que CutyCapt hace, pero desde un módulo python así que acá está una implementación rapidita que funciona para mí, unque carece de muchos features de CutyCapt.

Con un poco más de esfuerzo, puede guardar como PDF o SVG, lo que permitiría usarla casi como una página web de verdad.

Se usa así:

python  capty.py http://www.kde.org kde.png

Y acá está el código [descargar capty.py]

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

"""This tries to do more or less the same thing as CutyCapt, but as a
python module.

This is a derived work from CutyCapt: http://cutycapt.sourceforge.net/

////////////////////////////////////////////////////////////////////
//
// CutyCapt - A Qt WebKit Web Page Rendering Capture Utility
//
// Copyright (C) 2003-2010 Bjoern Hoehrmann <[email protected]>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// $Id$
//
////////////////////////////////////////////////////////////////////

"""

import sys
from PyQt4 import QtCore, QtGui, QtWebKit


class Capturer(object):
    """A class to capture webpages as images"""

    def __init__(self, url, filename):
        self.url = url
        self.filename = filename
        self.saw_initial_layout = False
        self.saw_document_complete = False

    def loadFinishedSlot(self):
        self.saw_document_complete = True
        if self.saw_initial_layout and self.saw_document_complete:
            self.doCapture()

    def initialLayoutSlot(self):
        self.saw_initial_layout = True
        if self.saw_initial_layout and self.saw_document_complete:
            self.doCapture()

    def capture(self):
        """Captures url as an image to the file specified"""
        self.wb = QtWebKit.QWebPage()
        self.wb.mainFrame().setScrollBarPolicy(
            QtCore.Qt.Horizontal, QtCore.Qt.ScrollBarAlwaysOff)
        self.wb.mainFrame().setScrollBarPolicy(
            QtCore.Qt.Vertical, QtCore.Qt.ScrollBarAlwaysOff)

        self.wb.loadFinished.connect(self.loadFinishedSlot)
        self.wb.mainFrame().initialLayoutCompleted.connect(
            self.initialLayoutSlot)

        self.wb.mainFrame().load(QtCore.QUrl(self.url))

    def doCapture(self):
        self.wb.setViewportSize(self.wb.mainFrame().contentsSize())
        img = QtGui.QImage(self.wb.viewportSize(), QtGui.QImage.Format_ARGB32)
        painter = QtGui.QPainter(img)
        self.wb.mainFrame().render(painter)
        painter.end()
        img.save(self.filename)
        QtCore.QCoreApplication.instance().quit()

if __name__ == "__main__":
    """Run a simple capture"""
    app = QtGui.QApplication(sys.argv)
    c = Capturer(sys.argv[1], sys.argv[2])
    c.capture()
    app.exec_()

2010-04-10 14:51

Android

Dado que espero que Android en tablets sea una cosa importante en el 2010, estoy experimentando con lo más parecido que puedo conseguir: Android en my eee 701 Surf 4G:

SDC14690

Conswguí la imagen de testing de Android 2.0 de http://android-x86.org. Probé la "estable" 1.6 "stable" pero... era horrible, la mitad de las teclas o opciones de menú hacían que se colgara, reiniciara o se prendiera fuego.

Entonces... ¿cómo va? ¡Lento, pero con potencial!

Lo malo:

  • Arranca rápido... pero mi Arch Linux arranca más rápido.

  • Es leeeeento, podés ver cada letra individaual cuando escribís en el coso de búsqueda. Leí que es temporal. Ojalá!

  • Estoy con una experiencia limitada en aplicaciones porque los "android stores" libres no están tan provistos como el "android marketplace" oficial (¿y porqué corno no puedo bajar apps gratis de ahí????)

    Veo agujeros obvios en el panorama de aplicaciones que supongo están bien cubiertos en el marketplace (como, ¿hay un reemplazo para RadioTray?)

    ¿No hay editor de texto?

    ¿No hay un procesador de texto semi-decente? ¿Ni siquiera uno que genere HTML?

  • El web browser es patético. Tal vez esté bien para un teléfono, pero ¿para un sistema "real"? Es horrible. Te da la versión móbil de todos los sitios (obvio) y muchos no te dejan pasar a la "real" (hasta google con el Google Reader), y por supuesto, no hay flash.

  • El cliente de correo es terrible. No podés no hacer top-posting!!!! "In-Reply-To" está roto!

  • Las opciones de WiFi están demasiado escondidas. Deberían poderse sacar del icono de wifi.

Lo bueno:

  • Se apaga muy rápido.
  • Algunas apps están buenas, especialmente el Aldiko book reader es buenísimo (y puedo compartir los ePub con el fbReader del lado de arch.
  • El cliemte SSH tiene buenas ideas.
  • Me gusta mucho el enfoque de "todas tus cosas están en el SD". Hago exactamente lo mismo en Linux. De hecho tengo exactamente la misma organización en los dos sistemas operativos.
  • La pantalla home con el cajón deslizante de aplicaciones: lindo
  • La barra de notificaciones "agarrable": muy lindo
  • Lo de "apretá la tecla menú para ver el menú"? Genio ;-)
  • Lo de "todo fullscreen todo el tiempo"? Funciona en esta pantalla.
  • La instalación de aplicaciones es un problema solucionado.
  • Sé que voy a tener Qt nativo y no puedo esperar!

Todavía no estoy convenciso, Arch es mucho más rápido por ahora, y hace muchas más cosas pero...

  • Me encargué una pantalla táctil para tener la experiencia como se supone que hay que tenerla.
  • Lo uso mucho para leer de noche en la cama (Recién terminé Makers, léanlo, está bueno!).
  • Lo estoy usando para leer correo de vez en cuando (me niego a responder con esa porquería)
  • Es un despertador bastabte bueno, así que ahora es mi sistema operativo de mesa de luz.

Voy a escribir otro reporte una vez que tenga la pantalla táctil y/o una versión nueva (y ojalá que más rápida).

Contents © 2000-2019 Roberto Alsina