diff --git a/airtime_mvc/application/configs/constants.php b/airtime_mvc/application/configs/constants.php
index 1d1ed55e4..eb5cb3989 100644
--- a/airtime_mvc/application/configs/constants.php
+++ b/airtime_mvc/application/configs/constants.php
@@ -1,40 +1,40 @@
 <?php
 
-define('AIRTIME_COPYRIGHT_DATE', '2010-2012');
-define('AIRTIME_REST_VERSION', '1.1');
-define('AIRTIME_API_VERSION', '1.1');
+define('AIRTIME_COPYRIGHT_DATE' , '2010-2012');
+define('AIRTIME_REST_VERSION'   , '1.1');
+define('AIRTIME_API_VERSION'    , '1.1');
 
 // Metadata Keys for files
-define('MDATA_KEY_FILEPATH', 'filepath');
-define('MDATA_KEY_DIRECTORY', 'directory');
-define('MDATA_KEY_MD5', 'md5');
-define('MDATA_KEY_TITLE', 'track_title');
-define('MDATA_KEY_CREATOR', 'artist_name');
-define('MDATA_KEY_SOURCE', 'album_title');
-define('MDATA_KEY_DURATION', 'length');
-define('MDATA_KEY_MIME', 'mime');
-define('MDATA_KEY_FTYPE', 'ftype');
-define('MDATA_KEY_URL', 'info_url');
-define('MDATA_KEY_GENRE', 'genre');
-define('MDATA_KEY_MOOD', 'mood');
-define('MDATA_KEY_LABEL', 'label');
-define('MDATA_KEY_COMPOSER', 'composer');
-define('MDATA_KEY_DESCRIPTION', 'description');
-define('MDATA_KEY_SAMPLERATE', 'sample_rate');
-define('MDATA_KEY_BITRATE', 'bit_rate');
-define('MDATA_KEY_ENCODER', 'encoded_by');
-define('MDATA_KEY_ISRC', 'isrc_number');
-define('MDATA_KEY_COPYRIGHT', 'copyright');
-define('MDATA_KEY_YEAR', 'year');
-define('MDATA_KEY_BPM', 'bpm');
-define('MDATA_KEY_TRACKNUMBER', 'track_number');
-define('MDATA_KEY_CONDUCTOR', 'conductor');
-define('MDATA_KEY_LANGUAGE', 'language');
-define('MDATA_KEY_REPLAYGAIN', 'replay_gain');
-define('MDATA_KEY_OWNER_ID', 'owner_id');
+define('MDATA_KEY_FILEPATH'    , 'filepath');
+define('MDATA_KEY_DIRECTORY'   , 'directory');
+define('MDATA_KEY_MD5'         , 'md5');
+define('MDATA_KEY_TITLE'       , 'track_title');
+define('MDATA_KEY_CREATOR'     , 'artist_name');
+define('MDATA_KEY_SOURCE'      , 'album_title');
+define('MDATA_KEY_DURATION'    , 'length');
+define('MDATA_KEY_MIME'        , 'mime');
+define('MDATA_KEY_FTYPE'       , 'ftype');
+define('MDATA_KEY_URL'         , 'info_url');
+define('MDATA_KEY_GENRE'       , 'genre');
+define('MDATA_KEY_MOOD'        , 'mood');
+define('MDATA_KEY_LABEL'       , 'label');
+define('MDATA_KEY_COMPOSER'    , 'composer');
+define('MDATA_KEY_DESCRIPTION' , 'description');
+define('MDATA_KEY_SAMPLERATE'  , 'sample_rate');
+define('MDATA_KEY_BITRATE'     , 'bit_rate');
+define('MDATA_KEY_ENCODER'     , 'encoded_by');
+define('MDATA_KEY_ISRC'        , 'isrc_number');
+define('MDATA_KEY_COPYRIGHT'   , 'copyright');
+define('MDATA_KEY_YEAR'        , 'year');
+define('MDATA_KEY_BPM'         , 'bpm');
+define('MDATA_KEY_TRACKNUMBER' , 'track_number');
+define('MDATA_KEY_CONDUCTOR'   , 'conductor');
+define('MDATA_KEY_LANGUAGE'    , 'language');
+define('MDATA_KEY_REPLAYGAIN'  , 'replay_gain');
+define('MDATA_KEY_OWNER_ID'    , 'owner_id');
 
-define('UI_MDATA_VALUE_FORMAT_FILE', 'File');
-define('UI_MDATA_VALUE_FORMAT_STREAM', 'live stream');
+define('UI_MDATA_VALUE_FORMAT_FILE'   , 'File');
+define('UI_MDATA_VALUE_FORMAT_STREAM' , 'live stream');
 
 // Session Keys
 define('UI_PLAYLISTCONTROLLER_OBJ_SESSNAME', 'PLAYLISTCONTROLLER_OBJ');
@@ -43,6 +43,6 @@ define('UI_BLOCK_SESSNAME', 'BLOCK');*/
 
 
 // Soundcloud contants
-define('SOUNDCLOUD_NOT_UPLOADED_YET', -1);
-define('SOUNDCLOUD_PROGRESS', -2);
-define('SOUNDCLOUD_ERROR', -3);
+define('SOUNDCLOUD_NOT_UPLOADED_YET' , -1);
+define('SOUNDCLOUD_PROGRESS'         , -2);
+define('SOUNDCLOUD_ERROR'            , -3);
diff --git a/airtime_mvc/application/models/Scheduler.php b/airtime_mvc/application/models/Scheduler.php
index 664c40504..def7fb045 100644
--- a/airtime_mvc/application/models/Scheduler.php
+++ b/airtime_mvc/application/models/Scheduler.php
@@ -366,12 +366,11 @@ class Application_Model_Scheduler
      * @param array $fileIds
      * @param array $playlistIds
      */
