Make substitutions for most invalid characters rather than stripping them.
[pyTivo/wmcbrine.git] / mutagen / __init__.py
blob669ac7a1e92dd9a37736fd111c76864bb978d81e
1 #! /usr/bin/env python
3 # mutagen aims to be an all purpose media tagging library
4 # Copyright (C) 2005 Michael Urman
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of version 2 of the GNU General Public License as
8 # published by the Free Software Foundation.
10 # $Id: __init__.py 4348 2008-12-02 02:41:15Z piman $
13 """Mutagen aims to be an all purpose tagging library.
15 import mutagen.[format]
16 metadata = mutagen.[format].Open(filename)
18 metadata acts like a dictionary of tags in the file. Tags are generally a
19 list of string-like values, but may have additional methods available
20 depending on tag or format. They may also be entirely different objects
21 for certain keys, again depending on format.
22 """
24 version = (1, 20, -1)
25 version_string = ".".join(map(str, version))
27 import warnings
29 import mutagen._util
31 class Metadata(object):
32 """An abstract dict-like object.
34 Metadata is the base class for many of the tag objects in Mutagen.
35 """
37 def __init__(self, *args, **kwargs):
38 if args or kwargs:
39 self.load(*args, **kwargs)
41 def load(self, *args, **kwargs):
42 raise NotImplementedError
44 def save(self, filename=None):
45 raise NotImplementedError
47 def delete(self, filename=None):
48 raise NotImplementedError
50 class FileType(mutagen._util.DictMixin):
51 """An abstract object wrapping tags and audio stream information.
53 Attributes:
54 info -- stream information (length, bitrate, sample rate)
55 tags -- metadata tags, if any
57 Each file format has different potential tags and stream
58 information.
60 FileTypes implement an interface very similar to Metadata; the
61 dict interface, save, load, and delete calls on a FileType call
62 the appropriate methods on its tag data.
63 """
65 info = None
66 tags = None
67 filename = None
68 _mimes = ["application/octet-stream"]
70 def __init__(self, filename=None, *args, **kwargs):
71 if filename is None:
72 warnings.warn("FileType constructor requires a filename",
73 DeprecationWarning)
74 else:
75 self.load(filename, *args, **kwargs)
77 def load(self, filename, *args, **kwargs):
78 raise NotImplementedError
80 def __getitem__(self, key):
81 """Look up a metadata tag key.
83 If the file has no tags at all, a KeyError is raised.
84 """
85 if self.tags is None: raise KeyError, key
86 else: return self.tags[key]
88 def __setitem__(self, key, value):
89 """Set a metadata tag.
91 If the file has no tags, an appropriate format is added (but
92 not written until save is called).
93 """
94 if self.tags is None:
95 self.add_tags()
96 self.tags[key] = value
98 def __delitem__(self, key):
99 """Delete a metadata tag key.
101 If the file has no tags at all, a KeyError is raised.
103 if self.tags is None: raise KeyError, key
104 else: del(self.tags[key])
106 def keys(self):
107 """Return a list of keys in the metadata tag.
109 If the file has no tags at all, an empty list is returned.
111 if self.tags is None: return []
112 else: return self.tags.keys()
114 def delete(self, filename=None):
115 """Remove tags from a file."""
116 if self.tags is not None:
117 if filename is None:
118 filename = self.filename
119 else:
120 warnings.warn(
121 "delete(filename=...) is deprecated, reload the file",
122 DeprecationWarning)
123 return self.tags.delete(filename)
125 def save(self, filename=None, **kwargs):
126 """Save metadata tags."""
127 if filename is None:
128 filename = self.filename
129 else:
130 warnings.warn(
131 "save(filename=...) is deprecated, reload the file",
132 DeprecationWarning)
133 if self.tags is not None:
134 return self.tags.save(filename, **kwargs)
135 else: raise ValueError("no tags in file")
137 def pprint(self):
138 """Print stream information and comment key=value pairs."""
139 stream = "%s (%s)" % (self.info.pprint(), self.mime[0])
140 try: tags = self.tags.pprint()
141 except AttributeError:
142 return stream
143 else: return stream + ((tags and "\n" + tags) or "")
145 def add_tags(self):
146 raise NotImplementedError
148 def __get_mime(self):
149 mimes = []
150 for Kind in type(self).__mro__:
151 for mime in getattr(Kind, '_mimes', []):
152 if mime not in mimes:
153 mimes.append(mime)
154 return mimes
156 mime = property(__get_mime)
158 def File(filename, options=None, easy=False):
159 """Guess the type of the file and try to open it.
161 The file type is decided by several things, such as the first 128
162 bytes (which usually contains a file type identifier), the
163 filename extension, and the presence of existing tags.
165 If no appropriate type could be found, None is returned.
168 if options is None:
169 from mutagen.asf import ASF
170 from mutagen.apev2 import APEv2File
171 from mutagen.flac import FLAC
172 if easy:
173 from mutagen.easyid3 import EasyID3FileType as ID3FileType
174 else:
175 from mutagen.id3 import ID3FileType
176 if easy:
177 from mutagen.mp3 import EasyMP3 as MP3
178 else:
179 from mutagen.mp3 import MP3
180 from mutagen.oggflac import OggFLAC
181 from mutagen.oggspeex import OggSpeex
182 from mutagen.oggtheora import OggTheora
183 from mutagen.oggvorbis import OggVorbis
184 if easy:
185 from mutagen.trueaudio import EasyTrueAudio as TrueAudio
186 else:
187 from mutagen.trueaudio import TrueAudio
188 from mutagen.wavpack import WavPack
189 if easy:
190 from mutagen.easymp4 import EasyMP4 as MP4
191 else:
192 from mutagen.mp4 import MP4
193 from mutagen.musepack import Musepack
194 from mutagen.monkeysaudio import MonkeysAudio
195 from mutagen.optimfrog import OptimFROG
196 options = [MP3, TrueAudio, OggTheora, OggSpeex, OggVorbis, OggFLAC,
197 FLAC, APEv2File, MP4, ID3FileType, WavPack, Musepack,
198 MonkeysAudio, OptimFROG, ASF]
200 if not options:
201 return None
203 fileobj = open(filename, "rb")
204 try:
205 header = fileobj.read(128)
206 # Sort by name after score. Otherwise import order affects
207 # Kind sort order, which affects treatment of things with
208 # equals scores.
209 results = [(Kind.score(filename, fileobj, header), Kind.__name__)
210 for Kind in options]
211 finally:
212 fileobj.close()
213 results = zip(results, options)
214 results.sort()
215 (score, name), Kind = results[-1]
216 if score > 0: return Kind(filename)
217 else: return None