<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Lukáš Lalinský &#187; Programming</title>
	<atom:link href="http://oxygene.sk/lukas/category/programming/feed/" rel="self" type="application/rss+xml" />
	<link>http://oxygene.sk/lukas</link>
	<description>Random notes and stuff</description>
	<lastBuildDate>Thu, 02 Feb 2012 20:17:03 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Chromaprint plug-in for GStreamer</title>
		<link>http://oxygene.sk/lukas/2011/01/chromaprint-plug-in-for-gstreamer/</link>
		<comments>http://oxygene.sk/lukas/2011/01/chromaprint-plug-in-for-gstreamer/#comments</comments>
		<pubDate>Sun, 02 Jan 2011 20:11:54 +0000</pubDate>
		<dc:creator>Lukáš Lalinský</dc:creator>
				<category><![CDATA[Acoustid]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[acoustid]]></category>
		<category><![CDATA[c++]]></category>
		<category><![CDATA[chromaprint]]></category>
		<category><![CDATA[gstreamer]]></category>
		<category><![CDATA[python]]></category>

		<guid isPermaLink="false">http://oxygene.sk/lukas/?p=972</guid>
		<description><![CDATA[It&#8217;s been a long time since I learned some new framework (or even wrote some real code), so I decided to write a GStreamer plug-in that will wrap libchromaprint and make it very easy to generate Chromaprint fingerprints in GStreamer &#8230; <a href="http://oxygene.sk/lukas/2011/01/chromaprint-plug-in-for-gstreamer/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>It&#8217;s been a long time since I learned some new framework (or even wrote some real code), so I decided to write a <a href="http://www.gstreamer.net/">GStreamer</a> plug-in that will wrap <a href="http://wiki.acoustid.org/wiki/Chromaprint">libchromaprint</a> and make it very easy to generate Chromaprint fingerprints in GStreamer applications. This was inspired by a similar plug-in that <a href="https://github.com/mderezynski">Milosz Derezynski</a> wrote for <a href="http://code.google.com/p/musicip-libofa/">MusicDNS/libofa</a> (the plug-in is now integrated in the official GStreamer distribution). Using the code from <code>gst-ofa</code> and <code>gst-template</code>, it turned out to be pretty easy. After a couple of hours, I was able to run commands like this and get valid fingerprints:</p>
<pre>
$ gst-launch-0.10 -t filesrc location=/path/to/audio/file ! decodebin ! audioconvert ! chromaprint duration=60 ! fakesink sync=0 | grep 'chromaprint fingerprint'
</pre>
<p>I also wrote a simple Python script that uses the GStreamer element to generate fingeprints and look them up using the Acoustid web service. It&#8217;s nice to see how little code is necessary to have a fully working fingerprinting application.</p>
<p>The only disadvantage is that it&#8217;s too slow. :) I had some GStreamer code in Picard for decoding audio files, but I eventually removed it because it was significantly slower compared to the other decoding options (FFmpeg and DirectX). It seems that the situation is still the same, because even compared to calling the <code>ffmpeg</code> binary in a subprocess (which I used in <code><a href="http://bazaar.launchpad.net/~luks/%2Bjunk/acoustid-tools/annotate/head:/audiodecoder.py">acoustid-tools</a></code>), GStreamer is about three times slower. It&#8217;s not too bad for applications that rarely use the functionality, but I wouldn&#8217;t use it in a tagger that needs to identify many files as fast as possible.</p>
<p>Anyway, the project is on <a href="https://launchpad.net/gst-chromaprint">Launchpad</a>, where you can download the <a href="https://code.launchpad.net/~luks/gst-chromaprint/trunk">source code using bzr</a> or a <a href="http://launchpad.net/gst-chromaprint/trunk/0.1/+download/gst-chromaprint-0.1.0.tar.gz">tarball</a>. It depends on the development version of libchromaprint.</p>
]]></content:encoded>
			<wfw:commentRss>http://oxygene.sk/lukas/2011/01/chromaprint-plug-in-for-gstreamer/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Acoustid moved to a new server</title>
		<link>http://oxygene.sk/lukas/2010/12/acoustid-moved-to-a-new-server/</link>
		<comments>http://oxygene.sk/lukas/2010/12/acoustid-moved-to-a-new-server/#comments</comments>
		<pubDate>Thu, 16 Dec 2010 19:15:24 +0000</pubDate>
		<dc:creator>Lukáš Lalinský</dc:creator>
				<category><![CDATA[Acoustid]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[acoustid]]></category>
		<category><![CDATA[chromaprint]]></category>
		<category><![CDATA[fingerprinting]]></category>

		<guid isPermaLink="false">http://oxygene.sk/lukas/?p=940</guid>
		<description><![CDATA[Since I announced the Acoustid project, I got over 1.1 million fingerprint submissions (mostly from MusicBrainz editors), covering about 580 thousand unique MusicBrainz track IDs. In the background I was running a process that imported the raw submissions and merged &#8230; <a href="http://oxygene.sk/lukas/2010/12/acoustid-moved-to-a-new-server/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Since I announced the Acoustid project, I got over 1.1 million fingerprint submissions (mostly from <a href="http://acoustid.org/stats">MusicBrainz editors</a>), covering about 580 thousand unique MusicBrainz track IDs. In the background I was running a process that imported the raw submissions and merged similar tracks. All this was done on a virtual machine with 1GB of RAM. It wasn&#8217;t very fast, but I was surprised it was even able to handle such amount of data. At some point I also enabled the lookup service, but I didn&#8217;t want to announce it, because it took too long to do fingerprint searches on the server.</p>
<p>In the past few days I&#8217;ve been setting up a new server from <a href="http://www.hetzner.de/en/">Hetzner</a>. It&#8217;s still not powerful enough for hosting the &#8220;final&#8221; service, but it should be fine to allow testing of the web service. With the new hosting, I&#8217;m also able to make the data dumps publicly available for download. It will take me some time to have an easy way to import/export them, but exporting a simple CSV file with all the submissions I got should be simple enough.</p>
<p>Finally, I hacked up <a href="https://code.launchpad.net/~luks/+junk/acoustid-tools">some scripts in Python</a> to demonstrate how to use libchromaprint and Acoustid to do audio file lookups. For now it requires the latest version of <a href="https://code.launchpad.net/~luks/chromaprint/trunk">libchromaprint from bzr</a>, but I&#8217;m planning to make a new release soon.</p>
<p>I must say that I&#8217;m really happy about the project. It started as a very simple experiment, but it&#8217;s becoming more and more realistic that it can end up being useful. I was also very surprised how much interest is there. I know about some people who are experimenting with chromaprint, I&#8217;ve been contacted by a few small companies that are interested in the project (way too early for that!), somebody even added it to <a href="http://en.wikipedia.org/wiki/Acoustic_fingerprint">Wikipedia</a> next to the big commercial fingerprinting systems. :)</p>
]]></content:encoded>
			<wfw:commentRss>http://oxygene.sk/lukas/2010/12/acoustid-moved-to-a-new-server/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Binary fingerprint compression</title>
		<link>http://oxygene.sk/lukas/2010/11/binary-fingerprint-compression/</link>
		<comments>http://oxygene.sk/lukas/2010/11/binary-fingerprint-compression/#comments</comments>
		<pubDate>Sun, 21 Nov 2010 23:15:11 +0000</pubDate>
		<dc:creator>Lukáš Lalinský</dc:creator>
				<category><![CDATA[Acoustid]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[acoustid]]></category>
		<category><![CDATA[chromaprint]]></category>
		<category><![CDATA[compression]]></category>
		<category><![CDATA[fingerpriting]]></category>

		<guid isPermaLink="false">http://oxygene.sk/lukas/?p=884</guid>
		<description><![CDATA[While working on the Acoustid web service, I had a hard time deciding how to send fingerprints to the server. The fingerprints are vectors of fairly large 32-bit numbers. Sending the numbers in binary (which would be ideal) is not &#8230; <a href="http://oxygene.sk/lukas/2010/11/binary-fingerprint-compression/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>While working on the <a href="http://wiki.acoustid.org/wiki/Web_Service">Acoustid web service</a>, I had a hard time deciding how to send fingerprints to the server. The fingerprints are vectors of fairly large 32-bit numbers. Sending the numbers in binary (which would be ideal) is not easy, because almost all web standards expect textual data, if not plain ASCII. The usual trick is to <a href="http://en.wikipedia.org/wiki/Base64">base64</a>-encode binary data, but that increases the size by 33 %, which wasn&#8217;t acceptable for me. So I came up with the idea to compress the data using a special-purpose algorithm and then base64-encode the compressed data, and ideally also compress the base64-encoded data using <a href="http://en.wikipedia.org/wiki/Gzip#Other_uses">GZip</a>. The double-compression might seem weird, but it allows me to use only standard web tools and the resulting size is still smaller than using binary encoding.</p>
<p>So the problem was to find an appropriate compression algorithm for the fingerprints. The usual integer compression algorithms (Elias codes, Golomb coding, Rice coding) work well for small numbers. The most common use case for these algorithms are posting lists in an inverted index, where the document IDs can be sorted and simple <a href="http://en.wikipedia.org/wiki/Delta_encoding">delta encoding</a> then produces small numbers that can be nicely compressed. The problem is that I had very large numbers and I couldn&#8217;t sort them anyway. Using subtraction to calculate deltas wouldn&#8217;t help me much, because the resulting numbers would be still too high. After some failed experiments with the standard approaches, I had one of those &#8220;light bulb moments&#8221;: it occurred to me that even though the fingerprint numbers are high, they change slowly in terms in bit changes. If I use XOR instead of subtraction for the delta encoding, I will still get high numbers, but they will usually not have many bits set. What if I just take the bits that are set and encode those as a sequence of numbers between 1 and 32. It turns out that this works pretty well. Let&#8217;s say we have a tiny fingerprint with four 32-bit numbers:</p>
<pre>0x7E65BC44 | 0x7E25B444 | 0x7E259414 | 0x6625D424</pre>
<p>After applying the XOR encoding, we will get the following results:</p>
<pre>0x7E65BC44 | 0x400800 | 0x2050 | 0x18004030</pre>
<p>The next step is to extract individual bits from these numbers:</p>
<pre>3 7 11 12 13 14 16 17 19 22 23 26 27 28 29 30 31 | 12 23 | 5 7 14 | 5 6 15 28 29</pre>
<p>This doesn&#8217;t seem like we achieved much, but if you look closer, you will notice that the numbers both small and increasing. Having large and randomly ordered numbers was the main reason for not being able to use traditional integer compression techniques. So, let&#8217;s delta-encode the numbers:</p>
<pre>3 4 4 1 1 1 2 1 2 3 1 3 1 1 1 1 1 | 12 11 | 5 2 7 | 5 1 9 13 1</pre>
<p>The result is a lot of very small numbers. That&#8217;s exactly the right situation variable length integer encoding. I decided to use the <a href="http://oai.cwi.nl/oai/asset/15564/15564B.pdf">PFOR</a>-like algorithm for this I&#8217;d still like to re-evaluate other variable length encodings, but PFOR was very simple to implement and worked better than any other alternative I&#8217;d tried. The basic idea is to use constant <em>n</em>-bit for encoding all the numbers, with an &#8220;exception&#8221; block after the main block for numbers greater or equal to <em>2<sup>n</sup>-1</em>. My version is different in that instead of storing the offset to the exception in the main block, I decided to always put the maximum possible value there. The current implementation in Chromaprint uses 3 bits, so the main block would contain these numbers, each of them encoded in 3 bits:</p>
<pre>3 4 4 1 1 1 2 1 2 3 1 3 1 1 1 1 1 0 7 7 0 5 2 7 0 5 1 7 7 1 0</pre>
<p>And the exception block would only have numbers that were greater or equal to 7, with the 7 subtracted from the to fit in 5 bits each:</p>
<pre>5 4 0 2 6</pre>
<p>In order to store this we need 31 * 3 + 5 * 5 = 118 bits, which is already a little less than the original binary encoding with 4 * 32 = 128 bits and it only gets better for larger fingerprints. In many cases, increasing the size of elements in main block to 4-bits also helps, so I&#8217;m planning to dynamically determine the ideal size and store in the fingerprint.</p>
<p>Here is a few examples of compression ratios on 120 seconds long fingerprints:</p>
<table>
<tr>
<th>Binary</th>
<th>PFOR 3-bits</th>
<th>PFOR 4-bits</th>
<th>Binary + zlib</th>
<th>PFOR 4-bits + base64 + zlib</th>
</tr>
<tr>
<td>3792</td>
<td>2604</td>
<td>2634</td>
<td>3465</td>
<td>2503</td>
</tr>
<tr>
<td>3792</td>
<td>2193</td>
<td>2160</td>
<td>2809</td>
<td>2072</td>
</tr>
<tr>
<td>3792</td>
<td>2430</td>
<td>2407</td>
<td>3409</td>
<td>2287</td>
</tr>
<tr>
<td>3792</td>
<td>2374</td>
<td>2343</td>
<td>3133</td>
<td>2221</td>
</tr>
</td>
</table>
<p>I have implemented this algorithm first in <a href="http://bazaar.launchpad.net/~luks/chromaprint/trunk/annotate/head:/src/fingerprint_compressor.cpp">C++</a> and then also ported to <a href="http://bazaar.launchpad.net/~luks/acoustid-server/trunk/annotate/head:/tools/src/main/java/org/acoustid/util/FingerprintCompressor.java">Java</a> to use it on the server side.</p>
]]></content:encoded>
			<wfw:commentRss>http://oxygene.sk/lukas/2010/11/binary-fingerprint-compression/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Acoustid Fingerprinter (BETA)</title>
		<link>http://oxygene.sk/lukas/2010/10/acoustid-fingerprinter-beta/</link>
		<comments>http://oxygene.sk/lukas/2010/10/acoustid-fingerprinter-beta/#comments</comments>
		<pubDate>Sat, 23 Oct 2010 22:10:45 +0000</pubDate>
		<dc:creator>Lukáš Lalinský</dc:creator>
				<category><![CDATA[Acoustid]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[acoustid]]></category>
		<category><![CDATA[audio]]></category>
		<category><![CDATA[fingerprinting]]></category>
		<category><![CDATA[qt]]></category>

		<guid isPermaLink="false">http://oxygene.sk/lukas/?p=789</guid>
		<description><![CDATA[Since my last Acoustid-related post, I was working on a GUI application for submitting fingerprints. It&#8217;s far from finished, but I think it has reached the state where it can do something useful without breaking on trivial problems. Please who &#8230; <a href="http://oxygene.sk/lukas/2010/10/acoustid-fingerprinter-beta/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p><a href="http://oxygene.sk/lukas/blog/wp-content/uploads/AcoustidFingerprinter1.png"><img class="alignright size-medium wp-image-798" title="Acoustid Fingerprinter" src="http://oxygene.sk/lukas/blog/wp-content/uploads/AcoustidFingerprinter1-264x300.png" alt="" width="264" height="300" /></a>Since my last Acoustid-related post, I was working on a GUI application for submitting fingerprints. It&#8217;s far from finished, but I think it has reached the state where it can do something useful without breaking on trivial problems. Please who used the old <a href="http://www.flickr.com/photos/ario/1277562689/">Last.fm fingerprinter</a> will know where I got the inspiration for the UI. :)</p>
<p>There isn&#8217;t much functionality. You copy&#038;paste your <a href="http://acoustid.org/api-key">Acoustid API key</a>, select which directories to scan and let the application do its job. It will go through the directories, look for audio files that it hasn&#8217;t seen before, read MBID tags, calculate fingerprints and submit them to Acoustid. It remembers which files to ignore on the next run by using a simple text file with the list of processed files.</p>
<p>This is not an official release, so I don&#8217;t have any packages yet (you can download <a href="https://code.launchpad.net/~luks/acoustid-fingerprinter/trunk">the source code</a> using bzr if you are interested), but I&#8217;d love if somebody could give the <a href="http://dl.dropbox.com/u/5215054/fingerprinter-2010-10-23-1.zip">Windows version</a> a try. I&#8217;m sure there will be some bugs, but after fixing them and releasing the fingerprinter app, I can finally ask non-technical people for help with submitting data and focus on the server side again.</p>
]]></content:encoded>
			<wfw:commentRss>http://oxygene.sk/lukas/2010/10/acoustid-fingerprinter-beta/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Minimal Qt/CMake template</title>
		<link>http://oxygene.sk/lukas/2010/10/minimal-qt-cmake-template/</link>
		<comments>http://oxygene.sk/lukas/2010/10/minimal-qt-cmake-template/#comments</comments>
		<pubDate>Sun, 10 Oct 2010 10:31:31 +0000</pubDate>
		<dc:creator>Lukáš Lalinský</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[cmake]]></category>
		<category><![CDATA[qt]]></category>

		<guid isPermaLink="false">http://oxygene.sk/lukas/?p=684</guid>
		<description><![CDATA[I&#8217;m starting working on the GUI submission tool for Acoustid and I wanted to use CMake instead of qmake for building the application. I couldn&#8217;t find anywhere a simple example of what do I have to put into my CMakeLists.txt &#8230; <a href="http://oxygene.sk/lukas/2010/10/minimal-qt-cmake-template/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>I&#8217;m starting working on the GUI submission tool for Acoustid and I wanted to use CMake instead of qmake for building the application. I couldn&#8217;t find anywhere a simple example of what do I have to put into my <code>CMakeLists.txt</code> in order to build a Qt application while correctly handling all moc files, ui files and resources.</p>
<p>Here is a minimal template that can be used for starting a Qt/CMake project from scratch:</p>
<pre>
cmake_minimum_required(VERSION 2.6)

project(myproject)

find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork REQUIRED)
include(${QT_USE_FILE})

set(myproject_HEADERS someclass.h)
set(myproject_SOURCES someclass.cpp main.cpp)
set(myproject_UIS myproject.ui)
set(myproject_RESOURCES myproject.qrc)

qt4_wrap_cpp(myproject_MOC ${myproject_HEADERS})
qt4_wrap_ui(myproject_UIS_H ${myproject_UIS})
qt4_add_resources(myproject_RESOURCES_CPP ${myproject_RESOURCES})

include_directories(${CMAKE_CURRENT_BINARY_DIR})

add_executable(myproject
    ${myproject_SOURCES}
    ${myproject_MOC}
    ${myproject_UIS_H}
    ${myproject_RESOURCES_CPP}
)

target_link_libraries(myproject ${QT_LIBRARIES})
</pre>
]]></content:encoded>
			<wfw:commentRss>http://oxygene.sk/lukas/2010/10/minimal-qt-cmake-template/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Acoustid updates</title>
		<link>http://oxygene.sk/lukas/2010/10/acoustid-updates/</link>
		<comments>http://oxygene.sk/lukas/2010/10/acoustid-updates/#comments</comments>
		<pubDate>Sat, 09 Oct 2010 18:52:37 +0000</pubDate>
		<dc:creator>Lukáš Lalinský</dc:creator>
				<category><![CDATA[Acoustid]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[acoustid]]></category>
		<category><![CDATA[chromaprint]]></category>
		<category><![CDATA[fingerprinting]]></category>

		<guid isPermaLink="false">http://oxygene.sk/lukas/?p=714</guid>
		<description><![CDATA[Some news on the Acoustid project&#8230; New website At first I wanted to do everything on the server side in Java, but as I was delaying it, I realized I better quickly just hack something up in PHP and then &#8230; <a href="http://oxygene.sk/lukas/2010/10/acoustid-updates/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Some news on the Acoustid project&#8230;</p>
<h3>New website</h3>
<p>At first I wanted to do everything on the server side in Java, but as I was delaying it, I realized I better quickly just hack something up in PHP and then maybe write a better solution later. So I spent last weekend working on the website and came up with <a href="http://acoustid.org/">this</a>. Nothing fancy, but the I needed the website only for API key management.</p>
<p>It allows you to log in with either OpenID or your MusicBrainz username and password. Once you are logged in, it will show you your user API key that can be used for submitting data to the database. Additionally, users can register applications and get their application API keys. I&#8217;m thinking about adding some statistics once there is more data, but that&#8217;s probably all the website has to do.</p>
<h3>Web service is now (partially) online</h3>
<p>The Jave web service code has also been changed. I think I&#8217;m finally reaching a point where I&#8217;m happy with the design of both code and database structure. The web service is documented on <a href="http://wiki.acoustid.org/wiki/Web_Service">wiki</a>, but the XML for results with MusicBrainz metadata will probably change. Ideas on how to integrate the MusicBrainz XML schema are <a href="http://wiki.acoustid.org/wiki/Mailing_List">welcome</a>.</p>
<p>Anyway, since I had the website only and I was happy with the submission API, I&#8217;ve decided to put it online as well, so <a href="http://api.acoustid.org/submit"><code>http://api.acoustid.org/submit</code></a> is now accepting submissions as described on the wiki. It is hosted on a cheap VPS for now, but it should be enough for collecting the test data.</p>
<p>Some tweaking based on the collected data will have to be done before the lookup API can be enabled, but I hope it will follow shortly. Data dumps will be released as soon as I figure out a good way to do that (and there data to release of course!).</p>
<h3>Chromaprint API</h3>
<p>A very important part of Chromaprint&#8217;s algorithm is machine-learned configuration. When I asked people to <a href="http://oxygene.sk/lukas/2010/07/introducing-chromaprint/">send me test fingerprints</a> in July, it was using a configuration that was learned on a random selection from my music collection. That wasn&#8217;t very good, so later I downloaded many 30 second audio snippets spanning many music genres from eMusic and other music websites and did some experiments with using this as the training data. It producer a little better results, but I still wanted something better. The problem was that I never had the time to do anything better. So I decided to take the best configuration I had and run another round of tests on real data.</p>
<p>Once I&#8217;ve done this decision, I needed to make a public API that can be used from external applications. The result is <a href="http://bazaar.launchpad.net/~luks/chromaprint/trunk/annotate/head:/src/chromaprint.h">here</a>. It&#8217;s a plain C API that I hope should be possible to use from any programming language that can load a shared library. There is still no official Chromaprint release, but there will be one as soon as I successfully use the API to build the graphical fingerprint submission tool that I was planning to. I don&#8217;t expected the API will change much though.</p>
<h3 id="help-needed">Help needed!</h3>
<p>Now I need to build a large database of fingerprint/MBID data to see how this all works in action. The last round helped me a lot, but I&#8217;ve made a big mistake of not collecting MusicBrainz identifiers along with the fingerprints. This has been fixed and the <code>fpcollect</code> tool from Chromaprint now extracts MB track IDs from files and writes it to the log file. If you are on Linux (or another platform that makes compiling from source code easy), please download and compile Chromaprint and run the fingerprinter on your music collection. On Debian/Ubuntu, something like this should do the trick:</p>
<pre>
sudo apt-get install bzr cmake libfftw3-dev libavcodec-dev libavformat-dev libtag1-dev libboost-dev
bzr branch lp:chromaprint
cd chromaprint
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TOOLS=ON .
make
./tools/fpcollect /path/to/your/music/library >fpcollect.log
</pre>
<p>When you have the log file, you can run a simple Python application that is also available in Chromaprint to submit the data via the Acoustid web service. For this, you will have to first log in to the Acoustid website and <a href="http://acoustid.org/api-key">get an API key</a>. Once you have the key, you can run a command like this to submit the data (replace <em>XXX</em> with your API key):</p>
<pre>
./tools/fpsubmit.py -a XXX fpcollect.log
</pre>
<p>I know the process isn&#8217;t very easy, but I&#8217;m hoping to have a better and properly packaged submission tool soon.</p>
]]></content:encoded>
			<wfw:commentRss>http://oxygene.sk/lukas/2010/10/acoustid-updates/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Cross-compiling with CMake and Autotools</title>
		<link>http://oxygene.sk/lukas/2010/09/cross-compiling-with-cmake-and-autotools/</link>
		<comments>http://oxygene.sk/lukas/2010/09/cross-compiling-with-cmake-and-autotools/#comments</comments>
		<pubDate>Wed, 15 Sep 2010 11:24:09 +0000</pubDate>
		<dc:creator>Lukáš Lalinský</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[Tools]]></category>
		<category><![CDATA[autotools]]></category>
		<category><![CDATA[cmake]]></category>
		<category><![CDATA[cross-compile]]></category>
		<category><![CDATA[mingw]]></category>
		<category><![CDATA[snippet]]></category>
		<category><![CDATA[windows]]></category>

		<guid isPermaLink="false">http://oxygene.sk/lukas/?p=657</guid>
		<description><![CDATA[The last time I needed to build a Windows binary, I found it easier to cross-compile it from Linux than to setup a development environment on a freshly installed Windows machine. The problem is that even though it&#8217;s not hard, &#8230; <a href="http://oxygene.sk/lukas/2010/09/cross-compiling-with-cmake-and-autotools/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p><a href="http://oxygene.sk/lukas/2010/07/introducing-chromaprint/">The last time</a> I needed to build a Windows binary, I found it easier to cross-compile it from Linux than to setup a development environment on a freshly installed Windows machine. The problem is that even though it&#8217;s not hard, you need to remember some obscure options. Today I needed to build a <a href="http://forums.musicbrainz.org/viewtopic.php?pid=11111#p11111">new version of ISRCsubmit</a> and I had to search for the recipe again. So, let&#8217;s document it somewhere I can find it in the future&#8230;</p>
<p>First, you need the <code>mingw32</code> package, which contains the compiler toolkit plus all the required WinAPI libraries. For cross-compiling CMake projects, you also need a file with paths to the MinGW toolkit:</p>
<pre>
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_C_COMPILER i586-mingw32msvc-gcc)
set(CMAKE_CXX_COMPILER i586-mingw32msvc-g++)
set(CMAKE_FIND_ROOT_PATH /usr/i586-mingw32msvc /home/lukas/projects/win32build)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
</pre>
<p>I have a special directory for installing compiled binaries, which makes it easier to use previously compiled libraries, so that&#8217;s included in the file. To compile a Windows version of <a href="http://musicbrainz.org/doc/libmusicbrainz">libmusicbrainz</a>, which uses CMake, you can use a serie of command like this:</p>
<pre>
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=/home/lukas/projects/mingw.cmake -DCMAKE_INSTALL_PREFIX=/home/lukas/projects/win32build/
make
make install
</pre>
<p>To build libmusicbrainz I also needed <a href="http://www.webdav.org/neon/">neon</a>, which uses autotools. Fortunately, cross-compiling autotools projects is usually also quite simple, but neon doesn&#8217;t support MinGW out of the box, so <a href="https://build.opensuse.org/package/view_file?file=neon-0.29.1-mingw.patch&#038;package=mingw32-libneon&#038;project=windows:mingw:win32&#038;srcmd5=eb762d7b7894e4ebd1cda4f69a6b5c60">one patch</a> is necessary. After applying the patch, it can be compiled like this:</p>
<pre>
./configure --host=i586-mingw32msvc --disable-debug --disable-webdav --prefix=/home/lukas/projects/win32build/
make
make install
</pre>
]]></content:encoded>
			<wfw:commentRss>http://oxygene.sk/lukas/2010/09/cross-compiling-with-cmake-and-autotools/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Acoustid</title>
		<link>http://oxygene.sk/lukas/2010/08/acoustid/</link>
		<comments>http://oxygene.sk/lukas/2010/08/acoustid/#comments</comments>
		<pubDate>Tue, 31 Aug 2010 05:08:50 +0000</pubDate>
		<dc:creator>Lukáš Lalinský</dc:creator>
				<category><![CDATA[Acoustid]]></category>
		<category><![CDATA[Announce]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[acoustid]]></category>
		<category><![CDATA[audio]]></category>
		<category><![CDATA[chromaprint]]></category>
		<category><![CDATA[fingerprinting]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[MusicBrainz]]></category>
		<category><![CDATA[postgresql]]></category>

		<guid isPermaLink="false">http://oxygene.sk/lukas/?p=625</guid>
		<description><![CDATA[After asking for fingerprint test data in my last post about Chromaprint I received about 270k fingerprints from. This helped me to perform some larger tests on the proof-of-concept lookup server I had implemented using Java and PostgreSQL. I had &#8230; <a href="http://oxygene.sk/lukas/2010/08/acoustid/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>After asking for fingerprint test data in my <a href="http://oxygene.sk/lukas/2010/07/introducing-chromaprint/">last post about Chromaprint</a> I received about 270k fingerprints from. This helped me to perform some larger tests on the proof-of-concept lookup server I had implemented using Java and PostgreSQL. I had to do some technical changes, but the main idea seemed to work pretty well. I wasn&#8217;t sure about this when I was working on Chromaprint, but now I believe that I can realistically run an audio recognition service.</p>
<p>So, let me introduce <a href="https://launchpad.net/acoustid-server">Acoustid</a> (named by <a href="http://chatlogs.musicbrainz.org/musicbrainz/2010/2010-07/2010-07-14.html#T11-27-54-618882">herojoker</a>), the server side component for audio fingerprint lookups. As I mentioned before, it uses PostgreSQL&#8217;s <a href="http://developer.postgresql.org/pgdocs/postgres/gin.html">GIN indexes</a> with the <a href="http://www.postgresql.org/docs/8.4/static/intarray.html">intarray</a> implemented to do the hard work. Java is used as a glue language that provides an web-based service for interacting with the database. I originally considered using C++ for this (using the <a href="http://pocoproject.org/">POCO libraries</a>), but there is significantly more tools in Java for an application like this, so even though I don&#8217;t particularly like Java, it seemed like the best language for this.</p>
<p>Chromaprint fingerprints are basically timed sequences of 32-bit numbers (&#8220;subfingerprints&#8221;). They are extracted from the audio stream, using a technique which is not important here, every 0.1238 seconds and cover about 1.9814 seconds of the following audio data. This means that to represent a minute of audio, you need around 460 of such numbers. For two audio files representing the same song in different file formats, these numbers should be very similar in terms of bit errors. Acoustid&#8217;s job is to take these numbers and find a fingerprint in the database that has the least bit errors compared to the query. Theoretically, this could be used to identify songs based on a few seconds of the original audio, but that would require indexing whole fingerprints, because we don&#8217;t which part of the original fingerprint does the query match. This requires a lot of RAM to perform efficiently, so I decided to collect the data that is necessary to perform searches like this, but not implement the feature for now.</p>
<p>Instead, Acoustid&#8217;s primary task is to identify audio files and for this purpose it can assume that the query is extracted from the beginning of the audio. It indexes only ~15 (currently from 0:25 to 0:40) seconds of audio and uses this index to search for possible matches. Currently it assumes that at least one number from the range matches exactly. This can yield some false positive matches, so the next step is to compare the full query to the full fingerprint. As the fingerprints might not be ideally aligned, it first finds the best alignment and then calculates a score as the number of subfingerprints with less than 2 bit errors. This matching and scoring is done completely on the database server, using the intarray extension and a custom C extension for calculating the score.</p>
<p>This seem to all work fine from technical point of view. What I&#8217;m fighting with is the API for this. How to pass the fingerprint data efficiently, how to authenticate users for submissions, whether to have the concept of users at all. The main problems currently are:</p>
<ol>
<li>The fingerprints are quite large and it would be ideal to send them as binary data. This conflicts with most existing standards for web services. I&#8217;ve implemented a compression algorithm that makes the size of base64-encoded fingerprint similar to the size of an uncompressed binary fingerprint, but I&#8217;d still like to not waste the bandwidth if I can avoid it. I&#8217;ve considered <a href="http://en.wikipedia.org/wiki/Bencode">bencode</a>-formatted requests, but that is probably too non-standard. I&#8217;ve also considered accepting &#8220;Content-Encoding: gzip&#8221; in HTTP requests, which everybody seems to be using only for responses, but it seems usable also for requests.</li>
<li>I&#8217;d like to track who submitted which fingerprints. I don&#8217;t need any private data, but I&#8217;d like to be able to delete all fingerprints from one source if I find out that there is something wrong with the data. Maybe I&#8217;m paranoid, but I&#8217;m afraid that some people might try to submit wrong data in order to reduce the usefulnes of the database.</li>
<li>If I want to track users, where should the user accounts come from. I definitely don&#8217;t want people to have to register and I&#8217;d like the service to be integrated with the MusicBrainz database, so accepting MB usernames/passports would be one option. Another option would be to go use any <a href="http://openid.net/">OpenID</a> account and use something like <a href="http://oauth.net/">OAuth</a> for API authentication. I guess some people would prefer to not use their Google/Yahoo/etc. OpenID accounts, so perhaps having MB as an OpenID provider would help?</li>
<li>There is also the question of how to authenticate the users. If I accept MB username/passwords I could use standard HTTP Digest authentication, but normally that means that applications would have to send most authenticated HTTP requests twice. Only submissions would have to be authenticated and these will have a lot of data in the HTTP body, it would be a waste to send them twice. Other options are OAuth or a custom token-based method.</li>
<li>How to handle metadata? Should it rely only on MusicBrainz identifiers? That would make it less usable for taggers that prefer other metadata databases. Should it allow submissions that are not in MusicBrainz?</li>
</ol>
<p>Regarding licensing, the source code is distributed under the GPLv3 license. I&#8217;ve considered more permissible licenses, but while there isn&#8217;t much code yet, I&#8217;d like to be sure that any improvements stay open source. The service is not yet live, so perhaps it&#8217;s too soon to speak about data licensing, but I meant to use something like <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC BY-SA</a> for the data (including database dumps), but not allow commercial usage of the free service. If there will be a commercial application interested in the service in the future, it should either run their own mirror or help paying the hosting costs so that the service can stay free for free applications.</p>
<p>Anyway, the source code is on <a href="https://code.launchpad.net/acoustid-server">Launchpad</a> and there is a mailing list on <a href="http://groups.google.com/group/acoustid">Google Groups</a>. There is also a <a href="http://acoustid.oxygene.sk/wiki/Main_Page">wiki</a>, which will eventually contain documentation for the project. If you would like to help me with finishing this project or are just interested in its future, please join the mailing list.</p>
]]></content:encoded>
			<wfw:commentRss>http://oxygene.sk/lukas/2010/08/acoustid/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Introducing Chromaprint</title>
		<link>http://oxygene.sk/lukas/2010/07/introducing-chromaprint/</link>
		<comments>http://oxygene.sk/lukas/2010/07/introducing-chromaprint/#comments</comments>
		<pubDate>Sat, 24 Jul 2010 16:36:57 +0000</pubDate>
		<dc:creator>Lukáš Lalinský</dc:creator>
				<category><![CDATA[Acoustid]]></category>
		<category><![CDATA[Announce]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[acoustid]]></category>
		<category><![CDATA[audio]]></category>
		<category><![CDATA[c++]]></category>
		<category><![CDATA[chromaprint]]></category>
		<category><![CDATA[fingerprinting]]></category>
		<category><![CDATA[MusicBrainz]]></category>

		<guid isPermaLink="false">http://oxygene.sk/lukas/?p=529</guid>
		<description><![CDATA[After several months of reading research papers, learning and weekend coding, I&#8217;m very happy to make the half-finished code of my audio fingerprinting library public. :) I&#8217;m doing this mostly for selfish reasons, because it will force me to stop &#8230; <a href="http://oxygene.sk/lukas/2010/07/introducing-chromaprint/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>After several months of reading research papers, learning and weekend coding, I&#8217;m very happy to make the half-finished code of my audio fingerprinting library public. :) I&#8217;m doing this mostly for selfish reasons, because it will force me to stop thinking in &#8220;hacker mode&#8221; and hopefully properly finish it, and I also hope to get some help and feedback from other people. There is nothing for regular users yet though, just for developers or people not afraid of the command line.</p>
<p>It all started in February this year, when when I got my Google Alerts mail for &#8220;musicbrainz picard&#8221; which included a link to <a href="http://portal.acm.org/citation.cfm?id=1399913&#038;CFID=98145194&#038;CFTOKEN=53175875">this paper</a> (&#8220;<em>Waveprint: Efficient wavelet-based audio fingerprinting</em>&#8221; (2007) by Shumeet Baluja and Michele Covell). I&#8217;ve never paid much attention to how audio identification systems are actually implemented, but I found it interesting that a paper published by Google researchers cited <a href="http://musicbrainz.org/doc/PicardTagger">Picard</a>, especially because Picard doesn&#8217;t implement any fingerprinting algorithm, just uses <a href="http://code.google.com/p/musicip-libofa/">libofa</a>. Anyway, I&#8217;ve read the paper and realized that maybe it&#8217;s not that hard to implement such a system. No tough DSP stuff or scary mathematics (of course there is some DSP stuff and mathematics, but mostly basics). The system described in the paper seemed quite straight-forward to implement, so I become curious and decided to give it a try. Later on I realized that it&#8217;s perhaps not the best system and that even the authors published new papers describing different approaches. That, combined with the fact that I was still officially an university student and had free access to all the papers from most organizations like ACM or IEEE, caused that I started reading more and more papers on the topic, learning about the history, how the systems evolved, and so on.</p>
<p>Many ideas were based on a paper by Yan Ke, Derek Hoiem, and Rahul Sukthankar called &#8220;<a href="http://www.cs.cmu.edu/~yke/musicretrieval/"><em>Computer Vision for Music Identification</em></a>&#8221; (2005). In fact, even the <a href="http://blog.last.fm/2010/07/09/fingerprint-api-and-app-updated">Last.fm fingerprinter</a> uses the code published by the authors of this paper. This is where I learned that audio identification is more about machine learning that it is about DSP. Many useful methods for extracting interesting features from audio streams are well-known and the problem is more about how to apply and index them the best way. The basic idea here is to treat audio as a spectral image and index the content of the image. I&#8217;ll explain this in more detail and how Chromaprint uses this in a following post.</p>
<p>Another important paper for me was &#8220;<em><a href="http://dx.doi.org/10.1109/TIFS.2009.2034452">Pairwise Boosted Audio Fingerprint</a></em>&#8221; (2009) by Dalwon Jang, Chang D. Yoo, Sunil Lee, Sungwoong Kim and Ton Kalker (Ton Kalker is a co-author of a historically important paper &#8220;<a href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.16.2893"><em>Robust Audio Hashing for Content Identification</em></a>&#8221; (2001) published by Philips Research), which combined previous experiments of the authors with audio identification based on spectral centroid features and the indexing approach similar to the one suggested by Y. Ke, D. Hoiem and R. Sukthankar. For a long time this was the best solution I had and since it was actually not very hard to implement, the most time I spent on tweaking the configuration to get the best results.</p>
<p>The last major change came after I learned about &#8220;chroma&#8221; features by reading the &#8220;<em><a href="http://dx.doi.org/10.1109/TASL.2007.911552">Efficient Index-Based Audio Matching</a></em>&#8221; (2008) by Frank Kurth and Meinard Müller. I&#8217;ve read more papers about chroma features later, but this was the first and also the most important one for me and some ideas about processing the feature vectors from it are implemented in Chromaprint. Chroma features are typically used for music identification, as opposed to audio file identification, but I tried to use them with the approach I already had implemented and it nicely improved the quality of the fingerprinting function and actually reduced complexity which allowed me to use much larger training data sets.</p>
<p>Anyway, this is more or less how I got to this point. As I mentioned, I&#8217;ll try to describe in more detail how Chromaprint works and where are the exact ideas from in an another post later. The code is not finished yet, but the core ideas are already implemented and tested. The work that has to be done is mostly about cleaning the code, tweaking the configuration, running the learning algorithm on a better training data set (as I used only random selections of my music collection so far) and building some API that can be used by external applications.</p>
<p>The code is written in C++, but I plan the public API to be in plain C. Except for a <a href="http://en.wikipedia.org/wiki/Fast_Fourier_transform">FFT</a> library (either <a href="http://www.fftw.org/">FFTW3</a> or <a href="http://www.ffmpeg.org/">FFmpeg</a>), it has no external dependencies. It&#8217;s released under the LGPL 2.1 license, so there should be no problem integrating it into a commercial application, assuming FFmpeg is used for FFT calculations (using FFTW3 would require the binary to be GPL compatible). The project is hosted on <a href="https://launchpad.net/chromaprint">Launchpad</a> using Bazaar for development. I&#8217;m sorry I didn&#8217;t include the complete development history there, but it&#8217;s just full of junk commits, so you will not miss much. :)</p>
<p>What I&#8217;d really like is to start actually working also on the fingerprint lookup service, for which I need as many fingerprints as possible. I have a proof of concept written in Java, using PostgreSQL and the <a href="http://www.postgresql.org/docs/8.4/static/intarray.html">intarray</a> extension, which allows me to search the fingerprints using <a href="http://www.postgresql.org/docs/8.4/interactive/gin.html">GIN indexes</a>. This works fine on a database with tens of thousands of fingerprints, but I&#8217;m not sure if it will scale to much higher numbers. If you would like to help and you are running Debian/Ubuntu Linux, please run these commands and <a href="mailto:lalinsky@gmail.com">email me</a> the compressed <code>fpcollect.log</code> file:</p>
<pre>
sudo apt-get install bzr cmake libfftw3-dev libavcodec-dev libavformat-dev libtag1-dev libboost-dev
bzr branch lp:chromaprint
cd chromaprint
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TOOLS=ON .
make
./tools/fpcollect /path/to/your/music/library >fpcollect.log
</pre>
<p>I&#8217;m sure the recipe should be easy enough to modify for other Linux distributions. I&#8217;ll try to build a Windows binary in the next days.</p>
<p><em><strong>UPDATE:</strong> I&#8217;ve compiled a <a href="http://dl.dropbox.com/u/5215054/fpcollect-r8.zip">Windows version of fpcollect</a>. If you would like to help, please download it, change the <code>fpcollect.bat</code> file to point to your collection and run it. It should should produce a file called <code>fpcollect.log</code> like the Linux example above.</em></p>
<p>So that&#8217;s all for now. My plans for near future are to clean up the library and build some simple GUI application for collecting fingerprints. Once this is done, I can ask non-programmers to help me build a test database of fingerprints (including MusicBrainz IDs) and work primarily on the server component. There are some things that could be improved on the client library from functional point of view, but I think it&#8217;s good enough for now, so the server part seems more important at the moment.</p>
<p>Btw, I always considered &#8220;Chromaprint&#8221; to be a temporary name for the client library, not meant to be its final name. If you can think of a better name, ideally something that could be used also for the service/server, please let me know!</p>
]]></content:encoded>
			<wfw:commentRss>http://oxygene.sk/lukas/2010/07/introducing-chromaprint/feed/</wfw:commentRss>
		<slash:comments>12</slash:comments>
		</item>
		<item>
		<title>Fun with timestamps</title>
		<link>http://oxygene.sk/lukas/2010/03/fun-with-timestamps/</link>
		<comments>http://oxygene.sk/lukas/2010/03/fun-with-timestamps/#comments</comments>
		<pubDate>Tue, 02 Mar 2010 19:32:56 +0000</pubDate>
		<dc:creator>Lukáš Lalinský</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[fail]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[stackoverflow]]></category>
		<category><![CDATA[timezone]]></category>

		<guid isPermaLink="false">http://oxygene.sk/lukas/?p=342</guid>
		<description><![CDATA[Some programming languages really encourage using UNIX timestamps for working with dates. PHP is a good example of such a language. Functions like date, strtotime, strftime are used all the time. Most people don&#8217;t realize that timestamps in general can&#8217;t &#8230; <a href="http://oxygene.sk/lukas/2010/03/fun-with-timestamps/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Some programming languages really encourage using <a href="http://en.wikipedia.org/wiki/Unix_time">UNIX timestamps</a> for working with dates. PHP is a good example of such a language. Functions like <a href="http://php.net/manual/en/function.date.php"><code>date</code></a>, <a href="http://php.net/manual/en/function.strtotime.php"><code>strtotime</code></a>, <a href="http://php.net/manual/en/function.strftime.php"><code>strftime</code></a> are used all the time. Most people don&#8217;t realize that timestamps in general can&#8217;t really be used for calculations though. The problem is that most countries use <a href="http://en.wikipedia.org/wiki/Daylight_saving_time">daylight saving time</a>, which means that two times a year the local timezone changes. This nicely breaks the assumption that every day has 24 hours. It doesn&#8217;t. Sometimes it has 23 or 25 hours.</p>
<p>This <a href="http://stackoverflow.com/questions/2361222/php-date-function-finding-the-previous-week/2361335#2361335">StackOverflow answer</a> is a nice example of the problem:</p>
<pre>
$mondayStr = "last monday";
if (date('N') !== '1') {  // it's not Monday today
    $mondayStr .= " last week";
}

$monday = strtotime($mondayStr);
echo date('r', $monday);    // Mon, 22 Feb 2010 00:00:00 +1000

$sunday = $monday + 86400 * 7 - 1;
echo date('r', $sunday);    // Sun, 28 Feb 2010 23:59:59 +1000
</pre>
<p>The code seems logical. Get the timestamp of the last Monday, add 60 * 60 * 24 * 7 &#8211; 1 seconds and you have the end of Sunday. Works fine most of the time. Although, if the <code>$monday</code> happens to be <em>Mon, 22 Mar 2010 00:00:00</em>, the date that was supposed to be <em>Sun, 28 Mar 2010  23:59:59</em> will actually be <em>Mon, 29 Mar 2010 00:59:59</em>. Why? Because 28 March 2010 has only 23 hours.</p>
<p>Never use timestamps to do calendar calculations. It&#8217;s hard to get it right. If you really have to, at least use GMT timestamps.</p>
]]></content:encoded>
			<wfw:commentRss>http://oxygene.sk/lukas/2010/03/fun-with-timestamps/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

