1# -*- coding: utf-8 -*- 2# Copyright 2006 Joe Wreschnig 3# 2014 Christoph Reiter 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 as published by 7# the Free Software Foundation; either version 2 of the License, or 8# (at your option) any later version. 9 10"""WavPack reading and writing. 11 12WavPack is a lossless format that uses APEv2 tags. Read 13 14* http://www.wavpack.com/ 15* http://www.wavpack.com/file_format.txt 16 17for more information. 18""" 19 20__all__ = ["WavPack", "Open", "delete"] 21 22from mutagen import StreamInfo 23from mutagen.apev2 import APEv2File, error, delete 24from mutagen._util import cdata, convert_error 25 26 27class WavPackHeaderError(error): 28 pass 29 30RATES = [6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 31 48000, 64000, 88200, 96000, 192000] 32 33 34class _WavPackHeader(object): 35 36 def __init__(self, block_size, version, track_no, index_no, total_samples, 37 block_index, block_samples, flags, crc): 38 39 self.block_size = block_size 40 self.version = version 41 self.track_no = track_no 42 self.index_no = index_no 43 self.total_samples = total_samples 44 self.block_index = block_index 45 self.block_samples = block_samples 46 self.flags = flags 47 self.crc = crc 48 49 @classmethod 50 @convert_error(IOError, WavPackHeaderError) 51 def from_fileobj(cls, fileobj): 52 """A new _WavPackHeader or raises WavPackHeaderError""" 53 54 header = fileobj.read(32) 55 if len(header) != 32 or not header.startswith(b"wvpk"): 56 raise WavPackHeaderError("not a WavPack header: %r" % header) 57 58 block_size = cdata.uint_le(header[4:8]) 59 version = cdata.ushort_le(header[8:10]) 60 track_no = ord(header[10:11]) 61 index_no = ord(header[11:12]) 62 samples = cdata.uint_le(header[12:16]) 63 if samples == 2 ** 32 - 1: 64 samples = -1 65 block_index = cdata.uint_le(header[16:20]) 66 block_samples = cdata.uint_le(header[20:24]) 67 flags = cdata.uint_le(header[24:28]) 68 crc = cdata.uint_le(header[28:32]) 69 70 return _WavPackHeader(block_size, version, track_no, index_no, 71 samples, block_index, block_samples, flags, crc) 72 73 74class WavPackInfo(StreamInfo): 75 """WavPack stream information. 76 77 Attributes: 78 channels (int): number of audio channels (1 or 2) 79 length (float: file length in seconds, as a float 80 sample_rate (int): audio sampling rate in Hz 81 version (int) WavPack stream version 82 """ 83 84 def __init__(self, fileobj): 85 try: 86 header = _WavPackHeader.from_fileobj(fileobj) 87 except WavPackHeaderError: 88 raise WavPackHeaderError("not a WavPack file") 89 90 self.version = header.version 91 self.channels = bool(header.flags & 4) or 2 92 self.sample_rate = RATES[(header.flags >> 23) & 0xF] 93 94 if header.total_samples == -1 or header.block_index != 0: 95 # TODO: we could make this faster by using the tag size 96 # and search backwards for the last block, then do 97 # last.block_index + last.block_samples - initial.block_index 98 samples = header.block_samples 99 while 1: 100 fileobj.seek(header.block_size - 32 + 8, 1) 101 try: 102 header = _WavPackHeader.from_fileobj(fileobj) 103 except WavPackHeaderError: 104 break 105 samples += header.block_samples 106 else: 107 samples = header.total_samples 108 109 self.length = float(samples) / self.sample_rate 110 111 def pprint(self): 112 return u"WavPack, %.2f seconds, %d Hz" % (self.length, 113 self.sample_rate) 114 115 116class WavPack(APEv2File): 117 _Info = WavPackInfo 118 _mimes = ["audio/x-wavpack"] 119 120 @staticmethod 121 def score(filename, fileobj, header): 122 return header.startswith(b"wvpk") * 2 123 124 125Open = WavPack 126