.:: StyXman's glob ::.StyXman's globhttp://grulicueva.homelinux.net/~mdione/glob//StyXman's globikiwiki2009-11-20T16:42:58Zsatyr-0.1-beta1http://grulicueva.homelinux.net/~mdione/glob//posts/satyr-0.1-beta1/2009-11-20T16:42:58Z2009-11-20T00:38:21Z
<p>Today I sat down and tried to refactor <code>satyr</code> once
more after dinner. This time I was trying to decouple the
functionality related to multi-collection playlists from
<code>PlayListModel</code> while moving it to the
<code>default</code> skin. The idea was to be able to create
another skin which used a <code>QTableView</code>, which in turn
would be a trampoline for implementing tag editing and writing. But
I started to stall a little, and that normally provokes me to
defocus, to dezone. When that happens, I go and read some piled up
posts in <code>akregator</code>[1].</p>
<p>This time I came around <a href=
"http://www.cyrius.com/journal/fossbazaar/lessons-from-freedos">a
post by Martin Michlmayr</a> (who I read through <a href=
"http://planet.debian.org/">Planet Debian</a>) from 10 days ago
which talks about lessons learned about free software projects.
Actually the post is just a resume of 4 posts from the FreeDOS
founder Jim Hall. At some point he writes «releases are
important».</p>
<p><strong>Bing!</strong> goes my head[2]. <code>satyr</code> is
already 3 months, 12 days or 117 revisions old and I hadn't
released it yet, even after I promissed to do so almost a month
ago! The problem is that I kept adding features (and squashing bugs
in <code>Phonon</code>[3]) and completely forgot about releasing.
Just one semi-colon before he also writes «initial users of the
software should be recruited as developers»... which users? If one
doesn't release, one might never have users to recruit!</p>
<p>So instead of the pharaonic refactor I had in mind (an in
another branch, blessed be <code>bazaar</code>) I wrote a
<code>setup.py</code> script in 15 minutes, massaged a little the
files (I had to create a package and modify almost all the files to
reflect this), tested a little, and produced a nice triplet of
files:</p>
<pre>
<code>satyr-0.1-beta1.tar.bz2
satyr-0.1-beta1.tar.gz
satyr-0.1-beta1.zip
</code>
</pre>
<p>So there you have it, a realease! Ok, it's a 'beta1', but it's
out. Go grab it, test it, complain about bugs, tell us you like it,
suggest improvements, whatever! And tell your best friend to use
it, even if you don't like it! Where to get it? Why, from <a href=
"http://mirrors.aixtools.net/sv/satyr/">the project's download's
page</a>, of course!</p>
<hr />
<p><a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/satyr/">satyr</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/pykde/">pykde</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/python/">python</a></p>
<hr />
<p>[1] Yes, once upon a time I developed my own feed reader called
<code>kReiSSy</code>, but it's implemented in <code>PyKDE3</code>
and I don't plan to port it yet, even if somehow is better for me
than <code>akregator</code>. A shame, reallly...</p>
<p>[2] in the same way that a µ-wave oven goes “bing”, not in an
“Eureka!” way...</p>
<p>[3] I even fixed the need for the 'file' scheme in the Gstreamer
backend.</p>
qlistmodel-in-pyqthttp://grulicueva.homelinux.net/~mdione/glob//posts/qlistmodel-in-pyqt/2009-11-19T00:28:01Z2009-11-19T00:28:01Z
<p>One of the things I had to while developing <code>satyr</code>
is building a model for a <code>QListViewer</code>. It should be
straighforward from qt's documentation, but I found a couple of
things that I would like to put in a post, specially because there
doesn't seem to be much models in <code>PyQt4</code> easily found
in the web.</p>
<p>According to its description, a subclass of
<code>QAbstractListModel</code> as this one should mostly implement
the <code>data()</code> and <code>rowCount()</code> methods, which
is true. This example creates a read-only model, so no need to
implement <code>setData()</code>, but given the simplicity of
<code>data()</code>, it doesn't seem too difficult to do. I also
wanted it to react when more <code>Song</code>s were added on the
fly[1].</p>
<p>The method <code>data()</code> is the most important one. It is
not only used for retrieving the data itself, but also some
metadata useful for showing the data, like icons and other stuff.
For selecting what the caller wants, it refers a
<code>Qt.ItemDataRole</code>. The role for the data itself is
<code>Qt.DisplayRole</code>. One of the particularities of this
method is that it could be called with any vegetable as input;
namely, it can refer to a row that does not exist anymore or for
metadata that you don't care about. In those cases you must return
an empty <code>QVariant</code>, not <code>None</code>. So, a first
implementation is:</p>
<pre class="hl">
<span class="hl kwa">def</span> <span class=
"hl kwd">data</span> <span class="hl sym">(</span>self<span class=
"hl sym">,</span> modelIndex<span class=
"hl sym">,</span> role<span class="hl sym">):</span>
<span class="hl kwa">if</span> modelIndex<span class=
"hl sym">.</span><span class="hl kwd">isValid</span> <span class=
"hl sym">()</span> <span class=
"hl kwa">and</span> modelIndex<span class=
"hl sym">.</span><span class="hl kwd">row</span> <span class=
"hl sym">()<</span>self<span class=
"hl sym">.</span>count <span class=
"hl kwa">and</span> role<span class=
"hl sym">==</span>Qt<span class="hl sym">.</span>DisplayRole<span class="hl sym">:</span>
<span class=
"hl slc"># songForIndex() returns the Song corresponding to the row</span>
song<span class="hl sym">=</span> self<span class=
"hl sym">.</span><span class=
"hl kwd">songForIndex</span> <span class=
"hl sym">(</span>modelIndex<span class=
"hl sym">.</span><span class="hl kwd">row</span> <span class=
"hl sym">())</span>
<span class=
"hl slc"># formatSong() returns a QString with the data to show</span>
data<span class="hl sym">=</span> <span class=
"hl kwd">QVariant</span> <span class=
"hl sym">(</span>self<span class="hl sym">.</span><span class=
"hl kwd">formatSong</span> <span class=
"hl sym">(</span>song<span class="hl sym">))</span>
<span class="hl kwa">else</span><span class="hl sym">:</span>
data<span class="hl sym">=</span> <span class=
"hl kwd">QVariant</span> <span class="hl sym">()</span>
<span class="hl kwa">return</span> data
</pre>
<p>This method, together with a <code>rowCount()</code> that simply
returns <code>self.count</code>, is enough for showing data that is
already there. Notice that the <code>QModelIndex</code> can be not
valid, and in this case we only care about its row because we're a
list.</p>
<p>But then I wanted my <code>QListViewer</code> to show songs
progresively as they are loaded/scanned[2] and also as they are
found as new. But then a problem arises: the view is like a table
of only one column. The width of this colunm at the begining is the
same width as the <code>QListView</code> itself. But what happens
when the string shown is too big? What happens is that it gets
chopped. We must inform the view that some of the rows are bigger.
That's where the metadata comes into play.</p>
<p>Another possible role is <code>Qt.SizeHintRole</code>. If we
return a size instead of an empty <code>QVariant</code>, that size
will be used to expand the column as needed, even giving us a
scrollbar if it's wider that the view.</p>
<p>Now, we're supposed to show the tags for the <code>Song</code>
(that's what <code>formatSong()</code> does if possible; if not, it
simply returns the filepath), so this width should be calculated
based on the length of the string that represents the song[3]. But
if we try to read the tags for all the songs as we load the
<code>Collection</code>, we end up with too much disk activity
before you can show anything to the user, which is unacceptable[4].
So instead we calculate based on the filepath, which is used for
<code>Songs</code> with too few tags anyways. Here's the hacky
code:</p>
<pre class="hl">
<span class="hl sym">...</span>
<span class="hl slc"># FIXME: kinda hacky</span>
self<span class="hl sym">.</span>fontMetrics<span class=
"hl sym">=</span> <span class=
"hl kwd">QFontMetrics</span> <span class=
"hl sym">(</span>KGlobalSettings<span class=
"hl sym">.</span><span class=
"hl kwd">generalFont</span> <span class="hl sym">())</span>
<span class="hl sym">...</span>
<span class="hl kwa">def</span> <span class=
"hl kwd">data</span> <span class="hl sym">(</span>self<span class=
"hl sym">,</span> modelIndex<span class=
"hl sym">,</span> role<span class="hl sym">):</span>
<span class="hl kwa">if</span> modelIndex<span class=
"hl sym">.</span><span class="hl kwd">isValid</span> <span class=
"hl sym">()</span> <span class=
"hl kwa">and</span> modelIndex<span class=
"hl sym">.</span><span class="hl kwd">row</span> <span class=
"hl sym">()<</span>self<span class=
"hl sym">.</span>count<span class="hl sym">:</span>
song<span class="hl sym">=</span> self<span class=
"hl sym">.</span><span class=
"hl kwd">songForIndex</span> <span class=
"hl sym">(</span>modelIndex<span class=
"hl sym">.</span><span class="hl kwd">row</span> <span class=
"hl sym">())</span>
<span class="hl kwa">if</span> role<span class=
"hl sym">==</span>Qt<span class=
"hl sym">.</span>DisplayRole<span class="hl sym">:</span>
data<span class="hl sym">=</span> <span class=
"hl kwd">QVariant</span> <span class=
"hl sym">(</span>self<span class="hl sym">.</span><span class=
"hl kwd">formatSong</span> <span class=
"hl sym">(</span>song<span class="hl sym">))</span>
<span class="hl kwa">elif</span> role<span class=
"hl sym">==</span>Qt<span class=
"hl sym">.</span>SizeHintRole<span class="hl sym">:</span>
<span class=
"hl slc"># calculate something based on the filepath</span>
data<span class="hl sym">=</span> <span class=
"hl kwd">QVariant</span> <span class=
"hl sym">(</span>self<span class=
"hl sym">.</span>fontMetrics<span class=
"hl sym">.</span><span class="hl kwd">size</span> <span class=
"hl sym">(</span>Qt<span class=
"hl sym">.</span>TextSingleLine<span class=
"hl sym">,</span> song<span class=
"hl sym">.</span>filepath<span class="hl sym">))</span>
<span class="hl kwa">else</span><span class=
"hl sym">:</span>
data<span class="hl sym">=</span> <span class=
"hl kwd">QVariant</span> <span class="hl sym">()</span>
<span class="hl kwa">else</span><span class="hl sym">:</span>
data<span class="hl sym">=</span> <span class=
"hl kwd">QVariant</span> <span class="hl sym">()</span>
<span class="hl kwa">return</span> data
</pre>
<p>The last point then is reacting to <code>Song</code>s are added
on the fly. This is also easy: you tell the views you're about to
insert rows, you insert them, tell the views you finished, and then
emit <code>dataChanged()</code>:</p>
<pre class="hl">
<span class="hl kwa">def</span> <span class=
"hl kwd">addSong</span> <span class=
"hl sym">(</span>self<span class="hl sym">):</span>
<span class=
"hl slc"># lastIndex keeps track of the last index used.</span>
row<span class="hl sym">=</span> self<span class=
"hl sym">.</span>lastIndex
self<span class="hl sym">.</span>lastIndex<span class=
"hl sym">+=</span> <span class="hl num">1</span>
self<span class="hl sym">.</span><span class=
"hl kwd">beginInsertRows</span> <span class=
"hl sym">(</span><span class=
"hl kwd">QModelIndex</span> <span class=
"hl sym">(),</span> row<span class=
"hl sym">,</span> row<span class="hl sym">)</span>
<span class=
"hl slc"># actually the Song has already been added to the Collection[5]</span>
<span class="hl slc"># so I don't do anything here,</span>
<span class=
"hl slc"># but if you keep your rows in this model you should do something here</span>
self<span class="hl sym">.</span><span class=
"hl kwd">endInsertRows</span> <span class="hl sym">()</span>
self<span class="hl sym">.</span>count<span class=
"hl sym">+=</span> <span class="hl num">1</span>
modelIndex<span class="hl sym">=</span> self<span class=
"hl sym">.</span><span class="hl kwd">index</span> <span class=
"hl sym">(</span>row<span class="hl sym">,</span> <span class=
"hl num">0</span><span class="hl sym">)</span>
self<span class="hl sym">.</span>dataChanged<span class=
"hl sym">.</span><span class="hl kwd">emit</span> <span class=
"hl sym">(</span>modelIndex<span class=
"hl sym">,</span> modelIndex<span class="hl sym">)</span>
</pre>
<p>Later I'll post any peculiarities I find porting all this stuff
to a read/write <code>QTableModel</code>.</p>
<hr />
<p><a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/satyr/">satyr</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/pykde/">pykde</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/python/">python</a></p>
<hr />
<p>[1] That's material for another post :)</p>
<p>[2] This feature can be said to be a little too much. Actually,
I get a flicker when scanning.</p>
<p>[3] Of course the next step is to use a table view and make a
model for it.</p>
<p>[4] Right now the load time for a <code>Collection</code> of
~6.5k songs is quite long as it is.</p>
<p>[5] This is a design decision which is not relevant to this
example.</p>
skinning-satyrhttp://grulicueva.homelinux.net/~mdione/glob//posts/skinning-satyr/2009-11-19T00:28:01Z2009-11-17T11:15:32Z
<p>One of the features I planned for <code>satyr</code> almost
since the begining was the possibility to have 'skins'. In this
context, a skin would not only implement the look and feel, but
also could implement features the weren't available in the shipped
classes. I also planned to implement this feature after I had most
of the others one already done. But then I was bored this weekend
with nothing to do and I decided to set off to at least investigate
how to do it. Of course, what happened was that I implemented it
almost completely.</p>
<p>Up to now, <code>satyr</code>'s user interface was implemented
in two files: <code>default.ui</code>, which was compiled with
<code>pyuic4</code> into <code>default.py</code>, and some code in
<code>satyr.py</code> itself. This of course would not scale, and I
always had the idea of moving the behaviour implemented in
<code>satyr.py</code> to a file called <code>default.py</code> and
load the ui directly from the <code>default.ui</code> file without
compiling, getting rid of the need for a compilation at the same
time. This also meant that then a skin would consist of a
<code>.py</code> file and possibly a <code>.ui</code> file. There
are three problems to solve for this: getting the
local-to-the-user's skin directory, loading the skin and loading
the correspondant <code>.ui</code> file.</p>
<p>The first part is simple from the <code>PyKDE4</code> point of
view:</p>
<pre class="hl">
<span class=
"hl slc"># get the app's dir; don't forget the trailing '/'!</span>
appDir<span class="hl sym">=</span> KStandardDirs<span class=
"hl sym">.</span><span class=
"hl kwd">locateLocal</span> <span class=
"hl sym">(</span><span class="hl str">'data'</span><span class=
"hl sym">,</span> <span class="hl str">'satyr/'</span><span class=
"hl sym">)</span>
</pre>
<p>I'll first explain the other two parts, loading the skin and the
<code>.ui</code> file, before returning more deeply to the
consequences of this solution.</p>
<p>I put all the skins in a <code>skins</code> subdirectory. To
make it a proper python module I added an empty
<code>__init__.py</code> file. Now, I could simply <code>import
skins.<skinName></code> and possibly instantiate some class
in it, but of course one cannot write that. I could resort to
<code>eval ('import skins.'+skinName)</code>, but we know that
<code>eval()</code> has the most long-standing typo in the history
of computer languages, and it's actually called
<code>evil()</code>.</p>
<p>What we can do is resort to <code>__import__()</code> instead.
This little function does approximately what we want. I say approx
because it has some surprises in the sleeves of its sleeveless
code. I suggest you to go read carefully its documentation.
Meanwhile, the magic itself:</p>
<pre class="hl">
mod<span class="hl sym">=</span> <span class=
"hl kwb">__import__</span> <span class=
"hl sym">(</span><span class="hl str">'skins.'</span><span class=
"hl sym">+</span>skinName<span class="hl sym">,</span> <span class=
"hl kwb">globals</span><span class="hl sym">(),</span> <span class=
"hl kwb">locals</span><span class="hl sym">(),</span> <span class=
"hl str">'MainWindow'</span><span class="hl sym">)</span>
mw<span class="hl sym">=</span> mod<span class=
"hl sym">.</span><span class=
"hl kwd">MainWindow</span> <span class="hl sym">()</span>
</pre>
<p>Loading the <code>.ui</code> file in the skin's code is rather
simple: just get the skin module's filepath, replace
<code>.py</code> with <code>.ui</code>, and load it with
<code>PyQt4.uic.loadUiType()</code>[1]. This function returns a
generated class for the topmost widget and its Qt base class. This
generated class has a <code>setupUi()</code> method that is the one
that actually builds de UI[2]. So, we just instantiate the main
window's class and call its <code>setupUi()</code> method:</p>
<pre class="hl">
<span class="hl kwa">from</span> PyQt4 <span class=
"hl kwa">import</span> uic
<span class="hl slc"># !!! __file__ might end with .py[co]!</span>
uipath<span class="hl sym">=</span> __file__<span class=
"hl sym">[:</span>__file__<span class="hl sym">.</span><span class=
"hl kwd">rfind</span> <span class="hl sym">(</span><span class=
"hl str">'.'</span><span class="hl sym">)]+</span><span class=
"hl str">'.ui'</span>
<span class="hl slc"># I don't care about the base class</span>
<span class="hl sym">(</span>UIMainWindow<span class=
"hl sym">,</span> buh<span class="hl sym">)=</span> uic<span class=
"hl sym">.</span><span class=
"hl kwd">loadUiType</span> <span class="hl sym">(</span>uipath<span class="hl sym">)</span>
self<span class="hl sym">.</span>ui<span class=
"hl sym">=</span> <span class=
"hl kwd">UIMainWindow</span> <span class="hl sym">()</span>
self<span class="hl sym">.</span>ui<span class=
"hl sym">.</span><span class="hl kwd">setupUi</span> <span class=
"hl sym">(</span>self<span class="hl sym">)</span>
</pre>
<p>Note the comment about the <code>__file__</code> attribute of a
module.</p>
<p>Now, and back to the first part, finding the local-to-the-user's
skin directory is the easiest part. From there, things get a little
bit more complicated:</p>
<ul>
<li>The <code>skins</code> subdirectory might not exist.</li>
<li>If you create it, you gotta make sure to also throw in a
<code>__init__.py</code> file.</li>
<li>Once you've done it, you also need to add the
local-to-the-user's app directory to the path. It's easy, just
prepend it to <code>sys.path</code>, so it's used before any
system-wide directory.</li>
<li>The last problem that remains is exactly that: once the
<code>__init__.py</code> is there and the user's dir is prepended
to <code>sys.path</code>, the user's local skin directory is always
used when importing anything from the <code>skins</code> module, so
if a skin is not there it is not loadable. All skins distributed
with <code>satyr</code> will be inaccesible!</li>
</ul>
<p>So I'm in a kind of dead alley here. I have a couple of ideas on
how to work-around this, but they're at best hacky, and I don't
want to implement them until I'm sure that it's inevitable.</p>
<hr />
<p><a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/satyr/">satyr</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/pykde/">pykde</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/python/">python</a></p>
<hr />
<p>[1] Not a very happy name, if you ask me.</p>
<p>[2] Very similar to what you get if you compile the
<code>.ui</code> with <code>pyuic4</code>.</p>
phonon-and-filenames-fixedhttp://grulicueva.homelinux.net/~mdione/glob//posts/phonon-and-filenames-fixed/2009-11-16T09:31:29Z2009-11-15T13:45:16Z
<p>I certainly hope this is the last post in the
<code>Phonon</code>-and-badly-encoded/mixed-encodings-filenames
saga, but I know is just wishful thinking: as all encoding-related
problems they never really dissapear, it's just that you hadn't hit
the right wrong stone yet. In any case, I fixed all my later
problems wherever they where, and now I can answer this question:
how to play files whose filenames are badly encoded and/or have
mixed encodings, all this in <code>Phonon</code>?</p>
<p>Right now the answer is: you have to provide a properly encoded
<code>QUrl</code>. How, you might ask, can I get one of those? Are
they selled in the same odly-looking places where you can buy
cigarretes, or even marihuana[1]? The answer, luckly, is way more
simple.</p>
<p>Putting together all the code I've been showing about
<code>Python</code>, <code>PyQt4/PyKDE4</code> and
<code>Phonon</code> recently, it comes down to this[5]:</p>
<pre>
<code># path is a str()
qba= QByteArray (path)
# the exceptions are not needed,
# but is cleaner if you print the outcome of this
qu= QUrl.fromEncoded (qba.toPercentEncoding ("/ "))
# this is needed by the gstreamer backend[3],
# and the xine backend doesn't complain
qu.setScheme ('file')
</code>
</pre>
<p>... and that's it. You can now create a <code>MediaSource</code>
with this <code>qu</code>.</p>
<p>There are a couple of ideas that I want to express as conclusion
to all this:</p>
<ul>
<li>In an ideal world these things should not happen. But this is
one of the lesser problems with this non-ideal world, so bear with
it.</li>
<li>Paths should not be stored in <code>QString</code>s, even if
they can (and they do) store this kind of pathnames, because if you
try to 'encode' its contents (in the Unicode sense; that is,
convert it to an encoding like UTF-8[4]) you get farts or barks at
best. Yes, you always have <code>constData()</code> but from
<code>QString</code>'s class reference there is no warranty that
this will keep being the case[6].</li>
<li>In fact, <code>QString</code>'s class reference says at some
point: «[one case] where <code>QByteArray</code> is appropriate are
when you need to store raw binary data...», and <a href=
"http://grulicueva.homelinux.net/~mdione/glob/posts/from-qstring-to-bytes-in-pyqt/">
as I already wrote</a>, «[t]his would be the case for paths; you
<strong>need</strong> the bytes».</li>
<li><code>QFile</code> and <code>QDir</code> can only be created
from <code>QString</code>s. I'm not sure if, given all I wrote,
that's right.</li>
</ul>
<p>The good news is that <code>satyr</code> now can play any file
that the backends can whatever their filename-as-string-of-bytes
is, I'm a little bit happier about it, I got another contribution
to <code>KDE</code> and might even have to close a lot of bugs!</p>
<hr />
<p><a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/satyr/">satyr</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/pykde/">pykde</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/python/">python</a>
<a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/phonon/">phonon</a></p>
<hr />
<p>[1] That question is only legal in Nederlands[2] and very few
others cities in the planet.</p>
<p>[2] Actually is not <em>legal</em>. See <a href=
"http://en.wikipedia.org/wiki/Drug_policy_of_the_Netherlands#Non-enforcement">
this wikipedia article</a>.</p>
<p>[3] I might pull up my sleeves again and fix that.</p>
<p>[4] You might have already know this, but if you not: you cannot
print Unicode, because Unicode is not and encoding. You
<em>have</em> to encode it first. Hence, the
<code>toLatin1()</code>, <code>toUtf8()</code> and similar
<code>QString</code> methods, and also the inverse
<code>from*()</code>.</p>
<p>[5] Of course the equivalent C++ code also works, with
<code>path</code> being a <code>char *</code>.</p>
<p>[6] And in the case of <code>PyQt4</code>, that method is not
even available. But <a href=
"http://grulicueva.homelinux.net/~mdione/glob/posts/from-qstring-to-bytes-in-pyqt/">
I already globed about it</a>.</p>
our-man-in-toulonhttp://grulicueva.homelinux.net/~mdione/glob//posts/our-man-in-toulon/2009-11-15T13:45:16Z2009-11-15T13:45:16Z
<p>A couple of days ago <a href=
"http://feedproxy.google.com/~r/ElBlogDeMarcelo/~3/kukV8oltwWI/">Marcelo
Fernández wrote a simple image viewer in <code>PyGTK</code></a>.
It's less than 200 lines long[1], and I thought that it would be
nice to compare how the same app would be written in
<code>PyKDE4</code>. But then I though that it would not be fair,
as <code>KDE</code> is a whole desktop environment and
<code>GTK</code> is 'only' a widget library, so I did it in
<code>PyQt4</code> instead.</p>
<p>To make this even more fair, I hadn't had a good look at the
code itself, I only run it to see what it looks like: a window with
only the shown image in it, both scrollbars, no menu or statusbar,
and no external file, so I assume he builds the ui 'by hand'. He
mentions these features:</p>
<ul>
<li>Pan the image with the mouse.</li>
<li>F1 to F5 handle the zoom from 'fit to window', 25%, 50%, 75%
and 100%.</li>
<li>Zooming with the mouse wheel doesn't work.</li>
</ul>
<p>Here's my take:</p>
<pre class="hl">
<span class="hl slc">#! /usr/bin/python</span>
<span class="hl slc"># -*- coding: utf-8 -*-</span>
<span class=
"hl slc"># OurManInToulon - Example image viewer in PyQt4</span>
<span class=
"hl slc"># Marcos Dione <mdione@grulic.org.ar> - http://grulicueva.homelinux.net/~mdione/glob/</span>
<span class="hl slc"># TODO:</span>
<span class="hl slc"># * add licence! (GPLv2 or later)</span>
<span class="hl kwa">from</span> PyQt4<span class=
"hl sym">.</span>QtGui <span class=
"hl kwa">import</span> QApplication<span class=
"hl sym">,</span> QMainWindow<span class=
"hl sym">,</span> QGraphicsView<span class=
"hl sym">,</span> QGraphicsScene
<span class="hl kwa">from</span> PyQt4<span class=
"hl sym">.</span>QtGui <span class=
"hl kwa">import</span> QPixmap<span class=
"hl sym">,</span> QGraphicsPixmapItem<span class=
"hl sym">,</span> QAction<span class="hl sym">,</span> QKeySequence
<span class="hl kwa">import</span> sys
<span class="hl kwa">class</span> <span class=
"hl kwd">OMITGraphicsView</span> <span class=
"hl sym">(</span>QGraphicsView<span class="hl sym">):</span>
<span class="hl kwa">def</span> <span class=
"hl kwd">__init__</span> <span class=
"hl sym">(</span>self<span class=
"hl sym">,</span> pixmap<span class=
"hl sym">,</span> scene<span class=
"hl sym">,</span> parent<span class=
"hl sym">, *</span>args<span class="hl sym">):</span>
QGraphicsView<span class="hl sym">.</span><span class=
"hl kwd">__init__</span> <span class=
"hl sym">(</span>self<span class=
"hl sym">,</span> scene<span class="hl sym">)</span>
self<span class="hl sym">.</span>zoomLevel<span class=
"hl sym">=</span> <span class="hl num">1.0</span>
self<span class="hl sym">.</span>win<span class=
"hl sym">=</span> parent
self<span class="hl sym">.</span>img<span class=
"hl sym">=</span> pixmap
self<span class="hl sym">.</span><span class=
"hl kwd">setupActions</span> <span class="hl sym">()</span>
<span class="hl kwa">def</span> <span class=
"hl kwd">setupActions</span> <span class=
"hl sym">(</span>self<span class="hl sym">):</span>
<span class="hl slc"># a factory to the right!</span>
zoomfit<span class="hl sym">=</span> <span class=
"hl kwd">QAction</span> <span class=
"hl sym">(</span>self<span class="hl sym">)</span>
zoomfit<span class="hl sym">.</span><span class=
"hl kwd">setShortcuts</span> <span class=
"hl sym">([</span>QKeySequence<span class=
"hl sym">.</span><span class=
"hl kwd">fromString</span> <span class="hl sym">(</span><span class="hl str">'F1'</span><span class="hl sym">)])</span>
zoomfit<span class="hl sym">.</span>triggered<span class=
"hl sym">.</span><span class="hl kwd">connect</span> <span class=
"hl sym">(</span>self<span class=
"hl sym">.</span>zoomfit<span class="hl sym">)</span>
self<span class="hl sym">.</span><span class=
"hl kwd">addAction</span> <span class=
"hl sym">(</span>zoomfit<span class="hl sym">)</span>
zoom25<span class="hl sym">=</span> <span class=
"hl kwd">QAction</span> <span class=
"hl sym">(</span>self<span class="hl sym">)</span>
zoom25<span class="hl sym">.</span><span class=
"hl kwd">setShortcuts</span> <span class=
"hl sym">([</span>QKeySequence<span class=
"hl sym">.</span><span class=
"hl kwd">fromString</span> <span class="hl sym">(</span><span class="hl str">'F2'</span><span class="hl sym">)])</span>
zoom25<span class="hl sym">.</span>triggered<span class=
"hl sym">.</span><span class="hl kwd">connect</span> <span class=
"hl sym">(</span>self<span class=
"hl sym">.</span>zoom25<span class="hl sym">)</span>
self<span class="hl sym">.</span><span class=
"hl kwd">addAction</span> <span class=
"hl sym">(</span>zoom25<span class="hl sym">)</span>
zoom50<span class="hl sym">=</span> <span class=
"hl kwd">QAction</span> <span class=
"hl sym">(</span>self<span class="hl sym">)</span>
zoom50<span class="hl sym">.</span><span class=
"hl kwd">setShortcuts</span> <span class=
"hl sym">([</span>QKeySequence<span class=
"hl sym">.</span><span class=
"hl kwd">fromString</span> <span class="hl sym">(</span><span class="hl str">'F3'</span><span class="hl sym">)])</span>
zoom50<span class="hl sym">.</span>triggered<span class=
"hl sym">.</span><span class="hl kwd">connect</span> <span class=
"hl sym">(</span>self<span class=
"hl sym">.</span>zoom50<span class="hl sym">)</span>
self<span class="hl sym">.</span><span class=
"hl kwd">addAction</span> <span class=
"hl sym">(</span>zoom50<span class="hl sym">)</span>
zoom75<span class="hl sym">=</span> <span class=
"hl kwd">QAction</span> <span class=
"hl sym">(</span>self<span class="hl sym">)</span>
zoom75<span class="hl sym">.</span><span class=
"hl kwd">setShortcuts</span> <span class=
"hl sym">([</span>QKeySequence<span class=
"hl sym">.</span><span class=
"hl kwd">fromString</span> <span class="hl sym">(</span><span class="hl str">'F4'</span><span class="hl sym">)])</span>
zoom75<span class="hl sym">.</span>triggered<span class=
"hl sym">.</span><span class="hl kwd">connect</span> <span class=
"hl sym">(</span>self<span class=
"hl sym">.</span>zoom75<span class="hl sym">)</span>
self<span class="hl sym">.</span><span class=
"hl kwd">addAction</span> <span class=
"hl sym">(</span>zoom75<span class="hl sym">)</span>
zoom100<span class="hl sym">=</span> <span class=
"hl kwd">QAction</span> <span class=
"hl sym">(</span>self<span class="hl sym">)</span>
zoom100<span class="hl sym">.</span><span class=
"hl kwd">setShortcuts</span> <span class=
"hl sym">([</span>QKeySequence<span class=
"hl sym">.</span><span class=
"hl kwd">fromString</span> <span class="hl sym">(</span><span class="hl str">'F5'</span><span class="hl sym">)])</span>
zoom100<span class="hl sym">.</span>triggered<span class=
"hl sym">.</span><span class="hl kwd">connect</span> <span class=
"hl sym">(</span>self<span class=
"hl sym">.</span>zoom100<span class="hl sym">)</span>
self<span class="hl sym">.</span><span class=
"hl kwd">addAction</span> <span class=
"hl sym">(</span>zoom100<span class="hl sym">)</span>
<span class="hl kwa">def</span> <span class=
"hl kwd">zoomfit</span> <span class=
"hl sym">(</span>self<span class=
"hl sym">, *</span>ignore<span class="hl sym">):</span>
winSize<span class="hl sym">=</span> self<span class=
"hl sym">.</span><span class="hl kwd">size</span> <span class=
"hl sym">()</span>
imgSize<span class="hl sym">=</span> self<span class=
"hl sym">.</span>img<span class="hl sym">.</span><span class=
"hl kwd">size</span> <span class="hl sym">()</span>
<span class="hl kwa">print</span> winSize<span class=
"hl sym">,</span> imgSize
hZoom<span class="hl sym">=</span> <span class=
"hl num">1.0</span><span class="hl sym">*</span>winSize<span class=
"hl sym">.</span><span class="hl kwd">width</span> <span class=
"hl sym">()/</span>imgSize<span class="hl sym">.</span><span class=
"hl kwd">width</span> <span class="hl sym">()</span>
vZoom<span class="hl sym">=</span> <span class=
"hl num">1.0</span><span class="hl sym">*</span>winSize<span class=
"hl sym">.</span><span class="hl kwd">height</span> <span class=
"hl sym">()/</span>imgSize<span class="hl sym">.</span><span class=
"hl kwd">height</span> <span class="hl sym">()</span>
zoomLevel<span class="hl sym">=</span> <span class=
"hl kwb">min</span> <span class="hl sym">(</span>hZoom<span class=
"hl sym">,</span> vZoom<span class="hl sym">)</span>
<span class="hl kwa">print</span> zoomLevel
self<span class="hl sym">.</span><span class=
"hl kwd">zoomTo</span> <span class=
"hl sym">(</span>zoomLevel<span class="hl sym">)</span>
<span class="hl kwa">def</span> <span class=
"hl kwd">zoom25</span> <span class=
"hl sym">(</span>self<span class="hl sym">, *</span>ignore<span class="hl sym">):</span>
self<span class="hl sym">.</span><span class=
"hl kwd">zoomTo</span> <span class="hl sym">(</span><span class=
"hl num">0.25</span><span class="hl sym">)</span>
<span class="hl kwa">def</span> <span class=
"hl kwd">zoom50</span> <span class=
"hl sym">(</span>self<span class="hl sym">, *</span>ignore<span class="hl sym">):</span>
self<span class="hl sym">.</span><span class=
"hl kwd">zoomTo</span> <span class="hl sym">(</span><span class=
"hl num">0.5</span><span class="hl sym">)</span>
<span class="hl kwa">def</span> <span class=
"hl kwd">zoom75</span> <span class=
"hl sym">(</span>self<span class="hl sym">, *</span>ignore<span class="hl sym">):</span>
self<span class="hl sym">.</span><span class=
"hl kwd">zoomTo</span> <span class="hl sym">(</span><span class=
"hl num">0.75</span><span class="hl sym">)</span>
<span class="hl kwa">def</span> <span class=
"hl kwd">zoom100</span> <span class=
"hl sym">(</span>self<span class=
"hl sym">, *</span>ignore<span class="hl sym">):</span>
self<span class="hl sym">.</span><span class=
"hl kwd">zoomTo</span> <span class="hl sym">(</span><span class=
"hl num">1.0</span><span class="hl sym">)</span>
<span class="hl kwa">def</span> <span class=
"hl kwd">zoomTo</span> <span class=
"hl sym">(</span>self<span class="hl sym">,</span> zoomLevel<span class="hl sym">):</span>
scale<span class="hl sym">=</span> zoomLevel<span class=
"hl sym">/</span>self<span class="hl sym">.</span>zoomLevel
<span class="hl kwa">print</span> <span class=
"hl str">"scaling"</span><span class="hl sym">,</span> scale
self<span class="hl sym">.</span><span class=
"hl kwd">scale</span> <span class=
"hl sym">(</span>scale<span class="hl sym">,</span> scale<span class="hl sym">)</span>
self<span class="hl sym">.</span>zoomLevel<span class=
"hl sym">=</span> zoomLevel
<span class="hl kwa">if</span> __name__<span class=
"hl sym">==</span><span class=
"hl str">'__main__'</span><span class="hl sym">:</span>
<span class=
"hl slc"># this code is enough for loading an image and show it!</span>
app<span class="hl sym">=</span> <span class=
"hl kwd">QApplication</span> <span class=
"hl sym">(</span>sys<span class="hl sym">.</span>argv<span class=
"hl sym">)</span>
win<span class="hl sym">=</span> <span class=
"hl kwd">QMainWindow</span> <span class="hl sym">()</span>
pixmap<span class="hl sym">=</span> <span class=
"hl kwd">QPixmap</span> <span class=
"hl sym">(</span>sys<span class="hl sym">.</span>argv<span class=
"hl sym">[</span><span class="hl num">1</span><span class=
"hl sym">])</span>
qgpi<span class="hl sym">=</span> <span class=
"hl kwd">QGraphicsPixmapItem</span> <span class=
"hl sym">(</span>pixmap<span class="hl sym">)</span>
scene<span class="hl sym">=</span> <span class=
"hl kwd">QGraphicsScene</span> <span class="hl sym">()</span>
scene<span class="hl sym">.</span><span class=
"hl kwd">addItem</span> <span class=
"hl sym">(</span>qgpi<span class="hl sym">)</span>
view<span class="hl sym">=</span> <span class=
"hl kwd">OMITGraphicsView</span> <span class=
"hl sym">(</span>pixmap<span class=
"hl sym">,</span> scene<span class=
"hl sym">,</span> win<span class="hl sym">)</span>
view<span class="hl sym">.</span><span class=
"hl kwd">setDragMode</span> <span class=
"hl sym">(</span>QGraphicsView<span class=
"hl sym">.</span>ScrollHandDrag<span class="hl sym">)</span>
view<span class="hl sym">.</span><span class=
"hl kwd">show</span><span class="hl sym">()</span>
app<span class="hl sym">.</span><span class=
"hl kwd">exec_</span> <span class="hl sym">()</span>
<span class="hl slc"># up to here!</span>
<span class="hl slc"># end</span>
</pre>
<p>Things to note:</p>
<ul>
<li>The code for loading, showing the image and pan support is only
13 lines of <code>Python</code> code, including 3 imports. The
resulting app is also able to handle vector graphics, but of course
I didn't exploit that, I just added a
<code>QPixmap</code>/<code>QGraphicsPixmapItem</code> pair.</li>
<li>Zooming is implemented via <code>QGraphicsView.scale()</code>,
which is accumulative (scaling twice to 0.5 actually scales to 0.25
of the original size), so I have to keep the zoom level all the
time. There should be a <code>zoom()</code> interface!</li>
<li>The code for calculating the scale level is not very good:
scaling between 75% and 50% or 25% produces scales of 0.666 and
0.333, which I think at the end of the day will accumulate a lot of
error.</li>
<li>For the same reason, <code>zoomToFit()</code> has to do some
magic. I also got hit by the integer division of
<code>Python</code> (I was getting zoom factors of 0) so I had to
add <code>1.0*</code> to the claculations. It's good that this is
fixed in <code>Python2.6/3.0</code>.</li>
<li>The size reported by the <code>QMainWindow</code> was any
vegetable (it said 640x480 when it actually was 960x600), so I used
the <code>QGraphicsView</code> instead. WTF?</li>
<li>For some strange reason <code>zoomToFit()</code> scales the
image a little bigger than it should, so a scrollbar appears in the
direction of the constraining dimension.</li>
<li>Less that 100 lines! Even if <code>setupActions()</code> could
surely be improved.</li>
<li>In Marcelo's favor I should mention that he writes docstrings
for most of his methods both in english and spanish (yes, of course
I read his code after I finished mine). I barely put a couple of
comments, but doing the same should add 10 more lines, tops. Also,
I don't want to convert this into a who-has-it-smaller contest (the
code, I mean :).</li>
<li>It took me approx 3 hours, with no previous knowledge of how to
do it and no internet connection, so no asking around. I just used
the «Qt Reference Documentation», going to the «Gropued Classes»
page and to the «Graphics View Classes» from there.</li>
<li>It doesn't zoom with the mouse wheel either.</li>
<li>The default colors of <code>ikiwiki</code>'s
<code>format</code> plugin are at most sucky, but better than
nothing.</li>
</ul>
<hr />
<p><a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/omit/">omit</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/pykde/">pykde</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/python/">python</a></p>
<hr />
<p>[1] Unluckly he didn't declared which license it has, so I'm not
sure if I really can do this. I GPL'ed mine.</p>
phonon-and-filenameshttp://grulicueva.homelinux.net/~mdione/glob//posts/phonon-and-filenames/2009-11-15T13:45:16Z2009-10-25T22:57:47Z
<p>More than two months ago <a href=
"http://grulicueva.homelinux.net/~mdione/glob/posts/from-qstring-to-bytes-in-pyqt/">
I globed about QStrings and paths</a>. The problem was this: my app
accepts paths via command line, which are processed via
<code>KCmdLineOptions</code>; which in turn converts everything to
<code>QString</code>s. What I wanted were paths, which are more
like <code>QByteArray</code>s, not <code>QString</code>s (because
the latter have internally an unicode representation; more on that
later). Including <code>PyQt4</code> in the equation forced me to
resort to <code>QByteArray</code> to get the path as a
<code>str</code> instead of using <code>QString.constData()</code>
(<code>PyQt4</code> doesn't export that function). But that's only
the beginning of the problem.</p>
<p>Take for instance this situation. I have a music collection that
I've been building for years now (more that 10, I think). In the
old times of this collection the filenames were encoded in
<code>iso-8859-1</code>. Then the future came and converted all my
machines to <code>utf-8</code>. But only the software; the
filesystems were in one way or another inherited from system to
system, from machine to machine. So I ended with a mixture of utf
and iso filenames, to the point where I have a file whose filename
is in iso, but the directory where it is is in utf. Yes, I know, it
<em>is</em> a mess. But if I take any decent media player, I can
play the file allright. That's because the filesystem knows nothing
of encodings (otherwise it would reject badly encoded
filenames).</p>
<p>I just spent last saturday making sure that <code>satyr</code>
only stored filepaths in <code>str</code>s, not
<code>unicode</code>s or <code>QString</code>s. It took
concentration, but having just a bunch of classes and only 3 or 4
points where the filepaths are managed it wasn't that difficult.
Still, it took a day. But then, as I mentioned in that post,
<code>Phonon</code> the is not able to play such files... or so I
thought.</p>
<p>If you run <code>satyr</code> after executing <code>export
PHONON_XINE_DEBUG=1</code> you'll see a lot of <code>Phonon</code>
debug info in the console (not that there is another way to run
<code>satyr</code> right now anyways). Among all that info you'll
see lines such as these two:</p>
<pre>
<code>void Phonon::Xine::XineStream::setMrl(const QByteArray&, Phonon::Xine::XineStream::StateForNewMrl) ...
bool Phonon::Xine::XineStream::xineOpen(Phonon::State) xine_open succeeded for m_mrl = ...
</code>
</pre>
<p>If you're sharp enough (I'm not; sandsmark from
<code>#phonon</code> had to tell me) you'll note the mention of
MRL's. MRL's are <code>xine</code>'s URL for media. As any URL,
they can (and most of the time must) encode 'strange' characters
with the so-called "percent encoding". This means that no matter
what encodings the different parts of a filepath is in, I just add
<code>file://</code> at the beginning and then I can safely encode
it scaping non-ascii characters to %xx representations... or that's
what the theory says. One thing to note is that the
<code>file://</code> part <em>must not</em> be scaped;
<code>xine</code> complains that the file does not exist in that
case.</p>
<p>Looking for help in <code>Qt</code>'s classes one can find
<code>QUrl</code> and the already known <code>QByteArray</code>. I
can call <code>QByteArray.toPercentEnconding()</code> from my
<code>str</code> and feed that to
<code>QUrl.fromPercentEncoding()</code> (which strangely returns a
<code>QString</code>, which is exactly what we're avoiding) or
<code>QUrl.fromEncoded()</code>. But then the first function
encodes too much, replacing <code>://</code> with
<code>%3A%2F%2F</code>. No fun.</p>
<p>Ok, let's try creating a <code>QByteArray</code> with only the
<code>file://</code> and then <code>append()</code> the
<code>toPercentEncoding()</code> of the path only. It works:</p>
<pre>
<code>PyQt4.QtCore.QByteArray('file://%2Fhome%2Fmdione...%2F%C3%9Altimo%20bondi%20a%20Finisterre%2F07-%20La%20peque%F1a%20novia%20del%20carioca.wav')
</code>
</pre>
<p>But then calling <code>QUrl.fromEncoded()</code> gives:</p>
<pre>
<code>PyQt4.QtCore.QUrl("file://xn--/home/mdione.../ltimo bondi a finisterre/07- la pequea novia del carioca-wkmz60758d.wav")
</code>
</pre>
<p>The URL got somehow <a href=
"http://en.wikipedia.org/wiki/Punycode">puny-encoded</a>, which of
course <code>xine</code> doesn't recognize for local files.</p>
<p><em>Another</em> option is to create an empty <code>QUrl</code>,
call <code>setEncodedUrl()</code> with the ParsingMode to
<code>QUrl.StrictMode</code> so we avoid 50 lines of code that
start <a href=
"http://qt.gitorious.org/qt/qt/blobs/4.5/src/corelib/io/qurl.cpp#line4094">
here</a>[1] that try to escape everything all over again (and I
already had some double-or-even-triple-enconding nightmares parsing
RSS/Atom feeds last year, thank you), but we get puny-encoded again
(maybe it is 'pwny-encoded'?).</p>
<p>Last resort: backtrack to the point were we created only one
<code>QByteArray</code> with the path and call
<code>toPercentEncoding()</code>; feed that to the method
<code>setEncodedPath()</code> of an empty <code>QUrl</code>. Then
we add the last piece calling <code>setScheme('file')</code> and
we're ready! Of course we're not:</p>
<pre>
<code>PyQt4.QtCore.QByteArray('file:%2Fhome%2Fmdione...%2F%C3%9Altimo%20bondi%20a%20Finisterre%2F07-%20La%20peque%F1a%20novia%20del%20carioca.wav')
</code>
</pre>
<p>Notice the lack of the two <code>//</code> after
<code>file:</code>? <code>xine</code> doesn't like it; hence, I
don't either.</p>
<p>Ok, this post got too long. I hope I can resolve this soon, I
already spent too much time on it. At least a good part of it was
expaining it, so others don't have to suffer the same as I did.</p>
<p>BTW, <code>satyr</code> will shortly be released, whether I fix
this bug or not.</p>
<hr />
<p><a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/satyr/">satyr</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/pykde/">pykde</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/phonon/">phonon</a></p>
<hr />
<p>[1] Look at the size of that file! 6k lines to handle URL's! Who
would say it was so difficult... Once more I'm remembered of how
lucky I am to have this libraries at the tips of my fingers,
yay!</p>
satyrhttp://grulicueva.homelinux.net/~mdione/glob//posts/satyr/2009-10-01T22:21:14Z2009-10-01T22:21:14Z
<p>For a couple of months I've been globbing about PyKDE4 stuff,
and laterally talking about my last project: <code>satyr</code>.
<code>satyr</code> (it's name should always be written in
lowercase) should have the following features:</p>
<ul>
<li>The PlayList and the Collection(s)[0] are the same thing.</li>
<li>Yours is a Collection of Albums, nothing else[3].</li>
<li>Some Albums are from the same artists and some are
compilations[3].</li>
<li>If you want an ephemeral playlist you could queue
songs[1].</li>
<li>If you want non-ephemeral playlists, then this player is not
for you.</li>
<li>Ability to search à la <code>xmms</code>, but in the same
interface[2]</li>
<li>Tag reading and writing[3].</li>
<li>Order you collection based on the tags[3].</li>
<li>The collection discovers new files and adds them to the
playlist on the fly[4].</li>
<li>Be able to use all the program only with your keyboard (die,
mouse, die!)</li>
</ul>
<p>This and other features should be available soon. The coding has
been fast lately, mainly because the Qt/KDE libs are fantastic to
work with. The only thing I couldn't do was to read the tags before
playing them, so I relied into the <code>kaa</code> libraries.</p>
<p><a href="https://savannah.nongnu.org/projects/satyr/">The
project is hosted in savannah</a>, and right now there is no
tarball (It's marked as alpha state because I sent a couple of
tarballs to some friends who asked for them), so the only way right
now is to branch anonimously <a href=
"https://savannah.nongnu.org/bzr/?group=satyr">the
<code>bazaar</code> repo</a>. I hope you download and enjoy it as
much as I do.</p>
<p><a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/satyr/">satyr</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/pykde/">pykde</a></p>
<hr />
<p>[0] The support for several collections is not complete yet.</p>
<p>[1] Functionality available via dbus only at the moment.</p>
<p>[2] This is with the current GUI. I'm also thinking in
several/pluginable GUI's</p>
<p>[3] Not yet available.</p>
<p>[4] Of course this only works if it's running. Otherwise, you
can always ask for a rescanning[1].</p>
python-magic-filename-encodinghttp://grulicueva.homelinux.net/~mdione/glob//posts/python-magic-filename-encoding/2009-08-29T14:49:06Z2009-08-29T14:49:06Z
<p>There's a problem with <code>Phonon</code>. If you try to play a
file in a format that it doesn't support, instead of failing or
simply sending the <code>finished()</code> signal it just does
nothing. I'll have to look more deeply into this and figure out if
it's a bug or feature or how to work-around it.</p>
<p>For now I just decided to use <code>python-magic</code> to
decide if the file is playable or not. This module uses
<code>libmagic</code>, the same library that the <code>file</code>
utility uses. But I hit a problem with it. One of its ussages is to
call <code>magic.file()</code> with the filename, but if the
filename contains a non-ascii character you get something like
this:</p>
<pre>
<code>Traceback (most recent call last):
File "satyr.py", line 173, in next
self.play ()
File "satyr.py", line 128, in play
mimetype_enc= self.magic.file (self.filename)
UnicodeEncodeError: 'ascii' codec can't encode character u'\xed' in position 37: ordinal not in range(128)
</code>
</pre>
<p>I found a workaround. There's another usage, which is to call
<code>magic.buffer()</code> with a piece of data to recognize. So I
just simply <code>open()</code> the file, <code>read()</code> 4KiB
and pass the data to that function.</p>
<pre>
<code>f= file (self.filename)
data= f.read (4096)
mimetype_enc= self.magic.buffer (data)
</code>
</pre>
<p>Now it works properly. I will file a bug report to the
<code>file</code> package once I resort out my mail setup (I cannot
send bugs with <code>reportbug</code>).</p>
<p><a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/python/">python</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/satyr/">satyr</a></p>
from-qstring-to-bytes-in-pyqthttp://grulicueva.homelinux.net/~mdione/glob//posts/from-qstring-to-bytes-in-pyqt/2009-11-15T13:45:16Z2009-08-17T18:16:29Z
<p><code>Satyr</code> handles paths. There are some problems with
paths and (<em>sigh</em>) encondings. Of those, here are two:
there's no way to know in which encoding the filenames in a
filesystem are enconded (f.i., there's no way to ask the
filesystem), and even if that were possible, the filenames might
not even be enconded in that enconding. In these (still!)
transitioning times, lots and lots and shitloads of filesystems are
used in UTF-8 environments, but some filenames are still in old
ISO-8859-1 or whatever the system was using before.</p>
<p>Then comes <code>QString</code>. I'm taking a path from the
command line; this path is the location of the (right now only)
<code>Collection</code> for the player. I'm handling the command
line using <code>KCmdLineOptions</code>, which returns
<code>QStrings</code>. As we all know, <code>QString</code>, just
like the <code>unicode</code> type in <code>Python</code>, handles
all the data internally as Unicode, which is The Right Thing™. If
you really need the internal data, say as bytes, you can always
call the <code>constData()</code> method and be happy with it[1].
This would be the case for paths; you <strong>need</strong> the
bytes.</p>
<p>Then comes <code>PyQt4</code>. For some reason, which maybe I
will ask in <a href=
"http://www.riverbankcomputing.co.uk/mailman/listinfo/pyqt">the
pyqt devel ML</a>[2], <code>constData()</code> is not available.
What to do? Well, that's what this post is about. What you're about
to read is hacky as it can be, but then it works. I might feel
dirty, but I can live with it. As long as I mark it as a utter/über
hack and promise to revert it once that's possible...</p>
<pre>
<code># path is a QString
qba= QByteArray ()
qba.append (path)
path= str (qba)
# now path is a list of bytes/string.
</code>
</pre>
<p>Even if this part of the bug is fixed, then
<code>Phonon.MediaSource</code> or
<code>Phonon.MediaObject.play()</code> fails when feeded that same
path with this message:</p>
<pre>
<code>ERROR: backend MediaObject reached ErrorState after 1 . It seems a KioMediaStream will not help here, trying anyway.
</code>
</pre>
<p>or simply refusing to continue. Sure, in my case I should simply
ignore the filename and inform the user what's going on, but
sometimes you can't be so gentle.</p>
<p><a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/satyr/">satyr</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/pykde/">pykde</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/python/">python</a>
<a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/phonon/">phonon</a></p>
<hr />
<p>[1] blah.</p>
<p>[2] but then it doesn't make much sense now since Phil wants to
get rid of QString (for several reasons, which might most possibly
include this one).</p>
qobject-dbus-fixedhttp://grulicueva.homelinux.net/~mdione/glob//posts/qobject-dbus-fixed/2009-08-14T09:56:41Z2009-08-14T09:56:41Z
<p>I seem to have fixed the bug I mentioned in <a href=
"http://grulicueva.homelinux.net/~mdione/glob/posts/qobject-dbus/">my
last post</a>. This is what I had:</p>
<pre>
<code>class MetaPlayer (type (QObject), type (dbus.service.Object)):
"""Dummy metaclass that allows us to inherit from both QObject and
d.s.Object"""
pass
class Player (QObject, dbus.service.Object):
__metaclass__= MetaPlayer
[...]
</code>
</pre>
<p>Notice that <code>MetaPlayer</code> doesn't have a explicit
<code>__init__()</code> method; one would spect that Python would
take are of that. Here's the fixing code:</p>
<pre>
<code>MetaQObject= type (QObject)
MetaObject= type (dbus.service.Object)
class MetaPlayer (MetaQObject, MetaObject):
"""Dummy metaclass that allows us to inherit from both QObject and d.s.Object"""
def __init__(cls, name, bases, dct):
MetaObject.__init__ (cls, name, bases, dct)
MetaQObject.__init__ (cls, name, bases, dct)
</code>
</pre>
<p>I really don't understand why I have to be so explicit. Maybe
it's because the metaclass for <code>dbus.service.Object</code>,
<code>dbus.service.InterfaceType</code>, inherits from the
<code>type</code> type[1]; this type is a new style class[2], but
doesn't inherits from <code>object</code>. Thus, I think, the
inherited <code>__init__()</code> methods are not called
automatically.</p>
<p>In any case, now I can mix <code>QObject</code> and
<code>dbus.service.Object</code>, and it works fine. For instance,
this call works:</p>
<pre>
<code>$ qdbus org.kde.satyr /player quit
</code>
</pre>
<p><a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/dbus/">dbus</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/python/">python</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/pykde/">pykde</a></p>
<hr />
<p>[1] the <code>type</code> type is of type <code>type</code>!
here:</p>
<pre>
<code>In [1]: type (type)
Out[1]: <type 'type'>
</code>
</pre>
<p>[2] its type is not <code>instance</code> but <code>type</code>,
as mentioned above.</p>
qobject-dbushttp://grulicueva.homelinux.net/~mdione/glob//posts/qobject-dbus/2009-08-13T21:56:02Z2009-08-13T21:56:02Z
<p>In <a href=
"http://grulicueva.homelinux.net/~mdione/glob/posts/pykde-dbus-server/">
my last post</a> I said «The next step is to make my
<code>Player</code> class to export its methods via
<code>DBus</code> and that's it!». Well, tell you what: is not that
easy. If you try to inherit from <code>QObject</code> and
<code>dbus.service.Object</code> you get this error:</p>
<pre>
<code>In [3]: class Klass (QtCore.QObject, dbus.service.Object): pass
TypeError: Error when calling the metaclass bases
metaclass conflict: the metaclass of a derived class must be a
(non-strict) subclass of the metaclasses of all its bases
</code>
</pre>
<p>This occurs when both ancestors have their own metaclasses.
Unluckily <code>Python</code> doesn't resolve it for you. The
answer is to create a intermediate metaclass which inherits from
both metaclasses (which we can obtain with <code>type()</code>) and
make it the metaclass of our class. In code:</p>
<pre>
<code>class MetaPlayer (type (QObject), type (dbus.service.Object)):
"""Dummy metaclass that allows us to inherit from both QObject and
d.s.Object"""
pass
class Player (QObject, dbus.service.Object):
__metaclass__= MetaPlayer
[...]
</code>
</pre>
<p>Is that it now? Can I go and do my code? Unfortunately no. See
this:</p>
<pre>
<code>qdbus org.kde.satyr
/
/player
Cannot introspect object /player at org.kde.satyr:
org.freedesktop.DBus.Python.KeyError (Traceback (most recent call last):
File "/usr/lib/pymodules/python2.5/dbus/service.py", line 702, in _message_cb
retval = candidate_method(self, *args, **keywords)
File "/usr/lib/pymodules/python2.5/dbus/service.py", line 759, in Introspect
interfaces = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__]
KeyError: '__main__.Player'
)
</code>
</pre>
<p>This is the class <code>dbus.service.Object</code> complaining
something else. It's getting late here and I'm tired, so I'll
continue tomorrow.</p>
<p><a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/dbus/">dbus</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/python/">python</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/pykde/">pykde</a>
<a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/satyr/">satyr</a></p>
pykde-dbus-serverhttp://grulicueva.homelinux.net/~mdione/glob//posts/pykde-dbus-server/2009-08-13T21:56:02Z2009-08-11T00:29:31Z
<p>Continuing with the development of <code>Satyr</code>, which
doesn't have any GUI. I thought it would be faster to make a
<code>DBus</code> interface that a good GUI, not to mention more
interesting.</p>
<p>The code snippet of today is this:</p>
<pre>
<code>class DBusServer (dbus.service.Object):
def __init__ (self):
bus= dbus.SessionBus ()
bus_name= dbus.service.BusName ('org.kde.satyr.dbus.test', bus=bus)
dbus.service.Object.__init__ (self, bus_name, "/server")
@dbus.service.method('org.kde.satyr.dbus.test', in_signature='', out_signature='')
def bing (self):
print "bing!"
@dbus.service.method('org.kde.satyr.dbus.test', in_signature='', out_signature='')
def quit (self):
app.quit ()
dbus.mainloop.qt.DBusQtMainLoop (set_as_default=True)
dbs= DBusServer ()
sys.exit (app.exec_ ())
</code>
</pre>
<p>This simply defines a class which registers itself with the
session bus under the name <code>org.kde.satyr.dbus.test</code>,
exporting itself under the path <code>/server</code> and then
defining a method that goes <strong>bing!</strong> :) and another
one that quits the app. Note the decorator for the methods.</p>
<p>You might notice the <code>dbus.mainloop.qt.DBusQtMainLoop
(set_as_default=True)</code> call. This is needed because both Qt
and DBus in asyncronous mode (which is the one we're using and the
only one that works under Qt or Gtk, AFAIK) both have their own
event loops, and that makes some kind of magic that let both loops
coexist without blocking the other. This must be called before
connecting to the bus; otherwise, you get this error:</p>
<pre>
<code>RuntimeError: To make asynchronous calls, receive signals or export objects,
D-Bus connections must be attached to a main loop by passing mainloop=...
to the constructor or calling dbus.set_default_main_loop(...)
</code>
</pre>
<p>So, let's test the beast. We run the script in one terminal and
in the other:</p>
<pre>
<code>mdione@mustang:~/src/projects/satyr/live$ qdbus | grep satyr
org.kde.satyr.py-10154
org.kde.satyr.dbus.test
mdione@mustang:~/src/projects/satyr/live$ qdbus org.kde.satyr.dbus.test
/
/server
mdione@mustang:~/src/projects/satyr/live$ qdbus org.kde.satyr.dbus.test /server
method void org.kde.satyr.dbus.test.bing()
method QString org.freedesktop.DBus.Introspectable.Introspect()
mdione@mustang:~/src/projects/satyr/live$ qdbus org.kde.satyr.dbus.test /server org.kde.satyr.dbus.test.bing
mdione@mustang:~/src/projects/satyr/live$ qdbus org.kde.satyr.dbus.test /server org.kde.satyr.dbus.test.quit
</code>
</pre>
<p>And in the other console:</p>
<pre>
<code>mdione@mustang:~/src/projects/satyr/live$ python dbus_test.py
bing!
</code>
</pre>
<p>It goes bing!... and then finishes. The next step is to make my
<code>Player</code> class to export its methods via
<code>DBus</code> and that's it!. More info in the <a href=
"http://dbus.freedesktop.org/doc/dbus-python/doc/tutorial.html">Python
DBus tutorial</a>.</p>
<p><a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/dbus/">dbus</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/python/">python</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/pykde/">pykde</a>
<a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/satyr/">satyr</a></p>
pycon.arhttp://grulicueva.homelinux.net/~mdione/glob//posts/pycon.ar/2009-08-10T13:52:16Z2009-08-10T13:52:16Z
<p>Desde hace ya casi un año que se vienen organizando (me refiero
a todo el laburo detrás de lo que es la organización del evento)
<a href="http://ar.pycom.org/">PyCon.ar</a>. Es ahorita nomás, en
el finde del 4 y 5 de Setiembre. Y ¡oh qué lástima!, justo por esa
semana voy a estar temporariamente en Argentina en mi camino entre
Nederland y France (porque <strong>obivamente</strong> queda de
camino). Así que nos vemos allá!</p>
<p><a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/pyar/">pyar</a></p>
a-media-player-written-in-pykdehttp://grulicueva.homelinux.net/~mdione/glob//posts/a-media-player-written-in-pykde/2009-08-09T21:40:29Z2009-08-09T21:40:29Z
<p>Since a long ago I'm looking for a media player for listening my
music. In that aspect I'm really exigent. Is not that I need lots
of plugins and eye candy, no, I just need a media player that fits
my way to listen music.</p>
<p>How do I listen to music? All day long, for starters. I have a
collection of <code>.ogg</code> files, which I normally listen in
random mode. From time to time I want to listen certain song, so I
either queue it or simply stop the current one and start the one I
want. Sometimes I enqueue several songs, which might not be related
between them (maybe only in my head they are).</p>
<p>I've been using <code>Amarok</code>, I really like its random
albums feature; that is, I listen to a whole album, and when it
finishes, another album is picked at random and I listen to all its
songs. The last feature, a really important one: My collection is
my playlist and viceversa. I don't build playlists; if I want to
listen to certain songs I just queue them. One feature I like also
is a tag editor and the posibility to rearrange the songs based on
its tags (with support for albums with songs from various authors,
like OST's). Last but no least, reacting to new files in the
collection is also well regarded.</p>
<p>I used to use <code>xmms</code>. I still think it's a very good
player for me, but it lacks utf support and doesn't react when I
add songs to the collection. Then I used <code>Amarok</code>,
<code>Juk</code>, <code>QuodLibet</code>, <code>Audacious</code> (I
was using it up to today) and probably a couple more. None of them
support all the features, so today, completely tired of this
situation, I started writing my own. I called it
<code>Satyr</code>. Another reason to do it is to play a little
more with <code>PyKDE</code>. Talking about <code>Python</code> and
<code>KDE</code>, I know the existence of <code>minirok</code>, but
it uses <code>GStreamer</code>, and I wanted to play with
<code>Phonon</code>.</p>
<p>So, what's different in this media player? If you think about
it, if you have a CD (vinyl, cassettes maybe?) collection in your
home, what you have is exactly that: a collection of Albums. Most
media players manage Songs, grouping them in Albums and by Author
too. Notably Amarok used to manage Albums with several artists
(what's called a 'Various artists' Album), but since Amarok 2 it
doesn't do it anymore, nor the queuing works. So the basic idea is
exactly that: you have a Collection of Albums, most of them with
songs from the same Author (and sometimes Featuring some other
Authors[1]), but sometimes with Songs from different Authors. Of
course I will try to implement all the features I mentioned
above.</p>
<p>Ok, enough introduction. This post was aimed to show some code,
and that's what I'm going to do now. This afternoon I was testing
the Phonon Python bindings, trying to make a script to play a
simple file. This snippet works:</p>
<pre>
<code># qt/kde related
from PyKDE4.kdecore import KCmdLineArgs, KAboutData, i18n, ki18n
from PyKDE4.kdeui import KApplication
# from PyKDE4.phonon import Phonon
from PyQt4.phonon import Phonon
from PyQt4.QtCore import SIGNAL
media= Phonon.MediaObject ()
ao= Phonon.AudioOutput (Phonon.MusicCategory, app)
print ao.outputDevice ().name ()
Phonon.createPath (media, ao)
media.setCurrentSource (Phonon.MediaSource ("/home/mdione/test.ogg")
media.play ()
app.connect (media, SIGNAL("finished ()"), app.quit)
app.exec_ ()
</code>
</pre>
<p>Of course, this must be preceded by the bureaucratic creation of
a <code>KApplication</code>, but it basically plays an ogg file and
quits. You just have to define a <code>MediaObject</code> as the
source, an <code>AudioOutput</code> as the sink, and then you
<code>createPath</code> between them. As you can see, with Phonon
you don't even have to worry about where the output will be going:
that is defined by the system/user configuration. You only have to
declare that your <code>AudioOutput</code> is going to play Music
(the second actual line of code).</p>
<p>There are a couple of peculiarities with the <code>Python</code>
bindings. First of all, <code>Phonon</code> comes both with Qt and
separately. The separate one has a binding in the
<code>PyKDE4</code> package, but it seems that it doesn't work very
well, so I used the <code>PyQt</code> binding. For that, I had to
install the <code>python-pyqt4-phonon</code> package. Second, the
bindings don't support to call <code>setCurrentSource()</code> with
a string; you have to wrap it in a <code>MediaSource</code>. The
original API supports it. Third, it seems that
<code>Phonon.createPlayer()</code> is not supported by the bindings
either, so I had to build the <code>AudioOutput</code> by hand. I
don't care, it's just a couple lines more.</p>
<p>This code also shows the name of the selected
<code>OutputDevice</code>. I my machine it shows <code>HDA Intel
(STAC92xx Analog)</code>.</p>
<p>In the following days I'll be posting more info about what comes
out of this project. I will only reveal that right now the code has
classes called Player, PlayList and Collection. It can scan a
Collection from a path given in the command line and play all the
files found there. Soon more features will come.</p>
<p><a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/python/">python</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/pykde/">pykde</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/satyr/">satyr</a></p>
<hr />
<p>[1] I'm not planing to do anything about it... yet.</p>
geoiphttp://grulicueva.homelinux.net/~mdione/glob//posts/geoip/2009-07-03T13:29:59Z2009-07-03T13:29:59Z
<p>GeoIP es fantástico: te permite saber de qué país, provincia y
muchas veces hasta qué ciudad es un IP dado. Esto sirve por ejemplo
para saber desde dónde se están conectando a tu server web, hacer
estadísticas inútiles al respecto y alardear de ello; o para
redirigir tráfico a servers más locales, como Debian está haciendo
con su servicio <code>security.debian.org</code>. Pero, como toda
tecnología, puede ser abusada, y volverse más contraproducente que
útil.</p>
<p>El domingo que viene va a hacer un mes que estoy en Nederland
(Holanda, que le dicen, aunque el nombre oficial es 'Países
Bajos'). Hace un mes que, a pesar que mi(s) browser(s) dicen
explícitamente que me gusta el contenido en inglés o, de no ser
posible, en castellano, me están entragando sus hermosas páginas en
un idioma de cual sólo sé decir 'muchas gracias', 'una cerveza',
'jamón', 'queso' y capaz alguna que otra cosa, pero que en general
me es tan comprensible como el chino mandarín (del cual sé decir
'cambei' o 'campei' cuando uno hace un brindis). El idioma en
cuestión es, obviamente, el Dutch ('holandés', creo, no es el
témino correcto, y 'paísbajense' suena muy mal).</p>
<p>Google, las compañías de vuelos baratos y toda una ristra de
otros sitios menores, que ahora se me escapan de la memoria, se me
escapan de la compresión. Algunos tiene la decencia de tener una
banderita con un link a la versión en inglés del sitio, pero por
ejemplo a Google le tengo que decir en mi profile (y si no tuviera,
me sería no imposible pero sí muy molesto) cómo lo quiero.</p>
<p>En fin, una vez más, la tecnología al servicio del hombre
(not).</p>
<hr />
<p>PD: Era cuestión de tiempo para que inaugurara el tag
'rants'.</p>
<p><a href="http://grulicueva.homelinux.net/~mdione/glob//./tags/rants/">rants</a></p>