added FLAC support to MediaFile (with tests)

--HG--
extra : convert_revision : svn%3A41726ec3-264d-0410-9c23-a9f1637257cc/trunk%4054
This commit is contained in:
adrian.sampson 2008-07-06 00:44:37 +00:00
parent 7e24b5d3ca
commit ec7e41a904
5 changed files with 63 additions and 44 deletions

View file

@ -13,7 +13,7 @@ A field will always return a reasonable value of the correct type, even if no
tag is present. If no value is available, the value will be false (e.g., zero tag is present. If no value is available, the value will be false (e.g., zero
or the empty string).""" or the empty string)."""
from mutagen import mp4, mp3, id3 import mutagen
import os.path import os.path
__all__ = ['FileTypeError', 'MediaFile'] __all__ = ['FileTypeError', 'MediaFile']
@ -115,21 +115,28 @@ class MediaField(object):
STYLE_RIGHT = 1 << 6 # likewise, in second entry STYLE_RIGHT = 1 << 6 # likewise, in second entry
# These are mutually exclusive and relevant only with SLASHED and 2PLE. # These are mutually exclusive and relevant only with SLASHED and 2PLE.
def __init__(self, id3key, mp4key, def __init__(self, id3key, mp4key, flackey,
# in ID3 tags, use only the frame with this "desc" field # in ID3 tags, use only the frame with this "desc" field
id3desc=None, id3desc=None,
# compositions of the TYPE_ flag above # compositions of the TYPE_ flag above
id3type=TYPE_UNICODE|TYPE_LIST, mp4type=TYPE_UNICODE|TYPE_LIST, id3type = TYPE_UNICODE|TYPE_LIST,
mp4type = TYPE_UNICODE|TYPE_LIST,
flactype = TYPE_UNICODE|TYPE_LIST,
# compositions of STYLE_ flags # compositions of STYLE_ flags
id3style=STYLE_UNICODE, mp4style=STYLE_UNICODE id3style = STYLE_UNICODE,
mp4style = STYLE_UNICODE,
flacstyle = STYLE_UNICODE
): ):
self.keys = { 'mp3': id3key, self.keys = { 'mp3': id3key,
'mp4': mp4key } 'mp4': mp4key,
self.types = { 'mp3': id3type, 'flac': flackey }
'mp4': mp4type } self.types = { 'mp3': id3type,
self.styles = { 'mp3': id3style, 'mp4': mp4type,
'mp4': mp4style } 'flac': flactype }
self.styles = { 'mp3': id3style,
'mp4': mp4style,
'flac': flacstyle }
self.id3desc = id3desc self.id3desc = id3desc
def _fetchdata(self, obj): def _fetchdata(self, obj):
@ -186,12 +193,12 @@ class MediaField(object):
# need to make a new frame? # need to make a new frame?
if not found: if not found:
frame = id3.Frames[mykey](encoding=3, desc=self.id3desc, frame = mutagen.id3.Frames[mykey](
text=val) encoding=3, desc=self.id3desc, text=val)
obj.mgfile.tags.add(frame) obj.mgfile.tags.add(frame)
else: # no match on desc; just replace based on key else: # no match on desc; just replace based on key
frame = id3.Frames[mykey](encoding=3, text=val) frame = mutagen.id3.Frames[mykey](encoding=3, text=val)
obj.mgfile.tags.setall(mykey, [frame]) obj.mgfile.tags.setall(mykey, [frame])
else: else:
obj.mgfile[mykey] = out obj.mgfile[mykey] = out
@ -301,16 +308,19 @@ class MediaFile(object):
metadata.""" metadata."""
def __init__(self, path): def __init__(self, path):
root, ext = os.path.splitext(path) self.mgfile = mutagen.File(path)
if ext == '.mp3': if self.mgfile is None: # Mutagen couldn't guess the type
self.type = 'mp3' raise FileTypeError('file type unsupported by Mutagen')
self.mgfile = mp3.Open(path) elif type(self.mgfile).__name__ == 'M4A' or \
# mgfile = mutagen file = that which a MediaFile wraps type(self.mgfile).__name__ == 'MP4':
elif ext == '.m4a' or ext == '.mp4' or ext == '.m4b' or ext == '.m4p':
self.type = 'mp4' self.type = 'mp4'
self.mgfile = mp4.Open(path) elif type(self.mgfile).__name__ == 'ID3' or \
type(self.mgfile).__name__ == 'MP3':
self.type = 'mp3'
elif type(self.mgfile).__name__ == 'FLAC':
self.type = 'flac'
else: else:
raise FileTypeError('unsupported file extension: ' + ext) raise FileTypeError('file type unsupported by MediaFile')
# add a set of tags if it's missing # add a set of tags if it's missing
if not self.mgfile.tags: if not self.mgfile.tags:
@ -322,39 +332,48 @@ class MediaFile(object):
#### field definitions #### #### field definitions ####
title = MediaField('TIT2', "\xa9nam") title = MediaField('TIT2', "\xa9nam", 'title')
artist = MediaField('TPE1', "\xa9ART") artist = MediaField('TPE1', "\xa9ART", 'artist')
album = MediaField('TALB', "\xa9alb") album = MediaField('TALB', "\xa9alb", 'album')
genre = MediaField('TCON', "\xa9gen") genre = MediaField('TCON', "\xa9gen", 'genre')
composer = MediaField('TCOM', "\xa9wrt") composer = MediaField('TCOM', "\xa9wrt", 'composer')
grouping = MediaField('TIT1', "\xa9grp") grouping = MediaField('TIT1', "\xa9grp", 'grouping')
year = MediaField('TDRC', "\xa9day", year = MediaField('TDRC', "\xa9day", 'date',
id3style=MediaField.STYLE_INTEGER, id3style=MediaField.STYLE_INTEGER,
mp4style=MediaField.STYLE_INTEGER) mp4style=MediaField.STYLE_INTEGER,
track = MediaField('TRCK', 'trkn', flacstyle=MediaField.STYLE_INTEGER)
track = MediaField('TRCK', 'trkn', 'tracknumber',
id3style=MediaField.STYLE_SLASHED | MediaField.STYLE_LEFT, id3style=MediaField.STYLE_SLASHED | MediaField.STYLE_LEFT,
mp4type=MediaField.TYPE_LIST, mp4type=MediaField.TYPE_LIST,
mp4style=MediaField.STYLE_2PLE | MediaField.STYLE_LEFT) mp4style=MediaField.STYLE_2PLE | MediaField.STYLE_LEFT,
maxtrack = MediaField('TRCK', 'trkn', flacstyle=MediaField.STYLE_INTEGER)
maxtrack = MediaField('TRCK', 'trkn', 'tracktotal',
id3style=MediaField.STYLE_SLASHED | MediaField.STYLE_RIGHT, id3style=MediaField.STYLE_SLASHED | MediaField.STYLE_RIGHT,
mp4type=MediaField.TYPE_LIST, mp4type=MediaField.TYPE_LIST,
mp4style=MediaField.STYLE_2PLE | MediaField.STYLE_RIGHT) mp4style=MediaField.STYLE_2PLE | MediaField.STYLE_RIGHT,
disc = MediaField('TPOS', 'disk', flacstyle=MediaField.STYLE_INTEGER)
disc = MediaField('TPOS', 'disk', 'disc',
id3style=MediaField.STYLE_SLASHED | MediaField.STYLE_LEFT, id3style=MediaField.STYLE_SLASHED | MediaField.STYLE_LEFT,
mp4type=MediaField.TYPE_LIST, mp4type=MediaField.TYPE_LIST,
mp4style=MediaField.STYLE_2PLE | MediaField.STYLE_LEFT) mp4style=MediaField.STYLE_2PLE | MediaField.STYLE_LEFT,
maxdisc = MediaField('TPOS', 'disk', flacstyle=MediaField.STYLE_INTEGER)
maxdisc = MediaField('TPOS', 'disk', 'disctotal',
id3style=MediaField.STYLE_SLASHED | MediaField.STYLE_RIGHT, id3style=MediaField.STYLE_SLASHED | MediaField.STYLE_RIGHT,
mp4type=MediaField.TYPE_LIST, mp4type=MediaField.TYPE_LIST,
mp4style=MediaField.STYLE_2PLE | MediaField.STYLE_RIGHT) mp4style=MediaField.STYLE_2PLE | MediaField.STYLE_RIGHT,
lyrics = MediaField(u"USLT", "\xa9lyr", id3desc=u'', flacstyle=MediaField.STYLE_INTEGER)
lyrics = MediaField(u"USLT", "\xa9lyr", 'lyrics',
id3desc=u'',
id3type=MediaField.TYPE_UNICODE) id3type=MediaField.TYPE_UNICODE)
comments = MediaField(u"COMM", "\xa9cmt", id3desc=u'') comments = MediaField(u"COMM", "\xa9cmt", 'description',
bpm = MediaField('TBPM', 'tmpo', id3desc=u'')
bpm = MediaField('TBPM', 'tmpo', 'bpm',
id3style=MediaField.STYLE_INTEGER, id3style=MediaField.STYLE_INTEGER,
mp4type=MediaField.TYPE_LIST | MediaField.TYPE_INTEGER, mp4type=MediaField.TYPE_LIST | MediaField.TYPE_INTEGER,
mp4style=MediaField.STYLE_INTEGER) mp4style=MediaField.STYLE_INTEGER,
comp = MediaField('TCMP', 'cpil', flacstyle=MediaField.STYLE_INTEGER)
comp = MediaField('TCMP', 'cpil', 'compilation',
id3style=MediaField.STYLE_BOOLEAN, id3style=MediaField.STYLE_BOOLEAN,
mp4type=MediaField.TYPE_BOOLEAN, mp4type=MediaField.TYPE_BOOLEAN,
mp4style=MediaField.STYLE_BOOLEAN) mp4style=MediaField.STYLE_BOOLEAN,
flacstyle=MediaField.STYLE_BOOLEAN)

BIN
test/rsrc/full.flac Normal file

Binary file not shown.

BIN
test/rsrc/min.flac Normal file

Binary file not shown.

BIN
test/rsrc/partial.flac Normal file

Binary file not shown.

View file

@ -150,7 +150,7 @@ def suite():
s = unittest.TestSuite() s = unittest.TestSuite()
# General tests. # General tests.
for kind in ('m4a', 'mp3'): for kind in ('m4a', 'mp3', 'flac'):
for tagset in ('full', 'partial', 'min'): for tagset in ('full', 'partial', 'min'):
path = 'rsrc' + os.sep + tagset + '.' + kind path = 'rsrc' + os.sep + tagset + '.' + kind
correct_dict = correct_dicts[tagset] correct_dict = correct_dicts[tagset]