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