commit ba1e6b20e04a3e6d0959a347bde786c7816cfa9a
Author: dsorber <david.sorber@gmail.com>
Date:   Sun Nov 10 12:49:28 2013 -0500

    Adding transmission config and my geany cobalt colorscheme. Also reorganizing the repository layout since I've finally got around to including more than just software.

diff --git a/configs/geany/README.txt b/configs/geany/README.txt
new file mode 100644
index 0000000..53bbbfe
--- /dev/null
+++ b/configs/geany/README.txt
@@ -0,0 +1,3 @@
+Place these files in:
+
+~/.config/geany/filedefs/
\ No newline at end of file
diff --git a/configs/geany/filetypes.c b/configs/geany/filetypes.c
new file mode 100644
index 0000000..b06fd4b
--- /dev/null
+++ b/configs/geany/filetypes.c
@@ -0,0 +1,78 @@
+# For complete documentation of this file, please see Geany's main documentation
+[styling]
+# foreground;background;bold;italic
+default=0xffffff;0x001b33;false;false
+comment=0x0088ff;0x001b33;false;true
+commentline=0x0088ff;0x001b33;false;true
+commentdoc=0x888a85;0x001b33;true;false
+
+number=0xff0044;0x001b33;false;false
+word=0xff9d00;0x001b33;true;false
+word2=0x00BF8C;0x001b33;true;false
+
+string=0x3ad900;0x001b33;false;false
+character=0x3ad900;0x2e3436;false;false
+
+uuid=0xffffff;0xffffff;true;false
+preprocessor=0x80ffbb;0x001b33;true;false
+
+operator=0xffffff;0x001b33;true;false
+identifier=0xcccccc;0x001b33;false;false
+
+#end of line where string is not closed
+stringeol=0xfff;0x990000;false;false
+
+#string @"str"	
+verbatim=0xfce94f;0x2e3436;false;false
+regex=0x4e9a06;0x2e3436;false;false
+
+commentlinedoc=0x888a85;0x2e3436;false;false
+commentdockeyword=0xad7fa8;0x2e3436;false;false
+commentdockeyworderror=0x888a85;0x2e3436;false;false
+	
+#array index
+globalclass=0xffcc00;0x001b33;true;false
+
+styling_within_preprocessor=1;0;false;false
+
+[keywords]
+# all items must be in one line
+primary=default export goto sizeof typename asm enum bool union typedef struct extern static register auto const if else switch for while do break continue return class new delete inline case volatile
+secondary=NULL FALSE TRUE char int float double void unsigned signed long short 
+# these are some doxygen keywords (incomplete)
+docComment=attention author brief bug class code date def enum example exception file fn namespace note param remarks return see since struct throw todo typedef var version warning union
+
+[settings]
+# default extension used when saving files
+#extension=c
+
+# the following characters are these which a "word" can contains, see documentation
+#wordchars=_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
+
+# if only single comment char is supported like # in this file, leave comment_close blank
+comment_open=//
+comment_close=
+# this is an alternative way, so multiline comments are used
+#comment_open=/*
+#comment_close=*/
+
+# set to false if a comment character/string should start at column 0 of a line, true uses any
+# indentation of the line, e.g. setting to true causes the following on pressing CTRL+d
+	#command_example();
+# setting to false would generate this
+#	command_example();
+# This setting works only for single line comments
+comment_use_indent=true
+
+# context action command (please see Geany's main documentation for details)
+context_action_cmd=
+
+[build_settings]
+# %f will be replaced by the complete filename
+# %e will be replaced by the filename without extension
+# (use only one of it at one time)
+compiler=gcc -Wall -c "%f"
+linker=gcc -Wall -o "%e" "%f"
+run_cmd="./%e"
+
+
diff --git a/configs/geany/filetypes.common b/configs/geany/filetypes.common
new file mode 100644
index 0000000..1070932
--- /dev/null
+++ b/configs/geany/filetypes.common
@@ -0,0 +1,86 @@
+# For complete documentation of this file, please see Geany's main documentation
+[styling]
+# foreground;background;bold;italic - used for filetype All/None
+default=0xffffff;0x001b33;false;false
+
+# 3rd selection argument is true to override default foreground
+# 4th selection argument is true to override default background
+selection=0xffffff;0x0088ff;true;true
+
+# style for a matching brace
+brace_good=0xffffff;0x4f94cd;true;false
+# style for a non-matching brace (a brace without a counterpart)
+brace_bad=0xffffff;0x990000;true;false
+
+# the following settings define the colours of the margins on the left side
+margin_linenumber=0xeeeeee;0x000000;false;false
+margin_folding=0x888a85;0x000000;false;false
+
+# background colour of the current line, only the second and third argument is interpreted
+# use the third argument to enable or disable the highlighting of the current line (has to be true/false)
+current_line=0x0;0x003b70;true;false
+
+# translucency for the current line(first argument) and the selection (second argument)
+# values between 0 and 256 are accepted. Note for Windows 95, 98 and ME users:
+# keep this value at 256 to disable translucency otherwise Geany might crash
+# only the first and second argument is interpreted
+translucency=256;256;false;false
+
+# style for a highlighted line (e.g when using Goto line or goto tag)
+# only the first and second argument is interpreted
+marker_line=0x000000;0xffff00;false;false
+
+# style for a highlighted search result (e.g when using "Mark" in Search dialogs)
+# only the first and second argument is interpreted
+marker_search=0x000000;0xffcc00;false;false
+
+# translucency for the line marker(first argument) and the search marker (second argument)
+# only the first and second argument is interpreted
+marker_translucency=256;256;false;false
+
+# colour of the caret(the blinking cursor), only first and third argument is interpreted
+# set the third argument to true to change the caret into a block caret
+caret=0xffffff;0x0;false;false
+
+# width of the caret(the blinking cursor), only first argument is interpreted
+# width in pixels, use 0 to make it invisible, maximum width is 3
+caret_width=1;0;false;false
+
+# set foreground and background colour of indentation guides
+indent_guide=0x474545;0x2e3436;false;false
+
+# third argument: if true, use this foreground color. If false, use the default value defined by the filetypes.
+# fourth argument: if true, use this background color. If false, use the default value defined by the filetypes.
+white_space=0x474545;0x2e3436;true;true
+
+# style of folding icons, only first and second arguments are used, valid values are:
+# first argument:  1 for boxes, 2 for circles
+# second argument: 1 for straight lines, 2 for curved lines
+folding_style=1;1;false;false
+
+# should an horizontal line be drawn at the line where text is folded (only first argument is interpreted)
+# 0 to disable
+# 1 to draw the line above folded text
+# 2 to draw the line below folded text
+folding_horiz_line=2;0;false;false
+
+# only first argument is interpreted, sets whether all defined colours should be inverted
+invert_all=0;0;false;false
+
+# only first and second argument is interpreted
+# first argument: drawing of visual flags to indicate a line is wrapped. This is a bitmask of the
+# values: 0 - No visual flags, 1 - Visual flag at end of subline of a wrapped line, 2 - Visual flag
+# at begin of subline of a wrapped line, Subline is indented by at least 1 to make room for the flag.
+# second argument: wether the visual flags to indicate a line is wrapped are drawn near the border
+# or near the text. This is a bitmask of the values: 0 - Visual flags drawn near border,
+# 1 - Visual flag at end of subline drawn near text, 2 - Visual flag at begin of subline drawn near text
+line_wrap_visuals=3;0;false;false
+
+# sets the size of indentation of sublines for wrapped lines in terms of the width of a space,
+# only first argument is interpreted
+line_wrap_indent=0;0;false;false
+
+[settings]
+# which characters should be skipped when moving (or included when deleting) to word boundaries
+# should always include space and tab (\s\t)
+whitespace_chars=\s\t!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~
diff --git a/configs/geany/filetypes.python b/configs/geany/filetypes.python
new file mode 100644
index 0000000..f4c9ccb
--- /dev/null
+++ b/configs/geany/filetypes.python
@@ -0,0 +1,59 @@
+# For complete documentation of this file, please see Geany's main documentation
+[styling]
+default=0xffffff;0x001b33;false;false
+commentline=0x0088ff;0x001b33;false;false
+number=0xff0044;0x001b33;false;false
+string=0x3ad900;0x001b33;false;false
+character=0x3ad900;0x001b33;false;false
+word=0xff9d00;0x001b33;true;false
+word2=0xff0044;0x001b33;true;false
+triple=0x729fcf;0x001b33;false;false
+tripledouble=0x3ad900;0x001b33;false;false
+classname=0xffffff;0x001b33;true;false
+defname=0xffffff;0x001b33;true;false
+operator=0xffffff;0x001b33;true;false
+identifier=0xcccccc;0x001b33;false;false
+commentblock=0x0088ff;0x001b33;false;false
+stringeol=0xffffff;0xad7fa8;false;false <------------
+decorator=0x729FCF;0x001b33;false;false
+commentdockeyword=0xad7fa8;0x001b33;false;false
+commentdockeyworderror=0x888a85;0x001b33;false;false
+
+marker_search=0x000000;0xffcc00;false;false
+
+[keywords]
+# all items must be in one line
+primary=and as assert break class continue def del elif else except exec finally for from global if import in is lambda not or pass print raise return try while with yield isinstance self __name__ abs all any basestring bin bool bytearray callable chr classmethod cmp compile complex delattr dict dir divmod enumerate eval execfile file filter float format frozenset getattr globals hasattr hash help hex id input int isinstance issubclass iter len list locals long map max memoryview min next object oct open ord pow print property range raw_input reduce reload repr reversed round set setattr slice sorted staticmethod str sum super tuple type unichr unicode vars xrange zip __import__ apply buffer coerce intern
+#uncomment this when bug is fixed:
+secondary=True False None NotImplemented Ellipsis __debug__
+
+[settings]
+# default extension used when saving files
+extension=py
+
+# the following characters are these which a "word" can contains, see documentation
+#wordchars=_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
+
+# if only single comment char is supported like # in this file, leave comment_close blank
+comment_open=#
+comment_close=
+
+# set to false if a comment character/string should start at column 0 of a line, true uses any
+# indentation of the line, e.g. setting to true causes the following on pressing CTRL+d
+	#command_example();
+# setting to false would generate this
+#	command_example();
+# This setting works only for single line comments
+comment_use_indent=true
+
+docComment=attention author brief bug class code date def enum example exception file fn namespace note param remarks return see since struct throw todo typedef var version warning union
+
+# context action command (please see Geany's main documentation for details)
+context_action_cmd=
+
+[build_settings]
+# %f will be replaced by the complete filename
+# %e will be replaced by the filename without extension
+# (use only one of it at one time)
+compiler=python -c "import py_compile; py_compile.compile('%f')"
+run_cmd=python "%f"
diff --git a/configs/geany/filetypes.sh b/configs/geany/filetypes.sh
new file mode 100644
index 0000000..fc01d1d
--- /dev/null
+++ b/configs/geany/filetypes.sh
@@ -0,0 +1,58 @@
+# For complete documentation of this file, please see Geany's main documentation
+[styling]
+# foreground;background;bold;italic
+default=0xffffff;0x001b33;false;false
+commentline=0x0088ff;0x001b33;false;false
+commands=0xFF9D00;0xffffff;false;false
+number=0xff0044;0x001b33;false;false
+word=0xff9d00;0x001b33;true;false
+string=0x3ad900;0x001b33;false;false
+character=0x3ad900;0x001b33;false;false
+operator=0xffffff;0x001b33;true;false
+identifier=0xffffff;0x001b33;false;false
+backticks=0x000000;0xd0d0d0;false;false
+param=0x009f00;0xffffff;false;false
+scalar=0x105090;0xffffff;false;false
+error=0xff0000;0xffffff;false;false
+here_delim=0x000000;0xddd0dd;false;false
+here_q=0x7f007f;0xddd0dd;false;false
+
+
+[keywords]
+primary=break case continue do done elif else esac eval exit export fi for function goto if in integer return set shift then until while bash bunzip2 busybox bzcat bzcmp bzdiff bzegrep bzexe bzfgrep bzgrep bzip2 bzip2recover bzless bzmore cat cd chgrp chmod chown chvt cp cpio dash date dd df dir dmesg dnsdomainname domainname dumpkeys echo ed egrep false fgconsole fgrep fuser fusermount grep gunzip gzexe gzip hostname init-checkconf initctl2dot ip kbd_mode kill less lessecho lessfile lesskey lesspipe ln loadkeys login ls lsmod mkdir mknod mktemp more mount mountpoint mt mt-gnu mv nano nc nc.openbsd netcat netstat nisdomainname open openvt pidof ping ping6 ps pwd rbash readlink rm rmdir rnano run-parts sed setfont setupcon sh sh.distrib sleep static-sh stty su sync tailf tar tempfile touch true umount uname uncompress unicode_start vdir vmmouse_detect which ypdomainname zcat zcmp zdiff zegrep zfgrep zforce zgrep zless zmore znew
+
+
+[settings]
+# default extension used when saving files
+extension=sh
+
+# the following characters are these which a "word" can contains, see documentation
+#wordchars=_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
+
+# single comments, like # in this file
+comment_single=#
+# multiline comments
+#comment_open=
+#comment_close=
+
+# set to false if a comment character/string should start a column 0 of a line, true uses any
+# indentation of the line, e.g. setting to true causes the following on pressing CTRL+d
+	#command_example();
+# setting to false would generate this
+#	command_example();
+# This setting works only for single line comments
+comment_use_indent=true
+
+# context action command (please see Geany's main documentation for details)
+context_action_cmd=
+
+[indentation]
+#width=4
+# 0 is spaces, 1 is tabs, 2 is tab & spaces
+#type=1
+
+[build_settings]
+# %f will be replaced by the complete filename
+# %e will be replaced by the filename without extension
+# (use only one of it at one time)
+run_cmd="./%f"
diff --git a/configs/geany/filetypes.vhdl b/configs/geany/filetypes.vhdl
new file mode 100644
index 0000000..bf4a69e
--- /dev/null
+++ b/configs/geany/filetypes.vhdl
@@ -0,0 +1,53 @@
+# For complete documentation of this file, please see Geany's main documentation
+[styling]
+# foreground;background;bold;italic
+default=0xffffff;0x001b33;false;false
+comment=0x0088ff;0x001b33;false;false
+commentline=0x0088ff;0x001b33;false;false
+comment_line_bang=0x0088ff;0x001b33;false;false
+number=0xff0044;0x001b33;false;false
+string=0x3ad900;0x001b33;false;false
+operator=0xffffff;0x001b33;true;false
+identifier=0xcccccc;0x001b33;false;false
+stringeol=0x000000;0xe0c0e0;false;false
+keyword=0xff9d00;0x001b33;true;false
+stdoperator=0xff9d00;0x001b33;true;false
+attribute=0x804020;0xffffff;false;false
+stdfunction=0xff9d00;0x001b33;true;false
+stdpackage=0xff9d00;0x001b33;true;false
+stdtype=0x208080;0x001b33;false;false
+userword=0x804020;0x001b33f;true;false
+
+[keywords]
+# all items must be in one line
+keywords=access after alias all architecture array assert attribute begin block body buffer bus case component configuration constant disconnect downto else elsif end entity exit file for function generate generic group guarded if impure in inertial inout is label library linkage literal loop map new next null of on open others out package port postponed procedure process pure range record register reject report return select severity shared signal subtype then to transport type unaffected units until use variable wait when while with
+operators=abs and mod nand nor not or rem rol ror sla sll sra srl xnor xor
+attributes=left right low high ascending image value pos val succ pred leftof rightof base range reverse_range length delayed stable quiet transaction event active last_event last_active last_value driving driving_value simple_name path_name instance_name
+std_functions=now readline read writeline write endfile resolved to_bit to_bitvector to_stdulogic to_stdlogicvector to_stdulogicvector to_x01 to_x01z to_UX01 rising_edge falling_edge is_x shift_left shift_right rotate_left rotate_right resize to_integer to_unsigned to_signed std_match to_01
+std_packages=std ieee work standard textio std_logic_1164 std_logic_arith std_logic_misc std_logic_signed std_logic_textio std_logic_unsigned numeric_bit numeric_std math_complex math_real vital_primitives vital_timing
+std_types=boolean bit character severity_level integer real time delay_length natural positive string bit_vector file_open_kind file_open_status line text side width std_ulogic std_ulogic_vector std_logic std_logic_vector X01 X01Z UX01 UX01Z unsigned signed
+userwords=
+
+[settings]
+# default extension used when saving files
+extension=vhd
+
+# the following characters are these which a "word" can contains, see documentation
+#wordchars=_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
+
+# if only single comment char is supported like # in this file, leave comment_close blank
+comment_open=--
+comment_close=
+
+# set to false if a comment character/string should start at column 0 of a line, true uses any
+# indentation of the line, e.g. setting to true causes the following on pressing CTRL+d
+	#command_example();
+# setting to false would generate this
+#	command_example();
+# This setting works only for single line comments
+comment_use_indent=true
+
+# context action command (please see Geany's main documentation for details)
+context_action_cmd=
+
+
diff --git a/configs/settings.json b/configs/settings.json
new file mode 100644
index 0000000..adb57d9
--- /dev/null
+++ b/configs/settings.json
@@ -0,0 +1,70 @@
+{
+    "alt-speed-down": 3072, 
+    "alt-speed-enabled": false, 
+    "alt-speed-time-begin": 540, 
+    "alt-speed-time-day": 127, 
+    "alt-speed-time-enabled": false, 
+    "alt-speed-time-end": 1020, 
+    "alt-speed-up": 256, 
+    "bind-address-ipv4": "0.0.0.0", 
+    "bind-address-ipv6": "::", 
+    "blocklist-enabled": true, 
+    "blocklist-url": "http://list.iblocklist.com/?list=bt_level1&fileformat=p2p&archiveformat=gz", 
+    "cache-size-mb": 4, 
+    "dht-enabled": true, 
+    "download-dir": "/var/lib/transmission-daemon/downloads", 
+    "download-limit": 100, 
+    "download-limit-enabled": 0, 
+    "download-queue-enabled": true, 
+    "download-queue-size": 5, 
+    "encryption": 1, 
+    "idle-seeding-limit": 30, 
+    "idle-seeding-limit-enabled": false, 
+    "incomplete-dir": "/home/debian-transmission/Downloads", 
+    "incomplete-dir-enabled": false, 
+    "lpd-enabled": false, 
+    "max-peers-global": 200, 
+    "message-level": 2, 
+    "peer-congestion-algorithm": "", 
+    "peer-limit-global": 1200, 
+    "peer-limit-per-torrent": 280, 
+    "peer-port": 61993, 
+    "peer-port-random-high": 65535, 
+    "peer-port-random-low": 49152, 
+    "peer-port-random-on-start": false, 
+    "peer-socket-tos": "default", 
+    "pex-enabled": true, 
+    "port-forwarding-enabled": false, 
+    "preallocation": 1, 
+    "prefetch-enabled": 1, 
+    "queue-stalled-enabled": true, 
+    "queue-stalled-minutes": 30, 
+    "ratio-limit": 2, 
+    "ratio-limit-enabled": false, 
+    "rename-partial-files": true, 
+    "rpc-authentication-required": false, 
+    "rpc-bind-address": "0.0.0.0", 
+    "rpc-enabled": true, 
+    "rpc-password": "{8728a1e529016b129773c32f8a5e959c78199d62nqZcItFf", 
+    "rpc-port": 9565, 
+    "rpc-url": "/transmission/", 
+    "rpc-username": "dsorber", 
+    "rpc-whitelist": "10.0.0.*", 
+    "rpc-whitelist-enabled": true, 
+    "scrape-paused-torrents-enabled": true, 
+    "script-torrent-done-enabled": false, 
+    "script-torrent-done-filename": "", 
+    "seed-queue-enabled": false, 
+    "seed-queue-size": 10, 
+    "speed-limit-down": 100, 
+    "speed-limit-down-enabled": false, 
+    "speed-limit-up": 1024, 
+    "speed-limit-up-enabled": true, 
+    "start-added-torrents": true, 
+    "trash-original-torrent-files": false, 
+    "umask": 18, 
+    "upload-limit": 100, 
+    "upload-limit-enabled": 0, 
+    "upload-slots-per-torrent": 14, 
+    "utp-enabled": true
+}
diff --git a/software/.DS_Store b/software/.DS_Store
new file mode 100644
index 0000000..3cf974e
Binary files /dev/null and b/software/.DS_Store differ
diff --git a/software/bashquote/bashquote b/software/bashquote/bashquote
new file mode 100644
index 0000000..c5a6273
--- /dev/null
+++ b/software/bashquote/bashquote
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+
+import cPickle
+import os
+import random
+import sys
+
+BQ_DIR = '/Users/dsorber/.bashquote'
+BQ_CACHE = '/Users/dsorber/.bashquote/bq_cache'
+
+def main():
+
+    # Create the cache file, or open it if it already exists    
+    if not os.path.exists(BQ_CACHE):
+        print 'bashquote cache does not exist!'
+        sys.exit()
+    else :
+        cache = open(BQ_CACHE, 'r')
+        quotes = cPickle.load(cache)
+        cache.close()
+
+    # Choose a random quote from the list
+    num_quotes = len(quotes)
+    if num_quotes == 1:
+        # Repopulate the list manually if there is only one quote remaining
+        print 'Low on quotes, grabbing some more...',
+        os.system('bashquote_populate')
+        print 'Done'
+    index = random.randint(0, num_quotes - 1)
+    quote = quotes.pop(index)
+    
+    # Print it out
+    print 'bashquote #%s ------ %s' % (quote[0], quote[1])
+    print quote[2]
+
+    # Re-store the remaining quotes and exit
+    cache = open(BQ_CACHE, 'w')
+    cPickle.dump(quotes, cache, -1)
+    cache.close()
+    sys.exit()
+
+if __name__=="__main__":
+    main()
\ No newline at end of file
diff --git a/software/bashquote/bashquote_populate b/software/bashquote/bashquote_populate
new file mode 100644
index 0000000..a9e0715
--- /dev/null
+++ b/software/bashquote/bashquote_populate
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+
+import cPickle
+import os
+import re
+import sys
+import urllib 
+
+BQ_DIR = '/Users/dsorber/.bashquote'
+BQ_CACHE = '/Users/dsorber/.bashquote/bq_cache'
+
+def main():
+    
+    # Create a list to hold the quotes
+    quotes = []
+
+    # Create the bashquote directory if it does not exist
+    if not os.path.exists(BQ_DIR):
+        print 'Creating bashquote directory...',
+        os.mkdir(BQ_DIR)
+        print 'Done'
+        
+    # Create the cache file, or open it if it already exists    
+    if not os.path.exists(BQ_CACHE):
+        print 'Creating bashquote cache...',
+        cache = open(BQ_CACHE, 'w')
+        cache.close()
+        print 'Done'
+    else:
+        cache = open(BQ_CACHE, 'r')
+        quotes = cPickle.load(cache)
+        cache.close()
+
+        
+    # Only grab fresh quotes if there are less than 50 in the cache    
+    if not len(quotes) < 50:
+        print 'plenty left'
+        sys.exit()
+
+	# Goto to the random >0 bash quotes page
+    listpage = urllib.urlopen("http://www.bash.org/?random1")
+    listpagetext = listpage.read()
+
+    quote_regex = '<p class="quote".+<b>#(\d+)</b>.+</a>\((\d+)\)<a .+<p class="qt">([\s\S]+?)</p>'
+
+    quotes = []
+    for quote_match in re.finditer(quote_regex, listpagetext):
+        quotes.append((quote_match.group(1),
+	                   quote_match.group(2),
+	                   unhtmlify(quote_match.group(3))))
+	
+	# Store the list of quotes in the cache file, using the highest protocol
+    cache = open(BQ_CACHE, 'w')
+    cPickle.dump(quotes, cache, -1)
+    cache.close()
+    sys.exit()
+
+
+def unhtmlify(quote):
+    # Filter out that HTML bullshit
+    return quote.replace("&lt;", "<").replace("\'&lt;", "<")\
+                .replace("&gt;", ">").replace("&quot;", "\"")\
+                .replace("&nbsp;", " ").replace("\\'", "'")\
+                .replace("\\x92", "'").replace("<br />\\r\\n', '", "\n")\
+                .replace("<br />\\r\\n\", '", "\n").replace("&amp;", "&")\
+                .replace("<br />\\r\\n', \"", "\n")\
+                .replace("<br />\\r\\n\", \"", "\n").replace("<br />", "")
+
+
+if __name__=="__main__":
+    main()
diff --git a/software/image_grabber/grabber.py b/software/image_grabber/grabber.py
new file mode 100644
index 0000000..683c16c
--- /dev/null
+++ b/software/image_grabber/grabber.py
@@ -0,0 +1,198 @@
+from multiprocessing import Process
+from multiprocessing import Queue
+import multiprocessing
+import os
+import re
+import sys
+import time
+import urllib2
+
+# --- DESCRIPTION ---
+# A script for circumventing the image "protection" from http://celebforum.to/
+# specifically for imagevenue.com links (will also support imagebam in the future)
+# 
+# USAGE:
+# python grabber.py <html source of gallery>
+
+
+# http://img184.imagevenue.com/loc663/th_74708_0005_blood_orange_dress05_123_663lo.jpg
+# http://img184.imagevenue.com/img.php?loc=loc663&image=74708_0005_blood_orange_dress05_123_663lo.jpg
+IMAGEVENUE_PAGE_RE = r'"http://(img\d{3})\.imagevenue\.com/(loc\d{3})/(.+?\.jpg)"'
+IMAGEVENUE_SRC_RE = r'<img id="thepic" .+? SRC="(.+?)"'
+
+IMAGEBAM_PAGE_RE = r'"http://thumbnails\d+\.imagebam\.com/\d+/(.+?\.jpg)"'
+IMAGEBAM_SRC_RE = r'<img .+? onclick="scale\(this\);" src="(.+?\.jpg)"'
+
+# Location where the images should be saved
+IMAGE_SAVE_PATH = 'images'
+
+
+class ImageVenueProcessor(Process):
+
+	def __init__(self, input_file_name, image_src_queue):
+		Process.__init__(self)
+		self.input_file_name = input_file_name
+		self.image_src_queue = image_src_queue
+		self.running = False
+
+	def run(self):
+		self.running = True
+
+		# Iterate over the input source file and look for all thumbnail image matches
+		matches = []
+		with open(self.input_file_name, 'r') as input_file:
+			for line in input_file:
+				line_matches = re.findall(IMAGEVENUE_PAGE_RE, line)
+				for line_match in line_matches:
+					matches.append(line_match)
+
+		# Iterate over each match and build the correct image page URL
+		# Major thanks to http://vintage-erotica-forum.com/showthread.php?p=694632
+		# for explaining the secret sauce!
+		total = len(matches)
+		for idx, match in enumerate(matches):
+			server = 'http://{:s}.imagevenue.com/'.format(match[0])
+			image_name = match[2][3:]
+			image_loc = 'img.php?loc={:s}&image={:s}'.format(match[1], image_name)
+			link = '{:s}{:s}'.format(server, image_loc)
+
+			# Open the image page and download the source HTML
+			response = urllib2.urlopen(link)
+			html = response.read()
+
+			# Iterate over the source HTML looking for the image source link
+			for line in html.split('\n'):
+				img = re.search(IMAGEVENUE_SRC_RE, line)
+				if img:
+					location = '{:s}{:s}'.format(server, img.group(1))
+					print '{:3d} of {:3d} - {:s}'.format(idx + 1, total, location)
+					self.image_src_queue.put(location)
+
+		self.running = False
+
+class ImageBamProcessor(Process):
+
+	def __init__(self, input_file_name, image_src_queue):
+		Process.__init__(self)
+		self.input_file_name = input_file_name
+		self.image_src_queue = image_src_queue
+		self.running = False
+
+	def run(self):
+		self.running = True
+
+		# Iterate over the input source file and look for all thumbnail image matches
+		matches = []
+		with open(self.input_file_name, 'r') as input_file:
+			for line in input_file:
+				line_matches = re.findall(IMAGEBAM_PAGE_RE, line)
+				for line_match in line_matches:
+					matches.append(line_match)
+
+		# Iterate over each match and build the correct image page URL
+		# Thanks to http://board.jdownloader.org/showthread.php?t=26255
+		total = len(matches)
+		for idx, match in enumerate(matches):
+			image_name = match
+			server = 'http://www.imagebam.com/image/'
+			link = '{:s}{:s}'.format(server, image_name)
+
+			# Open image page and download the source HTML
+			response = urllib2.urlopen(link)
+			html = response.read()
+
+			# Iterate over the source HTML looking for the image source link
+			for line in html.split('\n'):
+				img_match = re.search(IMAGEBAM_SRC_RE, line)
+				if img_match:
+					location = img_match.group(1)
+					print '{:3d} of {:3d} - {:s}'.format(idx + 1, total, location)
+					self.image_src_queue.put(location)
+
+		self.running = False
+
+class Downloader(Process):
+
+	def __init__(self, image_src_queue):
+		Process.__init__(self)
+		self.image_src_queue = image_src_queue
+
+	def run(self):
+
+		while True:
+			image_path = self.image_src_queue.get()
+			image_name = image_path.rsplit('/', 1)[1]
+
+			print 'Downloading "{:s}"...'.format(image_path),
+
+			# Check if the image has already been downloaded before 
+			# attempting to download it
+			contents = os.listdir(IMAGE_SAVE_PATH)
+			if image_name not in os.listdir(IMAGE_SAVE_PATH):
+
+				# Download the freakin image!
+				try:
+					response = urllib2.urlopen(image_path, timeout=4)
+					data = response.read()
+				except:
+					# print 'Unable to download: {:s}'.format(location)
+					print 'ERROR'
+					continue
+
+				# Save the image to the desired location
+				with open(os.path.join(IMAGE_SAVE_PATH, image_name), 'wb') as new_image:
+					new_image.write(data)
+
+				print 'DONE'
+
+			else:
+				print 'SKIPPED'
+
+def main():
+
+	if len(sys.argv) < 2:
+		print 'Not enough arguments specified!'
+		return -1
+
+	# Create the image source queue that the "Processing" objects will produce
+	# to and the "Downloader" objects will consume from
+	image_src_queue = Queue(2048)
+
+	# Based on the number of cores in the system determine how many 
+	# Downloader worker objects should be created
+	num_workers = max(1, multiprocessing.cpu_count() - 2)
+
+	# Create the Downloader worker objects
+	dl_workers = []
+	for ctr in xrange(num_workers):
+		dl_worker = Downloader(image_src_queue)
+		dl_worker.start()
+		dl_workers.append(dl_worker)
+
+	# Create the ImageBam Processsor
+	ib_proc = ImageBamProcessor(sys.argv[1], image_src_queue)
+	ib_proc.start()
+
+	# Create the ImageVenue Processor
+	iv_proc = ImageVenueProcessor(sys.argv[1], image_src_queue)
+	iv_proc.start()
+
+	# Wait for the Processor objects to complete
+	ib_proc.join()
+	iv_proc.join()
+
+	# Wait until the image source queue is empty, then kill all the
+	# Downloader workers
+	while True:
+		if image_src_queue.empty():
+			# Wait 3 seconds to allow any downloads to finish
+			time.sleep(3)
+			for dl_worker in dl_workers:
+				dl_worker.terminate()
+			break
+
+	print '\n\n'
+
+
+if __name__ == '__main__':
+	sys.exit(main())
\ No newline at end of file
diff --git a/software/keypress counter/keypress_counter.c b/software/keypress counter/keypress_counter.c
new file mode 100644
index 0000000..9db40eb
--- /dev/null
+++ b/software/keypress counter/keypress_counter.c	
@@ -0,0 +1,119 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <linux/input.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <termios.h>
+#include <signal.h>
+ 
+
+void perror_exit(char *error)
+{
+    perror(error);
+    printf("\nexiting...(%d)\n", 9);
+    exit(0);
+}
+ 
+int main(int argc, char *argv[])
+{
+    uint32_t counter;
+    struct input_event ev[64];
+    int fd, rd, value;
+    int size = sizeof(struct input_event);
+    char name[256] = "Unknown";
+    char *device = NULL;
+    
+    FILE *out_file; 
+     
+    // Make sure the user is root
+    if ((getuid ()) != 0) 
+    {
+        printf ("\nYou must be root to display keypress counts.\n\n");
+        exit(0);
+    }
+ 
+    // Setup check
+    if (argv[1] == NULL)
+    {
+        printf("Please specify (on the command line) the path to the dev event interface device\n");
+        exit(0);
+    }
+
+    if (argc > 1)
+    {
+        device = argv[1];   
+    }
+ 
+    if (argc > 2) 
+    {
+        out_file = fopen(argv[2], "w");
+        if (out_file == NULL)
+        {
+            printf("Unable to open file %s", argv[2]);
+            exit(0);
+        }
+    }
+    
+    // Open specified device
+    if ((fd = open (device, O_RDONLY)) == -1) 
+    {
+        printf("%s is not a vaild device.\n", device);
+        exit(0);
+    }
+    
+    // Print device name
+    //ioctl (fd, EVIOCGNAME(sizeof(name)), name);
+    //printf ("Reading From : %s (%s)\n", device, name);
+    
+    // Main loop, read
+    counter = 0;
+    while(1)
+    {
+        if ((rd = read(fd, ev, size * 64)) < size) 
+        {
+            perror_exit("read()");
+        }
+    
+        value = ev[0].value;
+    
+        // Only read the key press event
+        if (value != ' ' && ev[1].value == 1 && ev[1].type == 1)
+        { 
+            counter++;
+            
+            // If a filename was given, output to that file
+            if (argc > 2)
+            {
+                // Seek to beginning of file, output and flush to file
+                fseek(out_file, 0, SEEK_SET);
+                fprintf(out_file, "%10u", counter);
+                fflush(out_file);
+            } 
+            else
+            {
+                // Otherwise print to stdout
+                // Print count, fixed width of 10 spaces, over of the same line                
+                printf("\r%10u", counter);
+                fflush(stdout);
+            }
+            
+            // Check for (hopefully rare) overflow condition
+            if (counter == UINT32_MAX)
+            {
+                printf("\nGADZOOKS BATMAN!!! You overflowed a 32 bit unsigned integer with all your keystrokes!\n\n");
+                counter = 0;
+            }
+        }
+    }
+    
+    fclose(out_file);
+    return 0;
+}
diff --git a/software/keypress counter/keypress_wrapper.sh b/software/keypress counter/keypress_wrapper.sh
new file mode 100755
index 0000000..6924680
--- /dev/null
+++ b/software/keypress counter/keypress_wrapper.sh	
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# Keypress counter wrapper
+#
+./keypress_counter /dev/input/by-path/platform-i8042-serio-0-event-kbd /home/dsorber/Desktop/keypress_count.txt &
+KC_PID=$!
+echo $KC_PID
+sleep 30s
+echo "`date +%F` `cat /home/dsorber/Desktop/keypress_count.txt`" >> keypress_totals.txt
+kill -15 `echo $KC_PID`
+
+./keypress_counter /dev/input/by-path/platform-i8042-serio-0-event-kbd /home/dsorber/Desktop/keypress_count.txt &
+KC_PID=$!
+echo $KC_PID
+sleep 30s
+echo "`date +%F` `cat /home/dsorber/Desktop/keypress_count.txt`" >> keypress_totals.txt
+kill -15 `echo $KC_PID`
+
+./keypress_counter /dev/input/by-path/platform-i8042-serio-0-event-kbd /home/dsorber/Desktop/keypress_count.txt &
+KC_PID=$!
+echo $KC_PID
+sleep 30s
+echo "`date +%F` `cat /home/dsorber/Desktop/keypress_count.txt`" >> keypress_totals.txt
+kill -15 `echo $KC_PID`
diff --git a/software/misc/popcount_alg.py b/software/misc/popcount_alg.py
new file mode 100644
index 0000000..25aa368
--- /dev/null
+++ b/software/misc/popcount_alg.py
@@ -0,0 +1,58 @@
+import sys
+
+def popcount(i):
+	i = i - ((i >> 1) & 0x55555555)
+	i = (i & 0x33333333) + ((i >> 2) & 0x33333333)
+	return ((((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) & 0xFFFFFFFF) >> 24
+
+def main():
+	print 'Popcount Algorithm Overview\n'
+	
+	#i = i - ((i >> 1) & 0x55555555);
+    #i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
+    #return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
+	
+	uint = raw_input('Enter a 32 bit unsigned number> ')
+	if uint.startswith('0x'):
+		uint = int(uint, 16) & 0xFFFFFFFF
+	else:
+		uint = int(uint) & 0xFFFFFFFF
+	print
+
+	#i = i - ((i >> 1) & 0x55555555);
+	print 'Step  0: Input number:                                       {:>10d} -- hex: 0x{:08X}'.format(uint, uint)
+	op_step1 = (uint >> 1)
+	print 'Step  1: (i >> 1):                                           {:>10d} -- hex: 0x{:08X}'.format(op_step1, op_step1)
+	op_step2 = op_step1 & 0x55555555
+	print 'Step  2: ((i >> 1) & 0x55555555):                            {:>10d} -- hex: 0x{:08X}'.format(op_step2, op_step2)
+	op_step3 = uint - op_step2
+	print 'Step  3: i - ((i >> 1) & 0x55555555):                        {:>10d} -- hex: 0x{:08X}'.format(op_step3, op_step3)
+	print
+	
+	#i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
+	op_step4 = op_step3 & 0x33333333
+	print 'Step  4: (i & 0x33333333):                                   {:>10d} -- hex: 0x{:08X}'.format(op_step4, op_step4)
+	op_step5 = op_step3 >> 2
+	print 'Step  5: (i >> 2):                                           {:>10d} -- hex: 0x{:08X}'.format(op_step5, op_step5)
+	op_step6 = op_step5 & 0x33333333
+	print 'Step  6: ((i >> 2) & 0x33333333):                            {:>10d} -- hex: 0x{:08X}'.format(op_step6, op_step6)
+	op_step7 = op_step4 + op_step6
+	print 'Step  7: (i & 0x33333333) + ((i >> 2) & 0x33333333):         {:>10d} -- hex: 0x{:08X}'.format(op_step7, op_step7)
+	print
+	
+	#ans = (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24
+	op_step8 = op_step7 >> 4
+	print 'Step  8: (i >> 4):                                           {:>10d} -- hex: 0x{:08X}'.format(op_step8, op_step8)
+	op_step9 = op_step7 + op_step8
+	print 'Step  9: (i + (i >> 4)):                                     {:>10d} -- hex: 0x{:08X}'.format(op_step9, op_step9)
+	op_step10 = op_step9 & 0x0F0F0F0F
+	print 'Step 10: ((i + (i >> 4)) & 0x0F0F0F0F):                      {:>10d} -- hex: 0x{:08X}'.format(op_step10, op_step10)
+	op_step11 = (op_step10 * 0x01010101) & 0xFFFFFFFF
+	print 'Step 11: (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101):       {:>10d} -- hex: 0x{:08X}'.format(op_step11, op_step11)
+	op_step12 = op_step11 >> 24
+	print 'Step 12: (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24: {:>10d} -- hex: 0x{:08X}'.format(op_step12, op_step12)
+	print
+	print 'Popcount: {:d}'.format(popcount(uint))
+	
+if __name__ == '__main__':
+	sys.exit(main())
diff --git a/software/misc/unbruce.py b/software/misc/unbruce.py
new file mode 100644
index 0000000..d70c69a
--- /dev/null
+++ b/software/misc/unbruce.py
@@ -0,0 +1,17 @@
+import os
+import sys
+
+def main():
+
+	for file in os.listdir(sys.argv[1]):
+		if file.startswith('www-bruce-juice-com-') or \
+		   file.startswith('www-bruce-juice-com_'):
+			print '%s ---> %s' % (file, file[20:])
+			os.rename(file, file[20:])
+		elif file.startswith('www-bangtidy-net-') or \
+		     file.startswith('www-bangtidy-net_'):
+			print '%s ---> %s' % (file, file[17:])
+			os.rename(file, file[17:])
+
+if __name__ == '__main__':
+	sys.exit(main())
\ No newline at end of file
diff --git a/software/renamer/renamer.py b/software/renamer/renamer.py
new file mode 100644
index 0000000..f8ec300
--- /dev/null
+++ b/software/renamer/renamer.py
@@ -0,0 +1,29 @@
+import os
+import re
+import sys
+
+def main():
+    
+    if len(sys.argv) < 3:
+        print 'Not enough arguments specified!'
+        return
+
+    show_name = sys.argv[1]
+    directory = sys.argv[2]
+
+    show_name = show_name.replace(' ', '\.')
+
+    for filename in os.listdir(directory):
+        m = re.match("^(%s)\.S(\d+)E(\d+)\.(.*)\.(\w{3})$" % show_name, filename, re.IGNORECASE)
+        if m:
+            new_name = '%s - S%sE%s.%s' % (show_name.replace('\.', ' '), 
+                                           m.group(2), m.group(3), m.group(5).lower())
+
+            print '%s --- MATCH: %s' % (filename, new_name)
+            os.rename(os.path.join(directory, filename), 
+                      os.path.join(directory, new_name))
+        else:
+            print filename
+
+if __name__ == '__main__':
+    sys.exit(main())
\ No newline at end of file
diff --git a/software/similar images/build_index.py b/software/similar images/build_index.py
new file mode 100755
index 0000000..9c6cb0b
--- /dev/null
+++ b/software/similar images/build_index.py	
@@ -0,0 +1,62 @@
+#!/opt/local/bin/python2.7
+import bz2
+import cPickle
+import imghdr
+import os
+import sys
+
+import pHash
+
+from image import Image
+
+# Build index
+# TODO: write more schtuff here
+
+def main():
+
+    image_list = []
+
+    # Make sure at least one command line argument was provided
+    if len(sys.argv) < 2:
+        print 'Usage: build_index.py <path>'
+        return -1;
+
+    # Print out the path we're walking
+    path = sys.argv[1]
+    print 'Building image index from path: {:s}\n'.format(path)
+
+    # Walk the root path and count all images
+    img_counter = 0
+    for root, dirs, files in os.walk(path):
+        for file in files:
+            img_path = os.path.join(root, file)
+            # Check the make sure the file is a JPEG
+            if not imghdr.what(img_path) == 'jpeg':
+                continue
+            img_counter += 1
+    print 'Done counting, {:d} images total.'.format(img_counter)
+
+    # Walk the root path
+    ctr = 0
+    for root, dirs, files in os.walk(path):
+        for file in files:
+            img_path = os.path.join(root, file)
+            # Check the make sure the file is a JPEG
+            if not imghdr.what(img_path) == 'jpeg':
+                continue
+            ctr += 1
+            percent_done = int((float(ctr) / img_counter) * 100)
+            print '{:3d}% -- {:s}'.format(percent_done, img_path)
+            img_obj = Image(img_path)
+            # print '{:s} -> {:016X}'.format(img_path, img_obj.hash)
+            image_list.append(img_obj)
+
+    # Write index as a bzip2'd file to save space
+    index_file = bz2.BZ2File('images_index.bz2', 'w')
+    cPickle.dump(image_list, index_file)
+    index_file.close()
+
+    print '\nIndex file images_index.bz2 created. Done.'
+
+if __name__ == '__main__':
+    sys.exit(main())
\ No newline at end of file
diff --git a/software/similar images/build_index_faster.py b/software/similar images/build_index_faster.py
new file mode 100755
index 0000000..a3ab147
--- /dev/null
+++ b/software/similar images/build_index_faster.py	
@@ -0,0 +1,106 @@
+#!/opt/local/bin/python2.7
+import bz2
+import cPickle
+import imghdr
+import math
+import multiprocessing
+import os
+import Queue
+import random
+import sys
+
+import pHash
+
+from image import Image
+
+NUM_WORKERS = 7
+
+# Build index
+# TODO: write more schtuff here
+
+class IndexWorker(multiprocessing.Process):
+
+    def __init__(self, file_list, queue):
+        multiprocessing.Process.__init__(self)
+        self.file_list = file_list
+        self.queue = queue
+
+    def run(self):
+
+        list_size = len(self.file_list)
+        ctr = 0
+        for file_path in self.file_list:
+            ctr += 1
+            percent_done = int((float(ctr) / list_size) * 100)
+            # We can only index jpegs right now
+            if not imghdr.what(file_path) == 'jpeg':
+                continue
+            print '{:d} -- {:3d}% -- {:s}'.format(self.pid, percent_done, 
+                                                  file_path)
+            image_obj = Image(file_path)
+
+            self.queue.put(image_obj, True)
+
+
+
+def main():
+
+    # Make sure at least one command line argument was provided
+    if len(sys.argv) < 2:
+        print 'Usage: build_index_faster.py <path>'
+        return -1;
+
+    # Print out the path we're walking
+    path = sys.argv[1]
+    print 'Building image index from path: {:s}\n'.format(path)
+
+    master_file_list = []
+    image_index = []
+
+    # Walk the root path and create a list of all files in the root path
+    for root, dirs, files in os.walk(path):
+        for file in files:
+            master_file_list.append(os.path.join(root, file))
+
+    random.shuffle(master_file_list)
+
+    print 'Done counting, {:d} files total.'.format(len(master_file_list))
+
+    workers = []
+    queue = multiprocessing.Queue(64)
+
+    def chunker(seq, size):
+        return (seq[pos:pos + size] for pos in xrange(0, len(seq), size))
+
+    chunk_size = int(math.ceil(len(master_file_list) / NUM_WORKERS))
+
+    # Create worker processes
+    for work_chunk in chunker(master_file_list, chunk_size):
+        workers.append(IndexWorker(work_chunk, queue))
+
+    # Start workers
+    for worker in workers:
+        worker.start()
+
+    print 'Waiting for workers to complete'
+
+    # Loop until all workers are finished
+    while any([w.is_alive() for w in workers]):
+        try:
+            image_obj = queue.get(True, 2)
+            image_index.append(image_obj)
+        except Queue.Empty:
+            print 'Queue was empty'
+            continue
+
+    print 'Workers are complete: {:d} images found'.format(len(image_index))
+
+    # Write index as a bzip2'd file to save space
+    index_file = bz2.BZ2File('images_index.bz2', 'w')
+    cPickle.dump(image_index, index_file)
+    index_file.close()
+
+    print '\nIndex file images_index.bz2 created. Done.'
+
+if __name__ == '__main__':
+    sys.exit(main())
\ No newline at end of file
diff --git a/software/similar images/find_duplicates_simple.py b/software/similar images/find_duplicates_simple.py
new file mode 100755
index 0000000..c29e68f
--- /dev/null
+++ b/software/similar images/find_duplicates_simple.py	
@@ -0,0 +1,50 @@
+#!/opt/local/bin/python2.7
+from collections import defaultdict
+import imghdr
+import os
+import sys
+
+import pHash
+
+# Find Duplicates Simple
+# Attempt to find images by looking for exact hash collisions.
+# This method is not very sophisticated and does not work all
+# that well.
+
+def main():
+
+    img_map = defaultdict(list)
+
+    # Make sure at least one command line argument was provided
+    if len(sys.argv) < 2:
+        print 'Usage: find_duplicate_simple <path>'
+        return -1;
+
+    # Print out the path we're walking
+    path = sys.argv[1]
+    print 'Step 1: Walk path: {:s}\n'.format(path)
+
+    # Walk the root path
+    for root, dirs, files in os.walk(path):
+        for file in files:
+            img_path = os.path.join(root, file)
+            # Check the make sure the file is an actual image
+            if not imghdr.what(img_path):
+                continue
+            img_hash = pHash.imagehash(img_path)
+            print '{:s} -> {:016X}'.format(img_path, img_hash)
+            img_map[img_hash].append(img_path)
+
+
+    print '\n\nStep 2: find duplicates\n'
+
+    # Now look through the image hash dictionary for matches
+    for img_hash, files in img_map.items():
+        if len(files) > 2:
+            print '{:016X}'.format(img_hash)
+            for file_path in files:
+                print '\t{:s}'.format(file_path)
+
+
+if __name__ == '__main__':
+    sys.exit(main())
\ No newline at end of file
diff --git a/software/similar images/find_similar.py b/software/similar images/find_similar.py
new file mode 100755
index 0000000..15ec184
--- /dev/null
+++ b/software/similar images/find_similar.py	
@@ -0,0 +1,98 @@
+#!/opt/local/bin/python2.7
+import bz2
+from collections import defaultdict
+import cPickle
+import imghdr
+import os
+import sys
+
+import pHash
+
+from image import Image
+
+HAMMING_THRESHOLD = 10
+CORRELATION_THRESHOLD = 70.0
+
+# Find duplicates by looking over index
+# write some description
+
+def find_similar(total_done, index, image):
+    index_length = len(index)
+    image_digest = image.make_digest()
+    
+    # Search the index
+    ctr = 0
+    for index_image in index:
+        ctr += 1
+        percent_done = int((float(ctr) / index_length) * 100)
+        sys.stdout.write('\r\tTotal: {:3d}% --- This image: {:3d}%'.format(total_done, percent_done))
+        sys.stdout.flush()
+        # Original might be in index, skip it
+        if image.hash == index_image.hash and image.path == index_image.path:
+            continue
+        
+        # Hamming distance hash compare
+        hamming_distance = pHash.hamming_distance(image.hash, index_image.hash)
+        
+        # Cross correlation compare
+        correlation = pHash.crosscorr(image_digest, index_image.make_digest())[1]
+
+        # Determine if comparison criteria are within the thresholds
+        if hamming_distance <= HAMMING_THRESHOLD or \
+           correlation >= CORRELATION_THRESHOLD:
+            sys.stdout.write('\r\t--- SIMILAR IMAGE FOUND ---              \n')
+            print '\tHamming distance: {:d}'.format(hamming_distance)
+            print '\tCross correlation: {:f}'.format(correlation)
+            print '\tImage path: {:s}\n'.format(index_image.path)
+    print
+
+
+def main():
+
+    # Make sure at least one command line argument was provided
+    if len(sys.argv) < 3:
+        print 'Usage: find_similar.py <image dir root path> <index path>'
+        return -1;
+
+    # Print out the path we're walking
+    root_path = sys.argv[1]
+    index_path = sys.argv[2]
+
+    # Load image index
+    index_file = bz2.BZ2File(index_path, 'r')
+    image_index = cPickle.load(index_file)
+    index_file.close()
+
+    print 'Looking for similar images from from path: {:s}'.format(root_path)
+
+    # Walk the root path and count all images
+    # (is there a better way to do this?)
+    image_counter = 0
+    for root, dirs, files in os.walk(root_path):
+        for file in files:
+            image_path = os.path.join(root, file)
+            # Check the make sure the file is a JPEG
+            if not imghdr.what(image_path) == 'jpeg':
+                continue
+            image_counter += 1
+    print 'Done counting, {:d} images total.'.format(image_counter)
+
+    # Walk the root path
+    ctr = 0
+    for root, dirs, files in os.walk(root_path):
+        for file in files:
+            image_path = os.path.join(root, file)
+            # Check the make sure the file is a JPEG
+            if not imghdr.what(image_path) == 'jpeg':
+                continue
+            ctr += 1
+            percent_done = int((float(ctr) / image_counter) * 100)
+            print '\n\nLooking for similar -- {:s}'.format(image_path)
+            image_obj = Image(image_path)
+            # Find similar images by searching the index
+            find_similar(percent_done, image_index, image_obj)
+
+    print '\nDone.'
+
+if __name__ == '__main__':
+    sys.exit(main())
\ No newline at end of file
diff --git a/software/similar images/image.py b/software/similar images/image.py
new file mode 100644
index 0000000..692a898
--- /dev/null
+++ b/software/similar images/image.py	
@@ -0,0 +1,29 @@
+import copy
+
+import pHash
+
+class Image(object):
+
+	def __init__(self, path, hash=None, coefficients=None):
+		self.path = path
+
+		if not hash:
+			self.hash = pHash.imagehash(self.path)
+		else:
+			self.hash = hash
+
+		if not coefficients:
+			self.coefficients = copy.copy(pHash.image_digest(self.path, 1.0, 1.0, 180).coeffs)
+		else:
+			self.coefficients = coefficients
+
+	def make_digest(self):
+		digest = pHash.Digest()
+		digest.coeffs = copy.copy(self.coefficients)
+		digest.size = len(self.coefficients)
+		return digest
+
+	def __repr__(self):
+		print 'Image: {:s}'.format(self.path)
+		print '\tHash: {:016X}'.format(self.hash)
+		print '\tDigest coefficients: {:s}'.format(str(self.coefficients))
diff --git a/software/similar images/image.pyc b/software/similar images/image.pyc
new file mode 100644
index 0000000..40d89b5
Binary files /dev/null and b/software/similar images/image.pyc differ
diff --git a/software/similar images/test.py b/software/similar images/test.py
new file mode 100644
index 0000000..fdde56e
--- /dev/null
+++ b/software/similar images/test.py	
@@ -0,0 +1,14 @@
+import sys
+
+import pHash
+
+def main():
+	hash1 = pHash.imagehash(sys.argv[1])
+	hash2 = pHash.imagehash(sys.argv[2])
+
+	print 'Image 1: {:X}'.format(hash1)
+	print 'Image 2: {:X}'.format(hash2)
+	print 'Distance {:d}'.format(pHash.hamming_distance(hash2, hash1))
+
+if __name__ == '__main__':
+	sys.exit(main())
\ No newline at end of file