-    private function insertAfter($scheduleItems, $schedFiles, $adjustSched = true)
+    private function insertAfter($scheduleItems, $schedFiles, $adjustSched = true, $mediaItems = null)
     {
         try {
-
             $affectedShowInstances = array();
-
+            
             //dont want to recalculate times for moved items.
             $excludeIds = array();
             foreach ($schedFiles as $file) {
@@ -384,7 +383,17 @@ class Application_Model_Scheduler
 
             foreach ($scheduleItems as $schedule) {
                 $id = intval($schedule["id"]);
-
+                
+                // if mediaItmes is passed in, we want to create contents
+                // at the time of insert. This is for dyanmic blocks or
+                // playlist that contains dynamic blocks
+                if ($mediaItems != null) {
+                    $schedFiles = array();
+                    foreach ($mediaItems as $media) {
+                        $schedFiles = array_merge($schedFiles, $this->retrieveMediaFiles($media["id"], $media["type"]));
+                    }
+                }
+                
                 if ($id !== 0) {
                     $schedItem = CcScheduleQuery::create()->findPK($id, $this->con);
                     $instance = $schedItem->getCcShowInstances($this->con);
@@ -527,10 +536,32 @@ class Application_Model_Scheduler
 
             $this->validateRequest($scheduleItems);
 
+            $requireDynamicContentCreation = false;
+            
             foreach ($mediaItems as $media) {
-                $schedFiles = array_merge($schedFiles, $this->retrieveMediaFiles($media["id"], $media["type"]));
+                if ($media['type'] == "playlist") {
+                    $pl = new Application_Model_Playlist($media['id']);
+                    if ($pl->hasDynamicBlock()) {
+                        $requireDynamicContentCreation = true;
+                        break;
+                    }
+                } else if ($media['type'] == "block") {
+                    $bl = new Application_Model_Block($media['id']);
+                    if (!$bl->isStatic()) {
+                        $requireDynamicContentCreation = true;
+                        break;
+                    }
+                }
+            }
+            
+            if ($requireDynamicContentCreation) {
+                $this->insertAfter($scheduleItems, $schedFiles, $adjustSched, $mediaItems);
+            } else {
+                foreach ($mediaItems as $media) {
+                    $schedFiles = array_merge($schedFiles, $this->retrieveMediaFiles($media["id"], $media["type"]));
+                }
+                $this->insertAfter($scheduleItems, $schedFiles, $adjustSched);
             }
-            $this->insertAfter($scheduleItems, $schedFiles, $adjustSched);
 
             $this->con->commit();
 
diff --git a/airtime_mvc/public/js/airtime/common/common.js b/airtime_mvc/public/js/airtime/common/common.js
index 3125d8d55..0d9883b66 100644
--- a/airtime_mvc/public/js/airtime/common/common.js
+++ b/airtime_mvc/public/js/airtime/common/common.js
@@ -54,7 +54,10 @@ function open_audio_preview(type, id, audioFileTitle, audioFileArtist) {
         audioFileTitle = audioFileTitle.substring(0,index);
     }
 
-    openPreviewWindow('audiopreview/audio-preview/audioFileID/'+id+'/audioFileArtist/'+encodeURIComponent(audioFileArtist)+'/audioFileTitle/'+encodeURIComponent(audioFileTitle)+'/type/'+type);
+    // The reason that we need to encode artist and title string is that
+    // sometime they contain '/' or '\' and apache reject %2f or %5f
+    // so the work around is to encode it twice.
+    openPreviewWindow('audiopreview/audio-preview/audioFileID/'+id+'/audioFileArtist/'+encodeURIComponent(encodeURIComponent(audioFileArtist))+'/audioFileTitle/'+encodeURIComponent(encodeURIComponent(audioFileTitle))+'/type/'+type);
 
     _preview_window.focus();
 }
diff --git a/python_apps/media-monitor2/media/metadata/definitions.py b/python_apps/media-monitor2/media/metadata/definitions.py
index 51ef6fd14..ec3bed48f 100644
--- a/python_apps/media-monitor2/media/metadata/definitions.py
+++ b/python_apps/media-monitor2/media/metadata/definitions.py
@@ -1,111 +1,143 @@
 # -*- coding: utf-8 -*-
-import media.monitor.process as md
+import media.metadata.process as md
+import re
 from os.path import normpath
-from media.monitor.pure import format_length, file_md5
+from media.monitor.pure import format_length, file_md5, is_airtime_recorded, \
+    no_extension_basename
 
-with md.metadata('MDATA_KEY_DURATION') as t:
-    t.default(u'0.0')
-    t.depends('length')
-    t.translate(lambda k: format_length(k['length']))
+defs_loaded = False
 
-with md.metadata('MDATA_KEY_MIME') as t:
-    t.default(u'')
-    t.depends('mime')
-    t.translate(lambda k: k['mime'].replace('-','/'))
+def is_defs_loaded():
+    global defs_loaded
+    return defs_loaded
 
-with md.metadata('MDATA_KEY_BITRATE') as t:
-    t.default(u'')
-    t.depends('bitrate')
-    t.translate(lambda k: k['bitrate'])
+def load_definitions():
+    with md.metadata('MDATA_KEY_DURATION') as t:
+        t.default(u'0.0')
+        t.depends('length')
+        t.translate(lambda k: format_length(k['length']))
 
-with md.metadata('MDATA_KEY_SAMPLERATE') as t:
-    t.default(u'0')
-    t.depends('sample_rate')
-    t.translate(lambda k: k['sample_rate'])
+    with md.metadata('MDATA_KEY_MIME') as t:
+        t.default(u'')
+        t.depends('mime')
+        # Is this necessary?
+        t.translate(lambda k: k['mime'].replace('audio/vorbis','audio/ogg'))
 
-with md.metadata('MDATA_KEY_FTYPE'):
-    t.depends('ftype') # i don't think this field even exists
-    t.default(u'audioclip')
-    t.translate(lambda k: k['ftype']) # but just in case
+    with md.metadata('MDATA_KEY_BITRATE') as t:
+        t.default(u'')
+        t.depends('bitrate')
+        t.translate(lambda k: k['bitrate'])
 
-with md.metadata("MDATA_KEY_CREATOR") as t:
-    t.depends("artist")
-    # A little kludge to make sure that we have some value for when we parse
-    # MDATA_KEY_TITLE
-    t.default(u"")
-    t.max_length(512)
+    with md.metadata('MDATA_KEY_SAMPLERATE') as t:
+        t.default(u'0')
+        t.depends('sample_rate')
+        t.translate(lambda k: k['sample_rate'])
 
-with md.metadata("MDATA_KEY_SOURCE") as t:
-    t.depends("album")
-    t.max_length(512)
+    with md.metadata('MDATA_KEY_FTYPE') as t:
+        t.depends('ftype') # i don't think this field even exists
+        t.default(u'audioclip')
+        t.translate(lambda k: k['ftype']) # but just in case
 
-with md.metadata("MDATA_KEY_GENRE") as t:
-    t.depends("genre")
-    t.max_length(64)
+    with md.metadata("MDATA_KEY_CREATOR") as t:
+        t.depends("artist")
+        # A little kludge to make sure that we have some value for when we parse
+        # MDATA_KEY_TITLE
+        t.default(u"")
+        t.max_length(512)
 
-with md.metadata("MDATA_KEY_MOOD") as t:
-    t.depends("mood")
-    t.max_length(64)
+    with md.metadata("MDATA_KEY_SOURCE") as t:
+        t.depends("album")
+        t.max_length(512)
 
-with md.metadata("MDATA_KEY_TRACKNUMBER") as t:
-    t.depends("tracknumber")
+    with md.metadata("MDATA_KEY_GENRE") as t:
+        t.depends("genre")
+        t.max_length(64)
 
-with md.metadata("MDATA_KEY_BPM") as t:
-    t.depends("bpm")
-    t.max_length(8)
+    with md.metadata("MDATA_KEY_MOOD") as t:
+        t.depends("mood")
+        t.max_length(64)
 
-with md.metadata("MDATA_KEY_LABEL") as t:
-    t.depends("organization")
-    t.max_length(512)
+    with md.metadata("MDATA_KEY_TRACKNUMBER") as t:
+        t.depends("tracknumber")
 
-with md.metadata("MDATA_KEY_COMPOSER") as t:
-    t.depends("composer")
-    t.max_length(512)
+    with md.metadata("MDATA_KEY_BPM") as t:
+        t.depends("bpm")
+        t.max_length(8)
 
-with md.metadata("MDATA_KEY_ENCODER") as t:
-    t.depends("encodedby")
-    t.max_length(512)
+    with md.metadata("MDATA_KEY_LABEL") as t:
+        t.depends("organization")
+        t.max_length(512)
 
-with md.metadata("MDATA_KEY_CONDUCTOR") as t:
-    t.depends("conductor")
-    t.max_length(512)
+    with md.metadata("MDATA_KEY_COMPOSER") as t:
+        t.depends("composer")
+        t.max_length(512)
 
-with md.metadata("MDATA_KEY_YEAR") as t:
-    t.depends("date")
-    t.max_length(16)
+    with md.metadata("MDATA_KEY_ENCODER") as t:
+        t.depends("encodedby")
+        t.max_length(512)
 
-with md.metadata("MDATA_KEY_URL") as t:
-    t.depends("website")
+    with md.metadata("MDATA_KEY_CONDUCTOR") as t:
+        t.depends("conductor")
+        t.max_length(512)
 
-with md.metadata("MDATA_KEY_ISRC") as t:
-    t.depends("isrc")
-    t.max_length(512)
+    with md.metadata("MDATA_KEY_YEAR") as t:
+        t.depends("date")
+        t.max_length(16)
 
-with md.metadata("MDATA_KEY_COPYRIGHT") as t:
-    t.depends("copyright")
-    t.max_length(512)
+    with md.metadata("MDATA_KEY_URL") as t:
+        t.depends("website")
 
-with md.metadata("MDATA_KEY_FILEPATH") as t:
-    t.depends('path')
-    t.translate(lambda k: normpath(k['path']))
+    with md.metadata("MDATA_KEY_ISRC") as t:
+        t.depends("isrc")
+        t.max_length(512)
 
-with md.metadata("MDATA_KEY_MD5") as t:
-    t.depends('path')
-    t.optional(False)
-    t.translate(lambda k: file_md5(k['path'], max_length=100))
+    with md.metadata("MDATA_KEY_COPYRIGHT") as t:
+        t.depends("copyright")
+        t.max_length(512)
 
-# owner is handled differently by (by events.py)
+    with md.metadata("MDATA_KEY_ORIGINAL_PATH") as t:
+        t.depends('path')
+        t.translate(lambda k: unicode(normpath(k['path'])))
 
-with md.metadata('MDATA_KEY_ORIGINAL_PATH') as t:
-    t.depends('original_path')
+    with md.metadata("MDATA_KEY_MD5") as t:
+        t.depends('path')
+        t.optional(False)
+        t.translate(lambda k: file_md5(k['path'], max_length=100))
 
-# MDATA_KEY_TITLE is the annoying special case
-with md.metadata('MDATA_KEY_TITLE') as t:
-    # Need to know MDATA_KEY_CREATOR to know if show was recorded. Value is
-    # defaulted to "" from definitions above
-    t.depends('title','MDATA_KEY_CREATOR')
-    t.max_length(512)
+    # owner is handled differently by (by events.py)
 
-with md.metadata('MDATA_KEY_LABEL') as t:
-    t.depends('label')
-    t.max_length(512)
+    # MDATA_KEY_TITLE is the annoying special case b/c we sometimes read it
+    # from file name
+
+
+    # must handle 3 cases:
+    # 1. regular case (not recorded + title is present)
+    # 2. title is absent (read from file)
+    # 3. recorded file
+    def tr_title(k):
+        #unicode_unknown = u"unknown"
+        new_title = u""
+        if is_airtime_recorded(k) or k['title'] != u"":
+            new_title = k['title']
+        else:
+            default_title = no_extension_basename(k['path'])
+            default_title = re.sub(r'__\d+\.',u'.', default_title)
+
+            # format is: track_number-title-123kbps.mp3
+            m = re.match(".+?-(?P<title>.+)-(\d+kbps|unknown)$", default_title)
+            if m: new_title = m.group('title')
+            else: new_title = re.sub(r'-\d+kbps$', u'', default_title)
+
+        return new_title
+
+    with md.metadata('MDATA_KEY_TITLE') as t:
+        # Need to know MDATA_KEY_CREATOR to know if show was recorded. Value is
+        # defaulted to "" from definitions above
+        t.depends('title','MDATA_KEY_CREATOR','path')
+        t.optional(False)
+        t.translate(tr_title)
+        t.max_length(512)
+
+    with md.metadata('MDATA_KEY_LABEL') as t:
+        t.depends('label')
+        t.max_length(512)
diff --git a/python_apps/media-monitor2/media/metadata/process.py b/python_apps/media-monitor2/media/metadata/process.py
index a4c361468..b500d029d 100644
--- a/python_apps/media-monitor2/media/metadata/process.py
+++ b/python_apps/media-monitor2/media/metadata/process.py
@@ -1,14 +1,36 @@
 # -*- coding: utf-8 -*-
 from contextlib import contextmanager
 from media.monitor.pure import truncate_to_length, toposort
+from os.path import normpath
+from media.monitor.exceptions import BadSongFile
+from media.monitor.log import Loggable
+import media.monitor.pure as mmp
+from collections     import namedtuple
 import mutagen
 
+class FakeMutagen(dict):
+    """
+    Need this fake mutagen object so that airtime_special functions
+    return a proper default value instead of throwing an exceptions for
+    files that mutagen doesn't recognize
+    """
+    FakeInfo = namedtuple('FakeInfo','length bitrate')
+    def __init__(self,path):
+        self.path = path
+        self.mime = ['audio/wav']
+        self.info = FakeMutagen.FakeInfo(0.0, '')
+        dict.__init__(self)
+    def set_length(self,l):
+        old_bitrate = self.info.bitrate
+        self.info = FakeMutagen.FakeInfo(l, old_bitrate)
+
 
 class MetadataAbsent(Exception):
     def __init__(self, name): self.name = name
     def __str__(self): return "Could not obtain element '%s'" % self.name
 
-class MetadataElement(object):
+class MetadataElement(Loggable):
+
     def __init__(self,name):
         self.name = name
         # "Sane" defaults
@@ -18,6 +40,7 @@ class MetadataElement(object):
         self.__default       = None
         self.__is_normalized = lambda _ : True
         self.__max_length    = -1
+        self.__translator    = None
 
     def max_length(self,l):
         self.__max_length = l
@@ -57,31 +80,64 @@ class MetadataElement(object):
         return self.__path
 
     def __slice_deps(self, d):
+        """
+        returns a dictionary of all the key value pairs in d that are also
+        present in self.__deps
+        """
         return dict( (k,v) for k,v in d.iteritems() if k in self.__deps)
 
     def __str__(self):
         return "%s(%s)" % (self.name, ' '.join(list(self.__deps)))
 
     def read_value(self, path, original, running={}):
-        # If value is present and normalized then we don't touch it
+
+        # If value is present and normalized then we only check if it's
+        # normalized or not. We normalize if it's not normalized already
+
+
         if self.name in original:
             v = original[self.name]
             if self.__is_normalized(v): return v
             else: return self.__normalizer(v)
 
-        # A dictionary slice with all the dependencies and their values
+        # We slice out only the dependencies that are required for the metadata
+        # element.
         dep_slice_orig    = self.__slice_deps(original)
         dep_slice_running = self.__slice_deps(running)
+        # TODO : remove this later
+        dep_slice_special = self.__slice_deps({'path' : path})
+        # We combine all required dependencies into a single dictionary
+        # that we will pass to the translator
         full_deps         = dict( dep_slice_orig.items()
-                                + dep_slice_running.items() )
+                                + dep_slice_running.items()
+                                + dep_slice_special.items())
 
         # check if any dependencies are absent
-        if len(full_deps) != len(self.__deps) or len(self.__deps) == 0:
+        # note: there is no point checking the case that len(full_deps) >
+        # len(self.__deps) because we make sure to "slice out" any supefluous
+        # dependencies above.
+        if len(full_deps) != len(self.dependencies()) or \
+            len(self.dependencies()) == 0:
             # If we have a default value then use that. Otherwise throw an
             # exception
             if self.has_default(): return self.get_default()
             else: raise MetadataAbsent(self.name)
+
         # We have all dependencies. Now for actual for parsing
+        def def_translate(dep):
+            def wrap(k):
+                e = [ x for x in dep ][0]
+                return k[e]
+            return wrap
+
+        # Only case where we can select a default translator
+        if self.__translator is None:
+            self.translate(def_translate(self.dependencies()))
+            if len(self.dependencies()) > 2: # dependencies include themselves
+                self.logger.info("Ignoring some dependencies in translate %s"
+                                 % self.name)
+                self.logger.info(self.dependencies())
+
         r = self.__normalizer( self.__translator(full_deps) )
         if self.__max_length != -1:
             r = truncate_to_length(r, self.__max_length)
@@ -92,24 +148,40 @@ def normalize_mutagen(path):
     Consumes a path and reads the metadata using mutagen. normalizes some of
     the metadata that isn't read through the mutagen hash
     """
-    m = mutagen.File(path, easy=True)
+    if not mmp.file_playable(path): raise BadSongFile(path)
+    try              : m = mutagen.File(path, easy=True)
+    except Exception : raise BadSongFile(path)
+    if m is None: m = FakeMutagen(path)
+    try:
+        if mmp.extension(path) == 'wav':
+            m.set_length(mmp.read_wave_duration(path))
+    except Exception: raise BadSongFile(path)
     md = {}
     for k,v in m.iteritems():
-        if type(v) is list: md[k] = v[0]
+        if type(v) is list:
+            if len(v) > 0: md[k] = v[0]
         else: md[k] = v
     # populate special metadata values
-    md['length']      = getattr(m.info, u'length', 0.0)
+    md['length']      = getattr(m.info, 'length', 0.0)
     md['bitrate']     = getattr(m.info, 'bitrate', u'')
     md['sample_rate'] = getattr(m.info, 'sample_rate', 0)
     md['mime']        = m.mime[0] if len(m.mime) > 0 else u''
-    md['path']        = path
+    md['path']        = normpath(path)
+    if 'title' not in md: md['title']  = u''
     return md
 
+
+class OverwriteMetadataElement(Exception):
+    def __init__(self, m): self.m = m
+    def __str__(self): return "Trying to overwrite: %s" % self.m
+
 class MetadataReader(object):
     def __init__(self):
         self.clear()
 
     def register_metadata(self,m):
+        if m in self.__mdata_name_map:
+            raise OverwriteMetadataElement(m)
         self.__mdata_name_map[m.name] = m
         d = dict( (name,m.dependencies()) for name,m in
                 self.__mdata_name_map.iteritems() )
@@ -131,6 +203,9 @@ class MetadataReader(object):
                 if not mdata.is_optional(): raise
         return normalized_metadata
 
+    def read_mutagen(self, path):
+        return self.read(path, normalize_mutagen(path))
+
 global_reader = MetadataReader()
 
 @contextmanager
diff --git a/python_apps/media-monitor2/media/monitor/listeners.py b/python_apps/media-monitor2/media/monitor/listeners.py
index 4c860a97e..b33a5c1a9 100644
--- a/python_apps/media-monitor2/media/monitor/listeners.py
+++ b/python_apps/media-monitor2/media/monitor/listeners.py
@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 import pyinotify
 from pydispatch import dispatcher
+from functools import wraps
 
 import media.monitor.pure as mmp
 from media.monitor.pure import IncludeOnly
@@ -31,6 +32,7 @@ class FileMediator(object):
     def unignore(path): FileMediator.ignored_set.remove(path)
 
 def mediate_ignored(fn):
+    @wraps(fn)
     def wrapped(self, event, *args,**kwargs):
         event.pathname = unicode(event.pathname, "utf-8")
         if FileMediator.is_ignored(event.pathname):
diff --git a/python_apps/media-monitor2/media/monitor/log.py b/python_apps/media-monitor2/media/monitor/log.py
index 5d632bbf4..7e75c719d 100644
--- a/python_apps/media-monitor2/media/monitor/log.py
+++ b/python_apps/media-monitor2/media/monitor/log.py
@@ -6,37 +6,30 @@ from media.monitor.pure import LazyProperty
 appname = 'root'
 
 def setup_logging(log_path):
-    """
-    Setup logging by writing log to 'log_path'
-    """
+    """ Setup logging by writing log to 'log_path' """
     #logger = logging.getLogger(appname)
     logging.basicConfig(filename=log_path, level=logging.DEBUG)
 
 def get_logger():
-    """
-    in case we want to use the common logger from a procedural interface
-    """
+    """ in case we want to use the common logger from a procedural
+    interface """
     return logging.getLogger()
 
 class Loggable(object):
-    """
-    Any class that wants to log can inherit from this class and automatically
-    get a logger attribute that can be used like: self.logger.info(...) etc.
-    """
+    """ Any class that wants to log can inherit from this class and
+    automatically get a logger attribute that can be used like:
+    self.logger.info(...) etc. """
     __metaclass__ = abc.ABCMeta
     @LazyProperty
     def logger(self): return get_logger()
 
     def unexpected_exception(self,e):
-        """
-        Default message for 'unexpected' exceptions
-        """
+        """ Default message for 'unexpected' exceptions """
         self.fatal_exception("'Unexpected' exception has occured:", e)
 
     def fatal_exception(self, message, e):
-        """
-        Prints an exception 'e' with 'message'. Also outputs the traceback.
-        """
+        """ Prints an exception 'e' with 'message'. Also outputs the
+        traceback. """
         self.logger.error( message )
         self.logger.error( str(e) )
         self.logger.error( traceback.format_exc() )
diff --git a/python_apps/media-monitor2/media/monitor/metadata.py b/python_apps/media-monitor2/media/monitor/metadata.py
index bd42e434c..d5dba3b51 100644
--- a/python_apps/media-monitor2/media/monitor/metadata.py
+++ b/python_apps/media-monitor2/media/monitor/metadata.py
@@ -2,15 +2,19 @@
 import mutagen
 import os
 import copy
-from collections     import namedtuple
 from mutagen.easymp4 import EasyMP4KeyError
 from mutagen.easyid3 import EasyID3KeyError
 
 from media.monitor.exceptions import BadSongFile, InvalidMetadataElement
 from media.monitor.log        import Loggable
-from media.monitor.pure       import format_length, truncate_to_length
+from media.monitor.pure       import format_length
 import media.monitor.pure as mmp
 
+# emf related stuff
+from media.metadata.process import global_reader
+import media.metadata.definitions as defs
+defs.load_definitions()
+
 """
 list of supported easy tags in mutagen version 1.20
 ['albumartistsort', 'musicbrainz_albumstatus', 'lyricist', 'releasecountry',
@@ -43,21 +47,6 @@ airtime2mutagen = {
     "MDATA_KEY_COPYRIGHT"   : "copyright",
 }
 
-class FakeMutagen(dict):
-    """
-    Need this fake mutagen object so that airtime_special functions
-    return a proper default value instead of throwing an exceptions for
-    files that mutagen doesn't recognize
-    """
-    FakeInfo = namedtuple('FakeInfo','length bitrate')
-    def __init__(self,path):
-        self.path = path
-        self.mime = ['audio/wav']
-        self.info = FakeMutagen.FakeInfo(0.0, '')
-        dict.__init__(self)
-    def set_length(self,l):
-        old_bitrate = self.info.bitrate
-        self.info = FakeMutagen.FakeInfo(l, old_bitrate)
 
 # Some airtime attributes are special because they must use the mutagen object
 # itself to calculate the value that they need. The lambda associated with each
@@ -100,6 +89,7 @@ class Metadata(Loggable):
     # little bit messy. Some of the handling is in m.m.pure while the rest is
     # here. Also interface is not very consistent
 
+    # TODO : what is this shit? maybe get rid of it?
     @staticmethod
     def fix_title(path):
         # If we have no title in path we will format it
@@ -110,39 +100,6 @@ class Metadata(Loggable):
             m[u'title'] = new_title
             m.save()
 
-    @staticmethod
-    def airtime_dict(d):
-        """
-        Converts mutagen dictionary 'd' into airtime dictionary
-        """
-        temp_dict = {}
-        for m_key, m_val in d.iteritems():
-            # TODO : some files have multiple fields for the same metadata.
-            # genre is one example. In that case mutagen will return a list
-            # of values
-
-            if isinstance(m_val, list):
-                # TODO : does it make more sense to just skip the element in
-                # this case?
-                if len(m_val) == 0: assign_val = ''
-                else: assign_val = m_val[0]
-            else: assign_val = m_val
-
-            temp_dict[ m_key ] = assign_val
-        airtime_dictionary = {}
-        for muta_k, muta_v in temp_dict.iteritems():
-            # We must check if we can actually translate the mutagen key into
-            # an airtime key before doing the conversion
-            if muta_k in mutagen2airtime:
-                airtime_key = mutagen2airtime[muta_k]
-                # Apply truncation in the case where airtime_key is in our
-                # truncation table
-                muta_v =  \
-                        truncate_to_length(muta_v, truncate_table[airtime_key])\
-                        if airtime_key in truncate_table else muta_v
-                airtime_dictionary[ airtime_key ] = muta_v
-        return airtime_dictionary
-
     @staticmethod
     def write_unsafe(path,md):
         """
@@ -157,6 +114,7 @@ class Metadata(Loggable):
             if airtime_k in airtime2mutagen:
                 # The unicode cast here is mostly for integers that need to be
                 # strings
+                if airtime_v is None: continue
                 try:
                     song_file[ airtime2mutagen[airtime_k] ] = unicode(airtime_v)
                 except (EasyMP4KeyError, EasyID3KeyError) as e:
@@ -170,44 +128,7 @@ class Metadata(Loggable):
         # Forcing the unicode through
         try    : fpath = fpath.decode("utf-8")
         except : pass
-
-        if not mmp.file_playable(fpath): raise BadSongFile(fpath)
-
-        try              : full_mutagen  = mutagen.File(fpath, easy=True)
-        except Exception : raise BadSongFile(fpath)
-
-        self.path = fpath
-        if not os.path.exists(self.path):
-            self.logger.info("Attempting to read metadata of file \
-                    that does not exist. Setting metadata to {}")
-            self.__metadata = {}
-            return
-        # TODO : Simplify the way all of these rules are handled right now it's
-        # extremely unclear and needs to be refactored.
-        #if full_mutagen is None: raise BadSongFile(fpath)
-        if full_mutagen is None: full_mutagen = FakeMutagen(fpath)
-        self.__metadata = Metadata.airtime_dict(full_mutagen)
-        # Now we extra the special values that are calculated from the mutagen
-        # object itself:
-
-        if mmp.extension(fpath) == 'wav':
-            full_mutagen.set_length(mmp.read_wave_duration(fpath))
-
-        for special_key,f in airtime_special.iteritems():
-            try:
-                new_val = f(full_mutagen)
-                if new_val is not None:
-                    self.__metadata[special_key] = new_val
-            except Exception as e:
-                self.logger.info("Could not get special key %s for %s" %
-                        (special_key, fpath))
-                self.logger.info(str(e))
-        # Finally, we "normalize" all the metadata here:
-        self.__metadata = mmp.normalized_metadata(self.__metadata, fpath)
-        # Now we must load the md5:
-        # TODO : perhaps we shouldn't hard code how many bytes we're reading
-        # from the file?
-        self.__metadata['MDATA_KEY_MD5'] = mmp.file_md5(fpath,max_length=100)
+        self.__metadata = global_reader.read_mutagen(fpath)
 
     def is_recorded(self):
         """
diff --git a/python_apps/media-monitor2/media/monitor/organizer.py b/python_apps/media-monitor2/media/monitor/organizer.py
index ea6851356..ce1849b90 100644
--- a/python_apps/media-monitor2/media/monitor/organizer.py
+++ b/python_apps/media-monitor2/media/monitor/organizer.py
@@ -10,14 +10,12 @@ from os.path                  import dirname
 import os.path
 
 class Organizer(ReportHandler,Loggable):
-    """
-    Organizer is responsible to to listening to OrganizeListener events
-    and committing the appropriate changes to the filesystem. It does
-    not in any interact with WatchSyncer's even when the the WatchSyncer
-    is a "storage directory". The "storage" directory picks up all of
-    its events through pyinotify. (These events are fed to it through
-    StoreWatchListener)
-    """
+    """ Organizer is responsible to to listening to OrganizeListener
+    events and committing the appropriate changes to the filesystem.
+    It does not in any interact with WatchSyncer's even when the the
+    WatchSyncer is a "storage directory". The "storage" directory picks
+    up all of its events through pyinotify. (These events are fed to it
+    through StoreWatchListener) """
 
     # Commented out making this class a singleton because it's just a band aid
     # for the real issue. The real issue being making multiple Organizer
@@ -41,11 +39,9 @@ class Organizer(ReportHandler,Loggable):
         super(Organizer, self).__init__(signal=self.channel, weak=False)
 
     def handle(self, sender, event):
-        """
-        Intercept events where a new file has been added to the organize
-        directory and place it in the correct path (starting with
-        self.target_path)
-        """
+        """ Intercept events where a new file has been added to the
+        organize directory and place it in the correct path (starting
+        with self.target_path) """
         # Only handle this event type
         assert isinstance(event, OrganizeFile), \
             "Organizer can only handle OrganizeFile events.Given '%s'" % event
diff --git a/python_apps/media-monitor2/media/monitor/pure.py b/python_apps/media-monitor2/media/monitor/pure.py
index 877cbdc72..a879f449f 100644
--- a/python_apps/media-monitor2/media/monitor/pure.py
+++ b/python_apps/media-monitor2/media/monitor/pure.py
@@ -22,7 +22,6 @@ from configobj import ConfigObj
 
 from media.monitor.exceptions import FailedToSetLocale, FailedToCreateDir
 
-#supported_extensions =  [u"mp3", u"ogg", u"oga"]
 supported_extensions = [u"mp3", u"ogg", u"oga", u"flac", u"wav",
                         u'm4a', u'mp4']
 
@@ -67,7 +66,6 @@ class IncludeOnly(object):
                 return func(moi, event, *args, **kwargs)
         return _wrap
 
-
 def partition(f, alist):
     """
     Partition is very similar to filter except that it also returns the
@@ -93,14 +91,13 @@ def is_file_supported(path):
 # TODO : In the future we would like a better way to find out whether a show
 # has been recorded
 def is_airtime_recorded(md):
-    """
-    Takes a metadata dictionary and returns True if it belongs to a file that
-    was recorded by Airtime.
-    """
+    """ Takes a metadata dictionary and returns True if it belongs to a
+    file that was recorded by Airtime. """
     if not 'MDATA_KEY_CREATOR' in md: return False
     return md['MDATA_KEY_CREATOR'] == u'Airtime Show Recorder'
 
 def read_wave_duration(path):
+    """ Read the length of .wav file (mutagen does not handle this) """
     with contextlib.closing(wave.open(path,'r')) as f:
         frames   = f.getnframes()
         rate     = f.getframerate()
@@ -108,9 +105,7 @@ def read_wave_duration(path):
         return duration
 
 def clean_empty_dirs(path):
-    """
-    walks path and deletes every empty directory it finds
-    """
+    """ walks path and deletes every empty directory it finds """
     # TODO : test this function
     if path.endswith('/'): clean_empty_dirs(path[0:-1])
     else:
@@ -155,11 +150,10 @@ def no_extension_basename(path):
     else: return '.'.join(base.split(".")[0:-1])
 
 def walk_supported(directory, clean_empties=False):
-    """
-    A small generator wrapper around os.walk to only give us files that support
-    the extensions we are considering. When clean_empties is True we
-    recursively delete empty directories left over in directory after the walk.
-    """
+    """ A small generator wrapper around os.walk to only give us files
+    that support the extensions we are considering. When clean_empties
+    is True we recursively delete empty directories left over in
+    directory after the walk. """
     for root, dirs, files in os.walk(directory):
         full_paths = ( os.path.join(root, name) for name in files
                 if is_file_supported(name) )
@@ -173,10 +167,8 @@ def file_locked(path):
     return bool(f.readlines())
 
 def magic_move(old, new, after_dir_make=lambda : None):
-    """
-    Moves path old to new and constructs the necessary to directories for new
-    along the way
-    """
+    """ Moves path old to new and constructs the necessary to
+    directories for new along the way """
     new_dir = os.path.dirname(new)
     if not os.path.exists(new_dir): os.makedirs(new_dir)
     # We need this crusty hack because anytime a directory is created we must
@@ -186,18 +178,15 @@ def magic_move(old, new, after_dir_make=lambda : None):
     shutil.move(old,new)
 
 def move_to_dir(dir_path,file_path):
-    """
-    moves a file at file_path into dir_path/basename(filename)
-    """
+    """ moves a file at file_path into dir_path/basename(filename) """
     bs = os.path.basename(file_path)
     magic_move(file_path, os.path.join(dir_path, bs))
 
 def apply_rules_dict(d, rules):
-    """
-    Consumes a dictionary of rules that maps some keys to lambdas which it
-    applies to every matching element in d and returns a new dictionary with
-    the rules applied. If a rule returns none then it's not applied
-    """
+    """ Consumes a dictionary of rules that maps some keys to lambdas
+    which it applies to every matching element in d and returns a new
+    dictionary with the rules applied. If a rule returns none then it's
+    not applied """
     new_d = copy.deepcopy(d)
     for k, rule in rules.iteritems():
         if k in d:
@@ -212,17 +201,14 @@ def default_to_f(dictionary, keys, default, condition):
     return new_d
 
 def default_to(dictionary, keys, default):
-    """
-    Checks if the list of keys 'keys' exists in 'dictionary'. If not then it
-    returns a new dictionary with all those missing keys defaults to 'default'
-    """
+    """ Checks if the list of keys 'keys' exists in 'dictionary'. If
+    not then it returns a new dictionary with all those missing keys
+    defaults to 'default' """
     cnd = lambda dictionary, key: key not in dictionary
     return default_to_f(dictionary, keys, default, cnd)
 
 def remove_whitespace(dictionary):
-    """
-    Remove values that empty whitespace in the dictionary
-    """
+    """ Remove values that empty whitespace in the dictionary """
     nd = copy.deepcopy(dictionary)
     bad_keys = []
     for k,v in nd.iteritems():
@@ -234,6 +220,7 @@ def remove_whitespace(dictionary):
     return nd
 
 def parse_int(s):
+    # TODO : this function isn't used anywhere yet but it may useful for emf
     """
     Tries very hard to get some sort of integer result from s. Defaults to 0
     when it fails
@@ -249,53 +236,6 @@ def parse_int(s):
         try   : return str(reduce(op.add, takewhile(lambda x: x.isdigit(), s)))
         except: return None
 
-def normalized_metadata(md, original_path):
-    """
-    consumes a dictionary of metadata and returns a new dictionary with the
-    formatted meta data. We also consume original_path because we must set
-    MDATA_KEY_CREATOR based on in it sometimes
-    """
-    new_md = copy.deepcopy(md)
-    # replace all slashes with dashes
-    #for k,v in new_md.iteritems(): new_md[k] = unicode(v).replace('/','-')
-    # Specific rules that are applied in a per attribute basis
-    format_rules = {
-        'MDATA_KEY_TRACKNUMBER' : parse_int,
-        'MDATA_KEY_FILEPATH'    : lambda x: os.path.normpath(x),
-        'MDATA_KEY_BPM'         : lambda x: x[0:8],
-        'MDATA_KEY_MIME' : lambda x: x.replace('audio/vorbis','audio/ogg'),
-        # Whenever 0 is reported we change it to empty
-        #'MDATA_KEY_BITRATE' : lambda x: '' if str(x) == '0' else x
-    }
-
-    new_md = remove_whitespace(new_md) # remove whitespace fields
-    # Format all the fields in format_rules
-    new_md = apply_rules_dict(new_md, format_rules)
-    # set filetype to audioclip by default
-    new_md = default_to(dictionary=new_md, keys=['MDATA_KEY_FTYPE'],
-                        default=u'audioclip')
-
-    # Try to parse bpm but delete the whole key if that fails
-    if 'MDATA_KEY_BPM' in new_md:
-        new_md['MDATA_KEY_BPM'] = parse_int(new_md['MDATA_KEY_BPM'])
-        if new_md['MDATA_KEY_BPM'] is None:
-            del new_md['MDATA_KEY_BPM']
-
-    if not is_airtime_recorded(new_md):
-        # Read title from filename if it does not exist
-        default_title = no_extension_basename(original_path)
-        default_title = re.sub(r'__\d+\.',u'.', default_title)
-        if re.match(".+-%s-.+$" % unicode_unknown, default_title):
-            default_title = u''
-        new_md = default_to(dictionary=new_md, keys=['MDATA_KEY_TITLE'],
-                            default=default_title)
-        new_md['MDATA_KEY_TITLE'] = re.sub(r'-\d+kbps$', u'',
-                new_md['MDATA_KEY_TITLE'])
-
-    # TODO : wtf is this for again?
-    new_md['MDATA_KEY_TITLE'] = re.sub(r'-?%s-?' % unicode_unknown, u'',
-            new_md['MDATA_KEY_TITLE'])
-    return new_md
 
 def organized_path(old_path, root_path, orig_md):
     """
@@ -355,10 +295,9 @@ def organized_path(old_path, root_path, orig_md):
 # TODO : Get rid of this function and every one of its uses. We no longer use
 # the md5 signature of a song for anything
 def file_md5(path,max_length=100):
-    """
-    Get md5 of file path (if it exists). Use only max_length characters to save
-    time and memory. Pass max_length=-1 to read the whole file (like in mm1)
-    """
+    """ Get md5 of file path (if it exists). Use only max_length
+    characters to save time and memory. Pass max_length=-1 to read the
+    whole file (like in mm1) """
     if os.path.exists(path):
         with open(path, 'rb') as f:
             m = hashlib.md5()
@@ -374,16 +313,12 @@ def encode_to(obj, encoding='utf-8'):
     return obj
 
 def convert_dict_value_to_utf8(md):
-    """
-    formats a dictionary to send as a request to api client
-    """
+    """ formats a dictionary to send as a request to api client """
     return dict([(item[0], encode_to(item[1], "utf-8")) for item in md.items()])
 
 def get_system_locale(locale_path='/etc/default/locale'):
-    """
-    Returns the configuration object for the system's default locale. Normally
-    requires root access.
-    """
+    """ Returns the configuration object for the system's default
+    locale. Normally requires root access. """
     if os.path.exists(locale_path):
         try:
             config = ConfigObj(locale_path)
@@ -393,9 +328,7 @@ def get_system_locale(locale_path='/etc/default/locale'):
             permissions issue?" % locale_path)
 
 def configure_locale(config):
-    """
-    sets the locale according to the system's locale.
-    """
+    """ sets the locale according to the system's locale. """
     current_locale = locale.getlocale()
     if current_locale[1] is None:
         default_locale = locale.getdefaultlocale()
@@ -412,27 +345,21 @@ def configure_locale(config):
 
 def fondle(path,times=None):
     # TODO : write unit tests for this
-    """
-    touch a file to change the last modified date. Beware of calling this
-    function on the same file from multiple threads.
-    """
+    """ touch a file to change the last modified date. Beware of calling
+    this function on the same file from multiple threads. """
     with file(path, 'a'): os.utime(path, times)
 
 def last_modified(path):
-    """
-    return the time of the last time mm2 was ran. path refers to the index file
-    whose date modified attribute contains this information. In the case when
-    the file does not exist we set this time 0 so that any files on the
-    filesystem were modified after it
-    """
+    """ return the time of the last time mm2 was ran. path refers to the
+    index file whose date modified attribute contains this information.
+    In the case when the file does not exist we set this time 0 so that
+    any files on the filesystem were modified after it """
     if os.path.exists(path): return os.path.getmtime(path)
     else: return 0
 
 def expand_storage(store):
-    """
-    A storage directory usually consists of 4 different subdirectories. This
-    function returns their paths
-    """
+    """ A storage directory usually consists of 4 different
+    subdirectories. This function returns their paths """
     store = os.path.normpath(store)
     return {
         'organize'      : os.path.join(store, 'organize'),
@@ -442,10 +369,8 @@ def expand_storage(store):
     }
 
 def create_dir(path):
-    """
-    will try and make sure that path exists at all costs. raises an exception
-    if it fails at this task.
-    """
+    """ will try and make sure that path exists at all costs. raises an
+    exception if it fails at this task. """
     if not os.path.exists(path):
         try                   : os.makedirs(path)
         except Exception as e : raise FailedToCreateDir(path, e)
@@ -463,11 +388,10 @@ def sub_path(directory,f):
     return common == normalized
 
 def owner_id(original_path):
-    """
-    Given 'original_path' return the file name of the of 'identifier' file.
-    return the id that is contained in it. If no file is found or nothing is
-    read then -1 is returned. File is deleted after the number has been read
-    """
+    """ Given 'original_path' return the file name of the of
+    'identifier' file. return the id that is contained in it. If no file
+    is found or nothing is read then -1 is returned. File is deleted
+    after the number has been read """
     fname = "%s.identifier" % original_path
     owner_id = -1
     try:
@@ -483,9 +407,8 @@ def owner_id(original_path):
     return owner_id
 
 def file_playable(pathname):
-    """
-    Returns True if 'pathname' is playable by liquidsoap. False otherwise.
-    """
+    """ Returns True if 'pathname' is playable by liquidsoap. False
+    otherwise. """
     # when there is an single apostrophe inside of a string quoted by
     # apostrophes, we can only escape it by replace that apostrophe with
     # '\''. This breaks the string into two, and inserts an escaped
@@ -521,18 +444,14 @@ def toposort(data):
     assert not data, "A cyclic dependency exists amongst %r" % data
 
 def truncate_to_length(item, length):
-    """
-    Truncates 'item' to 'length'
-    """
+    """ Truncates 'item' to 'length' """
     if isinstance(item, int): item = str(item)
     if isinstance(item, basestring):
         if len(item) > length: return item[0:length]
         else: return item
 
 def format_length(mutagen_length):
-    """
-    Convert mutagen length to airtime length
-    """
+    """ Convert mutagen length to airtime length """
     t = float(mutagen_length)
     h = int(math.floor(t / 3600))
     t = t % 3600
diff --git a/python_apps/media-monitor2/media/monitor/request.py b/python_apps/media-monitor2/media/monitor/request.py
new file mode 100644
index 000000000..a2f3cdc8f
--- /dev/null
+++ b/python_apps/media-monitor2/media/monitor/request.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+import threading
+
+from media.monitor.exceptions import BadSongFile
+from media.monitor.log        import Loggable
+import api_clients.api_client as ac
+
+class ThreadedRequestSync(threading.Thread, Loggable):
+    def __init__(self, rs):
+        threading.Thread.__init__(self)
+        self.rs = rs
+        self.daemon = True
+        self.start()
+
+    def run(self):
+        self.rs.run_request()
+
+class RequestSync(Loggable):
+    """ This class is responsible for making the api call to send a
+    request to airtime. In the process it packs the requests and retries
+    for some number of times """
+    @classmethod
+    def create_with_api_client(cls, watcher, requests):
+        apiclient = ac.AirtimeApiClient.create_right_config()
+        self = cls(watcher, requests, apiclient)
+        return self
+
+    def __init__(self, watcher, requests, apiclient):
+        self.watcher   = watcher
+        self.requests  = requests
+        self.apiclient = apiclient
+
+    def run_request(self):
+        self.logger.info("Attempting request with %d items." %
+                len(self.requests))
+        packed_requests = []
+        for request_event in self.requests:
+            try:
+                for request in request_event.safe_pack():
+                    if isinstance(request, BadSongFile):
+                        self.logger.info("Bad song file: '%s'" % request.path)
+                    else: packed_requests.append(request)
+            except Exception as e:
+                self.unexpected_exception( e )
+                if hasattr(request_event, 'path'):
+                    self.logger.info("Possibly related to path: '%s'" %
+                            request_event.path)
+        try: self.apiclient.send_media_monitor_requests( packed_requests )
+        # most likely we did not get json response as we expected
+        except ValueError:
+            self.logger.info("ApiController.php probably crashed, we \
+                    diagnose this from the fact that it did not return \
+                    valid json")
+            self.logger.info("Trying again after %f seconds" %
+                    self.request_wait)
+        except Exception as e: self.unexpected_exception(e)
+        else: self.logger.info("Request was successful")
+        self.watcher.flag_done() # poor man's condition variable
+
diff --git a/python_apps/media-monitor2/media/monitor/syncdb.py b/python_apps/media-monitor2/media/monitor/syncdb.py
index 0c14fb038..cc8abc294 100644
--- a/python_apps/media-monitor2/media/monitor/syncdb.py
+++ b/python_apps/media-monitor2/media/monitor/syncdb.py
@@ -53,11 +53,11 @@ class AirtimeDB(Loggable):
         """
         return self.id_to_dir[ dir_id ]
 
-    def storage_path(self): return self.base_storage
-    def organize_path(self): return self.storage_paths['organize']
-    def problem_path(self): return self.storage_paths['problem_files']
-    def import_path(self): return self.storage_paths['imported']
-    def recorded_path(self): return self.storage_paths['recorded']
+    def storage_path(self)  : return self.base_storage
+    def organize_path(self) : return self.storage_paths['organize']
+    def problem_path(self)  : return self.storage_paths['problem_files']
+    def import_path(self)   : return self.storage_paths['imported']
+    def recorded_path(self) : return self.storage_paths['recorded']
 
     def list_watched(self):
         """
diff --git a/python_apps/media-monitor2/media/monitor/watchersyncer.py b/python_apps/media-monitor2/media/monitor/watchersyncer.py
index e7a2cf9c0..d2df9ed3a 100644
--- a/python_apps/media-monitor2/media/monitor/watchersyncer.py
+++ b/python_apps/media-monitor2/media/monitor/watchersyncer.py
@@ -6,69 +6,9 @@ import copy
 from media.monitor.handler         import ReportHandler
 from media.monitor.log             import Loggable
 from media.monitor.exceptions      import BadSongFile
-from media.monitor.pure            import LazyProperty
 from media.monitor.eventcontractor import EventContractor
 from media.monitor.events          import EventProxy
-
-import api_clients.api_client as ac
-
-class RequestSync(threading.Thread,Loggable):
-    """
-    This class is responsible for making the api call to send a request
-    to airtime. In the process it packs the requests and retries for
-    some number of times
-    """
-    def __init__(self, watcher, requests):
-        threading.Thread.__init__(self)
-        self.watcher      = watcher
-        self.requests     = requests
-        self.retries      = 1
-        self.request_wait = 0.3
-
-    @LazyProperty
-    def apiclient(self):
-        return ac.AirtimeApiClient.create_right_config()
-
-    def run(self):
-        self.logger.info("Attempting request with %d items." %
-                len(self.requests))
-        # Note that we must attach the appropriate mode to every
-        # response. Also Not forget to attach the 'is_record' to any
-        # requests that are related to recorded shows
-        # TODO : recorded shows aren't flagged right
-        # Is this retry shit even necessary? Consider getting rid of this.
-        packed_requests = []
-        for request_event in self.requests:
-            try:
-                for request in request_event.safe_pack():
-                    if isinstance(request, BadSongFile):
-                        self.logger.info("Bad song file: '%s'" % request.path)
-                    else: packed_requests.append(request)
-            except Exception as e:
-                self.unexpected_exception( e )
-                if hasattr(request_event, 'path'):
-                    self.logger.info("Possibly related to path: '%s'" %
-                            request_event.path)
-        def make_req():
-            self.apiclient.send_media_monitor_requests( packed_requests )
-        for try_index in range(0,self.retries):
-            try: make_req()
-            # most likely we did not get json response as we expected
-            except ValueError:
-                self.logger.info("ApiController.php probably crashed, we \
-                        diagnose this from the fact that it did not return \
-                        valid json")
-                self.logger.info("Trying again after %f seconds" %
-                        self.request_wait)
-                time.sleep( self.request_wait )
-            except Exception as e: self.unexpected_exception(e)
-            else:
-                self.logger.info("Request worked on the '%d' try" %
-                        (try_index + 1))
-                break
-        else: self.logger.info("Failed to send request after '%d' tries..." %
-                self.retries)
-        self.watcher.flag_done()
+from media.monitor.request         import ThreadedRequestSync, RequestSync
 
 class TimeoutWatcher(threading.Thread,Loggable):
     """
@@ -131,8 +71,7 @@ class WatchSyncer(ReportHandler,Loggable):
                 #self.push_queue( event )
             except BadSongFile as e:
                 self.fatal_exception("Received bas song file '%s'" % e.path, e)
-            except Exception as e:
-                self.unexpected_exception(e)
+            except Exception as e: self.unexpected_exception(e)
         else:
             self.logger.info("Received event that does not implement packing.\
                     Printing its representation:")
@@ -209,8 +148,8 @@ class WatchSyncer(ReportHandler,Loggable):
         requests = copy.copy(self.__queue)
         def launch_request():
             # Need shallow copy here
-            t = RequestSync(watcher=self, requests=requests)
-            t.start()
+            t = ThreadedRequestSync( RequestSync.create_with_api_client(
+                watcher=self, requests=requests) )
             self.__current_thread = t
         self.__requests.append(launch_request)
         self.__reset_queue()
@@ -218,7 +157,8 @@ class WatchSyncer(ReportHandler,Loggable):
     def __reset_queue(self): self.__queue = []
 
     def __del__(self):
-        # Ideally we would like to do a little more to ensure safe shutdown
+        #this destructor is completely untested and it's unclear whether
+        #it's even doing anything useful. consider removing it
         if self.events_in_queue():
             self.logger.warn("Terminating with events still in the queue...")
         if self.requests_in_queue():
diff --git a/python_apps/media-monitor2/mm2.py b/python_apps/media-monitor2/mm2.py
index 17731cad8..ea1178a2f 100644
--- a/python_apps/media-monitor2/mm2.py
+++ b/python_apps/media-monitor2/mm2.py
@@ -59,7 +59,8 @@ def main(global_config, api_client_config, log_config,
             try:
                 with open(config['index_path'], 'w') as f: f.write(" ")
             except Exception as e:
-                log.info("Failed to create index file with exception: %s" % str(e))
+                log.info("Failed to create index file with exception: %s" \
+                         % str(e))
             else:
                 log.info("Created index file, reloading configuration:")
                 main( global_config,  api_client_config, log_config,
diff --git a/python_apps/media-monitor2/tests/test_api_client.py b/python_apps/media-monitor2/tests/test_api_client.py
index 26f1a25ef..18595f860 100644
--- a/python_apps/media-monitor2/tests/test_api_client.py
+++ b/python_apps/media-monitor2/tests/test_api_client.py
@@ -19,8 +19,8 @@ class TestApiClient(unittest.TestCase):
         self.apc.register_component("api-client-tester")
         # All of the following requests should error out in some way
         self.bad_requests = [
-                { 'mode' : 'dang it', 'is_record' : 0 },
-                { 'mode' : 'damn frank', 'is_record' : 1 },
+                { 'mode' : 'foo', 'is_record' : 0 },
+                { 'mode' : 'bar', 'is_record' : 1 },
                 { 'no_mode' : 'at_all' }, ]
 
     def test_bad_requests(self):
diff --git a/python_apps/media-monitor2/tests/test_emf.py b/python_apps/media-monitor2/tests/test_emf.py
new file mode 100644
index 000000000..140c0aa8b
--- /dev/null
+++ b/python_apps/media-monitor2/tests/test_emf.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+import unittest
+#from pprint import pprint as pp
+
+from media.metadata.process import global_reader
+from media.monitor.metadata import Metadata
+
+import media.metadata.definitions as defs
+defs.load_definitions()
+
+class TestMMP(unittest.TestCase):
+
+    def setUp(self):
+        self.maxDiff = None
+
+    def metadatas(self,f):
+        return global_reader.read_mutagen(f), Metadata(f).extract()
+
+    def test_old_metadata(self):
+        path = "/home/rudi/music/Nightingale.mp3"
+        m = global_reader.read_mutagen(path)
+        self.assertTrue( len(m) > 0 )
+        n = Metadata(path)
+        self.assertEqual(n.extract(), m)
+
+    def test_recorded(self):
+        recorded_file = "./15:15:00-Untitled Show-256kbps.ogg"
+        emf, old = self.metadatas(recorded_file)
+        self.assertEqual(emf, old)
+
+if __name__ == '__main__': unittest.main()
diff --git a/python_apps/media-monitor2/tests/test_metadata.py b/python_apps/media-monitor2/tests/test_metadata.py
index 6f24240b0..7a32b61a8 100644
--- a/python_apps/media-monitor2/tests/test_metadata.py
+++ b/python_apps/media-monitor2/tests/test_metadata.py
@@ -26,7 +26,6 @@ class TestMetadata(unittest.TestCase):
                     i += 1
                     print("Sample metadata: '%s'" % md)
                 self.assertTrue( len( md.keys() ) > 0 )
-                self.assertTrue( 'MDATA_KEY_MD5' in md )
                 utf8 = md_full.utf8()
                 for k,v in md.iteritems():
                     if hasattr(utf8[k], 'decode'):
@@ -42,10 +41,4 @@ class TestMetadata(unittest.TestCase):
         x1 = 123456
         print("Formatting '%s' to '%s'" % (x1, mmm.format_length(x1)))
 
-    def test_truncate_to_length(self):
-        s1 = "testing with non string literal"
-        s2 = u"testing with unicode literal"
-        self.assertEqual( len(mmm.truncate_to_length(s1, 5)), 5)
-        self.assertEqual( len(mmm.truncate_to_length(s2, 8)), 8)
-
 if __name__ == '__main__': unittest.main()
diff --git a/python_apps/media-monitor2/tests/test_metadata_def.py b/python_apps/media-monitor2/tests/test_metadata_def.py
new file mode 100644
index 000000000..e666ef68a
--- /dev/null
+++ b/python_apps/media-monitor2/tests/test_metadata_def.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+import unittest
+
+import media.metadata.process as md
+
+class TestMetadataDef(unittest.TestCase):
+    def test_simple(self):
+
+        with md.metadata('MDATA_TESTING') as t:
+            t.optional(True)
+            t.depends('ONE','TWO')
+            t.default('unknown')
+            t.translate(lambda kw: kw['ONE'] + kw['TWO'])
+
+        h = { 'ONE' : "testing", 'TWO' : "123" }
+        result = md.global_reader.read('test_path',h)
+        self.assertTrue( 'MDATA_TESTING' in result )
+        self.assertEqual( result['MDATA_TESTING'], 'testing123' )
+        h1 = { 'ONE' : 'big testing', 'two' : 'nothing' }
+        result1 = md.global_reader.read('bs path', h1)
+        self.assertEqual( result1['MDATA_TESTING'], 'unknown' )
+
+    def test_topo(self):
+        with md.metadata('MDATA_TESTING') as t:
+            t.depends('shen','sheni')
+            t.default('megitzda')
+            t.translate(lambda kw: kw['shen'] + kw['sheni'])
+
+        with md.metadata('shen') as t:
+            t.default('vaxo')
+
+        with md.metadata('sheni') as t:
+            t.default('gio')
+
+        with md.metadata('vaxo') as t:
+            t.depends('shevetsi')
+
+        v = md.global_reader.read('bs mang', {})
+        self.assertEqual(v['MDATA_TESTING'], 'vaxogio')
+        self.assertTrue( 'vaxo' not in v )
+
+        md.global_reader.clear()
+
+if __name__ == '__main__': unittest.main()
diff --git a/python_apps/media-monitor2/tests/test_pure.py b/python_apps/media-monitor2/tests/test_pure.py
index 64d09dc62..6bc5d4906 100644
--- a/python_apps/media-monitor2/tests/test_pure.py
+++ b/python_apps/media-monitor2/tests/test_pure.py
@@ -2,7 +2,6 @@
 import unittest
 import os
 import media.monitor.pure as mmp
-from media.monitor.metadata import Metadata
 
 class TestMMP(unittest.TestCase):
     def setUp(self):
@@ -34,68 +33,6 @@ class TestMMP(unittest.TestCase):
         sd = mmp.default_to(dictionary=sd, keys=def_keys, default='DEF')
         for k in def_keys: self.assertEqual( sd[k], 'DEF' )
 
-    def test_normalized_metadata(self):
-        #Recorded show test first
-        orig = Metadata.airtime_dict({
-                'date'        : [u'2012-08-21'],
-                'tracknumber' : [u'2'],
-                'title'       : [u'record-2012-08-21-11:29:00'],
-                'artist'      : [u'Airtime Show Recorder']
-        })
-        orga = Metadata.airtime_dict({
-                'date'        : [u'2012-08-21'],
-                'tracknumber' : [u'2'],
-                'artist'      : [u'Airtime Show Recorder'],
-                'title'       : [u'record-2012-08-21-11:29:00']
-        })
-        orga['MDATA_KEY_FTYPE']   = u'audioclip'
-        orig['MDATA_KEY_BITRATE'] = u'256000'
-        orga['MDATA_KEY_BITRATE'] = u'256000'
-        old_path   = "/home/rudi/recorded/2012-08-21-11:29:00.ogg"
-        normalized = mmp.normalized_metadata(orig, old_path)
-        normalized['MDATA_KEY_BITRATE'] = u'256000'
-
-        self.assertEqual( orga, normalized )
-
-        organized_base_name = "11:29:00-record-256kbps.ogg"
-        base                = "/srv/airtime/stor/"
-        organized_path      = mmp.organized_path(old_path,base, normalized)
-        self.assertEqual(os.path.basename(organized_path), organized_base_name)
-
-    def test_normalized_metadata2(self):
-        """
-        cc-4305
-        """
-        orig = Metadata.airtime_dict({
-            'date'        : [u'2012-08-27'],
-            'tracknumber' : [u'3'],
-            'title'       : [u'18-11-00-Untitled Show'],
-            'artist'      : [u'Airtime Show Recorder']
-        })
-        old_path   = "/home/rudi/recorded/doesnt_really_matter.ogg"
-        normalized = mmp.normalized_metadata(orig, old_path)
-        normalized['MDATA_KEY_BITRATE'] = u'256000'
-        opath = mmp.organized_path(old_path, "/srv/airtime/stor/",
-                normalized)
-        # TODO : add a better test than this...
-        self.assertTrue( len(opath) > 0 )
-
-    def test_normalized_metadata3(self):
-        """
-        Test the case where the metadata is empty
-        """
-        orig = Metadata.airtime_dict({})
-        paths_unknown_title = [
-                ("/testin/unknown-unknown-unknown.mp3",""),
-                ("/testin/01-unknown-123kbps.mp3",""),
-                ("/testin/02-unknown-140kbps.mp3",""),
-                ("/testin/unknown-unknown-123kbps.mp3",""),
-                ("/testin/unknown-bibimbop-unknown.mp3","bibimbop"),
-        ]
-        for p,res in paths_unknown_title:
-            normalized = mmp.normalized_metadata(orig, p)
-            self.assertEqual( normalized['MDATA_KEY_TITLE'], res)
-
     def test_file_md5(self):
         p = os.path.realpath(__file__)
         m1 = mmp.file_md5(p)
@@ -116,6 +53,13 @@ class TestMMP(unittest.TestCase):
         self.assertEqual( mmp.parse_int("123asf"), "123" )
         self.assertEqual( mmp.parse_int("asdf"), None )
 
+    def test_truncate_to_length(self):
+        s1 = "testing with non string literal"
+        s2 = u"testing with unicode literal"
+        self.assertEqual( len(mmp.truncate_to_length(s1, 5)), 5)
+        self.assertEqual( len(mmp.truncate_to_length(s2, 8)), 8)
+
+
     def test_owner_id(self):
         start_path = "testing.mp3"
         id_path    = "testing.mp3.identifier"
diff --git a/python_apps/media-monitor2/tests/test_requestsync.py b/python_apps/media-monitor2/tests/test_requestsync.py
new file mode 100644
index 000000000..2570bc34e
--- /dev/null
+++ b/python_apps/media-monitor2/tests/test_requestsync.py
@@ -0,0 +1,48 @@
+import unittest
+from mock import MagicMock
+
+from media.monitor.request import RequestSync
+
+class TestRequestSync(unittest.TestCase):
+
+    def apc_mock(self):
+        fake_apc = MagicMock()
+        fake_apc.send_media_monitor_requests = MagicMock()
+        return fake_apc
+
+    def watcher_mock(self):
+        fake_watcher = MagicMock()
+        fake_watcher.flag_done = MagicMock()
+        return fake_watcher
+
+    def request_mock(self):
+        fake_request = MagicMock()
+        fake_request.safe_pack = MagicMock(return_value=[])
+        return fake_request
+
+    def test_send_media_monitor(self):
+        fake_apc      = self.apc_mock()
+        fake_requests = [ self.request_mock() for x in range(1,5) ]
+        fake_watcher  = self.watcher_mock()
+        rs = RequestSync(fake_watcher, fake_requests, fake_apc)
+        rs.run_request()
+        self.assertEquals(fake_apc.send_media_monitor_requests.call_count, 1)
+
+    def test_flag_done(self):
+        fake_apc      = self.apc_mock()
+        fake_requests = [ self.request_mock() for x in range(1,5) ]
+        fake_watcher  = self.watcher_mock()
+        rs = RequestSync(fake_watcher, fake_requests, fake_apc)
+        rs.run_request()
+        self.assertEquals(fake_watcher.flag_done.call_count, 1)
+
+    def test_safe_pack(self):
+        fake_apc      = self.apc_mock()
+        fake_requests = [ self.request_mock() for x in range(1,5) ]
+        fake_watcher  = self.watcher_mock()
+        rs = RequestSync(fake_watcher, fake_requests, fake_apc)
+        rs.run_request()
+        for req in fake_requests:
+            self.assertEquals(req.safe_pack.call_count, 1)
+
+if __name__ == '__main__': unittest.main()