Merge branch 'devel' of dev.sourcefabric.org:airtime into devel

This commit is contained in:
denise 2012-08-21 14:08:36 -04:00
commit f52cb81c64
6 changed files with 95 additions and 71 deletions
python_apps

View File

@ -59,4 +59,6 @@ class EventContractor(Loggable):
try: del self.store[evt.path] try: del self.store[evt.path]
except Exception as e: except Exception as e:
self.unexpected_exception(e) self.unexpected_exception(e)
self.logger.info("Unregistering. Left: '%d'" % len(self.store.keys())) # the next line is commented out because it clutters up logging real
# bad
#self.logger.info("Unregistering. Left: '%d'" % len(self.store.keys()))

View File

@ -105,10 +105,38 @@ class Metadata(Loggable):
# little bit messy. Some of the handling is in m.m.pure while the rest is # little bit messy. Some of the handling is in m.m.pure while the rest is
# here. Also interface is not very consistent # here. Also interface is not very consistent
@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
assign_val = m_val[0] if isinstance(m_val, list) else 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 @staticmethod
def write_unsafe(path,md): def write_unsafe(path,md):
""" """
Writes 'md' metadata into 'path' through mutagen Writes 'md' metadata into 'path' through mutagen. Converts all
dictionary values to strings because mutagen will not write anything
else
""" """
if not os.path.exists(path): raise BadSongFile(path) if not os.path.exists(path): raise BadSongFile(path)
song_file = mutagen.File(path, easy=True) song_file = mutagen.File(path, easy=True)
@ -128,37 +156,13 @@ class Metadata(Loggable):
self.path = fpath self.path = fpath
# TODO : Simplify the way all of these rules are handled right not it's # TODO : Simplify the way all of these rules are handled right not it's
# extremely unclear and needs to be refactored. # extremely unclear and needs to be refactored.
metadata = {} self.__metadata = Metadata.airtime_dict(full_mutagen)
# Load only the metadata avilable in mutagen into metdata
for k,v in full_mutagen.iteritems():
# Special handling of attributes here
if isinstance(v, list):
# TODO : some files have multiple fields for the same metadata.
# genre is one example. In that case mutagen will return a list
# of values
metadata[k] = v[0]
#if len(v) == 1: metadata[k] = v[0]
#else: raise Exception("Unknown mutagen %s:%s" % (k,str(v)))
else: metadata[k] = v
self.__metadata = {}
# Start populating a dictionary of airtime metadata in __metadata
for muta_k, muta_v in metadata.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
self.__metadata[ airtime_key ] = muta_v
# Now we extra the special values that are calculated from the mutagen # Now we extra the special values that are calculated from the mutagen
# object itself: # object itself:
for special_key,f in airtime_special.iteritems(): for special_key,f in airtime_special.iteritems():
new_val = f(full_mutagen) new_val = f(full_mutagen)
if new_val is not None: if new_val is not None:
self.__metadata[special_key] = f(full_mutagen) self.__metadata[special_key] = new_val
# Finally, we "normalize" all the metadata here: # Finally, we "normalize" all the metadata here:
self.__metadata = mmp.normalized_metadata(self.__metadata, fpath) self.__metadata = mmp.normalized_metadata(self.__metadata, fpath)
# Now we must load the md5: # Now we must load the md5:

View File

