Merge branch 'devel' of dev.sourcefabric.org:airtime into devel
This commit is contained in:
commit
f52cb81c64
python_apps
media-monitor2
pypo
|
@ -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()))
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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__)
|
||||||
|
|
|
@ -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!"
|
||||||
|
|
|
@ -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")
|
||||||
|
|
Loading…
Reference in New Issue