Added script to semiautomatically rename mp3 and ogg files according to their id3...
[wolnelektury.git] / lib / mutagen / musepack.py
1 # A Musepack reader/tagger
2 #
3 # Copyright 2006 Lukas Lalinsky <lalinsky@gmail.com>
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License version 2 as
7 # published by the Free Software Foundation.
8 #
9 # $Id: musepack.py 4275 2008-06-01 06:32:37Z piman $
10
11 """Musepack audio streams with APEv2 tags.
12
13 Musepack is an audio format originally based on the MPEG-1 Layer-2
14 algorithms. Stream versions 4 through 7 are supported.
15
16 For more information, see http://www.musepack.net/.
17 """
18
19 __all__ = ["Musepack", "Open", "delete"]
20
21 import struct
22
23 from mutagen.apev2 import APEv2File, error, delete
24 from mutagen.id3 import BitPaddedInt
25 from mutagen._util import cdata
26
27 class MusepackHeaderError(error): pass
28
29 RATES = [44100, 48000, 37800, 32000]
30
31 class MusepackInfo(object):
32     """Musepack stream information.
33
34     Attributes:
35     channels -- number of audio channels
36     length -- file length in seconds, as a float
37     sample_rate -- audio sampling rate in Hz
38     bitrate -- audio bitrate, in bits per second 
39     version -- Musepack stream version
40
41     Optional Attributes:
42     title_gain, title_peak -- Replay Gain and peak data for this song
43     album_gain, album_peak -- Replay Gain and peak data for this album
44
45     These attributes are only available in stream version 7. The
46     gains are a float, +/- some dB. The peaks are a percentage [0..1] of
47     the maximum amplitude. This means to get a number comparable to
48     VorbisGain, you must multiply the peak by 2.
49     """
50
51     def __init__(self, fileobj):
52         header = fileobj.read(32)
53         if len(header) != 32:
54             raise MusepackHeaderError("not a Musepack file")
55         # Skip ID3v2 tags
56         if header[:3] == "ID3":
57             size = 10 + BitPaddedInt(header[6:10])
58             fileobj.seek(size)
59             header = fileobj.read(32)
60             if len(header) != 32:
61                 raise MusepackHeaderError("not a Musepack file")
62         # SV7
63         if header.startswith("MP+"):
64             self.version = ord(header[3]) & 0xF
65             if self.version < 7:
66                 raise MusepackHeaderError("not a Musepack file")
67             frames = cdata.uint_le(header[4:8])
68             flags = cdata.uint_le(header[8:12])
69
70             self.title_peak, self.title_gain = struct.unpack(
71                 "<Hh", header[12:16])
72             self.album_peak, self.album_gain = struct.unpack(
73                 "<Hh", header[16:20])
74             self.title_gain /= 100.0
75             self.album_gain /= 100.0
76             self.title_peak /= 65535.0
77             self.album_peak /= 65535.0
78
79             self.sample_rate = RATES[(flags >> 16) & 0x0003]
80             self.bitrate = 0
81         # SV4-SV6
82         else:
83             header_dword = cdata.uint_le(header[0:4])
84             self.version = (header_dword >> 11) & 0x03FF;
85             if self.version < 4 or self.version > 6:
86                 raise MusepackHeaderError("not a Musepack file")
87             self.bitrate = (header_dword >> 23) & 0x01FF;
88             self.sample_rate = 44100
89             if self.version >= 5:
90                 frames = cdata.uint_le(header[4:8])
91             else:
92                 frames = cdata.ushort_le(header[6:8])
93             if self.version < 6:
94                 frames -= 1
95         self.channels = 2
96         self.length = float(frames * 1152 - 576) / self.sample_rate
97         if not self.bitrate and self.length != 0:
98             fileobj.seek(0, 2)
99             self.bitrate = int(fileobj.tell() * 8 / (self.length * 1000) + 0.5)
100
101     def pprint(self):
102         if self.version >= 7:
103             rg_data = ", Gain: %+0.2f (title), %+0.2f (album)" %(
104                 self.title_gain, self.album_gain)
105         else:
106             rg_data = ""
107         return "Musepack, %.2f seconds, %d Hz%s" % (
108             self.length, self.sample_rate, rg_data)
109
110 class Musepack(APEv2File):
111     _Info = MusepackInfo
112     _mimes = ["audio/x-musepack", "audio/x-mpc"]
113
114     def score(filename, fileobj, header):
115         return header.startswith("MP+") + filename.endswith(".mpc")
116     score = staticmethod(score)
117
118 Open = Musepack