Posts about open source (old posts, page 5)

2010-08-29 16:58

PET: English Translation Issue 1 has a date

Because it worked once, let's do it again. I have just set a completely arbitrary, and probably too early date for the release of the first english Issue of "PET: Python Entre Todos" magazine.

The english version is called PET, which means "Python Entre Todos: English Translation".

It will have the same contents as the first spanish Issue and... it will be the last Issue done like this.

From now on, both versions will be published at the same time, if we can.

So, there will be a very short gap between the english first Issue and the second one (less than a month, we hope).

So, stay tuned

2010-08-23 18:50

I'm a nerd, but I Have a Cheap Phone

I just got a new phone because my old one disappeared. I got a Samsung Star, which is not a smartphone, but what they call a featurephone, which seems to mean "it does a lot of things but is cheap, can only be coded in Java and doesn't run android".

Here's some of the features of this baby:

  • Touchscreen, 400x240
  • Fake GPS (gives you a 400 meters radius of your location. Good enough for me in the city)
  • Webkit-based browser that works surprisingly well (it comes with the LGPL as a document and you can't delete it :-)
  • Accelerometer
  • FM radio
  • A 2GB micro-SD card
  • 3.2MP camera (takes video at 320x240).

The camera can take decent photos in good conditions:

Foto0005.jpg

And it even has some "advanced" features (by which I mean: things my cheap dedicated camera doesn't do), like smile shutter and panoramic shots:

Foto0018.jpg

Other that that... well, it's a phone, what can I say. It was "cheap", which means "it costs more than I want to pay for it, but less than the alternatives".

So, how am I taking advantage of it...

First: I have not had a decent pocket-sized ebook reader since my last Clie died. So, I looked for software to do that.

It turns out that the world of Midlets, as feature phone apps are often called, is aweird place, where things are quite hard to find for the uninitiated.

There is a sort of "app shop" at http://getjar.com but it's by no means comprehensive, and often things are quite hard to find.

After lots of looking, I found a good (I may even say very good) program called Foliant. Here's their home page, russian only ... it's weird, my favourite Palm ebook reader, Palm Fiction also has a russian-only site!

Of course, the fonts sucked (way too large) but it turns out you can convert TTF fonts using this tool so I am now back to the lovely Droid Sans I am used to.

Another nice thing about the new phone is that it can actually play media (yes, my previous phone was so crappy it didn't even play mp3). But... not every kind of media. For audio, just use mp3. For video... it's a bit more complicated.

Here's the short version:

  1. Convert using HandBrake, ffmpeg MP4 video, AAC audio.
  2. Always use the .mp4 extension, the phone is not smart enough to know what a .m4v is.
  3. Don't make the video larger than 320x240.

Number 3 is a problem. For example, you may have a video in a wide screen format, like 640x272, which is a 2.35:1 ratio.

The obvious thing is to cap the width to 320, and that would give you a 320x136 video.

Well, that's wrong. What you should do is find the right height, keeping aspect ratio, for a 400px width. In this case, that would be a 400x170.

But you can't use a 400x170 video! which is why you will use 320x170, and on playback tell the phone to stretch it and ignore aspect ratio. And voila, 400x170 and the correct aspect ratio.

The difference? 320x130 has only 41600 pixels, while 320*170 has 54400, which means you get a 30% better picture.

Yes, it's tiny (3 inches) but it looks pretty sharp, and depending on the kind of material you are watching, it works.

UPDATE: Foliant is better than I thought, once you get the Samsung-specific version. It's fullscreen (no silly soft buttons) and the screen rotates automatically using the accelerometer. It's a pleasure to use, and the UI is very nice.

2010-08-13 18:46

Things I learned publishing a magazine

Today at 00:00:00 GMT-3 PET: Python entre todos was indeed launched, in time (arbitrary but forced) and in budget ($0).

So, what did I learn? I learned a lot!

  • The only thing you need to publish an e-mag is time and content.
  • Time can be converted into content, but if you write everything yourself it's a blog, not a magazine. Luckily, PET found great contributors.
  • If you want utilitarian design, rst2pdf can do the job
  • In fact, it can do it better than other tools in some ways
    • I can push a fixed version of the PDFs in 5 minutes for all layouts. How much would it take me using Scribus or other DTP? In a magazine where correctness matters, that's a big deal.
    • TOCs are better than in most amateur PDF magazines I've seen. The in-content-TOC is clickable, and the PDF TOC is perfect.
    • Page numbers in the PDF TOC make sense (no, the cover is not page 1)
    • I am producing 6 PDF versions: A4(bw, colour), A5(bw,colour), Booklet(bw, colour) and I could add any other I want in a few minutes.
  • I learned about PDF imposition!

Let's explain the last one:

Suppose you want to print a small booklet, and you have 32 pages of content. How do you do that?

The easiest way is to print it 2-up double-sided in A4 paper so that you can stack the pages, fold them down the middle, staple them, and get a nice A5 booklet.

The problem is that the page ordering is hard to get right. For example, for a 4-page booklet, you need to print one A4 page with pages 4-1 on one side and 2-3 on the other. For an 8 page booklet it's 8-1,2-7,3-6,4-5.

Lucklily there's a way to get this done automatically:

1. Install podofo 3. Get booklet-A4.plan (see below) 2. Run this:

podofoimpose my-A5-pages.pdf my-booklet.pdf booklet-A4.plan lua

booklet-A4.plan is this:

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

That code is taken from here: http://www.oep-h.com/impose/

And voilá, you get a scrambled PDF with the pages in exactly the right order (and empty pages added as needed).

2010-08-10 16:57

Come see me in Bahía Blanca next weekend!

I will be speaking at the Jornadas del Sur in Bahía Blanca this weekend (August 14/15 and 16).

For a change, I will not be giving my old tired talks, but a brand new one, called "The Amateur", and probably a lightning talk or something else.

The usual offer of free beer will not be possible this year, so: If you mention this blog in the QA session, you get... free candy!

After that, I will be speaking at FM La Tribu on saturday August 21st in the Charlas Abiertas 2010 where I will be speaking about a lot of things between virtualenv and nose and tox and other testing-related topics.

2010-08-09 15:57

It's coming: PET - Python Entre Todos

Edited

I removed the countdown because it was wrong (timezone problem). Check http://revista.python.org.ar for one that works :-)

I have been dragged into YAP (Yet Another Project) and in this case it's the first-issue-ever of PET - Python Entre Todos a python online magazine in spanish.

This is an emergent output of PyAr the online Python community of Argentina, and it may never have a second issue unless we get a lot of help, so pay attention to the first one :-)

I have been doing webmastering, editing and PDF production, Emiliano Dalla Verde Marcozzi has been helping everywhere and getting articles.

The first issue is some 45 A5 pages (or 23 A4 ones) in small type, so it's not exactly huge, but it has half a dozen articles aimed at different python levels.

It has been a lot of fun so far, I hope it's well received!

2010-08-05 18:55

Extending rst2pdf: easy and powerful

I do almost all my business writing (and my book) using restructured text. And when I want to produce print-quality output, I tend to use my own tool, rst2pdf.

It's popular, surely my most popular program, but very few know it's also extremely easy to extend (hat tip to Patrick Maupin, who wrote this part!). And not only that, but you can make it do some amazing stuff with a little effort.

To show that, let's create the most dazzling section headings known to man ( Let's see you do what this baby can do in LaTeX ;-).

First: define the problem.

The titles rst2pdf can produce are boring. If you pull every lever and push every button, you may end with the title text, in a Comic Sans, right-aligned, in pink lettering, with avocado-green background and a red border.

And that's as far as the customization capabilities go using stylesheets. That's usually enough, because rst2pdf is not meant for brochures or something like that (but I have done it).

The real problem is that when you get all graphic-designer on rst2pdf, you lose document structure, because you are not being semantic.

Second: define the goal.

So, imagine you want to make a heading that looks like this:

fancytitles1

The image is taken from the library of congress with some light (and bad) gimping by me to leave that empty space at the left, and the title was added using Inkscape.

Can you do that with rst2pdf? Hell no you can't. Not without coding. So let's code an extension that lets you create any heading you like within the limits of Inkscape!

First, we create a SVG template for the headings (it's a bit big because it has the bitmap embedded).

Three: the image-heading flowable

Suppose you have an image of the heading just like the one above. How would you draw that in a PDF? In reportlab, you do that using flowables which are elements that compose the story that is your document. These flowables are arranged in pages, and that's your PDF.

If you are doing a heading, there's a bit more, in that you need to add a bookmark, so it appears on the PDF table of contents.

So, here's a flowable that does just that. It's cobbled from pieces inside rst2pdf, and is basically an unholy mix of Heading and 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)

And how do we tell rst2df to use that instead of a regular Heading? by overriding the TitleHandler class. Here's where the extension magic kicks in.

If you define, in an extension, a class like this:

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

Then that class will handle all docutils nodes of class docutils.nodes.title. Here, I just took rst2pdf.genelements.HandleTitle and changed how it works for level-1 headings, making it generate a FancyHeading instead of a Heading... and that's all there is to it.

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

The full extension is in SVN and you can try it this way:

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

You need to enable the Inkscape extension so the SVG will look nice. And here's the output:

fancytitles2

You can override how any element is handled. That's being extensible :-)

2010-07-24 20:37

This is why Qt (and PyQt) are cool

Alejandro Dolina once wrote (and this is from memory that's probably 25 years old) of a round table discussing "What's Tango?", and how after two hours of discussing the nature, characteristics and history of tango, one of the members of the panel picked up a bandoneón, played "El apache argentino" stood up and left without saying a word.

So, why are Qt and PyQt cool?

Audio player widget:

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

Video player widget:

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

Desktop apps and clouds (with video)

I enjoy creating desktop applications. That means I may be a member of a dying breed, since web apps are going to make us all obsolete next week, but I do enjoy doing it.

The bad side of it is, of course that sometimes it's much more convenient to use a web application. For example, I have abandoned my own baby (uRSSus) because google reader is just easier and more convenient to use.

But then I thought... what bothers me of uRSSus? And there are quite a few things!

  1. It's not in all computers I may use

    That means I will not ever be able to use it exclusively.

  2. It's pretty useless without an Internet connection (but so is google reader mostly)

  3. Since I can't use it exclusively, I end with feeds on uRSSus that are not on google reader and viceversa.

  4. It's freaking slow

So, I decided to see what I could do about that without giving up the good side of uRSSus:

  1. It looks much nicer than a web app, because it looks like a desktop app
  2. It does things like opening the site instead of showing the feed item (great for partial content feeds)
  3. I wrote it (yes, that's a feature for me. I like self-made programs)

So, this attempt at rewriting the desktop RSS reader produced this:

As you can see in the above video, this reader syncs the subscription list to google reader. It will also eventually sync your read/unread posts.

It still can open full sites instead of feed items, it has/will have a heck of an offline mode (full pages captured as images, for example), and... it's very very fast.

It's much faster than google reader in Chromium, and hella faster than uRSSus. That was done via smarter coding, so it probably means I was braindead before and experienced a minor recovery.

The code is not fit for release (for example, the database schema will change) but you can try it: http://code.google.com/p/kakawana/source/checkout

2010-07-16 17:14

Learn python! For free! With me! (in part)

I'm one of the speakers for the free python courses in FM La Tribu, in Buenos Aires.

Every saturday you can learn something from one of the best python programmers in a thousand miles, or from me.

I'll be teaching about virtualenv, buildout, nose and other things on August 21, about GUI stuff on September 25 and October 2, and about PyQt in October 30.

This is all for free, and I hope lots of people show up!

The full schedule is here.

Contents © 2000-2019 Roberto Alsina