1# -*- coding: utf-8 -*-
2# Copyright (C) 2006  Lukas Lalinsky
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 2 of the License, or
7# (at your option) any later version.
8
9"""Monkey's Audio streams with APEv2 tags.
10
11Monkey's Audio is a very efficient lossless audio compressor developed
12by Matt Ashland.
13
14For more information, see http://www.monkeysaudio.com/.
15"""
16
17__all__ = ["MonkeysAudio", "Open", "delete"]
18
19import struct
20
21from ._compat import endswith
22from mutagen import StreamInfo
23from mutagen.apev2 import APEv2File, error, delete
24from mutagen._util import cdata, convert_error
25
26
27class MonkeysAudioHeaderError(error):
28    pass
29
30
31class MonkeysAudioInfo(StreamInfo):
32    """MonkeysAudioInfo()
33
34    Monkey's Audio stream information.
35
36    Attributes:
37        channels (`int`): number of audio channels
38        length (`float`): file length in seconds, as a float
39        sample_rate (`int`): audio sampling rate in Hz
40        bits_per_sample (`int`): bits per sample
41        version (`float`): Monkey's Audio stream version, as a float (eg: 3.99)
42    """
43
44    @convert_error(IOError, MonkeysAudioHeaderError)
45    def __init__(self, fileobj):
46        """Raises MonkeysAudioHeaderError"""
47
48        header = fileobj.read(76)
49        if len(header) != 76 or not header.startswith(b"MAC "):
50            raise MonkeysAudioHeaderError("not a Monkey's Audio file")
51        self.version = cdata.ushort_le(header[4:6])
52        if self.version >= 3980:
53            (blocks_per_frame, final_frame_blocks, total_frames,
54             self.bits_per_sample, self.channels,
55             self.sample_rate) = struct.unpack("<IIIHHI", header[56:76])
56        else:
57            compression_level = cdata.ushort_le(header[6:8])
58            self.channels, self.sample_rate = struct.unpack(
59                "<HI", header[10:16])
60            total_frames, final_frame_blocks = struct.unpack(
61                "<II", header[24:32])
62            if self.version >= 3950:
63                blocks_per_frame = 73728 * 4
64            elif self.version >= 3900 or (self.version >= 3800 and
65                                          compression_level == 4):
66                blocks_per_frame = 73728
67            else:
68                blocks_per_frame = 9216
69            self.bits_per_sample = 0
70            if header[48:].startswith(b"WAVEfmt"):
71                self.bits_per_sample = struct.unpack("<H", header[74:76])[0]
72        self.version /= 1000.0
73        self.length = 0.0
74        if (self.sample_rate != 0) and (total_frames > 0):
75            total_blocks = ((total_frames - 1) * blocks_per_frame +
76                            final_frame_blocks)
77            self.length = float(total_blocks) / self.sample_rate
78
79    def pprint(self):
80        return u"Monkey's Audio %.2f, %.2f seconds, %d Hz" % (
81            self.version, self.length, self.sample_rate)
82
83
84class MonkeysAudio(APEv2File):
85    """MonkeysAudio(filething)
86
87    Arguments:
88        filething (filething)
89
90    Attributes:
91        info (`MonkeysAudioInfo`)
92    """
93
94    _Info = MonkeysAudioInfo
95    _mimes = ["audio/ape", "audio/x-ape"]
96
97    @staticmethod
98    def score(filename, fileobj, header):
99        return header.startswith(b"MAC ") + endswith(filename.lower(), ".ape")
100
101
102Open = MonkeysAudio
103