@ -2,6 +2,7 @@
import copy import copy
import os import os
import shutil import shutil
import re
import sys import sys
import hashlib import hashlib
import locale import locale
@ -228,34 +229,33 @@ def normalized_metadata(md, original_path):
# Specific rules that are applied in a per attribute basis # Specific rules that are applied in a per attribute basis
format_rules = { format_rules = {
'MDATA_KEY_TRACKNUMBER' : parse_int, 'MDATA_KEY_TRACKNUMBER' : parse_int,
'MDATA_KEY_BITRATE' : lambda x: str(int(x) / 1000) + "kbps",
'MDATA_KEY_FILEPATH' : lambda x: os.path.normpath(x), 'MDATA_KEY_FILEPATH' : lambda x: os.path.normpath(x),
'MDATA_KEY_MIME' : lambda x: x.replace('-','/'), 'MDATA_KEY_MIME' : lambda x: x.replace('-','/'),
'MDATA_KEY_BPM' : lambda x: x[0:8], 'MDATA_KEY_BPM' : lambda x: x[0:8],
} }
new_md = remove_whitespace(new_md) 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) new_md = apply_rules_dict(new_md, format_rules)
new_md = default_to(dictionary=new_md, keys=['MDATA_KEY_TITLE'], # set filetype to audioclip by default
default=no_extension_basename(original_path))
new_md = default_to(dictionary=new_md, keys=['MDATA_KEY_CREATOR',
'MDATA_KEY_SOURCE'], default=u'')
new_md = default_to(dictionary=new_md, keys=['MDATA_KEY_FTYPE'], new_md = default_to(dictionary=new_md, keys=['MDATA_KEY_FTYPE'],
default=u'audioclip') default=u'audioclip')
# In the case where the creator is 'Airtime Show Recorder' we would like to
# format the MDATA_KEY_TITLE slightly differently # Try to parse bpm but delete the whole key if that fails
# Note: I don't know why I'm doing a unicode string comparison here if 'MDATA_KEY_BPM' in new_md:
# that part is copied from the original code 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 is_airtime_recorded(new_md): if is_airtime_recorded(new_md):
hour,minute,second,name = md['MDATA_KEY_TITLE'].split("-",3) hour,minute,second,name = new_md['MDATA_KEY_TITLE'].split("-",3)
# We assume that MDATA_KEY_YEAR is always given for airtime recorded
# shows
new_md['MDATA_KEY_TITLE'] = u'%s-%s-%s:%s:%s' % \ new_md['MDATA_KEY_TITLE'] = u'%s-%s-%s:%s:%s' % \
(name, new_md['MDATA_KEY_YEAR'], hour, minute, second) (name, new_md['MDATA_KEY_YEAR'], hour, minute, second)
# IMPORTANT: in the original code. MDATA_KEY_FILEPATH would also else:
# be set to the original path of the file for airtime recorded shows # Read title from filename if it does not exist
# (before it was "organized"). We will skip this procedure for now new_md = default_to(dictionary=new_md, keys=['MDATA_KEY_TITLE'],
# because it's not clear why it was done default=no_extension_basename(original_path))
return new_md return new_md
def organized_path(old_path, root_path, orig_md): def organized_path(old_path, root_path, orig_md):
@ -275,14 +275,21 @@ def organized_path(old_path, root_path, orig_md):
else: return True else: return True
# We set some metadata elements to a default "unknown" value because we use # We set some metadata elements to a default "unknown" value because we use
# these fields to create a path hence they cannot be empty # these fields to create a path hence they cannot be empty
# Here "normal" means normalized only for organized path
normal_md = default_to_f(orig_md, path_md, unicode_unknown, default_f) normal_md = default_to_f(orig_md, path_md, unicode_unknown, default_f)
if normal_md['MDATA_KEY_BITRATE']:
formatted = str(int(normal_md['MDATA_KEY_BITRATE']) / 1000)
normal_md['MDATA_KEY_BITRATE'] = formatted + 'kbps'
else: normal_md['MDATA_KEY_BITRATE'] = unicode_unknown
if is_airtime_recorded(normal_md): if is_airtime_recorded(normal_md):
fname = u'%s-%s-%s.%s' % ( normal_md['MDATA_KEY_YEAR'], title_re = re.match("(?P<show>\w+)-(?P<date>\d+-\d+-\d+-\d+:\d+:\d+)$",
normal_md['MDATA_KEY_TITLE'], normal_md['MDATA_KEY_TITLE'])
normal_md['MDATA_KEY_BITRATE'], ext ) show_name, = title_re.group('show'),
yyyy, mm, _ = normal_md['MDATA_KEY_YEAR'].split('-',3) date = title_re.group('date').replace(':','-')
path = os.path.join(root_path, yyyy, mm) yyyy, mm, _ = normal_md['MDATA_KEY_YEAR'].split('-',2)
filepath = os.path.join(path,fname) fname_base = '%s-%s-%s.%s' % \
(date, show_name, normal_md['MDATA_KEY_BITRATE'], ext)
filepath = os.path.join(root_path, yyyy, mm, fname_base)
elif len(normal_md['MDATA_KEY_TRACKNUMBER']) == 0: elif len(normal_md['MDATA_KEY_TRACKNUMBER']) == 0:
fname = u'%s-%s.%s' % (normal_md['MDATA_KEY_TITLE'], fname = u'%s-%s.%s' % (normal_md['MDATA_KEY_TITLE'],
normal_md['MDATA_KEY_BITRATE'], ext) normal_md['MDATA_KEY_BITRATE'], ext)

