<?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>Stuporglue.org &#187; stuporglue</title>
	<atom:link href="http://stuporglue.org/author/stuporglue/feed/" rel="self" type="application/rss+xml" />
	<link>http://stuporglue.org</link>
	<description>Programming, Rambling and More!</description>
	<lastBuildDate>Tue, 15 May 2012 17:03:17 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.2</generator>
		<item>
		<title>I&#8217;m Looking for a Microfilm Digitization Quote</title>
		<link>http://stuporglue.org/im-looking-for-a-microfilm-digitization-quote/</link>
		<comments>http://stuporglue.org/im-looking-for-a-microfilm-digitization-quote/#comments</comments>
		<pubDate>Tue, 15 May 2012 16:58:51 +0000</pubDate>
		<dc:creator>stuporglue</dc:creator>
				<category><![CDATA[Computers]]></category>
		<category><![CDATA[Digitization]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Projects]]></category>
		<category><![CDATA[Something Interesting]]></category>

		<guid isPermaLink="false">http://stuporglue.org/?p=1327</guid>
		<description><![CDATA[I&#8217;m looking for a microfilm digitization quote. If you or someone you know provides microfilm digitization, please have them send me a quote. I&#8217;ve got 113 reels of microfilm I&#8217;d like to digitize and I&#8217;m looking for a ballpark estimate &#8230; <a href="http://stuporglue.org/im-looking-for-a-microfilm-digitization-quote/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>I&#8217;m looking for a microfilm digitization quote. If you or someone you know provides microfilm digitization, please have them send me a quote.</p>
<p>I&#8217;ve got 113 reels of microfilm I&#8217;d like to digitize and I&#8217;m looking for a ballpark estimate for the project.</p>
<p>Here&#8217;s the info I know, please let me know if you need anything else:</p>
<ul>
<li>There are an average of 600 images per reel (about 67,800 images)</li>
<li>I&#8217;d like to scan at 300 dpi, 8bit grayscale lossless images (tiff? png?)</li>
<li>I have the copyright on the images on these reels</li>
<li>The reels are lightly used duplicates of the master reels. The master reels are unfortunately unavailable</li>
<li>The images are all scanned newspapers</li>
<li>I don&#8217;t need any OCR done</li>
<li>The only metadata I need is which reel each of the images came from eg. One directory per reel with incremental file names would be just fine.</li>
</ul>
<p>Project Background: I would like to put 128 years of <a title="Iron County Miner" href="http://ironcountyminer.com/" target="_blank">Iron County Miner</a> newspaper archives online. They would be freely available (no subscription or account required) and there&#8217;s no plan to make money from them. Since there&#8217;s no revenue expected I&#8217;m looking for ways to reduce costs while still putting something out there to benefit genealogists and historians.</p>
<p>The master rolls are held by the Wisconsin Historical Society who wants nearly $10,000 ($0.145/image) for the project or $80 per reel to send us fresh copies of the reels. From their perspective, I think that&#8217;s probably fair; they aren&#8217;t in the digitization business and they probably aren&#8217;t set up to do this sort of project in a streamlined manner. They also can&#8217;t amortize their digitization equipment costs across so many clients as a commercial company can.</p>
<p>For me though, $10,000 means that I can&#8217;t pursue this project right now.</p>
<p>Most digitization companies I have contacted have been reluctant to provide even a ballpark quote without seeing test reels, and I understand that that is a factor. Right now though, I just need a gauge to determine if this project is viable. If $10,000 is the real cost for this sort of project it will have to wait till I&#8217;m rich, but if I can get a cheaper quote I hope to make it happen this summer.</p>
<h2>Pre-Announcing NewspaperCMS</h2>
<p>I have been working on a CMS (Content Management System) called NewspaperCMS, to host the scanned images with and to make them easily navigable. It is licensed under the GPLv2 so anybody needing to host newspaper archives can use it.</p>
<p>Here&#8217;s its page on Google Code: <a title="NewspaperCMS" href="http://code.google.com/p/newspapercms/" target="_blank">http://code.google.com/p/newspapercms/</a></p>
<p>I would classify it as in late Alpha or early Beta stages right now. I&#8217;ll do an official post on it as it matures and as I get a publicly accessible test site set up. As a teaser, features include:</p>
<ul>
<li>Browse collection by microfilm, newspaper or date
<ul>
<li>Drill down within those categories by newspaper, issue, year or month</li>
</ul>
</li>
<li>Access-driven generation of midsized images. No need to generate 60,000 midsized images ahead of time.</li>
<li>Valid HTML5/CSS3</li>
<li>HTML5/Canvas based client-side image viewer. The user can zoom, rotate, invert, sharpen and change the contrast of the image (uses the <a title="Pixastic HTML5 canvas image processing" href="http://www.pixastic.com/" target="_blank">http://www.pixastic.com/</a>JavaScript libraries)
<ul>
<li>Falls back to a static image if they don&#8217;t have Canvas or JavaScript support</li>
</ul>
</li>
<li>Built in search engine</li>
<li>Support for the <a title="Tesseract OCR" href="http://code.google.com/p/tesseract-ocr/" target="_blank">tesseract</a> OCR engine</li>
</ul>
<p>As I said, it&#8217;s still in development, but if you need something like it, you can play with it now. It&#8217;s at the point where more development doesn&#8217;t make sense until I know I can get the microfilms scanned.</p>
]]></content:encoded>
			<wfw:commentRss>http://stuporglue.org/im-looking-for-a-microfilm-digitization-quote/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Create a Side-to-Side Draggable HTML5 Canvas in a Div</title>
		<link>http://stuporglue.org/create-a-side-to-side-draggable-html5-canvas-in-a-div/</link>
		<comments>http://stuporglue.org/create-a-side-to-side-draggable-html5-canvas-in-a-div/#comments</comments>
		<pubDate>Tue, 08 May 2012 20:02:00 +0000</pubDate>
		<dc:creator>stuporglue</dc:creator>
				<category><![CDATA[Computers]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[jquery]]></category>

		<guid isPermaLink="false">http://stuporglue.org/?p=1324</guid>
		<description><![CDATA[I have been playing with Pixastic and a little bit of HTML5 canvas image manipulation for a site I&#8217;m working on. I load an image into an HTML5 canvas and let the user do some basic manipulation, including zooming in &#8230; <a href="http://stuporglue.org/create-a-side-to-side-draggable-html5-canvas-in-a-div/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>I have been playing with <a title="Pixastic HTML5 canvas image processing" href="http://www.pixastic.com/">Pixastic</a> and a little bit of HTML5 canvas image manipulation for a site I&#8217;m working on. I load an image into an HTML5 canvas and let the user do some basic manipulation, including zooming in on the image.</p>
<p>Zooming in on the image quickly causes the canvas to outgrow my browser window. The div around the canvas is set to use <em>overflow: auto</em> so that the growing canvas doesn&#8217;t disrupt the rest of the page flow.</p>
<pre>&lt;div id="<a>pageimgdiv</a>" style="max-width: 100%; overflow: auto;"&gt;
   &lt;canvas&gt;
      Your browser doesn't support HTML5 Canvas
   &lt;/canvas&gt;
&lt;/div&gt;</pre>
<p>The overflowed div gains horizontal scrollbars (but not vertical ones, since there&#8217;s no max-height in my case). Unfortunately many people, including myself, don&#8217;t have horizontal scrolling configured for their mouse which means scrolling down to the scrollbar, moving over, then scrolling back up.</p>
<h2>JQuery To The Rescue</h2>
<p>I was able to use JQuery and the scrollLeft() function to make the canvas dragable within the div. The canvas itself doesn&#8217;t change sizes or pan (which would require a second canvas used as a buffer, I think). Instead we get the mouse position and current scrollLeft setting when the mouse is clicked, and then scroll more as they move the mouse, until they release the mouse or until they leave the wrapper div.</p>
<p>I&#8217;m using JQuery 1.7.2.</p>
<pre>$(document).ready(function(){
    $('#pageimgdiv').on(
    {
	mousedown: function(clicke){
	    origX = clicke.pageX + $('#pageimgdiv').scrollLeft();
	    $('#pageimgdiv').on(
	    {
		mousemove : function(e){
		    curX = e.pageX + $('#pageimgdiv').scrollLeft();
		    var diff = (origX - curX);
		    var newpos = $('#pageimgdiv').scrollLeft() + diff;
		    if(newpos &gt; ($('canvas').width() - $('#pageimgdiv').width())){
			newpos = ($('canvas').width() - $('#pageimgdiv').width());
		    }
		    if(newpos &lt; 0){
			newpos = 0;
		    }
		    $('#pageimgdiv').scrollLeft(newpos);
		}
	    }
	    );
	},
	mouseleave: function(){
	    $('#pageimgdiv').off('mousemove');
	},
	mouseup: function(){
	    $('#pageimgdiv').off('mousemove');
	},
	click: function(){
	    $('#pageimgdiv').off('mousemove');
	}
    }
    );
});</pre>
<h2>Embrace and Extend</h2>
<p>This code only scrolls horizontally. You could easily extend it to use scrollTop() and enable vertical scrolling as well.</p>
]]></content:encoded>
			<wfw:commentRss>http://stuporglue.org/create-a-side-to-side-draggable-html5-canvas-in-a-div/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Setting up Xdebug with NetBeans on Windows, with a Remote Apache Server</title>
		<link>http://stuporglue.org/setting-up-xdebug-with-netbeans-on-windows-with-a-remote-apache-server/</link>
		<comments>http://stuporglue.org/setting-up-xdebug-with-netbeans-on-windows-with-a-remote-apache-server/#comments</comments>
		<pubDate>Wed, 02 May 2012 01:11:06 +0000</pubDate>
		<dc:creator>stuporglue</dc:creator>
				<category><![CDATA[Computers]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Something Interesting]]></category>
		<category><![CDATA[debugging]]></category>
		<category><![CDATA[netbeans]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[putty]]></category>
		<category><![CDATA[ssh]]></category>
		<category><![CDATA[ssh tunnel]]></category>
		<category><![CDATA[xdebug]]></category>

		<guid isPermaLink="false">http://stuporglue.org/?p=1304</guid>
		<description><![CDATA[I fought with Xdebug and NetBeans enough to necessitate a post about it, if only so I don&#8217;t forget. Most Xdebug/NetBeans tutorial assume that you&#8217;re doing development on your local machine. That&#8217;s a fine setup, but not what I was &#8230; <a href="http://stuporglue.org/setting-up-xdebug-with-netbeans-on-windows-with-a-remote-apache-server/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>I fought with Xdebug and NetBeans enough to necessitate a post about it, if only so I don&#8217;t forget.</p>
<p>Most Xdebug/NetBeans tutorial assume that you&#8217;re doing development on your local machine. That&#8217;s a fine setup, but not what I was needed for this project.</p>
<h2>Environment</h2>
<p>Server: A typical Linux server &#8212; Debian, Apache2 and PHP.</p>
<p>Debugger: <a title="Xdebug" href="http://xdebug.org/" target="_blank">Xdebug</a></p>
<p>Client: Firefox on Windows, etc.</p>
<p>IDE: <a title="NetBeans for PHP" href="http://netbeans.org/features/php/" target="_blank">NetBeans</a> 7.1.1</p>
<p>Other Tools: <a title="PuTTY" href="http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html" target="_blank">PuTTY</a></p>
<h2>Setting Up Xdebug</h2>
<p>Install xdebug with the command:</p>
<pre>pecl install xdebug</pre>
<p>The final lines of output should say something like:</p>
<pre>Build process completed successfully
Installing '/usr/lib/php5/20090626/xdebug.so'
install ok: channel://pecl.php.net/xdebug-2.2.</pre>
<p>Take note of the install path, <strong>/usr/lib/php5/20090626/xdebug.so</strong>, in this case. Now add the following to your PHP configuration. I created a new .ini file at</p>
<pre>/etc/php5/conf.d/xdebug.ini</pre>
<p>Its contents should be as follows (change the zend_extension to match the install path found above):</p>
<pre>[xdebug]
zend_extension = "/usr/lib/php5/20090626/xdebug.so"

xdebug.remote_enable = on

; Most users won't want autostart. More on this later.
; xdebug.remote_autostart = on
xdebug.remote_autostart = off
xdebug.remote_handler = dbgp
xdebug.remote_port = 9000
xdebug.remote_server = localhost
xdebug.remote_mode = req

; Most users won't want a hard coded idekey. More on this later.
; xdebug.idekey = netbeans-xdebug
output_buffering = off

xdebug.remote_log = "/var/log/xdebug.log"</pre>
<p>Restart Apache to complete the installation.</p>
<p>xdebug.remote_server = localhost. Localhost? Wait a second, I thought this was for working with a remote server? Yes, but xdebug needs to be able to connect to your computer. The easy option is to have xdebug connect to the server&#8217;s localhost, then use PuTTY to create an SSH tunnel so that NetBeans can listen on your computer&#8217;s localhost.</p>
<p>The harder option is to configure your home or office router to forward port 9000 to you, be sure to never change your IP address, and open port 9000 on your Windows firewall. You could use <em>xdebug.remote_connect_back</em> so that xdebug would connect to whichever IP made the web request, but then someone who isn&#8217;t you could access your code.</p>
<p>In my opinion, the SSH tunneling is the cleanest option. You can use it anywhere that you have SSH access, xdebug access is restricted to those who have SSH access and you don&#8217;t have to worry about your IP address changing.</p>
<h2>Creating an SSH Tunnel for Xdebug</h2>
<p>Using <a title="PuTTY" href="http://www.chiark.greenend.org.uk/~sgtatham/putty/">PuTTY.exe</a> create and save a new SSH session which connects to your server. In this saved session configure a tunnel. Set the source port to 9000, the destination to localhost:9000, and choose the Remove and Auto radio buttons. Click the Add button to add that port forwarding configuration, then save that session to use every time you want to use xdebug.</p>
<figure id="attachment_1305" aria-labelledby="figcaption_attachment_1305" class="wp-caption aligncenter" style="width: 476px"><a href="http://stuporglue.org/setting-up-xdebug-with-netbeans-on-windows-with-a-remote-apache-server/putty_tunnel/" rel="attachment wp-att-1305"><img class="size-full wp-image-1305" title="PuTTY xdebug SSH tunnel" src="http://stuporglue.org/wp-content/uploads/2012/05/putty_tunnel.png" alt="PuTTY xdebug SSH tunnel" width="466" height="451" /></a><figcaption id="figcaption_attachment_1305" class="wp-caption-text">PuTTY xdebug SSH tunnel</figcaption></figure>
<p>Go ahead and connect to that saved PuTTY session now. Once you&#8217;re connected to your server you should be able to run netstat to verify that it&#8217;s working correctly. It should show something like this:</p>
<pre>netstat -a -n | grep 9000
tcp        0      0 127.0.0.1:9000          0.0.0.0:*               LISTEN
tcp6       0      0 ::1:9000                :::*                    LISTEN</pre>
<h2>Setting up NetBeans</h2>
<p>Set up your NetBeans project like you usually would. Verify that the PHP Debugging Settings (In the Tools-&gt;Options menu) are set to debugger port 9000, and the Session ID of netbeans-xdebug. I had problems with the <em>Watches and Balloon Evaluation</em> options. YMMV.</p>
<figure id="attachment_1306" aria-labelledby="figcaption_attachment_1306" class="wp-caption aligncenter" style="width: 580px"><a href="http://stuporglue.org/setting-up-xdebug-with-netbeans-on-windows-with-a-remote-apache-server/netbeans_debugging/" rel="attachment wp-att-1306"><img class="size-medium wp-image-1306" title="NetBeans Debugging Settings" src="http://stuporglue.org/wp-content/uploads/2012/05/NetBeans_Debugging-570x422.png" alt="NetBeans Debugging Settings" width="570" height="422" /></a><figcaption id="figcaption_attachment_1306" class="wp-caption-text">NetBeans Debugging Settings</figcaption></figure>
<p>In your project properties (Right click your project, click Properties) edit the Run Configuration&#8217;s Advanced properties. Select &#8220;Do Not Open Web Browser&#8221;.</p>
<h3>A Note on Path Mappings</h3>
<p>Based on the tutorials I read, most users don&#8217;t seem to need Path Mappings. NetBeans seems to figure out the path based on the upload directory, the URL and the Web Root settings.  That didn&#8217;t work for me<strong>. I needed Path Mapping because I had a symlink on the server</strong>.</p>
<p>PHP (and so Xdebug) dereferences symlinks and NetBeans needs the mapping between the dereferenced path on the server and the local sources directory.</p>
<p>This is a <a title="Xdebug does not follow Symlinks" href="http://bugs.xdebug.org/view.php?id=432" target="_blank">known issue</a>, but not very well known. Symptoms that you need to  use mapping include NetBeans not breaking in symlinked files, NetBeans not opening the file or not opening the correct file when Xdebug connection is made. Or it may work only if you use &#8220;Debug File&#8221; instead of &#8220;Debug Project&#8221;.</p>
<p><a href="http://stuporglue.org/setting-up-xdebug-with-netbeans-on-windows-with-a-remote-apache-server/netbeans_mapping/" rel="attachment wp-att-1307"><img class="aligncenter size-medium wp-image-1307" title="NetBeans Run Configuration Advanced Properties" src="http://stuporglue.org/wp-content/uploads/2012/05/Netbeans_mapping-570x409.png" alt="" width="570" height="409" /></a></p>
<h2> Debugging With Xdebug and NetBeans</h2>
<p>Here&#8217;s where you have some options. The manual way to active Xdebug is to append XDEBUG_SESSION_START=netbeans-xdebug to your query string when you request a page in the browser. With the current setup, this should work. Go ahead and test it.</p>
<p>Choose Debug -&gt; Debug Project from the NetBeans menu. Then, in your browser, go to http://example.com/path/to/page.php??XDEBUG_SESSION_START=netbeans-xdebug (replacing the URL with your own, of course).</p>
<p>If that doesn&#8217;t work then something isn&#8217;t set up correctly. It might be NetBeans, it might be Xdebug, it might be the SSH tunnel. Figure it out, get it fixed, then keep reading (you can leave comments here, and I&#8217;ll help as I can. Google is pretty helpful too).</p>
<p>If that does work, then GREAT!</p>
<p>Now you have some options.</p>
<h3>Browser Extensions</h3>
<p>Firefox and Chrome both have Xdebug extensions that set the XDEBUG_SESSION_START parameter in the http headers. This makes it so you don&#8217;t have to type it yourself.  <a title="Firefox Easy Xdebug" href="https://addons.mozilla.org/en-US/firefox/addon/easy-xdebug/" target="_blank">Yay Firefox</a>. <a title="Chrome Xdebug Helper" href="https://chrome.google.com/webstore/detail/eadndfjplgieldjbigjakmdgkmoaaaoc" target="_blank">Yay Chrome</a>.</p>
<h3>Always Auto-start Xdebug</h3>
<p>If you are testing embedded webkit, or from mobile devices, or something else that&#8217;s not a normal browser, then appending XDEBUG_SESSION_START is going to be difficult. This is where I send you back to your xdebug.ini file to change some settings. If you need to debug requests from these sorts of devices, then you&#8217;re going to want to edit xdebug.ini and set:</p>
<pre>xdebug.remote_autostart = on
xdebug.idekey = netbeans-xdebug</pre>
<p>This will cause Xdebug to attempt to connect on port 9000 on every request. The idekey setting is so that NetBeans will know that the connection is for it.</p>
<h2>Other PHP Debugging Tools</h2>
<p>If you are looking for good PHP debugging tools, you may also want to try out <a title="KCacheGrind" href="http://kcachegrind.sourceforge.net/html/Home.html" target="_blank">KCacheGrind</a> which will profile your code giving you an idea what&#8217;s taking up time and memory. <a title="HipHop-PHP" href="https://github.com/facebook/hiphop-php/wiki/" target="_blank">HipHop-PHP</a>, a tool from Facebook of all places, compiles PHP into C++. In the process it spits out all sorts of helpful errors and notices that will help you find errors in your code.</p>
<p>That concludes one more NetBeans Xdebug tutorial that will hopefully get you that much closer to doing some serious PHP debugging. Happy coding!</p>
]]></content:encoded>
			<wfw:commentRss>http://stuporglue.org/setting-up-xdebug-with-netbeans-on-windows-with-a-remote-apache-server/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Add Trailing Slashes Without Hardcoding The Domain</title>
		<link>http://stuporglue.org/add-trailing-slashes-without-hardcoding-the-domain/</link>
		<comments>http://stuporglue.org/add-trailing-slashes-without-hardcoding-the-domain/#comments</comments>
		<pubDate>Thu, 26 Apr 2012 16:28:24 +0000</pubDate>
		<dc:creator>stuporglue</dc:creator>
				<category><![CDATA[Computers]]></category>
		<category><![CDATA[Apache]]></category>
		<category><![CDATA[mod_rewrite]]></category>

		<guid isPermaLink="false">http://stuporglue.org/?p=1300</guid>
		<description><![CDATA[There are lots of tutorials showing how to add trailing slashes to a URL with mod_rewrite, but 99.9% of them hard code the domain. You might want to always append a trailing slash if you depend on it in $_SERVER['REDIRECT_URL'] &#8230; <a href="http://stuporglue.org/add-trailing-slashes-without-hardcoding-the-domain/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>There are lots of tutorials showing how to add trailing slashes to a URL with mod_rewrite, but 99.9% of them hard code the domain.</p>
<p>You might want to always append a trailing slash if you depend on it in $_SERVER['REDIRECT_URL'] or if you want to ensure that content is only available via a single URL for search engines etc.</p>
<p>Here&#8217;s the Apache mod_rewrite code:</p>
<pre>&lt;IfModule mod_rewrite.c&gt;
RewriteEngine On

# Always append a trailing slash
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !/$
RewriteRule . %{REQUEST_URI}/ [R=301,L]

&lt;/IfModule&gt;</pre>
]]></content:encoded>
			<wfw:commentRss>http://stuporglue.org/add-trailing-slashes-without-hardcoding-the-domain/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Announcing Todayish In History</title>
		<link>http://stuporglue.org/announcing-todayish-in-history/</link>
		<comments>http://stuporglue.org/announcing-todayish-in-history/#comments</comments>
		<pubDate>Mon, 16 Apr 2012 14:57:48 +0000</pubDate>
		<dc:creator>stuporglue</dc:creator>
				<category><![CDATA[Computers]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Projects]]></category>
		<category><![CDATA[Something Interesting]]></category>
		<category><![CDATA[Todayish In History Plugin]]></category>
		<category><![CDATA[plugin]]></category>
		<category><![CDATA[today in history.]]></category>
		<category><![CDATA[todayish in history]]></category>
		<category><![CDATA[wordpress]]></category>

		<guid isPermaLink="false">http://stuporglue.org/?p=1096</guid>
		<description><![CDATA[My other blog is a garden blog and so old posts have a high likelihood of becoming relevant again each and every year around the same time. I wanted a way to display a link to the blog post written &#8230; <a href="http://stuporglue.org/announcing-todayish-in-history/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>My <a title="The Fridley Farmer" href="http://fridleyfarmer.com/" target="_blank">other blog is a garden blog</a> and so old posts have a high likelihood of becoming relevant again each and every year around the same time. I wanted a way to display a link to the blog post written nearest to the current date from each previous year. After not finding a plugin that did what I wanted, I wrote my own.</p>
<p>The plugin is named Todayish because if there isn&#8217;t a blog post from today&#8217;s<br />
date in previous years it will use the blog post which is the least<br />
number of days away from todays date.</p>
<p>Todayish in History provides a function for use in themes as well as a<br />
widget for use in your sidebar.</p>
<h2>Todayish In History&#8217;s Homepage</h2>
<p>Todayish In History has a permanent home at <a title="Todayish In History" href="http://stuporglue.org/todayish-in-history/">http://stuporglue.org/todayish-in-history/</a>.</p>
<p>Hopefully this proves to be a useful plugin for someone. Please send me any feedback. You can use the comments, the contact page, or email me at stuporglue@gmail.com. <a title="Todayish In History" href="http://stuporglue.org/todayish-in-history/"><br />
</a></p>
<h2>Screenshots</h2>
<p>&nbsp;</p>

<a href='http://stuporglue.org/announcing-todayish-in-history/screenshot-1-2/' title='Todayish in a theme'><img width="150" height="54" src="http://stuporglue.org/wp-content/uploads/2012/04/screenshot-1-150x54.png" class="attachment-thumbnail" alt="Todayish in a theme" title="Todayish in a theme" /></a>
<a href='http://stuporglue.org/announcing-todayish-in-history/screenshot-2/' title='Todayish in a theme, expanded'><img width="150" height="44" src="http://stuporglue.org/wp-content/uploads/2012/04/screenshot-2-150x44.png" class="attachment-thumbnail" alt="Todayish in a theme, expanded" title="Todayish in a theme, expanded" /></a>
<a href='http://stuporglue.org/announcing-todayish-in-history/screenshot-3/' title='Todayish as a widget in vertical mode'><img width="150" height="115" src="http://stuporglue.org/wp-content/uploads/2012/04/screenshot-3-150x115.png" class="attachment-thumbnail" alt="Todayish as a widget in vertical mode" title="Todayish as a widget in vertical mode" /></a>
<a href='http://stuporglue.org/announcing-todayish-in-history/screenshot-4/' title='Todayish as a widget in horizontal mode'><img width="150" height="112" src="http://stuporglue.org/wp-content/uploads/2012/04/screenshot-4-150x112.png" class="attachment-thumbnail" alt="Todayish as a widget in horizontal mode" title="Todayish as a widget in horizontal mode" /></a>
<a href='http://stuporglue.org/announcing-todayish-in-history/screenshot-5/' title='Todayish widget options'><img width="136" height="150" src="http://stuporglue.org/wp-content/uploads/2012/04/screenshot-5-136x150.png" class="attachment-thumbnail" alt="Todayish widget options" title="Todayish widget options" /></a>

<h2>Demo</h2>
<p>You can see Todayish In History in action at the top of every page over at<a title="The Fridley Farmer" href="http://fridleyfarmer.com/" target="_blank"> The Fridley Farmer</a>.</p>
<h2></h2>
]]></content:encoded>
			<wfw:commentRss>http://stuporglue.org/announcing-todayish-in-history/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>mailReader.php &#8212; Parse E-mail and Save Attachments PHP, Version 2</title>
		<link>http://stuporglue.org/mailreader-php-parse-e-mail-and-save-attachments-php-version-2/</link>
		<comments>http://stuporglue.org/mailreader-php-parse-e-mail-and-save-attachments-php-version-2/#comments</comments>
		<pubDate>Sat, 14 Apr 2012 06:59:57 +0000</pubDate>
		<dc:creator>stuporglue</dc:creator>
				<category><![CDATA[Computers]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Something Interesting]]></category>
		<category><![CDATA[attachments]]></category>
		<category><![CDATA[email]]></category>
		<category><![CDATA[mailReader.php]]></category>
		<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://stuporglue.org/?p=1249</guid>
		<description><![CDATA[One of most popular pages of all time is Recieve E-mail and Save Attachments with a PHP script. What was meant to be a quick hack that was only ever tested with Gmail ended up generating lots of support requests. &#8230; <a href="http://stuporglue.org/mailreader-php-parse-e-mail-and-save-attachments-php-version-2/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>One of most popular pages of all time is <a title="Receive Email and Save Attachments With a PHP Script" href="http://stuporglue.org/recieve-e-mail-and-save-attachments-with-a-php-script/">Recieve E-mail and Save Attachments with a PHP script</a>. What was meant to be a quick hack that was only ever tested with Gmail ended up generating lots of support requests.</p>
<p>At first I suggested that people needed more robust email parsing use a dedicated library. But no one seemed to want to do the coding for that, so I ended up writing a new version which uses the <a title="Mail::mimeDecode" href="http://pear.php.net/package/Mail_mimeDecode" target="_blank">PEAR mimeDecode.php</a> library to do the parsing.</p>
<p>Without further ado, here&#8217;s mailReader.php!</p>
<h2>E-mail Processing Script Features:</h2>
<ol>
<li>Saves the e-mail sender, subject and body to a database</li>
<li>Saves any attachments as files and creates an entry for those files in the database, associated with the e-mail info in #1</li>
<li>Sends  a response back to the sender telling them what files were received and their file sizes</li>
<li>Checks a list of allowed senders to make sure we only take files from specified addresses.</li>
</ol>
<h2>Database Setup:</h2>
<p>If you&#8217;re going to use the database features, you&#8217;ll need a database. Here&#8217;s the SQL to create an identical setup to the one I have:</p>
<pre>-- Here's my DB structureCREATE TABLE IF NOT EXISTS `emails` (
  `id` int(255) NOT NULL AUTO_INCREMENT,
  `from` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `subject` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `body` text COLLATE utf8_unicode_ci NOT NULL,
  `date` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;

CREATE TABLE IF NOT EXISTS `files` (
  `id` int(255) NOT NULL AUTO_INCREMENT,
  `email_id` int(255) NOT NULL,
  `filename` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `size` varchar(20) COLLATE utf8_unicode_ci NOT NULL,
  `mime` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;</pre>
<h2>Security</h2>
<p>Make sure that your upload directory is out of your webroot. If someone emails you a malicious PHP script (eg. Virus.php) and can access it via the web, they could infect your server or your visitors. Many servers are configured to automatically treat .pl and .cgi as CGI scripts and run them as well. You do not want to create a way for untrusted users to upload files to your webroot!</p>
<p>With the file names in the database you can use <a title="PHP's Readfile" href="http://php.net/manual/en/function.readfile.php" target="_blank">Readfile</a> to pass files down to users.</p>
<h2>The Script: mailReader.php</h2>
<p><a title="mailReader.txt" href="http://stuporglue.org/downloads/mailReader.txt" target="_blank">Download it here</a>.</p>
<pre>#!/usr/bin/php -q
&lt;?php
//  Use -q so that php doesn't print out the HTTP headers

/*
 * mailReader.php
 *
 * Recieve mail and attachments with PHP
 *
 * Usage:
 * This script expects to recieve raw emails via STDIN.
 *
 * Configure your mail server to pipe emails to this script. (See
 * http://stuporglue.org/add-an-email-address-that-forwards-to-a-script/
 * for instructions).  Make this script executable, and edit the
 * configuration options to suit your needs. Change permissions
 * of the directories so that the user executing the script (probably the
 * mail user) will have write permission to the file upload directory.
 *
 * By default the script is configured to save pdf, zip, jpg, png and gif files.
 * Edit the switch statements around line 200 to change this.
 *
 * Requirements:
 * You will need mimeDecode.php from http://pear.php.net/package/Mail_mimeDecode/
 * I used version 1.5.5
 *
 * Copyright 2012, Michael Moore
 * Licensed under the same terms as PHP itself. You are free to use this script
 * for personal or commercial projects. Use at your own risk. No guarantees or
 * warranties.
 *
 * Contact:
 * &lt;stuporglue@gmail.com&gt;
 * http://stuporglue.org
 *
 * Support:
 * Limited free support available in the comments on the webpage for this script
 * or via email. Contracted support available for specific projects.
 * http://stuporglue.org/mailreader-php-parse-e-mail-and-save-attachments-php-version-2/
 *
 * Thanks:
 * Many thanks to forahobby of www.360-hq.com for testing this script and helping me find
 * the initial bugs.
 * Thanks to Craig Hopson of twitterrooms.co.uk for help tracking down an iOS email handling bug.
 */

global $save_directory,$saved_files,$debug,$body;

/*
 *
 * 	Configuration Options
 *
 */

// What's the max # of seconds to try to process an email?
$max_time_limit = 600; 

// A safe place for files WITH TRAILING SLASH
// Malicious users could upload a php or executable file,
// so keep this out of your web root
$save_directory = "/a/safe/save/directory/";

// Allowed senders is now just the email part of the sender (no name part)
$allowed_senders = Array(
    'myemail@example.com',
    'whatever@example.com',
); 

// Send confirmation e-mail back to sender?
$send_email = FALSE; 

// Save e-mail message and file list to DB?
$save_msg_to_db = FALSE; 

// Configure your MySQL database connection here
$db_host = 'localhost';
$db_un = 'db_un';
$db_pass = 'db_pass';
$db_name = 'db_name';

$debug = FALSE;

/*
 *
 * 	End of Configuration Options
 *
 */

//Anything printed to STDOUT will be sent back to the sender as an error!
//error_reporting(-1);
//ini_set("display_errors", 1);

// Initialize the other global, set PHP options, load email library
$saved_files = Array();
set_time_limit($max_time_limit);
ini_set('max_execution_time',$max_time_limit);
require_once('mimeDecode.php');

// Some functions we'll use
function formatBytes($bytes, $precision = 2) {
    $units = array('B', 'KB', 'MB', 'GB', 'TB');

    $bytes = max($bytes, 0);
    $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
    $pow = min($pow, count($units) - 1);

    $bytes /= pow(1024, $pow);

    return round($bytes, $precision) . ' ' . $units[$pow];
} 

// Find a happy place! Find a happy place!
function saveFile($filename,$contents,$mimeType){
    global $save_directory,$saved_files,$debug;
    $filename = preg_replace('/[^a-zA-Z0-9_-]/','_',$filename);

    $unlocked_and_unique = FALSE;
    while(!$unlocked_and_unique){
	// Find unique
	$name = time() . "_" . $filename;
	while(file_exists($save_directory . $name)) {
	    $name = time() . "_" . $filename;
	}

	// Attempt to lock
	$outfile = fopen($save_directory.$name,'w');
	if(flock($outfile,LOCK_EX)){
	    $unlocked_and_unique = TRUE;
	}else{
	    flock($outfile,LOCK_UN);
	    fclose($outfile);
	}
    }

    fwrite($outfile,$contents);
    fclose($outfile);

    // This is for readability for the return e-mail and in the DB
    $saved_files[$name] = Array(
	'size' =&gt; formatBytes(filesize($save_directory.$name)),
	'mime' =&gt; $mimeType
    );
}

function decodePart($body_part){
    global $body,$debug;
    if(array_key_exists('name',$body_part-&gt;ctype_parameters)){ // everyone else I've tried
	$filename = $body_part-&gt;ctype_parameters['name'];
    }else if($body_part-&gt;ctype_parameters &amp;&amp; array_key_exists('filename',$body_part-&gt;ctype_parameters)){ // hotmail
	$filename = $body_part-&gt;ctype_parameters['filename'];
    }else{
	$filename = "file";
    }

    if($debug){
	print "Found body part type {$body_part-&gt;ctype_primary}/{$body_part-&gt;ctype_secondary}\n";
    }

    $mimeType = "{$body_part-&gt;ctype_primary}/{$body_part-&gt;ctype_secondary}"; 

    switch($body_part-&gt;ctype_primary){
    case 'text':
	switch($body_part-&gt;ctype_secondary){
	case 'plain':
	    $body = $body_part-&gt;body; // If there are multiple text/plain parts, we will only get the last one.
	    break;
	}
	break;
    case 'application':
	switch ($body_part-&gt;ctype_secondary){
	case 'pdf': // save these file types
	case 'zip':
	case 'octet-stream':
	    saveFile($filename,$body_part-&gt;body,$mimeType);
	    break;
	default:
	    // anything else (exe, rar, etc.) will faill into this hole and die
	    break;
	}
	break;
    case 'image':
	switch($body_part-&gt;ctype_secondary){
	case 'jpeg': // Save these image types
	case 'png':
	case 'gif':
	    saveFile($filename,$body_part-&gt;body,$mimeType);
	    break;
	default:
	    break;
	}
	break;
    case 'multipart':
	if(is_array($body_part-&gt;parts)){
	    foreach($body_part-&gt;parts as $ix =&gt; $sub_part){
		decodePart($sub_part);
	    }
	}
	break;
    default:
	// anything else isn't handled
	break;
    }
}

//
// Actual email handling starts here!
// 

// Process the e-mail from stdin
$fd = fopen('php://stdin','r');
$raw = '';
while(!feof($fd)){ $raw .= fread($fd,1024); }

// Uncomment this for debugging.
// Then you can do
// cat /my/saved/file.raw | ./mailReader.php
// for testing
//file_put_contents("$save_directory/" . time() . "_email.raw",$raw);

// Now decode it!
// http://pear.php.net/manual/en/package.mail.mail-mimedecode.decode.php
$decoder = new Mail_mimeDecode($raw);
$decoded = $decoder-&gt;decode(
    Array(
	'decode_headers' =&gt; TRUE,
	'include_bodies' =&gt; TRUE,
	'decode_bodies' =&gt; TRUE,
    )
);

// Set $from_email and check if it's allowed
$from = $decoded-&gt;headers['from'];
$from_email = preg_replace('/.*&lt;(.*)&gt;.*/',"$1",$from);
if(!in_array($from_email,$allowed_senders)){
    die("$from_email not an allowed sender");
}

// Set the $subject
$subject = $decoded-&gt;headers['subject'];

// Find the email body, and any attachments
// $body_part-&gt;ctype_primary and $body_part-&gt;ctype_secondary make up the mime type eg. text/plain or text/html
if(is_array($decoded-&gt;parts)){
    foreach($decoded-&gt;parts as $idx =&gt; $body_part){
	decodePart($body_part);
    }
}

// $from_email, $subject and $body should be set now. $saved_files should have
// the files we captured

// Put the results in the database if needed
if($save_msg_to_db){
    mysql_connect($db_host,$db_un,$db_pass);
    mysql_select_db($db_name);

    $q = "INSERT INTO `emails` (`from`,`subject`,`body`) VALUES ('" .
	mysql_real_escape_string($from_email) . "','" .
	mysql_real_escape_string($subject) . "','" .
	mysql_real_escape_string($body) . "')";

    mysql_query($q) or die(mysql_error());

    if(count($saved_files) &gt; 0){
	$id = mysql_insert_id();
	$q = "INSERT INTO `files` (`email_id`,`filename`,`size`,`mime`) VALUES ";
	$filesar = Array();
	foreach($saved_files as $f =&gt; $data){
	    $filesar[] = "('$id','" .
		mysql_real_escape_string($f) . "','" .
		mysql_real_escape_string($data['size']) . "','" .
		mysql_real_escape_string($data['mime']) . "')";
	}
	$q .= implode(', ',$filesar);
	mysql_query($q) or die(mysql_error());
    }
}

// Send response e-mail if needed
if($send_email &amp;&amp; $from_email != ""){
    $to = $from_email;
    $newmsg = "Thanks! I just uploaded the following ";
    $newmsg .= "files to your storage:\n\n";
    $newmsg .= "Filename -- Size\n";
    foreach($saved_files as $f =&gt; $s){
	$newmsg .= "$f -- $s\n";
    }
    $newmsg .= "\nI hope everything looks right. If not,";
    $newmsg .=  "please send me an e-mail!\n";

    mail($to,$subject,$newmsg);
}

if($debug){
    print "From : $from_email\n";
    print "Subject : $subject\n";
    print "Body : $body\n";
    print "Saved Files : \n";
    print_r($saved_files);
}</pre>
<h2>Thanks</h2>
<p>Many thanks to <a title="www.360-hq.com" href="www.360-hq.com" target="_blank">forahobby</a> for testing this script and helping me squash a bunch of little bugs. Thanks to <a title="Twitter Rooms" href="http://twitterrooms.co.uk/" target="_blank">Craig Hopson</a> for his help finding a problem handling emails from iOS devices.</p>
]]></content:encoded>
			<wfw:commentRss>http://stuporglue.org/mailreader-php-parse-e-mail-and-save-attachments-php-version-2/feed/</wfw:commentRss>
		<slash:comments>23</slash:comments>
		</item>
		<item>
		<title>Make X11 work through sudo su -</title>
		<link>http://stuporglue.org/make-x11-work-through-sudo-su/</link>
		<comments>http://stuporglue.org/make-x11-work-through-sudo-su/#comments</comments>
		<pubDate>Wed, 07 Mar 2012 03:12:04 +0000</pubDate>
		<dc:creator>stuporglue</dc:creator>
				<category><![CDATA[Computers]]></category>
		<category><![CDATA[mit-magic-cookie]]></category>
		<category><![CDATA[putty]]></category>
		<category><![CDATA[x forwarding]]></category>
		<category><![CDATA[x11]]></category>
		<category><![CDATA[xargs]]></category>
		<category><![CDATA[xauth]]></category>

		<guid isPermaLink="false">http://stuporglue.org/?p=1227</guid>
		<description><![CDATA[I&#8217;ve got a server at work that I need to SSH into as a different user, then do an &#8220;sudo su -&#8221; to become root, and then run a program with X11 forwarding. I was getting this error: PuTTY X11 &#8230; <a href="http://stuporglue.org/make-x11-work-through-sudo-su/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve got a server at work that I need to SSH into as a different user, then do an &#8220;sudo su -&#8221; to become root, and then run a program with X11 forwarding.</p>
<p>I was getting this error:</p>
<pre>PuTTY X11 proxy: MIT-MAGIC-COOKIE-1 data did not matchWarning: This program is an suid-root program or is being run by the root user.
The full text of the error or warning message cannot be safely formatted
in this environment. You may get a more descriptive message by running the
program as a non-root user or by removing the suid bit on the executable.
xterm Xt error: Can't open display: %s
root@myserver:~#</pre>
<p>Some googling lead me to <a title="X11 when issuing sudo" href="http://www.lalitkapoor.com/blog/2010/01/08/x11-forwarding-when-issuing-sudo-su/">this blog post</a> which didn&#8217;t work for me, but put me on the right path.</p>
<p>Here&#8217;s the line I ended up using</p>
<pre>su - non_root_user_name -c "xauth list"  | xargs -n 3 xauth add</pre>
<p>If you need to use this frequently you could add an alias to you /root/.bashrc file</p>
<pre>alias xwork='su - non_root_user_name -c "xauth list"  | xargs -n 3 xauth add'</pre>
<p>That way you can just run xwork when you need X11 working.</p>
]]></content:encoded>
			<wfw:commentRss>http://stuporglue.org/make-x11-work-through-sudo-su/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Bulk DVD Creation With Tovid</title>
		<link>http://stuporglue.org/bulk-dvd-creation-with-tovid/</link>
		<comments>http://stuporglue.org/bulk-dvd-creation-with-tovid/#comments</comments>
		<pubDate>Fri, 16 Dec 2011 11:50:59 +0000</pubDate>
		<dc:creator>stuporglue</dc:creator>
				<category><![CDATA[Computers]]></category>
		<category><![CDATA[Something Interesting]]></category>
		<category><![CDATA[bulk DVD converting]]></category>
		<category><![CDATA[dvd]]></category>
		<category><![CDATA[ffmpeg]]></category>
		<category><![CDATA[forking]]></category>
		<category><![CDATA[pcntl_fork example]]></category>
		<category><![CDATA[pcntl_wexitstatus]]></category>
		<category><![CDATA[pcntl_wexitstatus example]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[split a file with ffmpeg]]></category>
		<category><![CDATA[tovid]]></category>
		<category><![CDATA[VHS to DVD]]></category>

		<guid isPermaLink="false">http://stuporglue.org/?p=1212</guid>
		<description><![CDATA[Today I&#8217;m going to show you how I automated the DVD creation part of the VHS home video conversion process. I&#8217;m assuming you have already captured the video to your computer somehow, and just want to make DVDs with your &#8230; <a href="http://stuporglue.org/bulk-dvd-creation-with-tovid/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Today I&#8217;m going to show you how I automated the DVD creation part of the VHS home video conversion process. I&#8217;m assuming you have already captured the video to your computer somehow, and just want to make DVDs with your captured video.</p>
<p>Note: All of these tools are Linux command line tools. Less convenient than a GUI, but much more appropriate for bulk operations and scripting.</p>
<h2>Preparing the Videos</h2>
<p>Tovid can take any video format and make a DVD from it by calling ffmpeg and other libraries when it needs to. In practice, I find that tovid and ffmpeg fight like sibling when they get along it&#8217;s true love, but when they fight things get broken.</p>
<p>Encoding is the slowest part of making a DVD, so we&#8217;ll do it separately. This way we can tweak the DVD quickly without re-encoding each time. FFMpeg can do it with the command:</p>
<pre>ffmpeg -i SourceFile.whatever -target ntsc-dvd outputfile.mpg</pre>
<p>This will produce mpeg2 files which don&#8217;t need to be further encoded in order to be put on a DVD.</p>
<h3>Splitting Videos</h3>
<p>If the resulting outputfile.mpg is too big for a DVD, you will need to split it. FFMpeg can split your file using a starting second (-ss) and a time (-t).  With this syntax you can break your file into as many pieces as you need to fit them on a DVD.</p>
<pre>ffmpeg -i SourceFile.whatever -ss 0 -t 7200 -target ntsc-dvd outputfile-part1.mpg
ffmpeg -i SourceFile.whatever -ss 7201 -target ntsc-dvd outputfile-part2.mpg</pre>
<h2>Creating the Metadata</h2>
<p>Tovid will want a couple of pieces of metadata, depending on what menu options you choose. Main titles, submenu titles and a disc title.</p>
<p>Since I was digitizing family videos, I wanted the disc title to be the years spanned. So I created a CSV file with 4 columns. the year would become the disc title.</p>
<pre>filename,year,long description,short description
SourceFile.whatever,1990,The family goes sledding at Ice Hill,Sledding</pre>
<h2>The Tovid Wrapper Script</h2>
<p>I know Tovid is already a wrapper for dvdauthor, ffmpeg and whatever other tools it uses behind the scenes, but what&#8217;s another layer, right?</p>
<p>I put all of my videos, the CSV file and this script in a folder:</p>
<pre>#!/usr/bin/env php
&lt;?php

/*
 * @brief Make as few DVDs as needed to fit all of the videos listed in a CSV file
 *
 * Copyright Michael Moore &lt;stuporglue@gmail.com&gt;
 * This script assumes that
 *      * All of the videos have already been encoded into mpeg2 format for DVD
 *      * All of the videos paths are relatie to INPUTDIR
 *
 * As many videos are fit into one DVD as possible. Videos are added in the order
 * listed in the CSV file. If a video is too big for a DVD the script will tell
 * you, and then exit. DVDs are named either "year", "year1 - year2" or
 * "year disc n", using the year from the CSV.
 *
 * Requires php-cli installed so that we can fork processes.
 *
 * Our metadata.csv file format:
 * pathtofile,year,looooooooooooong title here,short title
 *
 * We assume the file has a header row.
 */

//
//         Settings
//
define('DVDSIZE',8500000000); // Our estimate of the number of bytes available on a DVD+R DL
define('MENUOVERHEAD',45*1024*1024); // Generous leeway for the menu system
define('INPUTDIR',"/home/myuser/videos/input/"); // starting point for all file paths. Use / for abs. paths
define('TMPDIR','/tmp/tovid/'); // Where do you want the tmp files?
define('OUTDIR',"/home/myuser/videos/DVDs/"); // Destination?
define('MAXCHILDREN',5); // How many tovids to run simultaneously? Probably 1-2 less than the # of cores available?
$fh = fopen(INPUTDIR.'/metadata.csv','r');

//
//         No Lifeguard on duty!
//
@mkdir(TMPDIR);
chdir(TMPDIR); // Tovid likes to dump in the cwd. Chdir so that tovid dumps its temp files somewhere usefulish

define('MAXVOB',1073709056); // The max size of a single VOB
global $pids;
$pids = Array();

$currentdvdsize = MENUOVERHEAD;
$currentdvdfiles = Array();
fgetcsv($fh); // remove header from csv file
while($file = fgetcsv($fh)){

    // Fast check!
    if((filesize(INPUTDIR .'/'. $file[0]) + $currentdvdsize) &gt; DVDSIZE){
    makeDVD($currentdvdfiles); // Make a DVD!

    $currentdvdfiles = Array(); // Reset!
    $currentdvdsize = MENUOVERHEAD;
    }

    $currentdvdfiles[] = $file;

    // Calculate how much space this video will take on the disc
    // dvdauthor seems to:
    // 1) Never mix videos in the same VOB
    // 2) Make all VOBs except the last one come out to MAXVOB size (the last one can be whatever size smaller than MAXVOB)
    $currentdvdsize += (MAXVOB * ceil(filesize(INPUTDIR .'/'. $file[0])/MAXVOB));

    if($currentdvdsize &gt; DVDSIZE){
    die("It looks like {$file[0]} is too big to fit on a DVD!\n");
    }
}

// Make any remnants
if(count($currentdvdfiles) &gt; 0){
    makeDVD($currentdvdfiles);
}

function makeDVD($files){
    global $pids;

    // Make title
    $last = count($files) - 1;
    if($files[0][1] != $files[$last][1]){
    $title = "{$files[0][1]} - {$files[$last][1]}";
    }else{
    $title = "{$files[0][1]}";
    }

    $count = 2;
    $origtitle = $title;
    while(file_exists(OUTDIR . "/$title")){
    $title = "$origtitle disc $count";
    $count++;
    }

    // List of files
    $input = Array();
    $shorttitles = Array();
    $fulltitles = Array();
    foreach($files as $file){
    $input[] = INPUTDIR . "/{$file[0]}";
    $fulltitles[] = $file[2];
    $shorttitles[] = $file[3];
    }

    $cmd = "tovid disc
    -files  " . implode(" ",array_map('escapeshellarg',$input)) . "
    -titles " . implode(' ',array_map('escapeshellarg',$shorttitles)) . "
    -menu-title " . escapeshellarg($title) . "
    -menu-fontsize 18
    -title-color '#ff7700'
    -title-stroke black
    -titles-fontsize 18
    -titles-color '#ff7700'
    -showcase-titles-align east
    -rotate 5
    -wave default
    -submenus
    -submenu-titles " . implode(' ',array_map('escapeshellarg',$fulltitles)) . "
    -submenu-title-color '#ff7700'
    -submenu-stroke black
    -loop 0
    -submenu-length 20
    -noask
    -out " . escapeshellarg(OUTDIR . "/$title");

    // Now wait for our turn...
    while(count($pids) &gt;= MAXCHILDREN){
    $status = NULL;
    $exited_pid = pcntl_wait($status);            
    if(pcntl_wexitstatus($status) != 0){
        print "FAILURE IN " . TMPDIR . "/tovid.$exited_pid!!!\n{$pids[$exited_pid]}\n";
    }
    unset($pids[$exited_pid]);
    }

    $cmd = str_replace("\n",' ',$cmd);

    print "LAUNCHING!!!\n$cmd\n";

    $pid = pcntl_fork();
    if($pid == -1){
    die("COULDNT FORK!");
    }else if($pid){
    $pids[$pid] = $cmd;
    sleep(3); // give tovid a chance to claim its temp directories
    }else{
    $cmd .= " &gt; " . TMPDIR . "/tovid." . getmypid() . " 2&gt;&amp;1";
    exec($cmd);
    exit();
    }
}</pre>
<h2>Using the Script</h2>
<p>Save the PHP script above to a file named tovidBatch.php and make it executable.</p>
<p>Edit the defined constants to fit your directories and output media (DVDSIZE). If desired, edit the <em>tovid disc</em> command to build your DVDs the way you want them.</p>
<p>Finally, run (on a command line)</p>
<pre>php tovidBatch.php</pre>
<p>Your videos are on their way!</p>
<figure id="attachment_1214" aria-labelledby="figcaption_attachment_1214" class="wp-caption aligncenter" style="width: 580px"><a href="http://stuporglue.org/bulk-dvd-creation-with-tovid/sample_dvd_menu/" rel="attachment wp-att-1214"><img class="size-medium wp-image-1214" title="Sample DVD Menu" src="http://stuporglue.org/wp-content/uploads/2011/12/sample_dvd_menu-570x427.png" alt="Sample DVD Menu" width="570" height="427" /></a><figcaption id="figcaption_attachment_1214" class="wp-caption-text">Sample DVD Menu</figcaption></figure>
]]></content:encoded>
			<wfw:commentRss>http://stuporglue.org/bulk-dvd-creation-with-tovid/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Don&#8217;t buy HP laptops</title>
		<link>http://stuporglue.org/dont-buy-hp-laptops/</link>
		<comments>http://stuporglue.org/dont-buy-hp-laptops/#comments</comments>
		<pubDate>Sun, 04 Dec 2011 04:27:40 +0000</pubDate>
		<dc:creator>stuporglue</dc:creator>
				<category><![CDATA[Computers]]></category>
		<category><![CDATA[Something Interesting]]></category>
		<category><![CDATA[dv7 broken]]></category>
		<category><![CDATA[dv7 overheating]]></category>
		<category><![CDATA[dv7 review]]></category>
		<category><![CDATA[HP]]></category>
		<category><![CDATA[HP Pavilion dv7]]></category>
		<category><![CDATA[hp review]]></category>
		<category><![CDATA[pavilion]]></category>

		<guid isPermaLink="false">http://stuporglue.org/?p=1202</guid>
		<description><![CDATA[I bought an HP Pavilion dv7 (dv7t-4100) a little less than a year ago (March, 2011) It has been a big disapointment and I do not plan on buying an HP laptop again and can&#8217;t recommend that anyone else buy &#8230; <a href="http://stuporglue.org/dont-buy-hp-laptops/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>I bought an HP Pavilion dv7 (dv7t-4100) a little less than a year ago (March, 2011)</p>
<p>It has been a big disapointment and I do not plan on buying an HP laptop again and can&#8217;t recommend that anyone else buy one either.</p>
<p>I chose the dv7 because I needed something with some processing power. My dv7 has anIntel i7 clocked at 1.87Ghz, with 6 gigs of RAM.</p>
<h3>Update (January, 23, 2011)</h3>
<p>The screw that was loose on the case eventually came out. Within a day the hinge near where the screw fell out of broke, and I could hear a piece of metal shaking around inside the case. The warranty expires at the end of March.</p>
<p>The HP support person I spoke with was professional in the way that a police officer who pulls you over for speeding is professional. He kept trying to get me to say that I had dropped, bumped or otherwise abused the laptop and warned me multiple times that if I sent it in and they found signs of abuse that I would have to pay for repairs.</p>
<p>Still, 50 minutes later and a box was on its way to my house. The box took 2 days to get here, 2 days to get to HP, and they must&#8217;ve fixed it that day because I got it back 3 days after that.</p>
<p>I had only complained about the hinge. The loose power plug and missing keyboard/worn off keyboard I had chalked up to wear-and-tear and didn&#8217;t expect them to fix under warranty, but they did.</p>
<p>So, do I love HP laptops now? No, but I&#8217;m happy with how their warranty service turned out, and hopeful that I won&#8217;t have these problems once the warranty expires!</p>
<p>&nbsp;</p>
<h2>The HP Pavilion dv7 Overheats</h2>
<p>Unfortunately, the dv7 is inadequately cooled. When I start the computer temperature of the computer quicky reaches 75-78 degress (Celcius) and the fans are running loudly. As soon as I start a couple of desktop applications (Firefox, Pidgin, Thunderbird and a terminal) the temperature reaches about 85.</p>
<p>When I got this machine, I was excited because from time to time I rip VHS tapes (old homevideos) and convert them to DVDs for family members. The encoding process is long and slow, and I expected to benefit from all 4 cores.</p>
<p>Sadly, I can&#8217;t even run a video encoder at full speed on a single core without the computer overheating, and shutting down.  The computer shuts down if it reaches 100 degrees. I have to run ffmpeg (or mencoder) at 55% CPU usage (of one core) in oder for the fans to keep up with the CPU.</p>
<p>I&#8217;m effectivly stuck with a single core 1.03 GHz (55% of a single 1.87GHz core) computer since I can&#8217;t use more than that at once. Extremely disapointing.</p>
<pre>TIP: If you're in a similar situation, and you're on Linux, you
can use the program <a title="CPULimit Saved My Life" href="http://cpulimit.sourceforge.net/" target="_blank">cpulimit</a> throttle a specific process or
program. When I try to encode video on Windows (in iTunes) the
computer overheats no matter what I do.</pre>
<p><a href="http://stuporglue.org/dont-buy-hp-laptops/screenshot-12032011-095250-pm/" rel="attachment wp-att-1204"><img class="aligncenter size-medium wp-image-1204" title="Screenshot - 12032011 - 09:52:50 PM" src="http://stuporglue.org/wp-content/uploads/2011/12/Screenshot-12032011-095250-PM-570x320.png" alt="" width="570" height="320" /></a></p>
<p>NOTE #1:  This happens in both Windows AND Linux, so it&#8217;s not some weird Linux bug. I nearly always use my laptop on a hard surface &#8212; and I have to have it on something hard with great airflow if I want to even think about encoding a video.</p>
<p>NOTE #2: My fans and vents are clean. I can see through them to the fans, and have used compressed air on them anyways just in case.</p>
<h2>The HP Pavilion dv7 Has a Low Quality Keyboard</h2>
<p>I admit that I use  my computer more than most people do. Between work and hobbies, it&#8217;s not unusual for me to be on my computer 10-12 hours a day, and often more.</p>
<p>This laptop however, is the first one that I have ever worn the letters off the keys. It is also the first one that I have ever had a key spring break during normal use.</p>
<p>Luckly the dv7 comes with a numeric keypad which I don&#8217;t use, so I took the keyspring from one of those keys and repaired my broken key.</p>
<p>Again, very disapointing</p>
<h2>The HP Pavilion dv7 Case is Poorly Built</h2>
<p>Within a few months of getting my dv7 I noticed that the screen wouldn&#8217;t stay in position. It would always settle back an extra inch from where I had pushed it. It turned out that the top plastic piece of the case was coming loose from the bottom piece, which in turn left the lid hinge a little loose.</p>
<p>I tightened the screw on the case, and have had to tighten it again every couple of months.</p>
<h2>I&#8217;m Done Whining Now</h2>
<p>I don&#8217;t want/need a replacement from HP badly enough to talk to someone in a call center. I&#8217;m just going to keep using this computer until it dies. I&#8217;m betting it doesn&#8217;t make it a year. My guess is that the threads for the case screw eventually wear out and the screen won&#8217;t stay up.</p>
<p>I have a rock-solid reliable P4 desktop computer (an HP, no less!) I can use if this thing goes, and next time I&#8217;ll buy something more reliable, like a Thinkpad or a Mac Book.</p>
<p>So, if you&#8217;re in the market for a laptop, use extreme caution when buying an HP Pavilion. You might not get as good of a machine as you expect.</p>
]]></content:encoded>
			<wfw:commentRss>http://stuporglue.org/dont-buy-hp-laptops/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Debugging MongoDB Problems In PHP</title>
		<link>http://stuporglue.org/debugging-mongodb-problems-in-php/</link>
		<comments>http://stuporglue.org/debugging-mongodb-problems-in-php/#comments</comments>
		<pubDate>Wed, 30 Nov 2011 08:30:10 +0000</pubDate>
		<dc:creator>stuporglue</dc:creator>
				<category><![CDATA[Computers]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[code]]></category>
		<category><![CDATA[debugging]]></category>
		<category><![CDATA[logging]]></category>
		<category><![CDATA[mongo]]></category>
		<category><![CDATA[mongodb]]></category>
		<category><![CDATA[mongodb error]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[shim]]></category>

		<guid isPermaLink="false">http://stuporglue.org/?p=1191</guid>
		<description><![CDATA[Coming from the MySQL world, MongoDB presented some challenges to my workflow. With MySQL I usually would create a class or at least a function to pass my queries to so that if I had SQL problems, I had a &#8230; <a href="http://stuporglue.org/debugging-mongodb-problems-in-php/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Coming from the MySQL world, MongoDB presented some challenges to my workflow. With MySQL I usually would create a class or at least a function to pass my queries to so that if I had SQL problems, I had a single place to go and put a print statement to see the exact query I was trying to run.</p>
<p>It also gave me a single place to put a try/catch block with a debug_print_backtrace to figure out where the offending query was coming from. In my admitedly brief experience with MongoDB so far, Mongo doesn&#8217;t lend itself to such practices quite as easily.</p>
<p>With this project I&#8217;ve been working on at work we&#8217;ve got 34,000 lines of code. We have a global $mongo object that we run queries against. When a Mongo query chokes up our site I need to know what exact call has been made, with what arguments and coming from where.</p>
<p>So I wrote this shim, MongoLogger that would pretends to be Mongo. During development I replace the $mongo object with my MongoLogger object, like so:</p>
<pre>$mongodb = new Mongo("mongodb://un:pw@host/db"); // Connect to Mongo
$mongo = $mongodb-&gt;db;                   // pick your database
$mongo = new MongoLogger($mongo); // Replace the $mongo</pre>
<p>MongoLogger then uses the PHP magic methods __call and __get and the almost magic method call_user_func_array to do the needed logging, and to pass the arguments on to the real $mongo object.</p>
<p style="padding-left: 30px;"><strong>Note: Magic Methods, an call_user_func_array in particular, are slow. Not recommended for production use!</strong></p>
<p>This is simpler than what I wrote for work, but the concept is the same and the important bits are here. It&#8217;s what I wish I could&#8217;ve found when I first started worrying about how to debug MongoDB calls.</p>
<pre>&lt;?php

class MongoLogger{
    /**
     * @class MongoLogger
     *
     * @brief A debugging shim between your code and MongoDB. Should be disbaled for production
     *
     * Usage:
     * $mongodb = new Mongo("mongodb://un:pw@host/db"); // Connect to Mongo
     * $mongo = $mongodb-&gt;db;                           // pick your database
     * $mongo = new MongoLogger($mongo);                // and all calls made to the original $mongo just keep working...
     *
     * YAY MAGIC METHODS! http://php.net/manual/en/language.oop5.magic.php
     * Makes use of magic methods __construct, __call and __get and the almost magic method call_user_func_array.
     */

    var $collections = Array();

    function __construct($mongo){
    global $real_mongo;
    $real_mongo = $mongo;
    }

    public function __call($name,$args){
    global $real_mongo;

    error_log("Mongo-&gt;$name called with " . print_r($args,TRUE));

    try {
        $res = call_user_func_array(Array($real_mongo,$name),$args);
        return $res;
    } catch (Exception $e){
        debug_print_backtrace();
        error_log(print_r($e,TRUE));
        throw $e;
    }
    }

    /**
     * @brief Return a MongoLoggerCollection for the requested collection
     * This is the main function that gets called in this class. We just pretend that
     * we've got whatever we're asked for (just like Mongo does)
     *
     */
    public function __get($name){
    if(!array_key_exists($name,$this-&gt;collections)){ $this-&gt;collections[$name] = new MongoLoggerCollection($name); }
    return $this-&gt;collections[$name];
    }
}

class MongoLoggerCollection {
    /**
     * @class MongoLoggerCollection
     *
     * @brief Represents a collection in Mongo. Most of your calls will come through here.
     */
    var $collection;

    function __construct($name){
    $this-&gt;collection = $name;
    }

    public function __call($name,$args){
    global $real_mongo;

    // Logging!
    $debug = debug_backtrace();
    $caller = $debug[1];
    error_log($caller['file'] . ':' . $caller['line']);
    error_log("\$mongo-&gt;{$this-&gt;collection}-&gt;$name(");
    foreach($args as $arg){ error_log(print_r($arg,TRUE)); }
    error_log(")");

    try {
        $res = call_user_func_array(Array($real_mongo-&gt;{$this-&gt;collection},$name),$args);
        return $res;
    } catch (Exception $e){
        debug_print_backtrace();
        error_log(print_r($e,TRUE));
        throw $e;
    }
    }
}</pre>
]]></content:encoded>
			<wfw:commentRss>http://stuporglue.org/debugging-mongodb-problems-in-php/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

