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