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 @@
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
.+)-(\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()