1#!/usr/bin/python
2
3# Audio Tools, a module and set of tools for manipulating audio data
4# Copyright (C) 2007-2014  Brian Langenberger
5
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19
20import unittest
21import audiotools
22import tempfile
23import os
24import os.path
25from hashlib import md5
26import random
27import decimal
28import test_streams
29import subprocess
30try:
31    from configparser import SafeConfigParser
32except ImportError:
33    from ConfigParser import SafeConfigParser
34
35parser = SafeConfigParser()
36parser.read("test.cfg")
37
38
39def do_nothing(self):
40    pass
41
42
43# add a bunch of decorator metafunctions like LIB_CORE
44# which can be wrapped around individual tests as needed
45for section in parser.sections():
46    for option in parser.options(section):
47        if parser.getboolean(section, option):
48            vars()["%s_%s" % (section.upper(),
49                              option.upper())] = lambda function: function
50        else:
51            vars()["%s_%s" % (section.upper(),
52                              option.upper())] = lambda function: do_nothing
53
54
55def BLANK_PCM_Reader(length, sample_rate=44100, channels=2,
56                     bits_per_sample=16, channel_mask=None):
57    from audiotools.decoders import SameSample
58
59    if channel_mask is None:
60        channel_mask = int(audiotools.ChannelMask.from_channels(channels))
61
62    return SameSample(sample=1,
63                      total_pcm_frames=length * sample_rate,
64                      sample_rate=sample_rate,
65                      channels=channels,
66                      channel_mask=channel_mask,
67                      bits_per_sample=bits_per_sample)
68
69
70class EXACT_RANDOM_PCM_Reader(object):
71    def __init__(self, pcm_frames,
72                 sample_rate=44100, channels=2, bits_per_sample=16,
73                 channel_mask=None):
74        self.sample_rate = sample_rate
75        self.channels = channels
76        if channel_mask is None:
77            self.channel_mask = \
78                int(audiotools.ChannelMask.from_channels(channels))
79        else:
80            self.channel_mask = channel_mask
81        self.bits_per_sample = bits_per_sample
82
83        self.total_frames = pcm_frames
84        self.original_frames = self.total_frames
85
86        self.read = self.read_opened
87
88    def __enter__(self):
89        return self
90
91    def __exit__(self, exc_type, exc_value, traceback):
92        self.close()
93
94    def read_opened(self, pcm_frames):
95        if self.total_frames > 0:
96            frames_to_read = min(pcm_frames, self.total_frames)
97            frame = audiotools.pcm.FrameList(
98                os.urandom(frames_to_read *
99                           (self.bits_per_sample // 8) *
100                           self.channels),
101                self.channels,
102                self.bits_per_sample,
103                True,
104                True)
105            self.total_frames -= frame.frames
106            return frame
107        else:
108            return audiotools.pcm.empty_framelist(
109                self.channels, self.bits_per_sample)
110
111    def read_closed(self, pcm_frames):
112        raise ValueError("unable to read closed stream")
113
114    def close(self):
115        self.read = self.read_closed
116
117    def reset(self):
118        self.read = self.read_opened
119        self.total_frames = self.original_frames
120
121
122class RANDOM_PCM_Reader(EXACT_RANDOM_PCM_Reader):
123    def __init__(self, length,
124                 sample_rate=44100, channels=2, bits_per_sample=16,
125                 channel_mask=None):
126        EXACT_RANDOM_PCM_Reader.__init__(
127            self,
128            pcm_frames=length * sample_rate,
129            sample_rate=sample_rate,
130            channels=channels,
131            bits_per_sample=bits_per_sample,
132            channel_mask=channel_mask)
133
134
135def EXACT_BLANK_PCM_Reader(pcm_frames, sample_rate=44100, channels=2,
136                           bits_per_sample=16, channel_mask=None):
137    from audiotools.decoders import SameSample
138
139    if channel_mask is None:
140        channel_mask = int(audiotools.ChannelMask.from_channels(channels))
141
142    return SameSample(sample=1,
143                      total_pcm_frames=pcm_frames,
144                      sample_rate=sample_rate,
145                      channels=channels,
146                      channel_mask=channel_mask,
147                      bits_per_sample=bits_per_sample)
148
149
150def EXACT_SILENCE_PCM_Reader(pcm_frames, sample_rate=44100, channels=2,
151                             bits_per_sample=16, channel_mask=None):
152    from audiotools.decoders import SameSample
153
154    if channel_mask is None:
155        channel_mask = int(audiotools.ChannelMask.from_channels(channels))
156
157    return SameSample(sample=0,
158                      total_pcm_frames=pcm_frames,
159                      sample_rate=sample_rate,
160                      channels=channels,
161                      channel_mask=channel_mask,
162                      bits_per_sample=bits_per_sample)
163
164
165class MD5_Reader(audiotools.PCMReader):
166    def __init__(self, pcmreader):
167        audiotools.PCMReader.__init__(
168            self,
169            sample_rate=pcmreader.sample_rate,
170            channels=pcmreader.channels,
171            channel_mask=pcmreader.channel_mask,
172            bits_per_sample=pcmreader.bits_per_sample)
173        self.pcmreader = pcmreader
174        self.md5 = md5()
175
176    def __repr__(self):
177        return "MD5Reader(%s,%s,%s)" % (self.sample_rate,
178                                        self.channels,
179                                        self.bits_per_sample)
180
181    def reset(self):
182        if hasattr(self.pcmreader, "reset"):
183            self.pcmreader.reset()
184        self.md5 = md5()
185
186    def read(self, pcm_frames):
187        framelist = self.pcmreader.read(pcm_frames)
188        self.md5.update(framelist.to_bytes(False, True))
189        return framelist
190
191    def close(self):
192        self.pcmreader.close()
193
194    def digest(self):
195        return self.md5.digest()
196
197    def hexdigest(self):
198        return self.md5.hexdigest()
199
200
201class Variable_Reader(audiotools.PCMReader):
202    def __init__(self, pcmreader):
203        audiotools.PCMReader.__init__(
204            self,
205            sample_rate=pcmreader.sample_rate,
206            channels=pcmreader.channels,
207            channel_mask=pcmreader.channel_mask,
208            bits_per_sample=pcmreader.bits_per_sample)
209        self.pcmreader = audiotools.BufferedPCMReader(pcmreader)
210        self.md5 = md5()
211        self.range = range(self.channels * (self.bits_per_sample // 8),
212                           4096)
213
214    def read(self, pcm_frames):
215        return self.pcmreader.read(random.choice(self.range))
216
217    def close(self):
218        self.pcmreader.close()
219
220
221class Join_Reader(audiotools.PCMReader):
222    # given a list of 1 channel PCM readers,
223    # combines them into a single reader
224    # a bit like PCMCat but across channels instead of PCM frames
225    def __init__(self, pcm_readers, channel_mask):
226        if len({r.sample_rate for r in pcm_readers}) != 1:
227            raise ValueError("all readers must have the same sample rate")
228        if len({r.bits_per_sample for r in pcm_readers}) != 1:
229            raise ValueError("all readers must have the same bits per sample")
230        if {r.channels for r in pcm_readers} != {1}:
231            raise ValueError("all readers must be 1 channel")
232        assert(isinstance(channel_mask, int))
233
234        audiotools.PCMReader.__init__(
235            self,
236            sample_rate=pcm_readers[0].sample_rate,
237            channels=len(pcm_readers),
238            channel_mask=channel_mask,
239            bits_per_sample=pcm_readers[0].bits_per_sample)
240
241        self.pcm_readers = pcm_readers
242        self.readers = map(audiotools.BufferedPCMReader, pcm_readers)
243
244    def read(self, pcm_frames):
245        return audiotools.pcm.from_channels(
246            [r.read(pcm_frames) for r in self.pcm_readers])
247
248    def reset(self):
249        for r in self.pcm_readers:
250            r.reset()
251        self.readers = map(audiotools.BufferedPCMReader, self.pcm_readers)
252
253    def close(self):
254        for r in self.pcm_readers:
255            r.close()
256
257
258class FrameCounter:
259    def __init__(self, channels, bits_per_sample, sample_rate, value=0):
260        self.channels = channels
261        self.bits_per_sample = bits_per_sample
262        self.sample_rate = sample_rate
263        self.value = value
264
265    def __repr__(self):
266        return "FrameCounter(%d %d %d %d)" % \
267            (self.channels,
268             self.bits_per_sample,
269             self.sample_rate,
270             self.value)
271
272    def update(self, f):
273        self.value += len(f)
274
275    def __int__(self):
276        return int(round(decimal.Decimal(self.value) /
277                         (self.channels *
278                          (self.bits_per_sample // 8) *
279                          self.sample_rate)))
280
281
282# probstat does this better, but I don't want to require that
283# for something used only rarely
284def Combinations(items, n):
285    if n == 0:
286        yield []
287    else:
288        for i in range(len(items)):
289            for combos in Combinations(items[i + 1:], n - 1):
290                yield [items[i]] + combos
291
292
293def Possibilities(*lists):
294    if len(lists) == 0:
295        yield ()
296    else:
297        remainder = list(Possibilities(*lists[1:]))
298        for item in lists[0]:
299            for rem in remainder:
300                yield (item,) + rem
301
302
303from_channels = audiotools.ChannelMask.from_channels
304
305# these are combinations that tend to occur in nature
306SHORT_PCM_COMBINATIONS = \
307    ((11025,  1, int(from_channels(1)), 8),
308     (22050,  1, int(from_channels(1)), 8),
309     (22050,  1, int(from_channels(1)), 16),
310     (32000,  2, int(from_channels(2)), 16),
311     (44100,  1, int(from_channels(1)), 16),
312     (44100,  2, int(from_channels(2)), 16),
313     (48000,  1, int(from_channels(1)), 16),
314     (48000,  2, int(from_channels(2)), 16),
315     (48000,  6, int(audiotools.ChannelMask.from_fields(
316                         front_left=True,
317                         front_right=True,
318                         front_center=True,
319                         low_frequency=True,
320                         back_left=True,
321                         back_right=True)), 16),
322     (192000, 2, int(from_channels(2)), 24),
323     (96000,  6, int(audiotools.ChannelMask.from_fields(
324                         front_left=True,
325                         front_right=True,
326                         front_center=True,
327                         low_frequency=True,
328                         back_left=True,
329                         back_right=True)), 24))
330
331
332TEST_COVER1 = open("test_cover1.jpg", "rb").read()
333
334TEST_COVER2 = open("test_cover2.png", "rb").read()
335
336TEST_COVER3 = open("test_cover3.jpg", "rb").read()
337
338TEST_COVER4 = open("test_cover4.png", "rb").read()
339
340# this is a very large, plain BMP encoded as bz2
341HUGE_BMP = open("huge.bmp.bz2", "rb").read()
342
343
344from test_formats import *
345from test_core import *
346from test_metadata import *
347from test_utils import *
348
349if (__name__ == '__main__'):
350    unittest.main()
351