View File

@ -2,6 +2,7 @@
import unittest import unittest
import os import os
import media.monitor.pure as mmp import media.monitor.pure as mmp
from media.monitor.metadata import Metadata
class TestMMP(unittest.TestCase): class TestMMP(unittest.TestCase):
def setUp(self): def setUp(self):
@ -34,23 +35,32 @@ class TestMMP(unittest.TestCase):
for k in def_keys: self.assertEqual( sd[k], 'DEF' ) for k in def_keys: self.assertEqual( sd[k], 'DEF' )
def test_normalized_metadata(self): def test_normalized_metadata(self):
normal = mmp.normalized_metadata(self.md1,"") # Recorded show test first
self.assertTrue(hasattr(normal['MDATA_KEY_CREATOR'],'startswith')) orig = Metadata.airtime_dict({
self.assertTrue('MDATA_KEY_CREATOR' in normal) 'date': [u'2012-08-21'],
self.assertTrue('MDATA_KEY_SOURCE' in normal) 'tracknumber': [u'2'],
'title': [u'11-29-00-record'],
'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'256kbps'
def test_organized_path(self): old_path = "/home/rudi/recorded/2012-08-21-11:29:00.ogg"
o_path = '/home/rudi/throwaway/ACDC_-_Back_In_Black-sample-64kbps.ogg' normalized = mmp.normalized_metadata(orig, old_path)
normal = mmp.normalized_metadata(self.md1,o_path) print(normalized)
og = mmp.organized_path(o_path, self.assertEqual( orga, normalized )
'/home/rudi/throwaway/fucking_around/watch/',
normal) organized_base_name = "2012-08-21-11-29-00-record-256kbps.ogg"
real_path1 = \ base = "/srv/airtime/stor/"
u'/home/rudi/throwaway/fucking_around/watch/unknown/unknown/ACDC_-_Back_In_Black-sample-64kbps-64kbps.ogg' organized_path = mmp.organized_path(old_path,base, normalized)
self.assertTrue( 'unknown' in og, True ) self.assertEqual(os.path.basename(organized_path), organized_base_name)
self.assertEqual( og, real_path1 ) # TODO : fix this failure
# for recorded it should be something like this
# ./recorded/2012/07/2012-07-09-17-55-00-Untitled Show-256kbps.ogg
def test_file_md5(self): def test_file_md5(self):
p = os.path.realpath(__file__) p = os.path.realpath(__file__)

View File

@ -94,7 +94,6 @@ try:
#liq_path DNE, which is OK. #liq_path DNE, which is OK.
pass pass
os.symlink(liq_path, symlink_path) os.symlink(liq_path, symlink_path)
else: else:
print " * Liquidsoap binary not found!" print " * Liquidsoap binary not found!"

View File

@ -73,16 +73,18 @@ class ShowRecorder(Thread):
else: else:
filetype = "ogg"; filetype = "ogg";
filepath = "%s%s.%s" % (config["base_recorded_files"], filename, filetype) joined_path = os.path.join(config["base_recorded_files"], filename)
filepath = "%s.%s" % (joined_path, filetype)
br = config["record_bitrate"] br = config["record_bitrate"]
sr = config["record_samplerate"] sr = config["record_samplerate"]
c = config["record_channels"] c = config["record_channels"]
ss = config["record_sample_size"] ss = config["record_sample_size"]
#-f:16,2,44100 #-f:16,2,44100
#-b:256 #-b:256
command = "ecasound -f:%s,%s,%s -i alsa -o %s,%s000 -t:%s" % (ss, c, sr, filepath, br, length) command = "ecasound -f:%s,%s,%s -i alsa -o %s,%s000 -t:%s" % \
(ss, c, sr, filepath, br, length)
args = command.split(" ") args = command.split(" ")
self.logger.info("starting record") self.logger.info("starting record")