<?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, 24 Jan 2012 04:18:54 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<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>0</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>
		<item>
		<title>PHP Protocol Buffer to MySQL (and back!) bridge</title>
		<link>http://stuporglue.org/protocol-buffer-to-mysql-bridge/</link>
		<comments>http://stuporglue.org/protocol-buffer-to-mysql-bridge/#comments</comments>
		<pubDate>Thu, 27 Oct 2011 20:19:58 +0000</pubDate>
		<dc:creator>stuporglue</dc:creator>
				<category><![CDATA[Computers]]></category>
		<category><![CDATA[Digitization]]></category>
		<category><![CDATA[Projects]]></category>
		<category><![CDATA[Something Interesting]]></category>

		<guid isPermaLink="false">http://stuporglue.org/?p=1181</guid>
		<description><![CDATA[Protocol Buffers are a binary data transfer protocol from Google. Google officially supports C++, Java and Python. There are 3rd party libraries that support other languages. I previously mentioned several that support PHP, including the one that we&#8217;re using at &#8230; <a href="http://stuporglue.org/protocol-buffer-to-mysql-bridge/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p><a title="Protocol Buffers" href="http://code.google.com/p/protobuf/" target="_blank">Protocol Buffers</a> are a binary data transfer protocol from Google. Google officially supports C++, Java and Python. There are 3rd party libraries that support other languages. I <a title="PHP Protocol Buffer Libraries" href="http://stuporglue.org/using-google-protcol-buffers-with-php-and-protoc-gen-php/ " target="_blank">previously mentioned</a> several that support PHP, including the one that we&#8217;re using at work, <a title="Protoc-gen-php" href="https://github.com/stuporglue/protoc-gen-php" target="_blank">protoc-gen-php</a>.</p>
<p>One challenge that we faced was storing our data. Should we store our data and convert to Protocol Buffers every time we sent it, or should we just work in Protocol Buffers and store it to the database directly?</p>
<p>We decided to store it in the database in a format compatible with the Protocol Buffer classes so we could easily access it as a Protocol Buffer object again later.</p>
<p>The following classes and scripts were written to help make that bridge between the Protocol Buffer classes generated by protoc-gen-php and MySQL.</p>
<p>They perform two main functions:</p>
<ol>
<li>Generate MySQL table create statements to build tables to hold the protocol buffer data</li>
<li>Make classes which extend the protoc-gen-php classes with extra functions for database storage and retrieval (and a few bonuses)</li>
</ol>
<h2> Generating The MySQL</h2>
<p>We&#8217;ll start by generating some tables for our database. You&#8217;ll need php-cli installed, and you&#8217;ll need protoParser.php and protoMySQL.php in your PHP include path (or current directory) and makeMysql.php in your $PATH (or current directory).</p>
<p>Edit protoMySQL.php&#8217;s preferences (lines 17-36) to suit your configuration and needs.</p>
<p>Now something as simple as:</p>
<pre>php ./makeMysql.php *.proto</pre>
<p>should generate the MySQL table create statements you will need.</p>
<h2>Generate The DB Classes</h2>
<p>With protoc-gen-php, each .proto object gets a corresponding class. eg. list.proto.php is created from list.proto</p>
<p>makeClasses.php creates listDB.php which extends the classes generated by protoc-gen-php. Each proto object gets its own protoDB class and file.</p>
<pre>php ./makeClasses.php *.proto</pre>
<p>Those classes should then be used instead of their original non-database supporting proto classes.</p>
<h2>DB Class Functions</h2>
<p><strong>__construct($id_or_object = NULL, $limit = PHP_INT_MAX)</strong></p>
<p><em>$id_or_object</em><br />
If it&#8217;s an object, we assume it&#8217;s a non-database variant, and use its members to populate this object.<br />
If it&#8217;s numeric, we fetch the object from the database<br />
Otherwise, we pass it up to the parent object.</p>
<p><em>$limit</em><br />
Used in the parent constructor</p>
<p><strong>get($id = NULL, $args = NULL)</strong><br />
<em>$id</em><br />
The database ID of the object to fetch.</p>
<p><em>$args</em><br />
Enough MySQL arguments to uniquely identify the proto to retrieve.<br />
If an array, each field is added to the query.<br />
If a string, the string is appended to the query as is, after the WHERE clause.</p>
<p>Returns object if found, NULL if not found (or if multiples found)</p>
<p><strong>unique()</strong><br />
Calls array_unique on any repeating elements.<br />
Note: Arrays of objects are compared using their __toString methods</p>
<p>Returns nothing</p>
<p><strong>load($object)</strong></p>
<p>If you have an equivalent object (eg. a non-database or database version of the object) you can load it into the current object with this method.</p>
<p>Returns nothing</p>
<p><strong>nullOrVal($val)</strong><br />
Determine if we should append NULL or an escaped string. Used in MySQL queries to ensure safe values.</p>
<p>Returns &#8220;NULL&#8221; or a mysql_real_escape&#8217;d string</p>
<p><strong>delete()</strong><br />
Shallow delete from database. Since sub-objects could be shared/referenced by other proto objects this only deletes this object&#8217;s entry in the database</p>
<p>Returns nothing</p>
<p><strong>put()</strong><br />
INSERT or UPDATE this object in the database.</p>
<p>Returns the insert ID of the object</p>
<p><strong>toJSON($asArray = FALSE)</strong><br />
Returns a JSON representation of the current object, or an array appropriate for use in json_encode.</p>
<p><strong>fromJSON($json)</strong><br />
Load the variables in the object from a JSON string</p>
<p><strong>purge()</strong><br />
Like delete, but does delete referenced proto objects from database.<br />
Returns number of sub-objects deleted.</p>
<h2>License, Warranty and Support</h2>
<p>My employers have been kind enough to let me release these scripts under and Open Source license. They are released under the GPL v.2 without any warranty.</p>
<p>We are actually switching away from MySQL on this particular project, and so these scripts are unlikely to receive any further updates.</p>
<p>I will provide such support as I have time for through the comments on this blog post.</p>
<p>Happy programming!</p>
]]></content:encoded>
			<wfw:commentRss>http://stuporglue.org/protocol-buffer-to-mysql-bridge/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Automatically Orient Scanned Photos With OpenCV</title>
		<link>http://stuporglue.org/automatically-orient-scanned-photos-correctly-with-opencv/</link>
		<comments>http://stuporglue.org/automatically-orient-scanned-photos-correctly-with-opencv/#comments</comments>
		<pubDate>Sat, 22 Oct 2011 21:21:51 +0000</pubDate>
		<dc:creator>stuporglue</dc:creator>
				<category><![CDATA[Computers]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[facial detection]]></category>
		<category><![CDATA[opencv]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[scanning]]></category>

		<guid isPermaLink="false">http://stuporglue.org/?p=1147</guid>
		<description><![CDATA[Most pictures taken these days are digital, and include information inside the picture&#8217;s exif tags about the correct orientation. For these types of pictures you can simply use exifautotran and you&#8217;re done. Easy Peasy. What about pictures that don&#8217;t have &#8230; <a href="http://stuporglue.org/automatically-orient-scanned-photos-correctly-with-opencv/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Most pictures taken these days are digital, and include information inside the picture&#8217;s exif tags about the correct orientation. For these types of pictures you can simply use exifautotran and you&#8217;re done. Easy Peasy.</p>
<p>What about pictures that don&#8217;t have exif info? Maybe you&#8217;ve got an old/cheap camera, scanned images, or another source that dosn&#8217;t include the orientation information. I&#8217;m in the middle of scanning about 5000 pictures, and I didn&#8217;t want to manually rotate all the images&#8230;so I started looking for solutions.</p>
<h2>OpenCV: Open Source Computer Vision</h2>
<p>I figured if I could detect a face in a photo, then it was right side up. I started searching for solutions and the Internet seemed to agree that the best free solution for facial detection was OpenCV, but I couldn&#8217;t find a script that did just what I wanted.</p>
<p><a title="OpenCV" href="http://opencv.willowgarage.com/wiki/" target="_blank">OpenCV &#8220;is a library of programming functions for real time computer vision.&#8221;</a> It can be used in robotics, for doing cool stuff with web cams, motion detection, and more.  I was mainly interested in because of its facial recognition features.</p>
<p>Then I found a <a title="Face Detection With OpenCV" href="http://blog.jozilla.net/2008/06/27/fun-with-python-opencv-and-face-detection/" target="_blank">this script by Jo Vermeulen</a> from way back in 2008. It&#8217;s made for use with webcams, and the particular script is just a toy. He does have<a title="Face Detection And DBus" href="http://jozilla.net/Software/Facade" target="_blank"> another script</a> which interacts with DBus to provide present/away status changes for your IM client. With just a little bit of work I was able to repurpose his script to do what I needed.</p>
<h2>Whatsup: An OpenCV Python Script To Detect Correct Photo Orientation</h2>
<p>Prerequisites:</p>
<ul>
<li> Install libcv2.1</li>
<li>Install python-opencv</li>
<li>Download one or more Haar Cascades and save them to /usr/local/share/ (more on this below)</li>
</ul>
<p>The script I made is called <em>whatsup</em>.</p>
<pre>Usage: whatsup [--debug] path_to_file</pre>
<p>whatsup  returns the number of degrees it thinks your photo needs to be rotated to be right side up.  You can then use that number of degrees in whatever program you want, be it imagemagick, jpegtran, gd, or whatever. whatsup assumes that your image is squared up (not square) and only returns multiples of 90 (0,90,180,270).</p>
<p>Using the &#8211;debug option will show the image with a box around the feature that gave the whatsup the final result.</p>
<pre>#!/usr/bin/env python

# This script reads in a file and tries to determine which orientation is correct
# by looking for faces in the photos
# It starts with the existing orientation, then rotates it 90 degrees at a time until
# it has either tried all 4 directions or until it finds a face

# INSTALL: Put the xml files in /usr/local/share, or change the script. Put whatsup somewhere in your path

# Usage: whatsup [--debug] filename
# Returns the number of degrees it should be rotated clockwise to orient the faces correctly

# Some code came from here: http://blog.jozilla.net/2008/06/27/fun-with-python-opencv-and-face-detection/
# The rest was cobbled together by me from the documentation here [1] and from snippets and samples found via Google
# [1] http://opencv.willowgarage.com/documentation/python/core_operations_on_arrays.html#createmat

import sys
import os
import cv

def detectFaces(small_img,loadedCascade):
    tries = 0 # 4 shots at getting faces. 

    while tries < 4:
	faces = cv.HaarDetectObjects(small_img, loadedCascade, cv.CreateMemStorage(0), scale_factor =1.2, min_neighbors =2, flags =cv.CV_HAAR_DO_CANNY_PRUNING)
	if(len(faces) > 0):
	    if(sys.argv[1] == '--debug'):
		for i in faces:
		    cv.Rectangle(small_img, (i[0][0],i[0][1]),(i[0][0] + i[0][2],i[0][1] + i[0][3]), cv.RGB(255,255,255), 3, 8, 0)
		cv.NamedWindow("Faces")
		cv.ShowImage("Faces",small_img)
		cv.WaitKey(1000)
	    return tries * 90

	# The rotation routine:
	tmp_mat = cv.GetMat(small_img)
	tmp_dst_mat = cv.CreateMat(tmp_mat.cols,tmp_mat.rows,cv.CV_8UC1) # Create a Mat that is rotated 90 degrees in size (3x4 becomes 4x3)
	dst_mat = cv.CreateMat(tmp_mat.cols,tmp_mat.rows,cv.CV_8UC1) # Create a Mat that is rotated 90 degrees in size (3x4 becomes 4x3)

	# To rotate 90 clockwise, we transpose, then flip on Y axis
	cv.Transpose(small_img,tmp_dst_mat) # Transpose it
	cv.Flip(tmp_dst_mat,dst_mat,flipMode=1) # flip it

	# put it back in small_img so we can try to detect faces again
	small_img = cv.GetImage(dst_mat)
	tries = tries + 1
    return False 

# Detect which side of the photo is brightest. Hopefully it will be the sky.
def detectBrightest(image):
    image_scale = 4 # This scale factor doesn't matter much. It just gives us less pixels to iterate over later
    newsize = (cv.Round(image.width/image_scale), cv.Round(image.height/image_scale)) # find new size
    small_img = cv.CreateImage(newsize, 8, 1)
    cv.Resize( image, small_img, cv.CV_INTER_LINEAR )

    # Take the top 1/3, right 1/3, etc. to compare for brightness
    width = small_img.width
    height = small_img.height
    top = small_img[0:height/3,0:width]
    right = small_img[0:height,(width/3*2):width]
    left = small_img[0:height,0:width/3]
    bottom = small_img[(height/3*2):height,0:height]

    sides = {'top':top,'left':left,'bottom':bottom,'right':right}

    # Find the brightest side
    greatest = 0
    winning = 'top'
    for name in sides:
	sidelum = 0
	side = sides[name]
	for x in range(side.rows - 1):
	    for y in range(side.cols - 1):
		sidelum = sidelum + side[x,y]
	sidelum = sidelum/(side.rows*side.cols)
	if sidelum > greatest:
	    winning = name

    if(sys.argv[1] == '--debug'):
	if winning == 'top':
	    first = (0,0)
	    second = (width,height/3)
	elif winning == 'left':
	    first = (0,0)
	    second = (width/3,height)
	elif winning == 'bottom':
	    first = (0,(height/3*2))
	    second = (width,height)
	elif winning == 'right':
	    first = ((width/3*2),0)
	    second = (width,height)
	cv.Rectangle(small_img,first,second,cv.RGB(125,125,125),3,8,0)
	cv.NamedWindow("Faces")
	cv.ShowImage("Faces",small_img)
	cv.WaitKey(3000)

    returns = {'top':0,'left':90,'bottom':180,'right':270}

    # return the winner
    if sys.argv[1] == '--debug':
	print "The " + winning + " side was the brightest!"
    return returns[winning]

# Try a couple different detection methods
def trydetect():
    # Load some things that we'll use during each loop so we don't keep re-creating them
    grayscale = cv.LoadImageM(os.path.abspath(sys.argv[-1]),cv.CV_LOAD_IMAGE_GRAYSCALE) # the image itself

    # Get more at: https://code.ros.org/svn/opencv/tags/latest_tested_snapshot/opencv/data/haarcascades/
    cascades = ( # Listed in order most likely to appear in a photo
	    '/usr/local/share/haarcascade_frontalface_alt.xml',
	    '/usr/local/share/haarcascade_profileface.xml',
	    '/usr/local/share/haarcascade_fullbody.xml',
	    )

    for cascade in cascades:
	loadedCascade = cv.Load(cascade)
	image_scale = 4
	while image_scale > 0: # Try 4 different sizes of our photo
	    newsize = (cv.Round(grayscale.width/image_scale), cv.Round(grayscale.height/image_scale)) # find new size
	    small_img = cv.CreateImage(newsize, 8, 1 )
	    cv.Resize( grayscale, small_img, cv.CV_INTER_LINEAR )
	    returnme = detectFaces(small_img,loadedCascade)
	    if returnme is not False:
		return returnme

	    image_scale = image_scale - 1
    return detectBrightest(grayscale) # no faces found, use the brightest side for orientation instead

# Usage Check
if ((len(sys.argv) != 2 and len(sys.argv) != 3) or (len(sys.argv) == 3 and sys.argv[1] != '--debug')):
    print "USAGE: whatsup [--debug] filename"
    sys.exit(-1)

# Sanity check
if not os.path.isfile(sys.argv[-1]):
    print "File '" + sys.argv[-1] + "' does not exist"
    sys.exit(-1)

# Make it happen
print str(trydetect()),</pre>
<h2>About OpenCV Feature Detection</h2>
<p>In order to detect features, like faces, OpenCV needs to be trained. You then use the training file, called a Haar Cascade, to define the detection. OpenCV provides lots of different ready <a title="Haar Cascades for OpenCV" href="https://code.ros.org/svn/opencv/tags/latest_tested_snapshot/opencv/data/haarcascades/" target="_blank">training files here</a>.</p>
<p style="padding-left: 30px;"><strong>To use whatsup you&#8217;ll need to download one or more harrcascade*.xml files and put them in /usr/local/share (or edit whatsup to point at the place you decide to save them).</strong></p>
<p>What you need to be aware of is that detection works best at the resolution the training file was created for. So  if you&#8217;re using haarcascade_frontalface_default.xml then you want to be giving the detection 24&#215;24 pixel faces.</p>
<p>In order for this to happen, whatsup tries scaling the images to different sizes and tries detecting faces in those different sizes. I am using scans that are roughly 1200&#215;800 and so I start with a scaling factor of 4 so that the first image tried is 1/4th the size of the original. If your images are larger then you probably need to start with a larger scaling factor.</p>
<h2>What If No Faces/Features Are Detected</h2>
<p>If no features are detected in any of the image sizes, then whatsup determines which side of the photo is brightest, and returns the number of degrees needed to rotate the brightest side upwards.</p>
<p>The assumption is that if no people are found, then maybe it&#8217;s a landscape photo and the sky should go at the top.</p>
<p>There are lots of times where this is incorrect (eg. a lit ski hill at night), but for my photos it will be true more times than not</p>
<h2>How Accurate Is It / Improving Accuracy</h2>
<p>I am getting better than 80% accuracy, but probably not 90%.</p>
<p>The better you know your photos the smarter you can make the script for your use.  If you choose Haar Cascade files that are more applicable to your photo set you are less likely to get false positive.</p>
<p>If you know what sizes your faces typically are you can choose appropriate scales or order the image scaling to happen in the most likely order. You could even make your own hasscascade.xml files if you have certain features you want to look for.</p>
<h2>Whatsup in Daily Use</h2>
<p>I have actually incorporated whatsup into a script that gets run every time I scan something on my scanner, but this is the bash script I used for testing and developing it.</p>
<p>Make sure whatsup is in your path, and that you have jpegtran and jpegexiforient installed. Jpegtran does lossless jpeg rotations, jpegexiforient sets that missing Exif flag that lets programs know which way to display a photo.</p>
<p>Save this script in the same directory as your jpegs you want to test this on and run it.</p>
<pre>#!/bin/bash
for i in saved/*
do
    echo -n "Processing $i : "
    degrees=`whatsup $i`
    if [ $degrees -gt 0 ]
    then
	echo $degrees
	cp $i /tmp/tmp.jpg
        jpegtran -rotate $degrees /tmp/tmp.jpg > $i
	jpegexiforient -1 $i
	sleep 1
    else
	echo ""
    fi
done</pre>
<h2>Disclaimers</h2>
<p>While whatsup doesn&#8217;t modify your photo, any program you would use it with does, including the bash script above. Please make responsible use of backups and testing as I disclaim any liability for any lost data.</p>
<p>I&#8217;m not a pro python coder, so the script could probably be optimized somehow.</p>
]]></content:encoded>
			<wfw:commentRss>http://stuporglue.org/automatically-orient-scanned-photos-correctly-with-opencv/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Grandpa&#8217;s Tools</title>
		<link>http://stuporglue.org/grandpas-tools/</link>
		<comments>http://stuporglue.org/grandpas-tools/#comments</comments>
		<pubDate>Wed, 05 Oct 2011 07:39:18 +0000</pubDate>
		<dc:creator>stuporglue</dc:creator>
				<category><![CDATA[Moore's Ramblings]]></category>

		<guid isPermaLink="false">http://stuporglue.org/?p=1131</guid>
		<description><![CDATA[Grandpa could fix just about anything and had the tools to prove it. He had a large collection of tools for wood working, construction, electrical work, plumbing, masonry and more. When he died I inherited some of his tools. Most &#8230; <a href="http://stuporglue.org/grandpas-tools/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Grandpa could fix just about anything and had the tools to prove it. He had a large collection of tools for wood working, construction, electrical work, plumbing, masonry and more.</p>
<p>When he died I inherited some of his tools. Most of them live in my toolboxes in the garage, but these ones seemed old enough that I decided to make a display with them.</p>
<p>The display is hung above my computers in my office. The tools are all hung on pegboard just like my daily-use tools in the garage.</p>
<figure id="attachment_1135" aria-labelledby="figcaption_attachment_1135" class="wp-caption aligncenter" style="width: 580px"><a href="http://stuporglue.org/grandpas-tools/img_0977/" rel="attachment wp-att-1135"><img class="size-medium wp-image-1135" title="The tools are above my desk and computers" src="http://stuporglue.org/wp-content/uploads/2011/10/IMG_0977-570x427.jpg" alt="The tools are above my desk and computers" width="570" height="427" /></a><figcaption id="figcaption_attachment_1135" class="wp-caption-text">The tools are above my desk and computers</figcaption></figure>
<p>Roughly top to bottom, left to right:</p>
<p>A brace, a tape measure (inside the brace), monkey wrench, an awl, a folding ruler, a putty knife, a level, a ball-peen hammer, a pocket knife, book,  plumb bob, eggbeater drill, wire cutters, pipe wrench, needle nose pliers, screw driver, planer.</p>
<figure id="attachment_1134" aria-labelledby="figcaption_attachment_1134" class="wp-caption aligncenter" style="width: 580px"><a href="http://stuporglue.org/grandpas-tools/img_0979/" rel="attachment wp-att-1134"><img class="size-medium wp-image-1134" title="Some of grandpa's old tools" src="http://stuporglue.org/wp-content/uploads/2011/10/IMG_0979-570x427.jpg" alt="Some of grandpa's old tools" width="570" height="427" /></a><figcaption id="figcaption_attachment_1134" class="wp-caption-text">Some of grandpa&#39;s old tools</figcaption></figure>
<p>The book is The Complete Home Handyman&#8217;s Guide. I like it partly because it starts out with the very basics. Figure 1 is a diagram titled &#8220;Claw Hammer&#8221;.  Trying to imagine grandpa ever needing such basic instruction makes me smile. The book does get into all sorts of home repairs though and is actually still very applicable to the 1950s rambler I live in now.</p>
<figure id="attachment_1136" aria-labelledby="figcaption_attachment_1136" class="wp-caption aligncenter" style="width: 437px"><a href="http://stuporglue.org/grandpas-tools/img_0982/" rel="attachment wp-att-1136"><img class="size-medium wp-image-1136" title="IMG_The Complete Home Handyman's Guide" src="http://stuporglue.org/wp-content/uploads/2011/10/IMG_0982-e1317799689674-427x570.jpg" alt="IMG_The Complete Home Handyman's Guide" width="427" height="570" /></a><figcaption id="figcaption_attachment_1136" class="wp-caption-text">IMG_The Complete Home Handyman&#39;s Guide</figcaption></figure>
<p>Inside the cover is a short inscription showing that the book was given to grandpa by his father-in-law Walt Johnson.</p>
<p><a href="http://stuporglue.org/grandpas-tools/img_0983/" rel="attachment wp-att-1137"><img class="aligncenter size-medium wp-image-1137" title="IMG_0983" src="http://stuporglue.org/wp-content/uploads/2011/10/IMG_0983-570x427.jpg" alt="" width="570" height="427" /></a></p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://stuporglue.org/grandpas-tools/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>How to Turn a Drill Press into a Lathe</title>
		<link>http://stuporglue.org/how-to-turn-a-drill-press-into-a-lathe/</link>
		<comments>http://stuporglue.org/how-to-turn-a-drill-press-into-a-lathe/#comments</comments>
		<pubDate>Wed, 22 Jun 2011 04:39:15 +0000</pubDate>
		<dc:creator>stuporglue</dc:creator>
				<category><![CDATA[Projects]]></category>
		<category><![CDATA[Something Interesting]]></category>
		<category><![CDATA[Woodworking]]></category>
		<category><![CDATA[lathe]]></category>
		<category><![CDATA[woodworking]]></category>

		<guid isPermaLink="false">http://stuporglue.org/?p=1110</guid>
		<description><![CDATA[Lathes are pretty sweet. You can turn table legs, fancy lamps, cool wooden bowls, pens, baseball bats&#8230; so many fun things you can make! Unfortunately little ones start at $400-ish and ones big enough to turn table legs and baluster &#8230; <a href="http://stuporglue.org/how-to-turn-a-drill-press-into-a-lathe/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Lathes are pretty sweet. You can turn table legs, fancy lamps, cool wooden bowls, pens, baseball bats&#8230; so many fun things you can make! Unfortunately little ones start at $400-ish and ones big enough to turn table legs and baluster start at about 2 grand. Some day maybe I&#8217;ll pick up a used one, but for now I just wanted to try making some homemade fishing lures.</p>
<p>I started by considering what tools I have that spin. The main contenders were the router, the drill and the drill press. I decided that I use the drill too often to use it, and I thought that the router might spin too fast. For a test, I put a screw into a piece of wood and put the screw into the router (where the router bit goes) the screw folded over the the wood whipped around sideways. Not good.</p>
<p>So, I went with the drill press. This green machine is a reliable but dated 1982 1/3 HP 6 Amp motor drill press. The lid lifts up so you can manually switch the belt between gears to change the speeds.</p>
<figure id="attachment_1117" aria-labelledby="figcaption_attachment_1117" class="wp-caption aligncenter" style="width: 437px"><a rel="attachment wp-att-1117" href="http://stuporglue.org/how-to-turn-a-drill-press-into-a-lathe/img_0285/"><img class="size-medium wp-image-1117" title="The old drill press" src="http://stuporglue.org/wp-content/uploads/2011/06/IMG_0285-e1308714577997-427x570.jpg" alt="The old drill press" width="427" height="570" /></a><figcaption id="figcaption_attachment_1117" class="wp-caption-text">The old drill press</figcaption></figure>
<h2>Step 1: Do a Little Jig</h2>
<p>The first step was to turn the drill press sideways. I had to find a way to turn it sideways while supporting it securely and being able to access the power button. I was able to unscrew the arms of the drillpress height adjuster and then I built this:</p>
<figure id="attachment_1116" aria-labelledby="figcaption_attachment_1116" class="wp-caption aligncenter" style="width: 437px"><a rel="attachment wp-att-1116" href="http://stuporglue.org/how-to-turn-a-drill-press-into-a-lathe/jig/"><img class="size-medium wp-image-1116" title="A jig to use the drill press as a lathe" src="http://stuporglue.org/wp-content/uploads/2011/06/jig-e1308715117529-427x570.jpg" alt="A jig to use the drill press as a lathe" width="427" height="570" /></a><figcaption id="figcaption_attachment_1116" class="wp-caption-text">A jig to use the drill press as a lathe</figcaption></figure>
<p>This jig holds the drill press drilling axis horizontal, and gives makes the drill press stable while operating in a horizontal position.</p>
<h2>Step 2: Create a Dead Center</h2>
<p>I used a grinder and a file to make the end of a bolt rounded and smooth, this would be my dead center. A live center would be better, but I didn&#8217;t have any appropriate parts available.</p>
<h3>Failure Number 1 &#8211; Attaching Dead Center to a Wooden Platform</h3>
<p>While a live center would be preferable, I decided to start with a dead center for simplicity. Since I don&#8217;t have any welding equipment, I used a piece of 3/4 inch plywood and some bolts to secure and provide the dead center.</p>
<figure id="attachment_1115" aria-labelledby="figcaption_attachment_1115" class="wp-caption aligncenter" style="width: 580px"><a rel="attachment wp-att-1115" href="http://stuporglue.org/how-to-turn-a-drill-press-into-a-lathe/img_0282/"><img class="size-medium wp-image-1115" title="The makeshift lathe with a wooden platform for the dead center" src="http://stuporglue.org/wp-content/uploads/2011/06/IMG_0282-570x427.jpg" alt="The makeshift lathe with a wooden platform for the dead center" width="570" height="427" /></a><figcaption id="figcaption_attachment_1115" class="wp-caption-text">The makeshift lathe with a wooden platform for the dead center</figcaption></figure>
<p>It started out find, but after about 5 minutes of spinning a piece of wood, the bold had vibrated loose from the wood.</p>
<h3>Success! Bolting the Dead Center to The Drill Press Platform</h3>
<p>Although I lost about 2.5 inches in doing so, I next tried bolting the dead center directly to the adjustable drill press platform.</p>
<figure id="attachment_1113" aria-labelledby="figcaption_attachment_1113" class="wp-caption aligncenter" style="width: 580px"><a rel="attachment wp-att-1113" href="http://stuporglue.org/how-to-turn-a-drill-press-into-a-lathe/img_0311/"><img class="size-medium wp-image-1113" title="Dead Center Bolted To Drill Press Platform" src="http://stuporglue.org/wp-content/uploads/2011/06/IMG_0311-570x427.jpg" alt="Dead Center Bolted To Drill Press Platform" width="570" height="427" /></a><figcaption id="figcaption_attachment_1113" class="wp-caption-text">Dead Center Bolted To Drill Press Platform</figcaption></figure>
<h2>Failure Number 2:  The Chuck Falls Off</h2>
<p>After using the lathe for a short while, the chuck fell off. I pounded it on nice and hard, and have been using less lateral force. I hope that if I let the tools do more cutting and I do less pushing, it won&#8217;t be an ongoing problem.</p>
<h2>Step 3: Cutting Some Blanks</h2>
<p>I cut two blanks. One from a piece of lilac branch from the yard and one from a 2&#215;4 (pine). I used a table saw and cut each blank to be 1.75&#215;1.75&#215;5 inches.</p>
<h2>Step 4: Making Some Tools</h2>
<p>I needed some tools to cut the wood so I turned to the two pieces of metal I had on my workbench which I wasn&#8217;t going to be using anytime soon.</p>
<figure id="attachment_1114" aria-labelledby="figcaption_attachment_1114" class="wp-caption aligncenter" style="width: 437px"><a rel="attachment wp-att-1114" href="http://stuporglue.org/how-to-turn-a-drill-press-into-a-lathe/img_0319/"><img class="size-medium wp-image-1114" title="Wood Turning Tools From a Chisel and A Railroad Spike" src="http://stuporglue.org/wp-content/uploads/2011/06/IMG_0319-e1308717011313-427x570.jpg" alt="Wood Turning Tools From a Chisel and A Railroad Spike" width="427" height="570" /></a><figcaption id="figcaption_attachment_1114" class="wp-caption-text">Wood Turning Tools From a Chisel and A Railroad Spike</figcaption></figure>
<p>That&#8217;s right, a railroad spike. I have a handful of them, and I&#8217;m not sure that they&#8217;re good for anything else. As for the chisel, my grandpa had more chisels than he probably ever used, which means I have more than I know I will ever use. I sacrificed one of them to the grinder to make its cutting angle more appropriate for wood turning</p>
<h2>Results</h2>
<p>The pine (2&#215;4) wood didn&#8217;t turn out well at all, but I didn&#8217;t really expect it to, I mean it&#8217;s a 2&#215;4, not some nice wood. The lilac turned out pretty decent for a first time.  A little bit more drilling and fiddling later and I am the happy owner of a drill press lathe and two through-wire construction fishing plugs that need to be painted.</p>
<figure id="attachment_1112" aria-labelledby="figcaption_attachment_1112" class="wp-caption aligncenter" style="width: 580px"><a rel="attachment wp-att-1112" href="http://stuporglue.org/how-to-turn-a-drill-press-into-a-lathe/turned/">on <img class="size-medium wp-image-1112" title="My first two turned fishing plugs" src="http://stuporglue.org/wp-content/uploads/2011/06/turned-570x427.jpg" alt="My first two turned fishing plugs" width="570" height="427" /></a><figcaption id="figcaption_attachment_1112" class="wp-caption-text">My first two turned fishing plugs</figcaption></figure>
<p>&nbsp;</p>
<h2>Warnings</h2>
<p>If you&#8217;re the sort of person who heeds warnings, don&#8217;t turn your drill press into a lathe. The comments on <a href="http://www.instructables.com/id/Drill-Press-Lathe/">this instructible</a> which I found while researching my own lathe project indicate that the chuck coming off is a common problem and could be dangerous.</p>
<p>If you&#8217;re the sort of person who doesn&#8217;t heed warnings, I&#8217;m not sure what you&#8217;re doing reading this, but best of luck, and be safe. I assume no responsibility for your success, safety or lack thereof  should you undertake this or a similar project.</p>
]]></content:encoded>
			<wfw:commentRss>http://stuporglue.org/how-to-turn-a-drill-press-into-a-lathe/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Using Google Protcol Buffers with PHP and protoc-gen-php</title>
		<link>http://stuporglue.org/using-google-protcol-buffers-with-php-and-protoc-gen-php/</link>
		<comments>http://stuporglue.org/using-google-protcol-buffers-with-php-and-protoc-gen-php/#comments</comments>
		<pubDate>Fri, 13 May 2011 17:20:38 +0000</pubDate>
		<dc:creator>stuporglue</dc:creator>
				<category><![CDATA[Something Interesting]]></category>
		<category><![CDATA[google prototcol buffers]]></category>
		<category><![CDATA[protoc-gen-php]]></category>
		<category><![CDATA[protocol buffers]]></category>

		<guid isPermaLink="false">http://stuporglue.org/?p=1101</guid>
		<description><![CDATA[Protocol Buffers are a binary data transfer protocol from Google. You define the structure of the data using a C-like text file, then compile that protocol buffer file into a library appropriate for the language you are using. Google officially &#8230; <a href="http://stuporglue.org/using-google-protcol-buffers-with-php-and-protoc-gen-php/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p><a title="Google Protocol Buffers" href="http://code.google.com/p/protobuf/" target="_blank">Protocol Buffers</a> are a binary data transfer protocol from Google. You define the structure of the data using a C-like text file, then compile that protocol buffer file into a library appropriate for the language you are using. Google officially supports Java, Python and C++.</p>
<h2>PHP Protocol Buffers</h2>
<h3>UPDATE!</h3>
<p style="padding-left: 30px;">I haven&#8217;t tested it yet, but DRSlump commented below to let me know  about a new, alive and mostly complete implementation he is using and  working on. I haven&#8217;t tried it yet since it uses PHP 5.3 and we haven&#8217;t  quite moved over from 5.2 yet, but here it is for your enjoyment:<a title="DRSlump's Protobuf-PHP" href="https://github.com/drslump/Protobuf-PHP"></a></p>
<p style="padding-left: 30px;"><a title="DRSlump's Protobuf-PHP" href="https://github.com/drslump/Protobuf-PHP">DRSlump&#8217;s Protobuf-PHP</a></p>
<p>I could find no complete implementation for PHP at this time. There are several 1/2 completed protocol buffer implementations out there for PHP, most of which date back to near the initial protocol buffer announcement from Google.</p>
<ul>
<li><a title="Protobuf-php" href="http://protobuf-php.sourceforge.net/" target="_blank">protobuf-php</a></li>
<li><a href="http://code.google.com/p/pb4php/" target="_blank">pb4php</a></li>
<li><a href="https://github.com/bramp/protoc-gen-php" target="_blank">protoc-gen-php</a></li>
</ul>
<p>If you are going to use Protocol Buffers in a PHP project, you will  probably want to evaluate the features and approach of each of these  projects. For the project I am working on I decided to go with protoc-gen-php since it seemed to have the most complete (3/4ths complete?!) implementation and the most recent update.</p>
<h2>Status of protoc-gen-php</h2>
<p>There were two branches of protoc-gen-php when I started using it. Bramp was the original author and responded to emails, Iamamused has a few more features, but didn&#8217;t respond to any of my emails. I took the email response to mean, in part, that despite the lack of updates that Bramp was still interested in at least maintaining the project.After forking his project to add some features I needed, he did in fact indicate that he will indeed merge appropriate changes from my fork and from Iamamused back into the main branch.</p>
<p>Until that happens though you&#8217;ve got three choices:</p>
<ul>
<li><a title="Bramp's protoc-gen-php" href="https://github.com/bramp/protoc-gen-php" target="_blank">Bramp&#8217;s branch</a> &#8212; the original and probably best long-term choice</li>
<li><a title="Iamamused's branch" href="https://github.com/iamamused/protoc-gen-php" target="_blank">Iamamused&#8217;s branch</a> &#8212; more features than Bramp&#8217;s branch (sint, for example) but no indication if he will be maintaining it long term</li>
<li><a title="Stuporglue's fork of protoc-gen-php" href="https://github.com/stuporglue/protoc-gen-php" target="_blank">Stuporglue&#8217;s branch (mine)</a> &#8212; See below for features and caveats</li>
</ul>
<h2>My fork of Protoc-gen-php</h2>
<p>I don&#8217;t really have interest in being a project maintainer long term. If you use my branch, I&#8217;m happy to help in whatever capacity I&#8217;m able until Bramp merges the features I&#8217;ve added back into his branch.</p>
<p>Here&#8217;s what I&#8217;ve added:</p>
<ul>
<li>Signed Int support (sint32 / sint64)</li>
<li>Float and Double support</li>
<li>Support for the [packed=false] option so that repeating packables can be used (ie. not packed support, just the ability to specify non-packed)</li>
</ul>
<p>The big caveat with float support is that PHP uses doubles to represent both floats and doubles, so when a float-double gets packed into actual an actual float in the protocol format it does lose precision. The best solution will be to either use a string or a double.</p>
<h2>Need Other Features Added?</h2>
<p>I have implemented the features I did because I needed them for the project I am working on. I&#8217;d be happy to add other features on a consulting basis, or to merge in your changes if you submit patches to me. Like I said though, hopefully it will be merged back into Bramp&#8217;s branch pretty soon, and he can handle the patches. It would be one less thing for me to worry about!</p>
]]></content:encoded>
			<wfw:commentRss>http://stuporglue.org/using-google-protcol-buffers-with-php-and-protoc-gen-php/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>T-Mobile Customer Support Is Like Getting Help From A Child</title>
		<link>http://stuporglue.org/t-mobile-customer-support-is-like-getting-help-from-a-child/</link>
		<comments>http://stuporglue.org/t-mobile-customer-support-is-like-getting-help-from-a-child/#comments</comments>
		<pubDate>Fri, 22 Apr 2011 01:35:40 +0000</pubDate>
		<dc:creator>stuporglue</dc:creator>
				<category><![CDATA[Something Interesting]]></category>
		<category><![CDATA[t-mobile]]></category>

		<guid isPermaLink="false">http://stuporglue.org/?p=1087</guid>
		<description><![CDATA[I just spent 30 minutes on the phone with T-Mobile trying to change my plan. 5 useless conversations later with 5 different customer service reps, having repeated my phone number, name and the last 4 digitis of my social security &#8230; <a href="http://stuporglue.org/t-mobile-customer-support-is-like-getting-help-from-a-child/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>I just spent 30 minutes on the phone with T-Mobile trying to change my plan. 5 useless conversations later with 5 different customer service reps, having repeated my phone number, name and the last 4 digitis of my social security number each time, I gave up and hung up.</p>
<h2>UPDATE:</h2>
<p>We went into a store and they called T-mobile again for us. It turns out that the magic words are &#8220;Close my account&#8221;. That gets you to customer loyalty. We are now happy prepaid customers. We did also sign up with a VoIP service and tie that into our Google Voice numbers to get free incoming calls when we&#8217;re at home, but that&#8217;s food for another post.</p>
<p>&nbsp;</p>
<p>What I&#8217;m trying to do isn&#8217;t that complicated. I currently have a family plan with two phone lines on it. The 750 Talk plan for $59.99 per month.</p>
<figure id="attachment_1088" aria-labelledby="figcaption_attachment_1088" class="wp-caption aligncenter" style="width: 580px"><a rel="attachment wp-att-1088" href="http://stuporglue.org/t-mobile-customer-support-is-like-getting-help-from-a-child/familyplan/"><img class="size-medium wp-image-1088" title="Our current plan" src="http://stuporglue.org/wp-content/uploads/2011/04/FamilyPlan-570x359.png" alt="Our current plan" width="570" height="359" /></a><figcaption id="figcaption_attachment_1088" class="wp-caption-text">Our current plan</figcaption></figure>
<p>Our contract expired last month, which meant I was in the market for a new plan. After downloading our recent usage into a spreadsheet and checking out all our options, the best place for us to be right now is on separate monthly pre-paid plans, the 1500 Talk &amp; Text with 30 MB data plan, for $30.00 per phone.</p>
<figure id="attachment_1090" aria-labelledby="figcaption_attachment_1090" class="wp-caption aligncenter" style="width: 580px"><a rel="attachment wp-att-1090" href="http://stuporglue.org/t-mobile-customer-support-is-like-getting-help-from-a-child/prepaidplan-2/"><img class="size-medium wp-image-1090" title="Our Desired Plan" src="http://stuporglue.org/wp-content/uploads/2011/04/PrepaidPlan1-570x304.png" alt="Our Desired Plan" width="570" height="304" /></a><figcaption id="figcaption_attachment_1090" class="wp-caption-text">Our Desired Plan</figcaption></figure>
<p>Same price for 4 t imes the minutes, not too bad, right? What the family plan has that the prepaid doesn&#8217;t is that the family plan includes free nights and weekends, and free T-Mobile to T-Mobile calling. We&#8217;re both using about 700-900 total minutes per month with no texting; essentially what this plan would do is give us texting at the same price we&#8217;re paying now.</p>
<h2>Problem 1: Service Reps Don&#8217;t Want to Talk to You If You Have a Plan That&#8217;s Not In Their Department</h2>
<p>The first issue I had was that each service rep wanted to send me back to the post-paid department since I have a post-paid plan. I literally had to interrupt them and say &#8220;Wait!&#8221; to not get sent back there again and again.In the end, no one really knew who I was supposed to talk to.</p>
<pre>Post Paid
    ||
    \/
Pre Paid
    ||
    \/
Customer Care ? (Gustavo)
    ||
    \/
Customer Care
    ||
    \/
Flex Pay
    ||
    \/
Pre Paid (but I said no, and hung up)</pre>
<p>Above is how I was transfered. I was automatically started in post paid, which makes sense since that&#8217;s the plan type I have. They sent me to pre-paid (see problem 2 below, for more on this) who said that Customer Care should be able to help me switch plans. Customer care apparently wasn&#8217;t really custoemer care because they transfered me to the real Customer Care. The real customer care actually seemed like they understood my problem, but couldn&#8217;t change my plans for me, and sent me to Flex Pay. Flex pay got confused and tried sending me back to pre-paid. At this point I figured enough was enough and said goodbye.</p>
<h2>Problem 2: Internal Plan Categories Don&#8217;t Match T-Mobile&#8217;s Website&#8217;s Plan Categories</h2>
<p>Check out the <a title="Post Paid Plans" href="http://www.t-mobile.com/shop/plans/cell-phone-plans.aspx?catgroup=family" target="_blank">Family Plan page</a>. Go ahead, open that link and search for the term &#8220;post paid&#8221; or &#8220;postpaid&#8221;. The only place it appears is in the light grey fine print at the bottom of the page, and yet customer service kept referring to the post-paid department when they talked about transferring me. The term <em>postpaid</em> was pretty easy to figure out, after all, I am paying for my account at the end of each month.</p>
<p>Where it starts to get fun is if you visit their <a title="Prepaid Plans" href="http://www.t-mobile.com/shop/plans/prepaid-plans.aspx" target="_blank">Prepaid Plans page</a>.  Looks like some nice pre-paid plans, right? You&#8217;ve got your <em>pay by the minute</em> section and your <em>prepaid monthly plan</em>. Pretty straightforward right? Except that once you have gotten transfered to the pre-paid department and start talking about the monthly plans, they get confused. The lady I spoke with kept saying &#8220;We don&#8217;t have any plans with 1500 minutes!&#8221;. Finally she figured out which plan I was talking about and said &#8220;OH, that&#8217;s not prepaid, that&#8217;s a flex plan&#8221;.  What do you mean it&#8217;s not pre-paid? It says on the website &#8220;PREPAID PLANS&#8221;. Go back to the Prepaid plans page and search for the word &#8220;Flex&#8221; &#8212; it&#8217;s nowhere to be found.</p>
<p>Now, I&#8217;m not sure if the pre-paid customer service rep was just confused or what, but I don&#8217;t even see the word Flex anywhere in the Plans menu list.I have added it for your convenience.</p>
<p style="text-align: center;">&nbsp;</p>
<figure id="attachment_1091" aria-labelledby="figcaption_attachment_1091" class="wp-caption aligncenter" style="width: 548px"><a rel="attachment wp-att-1091" href="http://stuporglue.org/t-mobile-customer-support-is-like-getting-help-from-a-child/flexismissing/"><img class="size-full wp-image-1091 " title="Flex is Missing" src="http://stuporglue.org/wp-content/uploads/2011/04/FlexIsMissing.png" alt="Flex is Missing" width="538" height="342" /></a><figcaption id="figcaption_attachment_1091" class="wp-caption-text">Flex is Missing</figcaption></figure>
<p>There is in fact a <a title="Flex Pay Plans" href="http://www.t-mobile.com/shop/plans/flexpayplans.aspx?direct=yes&amp;type=1&amp;wt.mc_" target="_blank">FlexPay Plans webpag</a>e, but I suggest you use Google to find it because it&#8217;s sure not in the menu or on the<a title="Plans Overview" href="http://www.t-mobile.com/shop/plans/Cell-Phone-Plans-Overview.aspx" target="_blank"> Plans Overview webpag</a>e. Actually, I suggest not bothering with finding it because the plans aren&#8217;t very good.</p>
<p>In any case, it doesn&#8217;t look like the T-mobile website likes Flex Pay very much, and it seems like the pre-paid people need some more training on their pre-paid plans. Or the website needs to be more clear on the definition of pre-paid.</p>
<h2>Problem 3: Getting Help From T-Mobile is Like Getting Help From a Child</h2>
<p>I&#8217;ll give T-Mobile this much, I was speaking to a live person in about 3 minutes, and every one of the 5 people I spoke with was friendly.</p>
<p>Working with them was like getting help from a child though. A happy friendly kid who you have to explain things to over and over again. Every time you speak to someone you are required to enter your phone number, say your name and the last 4 of your Social Security number, and then you have to re-explain your whole issue to the new person. Isn&#8217;t this the reason that computers were made? To help us with problems like this? Once I have proven who I am, shouldn&#8217;t I be able to get transfered through the whole support system without doing so again?</p>
<h2>Conclusion</h2>
<p>After 30 minutes I&#8217;d had enough. I guess I&#8217;ll have to go into a physical store. It just seems that a phone company should be able to help a customer change phone plans over the phone. I mean, isn&#8217;t that kind of the business they&#8217;re in?</p>
<p>Sorry for the rant, I usually try to keep this website more technical and interesting with less whining, but this is what you get tonight.</p>
<h2>Sidenote</h2>
<p>Runners up in the search for a new plan were</p>
<ul>
<li><a title="H2O Wireless" href="https://www.h2owirelessnow.com/pageControl.php?page=plans" target="_blank">H2O Wireless</a> &#8211; $40/month for unlimited voice and text &#8212; GSM prepaid</li>
<li><a title="Boost Mobile" href="http://plans.boostmobile.com/monthlyunlimited.aspx" target="_blank">Boost Mobile</a> &#8211; $50/month starting decreasing every 6 months to $35/month after 18 months. iDEN prepaid</li>
</ul>
<p>The price to match was $60/month for two lines for about 900 minutes per line. Boost would&#8217;ve been worth the extra money for unlimited data, but we got my wife a nice GSM phone with built-in GPS (no data plan needed!)and I haven&#8217;t seen a smart phone that fits my needs yet, so I&#8217;d rather stick with my smallish dump-phone. H2O would&#8217;ve been an acceptable option, but since we won&#8217;t be maxing out the 1500 minutes on the T-Mobile plan, the price was the only difference and T-Mobile won out. Well&#8230;it will if they can get our plans switched over.</p>
]]></content:encoded>
			<wfw:commentRss>http://stuporglue.org/t-mobile-customer-support-is-like-getting-help-from-a-child/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>SVN pre-commit hook which can syntax check all files</title>
		<link>http://stuporglue.org/svn-pre-commit-hook-which-can-syntax-check-all-files/</link>
		<comments>http://stuporglue.org/svn-pre-commit-hook-which-can-syntax-check-all-files/#comments</comments>
		<pubDate>Thu, 07 Apr 2011 06:01:38 +0000</pubDate>
		<dc:creator>stuporglue</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[Something Interesting]]></category>
		<category><![CDATA[automatic syntax check]]></category>
		<category><![CDATA[checkin]]></category>
		<category><![CDATA[hooks]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[php -l]]></category>
		<category><![CDATA[php syntax checking]]></category>
		<category><![CDATA[pre-commit]]></category>
		<category><![CDATA[subversion]]></category>
		<category><![CDATA[svn]]></category>

		<guid isPermaLink="false">http://stuporglue.org/?p=1080</guid>
		<description><![CDATA[If you manage a project which uses Subversion you&#8217;re going to eventually want to check the syntax of files before they get committed to the repository. Checking files before they get committed to the repository solves at least two problems &#8230; <a href="http://stuporglue.org/svn-pre-commit-hook-which-can-syntax-check-all-files/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>If you manage a project which uses <a title="Subversion" href="http://subversion.tigris.org/">Subversion </a>you&#8217;re going to eventually want to check the syntax of files before they get committed to the repository. Checking files before they get committed to the repository solves at least two problems for me.</p>
<ol>
<li>It stops me from checking something in, noticing it doesn&#8217;t work and committing a fixed version only moments later.</li>
<li>It prevents other users from accidentally checking in broken code</li>
</ol>
<p>A quick search online will reveal that it&#8217;s easy to check the syntax of your PHP files before they are committed. All you need to use is a pre-commit hook. Unfortunately scripts I found would all stop on the first error which was not the behavior I wanted. I wanted to check all of the files I was committing and get a list of all errors immediately. So, I wrote my own pre-commit hook.</p>
<h2>Checking All PHP Files With A pre-commit Hook</h2>
<p>The script I came up with is in PHP. Save this in the hooks directory of your Subversion repository. Name it pre-commit and make it executable.</p>
<pre>#!/usr/bin/php
&lt;?php 

// Set these manually since Subversion doesn't set ENV
$PHP = '/usr/bin/php';
$SVNLOOK = '/usr/bin/svnlook';
$AWK = '/usr/bin/awk';
$GREP = '/bin/egrep';
$SED = '/bin/sed'; 

$REPOS = $argv[1];
$TXN = $argv[2]; 

// Find the changes...
$CHANGED=`$SVNLOOK changed -t "$TXN" "$REPOS" | $GREP "^[U|A]" | $AWK '{print $2}'`;
// ...as an array
$CHANGED = split("\n",trim(rtrim($CHANGED))); 

$errors = Array();

// Perform specific actions based on the file extension
foreach($CHANGED as $FILE){
 switch(pathinfo($FILE,PATHINFO_EXTENSION)){
 case 'php':
 case 'class':
    // Get just the error/no error message from php -l
    $cmd="$SVNLOOK cat -t '$TXN' '$REPOS' '$FILE' | $PHP -l | head -2 | tail -1";
    $msg=trim(rtrim(`$cmd`));
    if(preg_match('/No syntax errors detected/',$msg) != 1){
       $msg = preg_replace('/in - /','',$msg);
       $errors[] = "In $FILE: $msg";
    }
   break;
  case 'js':
    // You could do something else for JavaScript -- like JSLint, if you're brave
    break;
 }
} 

// Print all the errors in a nice list
if(count($errors) &gt; 0){
 $warning ="
*************************************************************************
* Please correct the following errors before commiting these changes! *
*************************************************************************
";
 error_log($warning);
 for($i = 1;$i &lt;= count($errors);$i++){
 error_log("$i. " . $errors[($i - 1)]);
 } 

 exit(-1);
} 

exit(0);</pre>
<p>This script will check all of the files that changed based on their file extension. You could check .js files one way and .php another way. You can extend this script by simply adding more cases to the switch statement.</p>
<p>The script collects all errors and then prints a nice list when it&#8217;s done. The output looks like this:</p>
<pre>svn commit -m "pre-commit hook test"
Sending        test_scripts/info.php
Sending        test_scripts/user_read.php
Transmitting file data ..svn: Commit failed (details follow):
svn: Commit blocked by pre-commit hook (exit code 255) with output:

*************************************************************************
*  Please correct the following errors before commiting these changes!  *
*************************************************************************

1. In /test_scripts/info.php: Parse error: syntax error, unexpected $end on line 4
2. In /test_scripts/user_read.php: Parse error: syntax error, unexpected T_ENCAPSED_AND_WHITESPACE, expecting T_STRING or T_VARIABLE or T_NUM_STRING on line 8</pre>
<p>I&#8217;m not going to say that it&#8217;s the most elegant output in the world but it does the job and I haven&#8217;t checked in a bad PHP file since.</p>
]]></content:encoded>
			<wfw:commentRss>http://stuporglue.org/svn-pre-commit-hook-which-can-syntax-check-all-files/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

