overThere.co.ukhttps://overthere.co.uk/2014-09-02T14:43:52+01:00Thoughts. And some software.Convert local path to UNC path in Python2014-09-02T14:43:52+01:002014-09-02T14:43:52+01:00Gary Hughestag:overthere.co.uk,2014-09-02:/2014/09/02/python-local-path-to-unc/
<p>I recently had the need to convert a local path to a <span class="caps">UNC</span> path for an application I was writing. The application monitors a local folder for files and serves them to a client over the network as requested, but for the client to be able to access the file the path had to be a <span class="caps">UNC</span> path.</p>
<p>There didn’t seem to be a built in way to do this in Python so here’s what I ended up with. </p>
<p>I recently had the need to convert a local path to a <span class="caps">UNC</span> path for an application I was writing. The application monitors a local folder for files and serves them to a client over the network as requested, but for the client to be able to access the file the path had to be a <span class="caps">UNC</span> path.</p>
<p>There didn’t seem to be a built in way to do this in Python so here’s what I ended up with. </p>
<p>When the <code>NetworkPaths</code> class is initialised it will build a dictionary of available Windows shares on the current machine with the path as the key and share name as the value. It then creates a list of the share paths ordered by longest first.</p>
<p>When the <code>convert</code> method is given a path it works through the list of stored share paths checking to see if any match the start of the given path. If a match is found it builds the <span class="caps">UNC</span> path using the dictionary lookup to get the share name.</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">NetworkPaths</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="nb">super</span><span class="p">(</span><span class="n">NetworkPaths</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span><span class="o">.</span><span class="fm">__init__</span><span class="p">()</span>
<span class="n">obj_wmi_service</span> <span class="o">=</span> <span class="n">win32com</span><span class="o">.</span><span class="n">client</span><span class="o">.</span><span class="n">Dispatch</span><span class="p">(</span><span class="s1">'WbemScripting.SWbemLocator'</span><span class="p">)</span>
<span class="n">obj_s_wbem_services</span> <span class="o">=</span> <span class="n">obj_wmi_service</span><span class="o">.</span><span class="n">ConnectServer</span><span class="p">(</span><span class="s1">'.'</span><span class="p">,</span> <span class="s1">'root\cimv2'</span><span class="p">)</span>
<span class="n">items</span> <span class="o">=</span> <span class="n">obj_s_wbem_services</span><span class="o">.</span><span class="n">ExecQuery</span><span class="p">(</span><span class="s1">'SELECT * FROM Win32_Share'</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">shares_lookup</span> <span class="o">=</span> <span class="p">{</span><span class="nb">str</span><span class="p">(</span><span class="n">share</span><span class="o">.</span><span class="n">Path</span><span class="p">):</span> <span class="nb">str</span><span class="p">(</span><span class="n">share</span><span class="o">.</span><span class="n">Name</span><span class="p">)</span> <span class="k">for</span> <span class="n">share</span> <span class="ow">in</span> <span class="n">items</span> <span class="k">if</span>
<span class="n">share</span><span class="o">.</span><span class="n">Path</span> <span class="ow">and</span> <span class="ow">not</span> <span class="s1">'$'</span> <span class="ow">in</span> <span class="n">share</span><span class="o">.</span><span class="n">Name</span> <span class="ow">and</span>
<span class="ow">not</span> <span class="n">share</span><span class="o">.</span><span class="n">Name</span> <span class="o">==</span> <span class="s1">'Users'</span><span class="p">}</span>
<span class="bp">self</span><span class="o">.</span><span class="n">shares</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">((</span><span class="n">x</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">shares_lookup</span><span class="o">.</span><span class="n">keys</span><span class="p">()),</span> <span class="n">key</span><span class="o">=</span><span class="nb">len</span><span class="p">,</span> <span class="n">reverse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">convert</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">local_path</span><span class="p">):</span>
<span class="k">if</span> <span class="n">local_path</span><span class="p">[:</span><span class="mi">2</span><span class="p">]</span> <span class="o">==</span> <span class="s1">'</span><span class="se">\\\\</span><span class="s1">'</span><span class="p">:</span>
<span class="c1"># Path is already UNC.</span>
<span class="k">return</span> <span class="n">local_path</span>
<span class="k">for</span> <span class="n">share_path</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">shares</span><span class="p">:</span>
<span class="k">if</span> <span class="n">local_path</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="n">share_path</span><span class="o">.</span><span class="n">lower</span><span class="p">()):</span>
<span class="c1"># We have a match. Return the UNC path.</span>
<span class="k">if</span> <span class="n">local_path</span><span class="p">[</span><span class="nb">len</span><span class="p">(</span><span class="n">share_path</span><span class="p">)]</span> <span class="o">==</span> <span class="s1">'</span><span class="se">\\</span><span class="s1">'</span><span class="p">:</span>
<span class="n">path_end</span> <span class="o">=</span> <span class="n">local_path</span><span class="p">[</span><span class="nb">len</span><span class="p">(</span><span class="n">share_path</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">:]</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">path_end</span> <span class="o">=</span> <span class="n">local_path</span><span class="p">[</span><span class="nb">len</span><span class="p">(</span><span class="n">share_path</span><span class="p">):]</span>
<span class="n">unc_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s1">'</span><span class="se">\\\\</span><span class="si">{0:s}</span><span class="s1">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">getenv</span><span class="p">(</span><span class="s1">'COMPUTERNAME'</span><span class="p">)),</span>
<span class="bp">self</span><span class="o">.</span><span class="n">shares_lookup</span><span class="p">[</span><span class="n">share_path</span><span class="p">],</span>
<span class="n">path_end</span><span class="p">)</span>
<span class="k">return</span> <span class="n">unc_path</span>
<span class="c1"># No match found.</span>
<span class="k">return</span> <span class="kc">None</span>
</code></pre></div>
<p>For example, given the path <code>C:\Users\Gary Hughes\Desktop\Batch Files\Full Backup.bat</code> on my machine:</p>
<div class="highlight"><pre><span></span><code><span class="n">network_paths</span> <span class="o">=</span> <span class="n">NetworkPaths</span><span class="p">()</span>
<span class="nb">print</span> <span class="n">network_paths</span><span class="o">.</span><span class="n">convert</span><span class="p">(</span><span class="sa">r</span><span class="s1">'C:\Users\Gary Hughes\Desktop\Batch Files\Full Backup.bat'</span><span class="p">)</span>
</code></pre></div>
<p>returns:</p>
<div class="highlight"><pre><span></span><code><span class="go">\\IRUSH\Desktop\Batch Files\Full Backup.bat</span>
</code></pre></div>
<p>With the paths sorted by longest first this should ensure the shortest <span class="caps">UNC</span> path is returned.</p>Bing Wallpaper Changer2013-09-01T18:32:23+01:002013-09-01T18:32:23+01:00Gary Hughestag:overthere.co.uk,2013-09-01:/2013/09/01/bing-wallpaper-changer/
<h3>About</h3>
<p>This program will run in the system tray and check for a new daily Bing image on a user defined interval. If the <span class="caps">URL</span> for the image has changed since the last check the new image will be downloaded and applied as the current wallpaper.</p>
<p>There is also the option to run a command after the wallpaper has been applied. This is useful for anyone that uses software that overlays data onto their wallpaper, Such as <a href="http://technet.microsoft.com/en-gb/sysinternals/bb897557.aspx" title="BgInfo">BgInfo</a> from Sysinternals.</p>
<p>The last two week’s worth of images is available on a <code>History</code> tab - just double click a thumbnail to apply a wallpaper. If using this and you want to keep an old wallpaper don’t forget to disable to the automatic updating from the main <code>Settings</code> tab, otherwise the wallpaper will be replaced with the daily wallpaper on the next update check.</p>
<h3>About</h3>
<p>This program will run in the system tray and check for a new daily Bing image on a user defined interval. If the <span class="caps">URL</span> for the image has changed since the last check the new image will be downloaded and applied as the current wallpaper.</p>
<p>There is also the option to run a command after the wallpaper has been applied. This is useful for anyone that uses software that overlays data onto their wallpaper, Such as <a href="http://technet.microsoft.com/en-gb/sysinternals/bb897557.aspx" title="BgInfo">BgInfo</a> from Sysinternals.</p>
<p>The last two week’s worth of images is available on a <code>History</code> tab - just double click a thumbnail to apply a wallpaper. If using this and you want to keep an old wallpaper don’t forget to disable to the automatic updating from the main <code>Settings</code> tab, otherwise the wallpaper will be replaced with the daily wallpaper on the next update check.</p>
<div class="alert alert-info">
<p><strong>New Version</strong>
<p>Since this blog post I’ve posted new versions. Take a look at the <a href="/bing-wallpaper-changer/">Bing Wallpaper Changer</a> page.</p></p>
</div>
<h3>Why?</h3>
<p>I don’t use Bing as a search engine, but I do very much like their daily images. I was excited when I first heard about the official <a href="http://www.bing.com/explore/desktop" title="Bing Desktop">Bing Desktop</a> application, but wasn’t keen on the toolbar that was added to my desktop. I tried to just live with it for a while but it caused problems with a few full screen applications when it refreshed.</p>
<p>After not being able to find a similar program with the features I wanted I decided to write my own.</p>
<h3>Changelog</h3>
<h5>1.3</h5>
<ul>
<li>Added ability to set old Bing wallpapers from the <code>History</code> tab by double clicking the thumbnail.</li>
<li>Reduced <code>History</code> tab range from three weeks to two as duplicates were shown when Bing had featured interactive images on the homepage.</li>
</ul>
<h5>1.2</h5>
<ul>
<li>Added option to system tray menu to directly open settings page.</li>
<li>Wallpaper change is now permanent and should be retained after logoff/shutdown.</li>
<li>Switched to Qt downloading methods instead of Python.</li>
<li>Initial Linux support added in source. Tested on Ubuntu 13.04 - working fine apart from <code>QSystemTrayIcon</code> not being displayed. Need to find how that’s handled in Ubuntu now…</li>
</ul>
<h5>1.1</h5>
<ul>
<li>Added <code>Settings</code> and <code>History</code> tabs to the main window.</li>
<li>Fixed broken refresh when current Bing image is the same as previously downloaded image.</li>
<li><span class="caps">GUI</span> fixes.</li>
</ul>
<h5>1.0</h5>
<ul>
<li>First release with basic functionality.</li>
</ul>Improving PyPDF2 with PDFtk2013-07-22T15:35:12+01:002013-07-22T15:35:12+01:00Gary Hughestag:overthere.co.uk,2013-07-22:/2013/07/22/improving-pypdf2-with-pdftk/
<p><a href="https://github.com/mstamy2/PyPDF2" title="PyPDF2">PyPDF2</a> (forked from <a href="http://pybrary.net/pyPdf/" title="pyPdf">pyPdf</a>) is wonderful. I use it a fair bit in my job, mainly for chopping up PDFs and re-assembling the pages in a different order. It does sometimes have difficulty with non-standard PDFs though that seem fine in other programs. This can be frustrating.</p>
<p>The one that I’ve been battling with today from some PDFs provided by a client was:</p>
<div class="highlight"><pre><span></span><code><span class="n">PyPDF2</span><span class="o">.</span><span class="n">utils</span><span class="o">.</span><span class="n">PdfReadError</span><span class="p">:</span> <span class="n">EOF</span> <span class="n">marker</span> <span class="ow">not</span> <span class="n">found</span>
</code></pre></div>
<p>I managed to find a workaround using <a href="http://www.pdflabs.com/tools/pdftk-the-pdf-toolkit/" title="PDFtk">PDFtk</a> to fix the <span class="caps">PDF</span> in memory at the first sign of any trouble. It works well so far, so in case anyone else is having similar issues I thought I’d write it up.</p>
<p><a href="https://github.com/mstamy2/PyPDF2" title="PyPDF2">PyPDF2</a> (forked from <a href="http://pybrary.net/pyPdf/" title="pyPdf">pyPdf</a>) is wonderful. I use it a fair bit in my job, mainly for chopping up PDFs and re-assembling the pages in a different order. It does sometimes have difficulty with non-standard PDFs though that seem fine in other programs. This can be frustrating.</p>
<p>The one that I’ve been battling with today from some PDFs provided by a client was:</p>
<div class="highlight"><pre><span></span><code><span class="n">PyPDF2</span><span class="o">.</span><span class="n">utils</span><span class="o">.</span><span class="n">PdfReadError</span><span class="p">:</span> <span class="n">EOF</span> <span class="n">marker</span> <span class="ow">not</span> <span class="n">found</span>
</code></pre></div>
<p>I managed to find a workaround using <a href="http://www.pdflabs.com/tools/pdftk-the-pdf-toolkit/" title="PDFtk">PDFtk</a> to fix the <span class="caps">PDF</span> in memory at the first sign of any trouble. It works well so far, so in case anyone else is having similar issues I thought I’d write it up.</p>
<p>So here’s how I was opening <span class="caps">PDF</span> files before.</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">PyPDF2</span> <span class="kn">import</span> <span class="n">PdfFileReader</span>
<span class="kn">from</span> <span class="nn">cStringIO</span> <span class="kn">import</span> <span class="n">StringIO</span>
<span class="n">input_path</span> <span class="o">=</span> <span class="s1">'c:/test_in.pdf'</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">input_path</span><span class="p">,</span> <span class="s1">'rb'</span><span class="p">)</span> <span class="k">as</span> <span class="n">input_file</span><span class="p">:</span>
<span class="n">input_buffer</span> <span class="o">=</span> <span class="n">StringIO</span><span class="p">(</span><span class="n">input_file</span><span class="o">.</span><span class="n">read</span><span class="p">())</span>
<span class="n">input_pdf</span> <span class="o">=</span> <span class="n">PdfFileReader</span><span class="p">(</span><span class="n">input_buffer</span><span class="p">)</span>
</code></pre></div>
<p>At that point you’re free to do whatever it is you want to do with <code>input_pdf</code>. Providing of course that it loaded without issue. I’m loading the file into a <code>StringIO</code> object first for speed; the program this is from does lots of things with the file and <code>StringIO</code> made things much faster.</p>
<p>So to work around the <span class="caps">EOF</span> problem I add a new <code>decompress_pdf</code> function that gets called if there’s a problem parsing the <span class="caps">PDF</span>. It takes the data from the <code>StringIO</code> and sends it to a PDFtk process on <code>stdin</code> that simply runs PDFtk’s <code>uncompress</code> command on the data. The fixed <span class="caps">PDF</span> is read back from <code>stdout</code> and returned as a <code>StringIO</code>, where things will hopefully carry on as if nothing happened.</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">PyPDF2</span> <span class="kn">import</span> <span class="n">PdfFileReader</span><span class="p">,</span> <span class="n">utils</span>
<span class="kn">from</span> <span class="nn">cStringIO</span> <span class="kn">import</span> <span class="n">StringIO</span>
<span class="kn">import</span> <span class="nn">subprocess</span>
<span class="n">input_path</span> <span class="o">=</span> <span class="s1">'c:/test_in.pdf'</span>
<span class="k">def</span> <span class="nf">decompress_pdf</span><span class="p">(</span><span class="n">temp_buffer</span><span class="p">):</span>
<span class="n">temp_buffer</span><span class="o">.</span><span class="n">seek</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="c1"># Make sure we're at the start of the file.</span>
<span class="n">process</span> <span class="o">=</span> <span class="n">subprocess</span><span class="o">.</span><span class="n">Popen</span><span class="p">([</span><span class="s1">'pdftk.exe'</span><span class="p">,</span>
<span class="s1">'-'</span><span class="p">,</span> <span class="c1"># Read from stdin.</span>
<span class="s1">'output'</span><span class="p">,</span>
<span class="s1">'-'</span><span class="p">,</span> <span class="c1"># Write to stdout.</span>
<span class="s1">'uncompress'</span><span class="p">],</span>
<span class="n">stdin</span><span class="o">=</span><span class="n">temp_buffer</span><span class="p">,</span>
<span class="n">stdout</span><span class="o">=</span><span class="n">subprocess</span><span class="o">.</span><span class="n">PIPE</span><span class="p">,</span>
<span class="n">stderr</span><span class="o">=</span><span class="n">subprocess</span><span class="o">.</span><span class="n">PIPE</span><span class="p">)</span>
<span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span> <span class="o">=</span> <span class="n">process</span><span class="o">.</span><span class="n">communicate</span><span class="p">()</span>
<span class="k">return</span> <span class="n">StringIO</span><span class="p">(</span><span class="n">stdout</span><span class="p">)</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">input_path</span><span class="p">,</span> <span class="s1">'rb'</span><span class="p">)</span> <span class="k">as</span> <span class="n">input_file</span><span class="p">:</span>
<span class="n">input_buffer</span> <span class="o">=</span> <span class="n">StringIO</span><span class="p">(</span><span class="n">input_file</span><span class="o">.</span><span class="n">read</span><span class="p">())</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">input_pdf</span> <span class="o">=</span> <span class="n">PdfFileReader</span><span class="p">(</span><span class="n">input_buffer</span><span class="p">)</span>
<span class="k">except</span> <span class="n">utils</span><span class="o">.</span><span class="n">PdfReadError</span><span class="p">:</span>
<span class="n">input_pdf</span> <span class="o">=</span> <span class="n">PdfFileReader</span><span class="p">(</span><span class="n">decompress_pdf</span><span class="p">(</span><span class="n">input_file</span><span class="p">))</span>
</code></pre></div>
<p>The problem I was seeing seemed to be because of invalid characters appearing after the %%<span class="caps">EOF</span> marker in the <span class="caps">PDF</span>. PDFtk seems better at fixing this and spits out a valid <span class="caps">PDF</span> when the <code>uncompress</code> command is used. </p>
<p>Of course, more error detection would be good in case parsing still fails, but this worked for me today and made me happy.</p>New Blog!2013-06-14T14:29:31+01:002013-06-14T14:29:31+01:00Gary Hughestag:overthere.co.uk,2013-06-14:/2013/06/14/new-blog/
<p>I’ve moved this site from WordPress to <a href="http://getpelican.com" title="Pelican">Pelican</a>. Pelican is awesome.</p>
<p>WordPress is written in <span class="caps">PHP</span> and uses MySQL to store the data needed to build each page. The good thing about WordPress is that the pages are built as they are requested. The bad thing about WordPress is that the pages are built as they are requested.</p>
<p>The whole database-driven dynamic page thing can slow down a site quite a bit, especially if you’re only using a basic hosting package. If like me you also install a boatload of plugins and themes to test things out the database gets full of cruft adding to more slowdowns. I experimented with various different caching plugins and they seemed to work great for a couple of weeks and then they’d stop updating the content and serve up week-old stale pages. I never found out why.</p>
<p>I’ve moved this site from WordPress to <a href="http://getpelican.com" title="Pelican">Pelican</a>. Pelican is awesome.</p>
<p>WordPress is written in <span class="caps">PHP</span> and uses MySQL to store the data needed to build each page. The good thing about WordPress is that the pages are built as they are requested. The bad thing about WordPress is that the pages are built as they are requested.</p>
<p>The whole database-driven dynamic page thing can slow down a site quite a bit, especially if you’re only using a basic hosting package. If like me you also install a boatload of plugins and themes to test things out the database gets full of cruft adding to more slowdowns. I experimented with various different caching plugins and they seemed to work great for a couple of weeks and then they’d stop updating the content and serve up week-old stale pages. I never found out why.</p>
<p>So a caching plugin basically saves a copy of a generated page as a static <span class="caps">HTML</span> file and serves it back up if the page is requested again instead of having to recreate it. It’s a way of turning your nice dynamic site into a static site. So why not just have a static site? Well, it’s good to have themes, plugins and control over other parts of the site to be able to change things quickly. It’s also nice to have a nice interface for writing posts.</p>
<p>While looking for alternative blogging platforms that might improve the speed of my site I stumbled across Pelican. The fact that it’s written in Python had me interested right away. Pelican doesn’t require any kind of fiddly setting up of the web host, it just parses a config file with site settings, takes posts written in single <a href="http://daringfireball.net/projects/markdown/" title="Markdown">Markdown</a> files and creates a static <span class="caps">HTML</span> site. When you add a new post by creating a new Markdown file the whole site can be regenerated again. Very quickly. Tie this with <a href="http://rsync.samba.org/" title="rsync">rsync</a> for quick deployment back to the web host and you have a very customisable and fast blogging solution that’s also fun to use.</p>
<p>I started out with a couple of the default themes with my own tweaks, but have finally settled on a Pelican port of the default <a href="http://octopress.org" title="Octopress">Octopress</a> (similar to Pelican but Ruby based) theme by <a href="https://github.com/duilio/pelican-octopress-theme" title="pelican-octopress-theme">Maurizio Sambati</a> along with some excellent modifications by <a href="https://github.com/jakevdp/pelican-octopress-theme" title="improved pelican-octopress-theme">Jake Vanderplas</a>. I’m very much happy with the results.</p>QGraphicsView with mouse wrapping2012-11-29T21:58:53+00:002012-11-29T21:58:53+00:00Gary Hughestag:overthere.co.uk,2012-11-29:/2012/11/29/qgraphicsview-with-mouse-wrapping/
<p>In a document management program that I’m writing I’m displaying images in a <code>QGraphicsView</code>. The <code>QGraphicsView</code> supports a drag mode that allows a user to simply click and drag within the view to pan around, which works great when a large image is displayed and the user has zoomed in quite far.</p>
<p>Panning the image this way does mean that the user has to click back on the image and move the mouse again when the mouse reaches the end of the screen. I needed a way to implement mouse wrapping so that when the mouse reaches the end of the <code>QGraphicsView</code> during panning and the user is still holding down the left mouse button the mouse will jump to the opposite edge, giving a smooth continuous pan.</p>
<p>Here’s what I came up with…</p>
<p>In a document management program that I’m writing I’m displaying images in a <code>QGraphicsView</code>. The <code>QGraphicsView</code> supports a drag mode that allows a user to simply click and drag within the view to pan around, which works great when a large image is displayed and the user has zoomed in quite far.</p>
<p>Panning the image this way does mean that the user has to click back on the image and move the mouse again when the mouse reaches the end of the screen. I needed a way to implement mouse wrapping so that when the mouse reaches the end of the <code>QGraphicsView</code> during panning and the user is still holding down the left mouse button the mouse will jump to the opposite edge, giving a smooth continuous pan.</p>
<p>Here’s what I came up with…</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">PyQt4.QtCore</span> <span class="kn">import</span> <span class="n">Qt</span><span class="p">,</span> <span class="n">QEvent</span><span class="p">,</span> <span class="n">QTimer</span>
<span class="kn">from</span> <span class="nn">PyQt4.QtGui</span> <span class="kn">import</span> <span class="n">QWidget</span><span class="p">,</span> <span class="n">QGraphicsView</span><span class="p">,</span> <span class="n">QVBoxLayout</span><span class="p">,</span> \
<span class="n">QApplication</span><span class="p">,</span> <span class="n">QGraphicsScene</span><span class="p">,</span> <span class="n">QBrush</span><span class="p">,</span> <span class="n">QColor</span><span class="p">,</span> <span class="n">QMouseEvent</span><span class="p">,</span> \
<span class="n">QCursor</span>
<span class="k">class</span> <span class="nc">GraphicsView</span><span class="p">(</span><span class="n">QGraphicsView</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">mouseMoveEvent</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">event</span><span class="p">):</span>
<span class="n">width</span><span class="p">,</span> <span class="n">height</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">width</span><span class="p">(),</span> <span class="bp">self</span><span class="o">.</span><span class="n">height</span><span class="p">()</span>
<span class="n">event_x</span><span class="p">,</span> <span class="n">event_y</span> <span class="o">=</span> <span class="n">event</span><span class="o">.</span><span class="n">x</span><span class="p">(),</span> <span class="n">event</span><span class="o">.</span><span class="n">y</span><span class="p">()</span>
<span class="k">if</span> <span class="n">event_y</span> <span class="o"><</span> <span class="mi">0</span> <span class="ow">or</span> <span class="n">event_y</span> <span class="o">></span> <span class="n">height</span> <span class="ow">or</span> \
<span class="n">event_x</span> <span class="o"><</span> <span class="mi">0</span> <span class="ow">or</span> <span class="n">event_x</span> <span class="o">></span> <span class="n">width</span><span class="p">:</span>
<span class="c1"># Mouse cursor has left the widget. Wrap the mouse.</span>
<span class="n">global_pos</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">mapToGlobal</span><span class="p">(</span><span class="n">event</span><span class="o">.</span><span class="n">pos</span><span class="p">())</span>
<span class="k">if</span> <span class="n">event_y</span> <span class="o"><</span> <span class="mi">0</span> <span class="ow">or</span> <span class="n">event_y</span> <span class="o">></span> <span class="n">height</span><span class="p">:</span>
<span class="c1"># Cursor left on the y axis. Move cursor to the</span>
<span class="c1"># opposite side.</span>
<span class="n">global_pos</span><span class="o">.</span><span class="n">setY</span><span class="p">(</span><span class="n">global_pos</span><span class="o">.</span><span class="n">y</span><span class="p">()</span> <span class="o">+</span>
<span class="p">(</span><span class="n">height</span> <span class="k">if</span> <span class="n">event_y</span> <span class="o"><</span> <span class="mi">0</span> <span class="k">else</span> <span class="o">-</span><span class="n">height</span><span class="p">))</span>
<span class="k">else</span><span class="p">:</span>
<span class="c1"># Cursor left on the x axis. Move cursor to the</span>
<span class="c1"># opposite side.</span>
<span class="n">global_pos</span><span class="o">.</span><span class="n">setX</span><span class="p">(</span><span class="n">global_pos</span><span class="o">.</span><span class="n">x</span><span class="p">()</span> <span class="o">+</span>
<span class="p">(</span><span class="n">width</span> <span class="k">if</span> <span class="n">event_x</span> <span class="o"><</span> <span class="mi">0</span> <span class="k">else</span> <span class="o">-</span><span class="n">width</span><span class="p">))</span>
<span class="c1"># For the scroll hand dragging to work with mouse wrapping</span>
<span class="c1"># we have to emulate a mouse release, move the cursor and</span>
<span class="c1"># then emulate a mouse press. Not doing this causes the</span>
<span class="c1"># scroll hand drag to stop after the cursor has moved.</span>
<span class="n">r_event</span> <span class="o">=</span> <span class="n">QMouseEvent</span><span class="p">(</span><span class="n">QEvent</span><span class="o">.</span><span class="n">MouseButtonRelease</span><span class="p">,</span>
<span class="bp">self</span><span class="o">.</span><span class="n">mapFromGlobal</span><span class="p">(</span><span class="n">QCursor</span><span class="o">.</span><span class="n">pos</span><span class="p">()),</span>
<span class="n">Qt</span><span class="o">.</span><span class="n">LeftButton</span><span class="p">,</span>
<span class="n">Qt</span><span class="o">.</span><span class="n">NoButton</span><span class="p">,</span>
<span class="n">Qt</span><span class="o">.</span><span class="n">NoModifier</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">mouseReleaseEvent</span><span class="p">(</span><span class="n">r_event</span><span class="p">)</span>
<span class="n">QCursor</span><span class="o">.</span><span class="n">setPos</span><span class="p">(</span><span class="n">global_pos</span><span class="p">)</span>
<span class="n">p_event</span> <span class="o">=</span> <span class="n">QMouseEvent</span><span class="p">(</span><span class="n">QEvent</span><span class="o">.</span><span class="n">MouseButtonPress</span><span class="p">,</span>
<span class="bp">self</span><span class="o">.</span><span class="n">mapFromGlobal</span><span class="p">(</span><span class="n">QCursor</span><span class="o">.</span><span class="n">pos</span><span class="p">()),</span>
<span class="n">Qt</span><span class="o">.</span><span class="n">LeftButton</span><span class="p">,</span>
<span class="n">Qt</span><span class="o">.</span><span class="n">LeftButton</span><span class="p">,</span>
<span class="n">Qt</span><span class="o">.</span><span class="n">NoModifier</span><span class="p">)</span>
<span class="n">QTimer</span><span class="o">.</span><span class="n">singleShot</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="k">lambda</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">mousePressEvent</span><span class="p">(</span><span class="n">p_event</span><span class="p">))</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">QGraphicsView</span><span class="o">.</span><span class="n">mouseMoveEvent</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">event</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">Widget</span><span class="p">(</span><span class="n">QWidget</span><span class="p">):</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">parent</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="n">QWidget</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">parent</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">scene</span> <span class="o">=</span> <span class="n">QGraphicsScene</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">scene</span><span class="o">.</span><span class="n">setSceneRect</span><span class="p">(</span><span class="o">-</span><span class="mi">5000</span><span class="p">,</span> <span class="o">-</span><span class="mi">5000</span><span class="p">,</span> <span class="mi">5000</span><span class="p">,</span> <span class="mi">5000</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">g_view</span> <span class="o">=</span> <span class="n">GraphicsView</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">scene</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">g_view</span><span class="o">.</span><span class="n">setBackgroundBrush</span><span class="p">(</span><span class="n">QBrush</span><span class="p">(</span><span class="n">QColor</span><span class="p">(</span><span class="s1">'black'</span><span class="p">),</span>
<span class="n">Qt</span><span class="o">.</span><span class="n">DiagCrossPattern</span><span class="p">))</span>
<span class="bp">self</span><span class="o">.</span><span class="n">g_view</span><span class="o">.</span><span class="n">setDragMode</span><span class="p">(</span><span class="n">QGraphicsView</span><span class="o">.</span><span class="n">ScrollHandDrag</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">g_view</span><span class="o">.</span><span class="n">scale</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>
<span class="n">v_layout</span> <span class="o">=</span> <span class="n">QVBoxLayout</span><span class="p">()</span>
<span class="n">v_layout</span><span class="o">.</span><span class="n">addWidget</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">g_view</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">setLayout</span><span class="p">(</span><span class="n">v_layout</span><span class="p">)</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="n">app</span> <span class="o">=</span> <span class="n">QApplication</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span>
<span class="n">widget</span> <span class="o">=</span> <span class="n">Widget</span><span class="p">()</span>
<span class="n">widget</span><span class="o">.</span><span class="n">show</span><span class="p">()</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="n">app</span><span class="o">.</span><span class="n">exec_</span><span class="p">())</span>
</code></pre></div>
<p>The code above uses a sub-classed <code>QGraphicsView</code>. The <code>mousePressEvent</code> checks to see if the mouse cursor has left the <code>QGraphicsView</code>; if it has then it moves the mouse to the opposite edge.
The bit that had me stuck were the <code>mouseReleaseEvent</code> and <code>mousePressEvent</code> calls. When moving the cursor the drag was still active, so the image just panned back as if the user had moved the mouse. </p>
<p>The mouse events are called to simulate releasing the mouse button, moving the pointer and then pressing the mouse button again. This gives a smooth continuous pan until you run out of either image or desk space :).</p>Using QStyledItemDelegate on a QTableView2012-07-29T16:23:38+01:002012-07-29T16:23:38+01:00Gary Hughestag:overthere.co.uk,2012-07-29:/2012/07/29/using-qstyleditemdelegate-on-a-qtableview/
<p>Most databases store dates in the format <code>YYYY-MM-DD</code>. When using a <code>QTableView</code> to display a database table in PyQt it’s useful to be able to display this in a different format. Being from the <span class="caps">UK</span> I’d rather have the date displayed in the <span class="caps">UK</span> style – <code>DD/MM/YYYY</code>. This is where <code>QStyledItemDelegate</code> comes in.</p>
<p>I’d put off looking into this for a while because whenever I tried to look in to using <code>QStyledItemDelegate</code> I saw people mentioning paint methods. I know nothing about painting in PyQt and get quite scared when I see it mentioned. I really need to get over that.</p>
<p>Not to worry though, because it’s not needed…</p>
<p>Most databases store dates in the format <code>YYYY-MM-DD</code>. When using a <code>QTableView</code> to display a database table in PyQt it’s useful to be able to display this in a different format. Being from the <span class="caps">UK</span> I’d rather have the date displayed in the <span class="caps">UK</span> style – <code>DD/MM/YYYY</code>. This is where <code>QStyledItemDelegate</code> comes in.</p>
<p>I’d put off looking into this for a while because whenever I tried to look in to using <code>QStyledItemDelegate</code> I saw people mentioning paint methods. I know nothing about painting in PyQt and get quite scared when I see it mentioned. I really need to get over that.</p>
<p>Not to worry though, because it’s not needed…</p>
<p>Just subclass <code>QStyledItemDelegate</code> and have it take the variable date_format in its <code>__init__</code> method:</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">DateFormatDelegate</span><span class="p">(</span><span class="n">QStyledItemDelegate</span><span class="p">):</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">date_format</span><span class="p">):</span>
<span class="n">QStyledItemDelegate</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">date_format</span> <span class="o">=</span> <span class="n">date_format</span>
<span class="k">def</span> <span class="nf">displayText</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">locale</span><span class="p">):</span>
<span class="k">return</span> <span class="n">value</span><span class="o">.</span><span class="n">toDate</span><span class="p">()</span><span class="o">.</span><span class="n">toString</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">date_format</span><span class="p">)</span>
</code></pre></div>
<p>This way we can customise the date format without changing the class making it a little more portable.
On your QTableView call setItemDelegateForColumn() and pass it your column index and a new instance of your subclassed QStyledItemDelegate with a date format:</p>
<div class="highlight"><pre><span></span><code><span class="bp">self</span><span class="o">.</span><span class="n">tableResults</span><span class="o">.</span><span class="n">setItemDelegateForColumn</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span>
<span class="n">DateFormatDelegate</span><span class="p">(</span><span class="s1">'dd/MM/yyyy'</span><span class="p">))</span>
</code></pre></div>
<p>Much easier than I expected.</p>
<p><img alt="Example Screenshot" src="https://overthere.co.uk/images/using-qstyleditemdelegate-on-a-qtableview/table_with_delegate.png"></p>Splitting old-style JPEG encoded TIFFs2012-05-18T10:56:23+01:002012-05-18T10:56:23+01:00Gary Hughestag:overthere.co.uk,2012-05-18:/2012/05/18/splitting-old-style-jpeg-encoded-tiffs/
<p>The tiffsplit program from the libtiff library does a good job of splitting multi-page <span class="caps">TIFF</span> files into single pages. It doesn’t seem that great at handling TIFFs saved with ‘old-style’ <span class="caps">JPEG</span> encoding.</p>
<p>It’ll produce files, and the correct number of files too, but the files are actually in <span class="caps">JPEG</span> format. Most image viewers won’t open them still because they’re <span class="caps">JPEG</span> files with <span class="caps">TIFF</span> headers.</p>
<p>Assuming you have a <span class="caps">TIFF</span> file made up entirely of old-style <span class="caps">JPEG</span> encoded images you can loop through the files that tiffsplit has exported and remove the first eight bytes to give valid <span class="caps">JPEG</span> files.</p>
<p>The tiffsplit program from the libtiff library does a good job of splitting multi-page <span class="caps">TIFF</span> files into single pages. It doesn’t seem that great at handling TIFFs saved with ‘old-style’ <span class="caps">JPEG</span> encoding.</p>
<p>It’ll produce files, and the correct number of files too, but the files are actually in <span class="caps">JPEG</span> format. Most image viewers won’t open them still because they’re <span class="caps">JPEG</span> files with <span class="caps">TIFF</span> headers.</p>
<p>Assuming you have a <span class="caps">TIFF</span> file made up entirely of old-style <span class="caps">JPEG</span> encoded images you can loop through the files that tiffsplit has exported and remove the first eight bytes to give valid <span class="caps">JPEG</span> files.</p>
<p>The Python function below will write the contents of a given file that come after the first eight bytes into a new file with the ‘<code>.jpg</code>’ extension before deleting the original passed file.</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">remove_header</span><span class="p">(</span><span class="n">path</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> Write the contents of the given file after the first 8 bytes to</span>
<span class="sd"> a new file, then delete the original.</span>
<span class="sd"> """</span>
<span class="n">new_path</span> <span class="o">=</span> <span class="s1">'</span><span class="si">%s</span><span class="s1">.jpg'</span> <span class="o">%</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">splitext</span><span class="p">(</span><span class="n">path</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="s1">'rb'</span><span class="p">)</span> <span class="k">as</span> <span class="n">input_file</span><span class="p">:</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">new_path</span><span class="p">,</span> <span class="s1">'wb'</span><span class="p">)</span> <span class="k">as</span> <span class="n">output_file</span><span class="p">:</span>
<span class="n">input_file</span><span class="o">.</span><span class="n">seek</span><span class="p">(</span><span class="mi">8</span><span class="p">)</span>
<span class="n">output_file</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">input_file</span><span class="o">.</span><span class="n">read</span><span class="p">())</span>
<span class="n">os</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
</code></pre></div>Windows 7 Minimal Taskbar Menu2011-10-20T12:08:46+01:002011-10-20T12:08:46+01:00Gary Hughestag:overthere.co.uk,2011-10-20:/2011/10/20/windows-7-minimal-taskbar-menu/
<p>Right clicking the taskbar in Windows 7 and selecting Toolbars and New Toolbar will let you select a folder on your computer to add as a taskbar menu. Here’s how to make the label take up less space…</p>
<p>Right clicking the taskbar in Windows 7 and selecting Toolbars and New Toolbar will let you select a folder on your computer to add as a taskbar menu. Here’s how to make the label take up less space…</p>
<p>When you create a new toolbar Windows will add the folder name as a label:</p>
<p><img alt="Long Name" src="https://overthere.co.uk/images/windows-7-minimal-taskbar-menu/long-name.png" title="Long Name"></p>
<p>I’d prefer it to not take up as much space. Wanting to create a menu for quick AutoHotkey scripts I started with a folder named, simply, ‘Scripts’. Too big.</p>
<p>I thought maybe if I created a folder containing just a space character that would work. Then I’d just have a menu without a label - I don’t really need a label anyway and I’m all for saving taskbar space. But no. Have you every tried creating a folder in Windows named just with the space character? It doesn’t seem to work. It kept being named ‘New Folder’ instead.</p>
<p>Then I remembered an old trick. I can’t even remember what it was for now, but I was using Windows 95 at the time so it was a while aog. I needed to put a space into something but the software I was using prevented it. I used a different character instead; the non-breaking space (Alt+0160 on the numerical keypad under Windows). I gave it a go and sure enough I was able to create a new folder with a seemingly blank character.</p>
<p><img alt="Folder View" src="https://overthere.co.uk/images/windows-7-minimal-taskbar-menu/folder-view.png" title="Folder View"></p>
<p>So the menu now?</p>
<p><img alt="Menu Open" src="https://overthere.co.uk/images/windows-7-minimal-taskbar-menu/menu-open.png" title="Menu Open"></p>
<p><img alt="Menu Closed" src="https://overthere.co.uk/images/windows-7-minimal-taskbar-menu/blank-name.png" title="Menu Closed"></p>
<p>Much nicer.</p>Cleaning QR Codes2011-06-14T23:12:23+01:002011-06-14T23:12:23+01:00Gary Hughestag:overthere.co.uk,2011-06-14:/2011/06/14/cleaning-qr-codes/
<p>At work we use <a href="http://en.wikipedia.org/wiki/QR_code" title="QR Codes"><span class="caps">QR</span> Codes</a>. It’s a pretty recent thing. For a few clients we used to scan pages of a file and then have the scanner operator stop what they were doing to give the <span class="caps">TIFF</span> file a meaningful filename. Now we have the clients supply cover sheets, generated with custom software, that contain <span class="caps">QR</span> codes. These then get processed after scanning and before the scan of the header sheet is discarded the indexing data is decoded using <a href="http://zbar.sourceforge.net" title="ZBar">ZBar</a> through an AutoHotkey script.</p>
<p>A few days ago we hit a problem. We’d scanned a few thousand pages but the script was unable to decode any of the indexing data from the <span class="caps">QR</span> codes. Not a single page. After looking into it I found that there were quality issues with the printing of the cover sheet. I could tell that the <span class="caps">QR</span> code was valid because the app I use on my mobile phone could read it fine, but ZBar could not. And ZBar is what matters – I wasn’t about to sit down decoding <span class="caps">QR</span> codes with my phone all day.</p>
<p>At work we use <a href="http://en.wikipedia.org/wiki/QR_code" title="QR Codes"><span class="caps">QR</span> Codes</a>. It’s a pretty recent thing. For a few clients we used to scan pages of a file and then have the scanner operator stop what they were doing to give the <span class="caps">TIFF</span> file a meaningful filename. Now we have the clients supply cover sheets, generated with custom software, that contain <span class="caps">QR</span> codes. These then get processed after scanning and before the scan of the header sheet is discarded the indexing data is decoded using <a href="http://zbar.sourceforge.net" title="ZBar">ZBar</a> through an AutoHotkey script.</p>
<p>A few days ago we hit a problem. We’d scanned a few thousand pages but the script was unable to decode any of the indexing data from the <span class="caps">QR</span> codes. Not a single page. After looking into it I found that there were quality issues with the printing of the cover sheet. I could tell that the <span class="caps">QR</span> code was valid because the app I use on my mobile phone could read it fine, but ZBar could not. And ZBar is what matters – I wasn’t about to sit down decoding <span class="caps">QR</span> codes with my phone all day.</p>
<p>Here’s one of the troublesome codes:</p>
<p><img alt="QR Before" src="https://overthere.co.uk/images/cleaning-qr-codes/before.png"></p>
<p>After playing with the <span class="caps">QR</span> code image in Photoshop for a bit I found a way of making the code readable to ZBar. I still needed a way to process the image from within a script on the processing machines, for which we do not have a Photoshop license. <a href="http://www.graphicsmagick.org" title="GraphicsMagick">GraphicsMagick</a> to the rescue! Or <a href="http://www.imagemagick.org" title="ImageMagick">ImageMagick</a>, if that’s your thing.</p>
<p>The AutoHotkey script was modified so that if no data was returned when attempting to read the <span class="caps">QR</span> code it would run the image through an extra function outputting a ‘fixed’ version of the image to a temporary file before reading that file instead and returning the data. The clean up used in GraphicsMagick is just:</p>
<div class="highlight"><pre><span></span><code>gm convert <span class="s2">"<input filepath>"</span> -blur 200 -level 80 <span class="s2">"<output filepath>"</span>
</code></pre></div>
<p>The <code>–blur</code> and <code>–level</code> values can be tweaked depending on the quality of your image, but this worked perfectly for me in all the scanned images for the dodgy batch. Here’s the cleaned <span class="caps">QR</span> code:</p>
<p><img alt="QR Before" src="https://overthere.co.uk/images/cleaning-qr-codes/after.png"></p>
<p>Ok, so not actually <em>that</em> different to the original, but enough for ZBar to actually return data this time.</p>
<p><img alt="QR Before" src="https://overthere.co.uk/images/cleaning-qr-codes/cmd_prompt.png"></p>
<p>If you’re having trouble reading a <span class="caps">QR</span> code, or any other barcode using ZBar then give this a try. Seeing how the Windows binary for ZBar actually uses ImageMagick I’m hoping that eventually there’ll be an auto-cleanup feature to deal with troublesome files in a later version.</p>Installing Poppler for PyQt4 on Windows2011-03-06T03:46:00+00:002011-03-06T03:46:00+00:00Gary Hughestag:overthere.co.uk,2011-03-06:/2011/03/06/setup-poppler-windows-pyqt/
<p>Poppler is a <span class="caps">PDF</span> rendering library based on the xpdf-3.0 code base. It has bindings for Qt4 which we can use through Python/PyQt. Using these libraries under most popular Linux distributions is as simple as installing the Poppler package, but under Windows I’ve found that things are a little more tricky to get working.</p>
<p>Poppler is a <span class="caps">PDF</span> rendering library based on the xpdf-3.0 code base. It has bindings for Qt4 which we can use through Python/PyQt. Using these libraries under most popular Linux distributions is as simple as installing the Poppler package, but under Windows I’ve found that things are a little more tricky to get working.</p>
<p>At work, with the main business being document scanning, we deal with <span class="caps">PDF</span> files a lot. I’ve written a few custom applications that allow people to use the <span class="caps">PDF</span> files that we have supplied for them. Some of the applications simply launch the <span class="caps">PDF</span> in the user’s default viewer when it is chosen from a list. Others use an embedded version of Adobe’s Acrobat Reader which has to first be installed on the user’s machine.</p>
<p>Acrobat Reader doesn’t play very well with apps written in PyQt in my experience. I don’t really like the idea of having to use an entire program (and Acrobat Reader is a little hefty) embedded in my software just to view a file. Although I was able to load files into the embedded Acrobat Reader and adjust the display preferences I found that the application focus was left with Acrobat Reader after each change. For example, I connected a <code>QSlider</code> to some code to adjust the zoom of the document and all worked fine when using the mouse. Selecting the <code>QSlider</code> with the keyboard and attempting to use it didn’t. Once the zoom had moved one step the focus would be taken from the <code>QSlider</code> and left with Acrobat Reader, meaning that further adjustments from the keyboard required the re-selecting of the <code>QSlider</code>. I tried to find a way around this. I failed.</p>
<p>I went in search of an alternative to Acrobat Reader and found Poppler. Based on the xpdf-3.0 codebase and released under the <span class="caps">GPL</span> this seemed to be exactly what I needed. It would let me view <span class="caps">PDF</span> files in my PyQt application without having to use Acrobat Reader along with letting me perform a few other <span class="caps">PDF</span> related tasks. Great.</p>
<p>I tried to get it to work on Windows. Oh, I tried. I’m not that experienced a programmer and don’t know a great deal about compiling C code. I headed over to the Poppler mailing list and although there were a few people asking how they could use Poppler on Windows there were not that many answers. At least, nothing that I was able to follow. A few people seem to be searching for the term ‘<em>How to compile Poppler on Windows</em>’, but there was nothing really that I could use.</p>
<p>In my Ubuntu Linux installation I was able to use Poppler simply by installing the package. I wrote a quick test program to view a <span class="caps">PDF</span> and it worked great and was a lot faster than I expected at rendering the pages. Unfortunately at work I use Windows, as do pretty much all of our customers.
A few months went by with a couple more failed attempts at getting things to work and then last week I actually managed it. It’s cheating a little; I’m using the installer from the <span class="caps">KDE</span> Windows Initiative to help me along, but it works and gives me a usable Poppler library that I can use from within PyQt on Windows. This is by no means the only way and there are many projects that use Poppler on Windows quite successfully, but this is a fairly simple way to get things set up and working and I thought I should post a guide on how to do it.</p>
<hr>
<h3>Download List</h3>
<p>First of all there are a few things that you will need to download.</p>
<h4>Python</h4>
<p><a href="http://python.org/download/" title="http://python.org/download/">http://python.org/download/</a><br>
Personally I’m still using version 2.x of Python. At the time of writing the most recent of this branch is 2.7.1, so click the link for ‘Python 2.7.1 Windows Installer’ at the download link and install it if you don’t already have Python set up on your machine.</p>
<h4>Qt <span class="caps">SDK</span></h4>
<p><a href="http://qt.nokia.com/downloads" title="http://qt.nokia.com/downloads">http://qt.nokia.com/downloads</a><br>
We don’t have to compile all of Qt. We can get away with just downloading the <span class="caps">SDK</span> installer which gives us the files we need for the other things we’ll be compiling. Choose <span class="caps">LGPL</span> from the download page and download the installer that at the time of writing is labelled ‘Qt <span class="caps">SDK</span> for Windows* (322 <span class="caps">MB</span>)‘. This gives me an install of Qt 4.7.0 (2010/05).</p>
<h4><span class="caps">SIP</span></h4>
<p><a href="http://www.riverbankcomputing.co.uk/software/sip/download" title="http://www.riverbankcomputing.co.uk/software/sip/download">http://www.riverbankcomputing.co.uk/software/sip/download</a><br>
<span class="caps">SIP</span> is a set of bindings for using C/C++ libraries with Python. You’ll need to have this compiled and installed before we build PyQt. The latest version for me right now is 4.12.1. Download the Windows Source archive.</p>
<h4>PyQt4</h4>
<p><a href="http://www.riverbankcomputing.co.uk/software/pyqt/download" title="http://www.riverbankcomputing.co.uk/software/pyqt/download">http://www.riverbankcomputing.co.uk/software/pyqt/download</a><br>
This is the package that performs the magical task of making Qt work with Python. There are binary files available that install the necessary Qt, <span class="caps">SIP</span> and PyQt4 files needed for normal PyQt4 tasks. These cannot be used if you want to use Poppler. The PyQt4 binaries for Windows available at the Riverbank Computing site are compiled using Microsoft Visual Studio. In order to use Poppler in PyQt4 on Windows we need to also compile pypoppler-qt4, which I’ll be compiling with MinGW. For pypoppler-qt4 to work we need to first compile PyQt4 ourselves using MinGW to make things compatible. It may be possible to use the Microsoft Visual Studio compiler to compile pypoppler-qt4, but that’s not something I have installed or have ever used. This is what caused a lot of my problems the first time I tried to get things working. The latest version of PyQt4 as I’m writing this is 4.8.3 and again you’ll need to download the Windows source package.</p>
<h4><span class="caps">KDE</span></h4>
<p><a href="http://windows.kde.org/" title="http://windows.kde.org/">http://windows.kde.org/</a><br>
The <span class="caps">KDE</span> Windows Initiative has done the hard work of compiling Poppler (along with it’s various dependencies) for us. To save us having to do the same thing we can just download the Windows installer and use it to grab the files we’ll need to compile pypoppler-qt4. Choose Download Installer from the Download menu on the linked page.</p>
<h4>pypoppler-qt4</h4>
<p><a href="http://svn.pardus.org.tr/uludag/trunk/playground/pypoppler-qt4/" title="http://svn.pardus.org.tr/uludag/trunk/playground/pypoppler-qt4/">http://svn.pardus.org.tr/uludag/trunk/playground/pypoppler-qt4/</a><br>
I’ve given the link here for the download location for the pypoppler-qt4 source, it’s up to you whether you download this with a Subversion client or through your web browser. If using a web browser just download all the files one at a time. There aren’t that many, just six at the time of writing including the license information and authors list. If you’re downloading using a Subversion client we’ll make a new folder called ‘src’ on the root of the drive. Switch to it and issue the command for Subversion to download the source that we need. The steps for this in the command prompt are:</p>
<div class="highlight"><pre><span></span><code><span class="k">cd</span> c:\
<span class="k">mkdir</span> src
<span class="k">cd</span> src
svn checkout http://svn.pardus.org.tr/uludag/trunk/playground/pypoppler-qt4/
</code></pre></div>
<p>If your Subversion client isn’t in your path you’ll need to type the full path to your svn binary. If using the Slik <span class="caps">SVN</span> client mentioned in the optional installs below this would mean replacing ‘svn’ above with ‘C:\Program Files\SlikSvn\bin\svn.exe’ if you installed the 32-bit version to the default folder.</p>
<p>I wasn’t able to get this to compile on Windows as it was but I had success with the configure.py file from the <a href="http://spv.rubico.info/wiki/">SlidePresenterView</a> project’s modifications. If you have <a href="http://bazaar.canonical.com/en/">Bazaar</a> installed you can get the latest version by entering:</p>
<div class="highlight"><pre><span></span><code>bzr branch lp:~spv-dev/slidepresenterview/pypoppler-qt4
</code></pre></div>
<p>into your command prompt. Otherwise you can download the copy I used from <a href="https://overthere.co.uk/downloads/setup-poppler-windows-pyqt/configure.py" title="setup-poppler-windows-pyqt/configure.py">here</a>.</p>
<hr>
<h3>Optional Downloads</h3>
<p>These aren’t actually needed to get things working, but are recommended.</p>
<h4>pywin32 / Python for Windows extensions (optional)</h4>
<p><a href="http://sourceforge.net/projects/pywin32/files/pywin32/Build%20214/" title="http://sourceforge.net/projects/pywin32/files/pywin32/Build%20214/">http://sourceforge.net/projects/pywin32/files/pywin32/Build%20214/</a><br>
Not actually needed for this tutorial, but if you’re using Python on a Windows machine it’s a pretty good idea to have it and should be part of your standard install. I’m using the file labelled ‘pywin32-214.win32-py2.7.exe’ to go with my Python 2.7.1 install. You’ll need to download a different install if your version of Python differs.</p>
<h4>setuptools – (optional)</h4>
<p>http://pypi.python.org/pypi/setuptools
Again, not actually needed for the tutorial but something that you should have anyway for easily installing PyPI packages along with their dependencies. The current version as of now is 0.6c11 – if you’re using Python 2.7.x as described above go ahead and download the file marked ‘setuptools-0.6c11.win32-py2.7.exe’, otherwise substitute for your setup.</p>
<h4>Slik <span class="caps">SVN</span>, or any Subversion client (optional)</h4>
<p><a href="http://www.sliksvn.com/en/download" title="http://www.sliksvn.com/en/download">http://www.sliksvn.com/en/download</a><br>
It doesn’t have to be slik subversion – any subversion client will do if you already have one installed. We’ll need this to download the pypoppler-qt4 source, unless you want to just download the files manually through your web browser. It’s a good idea to have a Subversion client installed though if you’re going to play with new bits of software. I grabbed slik subversion 1.6.15.</p>
<h4>QScintilla2</h4>
<p><a href="http://www.riverbankcomputing.co.uk/static/Downloads/QScintilla2/QScintilla-gpl-2.4.6.zip" title="http://www.riverbankcomputing.co.uk/static/Downloads/QScintilla2/QScintilla-gpl-2.4.6.zip">http://www.riverbankcomputing.co.uk/static/Downloads/QScintilla2/QScintilla-gpl-2.4.6.zip</a><br>
A Qt port of the Scintilla C++ editor control. This is a required install if you also want to install Eric4.</p>
<h4>Eric4</h4>
<p><a href="http://sourceforge.net/projects/eric-ide/files/eric4/stable/4.4.12/eric4-4.4.12.zip/download" title="http://sourceforge.net/projects/eric-ide/files/eric4/stable/4.4.12/eric4-4.4.12.zip/download">http://sourceforge.net/projects/eric-ide/files/eric4/stable/4.4.12/eric4-4.4.12.zip/download</a><br>
My favourite <span class="caps">IDE</span> for writing PyQt applications on Windows and Linux. The current version at the time of writing is 4.4.12.
You should now have all the files needed to work through this tutorial.</p>
<hr>
<h3>Step 1 - Installing Python</h3>
<p>This should be nice and easy. If you’re reading this tutorial then I’d imagine you’re a Python user with Python installed already. If not, just run the installer and follow the instructions. Although not needed directly for this tutorial I’d recommend installing pywin32 and setuptools as well at this point.</p>
<h3>Step 2 - Installing Qt <span class="caps">SDK</span></h3>
<p>Another easy one. Again, just run the installer and follow the instructions if you don’t already have the Qt <span class="caps">SDK</span> installed.</p>
<p>You also need to add a few folders to your system’s path environment. This is something that has had me screaming ‘<span class="caps">WHY</span>?!’ at my screen quite a bit – it turns out that the order that these folders appear in your list are quite important. On my machine the three folders that needed adding (and in this order too) were:</p>
<div class="highlight"><pre><span></span><code>C<span class="p">:</span><span class="nl">\Qt\2010.05\qt</span>
C<span class="p">:</span><span class="nl">\Qt\2010.05\bin</span>
C<span class="p">:</span><span class="nl">\Qt\2010.05\qt\bin</span>
</code></pre></div>
<p>To add these to your system path, right click on My Computer on the desktop (or the ‘<code>Computer</code>’ entry in the Start Menu) and choose ‘Properties’, followed by ‘Advanced System Settings’ from the menu on the left of the new window that appears. Typing ‘<code>sysdm.cpl</code>’ in the Run Dialog and clicking the Advanced tab will also get you here. Click the Environment Variables button at the bottom of the window and then from ‘System variables’ highlight ‘Path’ and click the Edit button. Enter these three paths (or the versions of them that match the setup on your machine) into the ‘<code>Variable value</code>’ box and separate them by a semicolon. Make sure they’re the first items in there and that you include a semicolon after all three folder paths so that your existing items are separated out.</p>
<p><img alt="Edit Path" src="https://overthere.co.uk/images/setup-poppler-windows-pyqt/edit_path.png"></p>
<h3>Step 3 - Installing <span class="caps">SIP</span></h3>
<p>First of all you must unzip your sip archive to a folder on your hard drive. I’ll be keeping all of my source code for this tutorial in a folder called ‘<code>src</code>’ at the root of my C drive. Using a folder without spaces and close to the root of your drive just makes things a little easier when we’re working with a command prompt.</p>
<p>The Qt <span class="caps">SDK</span>, along with the header files we need for our building, installs a copy of the MinGW compiler. I ran into a few problems with folders in my Windows system path conflicting with the files from the Qt <span class="caps">SDK</span> before finally finding out that the Qt <span class="caps">SDK</span> provides a batch file that opens a command prompt and, inside it, clears your system’s path variable and sets a few other variables needed for our compiling. This console can be started by clicking <code>Start –> All Programs –> Qt SDK by Nokia v2010.05 (open source) –> Qt Command Prompt</code>.</p>
<p>Switch to the folder containing your <span class="caps">SIP</span> source code. You may normally start Python scripts in Windows by either directly running the ‘<code>.py</code>’ file or by typing ‘python’ followed by the name of your file in a command prompt. You don’t need the full path because the Python installer adds the <code>python.exe</code> to your system path. Since we’re in Qt’s customised terminal Python is no longer in our path (at least, not in this command prompt instance) so we must call the <code>python.exe</code> using it’s full path, changing it if your version differs from mine. Run the configure script through Python passing it the ‘<code>-p win32-g++</code>’ argument:</p>
<div class="highlight"><pre><span></span><code><span class="k">cd</span> \src\sip-4.12.1
c<span class="p">:</span><span class="nl">\Python27\python.exe</span><span class="c1"> configure.py -p win32-g++</span>
</code></pre></div>
<p>You should see some text scroll through the window ending in ‘Creating sip module Makefile…’. Once done type two more commands to build and then install <span class="caps">SIP</span>:</p>
<div class="highlight"><pre><span></span><code>mingw32-make
mingw32-make install
</code></pre></div>
<p><span class="caps">SIP</span> should now be installed and your command prompt should look something like this:</p>
<p><img alt="SIP Install" src="https://overthere.co.uk/images/setup-poppler-windows-pyqt/sip_install.png"></p>
<h3>Step 4 - Installing PyQt4</h3>
<p>This is pretty similar to installing <span class="caps">SIP</span>. I’m using the commercial version of PyQt4, but this should work the same for the <span class="caps">GPL</span> version. Unzip your PyQt4 source to <code>c:\src</code> and if you’re using the commercial version you must also add your <code>pyqt-commercial.sip</code> file (available from your account page on the Riverbank site) in the ‘<code>sip</code>’ folder within your PyQt4 source folder (‘<code>C:\src\PyQt-win-commercial-4.8.3\sip</code>’ in my case).</p>
<p>In the Qt command prompt that you have left over from installing <span class="caps">SIP</span> switch to your PyQt4 source folder and run PyQt4’s configure script through Python with the ‘<code>-w</code>’ switch.</p>
<div class="highlight"><pre><span></span><code><span class="k">cd</span> \src\PyQt-win-commercial-4.8.3
c<span class="p">:</span><span class="nl">\Python27\python.exe</span><span class="c1"> configure.py –w</span>
</code></pre></div>
<p>If asked to accept the license type ‘<code>yes</code>’ followed by the enter key to proceed. After a couple of minutes this ends with the text ‘<code>Creating pyqtconfig.py…</code>’ on my machine.</p>
<p>As with <span class="caps">SIP</span> we now compile the source by entering:</p>
<div class="highlight"><pre><span></span><code>mingw32-make
mingw32-make install
</code></pre></div>
<p>This should take a few minutes to complete and your console window should look something like this when finished.</p>
<p><img alt="PyQt Complete" src="https://overthere.co.uk/images/setup-poppler-windows-pyqt/pyqt-complete.png"></p>
<p>We just need to check that everything installed ok and that we can actually import and use PyQt. Open up a new Python terminal (<span class="caps">IDLE</span> is fine, or you could just use the console window you already have open) and type the following:</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">PyQt4.Qt</span> <span class="kn">import</span> <span class="o">*</span>
<span class="kn">from</span> <span class="nn">sip</span> <span class="kn">import</span> <span class="o">*</span>
<span class="nb">print</span> <span class="p">[</span><span class="n">SIP_VERSION_STR</span><span class="p">,</span> <span class="n">QT_VERSION_STR</span><span class="p">,</span> <span class="n">PYQT_VERSION_STR</span><span class="p">]</span>
</code></pre></div>
<p>This should return:</p>
<div class="highlight"><pre><span></span><code><span class="p">[</span><span class="s1">'4.12.1'</span><span class="p">,</span> <span class="s1">'4.7.0'</span><span class="p">,</span> <span class="s1">'4.8.3'</span><span class="p">]</span>
</code></pre></div>
<p>or whatever versions of <span class="caps">SIP</span>, Qt and PyQt respectively that you installed. So long as you get numbers and not an error you should be ok.</p>
<h3>Step 5 - Installing Poppler (via <span class="caps">KDE</span> Installer)</h3>
<p>So here’s the bit where we cheat a little (thanks, <span class="caps">KDE</span>!). Run the installer and select the option ‘<code>Install from Internet</code>’. I’ve set the install path on my machine to ‘<code>c:\kde</code>’. When asked about the Install Mode choose ‘<code>Package Manager</code>’ and ‘<code>MinGW4 W32</code>’. I went with the download server ‘<code>Central Europe, Germany (http://www.winkde.org)</code>’ after trying a few that were a little closer to me but didn’t have the latest <span class="caps">KDE</span> version. Once you’ve chosen the latest version you’ll want to download the following packages:</p>
<ul>
<li>poppler-mingw: Bin and Devel version</li>
<li>poppler-data: Bin version</li>
<li>freetype-mingw: Bin version</li>
<li>jpeg: Bin version</li>
<li>libpng: Bin version</li>
<li>libxml2: Bin version</li>
<li>iconv: Bin version</li>
<li>zlib: Bin version</li>
</ul>
<p><img alt="KDE Install" src="https://overthere.co.uk/images/setup-poppler-windows-pyqt/kde-install.png"></p>
<p>Once these are selected hit ‘<code>Next</code>’ then ‘<code>Finish</code>’ to install.
Once installed we need to add the <span class="caps">KDE</span> ‘<code>bin</code>’ folder (‘<code>c:\kde\bin</code>’ in my install – adjust this if you installed <span class="caps">KDE</span> to a different folder) to the system path like we did for Qt in Step 2 and then we’re done.</p>
<h3>Step 6 - Installing pypoppler-qt4</h3>
<p>You should already have the pypoppler-qt4 source checked out of <span class="caps">SVN</span> from the download list at the top of this tutorial. If not, go back and do that now.</p>
<p>Next, download <a href="https://overthere.co.uk/downloads/setup-poppler-windows-pyqt/configure.py">this version of the configure.py</a> file and replace the version from your <span class="caps">SVN</span> checkout with it. This will allow us to specify where our Poppler libraries (thank you again, <span class="caps">KDE</span> Installer) are with this command (all on one line):</p>
<div class="highlight"><pre><span></span><code>c<span class="p">:</span><span class="nl">\Python27\python.exe</span><span class="c1"> configure.py --popplerqt-includes-dir ^</span>
c:\kde\include\poppler --popplerqt-libs-dir c:\kde\bin
</code></pre></div>
<p>The configure script should then tell you where it will be installing it’s files:</p>
<div class="highlight"><pre><span></span><code>Using PopplerQt include paths: ['c:\\kde\\include\\poppler']
Using PopplerQt libraries paths: ['c:\\kde\\bin']
Configured to install SIP in c:\Python27\sip
Configured to install binaries in c:\Python27\Lib\site-packages
</code></pre></div>
<p>If the paths look correct and match what you passed the configure.py script then go ahead and run:</p>
<div class="highlight"><pre><span></span><code>mingw32-make
mingw32-make install
</code></pre></div>
<p>When complete your command prompt should look something like this:</p>
<p><img alt="pypoppler-qt4 complete" src="https://overthere.co.uk/images/setup-poppler-windows-pyqt/pypoppler-qt4-complete.png"></p>
<h3>Step 7 - Installing other optional files.</h3>
<p>I swear by using <a href="http://eric-ide.python-projects.org/">eric4</a> for developing in PyQt. It’s a decent <span class="caps">IDE</span> and integrates well with the PyQt stuff allowing you auto compile any changed <span class="caps">GUI</span> elements automatically when running your project along with providing UIs for generating code for interface components and lots of other nice things.</p>
<p>It also depends on <code>QScintilla</code> for displaying code. This is something that comes standard with the PyQt4 setup binaries for Windows but we’re installing from source so if you need <code>QScintilla</code> you’ll need to compile that too.</p>
<p>Unpack the source archive into <code>c:\src</code> as with our other source packages. Once again in the Qt command prompt type the following from the <code>QScintilla</code> source folder:</p>
<div class="highlight"><pre><span></span><code><span class="k">cd</span> Qt4
qmake qscintilla.pro
mingw32-make
mingw32-make install
</code></pre></div>
<p>Once installed you’ll need to copy the <code>qscintilla2.dll</code> file from your Qt Lib folder to your Qt Bin folder.</p>
<div class="highlight"><pre><span></span><code><span class="k">copy</span> c:\Qt\2010.05\qt\lib\qscintilla2.dll c:\Qt\2010.05\qt\bin
</code></pre></div>
<p>And finally we compile the Python wrappers. Switch back to the root of your <code>QScintilla</code> source folder and then switch into the Python folder (replace for the path to your <code>QScintilla</code> source folder if needed):</p>
<div class="highlight"><pre><span></span><code><span class="k">cd</span> C:\src\QScintilla-commercial-2.4.6\Python
c<span class="p">:</span><span class="nl">\Python27\python.exe</span><span class="c1"> configure.py</span>
mingw32-make
mingw32-make install
</code></pre></div>
<p>To install eric4 you’ll just need to unpack the zip archive and execute:</p>
<div class="highlight"><pre><span></span><code>c<span class="p">:</span><span class="nl">\Python27\python.exe</span><span class="c1"> install.py</span>
</code></pre></div>
<p>from within the unpacked folder. Eric4 can be started by running the <code>eric4.bat</code> file that has been created in your Python folder.</p>
<h3>Testing - Making sure it all works.</h3>
<p>Now that everything is installed it’s time to see if it all works. Make sure you have a <span class="caps">PDF</span> file somewhere on your computer for testing and fire up a Python interpreter.</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">QtPoppler</span>
<span class="n">doc</span> <span class="o">=</span> <span class="n">QtPoppler</span><span class="o">.</span><span class="n">Poppler</span><span class="o">.</span><span class="n">Document</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="s1">'c:/test/test.pdf'</span><span class="p">)</span>
<span class="n">doc</span><span class="o">.</span><span class="n">numPages</span><span class="p">()</span>
</code></pre></div>
<p>This should return the number of pages in your <span class="caps">PDF</span> file. For my <span class="caps">PDF</span> it returned a count of 641 pages:</p>
<p><img alt="Poppler Test" src="https://overthere.co.uk/images/setup-poppler-windows-pyqt/poppler_test.png"></p>
<p>If QtPoppler imported without any errors and returned a pagecount after calling <code>numPages()</code> on a loaded document then everything is working. To jump in and start doing things with <span class="caps">PDF</span> pages you can render them to a <code>QImage</code> and display however you would normally display a <code>QImage</code> in your program:</p>
<div class="highlight"><pre><span></span><code><span class="n">doc</span><span class="o">.</span><span class="n">page</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="o">.</span><span class="n">renderToImage</span><span class="p">()</span>
</code></pre></div>
<p>Specify your page number as a parameter to <code>doc.page()</code> – page numbers start from zero.</p>
<p>For more information refer to the <a href="http://people.freedesktop.org/~aacid/docs/qt4/">Poppler Qt4 documentation</a>.</p>Native Desktop Application for Subsonic2010-12-08T21:50:02+00:002010-12-08T21:50:02+00:00Gary Hughestag:overthere.co.uk,2010-12-08:/2010/12/08/native-desktop-application-for-subsonic/
<p>I’ve been looking for a new personal project to work on for a while and I’ve been having trouble finding something. I wanted something that I’d use myself, otherwise my interest would quickly drop. Something that ideally I would use every day. I also wanted to write something that other people would find a use for as well. And it had to be written in Python and be something completely different to anything I’ve played with before. So I’ve decided to write a desktop client for <a href="" title='http://www.subsonic.org" "Subsonic'>Subsonic</a>.</p>
<p>I’ve been looking for a new personal project to work on for a while and I’ve been having trouble finding something. I wanted something that I’d use myself, otherwise my interest would quickly drop. Something that ideally I would use every day. I also wanted to write something that other people would find a use for as well. And it had to be written in Python and be something completely different to anything I’ve played with before. So I’ve decided to write a desktop client for <a href="" title="http://www.subsonic.org" "Subsonic">Subsonic</a>.</p>
<div class="alert alert-info">
<p><strong>This project is dead.</strong></p>
<p>Well, it didn’t make it much further than what is mentioned in this post.<br>
Take a look at <a href="http://code.google.com/p/subsonic-client">this</a> for a similar project whose aims are similar to what I was talking about in this post.</p>
</div>
<p>A few weeks ago I had a play around with <a href="http://www.spotify.com" title="Spotify">Spotify</a>. It’s a really good service, but a little too pricey and while the desktop application is quite good I found the Android application to be a bit laggy. They have a wide variety of music but with some notable omissions (no Pink Floyd or <span class="caps">AC</span>/<span class="caps">DC</span>). I cancelled my subscription before my first month had ended.</p>
<!-- PELICAN_END_SUMMARY -->
<p><a href="http://www.last.fm" title=""Last.fm">Last.fm</a> is an old favourite of mine for streaming music. They have just removed the feature I used the most though: the streaming of Loved Tracks. While Last.fm is still good for streaming random music that is a mix of stuff that I like and stuff that I’ve not heard before but would probably like, I miss having control of my playlist.</p>
<p>I tried <a href="http://www.grooveshark.com" title="Grooveshark">Grroveshark</a> – much, much better price wise than Spotify and with an Android application that seems to work a lot better. They provide a desktop application written in Adobe Air which is fine but I found that a lot of the music was not labelled correctly. It’s annoying to build a playlist and then have a totally different song to what you expected play. I still use Grooveshark through their website occasionally and may even re-new my <span class="caps">VIP</span> membership in the future with the price being so low.</p>
<p><a href="http://www.subsonic.org" title="Subsonic">Subsonic</a> works differently than all of these. The music is streamed from your own server. This gets around the lack of Pink Floyd quite nicely and for a one time donation that is comparable to a single month’s subscription to Spotify you can keep all features active after your 30 day trial. Music can be streamed either through a web browser, an Adobe Air client or a very well made Android client. So far this has worked out the best for me.</p>
<p>Subsonic is also open source and has an open <span class="caps">API</span>. While the Adobe Air client is functional it doesn’t really do much more than play music. Seeing how I’ve not used Python/<span class="caps">QT</span> to do anything with audio files yet or talk to web services / parse <span class="caps">XML</span> files I thought it would be a fun project to work on a desktop client.</p>
<p>Writing it in PyQt means that it should run on Windows as well as Linux and <span class="caps">OS</span> X. My plan is to integrate my client with Last.fm as well to provide dynamic playlist support (add one track and have the player automatically add similar tracks while playing), artist information and the old Loved Tracks feature that Last.fm used to provide.</p>
<p>It’s going to take a while and will be something I just work on in my free time. So far I’m able to pull a list of artists along with album and track information complete with cover art. Here’s a screenshot of how things are looking right now:</p>
<p><img alt="PySubsonic Screenshot" src="https://overthere.co.uk/images/native-desktop-application-for-subsonic/pysub-sshot.png"></p>
<p>Of course, the look is going to change quite a bit and I’m sure I’ll be thinking of new things to add as I’m working on it. There are quite a few features that could be added – local music support rather than just being limited to Subsonic, Last.fm streaming rather than just pulling track details and cross referencing them with the Subsonic library and many other things. It’s good to have finally found something I’ll enjoy building and will use myself quite often.</p>
<p>Also, I need a name for it. I can’t think of anything right now. Suggestions?</p>