XServer-less webpage screenshot

Date:2008-09-16 (python)
Date:2009-04-02 (c++)

With Python

There’s no good solution for that which I know of, and in fact, it would probably require hacking up some well known renderer (Gecko or WebKit mainly).

The Mozembed only allow to display the web page (so you need a X server, there are several programs automating this, but its slow and not very elegant).

Well, I came up with my own solution, it’s far from perfect, yet much better. Using QT4?s WebKit renderer and the fake x server (Xvfb - bundled with Xorg) you can have a command line tool rendering pages perfectly!

Of course this can be converted to C++ easily or probably you can call the WebKit library directly - maybe i’ll try that in the future. (Or then again, hack QT not to request a X server connection, if that’s possible).

Run as (for example):

xvfb-run -a ./thumbpage.py 1024x768 http://www.insecure.ws/ insecure

If you need, you can uncomment the full page output (no scaling).

#!/usr/bin/python
# Copyright (c) kang@insecure.ws 2008
# Licensed under the terms of the GPLv3
# Require LibQT4 and PyQT4

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtWebKit import *

app = QApplication(sys.argv)

def usage():
        print "USAGE: "+sys.argv[0]+"   "
        print
        print "Render the URL  to a PNG thumbnail .png using WebKit"
        print "Example: "+sys.argv[0]+" 1024x768 http://www.insecure.ws/ insecure"
        sys.exit()

if (len(sys.argv) < 4):
        usage()

try:
        dim = sys.argv[1].split("x")
        if len(dim) != 2:
                usage()
except IndexError:
        usage()

url = sys.argv[2]
out = sys.argv[3]

class Thumbnailer(QObject):
        def __init__(self, url, out, dim):
                self.url = url
                self.out = out
                self.dim = dim
                self.web = QWebView()
                self.page = self.web.page()
                print "Loading", self.url, "please wait a moment"
                self.web.load(QUrl(self.url))

        def render(self):
                print "Rendering"
                frame = self.page.currentFrame()
                self.page.setViewportSize(frame.contentsSize())
                img = QImage(self.page.viewportSize(), QImage.Format_ARGB32)
                paint = QPainter(img)
                frame.render(paint)
                paint.end()

                thumb = QImage(img.scaled(int(self.dim[0]), int(self.dim[1])))
                #img.save(self.out+"_full.png")
                if thumb.save(self.out+".png"):
                        print "Saved to",self.out+".png"
                else:
                        print "Failed to save"
                app.quit()

t = Thumbnailer(url, out, dim)
t.connect(t.page, SIGNAL("loadFinished(bool)"), t.render)
sys.exit(app.exec_())

This guy made a nicer version of this script with xvfb wrapped in and checking for loading timeout, etc.

With C++

While a better solution would still be to bypass QT and render ‘by hand’ to and image file, or hack QT to do so (without requiring any X server connection, and using fewer dependency than large QT libraries...), I decided to run my script again, and I ran into troubles with python-qt4 on some debian systems (and older Fedora system, as a reader pointed out).

Anyway, I remade the script into half-proper C++ so it uses fewer dependencies, execute faster, but most of all, always links properly to QT even if the python-qt4 bindings are not working properly.

To compile, you need the QT4 dev package.

# mkdir wkthumb
# cat > wkthumb.cpp

# qmake -project
# qmake && make
# ./wkthumb
#include <qapplication>
#include <qwidget>
#include <qpainter>
#include <qtwebkit>
#include <qwebpage>
#include <qtextstream>
#include <qsize>

QWebView *view;
QString outfile;

void QWebView::loadFinished(bool ok)
{
        QTextStream out(stdout);
        if (!ok) {
                out << "Page loading failed\n";
                return;
        }
        view->page()->setViewportSize(view->page()->currentFrame()->contentsSize());
        QImage *img = new QImage(view->page()->viewportSize(), QImage::Format_ARGB32);
        QPainter *paint = new QPainter(img);
        view->page()->currentFrame()->render(paint);
        paint->end();
        if(!img->save(outfile, "png"))
                out << "Save failure\n";
        QApplication::quit();
        return;
}

int main(int argc, char *argv[])
{
        QTextStream out(stdout);
        if(argc < 3) {
                out << "USAGE: " << argv[0] << " <url> <outfile>\n";
                return -1;
        }
        outfile = argv[2];
        QApplication app(argc, argv);
        view = new QWebView();
        view->load(QUrl(argv[1]));

        return app.exec();
}

Note

This project has also been forked here http://gitorious.org/wkthumb