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