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 29from io import BytesIO 30import subprocess 31import struct 32 33from test import (parser, 34 BLANK_PCM_Reader, RANDOM_PCM_Reader, 35 EXACT_BLANK_PCM_Reader, EXACT_SILENCE_PCM_Reader, 36 Variable_Reader, 37 EXACT_RANDOM_PCM_Reader, MD5_Reader, 38 Join_Reader, FrameCounter, 39 Combinations, 40 TEST_COVER1, TEST_COVER2, TEST_COVER3, 41 HUGE_BMP) 42 43 44def do_nothing(self): 45 pass 46 47 48# add a bunch of decorator metafunctions like LIB_CORE 49# which can be wrapped around individual tests as needed 50for section in parser.sections(): 51 for option in parser.options(section): 52 if parser.getboolean(section, option): 53 vars()["%s_%s" % (section.upper(), 54 option.upper())] = lambda function: function 55 else: 56 vars()["%s_%s" % (section.upper(), 57 option.upper())] = lambda function: do_nothing 58 59 60class CLOSE_PCM_Reader(audiotools.PCMReader): 61 def __init__(self, pcmreader): 62 audiotools.PCMReader.__init__( 63 self, 64 sample_rate=pcmreader.sample_rate, 65 channels=pcmreader.channels, 66 channel_mask=pcmreader.channel_mask, 67 bits_per_sample=pcmreader.bits_per_sample) 68 self.pcmreader = pcmreader 69 self.closes_called = 0 70 71 def read(self, pcm_frames): 72 return self.pcmreader.read(pcm_frames) 73 74 def close(self): 75 self.closes_called += 1 76 self.pcmreader.close() 77 78 79class ERROR_PCM_Reader(audiotools.PCMReader): 80 def __init__(self, error, 81 sample_rate=44100, channels=2, bits_per_sample=16, 82 channel_mask=None, failure_chance=.2, minimum_successes=0): 83 if channel_mask is None: 84 channel_mask = int(audiotools.ChannelMask.from_channels(channels)) 85 audiotools.PCMReader.__init__( 86 self, 87 sample_rate=sample_rate, 88 channels=channels, 89 bits_per_sample=bits_per_sample, 90 channel_mask=channel_mask) 91 self.error = error 92 93 # this is so we can generate some "live" PCM data 94 # before erroring out due to our error 95 self.failure_chance = failure_chance 96 97 self.minimum_successes = minimum_successes 98 99 self.frame = audiotools.pcm.from_list([0] * self.channels, 100 self.channels, 101 self.bits_per_sample, 102 True) 103 104 def read(self, pcm_frames): 105 if self.minimum_successes > 0: 106 self.minimum_successes -= 1 107 return audiotools.pcm.from_frames( 108 [self.frame for i in range(pcm_frames)]) 109 else: 110 if random.random() <= self.failure_chance: 111 raise self.error 112 else: 113 return audiotools.pcm.from_frames( 114 [self.frame for i in range(pcm_frames)]) 115 116 def close(self): 117 pass 118 119 120class Log: 121 def __init__(self): 122 self.results = [] 123 124 def update(self, *args): 125 self.results.append(args) 126 127 128class Filewrapper: 129 def __init__(self, file): 130 self.file = file 131 132 def read(self, bytes): 133 return self.file.read(bytes) 134 135 def tell(self): 136 return self.file.tell() 137 138 def seek(self, pos): 139 self.file.seek(pos) 140 141 def close(self): 142 self.file.close() 143 144 145class AudioFileTest(unittest.TestCase): 146 def setUp(self): 147 self.audio_class = audiotools.AudioFile 148 self.suffix = "." + self.audio_class.SUFFIX 149 150 @FORMAT_AUDIOFILE 151 def test_init(self): 152 if self.audio_class is audiotools.AudioFile: 153 return 154 155 # first check nonexistent files 156 self.assertRaises(audiotools.InvalidFile, 157 self.audio_class, 158 "/dev/null/foo.%s" % (self.audio_class.SUFFIX)) 159 160 f = tempfile.NamedTemporaryFile(suffix="." + self.audio_class.SUFFIX) 161 try: 162 # then check empty files 163 f.write(b"") 164 f.flush() 165 self.assertEqual(os.path.isfile(f.name), True) 166 self.assertRaises(audiotools.InvalidFile, 167 self.audio_class, 168 f.name) 169 170 # then check files with a bit of junk at the beginning 171 f.write(b'\x1aS\xc9\xf0I\xb2"CW\xd6') 172 f.flush() 173 self.assertGreater(os.path.getsize(f.name), 0) 174 self.assertRaises(audiotools.InvalidFile, 175 self.audio_class, 176 f.name) 177 178 # finally, check unreadable files 179 original_stat = os.stat(f.name)[0] 180 try: 181 os.chmod(f.name, 0) 182 self.assertRaises(audiotools.InvalidFile, 183 self.audio_class, 184 f.name) 185 finally: 186 os.chmod(f.name, original_stat) 187 finally: 188 f.close() 189 190 @FORMAT_AUDIOFILE 191 def test_is_type(self): 192 if self.audio_class is audiotools.AudioFile: 193 return 194 195 valid = tempfile.NamedTemporaryFile(suffix=self.suffix) 196 invalid = tempfile.NamedTemporaryFile(suffix=self.suffix) 197 # generate a valid file and check audiotools.file_type 198 self.audio_class.from_pcm(valid.name, BLANK_PCM_Reader(1)) 199 with open(valid.name, "rb") as f: 200 self.assertEqual(audiotools.file_type(f), self.audio_class) 201 202 # several invalid files and ensure audiotools.file_type 203 # returns None 204 # (though it's *possible* os.urandom might generate a valid file 205 # by virtue of being random that's extremely unlikely in practice) 206 for i in range(256): 207 self.assertEqual(os.path.getsize(invalid.name), i) 208 with open(invalid.name, "rb") as f: 209 self.assertEqual(audiotools.file_type(f), None) 210 invalid.write(os.urandom(1)) 211 invalid.flush() 212 213 valid.close() 214 invalid.close() 215 216 @FORMAT_AUDIOFILE 217 def test_bits_per_sample(self): 218 if self.audio_class is audiotools.AudioFile: 219 return 220 221 temp = tempfile.NamedTemporaryFile(suffix=self.suffix) 222 for bps in (8, 16, 24): 223 track = self.audio_class.from_pcm( 224 temp.name, BLANK_PCM_Reader(1, bits_per_sample=bps)) 225 self.assertEqual(track.bits_per_sample(), bps) 226 track2 = audiotools.open(temp.name) 227 self.assertEqual(track2.bits_per_sample(), bps) 228 temp.close() 229 230 @FORMAT_AUDIOFILE_PLACEHOLDER 231 def test_channels(self): 232 self.assertTrue(False) 233 234 @FORMAT_AUDIOFILE_PLACEHOLDER 235 def test_channel_mask(self): 236 self.assertTrue(False) 237 238 @FORMAT_AUDIOFILE_PLACEHOLDER 239 def test_sample_rate(self): 240 self.assertTrue(False) 241 242 @FORMAT_AUDIOFILE_PLACEHOLDER 243 def test_lossless(self): 244 self.assertTrue(False) 245 246 @FORMAT_AUDIOFILE 247 def test_metadata(self): 248 import string 249 from audiotools import PY3 250 251 if self.audio_class is audiotools.AudioFile: 252 return 253 254 dummy_metadata = audiotools.MetaData( 255 **dict([(field, char if PY3 else char.decode("UTF-8")) for 256 (field, char) in zip( 257 audiotools.MetaData.FIELDS, 258 string.ascii_letters) 259 if field not in audiotools.MetaData.INTEGER_FIELDS] + 260 [(field, i + 1) for (i, field) in 261 enumerate(audiotools.MetaData.INTEGER_FIELDS)])) 262 temp = tempfile.NamedTemporaryFile(suffix=self.suffix) 263 try: 264 track = self.audio_class.from_pcm(temp.name, 265 BLANK_PCM_Reader(1)) 266 track.set_metadata(dummy_metadata) 267 track = audiotools.open(temp.name) 268 metadata = track.get_metadata() 269 if metadata is None: 270 return 271 272 # check that delete_metadata works 273 nonblank_metadata = audiotools.MetaData( 274 track_name=u"Track Name", 275 track_number=1, 276 track_total=2, 277 album_name=u"Album Name") 278 track.set_metadata(nonblank_metadata) 279 self.assertEqual(track.get_metadata(), nonblank_metadata) 280 track.delete_metadata() 281 metadata = track.get_metadata() 282 if metadata is not None: 283 self.assertEqual( 284 metadata, 285 audiotools.MetaData()) 286 287 track.set_metadata(nonblank_metadata) 288 self.assertEqual(track.get_metadata(), nonblank_metadata) 289 290 old_mode = os.stat(track.filename).st_mode 291 os.chmod(track.filename, 0o400) 292 try: 293 # check IOError on set_metadata() 294 self.assertRaises(IOError, 295 track.set_metadata, 296 audiotools.MetaData(track_name=u"Foo")) 297 298 # check IOError on delete_metadata() 299 self.assertRaises(IOError, 300 track.delete_metadata) 301 finally: 302 os.chmod(track.filename, old_mode) 303 304 os.chmod(track.filename, 0) 305 try: 306 # check IOError on get_metadata() 307 self.assertRaises(IOError, 308 track.get_metadata) 309 finally: 310 os.chmod(track.filename, old_mode) 311 finally: 312 temp.close() 313 314 @FORMAT_AUDIOFILE 315 def test_length(self): 316 if self.audio_class is audiotools.AudioFile: 317 return 318 319 temp = tempfile.NamedTemporaryFile(suffix=self.suffix) 320 try: 321 for seconds in [1, 2, 3, 4, 5, 10, 20, 60, 120]: 322 track = self.audio_class.from_pcm(temp.name, 323 BLANK_PCM_Reader(seconds)) 324 self.assertEqual(int(track.seconds_length()), seconds) 325 finally: 326 temp.close() 327 328 @FORMAT_AUDIOFILE_PLACEHOLDER 329 def test_pcm(self): 330 self.assertTrue(False) 331 332 @FORMAT_AUDIOFILE_PLACEHOLDER 333 def test_convert(self): 334 self.assertTrue(False) 335 336 @FORMAT_AUDIOFILE 337 def test_context_manager(self): 338 if self.audio_class is audiotools.AudioFile: 339 return 340 341 with tempfile.NamedTemporaryFile(suffix=self.suffix) as temp: 342 track = self.audio_class.from_pcm(temp.name, 343 BLANK_PCM_Reader(5)) 344 with track.to_pcm() as pcmreader: 345 frame = pcmreader.read(4096) 346 while len(frame) > 0: 347 frame = pcmreader.read(4096) 348 349 @FORMAT_AUDIOFILE 350 def test_read_leaks(self): 351 # this checks to make sure PCMReader implementations 352 # aren't leaking file handles 353 354 if self.audio_class is audiotools.AudioFile: 355 return 356 elif self.audio_class.NAME == "m4a": 357 # M4A implemented using external programs 358 # so no need to check those 359 return 360 361 # make small temporary file 362 temp = tempfile.NamedTemporaryFile(suffix=self.suffix) 363 track = self.audio_class.from_pcm(temp.name, 364 BLANK_PCM_Reader(10)) 365 366 # open it a large number of times 367 for i in range(5000): 368 pcmreader = track.to_pcm() 369 pcmreader.close() 370 del(pcmreader) 371 372 temp.close() 373 374 @FORMAT_AUDIOFILE 375 def test_close(self): 376 if self.audio_class is audiotools.AudioFile: 377 return 378 379 pcm_frames = 123456 380 381 # ensure regular encode closes pcmreader 382 with tempfile.NamedTemporaryFile( 383 suffix="." + self.audio_class.SUFFIX) as f: 384 reader = CLOSE_PCM_Reader(EXACT_SILENCE_PCM_Reader(pcm_frames)) 385 self.assertEqual(reader.closes_called, 0) 386 track = self.audio_class.from_pcm(f.name, 387 reader) 388 self.assertEqual(reader.closes_called, 1) 389 390 # ensure encode closes pcmreader with "total_pcm_frames" set 391 with tempfile.NamedTemporaryFile( 392 suffix="." + self.audio_class.SUFFIX) as f: 393 reader = CLOSE_PCM_Reader(EXACT_SILENCE_PCM_Reader(pcm_frames)) 394 self.assertEqual(reader.closes_called, 0) 395 track = self.audio_class.from_pcm(f.name, 396 reader, 397 total_pcm_frames=pcm_frames) 398 self.assertEqual(reader.closes_called, 1) 399 400 unwritable_path = "/dev/null/foo." + self.audio_class.SUFFIX 401 self.assertFalse(os.access(unwritable_path, os.W_OK)) 402 403 # encoding to unwritable file should still close pcmreader 404 reader = CLOSE_PCM_Reader(EXACT_SILENCE_PCM_Reader(pcm_frames)) 405 self.assertEqual(reader.closes_called, 0) 406 self.assertRaises(audiotools.EncodingError, 407 self.audio_class.from_pcm, 408 unwritable_path, 409 reader) 410 self.assertEqual(reader.closes_called, 1) 411 412 # encoding to unwritable file with total_pcm_frames 413 # should still close pcmreader 414 reader = CLOSE_PCM_Reader(EXACT_SILENCE_PCM_Reader(pcm_frames)) 415 self.assertEqual(reader.closes_called, 0) 416 self.assertRaises(audiotools.EncodingError, 417 self.audio_class.from_pcm, 418 unwritable_path, 419 reader, 420 total_pcm_frames=pcm_frames) 421 self.assertEqual(reader.closes_called, 1) 422 423 # raising IOError should still close pcmreader 424 reader = CLOSE_PCM_Reader(ERROR_PCM_Reader(IOError("I/O error!"), 425 failure_chance=1.0)) 426 self.assertEqual(reader.closes_called, 0) 427 self.assertRaises(audiotools.EncodingError, 428 self.audio_class.from_pcm, 429 "error." + self.audio_class.SUFFIX, 430 reader) 431 self.assertEqual(reader.closes_called, 1) 432 self.assertFalse(os.path.isfile("error." + self.audio_class.SUFFIX)) 433 434 # raising ValueError should still close pcmreader 435 reader = CLOSE_PCM_Reader(ERROR_PCM_Reader(ValueError("value error!"), 436 failure_chance=1.0)) 437 self.assertEqual(reader.closes_called, 0) 438 self.assertRaises(audiotools.EncodingError, 439 self.audio_class.from_pcm, 440 "error." + self.audio_class.SUFFIX, 441 reader) 442 self.assertEqual(reader.closes_called, 1) 443 self.assertFalse(os.path.isfile("error." + self.audio_class.SUFFIX)) 444 445 # raising IOError with total_pcm_frames set 446 # should still close pcmreader 447 reader = CLOSE_PCM_Reader(ERROR_PCM_Reader(IOError("I/O error!"), 448 failure_chance=1.0)) 449 self.assertEqual(reader.closes_called, 0) 450 self.assertRaises(audiotools.EncodingError, 451 self.audio_class.from_pcm, 452 "error." + self.audio_class.SUFFIX, 453 reader, 454 total_pcm_frames=pcm_frames) 455 self.assertEqual(reader.closes_called, 1) 456 self.assertFalse(os.path.isfile("error." + self.audio_class.SUFFIX)) 457 458 # raising IOError with total_pcm_frames set 459 # should still close pcmreader 460 reader = CLOSE_PCM_Reader(ERROR_PCM_Reader(ValueError("value error!"), 461 failure_chance=1.0)) 462 self.assertEqual(reader.closes_called, 0) 463 self.assertRaises(audiotools.EncodingError, 464 self.audio_class.from_pcm, 465 "error." + self.audio_class.SUFFIX, 466 reader, 467 total_pcm_frames=pcm_frames) 468 self.assertEqual(reader.closes_called, 1) 469 self.assertFalse(os.path.isfile("error." + self.audio_class.SUFFIX)) 470 471 @FORMAT_AUDIOFILE 472 def test_convert_progress(self): 473 if self.audio_class is audiotools.AudioFile: 474 return 475 476 with tempfile.NamedTemporaryFile(suffix=self.suffix) as temp: 477 track = self.audio_class.from_pcm(temp.name, 478 BLANK_PCM_Reader(10)) 479 if track.lossless(): 480 self.assertTrue( 481 audiotools.pcm_cmp(track.to_pcm(), BLANK_PCM_Reader(10))) 482 for audio_class in audiotools.AVAILABLE_TYPES: 483 with tempfile.NamedTemporaryFile( 484 suffix="." + audio_class.SUFFIX) as outfile: 485 log = Log() 486 track2 = track.convert(outfile.name, 487 audio_class, 488 progress=log.update) 489 self.assertGreater( 490 len(log.results), 491 0, 492 "no logging converting %s to %s" % 493 (self.audio_class.NAME, 494 audio_class.NAME)) 495 self.assertEqual(len({r[1] for r in log.results}), 1) 496 for x, y in zip(log.results[1:], log.results): 497 self.assertGreaterEqual((x[0] - y[0]), 0) 498 499 if track.lossless() and track2.lossless(): 500 self.assertTrue( 501 audiotools.pcm_cmp(track.to_pcm(), 502 track2.to_pcm()), 503 "PCM mismatch converting %s to %s" % ( 504 self.audio_class.NAME, 505 audio_class.NAME)) 506 507 @FORMAT_AUDIOFILE 508 def test_track_name(self): 509 import sys 510 511 if self.audio_class is audiotools.AudioFile: 512 return 513 514 format_template = u"Fo\u00f3 %%(%(field)s)s" 515 # first, test the many unicode string fields 516 for field in audiotools.MetaData.FIELDS: 517 if field not in audiotools.MetaData.INTEGER_FIELDS: 518 metadata = audiotools.MetaData() 519 value = u"\u00dcnicode value \u2ec1" 520 setattr(metadata, field, value) 521 format_string = format_template % {u"field": field} 522 track_name = self.audio_class.track_name( 523 file_path="track", 524 track_metadata=metadata, 525 format=(format_string if 526 (sys.version_info[0] >= 3) else 527 format_string.encode("UTF-8", "replace"))) 528 self.assertGreater(len(track_name), 0) 529 if sys.version_info[0] >= 3: 530 self.assertEqual( 531 track_name, 532 (format_template % 533 {u"field": u"foo"} % 534 {u"foo": value})) 535 else: 536 self.assertEqual( 537 track_name, 538 (format_template % 539 {u"field": u"foo"} % 540 {u"foo": value}).encode("UTF-8", "replace")) 541 542 # then, check integer fields 543 format_template = (u"Fo\u00f3 %(album_number)d " + 544 u"%(track_number)2.2d %(album_track_number)s") 545 546 # first, check integers pulled from track metadata 547 for (track_number, album_number, album_track_number) in [ 548 (0, 0, u"00"), 549 (1, 0, u"01"), 550 (25, 0, u"25"), 551 (0, 1, u"100"), 552 (1, 1, u"101"), 553 (25, 1, u"125"), 554 (0, 36, u"3600"), 555 (1, 36, u"3601"), 556 (25, 36, u"3625")]: 557 for basepath in [u"track", 558 u"/foo/bar/track", 559 u"/f\u00f3o/bar/tr\u00e1ck"]: 560 metadata = audiotools.MetaData(track_number=track_number, 561 album_number=album_number) 562 563 if sys.version_info[0] < 3: 564 track_name = self.audio_class.track_name( 565 file_path=basepath.encode("UTF-8", "replace"), 566 track_metadata=metadata, 567 format=format_template.encode("UTF-8", "replace")) 568 569 self.assertEqual( 570 track_name.decode("UTF-8", "replace"), 571 (format_template % {u"album_number": 572 album_number, 573 u"track_number": 574 track_number, 575 u"album_track_number": 576 album_track_number})) 577 else: 578 track_name = self.audio_class.track_name( 579 file_path=basepath, 580 track_metadata=metadata, 581 format=format_template) 582 583 self.assertEqual( 584 track_name, 585 (format_template % {u"album_number": 586 album_number, 587 u"track_number": 588 track_number, 589 u"album_track_number": 590 album_track_number})) 591 592 # also, check track_total/album_total from metadata 593 format_template = u"Fo\u00f3 %(track_total)d %(album_total)d" 594 for track_total in [0, 1, 25, 99]: 595 for album_total in [0, 1, 25, 99]: 596 metadata = audiotools.MetaData(track_total=track_total, 597 album_total=album_total) 598 599 if sys.version_info[0] < 3: 600 track_name = self.audio_class.track_name( 601 file_path="track", 602 track_metadata=metadata, 603 format=format_template.encode("UTF-8", "replace")) 604 605 self.assertEqual( 606 track_name.decode("UTF-8", "replace"), 607 (format_template % {u"track_total": 608 track_total, 609 u"album_total": 610 album_total})) 611 else: 612 track_name = self.audio_class.track_name( 613 file_path="track", 614 track_metadata=metadata, 615 format=format_template) 616 617 self.assertEqual( 618 track_name, 619 (format_template % {u"track_total": 620 track_total, 621 u"album_total": 622 album_total})) 623 624 # ensure %(basename)s is set properly 625 format_template = u"Fo\u00f3 %(basename)s" 626 for (path, base) in [(u"track", u"track"), 627 (u"/foo/bar/track", u"track"), 628 (u"/f\u00f3o/bar/tr\u00e1ck", u"tr\u00e1ck")]: 629 for metadata in [None, audiotools.MetaData()]: 630 if sys.version_info[0] < 3: 631 track_name = self.audio_class.track_name( 632 file_path=path.encode("UTF-8", "replace"), 633 track_metadata=metadata, 634 format=format_template.encode("UTF-8", "replace")) 635 636 self.assertEqual( 637 track_name.decode("UTF-8", "replace"), 638 format_template % {u"basename": base}) 639 else: 640 track_name = self.audio_class.track_name( 641 file_path=path, 642 track_metadata=metadata, 643 format=format_template) 644 645 self.assertEqual( 646 track_name, 647 format_template % {u"basename": base}) 648 649 # ensure %(suffix)s is set properly 650 format_template = u"Fo\u00f3 %(suffix)s" 651 for path in [u"track", 652 u"/foo/bar/track", 653 u"/f\u00f3o/bar/tr\u00e1ck"]: 654 for metadata in [None, audiotools.MetaData()]: 655 if sys.version_info[0] < 3: 656 track_name = self.audio_class.track_name( 657 file_path=path.encode("UTF-8", "replace"), 658 track_metadata=metadata, 659 format=format_template.encode("UTF-8", "replace")) 660 661 self.assertEqual( 662 track_name.decode("UTF-8", "replace"), 663 (format_template % { 664 u"suffix": 665 self.audio_class.SUFFIX.decode('ascii')})) 666 else: 667 track_name = self.audio_class.track_name( 668 file_path=path, 669 track_metadata=metadata, 670 format=format_template) 671 672 self.assertEqual( 673 track_name, 674 (format_template % { 675 u"suffix": 676 self.audio_class.SUFFIX})) 677 678 for metadata in [None, audiotools.MetaData()]: 679 # unsupported template fields raise UnsupportedTracknameField 680 self.assertRaises(audiotools.UnsupportedTracknameField, 681 self.audio_class.track_name, 682 "", metadata, "%(foo)s") 683 684 # broken template fields raise InvalidFilenameFormat 685 self.assertRaises(audiotools.InvalidFilenameFormat, 686 self.audio_class.track_name, 687 "", metadata, "%") 688 689 self.assertRaises(audiotools.InvalidFilenameFormat, 690 self.audio_class.track_name, 691 "", metadata, "%{") 692 693 self.assertRaises(audiotools.InvalidFilenameFormat, 694 self.audio_class.track_name, 695 "", metadata, "%[") 696 697 self.assertRaises(audiotools.InvalidFilenameFormat, 698 self.audio_class.track_name, 699 "", metadata, "%(") 700 701 self.assertRaises(audiotools.InvalidFilenameFormat, 702 self.audio_class.track_name, 703 "", metadata, "%(track_name") 704 705 self.assertRaises(audiotools.InvalidFilenameFormat, 706 self.audio_class.track_name, 707 "", metadata, "%(track_name)") 708 709 @FORMAT_AUDIOFILE 710 def test_replay_gain(self): 711 if self.audio_class.supports_replay_gain(): 712 # make test file 713 temp_file = tempfile.NamedTemporaryFile( 714 suffix="." + self.audio_class.SUFFIX) 715 track = self.audio_class.from_pcm( 716 temp_file.name, 717 test_streams.Sine16_Stereo(44100, 44100, 718 441.0, 0.50, 719 4410.0, 0.49, 1.0)) 720 721 # ensure get_replay_gain() returns None 722 self.assertEqual(track.get_replay_gain(), None) 723 724 # set dummy gain with set_replay_gain() 725 dummy_gain = audiotools.ReplayGain( 726 track_gain=0.25, 727 track_peak=0.125, 728 album_gain=0.50, 729 album_peak=1.0) 730 track.set_replay_gain(dummy_gain) 731 732 # ensure get_replay_gain() returns dummy gain 733 self.assertEqual(track.get_replay_gain(), dummy_gain) 734 735 # delete gain with delete_replay_gain() 736 track.delete_replay_gain() 737 738 # ensure get_replay_gain() returns None again 739 self.assertEqual(track.get_replay_gain(), None) 740 741 # calling delete_replay_gain() again is okay 742 track.delete_replay_gain() 743 self.assertEqual(track.get_replay_gain(), None) 744 745 # ensure setting replay_gain on unwritable file 746 # raises IOError 747 # FIXME 748 749 # ensure getting replay_gain on unreadable file 750 # raises IOError 751 # FIXME 752 753 temp_file.close() 754 755 @FORMAT_AUDIOFILE 756 def test_read_after_eof(self): 757 if self.audio_class is audiotools.AudioFile: 758 return None 759 760 # build basic file 761 temp_file = tempfile.NamedTemporaryFile( 762 suffix="." + self.audio_class.SUFFIX) 763 try: 764 # build a generic file of silence 765 temp_track = self.audio_class.from_pcm( 766 temp_file.name, 767 EXACT_SILENCE_PCM_Reader(44100)) 768 769 # read all the PCM frames from the file 770 pcmreader = temp_track.to_pcm() 771 f = pcmreader.read(4000) 772 while len(f) > 0: 773 f = pcmreader.read(4000) 774 775 self.assertEqual(len(f), 0) 776 777 # then ensure subsequent reads return blank FrameList objects 778 # without triggering an error 779 for i in range(10): 780 f = pcmreader.read(4000) 781 self.assertEqual(len(f), 0) 782 783 pcmreader.close() 784 785 self.assertRaises(ValueError, 786 pcmreader.read, 787 4000) 788 finally: 789 temp_file.close() 790 791 @FORMAT_AUDIOFILE 792 def test_invalid_from_pcm(self): 793 if self.audio_class is audiotools.AudioFile: 794 return 795 796 # test our ERROR_PCM_Reader works 797 self.assertRaises(ValueError, 798 ERROR_PCM_Reader(ValueError("error"), 799 failure_chance=1.0).read, 800 1) 801 self.assertRaises(IOError, 802 ERROR_PCM_Reader(IOError("error"), 803 failure_chance=1.0).read, 804 1) 805 806 # ensure that our dummy file doesn't exist 807 dummy_filename = "invalid." + self.audio_class.SUFFIX 808 if os.path.isfile(dummy_filename): 809 os.unlink(dummy_filename) 810 811 # a decoder that raises IOError on to_pcm() 812 # should trigger an EncodingError 813 self.assertRaises(audiotools.EncodingError, 814 self.audio_class.from_pcm, 815 dummy_filename, 816 ERROR_PCM_Reader(IOError("I/O Error"))) 817 818 # ensure invalid files aren't left lying around 819 self.assertFalse(os.path.isfile(dummy_filename)) 820 821 # perform the same check with total_pcm_frames set 822 self.assertRaises(audiotools.EncodingError, 823 self.audio_class.from_pcm, 824 dummy_filename, 825 ERROR_PCM_Reader(IOError("I/O Error")), 826 total_pcm_frames=44100) 827 828 self.assertFalse(os.path.isfile(dummy_filename)) 829 830 # a decoder that raises ValueError on to_pcm() 831 # should trigger an EncodingError 832 self.assertRaises(audiotools.EncodingError, 833 self.audio_class.from_pcm, 834 dummy_filename, 835 ERROR_PCM_Reader(ValueError("Value Error"))) 836 837 # and ensure invalid files aren't left lying around 838 self.assertFalse(os.path.isfile(dummy_filename)) 839 840 # perform the same check with total_pcm_frames set 841 self.assertRaises(audiotools.EncodingError, 842 self.audio_class.from_pcm, 843 dummy_filename, 844 ERROR_PCM_Reader(ValueError("Value Error")), 845 total_pcm_frames=44100) 846 847 self.assertFalse(os.path.isfile(dummy_filename)) 848 849 @FORMAT_AUDIOFILE 850 def test_total_pcm_frames(self): 851 # all formats take a total_pcm_frames argument to from_pcm() 852 # none are expected to do anything useful with it 853 # but all should raise an exception if the actual amount 854 # of input frames doesn't match 855 856 if self.audio_class is audiotools.AudioFile: 857 return 858 859 temp_name = "test." + self.audio_class.SUFFIX 860 861 if os.path.isfile(temp_name): 862 os.unlink(temp_name) 863 864 # encode a file without the total_pcm_frames argument 865 track = self.audio_class.from_pcm( 866 temp_name, 867 EXACT_SILENCE_PCM_Reader(123456)) 868 track.verify() 869 870 if track.lossless(): 871 self.assertEqual(track.total_frames(), 123456) 872 873 del(track) 874 os.unlink(temp_name) 875 876 # encode a file with the total_pcm_frames argument 877 track = self.audio_class.from_pcm( 878 temp_name, 879 EXACT_SILENCE_PCM_Reader(234567), 880 total_pcm_frames=234567) 881 track.verify() 882 883 if track.lossless(): 884 self.assertEqual(track.total_frames(), 234567) 885 886 del(track) 887 os.unlink(temp_name) 888 889 # check too many total_pcm_frames 890 self.assertRaises(audiotools.EncodingError, 891 self.audio_class.from_pcm, 892 temp_name, 893 EXACT_SILENCE_PCM_Reader(345678), 894 total_pcm_frames=345679) 895 896 self.assertFalse(os.path.isfile(temp_name)) 897 898 # check not enough total_pcm_frames 899 self.assertRaises(audiotools.EncodingError, 900 self.audio_class.from_pcm, 901 temp_name, 902 EXACT_SILENCE_PCM_Reader(345678), 903 total_pcm_frames=345677) 904 905 self.assertFalse(os.path.isfile(temp_name)) 906 907 @FORMAT_AUDIOFILE 908 def test_seekable(self): 909 from hashlib import md5 910 from random import randrange 911 912 if self.audio_class is audiotools.AudioFile: 913 return 914 915 total_pcm_frames = 44100 * 60 * 3 916 917 # create a slightly long file 918 temp_file = tempfile.NamedTemporaryFile( 919 suffix="." + self.audio_class.SUFFIX) 920 try: 921 temp_track = self.audio_class.from_pcm( 922 temp_file.name, 923 EXACT_SILENCE_PCM_Reader(total_pcm_frames), 924 total_pcm_frames=total_pcm_frames) 925 926 if temp_track.seekable(): 927 # get a PCMReader of our format 928 with temp_track.to_pcm() as pcmreader: 929 # hash its data when read to end 930 raw_data = md5() 931 frame = pcmreader.read(4096) 932 while len(frame) > 0: 933 raw_data.update(frame.to_bytes(False, True)) 934 frame = pcmreader.read(4096) 935 936 # seeking to negative values should raise ValueError 937 self.assertRaises(ValueError, 938 pcmreader.seek, 939 -1) 940 941 # seeking to offset 0 should always work 942 # (since it's a very basic rewind) 943 self.assertEqual(pcmreader.seek(0), 0) 944 945 # hash its data again and ensure a match 946 rewound_raw_data = md5() 947 frame = pcmreader.read(4096) 948 while len(frame) > 0: 949 rewound_raw_data.update(frame.to_bytes(False, True)) 950 frame = pcmreader.read(4096) 951 self.assertEqual(raw_data.digest(), 952 rewound_raw_data.digest()) 953 954 # try a bunch of random seeks 955 # and ensure the offset is always <= the seeked value 956 for i in range(10): 957 position = randrange(0, total_pcm_frames) 958 actual_position = pcmreader.seek(position) 959 self.assertLessEqual(actual_position, position) 960 961 # if lossless, ensure seeking works as advertised 962 # by comparing stream to file window 963 actual_remaining_frames = 0 964 desired_remaining_frames = (total_pcm_frames - 965 actual_position) 966 frame = pcmreader.read(4096) 967 while len(frame) > 0: 968 actual_remaining_frames += frame.frames 969 frame = pcmreader.read(4096) 970 971 self.assertEqual(actual_remaining_frames, 972 desired_remaining_frames) 973 974 # seeking to some huge value should work 975 # even if its position doesn't get to the end of the file 976 for value in [2 ** 31, 2 ** 34, 2 ** 38]: 977 seeked = pcmreader.seek(value) 978 self.assertLessEqual(seeked, value, 979 "%s > %s" % (seeked, value)) 980 981 # a PCMReader that's closed should raise ValueError 982 # whenever seek is called 983 pcmreader.close() 984 self.assertRaises(ValueError, 985 pcmreader.seek, 986 0) 987 for i in range(10): 988 self.assertRaises(ValueError, 989 pcmreader.seek, 990 randrange(0, total_pcm_frames)) 991 else: 992 # ensure PCMReader has no .seek() method 993 # or that method always returns to the start of the file 994 with temp_track.to_pcm() as pcmreader: 995 if (hasattr(pcmreader, "seek") and 996 callable(pcmreader.seek)): 997 # try a bunch of random seeks 998 # and ensure the offset is always 0 999 for i in range(10): 1000 position = randrange(0, total_pcm_frames) 1001 self.assertEqual(pcmreader.seek(position), 0) 1002 1003 # seeking to some huge value should work 1004 # even if its position doesn't get 1005 # to the end of the file 1006 for value in [2 ** 31, 2 ** 34, 2 ** 38]: 1007 self.assertEqual(pcmreader.seek(value), 0) 1008 1009 # a PCMReader that's closed should raise ValueError 1010 # whenever seek is called 1011 pcmreader.close() 1012 self.assertRaises(ValueError, 1013 pcmreader.seek, 1014 0) 1015 for i in range(10): 1016 self.assertRaises(ValueError, 1017 pcmreader.seek, 1018 randrange(0, total_pcm_frames)) 1019 finally: 1020 temp_file.close() 1021 1022 # FIXME 1023 @FORMAT_AUDIOFILE_PLACEHOLDER 1024 def test_cuesheet(self): 1025 self.assertTrue(False) 1026 1027 # FIXME 1028 @FORMAT_AUDIOFILE_PLACEHOLDER 1029 def test_verify(self): 1030 self.assertTrue(False) 1031 1032 1033class LosslessFileTest(AudioFileTest): 1034 @FORMAT_LOSSLESS 1035 def test_lossless(self): 1036 if self.audio_class is audiotools.AudioFile: 1037 return 1038 1039 temp = tempfile.NamedTemporaryFile(suffix=self.suffix) 1040 try: 1041 track = self.audio_class.from_pcm(temp.name, BLANK_PCM_Reader(1)) 1042 self.assertEqual(track.lossless(), True) 1043 track = audiotools.open(temp.name) 1044 self.assertEqual(track.lossless(), True) 1045 finally: 1046 temp.close() 1047 1048 @FORMAT_LOSSLESS 1049 def test_channels(self): 1050 if self.audio_class is audiotools.AudioFile: 1051 return 1052 1053 temp = tempfile.NamedTemporaryFile(suffix=self.suffix) 1054 try: 1055 for channels in [1, 2, 3, 4, 5, 6]: 1056 track = self.audio_class.from_pcm( 1057 temp.name, BLANK_PCM_Reader(1, 1058 channels=channels, 1059 channel_mask=0)) 1060 self.assertEqual(track.channels(), channels) 1061 track = audiotools.open(temp.name) 1062 self.assertEqual(track.channels(), channels) 1063 finally: 1064 temp.close() 1065 1066 @FORMAT_LOSSLESS 1067 def test_channel_mask(self): 1068 if self.audio_class is audiotools.AudioFile: 1069 return 1070 1071 temp = tempfile.NamedTemporaryFile(suffix=self.suffix) 1072 try: 1073 for mask in [["front_center"], 1074 ["front_left", 1075 "front_right"], 1076 ["front_left", 1077 "front_right", 1078 "front_center"], 1079 ["front_left", 1080 "front_right", 1081 "back_left", 1082 "back_right"], 1083 ["front_left", 1084 "front_right", 1085 "front_center", 1086 "back_left", 1087 "back_right"], 1088 ["front_left", 1089 "front_right", 1090 "front_center", 1091 "low_frequency", 1092 "back_left", 1093 "back_right"]]: 1094 cm = audiotools.ChannelMask.from_fields( 1095 **dict([(f, True) for f in mask])) 1096 track = self.audio_class.from_pcm( 1097 temp.name, BLANK_PCM_Reader(1, 1098 channels=len(cm), 1099 channel_mask=int(cm))) 1100 self.assertEqual(track.channels(), len(cm)) 1101 if int(track.channel_mask()) != 0: 1102 self.assertEqual(track.channel_mask(), cm) 1103 track = audiotools.open(temp.name) 1104 self.assertEqual(track.channels(), len(cm)) 1105 self.assertEqual(track.channel_mask(), cm) 1106 finally: 1107 temp.close() 1108 1109 @FORMAT_LOSSLESS 1110 def test_sample_rate(self): 1111 if self.audio_class is audiotools.AudioFile: 1112 return 1113 1114 temp = tempfile.NamedTemporaryFile(suffix=self.suffix) 1115 try: 1116 for rate in [8000, 16000, 22050, 44100, 48000, 1117 96000, 192000]: 1118 track = self.audio_class.from_pcm( 1119 temp.name, BLANK_PCM_Reader(1, sample_rate=rate)) 1120 self.assertEqual(track.sample_rate(), rate) 1121 track = audiotools.open(temp.name) 1122 self.assertEqual(track.sample_rate(), rate) 1123 finally: 1124 temp.close() 1125 1126 @FORMAT_LOSSLESS 1127 def test_pcm(self): 1128 if self.audio_class is audiotools.AudioFile: 1129 return 1130 1131 temp = tempfile.NamedTemporaryFile(suffix=self.suffix) 1132 temp2 = tempfile.NamedTemporaryFile() 1133 temp_dir = tempfile.mkdtemp() 1134 try: 1135 for total_pcm_frames in [None, 44100]: 1136 for compression in (None,) + self.audio_class.COMPRESSION_MODES: 1137 # test silence 1138 reader = MD5_Reader(BLANK_PCM_Reader(1)) 1139 if compression is None: 1140 track = self.audio_class.from_pcm( 1141 temp.name, 1142 reader, 1143 total_pcm_frames=total_pcm_frames) 1144 else: 1145 track = self.audio_class.from_pcm( 1146 temp.name, 1147 reader, 1148 compression, 1149 total_pcm_frames=total_pcm_frames) 1150 checksum = md5() 1151 audiotools.transfer_framelist_data(track.to_pcm(), 1152 checksum.update) 1153 self.assertEqual(reader.hexdigest(), checksum.hexdigest()) 1154 1155 # test random noise 1156 reader = MD5_Reader(RANDOM_PCM_Reader(1)) 1157 if compression is None: 1158 track = self.audio_class.from_pcm( 1159 temp.name, 1160 reader, 1161 total_pcm_frames=total_pcm_frames) 1162 else: 1163 track = self.audio_class.from_pcm( 1164 temp.name, 1165 reader, 1166 compression, 1167 total_pcm_frames=total_pcm_frames) 1168 checksum = md5() 1169 audiotools.transfer_framelist_data(track.to_pcm(), 1170 checksum.update) 1171 self.assertEqual(reader.hexdigest(), checksum.hexdigest()) 1172 1173 # test randomly-sized chunks of silence 1174 reader = MD5_Reader(Variable_Reader(BLANK_PCM_Reader(10))) 1175 if compression is None: 1176 track = self.audio_class.from_pcm( 1177 temp.name, 1178 reader, 1179 total_pcm_frames=(total_pcm_frames * 10) 1180 if (total_pcm_frames is not None) else None) 1181 else: 1182 track = self.audio_class.from_pcm( 1183 temp.name, 1184 reader, 1185 compression, 1186 total_pcm_frames=(total_pcm_frames * 10) 1187 if (total_pcm_frames is not None) else None) 1188 checksum = md5() 1189 audiotools.transfer_framelist_data(track.to_pcm(), 1190 checksum.update) 1191 self.assertEqual(reader.hexdigest(), checksum.hexdigest()) 1192 1193 # test randomly-sized chunks of random noise 1194 reader = MD5_Reader(Variable_Reader(RANDOM_PCM_Reader(10))) 1195 if compression is None: 1196 track = self.audio_class.from_pcm( 1197 temp.name, 1198 reader, 1199 total_pcm_frames=(total_pcm_frames * 10) 1200 if (total_pcm_frames is not None) else None) 1201 else: 1202 track = self.audio_class.from_pcm( 1203 temp.name, 1204 reader, 1205 compression, 1206 total_pcm_frames=(total_pcm_frames * 10) 1207 if (total_pcm_frames is not None) else None) 1208 checksum = md5() 1209 audiotools.transfer_framelist_data(track.to_pcm(), 1210 checksum.update) 1211 self.assertEqual(reader.hexdigest(), checksum.hexdigest()) 1212 1213 # test PCMReaders that trigger a DecodingError 1214 self.assertRaises( 1215 ValueError, 1216 ERROR_PCM_Reader(ValueError("error"), 1217 failure_chance=1.0).read, 1218 1) 1219 self.assertRaises( 1220 IOError, 1221 ERROR_PCM_Reader(IOError("error"), 1222 failure_chance=1.0).read, 1223 1) 1224 self.assertRaises( 1225 audiotools.EncodingError, 1226 self.audio_class.from_pcm, 1227 os.path.join(temp_dir, "invalid" + self.suffix), 1228 ERROR_PCM_Reader(IOError("I/O Error"))) 1229 1230 self.assertEqual( 1231 os.path.isfile( 1232 os.path.join(temp_dir, 1233 "invalid" + self.suffix)), 1234 False) 1235 1236 self.assertRaises(audiotools.EncodingError, 1237 self.audio_class.from_pcm, 1238 os.path.join(temp_dir, 1239 "invalid" + self.suffix), 1240 ERROR_PCM_Reader(IOError("I/O Error"))) 1241 1242 self.assertEqual( 1243 os.path.isfile( 1244 os.path.join(temp_dir, 1245 "invalid" + self.suffix)), 1246 False) 1247 1248 # test unwritable output file 1249 self.assertRaises(audiotools.EncodingError, 1250 self.audio_class.from_pcm, 1251 "/dev/null/foo.%s" % (self.suffix), 1252 BLANK_PCM_Reader(1)) 1253 1254 # test without suffix 1255 reader = MD5_Reader(BLANK_PCM_Reader(1)) 1256 if compression is None: 1257 track = self.audio_class.from_pcm( 1258 temp2.name, 1259 reader, 1260 total_pcm_frames=total_pcm_frames) 1261 else: 1262 track = self.audio_class.from_pcm( 1263 temp2.name, 1264 reader, 1265 compression, 1266 total_pcm_frames=total_pcm_frames) 1267 checksum = md5() 1268 audiotools.transfer_framelist_data(track.to_pcm(), 1269 checksum.update) 1270 self.assertEqual(reader.hexdigest(), checksum.hexdigest()) 1271 finally: 1272 temp.close() 1273 temp2.close() 1274 for f in os.listdir(temp_dir): 1275 os.unlink(os.path.join(temp_dir, f)) 1276 os.rmdir(temp_dir) 1277 1278 @FORMAT_LOSSLESS 1279 def test_convert(self): 1280 if self.audio_class is audiotools.AudioFile: 1281 return 1282 1283 # check various round-trip options 1284 with tempfile.NamedTemporaryFile( 1285 suffix="." + self.audio_class.SUFFIX) as temp_input: 1286 track = self.audio_class.from_pcm( 1287 temp_input.name, 1288 test_streams.Sine16_Stereo(441000, 44100, 1289 8820.0, 0.70, 4410.0, 0.29, 1.0)) 1290 for audio_class in audiotools.AVAILABLE_TYPES: 1291 self.assertFalse(os.path.isfile("/dev/null/foo.%s" % 1292 (audio_class.SUFFIX))) 1293 self.assertRaises(audiotools.EncodingError, 1294 track.convert, 1295 "/dev/null/foo.%s" % 1296 (audio_class.SUFFIX), 1297 audio_class) 1298 1299 with tempfile.NamedTemporaryFile( 1300 suffix="." + audio_class.SUFFIX) as temp_output: 1301 track2 = track.convert(temp_output.name, audio_class) 1302 if track2.lossless(): 1303 self.assertTrue( 1304 audiotools.pcm_cmp(track.to_pcm(), 1305 track2.to_pcm()), 1306 "error round-tripping %s to %s" % 1307 (self.audio_class.NAME, 1308 audio_class.NAME)) 1309 else: 1310 pcm = track2.to_pcm() 1311 counter = FrameCounter(pcm.channels, 1312 pcm.bits_per_sample, 1313 pcm.sample_rate) 1314 1315 audiotools.transfer_framelist_data( 1316 pcm, counter.update) 1317 1318 self.assertEqual( 1319 int(counter), 10, 1320 "mismatch encoding %s (%s/%d != %s)" % 1321 (audio_class.NAME, 1322 counter, 1323 int(counter), 1324 10)) 1325 1326 for compression in audio_class.COMPRESSION_MODES: 1327 track2 = track.convert(temp_output.name, 1328 audio_class, 1329 compression) 1330 if track2.lossless(): 1331 self.assertTrue( 1332 audiotools.pcm_cmp(track.to_pcm(), 1333 track2.to_pcm()), 1334 "error round-tripping %s to %s at %s" % 1335 (self.audio_class.NAME, 1336 audio_class.NAME, 1337 compression)) 1338 else: 1339 pcm = track2.to_pcm() 1340 counter = FrameCounter( 1341 pcm.channels, 1342 pcm.bits_per_sample, 1343 pcm.sample_rate) 1344 audiotools.transfer_framelist_data( 1345 pcm, counter.update) 1346 self.assertEqual( 1347 int(counter), 10, 1348 ("mismatch encoding %s " + 1349 "at quality %s (%s != %s)") % 1350 (audio_class.NAME, compression, 1351 counter, 10)) 1352 1353 # check some obvious failures 1354 self.assertRaises(audiotools.EncodingError, 1355 track.convert, 1356 "/dev/null/foo.%s" % 1357 (audio_class.SUFFIX), 1358 audio_class, 1359 compression) 1360 1361 1362class LossyFileTest(AudioFileTest): 1363 @FORMAT_LOSSY 1364 def test_bits_per_sample(self): 1365 if self.audio_class is audiotools.AudioFile: 1366 return 1367 1368 temp = tempfile.NamedTemporaryFile(suffix=self.suffix) 1369 try: 1370 for bps in (8, 16, 24): 1371 track = self.audio_class.from_pcm( 1372 temp.name, BLANK_PCM_Reader(1, bits_per_sample=bps)) 1373 self.assertEqual(track.bits_per_sample(), 16) 1374 track2 = audiotools.open(temp.name) 1375 self.assertEqual(track2.bits_per_sample(), 16) 1376 finally: 1377 temp.close() 1378 1379 @FORMAT_LOSSY 1380 def test_lossless(self): 1381 if self.audio_class is audiotools.AudioFile: 1382 return 1383 1384 temp = tempfile.NamedTemporaryFile(suffix=self.suffix) 1385 try: 1386 track = self.audio_class.from_pcm(temp.name, BLANK_PCM_Reader(1)) 1387 self.assertEqual(track.lossless(), False) 1388 track = audiotools.open(temp.name) 1389 self.assertEqual(track.lossless(), False) 1390 finally: 1391 temp.close() 1392 1393 @FORMAT_LOSSY 1394 def test_channels(self): 1395 if self.audio_class is audiotools.AudioFile: 1396 return 1397 1398 temp = tempfile.NamedTemporaryFile(suffix=self.suffix) 1399 try: 1400 for channels in [1, 2, 3, 4, 5, 6]: 1401 track = self.audio_class.from_pcm( 1402 temp.name, BLANK_PCM_Reader(1, 1403 channels=channels, 1404 channel_mask=0)) 1405 self.assertEqual(track.channels(), 2) 1406 track = audiotools.open(temp.name) 1407 self.assertEqual(track.channels(), 2) 1408 finally: 1409 temp.close() 1410 1411 @FORMAT_LOSSY 1412 def test_channel_mask(self): 1413 if self.audio_class is audiotools.AudioFile: 1414 return 1415 1416 temp = tempfile.NamedTemporaryFile(suffix=self.suffix) 1417 try: 1418 cm = audiotools.ChannelMask.from_fields( 1419 front_left=True, 1420 front_right=True) 1421 track = self.audio_class.from_pcm( 1422 temp.name, BLANK_PCM_Reader(1, 1423 channels=len(cm), 1424 channel_mask=int(cm))) 1425 self.assertEqual(track.channels(), len(cm)) 1426 self.assertEqual(track.channel_mask(), cm) 1427 track = audiotools.open(temp.name) 1428 self.assertEqual(track.channels(), len(cm)) 1429 self.assertEqual(track.channel_mask(), cm) 1430 finally: 1431 temp.close() 1432 1433 @FORMAT_LOSSY 1434 def test_pcm(self): 1435 if self.audio_class is audiotools.AudioFile: 1436 return 1437 1438 temp = tempfile.NamedTemporaryFile(suffix=self.suffix) 1439 temp2 = tempfile.NamedTemporaryFile() 1440 temp_dir = tempfile.mkdtemp() 1441 try: 1442 for total_pcm_frames in [None, 44100 * 5]: 1443 for compression in (None,) + self.audio_class.COMPRESSION_MODES: 1444 # test silence 1445 reader = BLANK_PCM_Reader(5) 1446 if compression is None: 1447 track = self.audio_class.from_pcm( 1448 temp.name, 1449 reader, 1450 total_pcm_frames=total_pcm_frames) 1451 else: 1452 track = self.audio_class.from_pcm( 1453 temp.name, 1454 reader, 1455 compression, 1456 total_pcm_frames=total_pcm_frames) 1457 counter = FrameCounter(2, 16, 44100) 1458 audiotools.transfer_framelist_data(track.to_pcm(), 1459 counter.update) 1460 self.assertEqual(int(counter), 5, 1461 "mismatch encoding %s at quality %s" % 1462 (self.audio_class.NAME, 1463 compression)) 1464 1465 # test random noise 1466 reader = RANDOM_PCM_Reader(5) 1467 if compression is None: 1468 track = self.audio_class.from_pcm( 1469 temp.name, 1470 reader, 1471 total_pcm_frames=total_pcm_frames) 1472 else: 1473 track = self.audio_class.from_pcm( 1474 temp.name, 1475 reader, 1476 compression, 1477 total_pcm_frames=total_pcm_frames) 1478 counter = FrameCounter(2, 16, 44100) 1479 audiotools.transfer_framelist_data(track.to_pcm(), 1480 counter.update) 1481 self.assertEqual(int(counter), 5, 1482 "mismatch encoding %s at quality %s" % 1483 (self.audio_class.NAME, 1484 compression)) 1485 1486 # test randomly-sized chunks of silence 1487 reader = Variable_Reader(BLANK_PCM_Reader(5)) 1488 if compression is None: 1489 track = self.audio_class.from_pcm( 1490 temp.name, 1491 reader, 1492 total_pcm_frames=total_pcm_frames) 1493 else: 1494 track = self.audio_class.from_pcm( 1495 temp.name, 1496 reader, 1497 compression, 1498 total_pcm_frames=total_pcm_frames) 1499 1500 counter = FrameCounter(2, 16, 44100) 1501 audiotools.transfer_framelist_data(track.to_pcm(), 1502 counter.update) 1503 self.assertEqual(int(counter), 5, 1504 "mismatch encoding %s at quality %s" % 1505 (self.audio_class.NAME, 1506 compression)) 1507 1508 # test randomly-sized chunks of random noise 1509 reader = Variable_Reader(RANDOM_PCM_Reader(5)) 1510 if compression is None: 1511 track = self.audio_class.from_pcm( 1512 temp.name, 1513 reader, 1514 total_pcm_frames=total_pcm_frames) 1515 else: 1516 track = self.audio_class.from_pcm( 1517 temp.name, 1518 reader, 1519 compression, 1520 total_pcm_frames=total_pcm_frames) 1521 1522 counter = FrameCounter(2, 16, 44100) 1523 audiotools.transfer_framelist_data(track.to_pcm(), 1524 counter.update) 1525 self.assertEqual(int(counter), 5, 1526 "mismatch encoding %s at quality %s" % 1527 (self.audio_class.NAME, 1528 compression)) 1529 1530 # test PCMReaders that trigger a DecodingError 1531 self.assertRaises( 1532 ValueError, 1533 ERROR_PCM_Reader(ValueError("error"), 1534 failure_chance=1.0).read, 1535 1) 1536 self.assertRaises( 1537 IOError, 1538 ERROR_PCM_Reader(IOError("error"), 1539 failure_chance=1.0).read, 1540 1) 1541 self.assertRaises(audiotools.EncodingError, 1542 self.audio_class.from_pcm, 1543 os.path.join(temp_dir, 1544 "invalid" + self.suffix), 1545 ERROR_PCM_Reader(IOError("I/O Error"))) 1546 1547 self.assertEqual( 1548 os.path.isfile( 1549 os.path.join(temp_dir, 1550 "invalid" + self.suffix)), 1551 False) 1552 1553 self.assertRaises(audiotools.EncodingError, 1554 self.audio_class.from_pcm, 1555 os.path.join(temp_dir, 1556 "invalid" + self.suffix), 1557 ERROR_PCM_Reader(IOError("I/O Error"))) 1558 1559 self.assertEqual( 1560 os.path.isfile( 1561 os.path.join(temp_dir, 1562 "invalid" + self.suffix)), 1563 False) 1564 1565 # test unwritable output file 1566 self.assertRaises(audiotools.EncodingError, 1567 self.audio_class.from_pcm, 1568 "/dev/null/foo.%s" % (self.suffix), 1569 BLANK_PCM_Reader(1)) 1570 1571 # test without suffix 1572 reader = BLANK_PCM_Reader(5) 1573 if compression is None: 1574 track = self.audio_class.from_pcm( 1575 temp2.name, 1576 reader, 1577 total_pcm_frames=total_pcm_frames) 1578 else: 1579 track = self.audio_class.from_pcm( 1580 temp2.name, 1581 reader, 1582 compression, 1583 total_pcm_frames=total_pcm_frames) 1584 1585 counter = FrameCounter(2, 16, 44100) 1586 audiotools.transfer_framelist_data(track.to_pcm(), 1587 counter.update) 1588 self.assertEqual(int(counter), 5, 1589 "mismatch encoding %s at quality %s" % 1590 (self.audio_class.NAME, 1591 compression)) 1592 finally: 1593 temp.close() 1594 temp2.close() 1595 for f in os.listdir(temp_dir): 1596 os.unlink(os.path.join(temp_dir, f)) 1597 os.rmdir(temp_dir) 1598 1599 @FORMAT_LOSSY 1600 def test_convert(self): 1601 if self.audio_class is audiotools.AudioFile: 1602 return 1603 1604 # check various round-trip options 1605 temp = tempfile.NamedTemporaryFile(suffix=self.suffix) 1606 1607 track = self.audio_class.from_pcm( 1608 temp.name, 1609 test_streams.Sine16_Stereo(220500, 44100, 1610 8820.0, 0.70, 4410.0, 0.29, 1.0)) 1611 for audio_class in audiotools.AVAILABLE_TYPES: 1612 temp2 = tempfile.NamedTemporaryFile( 1613 suffix="." + audio_class.SUFFIX) 1614 track2 = track.convert(temp2.name, audio_class) 1615 1616 counter = FrameCounter(2, 16, 44100) 1617 audiotools.transfer_framelist_data(track2.to_pcm(), 1618 counter.update) 1619 self.assertEqual( 1620 int(counter), 5, 1621 "mismatch encoding %s" % 1622 (self.audio_class.NAME)) 1623 1624 self.assertRaises(audiotools.EncodingError, 1625 track.convert, 1626 "/dev/null/foo.%s" % 1627 (audio_class.SUFFIX), 1628 audio_class) 1629 1630 for compression in audio_class.COMPRESSION_MODES: 1631 track2 = track.convert(temp2.name, 1632 audio_class, 1633 compression) 1634 1635 counter = FrameCounter(2, 16, 44100) 1636 audiotools.transfer_framelist_data(track2.to_pcm(), 1637 counter.update) 1638 self.assertEqual( 1639 int(counter), 5, 1640 "mismatch encoding %s at quality %s" % 1641 (self.audio_class.NAME, 1642 compression)) 1643 1644 # check some obvious failures 1645 self.assertRaises(audiotools.EncodingError, 1646 track.convert, 1647 "/dev/null/foo.%s" % 1648 (audio_class.SUFFIX), 1649 audio_class, 1650 compression) 1651 1652 temp2.close() 1653 1654 temp.close() 1655 1656 1657class TestForeignWaveChunks: 1658 @FORMAT_LOSSLESS 1659 def test_from_wave_close(self): 1660 temp_name = "closed." + self.audio_class.SUFFIX 1661 if os.path.isfile(temp_name): 1662 os.unlink(temp_name) 1663 try: 1664 pcmreader = CLOSE_PCM_Reader( 1665 EXACT_SILENCE_PCM_Reader(304844)) 1666 1667 self.assertEqual(pcmreader.closes_called, 0) 1668 1669 # build our audio file using the .from_wave() interface 1670 track = self.audio_class.from_wave( 1671 filename=temp_name, 1672 header=(b'RIFFT\x9b\x12\x00WAVEfmt ' + 1673 b'\x10\x00\x00\x00\x01\x00\x02\x00D' + 1674 b'\xac\x00\x00\x10\xb1\x02\x00\x04\x00\x10\x00' + 1675 b'data0\x9b\x12\x00'), 1676 pcmreader=pcmreader, 1677 footer=b"") 1678 1679 # ensure pcmreader gets closed properly 1680 self.assertEqual(pcmreader.closes_called, 1) 1681 finally: 1682 os.unlink(temp_name) 1683 1684 @FORMAT_LOSSLESS 1685 def test_convert_wave_chunks(self): 1686 import filecmp 1687 from zlib import decompress 1688 1689 self.assertTrue(issubclass(self.audio_class, 1690 audiotools.WaveContainer)) 1691 1692 # several even-sized chunks 1693 chunks1 = (decompress(b"x\x9c\x0b\xf2ts\xdbQ\xc9\xcb\x10\xee\x18" + 1694 b"\xe6\x9a\x96[\xa2 \xc0\xc0\xc0\xc0\xc8\xc0" + 1695 b"\xc4\xe0\xb2\x86\x81A`#\x13\x03\x0b\x83" + 1696 b"\x00CZ~~\x15\x07P\xbc$\xb5\xb8\xa4$\xb5" + 1697 b"\xa2$)\xb1\xa8\n\xa4\xae8?757\xbf(\x15!^U" + 1698 b"\x05\xd40\nF\xc1(\x18\xc1 %\xb1$1\xa0\x94" + 1699 b"\x97\x01\x00`\xb0\x18\xf7"), 1700 (220500, 44100, 2, 16, 0x3), 1701 b"spam\x0c\x00\x00\x00anotherchunk") 1702 1703 # several odd-sized chunks 1704 chunks2 = (decompress(b"x\x9c\x0b\xf2ts\xcbc``\x08w\x0csM\xcb\xcf\xaf" + 1705 b"\xe2b@\x06i\xb9%\n\x02@\x9a\x11\x08]\xd60" + 1706 b"\x801#\x03\x07CRbQ\x157H\x1c\x01\x18R\x12K\x12" + 1707 b"\xf9\x81b\x00\x19\xdd\x0ba"), 1708 (15, 44100, 1, 8, 0x4), 1709 b"\x00barz\x0b\x00\x00\x00\x01\x01\x01\x01" + 1710 b"\x01\x01\x01\x01\x01\x01\x01\x00") 1711 1712 for (header, 1713 (total_frames, 1714 sample_rate, 1715 channels, 1716 bits_per_sample, 1717 channel_mask), footer) in [chunks1, chunks2]: 1718 temp1 = tempfile.NamedTemporaryFile( 1719 suffix="." + self.audio_class.SUFFIX) 1720 try: 1721 # build our audio file from the from_pcm() interface 1722 track = self.audio_class.from_pcm( 1723 temp1.name, 1724 EXACT_RANDOM_PCM_Reader( 1725 pcm_frames=total_frames, 1726 sample_rate=sample_rate, 1727 channels=channels, 1728 bits_per_sample=bits_per_sample, 1729 channel_mask=channel_mask)) 1730 1731 # check has_foreign_wave_chunks 1732 self.assertEqual(track.has_foreign_wave_chunks(), False) 1733 finally: 1734 temp1.close() 1735 1736 for (header, 1737 (total_frames, 1738 sample_rate, 1739 channels, 1740 bits_per_sample, 1741 channel_mask), footer) in [chunks1, chunks2]: 1742 temp1 = tempfile.NamedTemporaryFile( 1743 suffix="." + self.audio_class.SUFFIX) 1744 try: 1745 # build our audio file using the from_wave() interface 1746 track = self.audio_class.from_wave( 1747 temp1.name, 1748 header, 1749 EXACT_RANDOM_PCM_Reader( 1750 pcm_frames=total_frames, 1751 sample_rate=sample_rate, 1752 channels=channels, 1753 bits_per_sample=bits_per_sample, 1754 channel_mask=channel_mask), 1755 footer) 1756 1757 # check has_foreign_wave_chunks 1758 self.assertEqual(track.has_foreign_wave_chunks(), True) 1759 1760 # ensure wave_header_footer returns same header and footer 1761 (track_header, 1762 track_footer) = track.wave_header_footer() 1763 self.assertEqual(header, track_header) 1764 self.assertEqual(footer, track_footer) 1765 1766 # convert our file to every other WaveContainer format 1767 # (including our own) 1768 for new_class in audiotools.AVAILABLE_TYPES: 1769 if issubclass(new_class, audiotools.WaveContainer): 1770 temp2 = tempfile.NamedTemporaryFile( 1771 suffix="." + new_class.SUFFIX) 1772 log = Log() 1773 try: 1774 track2 = track.convert(temp2.name, 1775 new_class, 1776 progress=log.update) 1777 1778 # ensure the progress function 1779 # gets called during conversion 1780 self.assertGreater( 1781 len(log.results), 0, 1782 "no logging converting %s to %s" % 1783 (self.audio_class.NAME, 1784 new_class.NAME)) 1785 1786 self.assertEqual( 1787 len({r[1] for r in log.results}), 1) 1788 for x, y in zip(log.results[1:], log.results): 1789 self.assertGreaterEqual((x[0] - y[0]), 0) 1790 1791 # ensure newly converted file 1792 # matches has_foreign_wave_chunks 1793 self.assertTrue(track2.has_foreign_wave_chunks()) 1794 1795 # ensure newly converted file 1796 # has same header and footer 1797 (track2_header, 1798 track2_footer) = track2.wave_header_footer() 1799 self.assertEqual(header, track2_header) 1800 self.assertEqual(footer, track2_footer) 1801 1802 # ensure newly converted file has same PCM data 1803 self.assertTrue( 1804 audiotools.pcm_cmp( 1805 track.to_pcm(), track2.to_pcm())) 1806 finally: 1807 temp2.close() 1808 finally: 1809 temp1.close() 1810 1811 if os.path.isfile("bad.wav"): 1812 os.unlink("bad.wav") 1813 1814 for (header, footer) in [ 1815 # wave header without "RIFF<size>WAVE raises an error 1816 (b"", b""), 1817 (b"FOOZ\x00\x00\x00\x00BARZ", b""), 1818 1819 # invalid total size raises an error 1820 (b"RIFFZ\x00\x00\x00WAVEfmt \x10\x00\x00\x00\x01" + 1821 b"\x00\x01\x00D\xac\x00\x00\x88X\x01\x00\x02\x00" + 1822 b"\x10\x00data2\x00\x00\x00", b""), 1823 1824 # invalid data size raises an error 1825 (b"RIFFV\x00\x00\x00WAVEfmt \x10\x00\x00\x00\x01" + 1826 b"\x00\x01\x00D\xac\x00\x00\x88X\x01\x00\x02\x00" + 1827 b"\x10\x00data6\x00\x00\x00", b""), 1828 1829 # invalid chunk IDs in header raise an error 1830 (b"RIFFb\x00\x00\x00WAVEfmt \x10\x00\x00\x00\x01" + 1831 b"\x00\x01\x00D\xac\x00\x00\x88X\x01\x00\x02\x00\x10\x00" + 1832 b"chn\x00\x04\x00\x00\x00\x01\x02\x03\x04" + 1833 b"data2\x00\x00\x00", b""), 1834 1835 # mulitple fmt chunks raise an error 1836 (b"RIFFn\x00\x00\x00WAVEfmt \x10\x00\x00\x00\x01" + 1837 b"\x00\x01\x00D\xac\x00\x00\x88X\x01\x00\x02\x00" + 1838 b"\x10\x00" + 1839 b"fmt \x10\x00\x00\x00\x01" + 1840 b"\x00\x01\x00D\xac\x00\x00\x88X\x01\x00\x02\x00" + 1841 b"\x10\x00" + 1842 b"data2\x00\x00\x00", b""), 1843 1844 # data chunk before fmt chunk raises an error 1845 (b"RIFFJ\x00\x00\x00WAVE" + 1846 b"chnk\x04\x00\x00\x00\x01\x02\x03\x04" + 1847 b"data2\x00\x00\x00", b""), 1848 1849 # bytes after data chunk raises an error 1850 (b"RIFFb\x00\x00\x00WAVEfmt \x10\x00\x00\x00\x01" + 1851 b"\x00\x01\x00D\xac\x00\x00\x88X\x01\x00\x02\x00\x10\x00" + 1852 b"chnk\x04\x00\x00\x00\x01\x02\x03\x04" + 1853 b"data3\x00\x00\x00\x01", b""), 1854 1855 # truncated chunks in header raise an error 1856 (b"RIFFb\x00\x00\x00WAVEfmt \x10\x00\x00\x00\x01" + 1857 b"\x00\x01\x00D\xac\x00\x00\x88X\x01\x00\x02\x00\x10\x00" + 1858 b"chnk\x04\x00\x00\x00\x01\x02\x03", b""), 1859 1860 # fmt chunk in footer raises an error 1861 (b"RIFFz\x00\x00\x00WAVEfmt \x10\x00\x00\x00\x01" + 1862 b"\x00\x01\x00D\xac\x00\x00\x88X\x01\x00\x02\x00\x10\x00" + 1863 b"chnk\x04\x00\x00\x00\x01\x02\x03\x04" + 1864 b"data2\x00\x00\x00", 1865 b"fmt \x10\x00\x00\x00\x01" + 1866 b"\x00\x01\x00D\xac\x00\x00\x88X\x01\x00\x02\x00\x10\x00"), 1867 1868 # data chunk in footer raises an error 1869 (b"RIFFn\x00\x00\x00WAVEfmt \x10\x00\x00\x00\x01" + 1870 b"\x00\x01\x00D\xac\x00\x00\x88X\x01\x00\x02\x00\x10\x00" + 1871 b"chnk\x04\x00\x00\x00\x01\x02\x03\x04" + 1872 b"data2\x00\x00\x00", 1873 b"data\x04\x00\x00\x00\x01\x02\x03\x04"), 1874 1875 # invalid chunk IDs in footer raise an error 1876 (b"RIFFn\x00\x00\x00WAVEfmt \x10\x00\x00\x00\x01" + 1877 b"\x00\x01\x00D\xac\x00\x00\x88X\x01\x00\x02\x00\x10\x00" + 1878 b"chnk\x04\x00\x00\x00\x01\x02\x03\x04" + 1879 b"data2\x00\x00\x00", 1880 b"chn\x00\x04\x00\x00\x00\x01\x02\x03\x04"), 1881 1882 # truncated chunks in footer raise an error 1883 (b"RIFFn\x00\x00\x00WAVEfmt \x10\x00\x00\x00\x01" + 1884 b"\x00\x01\x00D\xac\x00\x00\x88X\x01\x00\x02\x00\x10\x00" + 1885 b"chnk\x04\x00\x00\x00\x01\x02\x03\x04" + 1886 b"data2\x00\x00\x00", 1887 b"chnk\x04\x00\x00\x00\x01\x02\x03")]: 1888 self.assertRaises(audiotools.EncodingError, 1889 self.audio_class.from_wave, 1890 "bad.wav", 1891 header, 1892 EXACT_BLANK_PCM_Reader(25, 1893 44100, 1894 1, 1895 16, 1896 0x4), 1897 footer) 1898 self.assertEqual(os.path.isfile("bad.wav"), False) 1899 1900 1901class TestForeignAiffChunks: 1902 @FORMAT_LOSSLESS 1903 def test_from_wave_close(self): 1904 temp_name = "closed." + self.audio_class.SUFFIX 1905 if os.path.isfile(temp_name): 1906 os.unlink(temp_name) 1907 try: 1908 pcmreader = CLOSE_PCM_Reader( 1909 EXACT_SILENCE_PCM_Reader(304844)) 1910 1911 self.assertEqual(pcmreader.closes_called, 0) 1912 1913 # build our audio file using the .from_wave() interface 1914 track = self.audio_class.from_aiff( 1915 filename=temp_name, 1916 header=(b'FORM\x00\x12\x9b^AIFFCOMM' + 1917 b'\x00\x00\x00\x12\x00\x02\x00\x04\xa6\xcc\x00' + 1918 b'\x10@\x0e\xacD\x00\x00\x00\x00\x00\x00' + 1919 b'SSND\x00\x12\x9b8\x00\x00\x00\x00\x00\x00\x00\x00'), 1920 pcmreader=pcmreader, 1921 footer=b"") 1922 1923 # ensure pcmreader gets closed properly 1924 self.assertEqual(pcmreader.closes_called, 1) 1925 finally: 1926 os.unlink(temp_name) 1927 1928 @FORMAT_LOSSLESS 1929 def test_convert_aiff_chunks(self): 1930 import filecmp 1931 from zlib import decompress 1932 1933 self.assertTrue(issubclass(self.audio_class, 1934 audiotools.AiffContainer)) 1935 1936 # several even-sized chunks 1937 chunks1 = (decompress(b"x\x9cs\xf3\x0f\xf2e\xe0\xad<\xe4\xe8\xe9" + 1938 b"\xe6\xe6\xec\xef\xeb\xcb\xc0\xc0 \xc4\xc0" + 1939 b"\xc4\xc0\x1c\x1b\xc2 \xe0\xc0\xb7\xc6\x85" + 1940 b"\x01\x0c\xdc\xfc\xfd\xa3\x80\x14GIjqIIjE" + 1941 b"\x89\x93c\x10\x88/P\x9c\x9f\x9b\x9a\x9b_" + 1942 b"\x94\x8a\x10\x8f\x02\x8a\xb30\x8c\x82Q0" + 1943 b"\nF.\x08\x0e\xf6sa\xe0-\x8d\x80\xf1\x01" + 1944 b"\xcf\x8c\x17\x18"), 1945 (220500, 44100, 2, 16, 0x3), 1946 b"SPAM\x00\x00\x00\x0canotherchunk") 1947 1948 # several odd-sized chunks 1949 chunks2 = (decompress(b"x\x9cs\xf3\x0f\xf2e``\xa8p\xf4tss\xf3" + 1950 b"\xf7\x8f\x02\xb2\xb9\x18\xe0\xc0\xd9" + 1951 b"\xdf\x17$+\xc4\xc0\x08$\xf9\x198\x1c" + 1952 b"\xf8\xd6\xb8@d\x9c\x1c\x83@j\xb9\x19" + 1953 b"\x11\x80!8\xd8\x0f$+\x0e\xd3\r\x00\x16" + 1954 b"\xa5\t3"), 1955 (15, 44100, 1, 8, 0x4), 1956 b"\x00BAZZ\x00\x00\x00\x0b\x02\x02\x02\x02" + 1957 b"\x02\x02\x02\x02\x02\x02\x02\x00") 1958 1959 for (header, 1960 (total_frames, 1961 sample_rate, 1962 channels, 1963 bits_per_sample, 1964 channel_mask), footer) in [chunks1, chunks2]: 1965 temp1 = tempfile.NamedTemporaryFile( 1966 suffix="." + self.audio_class.SUFFIX) 1967 try: 1968 # build our audio file from the from_pcm() interface 1969 track = self.audio_class.from_pcm( 1970 temp1.name, 1971 EXACT_RANDOM_PCM_Reader( 1972 pcm_frames=total_frames, 1973 sample_rate=sample_rate, 1974 channels=channels, 1975 bits_per_sample=bits_per_sample, 1976 channel_mask=channel_mask)) 1977 1978 # check has_foreign_aiff_chunks() 1979 self.assertEqual(track.has_foreign_aiff_chunks(), False) 1980 finally: 1981 temp1.close() 1982 1983 for (header, 1984 (total_frames, 1985 sample_rate, 1986 channels, 1987 bits_per_sample, 1988 channel_mask), footer) in [chunks1, chunks2]: 1989 temp1 = tempfile.NamedTemporaryFile( 1990 suffix="." + self.audio_class.SUFFIX) 1991 try: 1992 # build our audio file using from_aiff() interface 1993 track = self.audio_class.from_aiff( 1994 temp1.name, 1995 header, 1996 EXACT_RANDOM_PCM_Reader( 1997 pcm_frames=total_frames, 1998 sample_rate=sample_rate, 1999 channels=channels, 2000 bits_per_sample=bits_per_sample, 2001 channel_mask=channel_mask), 2002 footer) 2003 2004 # check has_foreign_aiff_chunks() 2005 self.assertEqual(track.has_foreign_aiff_chunks(), True) 2006 2007 # ensure aiff_header_footer returns same header and footer 2008 (track_header, 2009 track_footer) = track.aiff_header_footer() 2010 self.assertEqual(header, track_header) 2011 self.assertEqual(footer, track_footer) 2012 2013 # convert our file to every other AiffContainer format 2014 # (including our own) 2015 for new_class in audiotools.AVAILABLE_TYPES: 2016 if issubclass(new_class, audiotools.AiffContainer): 2017 temp2 = tempfile.NamedTemporaryFile( 2018 suffix="." + new_class.SUFFIX) 2019 log = Log() 2020 try: 2021 track2 = track.convert(temp2.name, 2022 new_class, 2023 progress=log.update) 2024 2025 # ensure the progress function 2026 # gets called during conversion 2027 self.assertGreater( 2028 len(log.results), 0, 2029 "no logging converting %s to %s" % 2030 (self.audio_class.NAME, 2031 new_class.NAME)) 2032 2033 self.assertEqual( 2034 len({r[1] for r in log.results}), 1) 2035 for x, y in zip(log.results[1:], log.results): 2036 self.assertGreaterEqual((x[0] - y[0]), 0) 2037 2038 # ensure newly converted file 2039 # matches has_foreign_wave_chunks 2040 self.assertTrue(track2.has_foreign_aiff_chunks()) 2041 2042 # ensure newly converted file 2043 # has same header and footer 2044 (track2_header, 2045 track2_footer) = track2.aiff_header_footer() 2046 self.assertEqual(header, track2_header) 2047 self.assertEqual(footer, track2_footer) 2048 2049 # ensure newly converted file has same PCM data 2050 self.assertTrue( 2051 audiotools.pcm_cmp(track.to_pcm(), 2052 track2.to_pcm())) 2053 finally: 2054 temp2.close() 2055 finally: 2056 temp1.close() 2057 2058 if os.path.isfile("bad.aiff"): 2059 os.unlink("bad.aiff") 2060 2061 for (header, footer) in [ 2062 # aiff header without "FORM<size>AIFF raises an error 2063 (b"", b""), 2064 (b"FOOZ\x00\x00\x00\x00BARZ", b""), 2065 2066 # invalid total size raises an error 2067 (b"FORM\x00\x00\x00tAIFF" + 2068 b"COMM\x00\x00\x00\x12\x00\x01\x00\x00\x00\x19\x00" + 2069 b"\x10@\x0e\xacD\x00\x00\x00\x00\x00\x00" + 2070 b"SSND\x00\x00\x00:\x00\x00\x00\x00\x00\x00\x00\x00", 2071 b"ID3 \x00\x00\x00\nID3\x02\x00\x00\x00\x00\x00\x00"), 2072 2073 # invalid SSND size raises an error 2074 (b"FORM\x00\x00\x00rAIFF" + 2075 b"COMM\x00\x00\x00\x12\x00\x01\x00\x00\x00\x19\x00" + 2076 b"\x10@\x0e\xacD\x00\x00\x00\x00\x00\x00" + 2077 b"SSND\x00\x00\x00<\x00\x00\x00\x00\x00\x00\x00\x00", 2078 b"ID3 \x00\x00\x00\nID3\x02\x00\x00\x00\x00\x00\x00"), 2079 2080 # invalid chunk IDs in header raise an error 2081 (b"FORM\x00\x00\x00~AIFF" + 2082 b"COMM\x00\x00\x00\x12\x00\x01\x00\x00\x00\x19\x00" + 2083 b"\x10@\x0e\xacD\x00\x00\x00\x00\x00\x00" + 2084 b"CHN\x00\x00\x00\x00\x04\x01\x02\x03\x04" + 2085 b"SSND\x00\x00\x00:\x00\x00\x00\x00\x00\x00\x00\x00", 2086 b"ID3 \x00\x00\x00\nID3\x02\x00\x00\x00\x00\x00\x00"), 2087 2088 # mulitple COMM chunks raise an error 2089 (b"FORM\x00\x00\x00\x8cAIFF" + 2090 b"COMM\x00\x00\x00\x12\x00\x01\x00\x00\x00\x19\x00" + 2091 b"\x10@\x0e\xacD\x00\x00\x00\x00\x00\x00" + 2092 b"COMM\x00\x00\x00\x12\x00\x01\x00\x00\x00\x19\x00" + 2093 b"\x10@\x0e\xacD\x00\x00\x00\x00\x00\x00" + 2094 b"SSND\x00\x00\x00:\x00\x00\x00\x00\x00\x00\x00\x00", 2095 b"ID3 \x00\x00\x00\nID3\x02\x00\x00\x00\x00\x00\x00"), 2096 2097 # SSND chunk before COMM chunk raises an error 2098 (b"FORM\x00\x00\x00XAIFF" + 2099 b"SSND\x00\x00\x00:\x00\x00\x00\x00\x00\x00\x00\x00", 2100 b"ID3 \x00\x00\x00\nID3\x02\x00\x00\x00\x00\x00\x00"), 2101 2102 # bytes missing from SSNK chunk raises an error 2103 (b"FORM\x00\x00\x00rAIFF" + 2104 b"COMM\x00\x00\x00\x12\x00\x01\x00\x00\x00\x19\x00" + 2105 b"\x10@\x0e\xacD\x00\x00\x00\x00\x00\x00" + 2106 b"SSND\x00\x00\x00<\x00\x00\x00\x00\x00\x00", 2107 b"ID3 \x00\x00\x00\nID3\x02\x00\x00\x00\x00\x00\x00"), 2108 2109 # bytes after SSND chunk raises an error 2110 (b"FORM\x00\x00\x00rAIFF" + 2111 b"COMM\x00\x00\x00\x12\x00\x01\x00\x00\x00\x19\x00" + 2112 b"\x10@\x0e\xacD\x00\x00\x00\x00\x00\x00" + 2113 b"SSND\x00\x00\x00<\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 2114 b"ID3 \x00\x00\x00\nID3\x02\x00\x00\x00\x00\x00\x00"), 2115 2116 # truncated chunks in header raise an error 2117 (b"FORM\x00\x00\x00rAIFF" + 2118 b"COMM\x00\x00\x00\x12\x00\x01\x00\x00\x00\x19\x00", 2119 b"ID3 \x00\x00\x00\nID3\x02\x00\x00\x00\x00\x00\x00"), 2120 2121 # COMM chunk in footer raises an error 2122 (b"FORM\x00\x00\x00\x8cAIFF" + 2123 b"COMM\x00\x00\x00\x12\x00\x01\x00\x00\x00\x19\x00" + 2124 b"\x10@\x0e\xacD\x00\x00\x00\x00\x00\x00" + 2125 b"SSND\x00\x00\x00:\x00\x00\x00\x00\x00\x00\x00\x00", 2126 b"COMM\x00\x00\x00\x12\x00\x01\x00\x00\x00\x19\x00" + 2127 b"\x10@\x0e\xacD\x00\x00\x00\x00\x00\x00" + 2128 b"ID3 \x00\x00\x00\nID3\x02\x00\x00\x00\x00\x00\x00"), 2129 2130 # SSND chunk in footer raises an error 2131 (b"FORM\x00\x00\x00rAIFF" + 2132 b"COMM\x00\x00\x00\x12\x00\x01\x00\x00\x00\x19\x00" + 2133 b"\x10@\x0e\xacD\x00\x00\x00\x00\x00\x00" + 2134 b"SSND\x00\x00\x00:\x00\x00\x00\x00\x00\x00\x00\x00", 2135 b"ID3 \x00\x00\x00\nID3\x02\x00\x00\x00\x00\x00\x00" + 2136 b"SSND\x00\x00\x00:\x00\x00\x00\x00\x00\x00\x00\x00"), 2137 2138 # invalid chunk IDs in footer raise an error 2139 (b"FORM\x00\x00\x00rAIFF" + 2140 b"COMM\x00\x00\x00\x12\x00\x01\x00\x00\x00\x19\x00" + 2141 b"\x10@\x0e\xacD\x00\x00\x00\x00\x00\x00" + 2142 b"SSND\x00\x00\x00:\x00\x00\x00\x00\x00\x00\x00\x00", 2143 b"ID3\00\x00\x00\x00\nID3\x02\x00\x00\x00\x00\x00\x00"), 2144 2145 # truncated chunks in footer raise an error 2146 (b"FORM\x00\x00\x00rAIFF" + 2147 b"COMM\x00\x00\x00\x12\x00\x01\x00\x00\x00\x19\x00" + 2148 b"\x10@\x0e\xacD\x00\x00\x00\x00\x00\x00" + 2149 b"SSND\x00\x00\x00:\x00\x00\x00\x00\x00\x00\x00\x00", 2150 b"ID3 \x00\x00\x00\nID3\x02\x00\x00\x00\x00\x00")]: 2151 self.assertRaises(audiotools.EncodingError, 2152 self.audio_class.from_aiff, 2153 "bad.aiff", 2154 header, 2155 EXACT_BLANK_PCM_Reader(25, 2156 44100, 2157 1, 2158 16, 2159 0x4), 2160 footer) 2161 self.assertEqual(os.path.isfile("bad.aiff"), False) 2162 2163 2164class AiffFileTest(TestForeignAiffChunks, LosslessFileTest): 2165 def setUp(self): 2166 self.audio_class = audiotools.AiffAudio 2167 self.suffix = "." + self.audio_class.SUFFIX 2168 2169 @FORMAT_AIFF 2170 def test_ieee_extended(self): 2171 from audiotools.bitstream import BitstreamReader, BitstreamRecorder 2172 import audiotools.aiff 2173 2174 for i in range(0, 192000 + 1): 2175 w = BitstreamRecorder(0) 2176 audiotools.aiff.build_ieee_extended(w, float(i)) 2177 s = BytesIO(w.data()) 2178 self.assertEqual(w.data(), s.getvalue()) 2179 self.assertEqual(i, audiotools.aiff.parse_ieee_extended( 2180 BitstreamReader(s, False))) 2181 2182 @FORMAT_AIFF 2183 def test_overlong_file(self): 2184 # trying to generate too large of a file 2185 # should throw an exception right away if total_pcm_frames known 2186 # instead of building it first 2187 2188 self.assertEqual(os.path.isfile("invalid.aiff"), False) 2189 2190 self.assertRaises(audiotools.EncodingError, 2191 self.audio_class.from_pcm, 2192 "invalid.aiff", 2193 EXACT_SILENCE_PCM_Reader( 2194 pcm_frames=715827883, 2195 sample_rate=44100, 2196 channels=2, 2197 bits_per_sample=24), 2198 total_pcm_frames=715827883) 2199 2200 self.assertEqual(os.path.isfile("invalid.aiff"), False) 2201 2202 @FORMAT_AIFF 2203 def test_verify(self): 2204 import audiotools.aiff 2205 from test_core import ints_to_bytes, bytes_to_ints 2206 2207 # test truncated file 2208 for aiff_file in ["aiff-8bit.aiff", 2209 "aiff-1ch.aiff", 2210 "aiff-2ch.aiff", 2211 "aiff-6ch.aiff"]: 2212 f = open(aiff_file, 'rb') 2213 aiff_data = f.read() 2214 f.close() 2215 2216 temp = tempfile.NamedTemporaryFile(suffix=".aiff") 2217 2218 try: 2219 # first, check that a truncated comm chunk raises an exception 2220 # at init-time 2221 for i in range(0, 0x25): 2222 temp.seek(0, 0) 2223 temp.write(aiff_data[0:i]) 2224 temp.flush() 2225 self.assertEqual(os.path.getsize(temp.name), i) 2226 2227 self.assertRaises(audiotools.InvalidFile, 2228 audiotools.AiffAudio, 2229 temp.name) 2230 2231 # then, check that a truncated ssnd chunk raises an exception 2232 # at read-time 2233 for i in range(0x37, len(aiff_data)): 2234 temp.seek(0, 0) 2235 temp.write(aiff_data[0:i]) 2236 temp.flush() 2237 reader = audiotools.AiffAudio(temp.name).to_pcm() 2238 self.assertNotEqual(reader, None) 2239 self.assertRaises(IOError, 2240 audiotools.transfer_framelist_data, 2241 reader, lambda x: x) 2242 finally: 2243 temp.close() 2244 2245 # test non-ASCII chunk ID 2246 temp = tempfile.NamedTemporaryFile(suffix=".aiff") 2247 try: 2248 f = open("aiff-metadata.aiff", "rb") 2249 aiff_data = bytes_to_ints(f.read()) 2250 f.close() 2251 aiff_data[0x89] = 0 2252 temp.seek(0, 0) 2253 temp.write(ints_to_bytes(aiff_data)) 2254 temp.flush() 2255 aiff = audiotools.open(temp.name) 2256 self.assertRaises(audiotools.InvalidFile, 2257 aiff.verify) 2258 finally: 2259 temp.close() 2260 2261 # test no SSND chunk 2262 aiff = audiotools.open("aiff-nossnd.aiff") 2263 self.assertRaises(audiotools.InvalidFile, aiff.verify) 2264 2265 # test convert errors 2266 with tempfile.NamedTemporaryFile(suffix=".aiff") as temp: 2267 with open("aiff-2ch.aiff", "rb") as f: 2268 temp.write(f.read()[0:-10]) 2269 temp.flush() 2270 flac = audiotools.open(temp.name) 2271 if os.path.isfile("dummy.wav"): 2272 os.unlink("dummy.wav") 2273 self.assertFalse(os.path.isfile("dummy.wav")) 2274 self.assertRaises(audiotools.EncodingError, 2275 flac.convert, 2276 "dummy.wav", 2277 audiotools.WaveAudio) 2278 self.assertFalse(os.path.isfile("dummy.wav")) 2279 2280 COMM = audiotools.aiff.AIFF_Chunk( 2281 b"COMM", 2282 18, 2283 b'\x00\x01\x00\x00\x00\r\x00\x10@\x0e\xacD\x00\x00\x00\x00\x00\x00') 2284 SSND = audiotools.aiff.AIFF_Chunk( 2285 b"SSND", 2286 34, 2287 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x02\x00\x03\x00\x02\x00\x01\x00\x00\xff\xff\xff\xfe\xff\xfd\xff\xfe\xff\xff\x00\x00') 2288 2289 # test multiple COMM chunks found 2290 # test multiple SSND chunks found 2291 # test SSND chunk before COMM chunk 2292 # test no SSND chunk 2293 # test no COMM chunk 2294 for chunks in [[COMM, COMM, SSND], 2295 [COMM, SSND, SSND], 2296 [SSND, COMM], 2297 [SSND], 2298 [COMM]]: 2299 temp = tempfile.NamedTemporaryFile(suffix=".aiff") 2300 try: 2301 audiotools.AiffAudio.aiff_from_chunks(temp, chunks) 2302 self.assertRaises( 2303 audiotools.InvalidFile, 2304 audiotools.open(temp.name).verify) 2305 finally: 2306 temp.close() 2307 2308 @FORMAT_AIFF 2309 def test_clean(self): 2310 import audiotools.aiff 2311 2312 COMM = audiotools.aiff.AIFF_Chunk( 2313 b"COMM", 2314 18, 2315 b'\x00\x01\x00\x00\x00\r\x00\x10@\x0e\xacD\x00\x00\x00\x00\x00\x00') 2316 SSND = audiotools.aiff.AIFF_Chunk( 2317 b"SSND", 2318 34, 2319 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x02\x00\x03\x00\x02\x00\x01\x00\x00\xff\xff\xff\xfe\xff\xfd\xff\xfe\xff\xff\x00\x00') 2320 2321 # test multiple COMM chunks 2322 # test multiple SSND chunks 2323 # test data chunk before fmt chunk 2324 fixed = tempfile.NamedTemporaryFile(suffix=".aiff") 2325 try: 2326 for chunks in [[COMM, COMM, SSND], 2327 [COMM, SSND, COMM], 2328 [COMM, SSND, SSND], 2329 [SSND, COMM], 2330 [SSND, COMM, COMM]]: 2331 temp = tempfile.NamedTemporaryFile(suffix=".aiff") 2332 audiotools.AiffAudio.aiff_from_chunks(temp, chunks) 2333 temp.flush() 2334 fixes = audiotools.open(temp.name).clean(fixed.name) 2335 temp.close() 2336 aiff = audiotools.open(fixed.name) 2337 chunks = list(aiff.chunks()) 2338 self.assertEqual([c.id for c in chunks], 2339 [c.id for c in [COMM, SSND]]) 2340 self.assertEqual([c.__size__ for c in chunks], 2341 [c.__size__ for c in [COMM, SSND]]) 2342 self.assertEqual([c.__data__ for c in chunks], 2343 [c.__data__ for c in [COMM, SSND]]) 2344 finally: 2345 fixed.close() 2346 2347 2348class ALACFileTest(LosslessFileTest): 2349 def setUp(self): 2350 self.audio_class = audiotools.ALACAudio 2351 self.suffix = "." + self.audio_class.SUFFIX 2352 2353 from audiotools.decoders import ALACDecoder 2354 from audiotools.encoders import encode_alac 2355 self.decoder = ALACDecoder 2356 self.encode = encode_alac 2357 2358 @FORMAT_ALAC 2359 def test_init(self): 2360 # check missing file 2361 self.assertRaises(audiotools.m4a.InvalidALAC, 2362 audiotools.ALACAudio, 2363 "/dev/null/foo") 2364 2365 # check invalid file 2366 with tempfile.NamedTemporaryFile(suffix=".m4a") as invalid_file: 2367 for c in [b"i", b"n", b"v", b"a", b"l", b"i", b"d", 2368 b"s", b"t", b"r", b"i", b"n", b"g", b"x", b"x", b"x"]: 2369 invalid_file.write(c) 2370 invalid_file.flush() 2371 self.assertRaises(audiotools.m4a.InvalidALAC, 2372 audiotools.ALACAudio, 2373 invalid_file.name) 2374 2375 # check some decoder errors, 2376 # mostly to ensure a failed init doesn't make Python explode 2377 self.assertRaises(TypeError, self.decoder) 2378 2379 self.assertRaises(TypeError, self.decoder, None) 2380 2381 @FORMAT_ALAC 2382 def test_bits_per_sample(self): 2383 with tempfile.NamedTemporaryFile(suffix=self.suffix) as temp: 2384 for bps in (16, 24): 2385 track = self.audio_class.from_pcm( 2386 temp.name, BLANK_PCM_Reader(1, bits_per_sample=bps)) 2387 self.assertEqual(track.bits_per_sample(), bps) 2388 track2 = audiotools.open(temp.name) 2389 self.assertEqual(track2.bits_per_sample(), bps) 2390 2391 @FORMAT_ALAC 2392 def test_channel_mask(self): 2393 with tempfile.NamedTemporaryFile(suffix=self.suffix) as temp: 2394 for mask in [["front_center"], 2395 ["front_left", 2396 "front_right"]]: 2397 cm = audiotools.ChannelMask.from_fields( 2398 **dict([(f, True) for f in mask])) 2399 track = self.audio_class.from_pcm( 2400 temp.name, BLANK_PCM_Reader(1, 2401 channels=len(cm), 2402 channel_mask=int(cm))) 2403 self.assertEqual(track.channels(), len(cm)) 2404 self.assertEqual(track.channel_mask(), cm) 2405 track = audiotools.open(temp.name) 2406 self.assertEqual(track.channels(), len(cm)) 2407 self.assertEqual(track.channel_mask(), cm) 2408 2409 for mask in [["front_center", 2410 "front_left", 2411 "front_right"], 2412 ["front_center", 2413 "front_left", 2414 "front_right", 2415 "back_center"], 2416 ["front_center", 2417 "front_left", 2418 "front_right", 2419 "back_left", 2420 "back_right"], 2421 ["front_center", 2422 "front_left", 2423 "front_right", 2424 "back_left", 2425 "back_right", 2426 "low_frequency"], 2427 ["front_center", 2428 "front_left", 2429 "front_right", 2430 "back_left", 2431 "back_right", 2432 "back_center", 2433 "low_frequency"], 2434 ["front_center", 2435 "front_left_of_center", 2436 "front_right_of_center", 2437 "front_left", 2438 "front_right", 2439 "back_left", 2440 "back_right", 2441 "low_frequency"]]: 2442 cm = audiotools.ChannelMask.from_fields( 2443 **dict([(f, True) for f in mask])) 2444 track = self.audio_class.from_pcm( 2445 temp.name, BLANK_PCM_Reader(1, 2446 channels=len(cm), 2447 channel_mask=int(cm))) 2448 self.assertEqual(track.channels(), len(cm)) 2449 self.assertEqual(track.channel_mask(), cm) 2450 track = audiotools.open(temp.name) 2451 self.assertEqual(track.channels(), len(cm)) 2452 self.assertEqual(track.channel_mask(), cm) 2453 2454 # ensure valid channel counts with invalid channel masks 2455 # raise an exception 2456 self.assertRaises(audiotools.UnsupportedChannelMask, 2457 self.audio_class.from_pcm, 2458 temp.name, 2459 BLANK_PCM_Reader(1, channels=4, 2460 channel_mask=0x0033)) 2461 2462 self.assertRaises(audiotools.UnsupportedChannelMask, 2463 self.audio_class.from_pcm, 2464 temp.name, 2465 BLANK_PCM_Reader(1, channels=5, 2466 channel_mask=0x003B)) 2467 2468 @FORMAT_ALAC 2469 def test_verify(self): 2470 with open("alac-allframes.m4a", "rb") as f: 2471 alac_data = f.read() 2472 2473 # test truncating the mdat atom triggers IOError 2474 with tempfile.NamedTemporaryFile(suffix='.m4a') as temp: 2475 for i in range(0x16CD, len(alac_data)): 2476 temp.seek(0, 0) 2477 temp.write(alac_data[0:i]) 2478 temp.flush() 2479 self.assertEqual(os.path.getsize(temp.name), i) 2480 decoder = audiotools.open(temp.name).to_pcm() 2481 self.assertNotEqual(decoder, None) 2482 self.assertRaises(IOError, 2483 audiotools.transfer_framelist_data, 2484 decoder, lambda x: x) 2485 2486 self.assertRaises(audiotools.InvalidFile, 2487 audiotools.open(temp.name).verify) 2488 2489 # test a truncated file's convert() method raises EncodingError 2490 with tempfile.NamedTemporaryFile(suffix=".m4a") as temp: 2491 with open("alac-allframes.m4a", "rb") as f: 2492 temp.write(f.read()[0:-10]) 2493 temp.flush() 2494 alac = audiotools.open(temp.name) 2495 if os.path.isfile("dummy.wav"): 2496 os.unlink("dummy.wav") 2497 self.assertEqual(os.path.isfile("dummy.wav"), False) 2498 self.assertRaises(audiotools.EncodingError, 2499 alac.convert, 2500 "dummy.wav", 2501 audiotools.WaveAudio) 2502 self.assertEqual(os.path.isfile("dummy.wav"), False) 2503 2504 @FORMAT_ALAC 2505 def test_too(self): 2506 # ensure that the 'too' meta atom isn't modified by setting metadata 2507 with tempfile.NamedTemporaryFile(suffix=self.suffix) as temp: 2508 track = self.audio_class.from_pcm( 2509 temp.name, 2510 BLANK_PCM_Reader(1)) 2511 metadata = track.get_metadata() 2512 encoder = u"%s" % (metadata[b'ilst'][b'\xa9too'],) 2513 track.set_metadata(audiotools.MetaData(track_name=u"Foo")) 2514 metadata = track.get_metadata() 2515 self.assertEqual(metadata.track_name, u"Foo") 2516 self.assertEqual(u"%s" % (metadata[b'ilst'][b'\xa9too'],), encoder) 2517 2518 def __test_reader__(self, pcmreader, total_pcm_frames, block_size=4096): 2519 if not audiotools.BIN.can_execute(audiotools.BIN["alac"]): 2520 self.assertTrue( 2521 False, 2522 "reference ALAC binary alac(1) required for this test") 2523 2524 temp_file = tempfile.NamedTemporaryFile(suffix=".alac") 2525 self.audio_class.from_pcm(temp_file.name, 2526 pcmreader, 2527 block_size=block_size) 2528 2529 alac = audiotools.open(temp_file.name) 2530 self.assertGreater(alac.total_frames(), 0) 2531 2532 # first, ensure the ALAC-encoded file 2533 # has the same MD5 signature as pcmreader once decoded 2534 md5sum_decoder = md5() 2535 with alac.to_pcm() as d: 2536 f = d.read(audiotools.FRAMELIST_SIZE) 2537 while len(f) > 0: 2538 md5sum_decoder.update(f.to_bytes(False, True)) 2539 f = d.read(audiotools.FRAMELIST_SIZE) 2540 self.assertEqual(md5sum_decoder.digest(), pcmreader.digest()) 2541 2542 # then compare our .to_pcm() output 2543 # with that of the ALAC reference decoder 2544 reference = subprocess.Popen([audiotools.BIN["alac"], 2545 "-r", temp_file.name], 2546 stdout=subprocess.PIPE) 2547 md5sum_reference = md5() 2548 audiotools.transfer_data(reference.stdout.read, 2549 md5sum_reference.update) 2550 reference.stdout.close() 2551 self.assertEqual(reference.wait(), 0) 2552 self.assertEqual(md5sum_reference.digest(), pcmreader.digest(), 2553 "mismatch decoding %s from reference (%s != %s)" % 2554 (repr(pcmreader), 2555 md5sum_reference.hexdigest(), 2556 pcmreader.hexdigest())) 2557 2558 # then, perform test again using from_pcm() 2559 # with total_pcm_frames indicated 2560 pcmreader.reset() 2561 2562 self.audio_class.from_pcm(temp_file.name, 2563 pcmreader, 2564 total_pcm_frames=total_pcm_frames, 2565 block_size=block_size) 2566 2567 alac = audiotools.open(temp_file.name) 2568 self.assertGreater(alac.total_frames(), 0) 2569 2570 # ensure the ALAC-encoded file 2571 # has the same MD5 signature as pcmreader once decoded 2572 md5sum_decoder = md5() 2573 with alac.to_pcm() as d: 2574 f = d.read(audiotools.FRAMELIST_SIZE) 2575 while len(f) > 0: 2576 md5sum_decoder.update(f.to_bytes(False, True)) 2577 f = d.read(audiotools.FRAMELIST_SIZE) 2578 self.assertEqual(md5sum_decoder.digest(), pcmreader.digest()) 2579 2580 # then compare our .to_pcm() output 2581 # with that of the ALAC reference decoder 2582 reference = subprocess.Popen([audiotools.BIN["alac"], 2583 "-r", temp_file.name], 2584 stdout=subprocess.PIPE) 2585 md5sum_reference = md5() 2586 audiotools.transfer_data(reference.stdout.read, 2587 md5sum_reference.update) 2588 reference.stdout.close() 2589 self.assertEqual(reference.wait(), 0) 2590 self.assertEqual(md5sum_reference.digest(), pcmreader.digest(), 2591 "mismatch decoding %s from reference (%s != %s)" % 2592 (repr(pcmreader), 2593 md5sum_reference.hexdigest(), 2594 pcmreader.hexdigest())) 2595 2596 def __test_reader_nonalac__(self, pcmreader, total_pcm_frames, 2597 block_size=4096): 2598 # This is for multichannel testing 2599 # since alac(1) doesn't handle them yet. 2600 # Unfortunately, it relies only on our built-in decoder 2601 # to test correctness. 2602 2603 temp_file = tempfile.NamedTemporaryFile(suffix=".alac") 2604 self.audio_class.from_pcm(temp_file.name, 2605 pcmreader, 2606 block_size=block_size) 2607 2608 alac = audiotools.open(temp_file.name) 2609 self.assertGreater(alac.total_frames(), 0) 2610 2611 # first, ensure the ALAC-encoded file 2612 # has the same MD5 signature as pcmreader once decoded 2613 md5sum_decoder = md5() 2614 with alac.to_pcm() as d: 2615 f = d.read(audiotools.FRAMELIST_SIZE) 2616 while len(f) > 0: 2617 md5sum_decoder.update(f.to_bytes(False, True)) 2618 f = d.read(audiotools.FRAMELIST_SIZE) 2619 self.assertEqual(md5sum_decoder.digest(), pcmreader.digest()) 2620 2621 # perform test again with total_pcm_frames indicated 2622 pcmreader.reset() 2623 self.audio_class.from_pcm(temp_file.name, 2624 pcmreader, 2625 total_pcm_frames=total_pcm_frames, 2626 block_size=block_size) 2627 2628 alac = audiotools.open(temp_file.name) 2629 self.assertGreater(alac.total_frames(), 0) 2630 2631 # first, ensure the ALAC-encoded file 2632 # has the same MD5 signature as pcmreader once decoded 2633 md5sum_decoder = md5() 2634 with alac.to_pcm() as d: 2635 f = d.read(audiotools.FRAMELIST_SIZE) 2636 while len(f) > 0: 2637 md5sum_decoder.update(f.to_bytes(False, True)) 2638 f = d.read(audiotools.FRAMELIST_SIZE) 2639 self.assertEqual(md5sum_decoder.digest(), pcmreader.digest()) 2640 2641 temp_file.close() 2642 2643 def __stream_variations__(self): 2644 for stream in [ 2645 test_streams.Silence16_Mono(200000, 44100), 2646 test_streams.Silence16_Mono(200000, 96000), 2647 test_streams.Silence16_Stereo(200000, 44100), 2648 test_streams.Silence16_Stereo(200000, 96000), 2649 test_streams.Silence24_Mono(200000, 44100), 2650 test_streams.Silence24_Mono(200000, 96000), 2651 test_streams.Silence24_Stereo(200000, 44100), 2652 test_streams.Silence24_Stereo(200000, 96000), 2653 2654 test_streams.Sine16_Mono(200000, 48000, 441.0, 0.50, 441.0, 0.49), 2655 test_streams.Sine16_Mono(200000, 96000, 441.0, 0.61, 661.5, 0.37), 2656 test_streams.Sine16_Mono(200000, 44100, 441.0, 0.50, 882.0, 0.49), 2657 test_streams.Sine16_Mono(200000, 44100, 441.0, 0.50, 4410.0, 0.49), 2658 test_streams.Sine16_Mono(200000, 44100, 8820.0, 0.70, 4410.0, 0.29), 2659 2660 test_streams.Sine16_Stereo(200000, 48000, 441.0, 0.50, 441.0, 0.49, 1.0), 2661 test_streams.Sine16_Stereo(200000, 48000, 441.0, 0.61, 661.5, 0.37, 1.0), 2662 test_streams.Sine16_Stereo(200000, 96000, 441.0, 0.50, 882.0, 0.49, 1.0), 2663 test_streams.Sine16_Stereo(200000, 44100, 441.0, 0.50, 4410.0, 0.49, 1.0), 2664 test_streams.Sine16_Stereo(200000, 44100, 8820.0, 0.70, 4410.0, 0.29, 1.0), 2665 test_streams.Sine16_Stereo(200000, 44100, 441.0, 0.50, 441.0, 0.49, 0.5), 2666 test_streams.Sine16_Stereo(200000, 44100, 441.0, 0.61, 661.5, 0.37, 2.0), 2667 test_streams.Sine16_Stereo(200000, 44100, 441.0, 0.50, 882.0, 0.49, 0.7), 2668 test_streams.Sine16_Stereo(200000, 44100, 441.0, 0.50, 4410.0, 0.49, 1.3), 2669 test_streams.Sine16_Stereo(200000, 44100, 8820.0, 0.70, 4410.0, 0.29, 0.1), 2670 2671 test_streams.Sine24_Mono(200000, 48000, 441.0, 0.50, 441.0, 0.49), 2672 test_streams.Sine24_Mono(200000, 96000, 441.0, 0.61, 661.5, 0.37), 2673 test_streams.Sine24_Mono(200000, 44100, 441.0, 0.50, 882.0, 0.49), 2674 test_streams.Sine24_Mono(200000, 44100, 441.0, 0.50, 4410.0, 0.49), 2675 test_streams.Sine24_Mono(200000, 44100, 8820.0, 0.70, 4410.0, 0.29), 2676 2677 test_streams.Sine24_Stereo(200000, 48000, 441.0, 0.50, 441.0, 0.49, 1.0), 2678 test_streams.Sine24_Stereo(200000, 48000, 441.0, 0.61, 661.5, 0.37, 1.0), 2679 test_streams.Sine24_Stereo(200000, 96000, 441.0, 0.50, 882.0, 0.49, 1.0), 2680 test_streams.Sine24_Stereo(200000, 44100, 441.0, 0.50, 4410.0, 0.49, 1.0), 2681 test_streams.Sine24_Stereo(200000, 44100, 8820.0, 0.70, 4410.0, 0.29, 1.0), 2682 test_streams.Sine24_Stereo(200000, 44100, 441.0, 0.50, 441.0, 0.49, 0.5), 2683 test_streams.Sine24_Stereo(200000, 44100, 441.0, 0.61, 661.5, 0.37, 2.0), 2684 test_streams.Sine24_Stereo(200000, 44100, 441.0, 0.50, 882.0, 0.49, 0.7), 2685 test_streams.Sine24_Stereo(200000, 44100, 441.0, 0.50, 4410.0, 0.49, 1.3), 2686 test_streams.Sine24_Stereo(200000, 44100, 8820.0, 0.70, 4410.0, 0.29, 0.1)]: 2687 yield stream 2688 2689 def __multichannel_stream_variations__(self): 2690 for stream in [ 2691 test_streams.Simple_Sine(200000, 44100, 0x0007, 16, 2692 (6400, 10000), 2693 (12800, 20000), 2694 (30720, 30000)), 2695 test_streams.Simple_Sine(200000, 44100, 0x0107, 16, 2696 (6400, 10000), 2697 (12800, 20000), 2698 (19200, 30000), 2699 (16640, 40000)), 2700 test_streams.Simple_Sine(200000, 44100, 0x0037, 16, 2701 (6400, 10000), 2702 (8960, 15000), 2703 (11520, 20000), 2704 (12800, 25000), 2705 (14080, 30000)), 2706 test_streams.Simple_Sine(200000, 44100, 0x003F, 16, 2707 (6400, 10000), 2708 (11520, 15000), 2709 (16640, 20000), 2710 (21760, 25000), 2711 (26880, 30000), 2712 (30720, 35000)), 2713 test_streams.Simple_Sine(200000, 44100, 0x013F, 16, 2714 (6400, 10000), 2715 (11520, 15000), 2716 (16640, 20000), 2717 (21760, 25000), 2718 (26880, 30000), 2719 (30720, 35000), 2720 (29000, 40000)), 2721 test_streams.Simple_Sine(200000, 44100, 0x00FF, 16, 2722 (6400, 10000), 2723 (11520, 15000), 2724 (16640, 20000), 2725 (21760, 25000), 2726 (26880, 30000), 2727 (30720, 35000), 2728 (29000, 40000), 2729 (28000, 45000)), 2730 2731 test_streams.Simple_Sine(200000, 44100, 0x0007, 24, 2732 (1638400, 10000), 2733 (3276800, 20000), 2734 (7864320, 30000)), 2735 test_streams.Simple_Sine(200000, 44100, 0x0107, 24, 2736 (1638400, 10000), 2737 (3276800, 20000), 2738 (4915200, 30000), 2739 (4259840, 40000)), 2740 test_streams.Simple_Sine(200000, 44100, 0x0037, 24, 2741 (1638400, 10000), 2742 (2293760, 15000), 2743 (2949120, 20000), 2744 (3276800, 25000), 2745 (3604480, 30000)), 2746 test_streams.Simple_Sine(200000, 44100, 0x003F, 24, 2747 (1638400, 10000), 2748 (2949120, 15000), 2749 (4259840, 20000), 2750 (5570560, 25000), 2751 (6881280, 30000), 2752 (7864320, 35000)), 2753 test_streams.Simple_Sine(200000, 44100, 0x013F, 24, 2754 (1638400, 10000), 2755 (2949120, 15000), 2756 (4259840, 20000), 2757 (5570560, 25000), 2758 (6881280, 30000), 2759 (7864320, 35000), 2760 (7000000, 40000)), 2761 test_streams.Simple_Sine(200000, 44100, 0x00FF, 24, 2762 (1638400, 10000), 2763 (2949120, 15000), 2764 (4259840, 20000), 2765 (5570560, 25000), 2766 (6881280, 30000), 2767 (7864320, 35000), 2768 (7000000, 40000), 2769 (6000000, 45000))]: 2770 yield stream 2771 2772 @FORMAT_ALAC 2773 def test_streams(self): 2774 for g in self.__stream_variations__(): 2775 md5sum = md5() 2776 f = g.read(audiotools.FRAMELIST_SIZE) 2777 while len(f) > 0: 2778 md5sum.update(f.to_bytes(False, True)) 2779 f = g.read(audiotools.FRAMELIST_SIZE) 2780 self.assertEqual(md5sum.digest(), g.digest()) 2781 g.close() 2782 2783 for g in self.__multichannel_stream_variations__(): 2784 md5sum = md5() 2785 f = g.read(audiotools.FRAMELIST_SIZE) 2786 while len(f) > 0: 2787 md5sum.update(f.to_bytes(False, True)) 2788 f = g.read(audiotools.FRAMELIST_SIZE) 2789 self.assertEqual(md5sum.digest(), g.digest()) 2790 g.close() 2791 2792 @FORMAT_ALAC 2793 def test_small_files(self): 2794 for g in [test_streams.Generate01, 2795 test_streams.Generate02]: 2796 self.__test_reader__(g(44100), 1, block_size=1152) 2797 for g in [test_streams.Generate03, 2798 test_streams.Generate04]: 2799 self.__test_reader__(g(44100), 5, block_size=1152) 2800 2801 @FORMAT_ALAC 2802 def test_full_scale_deflection(self): 2803 for (bps, fsd) in [(16, test_streams.fsd16), 2804 (24, test_streams.fsd24)]: 2805 for pattern in [test_streams.PATTERN01, 2806 test_streams.PATTERN02, 2807 test_streams.PATTERN03, 2808 test_streams.PATTERN04, 2809 test_streams.PATTERN05, 2810 test_streams.PATTERN06, 2811 test_streams.PATTERN07]: 2812 self.__test_reader__( 2813 test_streams.MD5Reader(fsd(pattern, 100)), 2814 len(pattern) * 100, 2815 block_size=1152) 2816 2817 @FORMAT_ALAC 2818 def test_sines(self): 2819 for g in self.__stream_variations__(): 2820 self.__test_reader__(g, 200000, block_size=1152) 2821 2822 for g in self.__multichannel_stream_variations__(): 2823 self.__test_reader_nonalac__(g, 200000, block_size=1152) 2824 2825 @FORMAT_ALAC 2826 def test_wasted_bps(self): 2827 self.__test_reader__(test_streams.WastedBPS16(1000), 2828 1000, 2829 block_size=1152) 2830 2831 @FORMAT_ALAC 2832 def test_blocksizes(self): 2833 noise = struct.unpack(">32h", os.urandom(64)) 2834 2835 for block_size in [16, 17, 18, 19, 20, 21, 22, 23, 24, 2836 25, 26, 27, 28, 29, 30, 31, 32, 33]: 2837 self.__test_reader__( 2838 test_streams.MD5Reader( 2839 test_streams.FrameListReader(noise, 2840 44100, 1, 16)), 2841 len(noise), 2842 block_size=block_size) 2843 2844 @FORMAT_ALAC 2845 def test_noise(self): 2846 for (channels, mask) in [ 2847 (1, int(audiotools.ChannelMask.from_channels(1))), 2848 (2, int(audiotools.ChannelMask.from_channels(2)))]: 2849 for bps in [16, 24]: 2850 # the reference decoder can't handle very large block sizes 2851 for blocksize in [32, 4096, 8192]: 2852 self.__test_reader__( 2853 MD5_Reader( 2854 EXACT_RANDOM_PCM_Reader( 2855 pcm_frames=65536, 2856 sample_rate=44100, 2857 channels=channels, 2858 channel_mask=mask, 2859 bits_per_sample=bps)), 2860 65536, 2861 block_size=blocksize) 2862 2863 @FORMAT_ALAC 2864 def test_fractional(self): 2865 def __perform_test__(block_size, pcm_frames): 2866 self.__test_reader__( 2867 MD5_Reader( 2868 EXACT_RANDOM_PCM_Reader( 2869 pcm_frames=pcm_frames, 2870 sample_rate=44100, 2871 channels=2, 2872 bits_per_sample=16)), 2873 pcm_frames, 2874 block_size=block_size) 2875 2876 for pcm_frames in [31, 32, 33, 34, 35, 2046, 2047, 2048, 2049, 2050]: 2877 __perform_test__(33, pcm_frames) 2878 2879 for pcm_frames in [254, 255, 256, 257, 258, 510, 511, 512, 2880 513, 514, 1022, 1023, 1024, 1025, 1026, 2881 2046, 2047, 2048, 2049, 2050, 4094, 4095, 2882 4096, 4097, 4098]: 2883 __perform_test__(256, pcm_frames) 2884 2885 for pcm_frames in [1022, 1023, 1024, 1025, 1026, 2046, 2047, 2886 2048, 2049, 2050, 4094, 4095, 4096, 4097, 4098]: 2887 __perform_test__(2048, pcm_frames) 2888 2889 for pcm_frames in [1022, 1023, 1024, 1025, 1026, 2046, 2047, 2048, 2890 2049, 2050, 4094, 4095, 4096, 4097, 4098, 4606, 2891 4607, 4608, 4609, 4610, 8190, 8191, 8192, 8193, 2892 8194, 16382, 16383, 16384, 16385, 16386]: 2893 __perform_test__(4608, pcm_frames) 2894 2895 @FORMAT_ALAC 2896 def test_frame_header_variations(self): 2897 self.__test_reader__(test_streams.Sine16_Mono(200000, 96000, 2898 441.0, 0.61, 661.5, 0.37), 2899 200000, 2900 block_size=16) 2901 2902 # The alac(1) decoder I'm using as a reference can't handle 2903 # this block size, even though iTunes handles the resulting files 2904 # just fine. Therefore, it's likely an alac bug beyond my 2905 # capability to fix. 2906 # I don't expect anyone will use anything other than the default 2907 # block size anyway. 2908 2909 # self.__test_reader__(test_streams.Sine16_Mono(200000, 96000, 2910 # 441.0, 0.61, 661.5, 0.37), 2911 # block_size=65535) 2912 2913 self.__test_reader__(test_streams.Sine16_Mono(200000, 9, 2914 441.0, 0.61, 661.5, 0.37), 2915 200000, 2916 block_size=1152) 2917 2918 self.__test_reader__(test_streams.Sine16_Mono(200000, 90, 2919 441.0, 0.61, 661.5, 0.37), 2920 200000, 2921 block_size=1152) 2922 2923 self.__test_reader__(test_streams.Sine16_Mono(200000, 90000, 2924 441.0, 0.61, 661.5, 0.37), 2925 200000, 2926 block_size=1152) 2927 2928 @FORMAT_ALAC 2929 def test_python_codec(self): 2930 def test_python_reader(pcmreader, total_pcm_frames, block_size=4096): 2931 # ALAC doesn't really have encoding options worth mentioning 2932 from audiotools.py_encoders import encode_mdat 2933 2934 # encode file using Python-based encoder 2935 temp_file = tempfile.NamedTemporaryFile(suffix=".m4a") 2936 audiotools.ALACAudio.from_pcm( 2937 temp_file.name, 2938 pcmreader, 2939 block_size=block_size, 2940 encoding_function=encode_mdat) 2941 2942 # verify contents of file decoded by 2943 # Python-based decoder against contents decoded by 2944 # C-based decoder 2945 from audiotools.py_decoders import ALACDecoder as ALACDecoder1 2946 from audiotools.decoders import ALACDecoder as ALACDecoder2 2947 2948 self.assertTrue( 2949 audiotools.pcm_cmp( 2950 ALACDecoder1(temp_file.name), 2951 ALACDecoder2(temp_file.name))) 2952 2953 # test from_pcm() with total_pcm_frames indicated 2954 pcmreader.reset() 2955 audiotools.ALACAudio.from_pcm( 2956 temp_file.name, 2957 pcmreader, 2958 total_pcm_frames=total_pcm_frames, 2959 block_size=block_size, 2960 encoding_function=encode_mdat) 2961 2962 # verify contents of file decoded by 2963 # Python-based decoder against contents decoded by 2964 # C-based decoder 2965 from audiotools.py_decoders import ALACDecoder as ALACDecoder1 2966 from audiotools.decoders import ALACDecoder as ALACDecoder2 2967 2968 self.assertTrue( 2969 audiotools.pcm_cmp( 2970 ALACDecoder1(temp_file.name), 2971 ALACDecoder2(temp_file.name))) 2972 2973 temp_file.close() 2974 2975 # test small files 2976 for g in [test_streams.Generate01, 2977 test_streams.Generate02]: 2978 test_python_reader(g(44100), 1, block_size=1152) 2979 for g in [test_streams.Generate03, 2980 test_streams.Generate04]: 2981 test_python_reader(g(44100), 5, block_size=1152) 2982 2983 # test full scale deflection 2984 for (bps, fsd) in [(16, test_streams.fsd16), 2985 (24, test_streams.fsd24)]: 2986 for pattern in [test_streams.PATTERN01, 2987 test_streams.PATTERN02, 2988 test_streams.PATTERN03, 2989 test_streams.PATTERN04, 2990 test_streams.PATTERN05, 2991 test_streams.PATTERN06, 2992 test_streams.PATTERN07]: 2993 test_python_reader(fsd(pattern, 100), 2994 len(pattern) * 100, 2995 block_size=1152) 2996 2997 # test silence 2998 for g in [test_streams.Silence16_Mono(5000, 48000), 2999 test_streams.Silence16_Stereo(5000, 48000), 3000 test_streams.Silence24_Mono(5000, 48000), 3001 test_streams.Silence24_Stereo(5000, 48000)]: 3002 test_python_reader(g, 5000, block_size=1152) 3003 3004 # test sines 3005 for g in [test_streams.Sine16_Mono(5000, 48000, 3006 441.0, 0.50, 441.0, 0.49), 3007 test_streams.Sine16_Mono(5000, 96000, 3008 441.0, 0.61, 661.5, 0.37), 3009 test_streams.Sine16_Stereo(5000, 48000, 3010 441.0, 0.50, 441.0, 0.49, 1.0), 3011 test_streams.Sine16_Stereo(5000, 96000, 3012 441.0, 0.50, 882.0, 0.49, 1.0), 3013 test_streams.Sine24_Mono(5000, 48000, 3014 441.0, 0.50, 441.0, 0.49), 3015 test_streams.Sine24_Mono(5000, 96000, 3016 441.0, 0.61, 661.5, 0.37), 3017 test_streams.Sine24_Stereo(5000, 48000, 3018 441.0, 0.50, 441.0, 0.49, 1.0), 3019 test_streams.Sine24_Stereo(5000, 96000, 3020 441.0, 0.50, 882.0, 0.49, 1.0)]: 3021 test_python_reader(g, 5000, block_size=1152) 3022 3023 for g in [test_streams.Simple_Sine(5000, 44100, 0x0007, 16, 3024 (6400, 10000), 3025 (12800, 20000), 3026 (30720, 30000)), 3027 test_streams.Simple_Sine(5000, 44100, 0x0107, 16, 3028 (6400, 10000), 3029 (12800, 20000), 3030 (19200, 30000), 3031 (16640, 40000)), 3032 test_streams.Simple_Sine(5000, 44100, 0x0037, 16, 3033 (6400, 10000), 3034 (8960, 15000), 3035 (11520, 20000), 3036 (12800, 25000), 3037 (14080, 30000)), 3038 test_streams.Simple_Sine(5000, 44100, 0x003F, 16, 3039 (6400, 10000), 3040 (11520, 15000), 3041 (16640, 20000), 3042 (21760, 25000), 3043 (26880, 30000), 3044 (30720, 35000)), 3045 test_streams.Simple_Sine(5000, 44100, 0x013F, 16, 3046 (6400, 10000), 3047 (11520, 15000), 3048 (16640, 20000), 3049 (21760, 25000), 3050 (26880, 30000), 3051 (30720, 35000), 3052 (29000, 40000)), 3053 test_streams.Simple_Sine(5000, 44100, 0x00FF, 16, 3054 (6400, 10000), 3055 (11520, 15000), 3056 (16640, 20000), 3057 (21760, 25000), 3058 (26880, 30000), 3059 (30720, 35000), 3060 (29000, 40000), 3061 (28000, 45000)), 3062 3063 test_streams.Simple_Sine(5000, 44100, 0x0007, 24, 3064 (1638400, 10000), 3065 (3276800, 20000), 3066 (7864320, 30000)), 3067 test_streams.Simple_Sine(5000, 44100, 0x0107, 24, 3068 (1638400, 10000), 3069 (3276800, 20000), 3070 (4915200, 30000), 3071 (4259840, 40000)), 3072 test_streams.Simple_Sine(5000, 44100, 0x0037, 24, 3073 (1638400, 10000), 3074 (2293760, 15000), 3075 (2949120, 20000), 3076 (3276800, 25000), 3077 (3604480, 30000)), 3078 test_streams.Simple_Sine(5000, 44100, 0x003F, 24, 3079 (1638400, 10000), 3080 (2949120, 15000), 3081 (4259840, 20000), 3082 (5570560, 25000), 3083 (6881280, 30000), 3084 (7864320, 35000)), 3085 test_streams.Simple_Sine(5000, 44100, 0x013F, 24, 3086 (1638400, 10000), 3087 (2949120, 15000), 3088 (4259840, 20000), 3089 (5570560, 25000), 3090 (6881280, 30000), 3091 (7864320, 35000), 3092 (7000000, 40000)), 3093 test_streams.Simple_Sine(5000, 44100, 0x00FF, 24, 3094 (1638400, 10000), 3095 (2949120, 15000), 3096 (4259840, 20000), 3097 (5570560, 25000), 3098 (6881280, 30000), 3099 (7864320, 35000), 3100 (7000000, 40000), 3101 (6000000, 45000))]: 3102 test_python_reader(g, 5000, block_size=1152) 3103 3104 # test wasted BPS 3105 test_python_reader(test_streams.WastedBPS16(1000), 3106 1000, 3107 block_size=1152) 3108 3109 # test block sizes 3110 noise = struct.unpack(">32h", os.urandom(64)) 3111 3112 for block_size in [16, 17, 18, 19, 20, 21, 22, 23, 24, 3113 25, 26, 27, 28, 29, 30, 31, 32, 33]: 3114 test_python_reader( 3115 test_streams.MD5Reader( 3116 test_streams.FrameListReader(noise, 44100, 1, 16)), 3117 len(noise), 3118 block_size=block_size) 3119 3120 # test noise 3121 for (channels, mask) in [ 3122 (1, int(audiotools.ChannelMask.from_channels(1))), 3123 (2, int(audiotools.ChannelMask.from_channels(2)))]: 3124 for bps in [16, 24]: 3125 # the reference decoder can't handle very large block sizes 3126 for blocksize in [32, 4096, 8192]: 3127 test_python_reader( 3128 EXACT_RANDOM_PCM_Reader( 3129 pcm_frames=4097, 3130 sample_rate=44100, 3131 channels=channels, 3132 channel_mask=mask, 3133 bits_per_sample=bps), 3134 4097, 3135 block_size=blocksize) 3136 3137 # test fractional 3138 for (block_size, 3139 pcm_frames) in [(33, [31, 32, 33, 34, 35, 2046, 3140 2047, 2048, 2049, 2050]), 3141 (256, [254, 255, 256, 257, 258, 510, 511, 512, 3142 513, 514, 1022, 1023, 1024, 1025, 1026, 3143 2046, 2047, 2048, 2049, 2050, 4094, 4095, 3144 4096, 4097, 4098])]: 3145 for frame_count in pcm_frames: 3146 test_python_reader( 3147 EXACT_RANDOM_PCM_Reader( 3148 pcm_frames=frame_count, 3149 sample_rate=44100, 3150 channels=2, 3151 bits_per_sample=16), 3152 frame_count, 3153 block_size=block_size) 3154 3155 # test frame header variations 3156 test_python_reader( 3157 test_streams.Sine16_Mono(5000, 96000, 3158 441.0, 0.61, 661.5, 0.37), 3159 5000, 3160 block_size=16) 3161 3162 test_python_reader( 3163 test_streams.Sine16_Mono(5000, 9, 3164 441.0, 0.61, 661.5, 0.37), 3165 5000, 3166 block_size=1152) 3167 3168 test_python_reader( 3169 test_streams.Sine16_Mono(5000, 90, 3170 441.0, 0.61, 661.5, 0.37), 3171 5000, 3172 block_size=1152) 3173 3174 test_python_reader( 3175 test_streams.Sine16_Mono(5000, 90000, 3176 441.0, 0.61, 661.5, 0.37), 3177 5000, 3178 block_size=1152) 3179 3180 3181class AUFileTest(LosslessFileTest): 3182 def setUp(self): 3183 self.audio_class = audiotools.AuAudio 3184 self.suffix = "." + self.audio_class.SUFFIX 3185 3186 @FORMAT_AU 3187 def test_overlong_file(self): 3188 # trying to generate too large of a file 3189 # should throw an exception right away if total_pcm_frames known 3190 # instead of building it first 3191 3192 self.assertEqual(os.path.isfile("invalid.au"), False) 3193 3194 self.assertRaises(audiotools.EncodingError, 3195 self.audio_class.from_pcm, 3196 "invalid.au", 3197 EXACT_SILENCE_PCM_Reader( 3198 pcm_frames=715827883, 3199 sample_rate=44100, 3200 channels=2, 3201 bits_per_sample=24), 3202 total_pcm_frames=715827883) 3203 3204 self.assertEqual(os.path.isfile("invalid.au"), False) 3205 3206 @FORMAT_AU 3207 def test_channel_mask(self): 3208 temp = tempfile.NamedTemporaryFile(suffix=self.suffix) 3209 try: 3210 for mask in [["front_center"], 3211 ["front_left", 3212 "front_right"]]: 3213 cm = audiotools.ChannelMask.from_fields( 3214 **dict([(f, True) for f in mask])) 3215 track = self.audio_class.from_pcm( 3216 temp.name, BLANK_PCM_Reader(1, 3217 channels=len(cm), 3218 channel_mask=int(cm))) 3219 self.assertEqual(track.channels(), len(cm)) 3220 self.assertEqual(track.channel_mask(), cm) 3221 track = audiotools.open(temp.name) 3222 self.assertEqual(track.channels(), len(cm)) 3223 self.assertEqual(track.channel_mask(), cm) 3224 3225 for mask in [["front_left", 3226 "front_right", 3227 "front_center"], 3228 ["front_left", 3229 "front_right", 3230 "back_left", 3231 "back_right"], 3232 ["front_left", 3233 "front_right", 3234 "front_center", 3235 "back_left", 3236 "back_right"], 3237 ["front_left", 3238 "front_right", 3239 "front_center", 3240 "low_frequency", 3241 "back_left", 3242 "back_right"]]: 3243 cm = audiotools.ChannelMask.from_fields( 3244 **dict([(f, True) for f in mask])) 3245 track = self.audio_class.from_pcm( 3246 temp.name, BLANK_PCM_Reader(1, 3247 channels=len(cm), 3248 channel_mask=int(cm))) 3249 self.assertEqual(track.channels(), len(cm)) 3250 self.assertEqual(track.channel_mask(), 0) 3251 track = audiotools.open(temp.name) 3252 self.assertEqual(track.channels(), len(cm)) 3253 self.assertEqual(track.channel_mask(), 0) 3254 finally: 3255 temp.close() 3256 3257 @FORMAT_AU 3258 def test_verify(self): 3259 # test truncated file 3260 with tempfile.NamedTemporaryFile( 3261 suffix="." + self.audio_class.SUFFIX) as temp: 3262 track = self.audio_class.from_pcm( 3263 temp.name, 3264 BLANK_PCM_Reader(1)) 3265 with open(temp.name, 'rb') as f: 3266 good_data = f.read() 3267 with open(temp.name, 'wb') as f: 3268 f.write(good_data[0:-10]) 3269 reader = track.to_pcm() 3270 self.assertNotEqual(reader, None) 3271 self.assertRaises(IOError, 3272 audiotools.transfer_framelist_data, 3273 reader, lambda x: x) 3274 3275 # test convert() error 3276 with tempfile.NamedTemporaryFile( 3277 suffix="." + self.audio_class.SUFFIX) as temp: 3278 track = self.audio_class.from_pcm( 3279 temp.name, 3280 BLANK_PCM_Reader(1)) 3281 with open(temp.name, 'rb') as f: 3282 good_data = f.read() 3283 with open(temp.name, 'wb') as f: 3284 f.write(good_data[0:-10]) 3285 if os.path.isfile("dummy.wav"): 3286 os.unlink("dummy.wav") 3287 self.assertEqual(os.path.isfile("dummy.wav"), False) 3288 self.assertRaises(audiotools.EncodingError, 3289 track.convert, 3290 "dummy.wav", 3291 audiotools.WaveAudio) 3292 self.assertEqual(os.path.isfile("dummy.wav"), False) 3293 3294 3295class FlacFileTest(TestForeignAiffChunks, 3296 TestForeignWaveChunks, 3297 LosslessFileTest): 3298 def setUp(self): 3299 self.audio_class = audiotools.FlacAudio 3300 self.suffix = "." + self.audio_class.SUFFIX 3301 3302 from audiotools.decoders import FlacDecoder 3303 from audiotools.encoders import encode_flac 3304 3305 self.decoder = FlacDecoder 3306 self.encode = encode_flac 3307 self.encode_opts = [{"block_size": 1152, 3308 "max_lpc_order": 0, 3309 "min_residual_partition_order": 0, 3310 "max_residual_partition_order": 3}, 3311 {"block_size": 1152, 3312 "max_lpc_order": 0, 3313 "adaptive_mid_side": True, 3314 "min_residual_partition_order": 0, 3315 "max_residual_partition_order": 3}, 3316 {"block_size": 1152, 3317 "max_lpc_order": 0, 3318 "exhaustive_model_search": True, 3319 "min_residual_partition_order": 0, 3320 "max_residual_partition_order": 3}, 3321 {"block_size": 4096, 3322 "max_lpc_order": 6, 3323 "min_residual_partition_order": 0, 3324 "max_residual_partition_order": 4}, 3325 {"block_size": 4096, 3326 "max_lpc_order": 8, 3327 "adaptive_mid_side": True, 3328 "min_residual_partition_order": 0, 3329 "max_residual_partition_order": 4}, 3330 {"block_size": 4096, 3331 "max_lpc_order": 8, 3332 "mid_side": True, 3333 "min_residual_partition_order": 0, 3334 "max_residual_partition_order": 5}, 3335 {"block_size": 4096, 3336 "max_lpc_order": 8, 3337 "mid_side": True, 3338 "min_residual_partition_order": 0, 3339 "max_residual_partition_order": 6}, 3340 {"block_size": 4096, 3341 "max_lpc_order": 8, 3342 "mid_side": True, 3343 "exhaustive_model_search": True, 3344 "min_residual_partition_order": 0, 3345 "max_residual_partition_order": 6}, 3346 {"block_size": 4096, 3347 "max_lpc_order": 12, 3348 "mid_side": True, 3349 "exhaustive_model_search": True, 3350 "min_residual_partition_order": 0, 3351 "max_residual_partition_order": 6}] 3352 3353 @FORMAT_FLAC 3354 def test_init(self): 3355 # check missing file 3356 self.assertRaises(audiotools.flac.InvalidFLAC, 3357 audiotools.FlacAudio, 3358 "/dev/null/foo") 3359 3360 # check invalid file 3361 with tempfile.NamedTemporaryFile(suffix=".flac") as invalid_file: 3362 for c in [b"i", b"n", b"v", b"a", b"l", b"i", b"d", 3363 b"s", b"t", b"r", b"i", b"n", b"g", b"x", b"x", b"x"]: 3364 invalid_file.write(c) 3365 invalid_file.flush() 3366 self.assertRaises(audiotools.flac.InvalidFLAC, 3367 audiotools.FlacAudio, 3368 invalid_file.name) 3369 3370 # check some decoder errors, 3371 # mostly to ensure a failed init doesn't make Python explode 3372 self.assertRaises(IOError, self.decoder, None) 3373 3374 self.assertRaises(IOError, self.decoder, "filename") 3375 3376 @FORMAT_FLAC 3377 def test_metadata2(self): 3378 from bz2 import decompress 3379 3380 with tempfile.NamedTemporaryFile(suffix=self.suffix) as temp: 3381 track = self.audio_class.from_pcm(temp.name, 3382 BLANK_PCM_Reader(1)) 3383 3384 # check that a non-cover image with a description round-trips 3385 m = audiotools.MetaData() 3386 m.add_image( 3387 audiotools.Image.new( 3388 TEST_COVER1, u'Unicode \u3057\u3066\u307f\u308b', 1)) 3389 track.set_metadata(m) 3390 3391 new_track = audiotools.open(track.filename) 3392 m2 = new_track.get_metadata() 3393 3394 self.assertEqual(m.images()[0], m2.images()[0]) 3395 3396 orig_md5 = md5() 3397 audiotools.transfer_framelist_data(track.to_pcm(), orig_md5.update) 3398 3399 # add an image too large to fit into a FLAC metadata chunk 3400 metadata = track.get_metadata() 3401 metadata.add_image( 3402 audiotools.Image.new(decompress(HUGE_BMP), u'', 0)) 3403 3404 track.update_metadata(metadata) 3405 3406 # ensure that setting the metadata doesn't break the file 3407 new_md5 = md5() 3408 audiotools.transfer_framelist_data(track.to_pcm(), new_md5.update) 3409 3410 self.assertEqual(orig_md5.hexdigest(), 3411 new_md5.hexdigest()) 3412 3413 # ensure that setting fresh oversized metadata 3414 # doesn't break the file 3415 metadata = audiotools.MetaData() 3416 metadata.add_image( 3417 audiotools.Image.new(decompress(HUGE_BMP), u'', 0)) 3418 3419 track.set_metadata(metadata) 3420 3421 new_md5 = md5() 3422 audiotools.transfer_framelist_data(track.to_pcm(), new_md5.update) 3423 3424 self.assertEqual(orig_md5.hexdigest(), 3425 new_md5.hexdigest()) 3426 3427 # add a COMMENT block too large to fit into a FLAC metadata chunk 3428 metadata = track.get_metadata() 3429 metadata.comment = u"a" * 16777216 3430 3431 track.update_metadata(metadata) 3432 3433 # ensure that setting the metadata doesn't break the file 3434 new_md5 = md5() 3435 audiotools.transfer_framelist_data(track.to_pcm(), new_md5.update) 3436 3437 self.assertEqual(orig_md5.hexdigest(), 3438 new_md5.hexdigest()) 3439 3440 # ensure that setting fresh oversized metadata 3441 # doesn't break the file 3442 metadata = audiotools.MetaData(comment=u"a" * 16777216) 3443 3444 track.set_metadata(metadata) 3445 3446 new_md5 = md5() 3447 audiotools.transfer_framelist_data(track.to_pcm(), new_md5.update) 3448 3449 self.assertEqual(orig_md5.hexdigest(), 3450 new_md5.hexdigest()) 3451 3452 track.set_metadata(audiotools.MetaData(track_name=u"Testing")) 3453 3454 # ensure that vendor_string isn't modified by setting metadata 3455 metadata = track.get_metadata() 3456 self.assertIsNotNone(metadata) 3457 self.assertEqual(metadata.track_name, u"Testing") 3458 self.assertIsNotNone( 3459 metadata.get_block(audiotools.flac.Flac_VORBISCOMMENT.BLOCK_ID)) 3460 vorbis_comment = metadata.get_blocks( 3461 audiotools.flac.Flac_VORBISCOMMENT.BLOCK_ID) 3462 proper_vendor_string = vorbis_comment[0].vendor_string 3463 vorbis_comment[0].vendor_string = u"Different String" 3464 metadata.replace_blocks(audiotools.flac.Flac_VORBISCOMMENT.BLOCK_ID, 3465 vorbis_comment) 3466 track.set_metadata(metadata) 3467 vendor_string = track.get_metadata().get_block( 3468 audiotools.flac.Flac_VORBISCOMMENT.BLOCK_ID).vendor_string 3469 self.assertEqual(vendor_string, proper_vendor_string) 3470 3471 # FIXME - ensure that channel mask isn't modified 3472 # by setting metadata 3473 3474 @FORMAT_FLAC 3475 def test_update_metadata(self): 3476 # build a temporary file 3477 with tempfile.NamedTemporaryFile(suffix=".flac") as temp: 3478 with open("flac-allframes.flac", "rb") as f: 3479 temp.write(f.read()) 3480 temp.flush() 3481 flac_file = audiotools.open(temp.name) 3482 3483 # attempt to adjust its metadata with bogus side data fields 3484 metadata = flac_file.get_metadata() 3485 streaminfo = metadata.get_block( 3486 audiotools.flac.Flac_STREAMINFO.BLOCK_ID) 3487 3488 minimum_block_size = streaminfo.minimum_block_size 3489 maximum_block_size = streaminfo.maximum_block_size 3490 minimum_frame_size = streaminfo.minimum_frame_size 3491 maximum_frame_size = streaminfo.maximum_frame_size 3492 sample_rate = streaminfo.sample_rate 3493 channels = streaminfo.channels 3494 bits_per_sample = streaminfo.bits_per_sample 3495 total_samples = streaminfo.total_samples 3496 md5sum = streaminfo.md5sum 3497 3498 streaminfo.minimum_block_size = 1 3499 streaminfo.maximum_block_size = 10 3500 streaminfo.minimum_frame_size = 2 3501 streaminfo.maximum_frame_size = 11 3502 streaminfo.sample_rate = 96000 3503 streaminfo.channels = 4 3504 streaminfo.bits_per_sample = 24 3505 streaminfo.total_samples = 96000 3506 streaminfo.md5sum = b"\x01" * 16 3507 3508 metadata.replace_blocks(audiotools.flac.Flac_STREAMINFO.BLOCK_ID, 3509 [streaminfo]) 3510 3511 # ensure that set_metadata() restores fields to original values 3512 flac_file.set_metadata(metadata) 3513 metadata = flac_file.get_metadata() 3514 streaminfo = metadata.get_block( 3515 audiotools.flac.Flac_STREAMINFO.BLOCK_ID) 3516 3517 self.assertEqual(minimum_block_size, 3518 streaminfo.minimum_block_size) 3519 self.assertEqual(maximum_block_size, 3520 streaminfo.maximum_block_size) 3521 self.assertEqual(minimum_frame_size, 3522 streaminfo.minimum_frame_size) 3523 self.assertEqual(maximum_frame_size, 3524 streaminfo.maximum_frame_size) 3525 self.assertEqual(sample_rate, 3526 streaminfo.sample_rate) 3527 self.assertEqual(channels, 3528 streaminfo.channels) 3529 self.assertEqual(bits_per_sample, 3530 streaminfo.bits_per_sample) 3531 self.assertEqual(total_samples, 3532 streaminfo.total_samples) 3533 self.assertEqual(md5sum, 3534 streaminfo.md5sum) 3535 3536 # adjust its metadata with new bogus side data files 3537 metadata = flac_file.get_metadata() 3538 streaminfo = metadata.get_block( 3539 audiotools.flac.Flac_STREAMINFO.BLOCK_ID) 3540 streaminfo.minimum_block_size = 1 3541 streaminfo.maximum_block_size = 10 3542 streaminfo.minimum_frame_size = 2 3543 streaminfo.maximum_frame_size = 11 3544 streaminfo.sample_rate = 96000 3545 streaminfo.channels = 4 3546 streaminfo.bits_per_sample = 24 3547 streaminfo.total_samples = 96000 3548 streaminfo.md5sum = b"\x01" * 16 3549 3550 metadata.replace_blocks(audiotools.flac.Flac_STREAMINFO.BLOCK_ID, 3551 [streaminfo]) 3552 3553 # ensure that update_metadata() uses the bogus side data 3554 flac_file.update_metadata(metadata) 3555 metadata = flac_file.get_metadata() 3556 streaminfo = metadata.get_block( 3557 audiotools.flac.Flac_STREAMINFO.BLOCK_ID) 3558 self.assertEqual(streaminfo.minimum_block_size, 1) 3559 self.assertEqual(streaminfo.maximum_block_size, 10) 3560 self.assertEqual(streaminfo.minimum_frame_size, 2) 3561 self.assertEqual(streaminfo.maximum_frame_size, 11) 3562 self.assertEqual(streaminfo.sample_rate, 96000) 3563 self.assertEqual(streaminfo.channels, 4) 3564 self.assertEqual(streaminfo.bits_per_sample, 24) 3565 self.assertEqual(streaminfo.total_samples, 96000) 3566 self.assertEqual(streaminfo.md5sum, b"\x01" * 16) 3567 3568 @FORMAT_FLAC 3569 def test_verify(self): 3570 from test_core import bytes_to_ints, ints_to_bytes 3571 3572 self.assertEqual( 3573 audiotools.open("flac-allframes.flac").__md5__, 3574 b'\xf5\x3f\x86\x87\x6d\xcd\x77\x83' + 3575 b'\x22\x5c\x93\xba\x8a\x93\x8c\x7d') 3576 3577 with open("flac-allframes.flac", "rb") as f: 3578 flac_data = bytes_to_ints(f.read()) 3579 3580 self.assertEqual(audiotools.open("flac-allframes.flac").verify(), 3581 True) 3582 3583 # try changing the file underfoot 3584 with tempfile.NamedTemporaryFile(suffix=".flac") as temp: 3585 temp.write(ints_to_bytes(flac_data)) 3586 temp.flush() 3587 flac_file = audiotools.open(temp.name) 3588 self.assertEqual(flac_file.verify(), True) 3589 3590 for i in range(0, len(flac_data)): 3591 with open(temp.name, "wb") as f: 3592 f.write(ints_to_bytes(flac_data[0:i])) 3593 self.assertRaises(audiotools.InvalidFile, 3594 flac_file.verify) 3595 3596 for i in range(0x2A, len(flac_data)): 3597 for j in range(8): 3598 new_data = list(flac_data) 3599 new_data[i] = new_data[i] ^ (1 << j) 3600 with open(temp.name, "wb") as f: 3601 f.write(ints_to_bytes(new_data)) 3602 self.assertRaises(audiotools.InvalidFile, 3603 flac_file.verify) 3604 3605 # check a FLAC file with a short header 3606 with tempfile.NamedTemporaryFile(suffix=".flac") as temp: 3607 for i in range(0, 0x2A): 3608 temp.seek(0, 0) 3609 temp.write(ints_to_bytes(flac_data[0:i])) 3610 temp.flush() 3611 self.assertEqual(os.path.getsize(temp.name), i) 3612 if i < 4: 3613 with open(temp.name, "rb") as f: 3614 self.assertIsNone(audiotools.file_type(f)) 3615 with open(temp.name, "rb") as f: 3616 self.assertRaises(IOError, 3617 audiotools.decoders.FlacDecoder, 3618 f) 3619 3620 # check a FLAC file that's been truncated 3621 with tempfile.NamedTemporaryFile(suffix=".flac") as temp: 3622 for i in range(0x2A, len(flac_data)): 3623 temp.seek(0, 0) 3624 temp.write(ints_to_bytes(flac_data[0:i])) 3625 temp.flush() 3626 self.assertEqual(os.path.getsize(temp.name), i) 3627 decoder = audiotools.open(temp.name).to_pcm() 3628 self.assertNotEqual(decoder, None) 3629 self.assertRaises(IOError, 3630 audiotools.transfer_framelist_data, 3631 decoder, lambda x: x) 3632 3633 self.assertRaises(audiotools.InvalidFile, 3634 audiotools.open(temp.name).verify) 3635 3636 # test a FLAC file with a single swapped bit 3637 with tempfile.NamedTemporaryFile(suffix=".flac") as temp: 3638 for i in range(0x2A, len(flac_data)): 3639 for j in range(8): 3640 bytes = flac_data[:] 3641 bytes[i] ^= (1 << j) 3642 temp.seek(0, 0) 3643 temp.write(ints_to_bytes(bytes)) 3644 temp.flush() 3645 self.assertEqual(len(flac_data), 3646 os.path.getsize(temp.name)) 3647 3648 with audiotools.open(temp.name).to_pcm() as decoders: 3649 try: 3650 self.assertRaises( 3651 ValueError, 3652 audiotools.transfer_framelist_data, 3653 decoders, lambda x: x) 3654 except IOError: 3655 # Randomly swapping bits may send the decoder 3656 # off the end of the stream before triggering 3657 # a CRC-16 error. 3658 # We simply need to catch that case and continue 3659 continue 3660 3661 # test a FLAC file with an invalid STREAMINFO block 3662 mismatch_streaminfos = [ 3663 (4096, 4096, 12, 12, 44101, 0, 15, 80, 3664 b'\xf5?\x86\x87m\xcdw\x83"\\\x93\xba\x8a\x93\x8c}'), 3665 (4096, 4096, 12, 12, 44100, 1, 15, 80, 3666 b'\xf5?\x86\x87m\xcdw\x83"\\\x93\xba\x8a\x93\x8c}'), 3667 (4096, 4096, 12, 12, 44100, 0, 7, 80, 3668 b'\xf5?\x86\x87m\xcdw\x83"\\\x93\xba\x8a\x93\x8c}'), 3669 (4096, 1, 12, 12, 44100, 0, 15, 80, 3670 b'\xf5?\x86\x87m\xcdw\x83"\\\x93\xba\x8a\x93\x8c}'), 3671 (4096, 4096, 12, 12, 44100, 0, 15, 80, 3672 b'\xf5?\x86\x87m\xcdw\x83"\\\x93\xba\x8a\x93\x8d}')] 3673 3674 header = flac_data[0:8] 3675 data = flac_data[0x2A:] 3676 3677 from audiotools.bitstream import BitstreamWriter 3678 3679 for streaminfo in mismatch_streaminfos: 3680 with tempfile.NamedTemporaryFile(suffix=".flac") as temp: 3681 temp.seek(0, 0) 3682 temp.write(ints_to_bytes(header)) 3683 BitstreamWriter(temp.file, False).build( 3684 "16u 16u 24u 24u 20u 3u 5u 36U 16b", 3685 streaminfo) 3686 temp.write(ints_to_bytes(data)) 3687 temp.flush() 3688 with audiotools.open(temp.name).to_pcm() as decoders: 3689 self.assertRaises(ValueError, 3690 audiotools.transfer_framelist_data, 3691 decoders, lambda x: x) 3692 3693 # test that convert() from an invalid file also raises an exception 3694 with tempfile.NamedTemporaryFile(suffix=".flac") as temp: 3695 temp.write(ints_to_bytes(flac_data[0:-10])) 3696 temp.flush() 3697 flac = audiotools.open(temp.name) 3698 if os.path.isfile("dummy.wav"): 3699 os.unlink("dummy.wav") 3700 self.assertEqual(os.path.isfile("dummy.wav"), False) 3701 self.assertRaises(audiotools.EncodingError, 3702 flac.convert, 3703 "dummy.wav", 3704 audiotools.WaveAudio) 3705 self.assertEqual(os.path.isfile("dummy.wav"), False) 3706 3707 def __stream_variations__(self): 3708 for stream in [ 3709 test_streams.Silence8_Mono(200000, 44100), 3710 test_streams.Silence8_Mono(200000, 96000), 3711 test_streams.Silence8_Stereo(200000, 44100), 3712 test_streams.Silence8_Stereo(200000, 96000), 3713 test_streams.Silence16_Mono(200000, 44100), 3714 test_streams.Silence16_Mono(200000, 96000), 3715 test_streams.Silence16_Stereo(200000, 44100), 3716 test_streams.Silence16_Stereo(200000, 96000), 3717 test_streams.Silence24_Mono(200000, 44100), 3718 test_streams.Silence24_Mono(200000, 96000), 3719 test_streams.Silence24_Stereo(200000, 44100), 3720 test_streams.Silence24_Stereo(200000, 96000), 3721 3722 test_streams.Sine8_Mono(200000, 48000, 441.0, 0.50, 441.0, 0.49), 3723 test_streams.Sine8_Mono(200000, 96000, 441.0, 0.61, 661.5, 0.37), 3724 test_streams.Sine8_Mono(200000, 44100, 441.0, 0.50, 882.0, 0.49), 3725 test_streams.Sine8_Mono(200000, 44100, 441.0, 0.50, 4410.0, 0.49), 3726 test_streams.Sine8_Mono(200000, 44100, 8820.0, 0.70, 4410.0, 0.29), 3727 3728 test_streams.Sine8_Stereo(200000, 48000, 441.0, 0.50, 441.0, 0.49, 1.0), 3729 test_streams.Sine8_Stereo(200000, 48000, 441.0, 0.61, 661.5, 0.37, 1.0), 3730 test_streams.Sine8_Stereo(200000, 96000, 441.0, 0.50, 882.0, 0.49, 1.0), 3731 test_streams.Sine8_Stereo(200000, 44100, 441.0, 0.50, 4410.0, 0.49, 1.0), 3732 test_streams.Sine8_Stereo(200000, 44100, 8820.0, 0.70, 4410.0, 0.29, 1.0), 3733 test_streams.Sine8_Stereo(200000, 44100, 441.0, 0.50, 441.0, 0.49, 0.5), 3734 test_streams.Sine8_Stereo(200000, 44100, 441.0, 0.61, 661.5, 0.37, 2.0), 3735 test_streams.Sine8_Stereo(200000, 44100, 441.0, 0.50, 882.0, 0.49, 0.7), 3736 test_streams.Sine8_Stereo(200000, 44100, 441.0, 0.50, 4410.0, 0.49, 1.3), 3737 test_streams.Sine8_Stereo(200000, 44100, 8820.0, 0.70, 4410.0, 0.29, 0.1), 3738 3739 test_streams.Sine16_Mono(200000, 48000, 441.0, 0.50, 441.0, 0.49), 3740 test_streams.Sine16_Mono(200000, 96000, 441.0, 0.61, 661.5, 0.37), 3741 test_streams.Sine16_Mono(200000, 44100, 441.0, 0.50, 882.0, 0.49), 3742 test_streams.Sine16_Mono(200000, 44100, 441.0, 0.50, 4410.0, 0.49), 3743 test_streams.Sine16_Mono(200000, 44100, 8820.0, 0.70, 4410.0, 0.29), 3744 3745 test_streams.Sine16_Stereo(200000, 48000, 441.0, 0.50, 441.0, 0.49, 1.0), 3746 test_streams.Sine16_Stereo(200000, 48000, 441.0, 0.61, 661.5, 0.37, 1.0), 3747 test_streams.Sine16_Stereo(200000, 96000, 441.0, 0.50, 882.0, 0.49, 1.0), 3748 test_streams.Sine16_Stereo(200000, 44100, 441.0, 0.50, 4410.0, 0.49, 1.0), 3749 test_streams.Sine16_Stereo(200000, 44100, 8820.0, 0.70, 4410.0, 0.29, 1.0), 3750 test_streams.Sine16_Stereo(200000, 44100, 441.0, 0.50, 441.0, 0.49, 0.5), 3751 test_streams.Sine16_Stereo(200000, 44100, 441.0, 0.61, 661.5, 0.37, 2.0), 3752 test_streams.Sine16_Stereo(200000, 44100, 441.0, 0.50, 882.0, 0.49, 0.7), 3753 test_streams.Sine16_Stereo(200000, 44100, 441.0, 0.50, 4410.0, 0.49, 1.3), 3754 test_streams.Sine16_Stereo(200000, 44100, 8820.0, 0.70, 4410.0, 0.29, 0.1), 3755 3756 test_streams.Sine24_Mono(200000, 48000, 441.0, 0.50, 441.0, 0.49), 3757 test_streams.Sine24_Mono(200000, 96000, 441.0, 0.61, 661.5, 0.37), 3758 test_streams.Sine24_Mono(200000, 44100, 441.0, 0.50, 882.0, 0.49), 3759 test_streams.Sine24_Mono(200000, 44100, 441.0, 0.50, 4410.0, 0.49), 3760 test_streams.Sine24_Mono(200000, 44100, 8820.0, 0.70, 4410.0, 0.29), 3761 3762 test_streams.Sine24_Stereo(200000, 48000, 441.0, 0.50, 441.0, 0.49, 1.0), 3763 test_streams.Sine24_Stereo(200000, 48000, 441.0, 0.61, 661.5, 0.37, 1.0), 3764 test_streams.Sine24_Stereo(200000, 96000, 441.0, 0.50, 882.0, 0.49, 1.0), 3765 test_streams.Sine24_Stereo(200000, 44100, 441.0, 0.50, 4410.0, 0.49, 1.0), 3766 test_streams.Sine24_Stereo(200000, 44100, 8820.0, 0.70, 4410.0, 0.29, 1.0), 3767 test_streams.Sine24_Stereo(200000, 44100, 441.0, 0.50, 441.0, 0.49, 0.5), 3768 test_streams.Sine24_Stereo(200000, 44100, 441.0, 0.61, 661.5, 0.37, 2.0), 3769 test_streams.Sine24_Stereo(200000, 44100, 441.0, 0.50, 882.0, 0.49, 0.7), 3770 test_streams.Sine24_Stereo(200000, 44100, 441.0, 0.50, 4410.0, 0.49, 1.3), 3771 test_streams.Sine24_Stereo(200000, 44100, 8820.0, 0.70, 4410.0, 0.29, 0.1), 3772 3773 test_streams.Simple_Sine(200000, 44100, 0x7, 8, 3774 (25, 10000), 3775 (50, 20000), 3776 (120, 30000)), 3777 test_streams.Simple_Sine(200000, 44100, 0x33, 8, 3778 (25, 10000), 3779 (50, 20000), 3780 (75, 30000), 3781 (65, 40000)), 3782 test_streams.Simple_Sine(200000, 44100, 0x37, 8, 3783 (25, 10000), 3784 (35, 15000), 3785 (45, 20000), 3786 (50, 25000), 3787 (55, 30000)), 3788 test_streams.Simple_Sine(200000, 44100, 0x3F, 8, 3789 (25, 10000), 3790 (45, 15000), 3791 (65, 20000), 3792 (85, 25000), 3793 (105, 30000), 3794 (120, 35000)), 3795 3796 test_streams.Simple_Sine(200000, 44100, 0x7, 16, 3797 (6400, 10000), 3798 (12800, 20000), 3799 (30720, 30000)), 3800 test_streams.Simple_Sine(200000, 44100, 0x33, 16, 3801 (6400, 10000), 3802 (12800, 20000), 3803 (19200, 30000), 3804 (16640, 40000)), 3805 test_streams.Simple_Sine(200000, 44100, 0x37, 16, 3806 (6400, 10000), 3807 (8960, 15000), 3808 (11520, 20000), 3809 (12800, 25000), 3810 (14080, 30000)), 3811 test_streams.Simple_Sine(200000, 44100, 0x3F, 16, 3812 (6400, 10000), 3813 (11520, 15000), 3814 (16640, 20000), 3815 (21760, 25000), 3816 (26880, 30000), 3817 (30720, 35000)), 3818 3819 test_streams.Simple_Sine(200000, 44100, 0x7, 24, 3820 (1638400, 10000), 3821 (3276800, 20000), 3822 (7864320, 30000)), 3823 test_streams.Simple_Sine(200000, 44100, 0x33, 24, 3824 (1638400, 10000), 3825 (3276800, 20000), 3826 (4915200, 30000), 3827 (4259840, 40000)), 3828 test_streams.Simple_Sine(200000, 44100, 0x37, 24, 3829 (1638400, 10000), 3830 (2293760, 15000), 3831 (2949120, 20000), 3832 (3276800, 25000), 3833 (3604480, 30000)), 3834 test_streams.Simple_Sine(200000, 44100, 0x3F, 24, 3835 (1638400, 10000), 3836 (2949120, 15000), 3837 (4259840, 20000), 3838 (5570560, 25000), 3839 (6881280, 30000), 3840 (7864320, 35000))]: 3841 yield stream 3842 3843 @FORMAT_FLAC 3844 def test_streams(self): 3845 for g in self.__stream_variations__(): 3846 md5sum = md5() 3847 f = g.read(audiotools.FRAMELIST_SIZE) 3848 while len(f) > 0: 3849 md5sum.update(f.to_bytes(False, True)) 3850 f = g.read(audiotools.FRAMELIST_SIZE) 3851 self.assertEqual(md5sum.digest(), g.digest()) 3852 g.close() 3853 3854 def __test_reader__(self, pcmreader, **encode_options): 3855 if not audiotools.BIN.can_execute(audiotools.BIN["flac"]): 3856 self.assertTrue( 3857 False, 3858 "reference FLAC binary flac(1) required for this test") 3859 3860 temp_file = tempfile.NamedTemporaryFile(suffix=".flac") 3861 self.encode(temp_file.name, 3862 audiotools.BufferedPCMReader(pcmreader), 3863 **encode_options) 3864 3865 self.assertEqual( 3866 subprocess.call([audiotools.BIN["flac"], "-ts", temp_file.name]), 3867 0, 3868 "flac decode error on %s with options %s" % 3869 (repr(pcmreader), 3870 repr(encode_options))) 3871 3872 flac = audiotools.open(temp_file.name) 3873 self.assertGreater(flac.total_frames(), 0) 3874 if hasattr(pcmreader, "digest"): 3875 self.assertEqual(flac.__md5__, pcmreader.digest()) 3876 3877 # check FlacDecoder using raw file 3878 md5sum = md5() 3879 d = self.decoder(open(temp_file.name, "rb")) 3880 f = d.read(audiotools.FRAMELIST_SIZE) 3881 while len(f) > 0: 3882 md5sum.update(f.to_bytes(False, True)) 3883 f = d.read(audiotools.FRAMELIST_SIZE) 3884 d.close() 3885 self.assertEqual(md5sum.digest(), pcmreader.digest()) 3886 3887 # check FlacDecoder using file-like wrapper 3888 md5sum = md5() 3889 d = self.decoder(Filewrapper(open(temp_file.name, "rb"))) 3890 f = d.read(audiotools.FRAMELIST_SIZE) 3891 while len(f) > 0: 3892 md5sum.update(f.to_bytes(False, True)) 3893 f = d.read(audiotools.FRAMELIST_SIZE) 3894 d.close() 3895 self.assertEqual(md5sum.digest(), pcmreader.digest()) 3896 3897 temp_file.close() 3898 3899 @FORMAT_FLAC 3900 def test_small_files(self): 3901 for g in [test_streams.Generate01, 3902 test_streams.Generate02, 3903 test_streams.Generate03, 3904 test_streams.Generate04]: 3905 self.__test_reader__(g(44100), 3906 block_size=1152, 3907 max_lpc_order=16, 3908 min_residual_partition_order=0, 3909 max_residual_partition_order=3, 3910 mid_side=True, 3911 adaptive_mid_side=True, 3912 exhaustive_model_search=True) 3913 3914 @FORMAT_FLAC 3915 def test_full_scale_deflection(self): 3916 for (bps, fsd) in [(8, test_streams.fsd8), 3917 (16, test_streams.fsd16), 3918 (24, test_streams.fsd24)]: 3919 for pattern in [test_streams.PATTERN01, 3920 test_streams.PATTERN02, 3921 test_streams.PATTERN03, 3922 test_streams.PATTERN04, 3923 test_streams.PATTERN05, 3924 test_streams.PATTERN06, 3925 test_streams.PATTERN07]: 3926 self.__test_reader__( 3927 test_streams.MD5Reader(fsd(pattern, 100)), 3928 block_size=1152, 3929 max_lpc_order=16, 3930 min_residual_partition_order=0, 3931 max_residual_partition_order=3, 3932 mid_side=True, 3933 adaptive_mid_side=True, 3934 exhaustive_model_search=True) 3935 3936 @FORMAT_FLAC 3937 def test_sines(self): 3938 import sys 3939 3940 for g in self.__stream_variations__(): 3941 self.__test_reader__(g, 3942 block_size=1152, 3943 max_lpc_order=16, 3944 min_residual_partition_order=0, 3945 max_residual_partition_order=3, 3946 mid_side=True, 3947 adaptive_mid_side=True, 3948 exhaustive_model_search=True) 3949 3950 @FORMAT_FLAC 3951 def test_wasted_bps(self): 3952 self.__test_reader__(test_streams.WastedBPS16(1000), 3953 block_size=1152, 3954 max_lpc_order=16, 3955 min_residual_partition_order=0, 3956 max_residual_partition_order=3, 3957 mid_side=True, 3958 adaptive_mid_side=True, 3959 exhaustive_model_search=True) 3960 3961 @FORMAT_FLAC 3962 def test_blocksizes(self): 3963 # FIXME - handle 8bps/24bps also 3964 noise = struct.unpack(">32h", os.urandom(64)) 3965 3966 encoding_args = {"min_residual_partition_order": 0, 3967 "max_residual_partition_order": 6, 3968 "mid_side": True, 3969 "adaptive_mid_side": True, 3970 "exhaustive_model_search": True} 3971 for to_disable in [[], 3972 ["disable_verbatim_subframes", 3973 "disable_constant_subframes"], 3974 ["disable_verbatim_subframes", 3975 "disable_constant_subframes", 3976 "disable_fixed_subframes"]]: 3977 for block_size in [16, 17, 18, 19, 20, 21, 22, 23, 3978 24, 25, 26, 27, 28, 29, 30, 31, 32, 33]: 3979 for lpc_order in [0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 3980 31, 32]: 3981 args = encoding_args.copy() 3982 for disable in to_disable: 3983 args[disable] = True 3984 args["block_size"] = block_size 3985 args["max_lpc_order"] = lpc_order 3986 self.__test_reader__( 3987 test_streams.MD5Reader( 3988 test_streams.FrameListReader(noise, 3989 44100, 1, 16)), 3990 **args) 3991 3992 @FORMAT_FLAC 3993 def test_frame_header_variations(self): 3994 max_lpc_order = 16 3995 3996 self.__test_reader__(test_streams.Sine16_Mono(200000, 96000, 3997 441.0, 0.61, 661.5, 0.37), 3998 block_size=max_lpc_order, 3999 max_lpc_order=max_lpc_order, 4000 min_residual_partition_order=0, 4001 max_residual_partition_order=3, 4002 mid_side=True, 4003 adaptive_mid_side=True, 4004 exhaustive_model_search=True) 4005 4006 self.__test_reader__(test_streams.Sine16_Mono(200000, 96000, 4007 441.0, 0.61, 661.5, 0.37), 4008 block_size=65535, 4009 max_lpc_order=max_lpc_order, 4010 min_residual_partition_order=0, 4011 max_residual_partition_order=3, 4012 mid_side=True, 4013 adaptive_mid_side=True, 4014 exhaustive_model_search=True) 4015 4016 self.__test_reader__(test_streams.Sine16_Mono(200000, 9, 4017 441.0, 0.61, 661.5, 0.37), 4018 block_size=1152, 4019 max_lpc_order=max_lpc_order, 4020 min_residual_partition_order=0, 4021 max_residual_partition_order=3, 4022 mid_side=True, 4023 adaptive_mid_side=True, 4024 exhaustive_model_search=True) 4025 4026 self.__test_reader__(test_streams.Sine16_Mono(200000, 90, 4027 441.0, 0.61, 661.5, 0.37), 4028 block_size=1152, 4029 max_lpc_order=max_lpc_order, 4030 min_residual_partition_order=0, 4031 max_residual_partition_order=3, 4032 mid_side=True, 4033 adaptive_mid_side=True, 4034 exhaustive_model_search=True) 4035 4036 self.__test_reader__(test_streams.Sine16_Mono(200000, 90000, 4037 441.0, 0.61, 661.5, 0.37), 4038 block_size=1152, 4039 max_lpc_order=max_lpc_order, 4040 min_residual_partition_order=0, 4041 max_residual_partition_order=3, 4042 mid_side=True, 4043 adaptive_mid_side=True, 4044 exhaustive_model_search=True) 4045 4046 # the reference encoder's test_streams.sh unit test 4047 # re-does the 9Hz/90Hz/90000Hz tests for some reason 4048 # which I won't repeat here 4049 4050 @FORMAT_FLAC 4051 def test_option_variations(self): 4052 # testing all the option variations 4053 # against all the stream variations 4054 # along with a few extra option variations 4055 # takes a *long* time - so don't panic 4056 4057 for opts in self.encode_opts: 4058 encode_opts = opts.copy() 4059 for disable in [[], 4060 ["disable_verbatim_subframes", 4061 "disable_constant_subframes"], 4062 ["disable_verbatim_subframes", 4063 "disable_constant_subframes", 4064 "disable_fixed_subframes"]]: 4065 for extra in [[], 4066 # FIXME - no analogue for -p option 4067 ["exhaustive_model_search"]]: 4068 for d in disable: 4069 encode_opts[d] = True 4070 for e in extra: 4071 encode_opts[e] = True 4072 for g in self.__stream_variations__(): 4073 self.__test_reader__(g, **encode_opts) 4074 4075 @FORMAT_FLAC 4076 def test_noise_silence(self): 4077 for opts in self.encode_opts: 4078 encode_opts = opts.copy() 4079 for disable in [[], 4080 ["disable_verbatim_subframes", 4081 "disable_constant_subframes"], 4082 ["disable_verbatim_subframes", 4083 "disable_constant_subframes", 4084 "disable_fixed_subframes"]]: 4085 for (channels, mask) in [ 4086 (1, audiotools.ChannelMask.from_channels(1)), 4087 (2, audiotools.ChannelMask.from_channels(2)), 4088 (4, audiotools.ChannelMask.from_fields(front_left=True, 4089 front_right=True, 4090 back_left=True, 4091 back_right=True)), 4092 (8, audiotools.ChannelMask(0))]: 4093 for bps in [8, 16, 24]: 4094 for extra in [[], 4095 # FIXME - no analogue for -p option 4096 ["exhaustive_model_search"]]: 4097 for blocksize in [None, 32, 32768, 65535]: 4098 for d in disable: 4099 encode_opts[d] = True 4100 for e in extra: 4101 encode_opts[e] = True 4102 if blocksize is not None: 4103 encode_opts["block_size"] = blocksize 4104 4105 self.__test_reader__( 4106 MD5_Reader( 4107 EXACT_RANDOM_PCM_Reader( 4108 pcm_frames=65536, 4109 sample_rate=44100, 4110 channels=channels, 4111 channel_mask=mask, 4112 bits_per_sample=bps)), 4113 **encode_opts) 4114 4115 self.__test_reader__( 4116 MD5_Reader( 4117 EXACT_SILENCE_PCM_Reader( 4118 pcm_frames=65536, 4119 sample_rate=44100, 4120 channels=channels, 4121 channel_mask=mask, 4122 bits_per_sample=bps)), 4123 **encode_opts) 4124 4125 @FORMAT_FLAC 4126 def test_fractional(self): 4127 def __perform_test__(block_size, pcm_frames): 4128 self.__test_reader__( 4129 MD5_Reader( 4130 EXACT_RANDOM_PCM_Reader( 4131 pcm_frames=pcm_frames, 4132 sample_rate=44100, 4133 channels=2, 4134 bits_per_sample=16)), 4135 block_size=block_size, 4136 max_lpc_order=8, 4137 min_residual_partition_order=0, 4138 max_residual_partition_order=6) 4139 4140 for pcm_frames in [31, 32, 33, 34, 35, 2046, 2047, 2048, 2049, 2050]: 4141 __perform_test__(33, pcm_frames) 4142 4143 for pcm_frames in [254, 255, 256, 257, 258, 510, 511, 512, 513, 4144 514, 1022, 1023, 1024, 1025, 1026, 2046, 2047, 4145 2048, 2049, 2050, 4094, 4095, 4096, 4097, 4098]: 4146 __perform_test__(256, pcm_frames) 4147 4148 for pcm_frames in [1022, 1023, 1024, 1025, 1026, 2046, 2047, 4149 2048, 2049, 2050, 4094, 4095, 4096, 4097, 4098]: 4150 __perform_test__(2048, pcm_frames) 4151 4152 for pcm_frames in [1022, 1023, 1024, 1025, 1026, 2046, 2047, 4153 2048, 2049, 2050, 4094, 4095, 4096, 4097, 4154 4098, 4606, 4607, 4608, 4609, 4610, 8190, 4155 8191, 8192, 8193, 8194, 16382, 16383, 16384, 4156 16385, 16386]: 4157 __perform_test__(4608, pcm_frames) 4158 4159 # PCMReaders don't yet support seeking, 4160 # so the seek tests can be skipped 4161 4162 # cuesheets are supported at the metadata level, 4163 # which is tested above 4164 4165 # WAVE and AIFF length fixups are handled by the 4166 # WaveAudio and AIFFAudio classes 4167 4168 # multiple file handling is performed at the tool level 4169 4170 # as is metadata handling 4171 4172 @FORMAT_FLAC 4173 def test_clean(self): 4174 # metadata is tested separately 4175 4176 from audiotools.text import (CLEAN_FLAC_REMOVE_ID3V2, 4177 CLEAN_FLAC_REMOVE_ID3V1, 4178 CLEAN_FLAC_REORDERED_STREAMINFO, 4179 CLEAN_FLAC_POPULATE_MD5, 4180 CLEAN_FLAC_ADD_CHANNELMASK, 4181 CLEAN_FLAC_FIX_SEEKTABLE, 4182 CLEAN_FLAC_ADD_SEEKTABLE) 4183 4184 # check FLAC files with ID3 tags 4185 with open("flac-id3.flac", "rb") as f: 4186 self.assertEqual(f.read(3), b"ID3") 4187 track = audiotools.open("flac-id3.flac") 4188 metadata1 = track.get_metadata() 4189 fixes = track.clean() 4190 self.assertEqual(fixes, 4191 [CLEAN_FLAC_REMOVE_ID3V2, 4192 CLEAN_FLAC_REMOVE_ID3V1]) 4193 with tempfile.NamedTemporaryFile(suffix=".flac") as temp: 4194 fixes = track.clean(temp.name) 4195 self.assertEqual(fixes, 4196 [CLEAN_FLAC_REMOVE_ID3V2, 4197 CLEAN_FLAC_REMOVE_ID3V1]) 4198 with open(temp.name, "rb") as f: 4199 self.assertEqual(f.read(4), b"fLaC") 4200 track2 = audiotools.open(temp.name) 4201 self.assertEqual(metadata1, track2.get_metadata()) 4202 self.assertTrue( 4203 audiotools.pcm_cmp(track.to_pcm(), track2.to_pcm())) 4204 4205 # check FLAC files with double ID3 tags 4206 f = open("flac-id3-2.flac", "rb") 4207 self.assertEqual(f.read(3), b"ID3") 4208 f.close() 4209 track = audiotools.open("flac-id3-2.flac") 4210 metadata1 = track.get_metadata() 4211 fixes = track.clean() 4212 self.assertEqual(fixes, 4213 [CLEAN_FLAC_REMOVE_ID3V2]) 4214 with tempfile.NamedTemporaryFile(suffix=".flac") as temp: 4215 fixes = track.clean(temp.name) 4216 self.assertEqual(fixes, 4217 [CLEAN_FLAC_REMOVE_ID3V2]) 4218 with open(temp.name, "rb") as f: 4219 self.assertEqual(f.read(4), b"fLaC") 4220 track2 = audiotools.open(temp.name) 4221 self.assertEqual(metadata1, track2.get_metadata()) 4222 self.assertTrue( 4223 audiotools.pcm_cmp(track.to_pcm(), track2.to_pcm())) 4224 4225 # check FLAC files with STREAMINFO in the wrong location 4226 with open("flac-disordered.flac", "rb") as f: 4227 self.assertEqual(f.read(5), b"fLaC\x04") 4228 track = audiotools.open("flac-disordered.flac") 4229 metadata1 = track.get_metadata() 4230 fixes = track.clean() 4231 self.assertEqual(fixes, 4232 [CLEAN_FLAC_REORDERED_STREAMINFO]) 4233 with tempfile.NamedTemporaryFile(suffix=".flac") as temp: 4234 fixes = track.clean(temp.name) 4235 self.assertEqual(fixes, 4236 [CLEAN_FLAC_REORDERED_STREAMINFO]) 4237 with open(temp.name, "rb") as f: 4238 self.assertEqual(f.read(5), b"fLaC\x00") 4239 track2 = audiotools.open(temp.name) 4240 self.assertEqual(metadata1, track2.get_metadata()) 4241 self.assertTrue( 4242 audiotools.pcm_cmp(track.to_pcm(), track2.to_pcm())) 4243 4244 # check FLAC files with empty MD5 sum 4245 track = audiotools.open("flac-nonmd5.flac") 4246 fixes = [] 4247 self.assertEqual( 4248 track.get_metadata().get_block( 4249 audiotools.flac.Flac_STREAMINFO.BLOCK_ID).md5sum, b"\x00" * 16) 4250 fixes = track.clean() 4251 self.assertEqual(fixes, [CLEAN_FLAC_POPULATE_MD5]) 4252 with tempfile.NamedTemporaryFile(suffix=".flac") as temp: 4253 fixes = track.clean(temp.name) 4254 self.assertEqual(fixes, [CLEAN_FLAC_POPULATE_MD5]) 4255 track2 = audiotools.open(temp.name) 4256 self.assertEqual( 4257 track2.get_metadata().get_block( 4258 audiotools.flac.Flac_STREAMINFO.BLOCK_ID).md5sum, 4259 b'\xd2\xb1 \x19\x90\x19\xb69' + 4260 b'\xd5\xa7\xe2\xb3F>\x9c\x97') 4261 self.assertTrue( 4262 audiotools.pcm_cmp(track.to_pcm(), track2.to_pcm())) 4263 4264 # check FLAC files with no SEEKTABLE 4265 track = audiotools.open("flac-noseektable.flac") 4266 fixed = [] 4267 self.assertFalse( 4268 track.get_metadata().has_block( 4269 audiotools.flac.Flac_SEEKTABLE.BLOCK_ID)) 4270 fixes = track.clean() 4271 self.assertEqual(fixes, [CLEAN_FLAC_ADD_SEEKTABLE]) 4272 with tempfile.NamedTemporaryFile(suffix=".flac") as temp: 4273 fixes = track.clean(temp.name) 4274 self.assertEqual(fixes, [CLEAN_FLAC_ADD_SEEKTABLE]) 4275 track2 = audiotools.open(temp.name) 4276 self.assertTrue( 4277 track2.get_metadata().has_block( 4278 audiotools.flac.Flac_SEEKTABLE.BLOCK_ID)) 4279 self.assertTrue( 4280 audiotools.pcm_cmp(track.to_pcm(), track2.to_pcm())) 4281 4282 # check 24bps/6ch FLAC files without WAVEFORMATEXTENSIBLE_CHANNEL_MASK 4283 for (path, mask) in [("flac-nomask1.flac", 0x3F), 4284 ("flac-nomask2.flac", 0x3F), 4285 ("flac-nomask3.flac", 0x3), 4286 ("flac-nomask4.flac", 0x3)]: 4287 with tempfile.NamedTemporaryFile(suffix=".flac") as no_blocks_file: 4288 with open(path, "rb") as f: 4289 no_blocks_file.write(f.read()) 4290 no_blocks_file.flush() 4291 track = audiotools.open(no_blocks_file.name) 4292 metadata = track.get_metadata() 4293 for block_id in [1, 2, 4, 5, 6]: 4294 metadata.replace_blocks(block_id, []) 4295 track.update_metadata(metadata) 4296 4297 for track in [audiotools.open(path), 4298 audiotools.open(no_blocks_file.name)]: 4299 fixes = track.clean() 4300 self.assertEqual(fixes, [CLEAN_FLAC_ADD_CHANNELMASK]) 4301 4302 with tempfile.NamedTemporaryFile(suffix=".flac") as temp: 4303 fixes = track.clean(temp.name) 4304 self.assertEqual( 4305 fixes, 4306 [CLEAN_FLAC_ADD_CHANNELMASK]) 4307 new_track = audiotools.open(temp.name) 4308 self.assertEqual(new_track.channel_mask(), 4309 track.channel_mask()) 4310 self.assertEqual(int(new_track.channel_mask()), mask) 4311 metadata = new_track.get_metadata() 4312 4313 self.assertEqual( 4314 metadata.get_block( 4315 audiotools.flac.Flac_VORBISCOMMENT.BLOCK_ID)[ 4316 u"WAVEFORMATEXTENSIBLE_CHANNEL_MASK"][0], 4317 u"0x%.4X" % (mask)) 4318 4319 # check bad seekpoint destinations 4320 track = audiotools.open("flac-seektable.flac") 4321 fixes = track.clean() 4322 self.assertEqual(fixes, [CLEAN_FLAC_FIX_SEEKTABLE]) 4323 with tempfile.NamedTemporaryFile(suffix=".flac") as temp: 4324 fixes = track.clean(temp.name) 4325 self.assertEqual( 4326 fixes, 4327 [CLEAN_FLAC_FIX_SEEKTABLE]) 4328 new_track = audiotools.open(temp.name) 4329 fixes = new_track.clean() 4330 self.assertEqual(fixes, []) 4331 4332 @FORMAT_FLAC 4333 def test_nonmd5(self): 4334 flac = audiotools.open("flac-nonmd5.flac") 4335 self.assertEqual(flac.__md5__, b"\x00" * 16) 4336 md5sum = md5() 4337 4338 # ensure that a FLAC file with an empty MD5 sum 4339 # decodes without errors 4340 audiotools.transfer_framelist_data(flac.to_pcm(), 4341 md5sum.update) 4342 self.assertEqual(md5sum.hexdigest(), 4343 'd2b120199019b639d5a7e2b3463e9c97') 4344 4345 # ensure that a FLAC file with an empty MD5 sum 4346 # verifies without errors 4347 self.assertEqual(flac.verify(), True) 4348 4349 @FORMAT_FLAC 4350 def test_python_codec(self): 4351 # Python decoder and encoder are far too slow 4352 # to run anything resembling a complete set of tests 4353 # so we'll cut them down to the very basics 4354 4355 def test_python_reader(pcmreader, **encode_options): 4356 from audiotools.py_encoders import encode_flac 4357 4358 # encode file using Python-based encoder 4359 temp_file = tempfile.NamedTemporaryFile(suffix=".flac") 4360 encode_flac(temp_file.name, 4361 audiotools.BufferedPCMReader(pcmreader), 4362 **encode_options) 4363 4364 # verify contents of file decoded by 4365 # Python-based decoder against contents decoded by 4366 # C-based decoder 4367 from audiotools.py_decoders import FlacDecoder as FlacDecoder1 4368 from audiotools.decoders import FlacDecoder as FlacDecoder2 4369 4370 self.assertTrue( 4371 audiotools.pcm_cmp( 4372 FlacDecoder1(temp_file.name, 0), 4373 FlacDecoder2(open(temp_file.name, "rb")))) 4374 4375 temp_file.close() 4376 4377 # test small files 4378 for g in [test_streams.Generate01, 4379 test_streams.Generate02, 4380 test_streams.Generate03, 4381 test_streams.Generate04]: 4382 test_python_reader(g(44100), 4383 block_size=1152, 4384 max_lpc_order=16, 4385 min_residual_partition_order=0, 4386 max_residual_partition_order=3, 4387 mid_side=True, 4388 adaptive_mid_side=True, 4389 exhaustive_model_search=True) 4390 4391 # test full-scale deflection 4392 for (bps, fsd) in [(8, test_streams.fsd8), 4393 (16, test_streams.fsd16), 4394 (24, test_streams.fsd24)]: 4395 for pattern in [test_streams.PATTERN01, 4396 test_streams.PATTERN02, 4397 test_streams.PATTERN03, 4398 test_streams.PATTERN04, 4399 test_streams.PATTERN05, 4400 test_streams.PATTERN06, 4401 test_streams.PATTERN07]: 4402 test_python_reader( 4403 fsd(pattern, 100), 4404 block_size=1152, 4405 max_lpc_order=16, 4406 min_residual_partition_order=0, 4407 max_residual_partition_order=3, 4408 mid_side=True, 4409 adaptive_mid_side=True, 4410 exhaustive_model_search=True) 4411 4412 # test silence 4413 for g in [test_streams.Silence8_Mono(5000, 48000), 4414 test_streams.Silence8_Stereo(5000, 48000), 4415 test_streams.Silence16_Mono(5000, 48000), 4416 test_streams.Silence16_Stereo(5000, 48000), 4417 test_streams.Silence24_Mono(5000, 48000), 4418 test_streams.Silence24_Stereo(5000, 48000)]: 4419 test_python_reader(g, 4420 block_size=1152, 4421 max_lpc_order=16, 4422 min_residual_partition_order=0, 4423 max_residual_partition_order=3, 4424 mid_side=True, 4425 adaptive_mid_side=True, 4426 exhaustive_model_search=True) 4427 4428 # test sines 4429 for g in [test_streams.Sine8_Mono(5000, 48000, 4430 441.0, 0.50, 441.0, 0.49), 4431 test_streams.Sine8_Stereo(5000, 48000, 4432 441.0, 0.50, 441.0, 0.49, 1.0), 4433 test_streams.Sine16_Mono(5000, 48000, 4434 441.0, 0.50, 441.0, 0.49), 4435 test_streams.Sine16_Stereo(5000, 48000, 4436 441.0, 0.50, 441.0, 0.49, 1.0), 4437 test_streams.Sine24_Mono(5000, 48000, 4438 441.0, 0.50, 441.0, 0.49), 4439 test_streams.Sine24_Stereo(5000, 48000, 4440 441.0, 0.50, 441.0, 0.49, 1.0), 4441 test_streams.Simple_Sine(5000, 44100, 0x7, 8, 4442 (25, 10000), 4443 (50, 20000), 4444 (120, 30000)), 4445 test_streams.Simple_Sine(5000, 44100, 0x33, 8, 4446 (25, 10000), 4447 (50, 20000), 4448 (75, 30000), 4449 (65, 40000)), 4450 test_streams.Simple_Sine(5000, 44100, 0x37, 8, 4451 (25, 10000), 4452 (35, 15000), 4453 (45, 20000), 4454 (50, 25000), 4455 (55, 30000)), 4456 test_streams.Simple_Sine(5000, 44100, 0x3F, 8, 4457 (25, 10000), 4458 (45, 15000), 4459 (65, 20000), 4460 (85, 25000), 4461 (105, 30000), 4462 (120, 35000)), 4463 test_streams.Simple_Sine(5000, 44100, 0x7, 16, 4464 (6400, 10000), 4465 (12800, 20000), 4466 (30720, 30000)), 4467 test_streams.Simple_Sine(5000, 44100, 0x33, 16, 4468 (6400, 10000), 4469 (12800, 20000), 4470 (19200, 30000), 4471 (16640, 40000)), 4472 test_streams.Simple_Sine(5000, 44100, 0x37, 16, 4473 (6400, 10000), 4474 (8960, 15000), 4475 (11520, 20000), 4476 (12800, 25000), 4477 (14080, 30000)), 4478 test_streams.Simple_Sine(5000, 44100, 0x3F, 16, 4479 (6400, 10000), 4480 (11520, 15000), 4481 (16640, 20000), 4482 (21760, 25000), 4483 (26880, 30000), 4484 (30720, 35000)), 4485 test_streams.Simple_Sine(5000, 44100, 0x7, 24, 4486 (1638400, 10000), 4487 (3276800, 20000), 4488 (7864320, 30000)), 4489 test_streams.Simple_Sine(5000, 44100, 0x33, 24, 4490 (1638400, 10000), 4491 (3276800, 20000), 4492 (4915200, 30000), 4493 (4259840, 40000)), 4494 test_streams.Simple_Sine(5000, 44100, 0x37, 24, 4495 (1638400, 10000), 4496 (2293760, 15000), 4497 (2949120, 20000), 4498 (3276800, 25000), 4499 (3604480, 30000)), 4500 test_streams.Simple_Sine(5000, 44100, 0x3F, 24, 4501 (1638400, 10000), 4502 (2949120, 15000), 4503 (4259840, 20000), 4504 (5570560, 25000), 4505 (6881280, 30000), 4506 (7864320, 35000))]: 4507 test_python_reader(g, 4508 block_size=1152, 4509 max_lpc_order=16, 4510 min_residual_partition_order=0, 4511 max_residual_partition_order=3, 4512 mid_side=True, 4513 adaptive_mid_side=True, 4514 exhaustive_model_search=True) 4515 4516 # test wasted BPS 4517 test_python_reader(test_streams.WastedBPS16(1000), 4518 block_size=1152, 4519 max_lpc_order=16, 4520 min_residual_partition_order=0, 4521 max_residual_partition_order=3, 4522 mid_side=True, 4523 adaptive_mid_side=True, 4524 exhaustive_model_search=True) 4525 4526 # test block sizes 4527 noise = struct.unpack(">32h", os.urandom(64)) 4528 4529 encoding_args = {"min_residual_partition_order": 0, 4530 "max_residual_partition_order": 6, 4531 "mid_side": True, 4532 "adaptive_mid_side": True, 4533 "exhaustive_model_search": True} 4534 for block_size in [16, 17, 18, 19, 20, 21, 22, 23, 4535 24, 25, 26, 27, 28, 29, 30, 31, 32, 33]: 4536 for lpc_order in [0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 31, 32]: 4537 args = encoding_args.copy() 4538 args["block_size"] = block_size 4539 args["max_lpc_order"] = lpc_order 4540 test_python_reader( 4541 test_streams.FrameListReader(noise, 44100, 1, 16), 4542 **args) 4543 4544 4545class M4AFileTest(LossyFileTest): 4546 def setUp(self): 4547 self.audio_class = audiotools.M4AAudio 4548 self.suffix = "." + self.audio_class.SUFFIX 4549 4550 @FORMAT_M4A 4551 def test_length(self): 4552 with tempfile.NamedTemporaryFile(suffix=self.suffix) as temp: 4553 for seconds in [1, 2, 3, 4, 5, 10, 20, 60, 120]: 4554 track = self.audio_class.from_pcm(temp.name, 4555 BLANK_PCM_Reader(seconds)) 4556 self.assertEqual(int(round(track.seconds_length())), seconds) 4557 4558 @FORMAT_LOSSY 4559 def test_channels(self): 4560 with tempfile.NamedTemporaryFile(suffix=self.suffix) as temp: 4561 for channels in [1, 2, 3, 4, 5, 6]: 4562 track = self.audio_class.from_pcm( 4563 temp.name, BLANK_PCM_Reader(1, 4564 channels=channels, 4565 channel_mask=0)) 4566 if self.audio_class is audiotools.m4a.M4AAudio_faac: 4567 self.assertEqual(track.channels(), 2) 4568 track = audiotools.open(temp.name) 4569 self.assertEqual(track.channels(), 2) 4570 else: 4571 self.assertEqual(track.channels(), max(2, channels)) 4572 track = audiotools.open(temp.name) 4573 self.assertEqual(track.channels(), max(2, channels)) 4574 4575 @FORMAT_M4A 4576 def test_too(self): 4577 # ensure that the 'too' meta atom isn't modified by setting metadata 4578 with tempfile.NamedTemporaryFile(suffix=self.suffix) as temp: 4579 track = self.audio_class.from_pcm( 4580 temp.name, 4581 BLANK_PCM_Reader(1)) 4582 metadata = track.get_metadata() 4583 encoder = u"%s" % (metadata[b'ilst'][b'\xa9too'],) 4584 track.set_metadata(audiotools.MetaData(track_name=u"Foo")) 4585 metadata = track.get_metadata() 4586 self.assertEqual(metadata.track_name, u"Foo") 4587 self.assertEqual(u"%s" % (metadata[b'ilst'][b'\xa9too'],), encoder) 4588 4589 4590class MP3FileTest(LossyFileTest): 4591 def setUp(self): 4592 self.audio_class = audiotools.MP3Audio 4593 self.suffix = "." + self.audio_class.SUFFIX 4594 4595 @FORMAT_MP3 4596 def test_length(self): 4597 with tempfile.NamedTemporaryFile(suffix=self.suffix) as temp: 4598 for seconds in [1, 2, 3, 4, 5, 10, 20, 60, 120]: 4599 track = self.audio_class.from_pcm(temp.name, 4600 BLANK_PCM_Reader(seconds)) 4601 self.assertEqual(int(round(track.seconds_length())), seconds) 4602 4603 @FORMAT_MP3 4604 def test_verify(self): 4605 # test invalid file sent to to_pcm() 4606 4607 # FIXME - mpg123 doesn't generate errors on invalid files 4608 # Ultimately, all of MP3/MP2 decoding needs to be internalized 4609 # so that these sorts of errors can be caught consistently. 4610 4611 # temp = tempfile.NamedTemporaryFile( 4612 # suffix=self.suffix) 4613 # try: 4614 # track = self.audio_class.from_pcm( 4615 # temp.name, 4616 # BLANK_PCM_Reader(1)) 4617 # good_data = open(temp.name, 'rb').read() 4618 # f = open(temp.name, 'wb') 4619 # f.write(good_data[0:100]) 4620 # f.close() 4621 # reader = track.to_pcm() 4622 # audiotools.transfer_framelist_data(reader, lambda x: x) 4623 # self.assertRaises(audiotools.DecodingError, 4624 # reader.close) 4625 # finally: 4626 # temp.close() 4627 4628 # test invalid file send to convert() 4629 # temp = tempfile.NamedTemporaryFile( 4630 # suffix=self.suffix) 4631 # try: 4632 # track = self.audio_class.from_pcm( 4633 # temp.name, 4634 # BLANK_PCM_Reader(1)) 4635 # good_data = open(temp.name, 'rb').read() 4636 # f = open(temp.name, 'wb') 4637 # f.write(good_data[0:100]) 4638 # f.close() 4639 # if os.path.isfile("dummy.wav"): 4640 # os.unlink("dummy.wav") 4641 # self.assertEqual(os.path.isfile("dummy.wav"), False) 4642 # self.assertRaises(audiotools.EncodingError, 4643 # track.convert, 4644 # "dummy.wav", 4645 # audiotools.WaveAudio) 4646 # self.assertEqual(os.path.isfile("dummy.wav"), False) 4647 # finally: 4648 # temp.close() 4649 4650 # # test verify() on invalid files 4651 # temp = tempfile.NamedTemporaryFile( 4652 # suffix=self.suffix) 4653 # mpeg_data = cStringIO.StringIO() 4654 # frame_header = audiotools.MPEG_Frame_Header("header") 4655 # try: 4656 # mpx_file = audiotools.open("sine" + self.suffix) 4657 # self.assertEqual(mpx_file.verify(), True) 4658 4659 # for (header, data) in mpx_file.mpeg_frames(): 4660 # mpeg_data.write(frame_header.build(header)) 4661 # mpeg_data.write(data) 4662 # mpeg_data = mpeg_data.getvalue() 4663 4664 # temp.seek(0, 0) 4665 # temp.write(mpeg_data) 4666 # temp.flush() 4667 4668 # # first, try truncating the file underfoot 4669 # bad_mpx_file = audiotools.open(temp.name) 4670 # for i in range(len(mpeg_data)): 4671 # try: 4672 # if ((mpeg_data[i] == chr(0xFF)) and 4673 # (ord(mpeg_data[i + 1]) & 0xE0)): 4674 # # skip sizes that may be the end of a frame 4675 # continue 4676 # except IndexError: 4677 # continue 4678 4679 # f = open(temp.name, "wb") 4680 # f.write(mpeg_data[0:i]) 4681 # f.close() 4682 # self.assertEqual(os.path.getsize(temp.name), i) 4683 # self.assertRaises(audiotools.InvalidFile, 4684 # bad_mpx_file.verify) 4685 4686 # # then try swapping some of the header bits 4687 # for (field, value) in [("sample_rate", 48000), 4688 # ("channel", 3)]: 4689 # temp.seek(0, 0) 4690 # for (i, (header, data)) in enumerate(mpx_file.mpeg_frames()): 4691 # if i == 1: 4692 # setattr(header, field, value) 4693 # temp.write(frame_header.build(header)) 4694 # temp.write(data) 4695 # else: 4696 # temp.write(frame_header.build(header)) 4697 # temp.write(data) 4698 # temp.flush() 4699 # new_file = audiotools.open(temp.name) 4700 # self.assertRaises(audiotools.InvalidFile, 4701 # new_file.verify) 4702 # finally: 4703 # temp.close() 4704 pass 4705 4706 @FORMAT_MP3 4707 def test_id3_ladder(self): 4708 with tempfile.NamedTemporaryFile(suffix=self.suffix) as temp_file: 4709 track = self.audio_class.from_pcm(temp_file.name, 4710 BLANK_PCM_Reader(5)) 4711 4712 dummy_metadata = audiotools.MetaData(track_name=u"Foo") 4713 4714 # ensure that setting particular ID3 variant 4715 # sticks, even through get/set_metadata 4716 track.set_metadata(dummy_metadata) 4717 for new_class in (audiotools.ID3v22Comment, 4718 audiotools.ID3v23Comment, 4719 audiotools.ID3v24Comment, 4720 audiotools.ID3v23Comment, 4721 audiotools.ID3v22Comment): 4722 metadata = new_class.converted(track.get_metadata()) 4723 track.set_metadata(metadata) 4724 metadata = track.get_metadata() 4725 self.assertTrue(isinstance(metadata, new_class)) 4726 self.assertEqual(metadata.__class__, new_class([]).__class__) 4727 self.assertEqual(metadata, dummy_metadata) 4728 4729 @FORMAT_MP3 4730 def test_ucs2(self): 4731 with tempfile.NamedTemporaryFile(suffix=self.suffix) as temp_file: 4732 track = self.audio_class.from_pcm(temp_file.name, 4733 BLANK_PCM_Reader(5)) 4734 4735 # this should be 4 characters long in UCS-4 environments 4736 # if not, we're in a UCS-2 environment 4737 # which is limited to 16 bits anyway 4738 test_string = u'f\U0001d55foo' 4739 4740 # u'\ufffd' is the "not found" character 4741 # this string should result from escaping through UCS-2 4742 test_string_out = u'f\ufffdoo' 4743 4744 if len(test_string) == 4: 4745 self.assertEqual(test_string, 4746 test_string.encode('utf-16').decode('utf-16')) 4747 self.assertEqual(test_string.encode('ucs2').decode('ucs2'), 4748 test_string_out) 4749 4750 # ID3v2.4 supports UTF-8/UTF-16 4751 metadata = audiotools.ID3v24Comment.converted( 4752 audiotools.MetaData(track_name=u"Foo")) 4753 track.set_metadata(metadata) 4754 id3 = track.get_metadata() 4755 self.assertEqual(id3, metadata) 4756 4757 metadata.track_name = test_string 4758 4759 track.set_metadata(metadata) 4760 id3 = track.get_metadata() 4761 self.assertEqual(id3, metadata) 4762 4763 metadata.comment = test_string 4764 track.set_metadata(metadata) 4765 id3 = track.get_metadata() 4766 self.assertEqual(id3, metadata) 4767 4768 metadata.add_image( 4769 audiotools.ID3v24Comment.IMAGE_FRAME.converted( 4770 audiotools.ID3v24Comment.IMAGE_FRAME_ID, 4771 audiotools.Image.new(TEST_COVER1, 4772 test_string, 4773 0))) 4774 track.set_metadata(metadata) 4775 id3 = track.get_metadata() 4776 self.assertEqual(id3.images()[0].description, test_string) 4777 4778 # ID3v2.3 and ID3v2.2 only support UCS-2 4779 for id3_class in (audiotools.ID3v23Comment, 4780 audiotools.ID3v22Comment): 4781 metadata = audiotools.ID3v23Comment.converted( 4782 audiotools.MetaData(track_name=u"Foo")) 4783 track.set_metadata(metadata) 4784 id3 = track.get_metadata() 4785 self.assertEqual(id3, metadata) 4786 4787 # ensure that text fields round-trip correctly 4788 # (i.e. the extra-wide char gets replaced) 4789 metadata.track_name = test_string 4790 4791 track.set_metadata(metadata) 4792 id3 = track.get_metadata() 4793 self.assertEqual(id3.track_name, test_string_out) 4794 4795 # ensure that comment blocks round-trip correctly 4796 metadata.comment = test_string 4797 track.set_metadata(metadata) 4798 id3 = track.get_metadata() 4799 self.assertEqual(id3.track_name, test_string_out) 4800 4801 # ensure that image comment fields round-trip correctly 4802 metadata.add_image( 4803 id3_class.IMAGE_FRAME.converted( 4804 id3_class.IMAGE_FRAME_ID, 4805 audiotools.Image.new(TEST_COVER1, 4806 test_string, 4807 0))) 4808 track.set_metadata(metadata) 4809 id3 = track.get_metadata() 4810 self.assertEqual(id3.images()[0].description, 4811 test_string_out) 4812 4813 @FORMAT_MP3 4814 def test_clean(self): 4815 # check MP3 file with double ID3 tags 4816 4817 from audiotools.text import CLEAN_REMOVE_DUPLICATE_ID3V2 4818 4819 original_size = os.path.getsize("id3-2.mp3") 4820 4821 track = audiotools.open("id3-2.mp3") 4822 # ensure second ID3 tag is ignored 4823 self.assertEqual(track.get_metadata().track_name, u"Title1") 4824 4825 # ensure duplicate ID3v2 tag is detected and removed 4826 fixes = track.clean() 4827 self.assertEqual(fixes, 4828 [CLEAN_REMOVE_DUPLICATE_ID3V2]) 4829 with tempfile.NamedTemporaryFile(suffix=".mp3") as temp: 4830 fixes = track.clean(temp.name) 4831 self.assertEqual(fixes, 4832 [CLEAN_REMOVE_DUPLICATE_ID3V2]) 4833 track2 = audiotools.open(temp.name) 4834 self.assertEqual(track2.get_metadata(), track.get_metadata()) 4835 # ensure new file is exactly one tag smaller 4836 # and the padding is preserved in the old tag 4837 self.assertEqual(os.path.getsize(temp.name), 4838 original_size - 0x46A) 4839 4840 4841class MP2FileTest(MP3FileTest): 4842 def setUp(self): 4843 self.audio_class = audiotools.MP2Audio 4844 self.suffix = "." + self.audio_class.SUFFIX 4845 4846 4847class OggVerify: 4848 @FORMAT_VORBIS 4849 @FORMAT_OPUS 4850 @FORMAT_OGGFLAC 4851 def test_verify(self): 4852 from test_core import ints_to_bytes, bytes_to_ints 4853 4854 good_file = tempfile.NamedTemporaryFile(suffix=self.suffix) 4855 bad_file = tempfile.NamedTemporaryFile(suffix=self.suffix) 4856 try: 4857 good_track = self.audio_class.from_pcm( 4858 good_file.name, 4859 BLANK_PCM_Reader(1)) 4860 good_file.seek(0, 0) 4861 good_file_data = good_file.read() 4862 self.assertEqual(len(good_file_data), 4863 os.path.getsize(good_file.name)) 4864 bad_file.write(good_file_data) 4865 bad_file.flush() 4866 4867 track = audiotools.open(bad_file.name) 4868 self.assertEqual(track.verify(), True) 4869 4870 # first, try truncating the file 4871 for i in range(len(good_file_data)): 4872 with open(bad_file.name, "wb") as f: 4873 f.write(good_file_data[0:i]) 4874 f.flush() 4875 self.assertEqual(os.path.getsize(bad_file.name), i) 4876 try: 4877 new_track = self.audio_class(bad_file.name) 4878 self.assertRaises(audiotools.InvalidFile, 4879 new_track.verify) 4880 except audiotools.InvalidFile: 4881 self.assertTrue(True) 4882 4883 # then, try flipping a bit 4884 for i in range(len(good_file_data)): 4885 for j in range(8): 4886 bad_file_data = bytes_to_ints(good_file_data) 4887 bad_file_data[i] = bad_file_data[i] ^ (1 << j) 4888 with open(bad_file.name, "wb") as f: 4889 f.write(ints_to_bytes(bad_file_data)) 4890 f.close() 4891 self.assertEqual(os.path.getsize(bad_file.name), 4892 len(good_file_data)) 4893 try: 4894 new_track = self.audio_class(bad_file.name) 4895 self.assertRaises(audiotools.InvalidFile, 4896 new_track.verify) 4897 except audiotools.InvalidFile: 4898 self.assertTrue(True) 4899 finally: 4900 good_file.close() 4901 bad_file.close() 4902 4903 if self.audio_class is audiotools.OpusAudio: 4904 # opusdec doesn't currently reject invalid 4905 # streams like it should 4906 # so the encoding test doesn't work right 4907 # (this is a known bug) 4908 return 4909 4910 with tempfile.NamedTemporaryFile(suffix=self.suffix) as temp: 4911 track = self.audio_class.from_pcm( 4912 temp.name, 4913 BLANK_PCM_Reader(1)) 4914 self.assertTrue(track.verify()) 4915 with open(temp.name, 'rb') as f: 4916 good_data = f.read() 4917 with open(temp.name, 'wb') as f: 4918 f.write(good_data[0:min(100, len(good_data) - 1)]) 4919 if os.path.isfile("dummy.wav"): 4920 os.unlink("dummy.wav") 4921 self.assertEqual(os.path.isfile("dummy.wav"), False) 4922 self.assertRaises(audiotools.EncodingError, 4923 track.convert, 4924 "dummy.wav", 4925 audiotools.WaveAudio) 4926 self.assertEqual(os.path.isfile("dummy.wav"), False) 4927 4928 4929class OggFlacFileTest(OggVerify, 4930 LosslessFileTest): 4931 def setUp(self): 4932 from audiotools.decoders import OggFlacDecoder 4933 4934 self.audio_class = audiotools.OggFlacAudio 4935 self.suffix = "." + self.audio_class.SUFFIX 4936 4937 self.decoder = OggFlacDecoder 4938 4939 @FORMAT_OGGFLAC 4940 def test_init(self): 4941 # check missing file 4942 self.assertRaises(audiotools.flac.InvalidFLAC, 4943 audiotools.OggFlacAudio, 4944 "/dev/null/foo") 4945 4946 # check invalid file 4947 with tempfile.NamedTemporaryFile(suffix=".oga") as invalid_file: 4948 for c in [b"i", b"n", b"v", b"a", b"l", b"i", b"d", 4949 b"s", b"t", b"r", b"i", b"n", b"g", b"x", b"x", b"x"]: 4950 invalid_file.write(c) 4951 invalid_file.flush() 4952 self.assertRaises(audiotools.flac.InvalidFLAC, 4953 audiotools.OggFlacAudio, 4954 invalid_file.name) 4955 4956 # check some decoder errors, 4957 # mostly to ensure a failed init doesn't make Python explode 4958 self.assertRaises(TypeError, self.decoder) 4959 4960 self.assertRaises(TypeError, self.decoder, None) 4961 4962 self.assertRaises(ValueError, self.decoder, "/dev/null", -1) 4963 4964 4965class ShortenFileTest(TestForeignWaveChunks, 4966 TestForeignAiffChunks, 4967 LosslessFileTest): 4968 def setUp(self): 4969 self.audio_class = audiotools.ShortenAudio 4970 self.suffix = "." + self.audio_class.SUFFIX 4971 4972 from audiotools.decoders import SHNDecoder 4973 from audiotools.encoders import encode_shn 4974 self.decoder = SHNDecoder 4975 self.encode = encode_shn 4976 self.encode_opts = [{"block_size": 4}, 4977 {"block_size": 256}, 4978 {"block_size": 1024}] 4979 4980 @FORMAT_SHORTEN 4981 def test_init(self): 4982 # check invalid file 4983 with tempfile.NamedTemporaryFile(suffix=".shn") as invalid_file: 4984 for c in [b"i", b"n", b"v", b"a", b"l", b"i", b"d", 4985 b"s", b"t", b"r", b"i", b"n", b"g", b"x", b"x", b"x"]: 4986 invalid_file.write(c) 4987 invalid_file.flush() 4988 self.assertRaises(audiotools.shn.InvalidShorten, 4989 audiotools.ShortenAudio, 4990 invalid_file.name) 4991 4992 # check some decoder errors, 4993 # mostly to ensure a failed init doesn't make Python explode 4994 self.assertRaises(TypeError, self.decoder) 4995 4996 self.assertRaises(IOError, self.decoder, None) 4997 4998 self.assertRaises(IOError, self.decoder, "filename") 4999 5000 @FORMAT_SHORTEN 5001 def test_bits_per_sample(self): 5002 with tempfile.NamedTemporaryFile(suffix=self.suffix) as temp: 5003 for bps in (8, 16): 5004 track = self.audio_class.from_pcm( 5005 temp.name, BLANK_PCM_Reader(1, bits_per_sample=bps)) 5006 self.assertEqual(track.bits_per_sample(), bps) 5007 track2 = audiotools.open(temp.name) 5008 self.assertEqual(track2.bits_per_sample(), bps) 5009 5010 @FORMAT_SHORTEN 5011 def test_verify(self): 5012 # test changing the file underfoot 5013 with tempfile.NamedTemporaryFile(suffix=".shn") as temp: 5014 with open("shorten-frames.shn", "rb") as f: 5015 shn_data = f.read() 5016 temp.write(shn_data) 5017 temp.flush() 5018 shn_file = audiotools.open(temp.name) 5019 self.assertEqual(shn_file.verify(), True) 5020 5021 for i in range(0, len(shn_data.rstrip(b"\x00"))): 5022 with open(temp.name, "wb") as f: 5023 f.write(shn_data[0:i]) 5024 f.close() 5025 self.assertRaises(audiotools.InvalidFile, 5026 shn_file.verify) 5027 5028 # unfortunately, Shorten doesn't have any checksumming 5029 # or other ways to reliably detect swapped bits 5030 5031 # testing truncating various Shorten files 5032 for (first, last, filename) in [(62, 89, "shorten-frames.shn"), 5033 (61, 116, "shorten-lpc.shn")]: 5034 5035 with open(filename, "rb") as f: 5036 shn_data = f.read() 5037 5038 with tempfile.NamedTemporaryFile(suffix=".shn") as temp: 5039 for i in range(0, first): 5040 temp.seek(0, 0) 5041 temp.write(shn_data[0:i]) 5042 temp.flush() 5043 self.assertEqual(os.path.getsize(temp.name), i) 5044 with open(temp.name, "rb") as f: 5045 self.assertRaises(IOError, 5046 audiotools.decoders.SHNDecoder, 5047 f) 5048 5049 for i in range(first, len(shn_data[0:last].rstrip(b"\x00"))): 5050 temp.seek(0, 0) 5051 temp.write(shn_data[0:i]) 5052 temp.flush() 5053 self.assertEqual(os.path.getsize(temp.name), i) 5054 decoder = audiotools.decoders.SHNDecoder( 5055 open(temp.name, "rb")) 5056 self.assertIsNotNone(decoder) 5057 self.assertRaises(IOError, decoder.pcm_split) 5058 decoder.close() 5059 5060 decoder = audiotools.decoders.SHNDecoder( 5061 open(temp.name, "rb")) 5062 self.assertIsNotNone(decoder) 5063 self.assertRaises(IOError, 5064 audiotools.transfer_framelist_data, 5065 decoder, lambda x: x) 5066 5067 # test running convert() on a truncated file 5068 # triggers EncodingError 5069 with tempfile.NamedTemporaryFile(suffix=".shn") as temp: 5070 with open("shorten-frames.shn", "rb") as f: 5071 temp.write(f.read()[0:-10]) 5072 temp.flush() 5073 shn = audiotools.open(temp.name) 5074 if os.path.isfile("dummy.wav"): 5075 os.unlink("dummy.wav") 5076 self.assertEqual(os.path.isfile("dummy.wav"), False) 5077 self.assertRaises(audiotools.EncodingError, 5078 shn.convert, 5079 "dummy.wav", 5080 audiotools.WaveAudio) 5081 self.assertEqual(os.path.isfile("dummy.wav"), False) 5082 5083 def __stream_variations__(self): 5084 for stream in [ 5085 test_streams.Silence8_Mono(200000, 44100), 5086 test_streams.Silence8_Mono(200000, 96000), 5087 test_streams.Silence8_Stereo(200000, 44100), 5088 test_streams.Silence8_Stereo(200000, 96000), 5089 test_streams.Silence16_Mono(200000, 44100), 5090 test_streams.Silence16_Mono(200000, 96000), 5091 test_streams.Silence16_Stereo(200000, 44100), 5092 test_streams.Silence16_Stereo(200000, 96000), 5093 5094 test_streams.Sine8_Mono(200000, 48000, 441.0, 0.50, 441.0, 0.49), 5095 test_streams.Sine8_Mono(200000, 96000, 441.0, 0.61, 661.5, 0.37), 5096 test_streams.Sine8_Mono(200000, 44100, 441.0, 0.50, 882.0, 0.49), 5097 test_streams.Sine8_Mono(200000, 44100, 441.0, 0.50, 4410.0, 0.49), 5098 test_streams.Sine8_Mono(200000, 44100, 8820.0, 0.70, 4410.0, 0.29), 5099 5100 test_streams.Sine8_Stereo(200000, 48000, 441.0, 0.50, 441.0, 0.49, 1.0), 5101 test_streams.Sine8_Stereo(200000, 48000, 441.0, 0.61, 661.5, 0.37, 1.0), 5102 test_streams.Sine8_Stereo(200000, 96000, 441.0, 0.50, 882.0, 0.49, 1.0), 5103 test_streams.Sine8_Stereo(200000, 44100, 441.0, 0.50, 4410.0, 0.49, 1.0), 5104 test_streams.Sine8_Stereo(200000, 44100, 8820.0, 0.70, 4410.0, 0.29, 1.0), 5105 test_streams.Sine8_Stereo(200000, 44100, 441.0, 0.50, 441.0, 0.49, 0.5), 5106 test_streams.Sine8_Stereo(200000, 44100, 441.0, 0.61, 661.5, 0.37, 2.0), 5107 test_streams.Sine8_Stereo(200000, 44100, 441.0, 0.50, 882.0, 0.49, 0.7), 5108 test_streams.Sine8_Stereo(200000, 44100, 441.0, 0.50, 4410.0, 0.49, 1.3), 5109 test_streams.Sine8_Stereo(200000, 44100, 8820.0, 0.70, 4410.0, 0.29, 0.1), 5110 5111 test_streams.Sine16_Mono(200000, 48000, 441.0, 0.50, 441.0, 0.49), 5112 test_streams.Sine16_Mono(200000, 96000, 441.0, 0.61, 661.5, 0.37), 5113 test_streams.Sine16_Mono(200000, 44100, 441.0, 0.50, 882.0, 0.49), 5114 test_streams.Sine16_Mono(200000, 44100, 441.0, 0.50, 4410.0, 0.49), 5115 test_streams.Sine16_Mono(200000, 44100, 8820.0, 0.70, 4410.0, 0.29), 5116 test_streams.Sine16_Stereo(200000, 48000, 441.0, 0.50, 441.0, 0.49, 1.0), 5117 test_streams.Sine16_Stereo(200000, 48000, 441.0, 0.61, 661.5, 0.37, 1.0), 5118 test_streams.Sine16_Stereo(200000, 96000, 441.0, 0.50, 882.0, 0.49, 1.0), 5119 test_streams.Sine16_Stereo(200000, 44100, 441.0, 0.50, 4410.0, 0.49, 1.0), 5120 test_streams.Sine16_Stereo(200000, 44100, 8820.0, 0.70, 4410.0, 0.29, 1.0), 5121 test_streams.Sine16_Stereo(200000, 44100, 441.0, 0.50, 441.0, 0.49, 0.5), 5122 test_streams.Sine16_Stereo(200000, 44100, 441.0, 0.61, 661.5, 0.37, 2.0), 5123 test_streams.Sine16_Stereo(200000, 44100, 441.0, 0.50, 882.0, 0.49, 0.7), 5124 test_streams.Sine16_Stereo(200000, 44100, 441.0, 0.50, 4410.0, 0.49, 1.3), 5125 test_streams.Sine16_Stereo(200000, 44100, 8820.0, 0.70, 4410.0, 0.29, 0.1), 5126 5127 test_streams.Simple_Sine(200000, 44100, 0x7, 8, 5128 (25, 10000), 5129 (50, 20000), 5130 (120, 30000)), 5131 test_streams.Simple_Sine(200000, 44100, 0x33, 8, 5132 (25, 10000), 5133 (50, 20000), 5134 (75, 30000), 5135 (65, 40000)), 5136 test_streams.Simple_Sine(200000, 44100, 0x37, 8, 5137 (25, 10000), 5138 (35, 15000), 5139 (45, 20000), 5140 (50, 25000), 5141 (55, 30000)), 5142 test_streams.Simple_Sine(200000, 44100, 0x3F, 8, 5143 (25, 10000), 5144 (45, 15000), 5145 (65, 20000), 5146 (85, 25000), 5147 (105, 30000), 5148 (120, 35000)), 5149 5150 test_streams.Simple_Sine(200000, 44100, 0x7, 16, 5151 (6400, 10000), 5152 (12800, 20000), 5153 (30720, 30000)), 5154 test_streams.Simple_Sine(200000, 44100, 0x33, 16, 5155 (6400, 10000), 5156 (12800, 20000), 5157 (19200, 30000), 5158 (16640, 40000)), 5159 test_streams.Simple_Sine(200000, 44100, 0x37, 16, 5160 (6400, 10000), 5161 (8960, 15000), 5162 (11520, 20000), 5163 (12800, 25000), 5164 (14080, 30000)), 5165 test_streams.Simple_Sine(200000, 44100, 0x3F, 16, 5166 (6400, 10000), 5167 (11520, 15000), 5168 (16640, 20000), 5169 (21760, 25000), 5170 (26880, 30000), 5171 (30720, 35000))]: 5172 yield stream 5173 5174 @FORMAT_SHORTEN 5175 def test_streams(self): 5176 for g in self.__stream_variations__(): 5177 md5sum = md5() 5178 f = g.read(audiotools.FRAMELIST_SIZE) 5179 while len(f) > 0: 5180 md5sum.update(f.to_bytes(False, True)) 5181 f = g.read(audiotools.FRAMELIST_SIZE) 5182 self.assertEqual(md5sum.digest(), g.digest()) 5183 g.close() 5184 5185 def __test_reader__(self, pcmreader, **encode_options): 5186 if not audiotools.BIN.can_execute(audiotools.BIN["shorten"]): 5187 self.assertTrue(False, 5188 "reference Shorten binary shorten(1) " + 5189 "required for this test") 5190 5191 temp_file = tempfile.NamedTemporaryFile(suffix=".shn") 5192 5193 # construct a temporary wave file from pcmreader 5194 temp_input_wave_file = tempfile.NamedTemporaryFile(suffix=".wav") 5195 temp_input_wave = audiotools.WaveAudio.from_pcm( 5196 temp_input_wave_file.name, pcmreader) 5197 temp_input_wave.verify() 5198 5199 options = encode_options.copy() 5200 (head, tail) = temp_input_wave.wave_header_footer() 5201 options["is_big_endian"] = False 5202 options["signed_samples"] = (pcmreader.bits_per_sample == 16) 5203 options["header_data"] = head 5204 if len(tail) > 0: 5205 options["footer_data"] = tail 5206 5207 with temp_input_wave.to_pcm() as pcmreader2: 5208 self.encode(temp_file.name, 5209 pcmreader2, 5210 **options) 5211 5212 shn = audiotools.open(temp_file.name) 5213 self.assertGreater(shn.total_frames(), 0) 5214 5215 temp_wav_file1 = tempfile.NamedTemporaryFile(suffix=".wav") 5216 temp_wav_file2 = tempfile.NamedTemporaryFile(suffix=".wav") 5217 5218 # first, ensure the Shorten-encoded file 5219 # has the same MD5 signature as pcmreader once decoded 5220 for shndec in [self.decoder(open(temp_file.name, "rb")), 5221 self.decoder(Filewrapper(open(temp_file.name, "rb")))]: 5222 md5sum = md5() 5223 f = shndec.read(audiotools.FRAMELIST_SIZE) 5224 while len(f) > 0: 5225 md5sum.update(f.to_bytes(False, True)) 5226 f = shndec.read(audiotools.FRAMELIST_SIZE) 5227 shndec.close() 5228 self.assertEqual(md5sum.digest(), pcmreader.digest()) 5229 5230 # then compare our .to_wave() output 5231 # with that of the Shorten reference decoder 5232 shn.convert(temp_wav_file1.name, audiotools.WaveAudio) 5233 subprocess.call([audiotools.BIN["shorten"], 5234 "-x", shn.filename, temp_wav_file2.name]) 5235 5236 wave = audiotools.WaveAudio(temp_wav_file1.name) 5237 wave.verify() 5238 wave = audiotools.WaveAudio(temp_wav_file2.name) 5239 wave.verify() 5240 5241 self.assertTrue( 5242 audiotools.pcm_cmp( 5243 audiotools.WaveAudio(temp_wav_file1.name).to_pcm(), 5244 audiotools.WaveAudio(temp_wav_file2.name).to_pcm())) 5245 5246 temp_wav_file1.close() 5247 temp_wav_file2.close() 5248 5249 # then perform PCM -> aiff -> Shorten -> PCM testing 5250 5251 # construct a temporary wave file from pcmreader 5252 temp_input_aiff_file = tempfile.NamedTemporaryFile(suffix=".aiff") 5253 temp_input_aiff = temp_input_wave.convert(temp_input_aiff_file.name, 5254 audiotools.AiffAudio) 5255 temp_input_aiff.verify() 5256 5257 options = encode_options.copy() 5258 options["is_big_endian"] = True 5259 options["signed_samples"] = True 5260 (head, tail) = temp_input_aiff.aiff_header_footer() 5261 options["header_data"] = head 5262 if len(tail) > 0: 5263 options["footer_data"] = tail 5264 5265 with temp_input_aiff.to_pcm() as pcmreader2: 5266 self.encode(temp_file.name, 5267 pcmreader2, 5268 **options) 5269 5270 shn = audiotools.open(temp_file.name) 5271 self.assertGreater(shn.total_frames(), 0) 5272 5273 temp_aiff_file1 = tempfile.NamedTemporaryFile(suffix=".aiff") 5274 temp_aiff_file2 = tempfile.NamedTemporaryFile(suffix=".aiff") 5275 5276 # first, ensure the Shorten-encoded file 5277 # has the same MD5 signature as pcmreader once decoded 5278 for shndec in [self.decoder(open(temp_file.name, "rb")), 5279 self.decoder(Filewrapper(open(temp_file.name, "rb")))]: 5280 md5sum = md5() 5281 f = shndec.read(audiotools.BUFFER_SIZE) 5282 while len(f) > 0: 5283 md5sum.update(f.to_bytes(False, True)) 5284 f = shndec.read(audiotools.BUFFER_SIZE) 5285 shndec.close() 5286 self.assertEqual(md5sum.digest(), pcmreader.digest()) 5287 5288 # then compare our .to_aiff() output 5289 # with that of the Shorten reference decoder 5290 shn.convert(temp_aiff_file1.name, audiotools.AiffAudio) 5291 5292 subprocess.call([audiotools.BIN["shorten"], 5293 "-x", shn.filename, temp_aiff_file2.name]) 5294 5295 aiff = audiotools.AiffAudio(temp_aiff_file1.name) 5296 aiff.verify() 5297 aiff = audiotools.AiffAudio(temp_aiff_file2.name) 5298 aiff.verify() 5299 5300 self.assertTrue( 5301 audiotools.pcm_cmp( 5302 audiotools.AiffAudio(temp_aiff_file1.name).to_pcm(), 5303 audiotools.AiffAudio(temp_aiff_file2.name).to_pcm())) 5304 5305 temp_file.close() 5306 temp_input_aiff_file.close() 5307 temp_input_wave_file.close() 5308 temp_aiff_file1.close() 5309 temp_aiff_file2.close() 5310 5311 @FORMAT_SHORTEN 5312 def test_small_files(self): 5313 for g in [test_streams.Generate01, 5314 test_streams.Generate02, 5315 test_streams.Generate03, 5316 test_streams.Generate04]: 5317 gen = g(44100) 5318 self.__test_reader__(gen, block_size=256) 5319 5320 @FORMAT_SHORTEN 5321 def test_full_scale_deflection(self): 5322 for (bps, fsd) in [(8, test_streams.fsd8), 5323 (16, test_streams.fsd16)]: 5324 for pattern in [test_streams.PATTERN01, 5325 test_streams.PATTERN02, 5326 test_streams.PATTERN03, 5327 test_streams.PATTERN04, 5328 test_streams.PATTERN05, 5329 test_streams.PATTERN06, 5330 test_streams.PATTERN07]: 5331 stream = test_streams.MD5Reader(fsd(pattern, 100)) 5332 self.__test_reader__( 5333 stream, block_size=256) 5334 5335 @FORMAT_SHORTEN 5336 def test_sines(self): 5337 for g in self.__stream_variations__(): 5338 self.__test_reader__(g, block_size=256) 5339 5340 @FORMAT_SHORTEN 5341 def test_blocksizes(self): 5342 noise = struct.unpack(">32h", os.urandom(64)) 5343 5344 for block_size in [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 5345 256, 1024]: 5346 args = {"block_size": block_size} 5347 self.__test_reader__( 5348 test_streams.MD5Reader( 5349 test_streams.FrameListReader(noise, 44100, 1, 16)), **args) 5350 5351 @FORMAT_SHORTEN 5352 def test_noise(self): 5353 for opts in self.encode_opts: 5354 encode_opts = opts.copy() 5355 for (channels, mask) in [ 5356 (1, int(audiotools.ChannelMask.from_channels(1))), 5357 (2, int(audiotools.ChannelMask.from_channels(2))), 5358 (4, int(audiotools.ChannelMask.from_fields(front_left=True, 5359 front_right=True, 5360 back_left=True, 5361 back_right=True))), 5362 (8, int(audiotools.ChannelMask(0)))]: 5363 for bps in [8, 16]: 5364 self.__test_reader__( 5365 MD5_Reader( 5366 EXACT_RANDOM_PCM_Reader(pcm_frames=65536, 5367 sample_rate=44100, 5368 channels=channels, 5369 channel_mask=mask, 5370 bits_per_sample=bps)), 5371 **encode_opts) 5372 5373 @FORMAT_SHORTEN 5374 def test_python_codec(self): 5375 def test_python_reader(pcmreader, total_pcm_frames, block_size=256): 5376 from audiotools.py_encoders import encode_shn 5377 5378 temp_file = tempfile.NamedTemporaryFile(suffix=".shn") 5379 audiotools.ShortenAudio.from_pcm( 5380 temp_file.name, 5381 pcmreader, 5382 block_size=block_size, 5383 encoding_function=encode_shn) 5384 5385 from audiotools.decoders import SHNDecoder as SHNDecoder1 5386 from audiotools.py_decoders import SHNDecoder as SHNDecoder2 5387 5388 self.assertTrue(audiotools.pcm_cmp( 5389 SHNDecoder1(open(temp_file.name, "rb")), 5390 SHNDecoder2(temp_file.name))) 5391 5392 # try test again, this time with total_pcm_frames indicated 5393 pcmreader.reset() 5394 audiotools.ShortenAudio.from_pcm( 5395 temp_file.name, 5396 pcmreader, 5397 total_pcm_frames=total_pcm_frames, 5398 block_size=block_size, 5399 encoding_function=encode_shn) 5400 5401 self.assertTrue(audiotools.pcm_cmp( 5402 SHNDecoder1(open(temp_file.name, "rb")), 5403 SHNDecoder2(temp_file.name))) 5404 5405 temp_file.close() 5406 5407 # test small files 5408 for g in [test_streams.Generate01, 5409 test_streams.Generate02]: 5410 test_python_reader(g(44100), 1, block_size=256) 5411 5412 for g in [test_streams.Generate03, 5413 test_streams.Generate04]: 5414 test_python_reader(g(44100), 5, block_size=256) 5415 5416 # test full scale deflection 5417 for (bps, fsd) in [(8, test_streams.fsd8), 5418 (16, test_streams.fsd16)]: 5419 for pattern in [test_streams.PATTERN01, 5420 test_streams.PATTERN02, 5421 test_streams.PATTERN03, 5422 test_streams.PATTERN04, 5423 test_streams.PATTERN05, 5424 test_streams.PATTERN06, 5425 test_streams.PATTERN07]: 5426 stream = test_streams.MD5Reader(fsd(pattern, 100)) 5427 test_python_reader(stream, 5428 len(pattern) * 100, 5429 block_size=256) 5430 5431 # test silence 5432 for g in [test_streams.Silence8_Mono(5000, 48000), 5433 test_streams.Silence8_Stereo(5000, 48000), 5434 test_streams.Silence16_Mono(5000, 48000), 5435 test_streams.Silence16_Stereo(5000, 48000)]: 5436 test_python_reader(g, 5000, block_size=256) 5437 5438 # test sines 5439 for g in [test_streams.Sine8_Mono(5000, 48000, 5440 441.0, 0.50, 441.0, 0.49), 5441 test_streams.Sine8_Stereo(5000, 48000, 5442 441.0, 0.50, 441.0, 0.49, 1.0), 5443 test_streams.Sine16_Mono(5000, 48000, 5444 441.0, 0.50, 441.0, 0.49), 5445 test_streams.Sine16_Stereo(5000, 48000, 5446 441.0, 0.50, 441.0, 0.49, 1.0), 5447 test_streams.Simple_Sine(5000, 44100, 0x7, 8, 5448 (25, 10000), 5449 (50, 20000), 5450 (120, 30000)), 5451 test_streams.Simple_Sine(5000, 44100, 0x33, 8, 5452 (25, 10000), 5453 (50, 20000), 5454 (75, 30000), 5455 (65, 40000)), 5456 test_streams.Simple_Sine(5000, 44100, 0x37, 8, 5457 (25, 10000), 5458 (35, 15000), 5459 (45, 20000), 5460 (50, 25000), 5461 (55, 30000)), 5462 test_streams.Simple_Sine(5000, 44100, 0x3F, 8, 5463 (25, 10000), 5464 (45, 15000), 5465 (65, 20000), 5466 (85, 25000), 5467 (105, 30000), 5468 (120, 35000)), 5469 test_streams.Simple_Sine(5000, 44100, 0x7, 16, 5470 (6400, 10000), 5471 (12800, 20000), 5472 (30720, 30000)), 5473 test_streams.Simple_Sine(5000, 44100, 0x33, 16, 5474 (6400, 10000), 5475 (12800, 20000), 5476 (19200, 30000), 5477 (16640, 40000)), 5478 test_streams.Simple_Sine(5000, 44100, 0x37, 16, 5479 (6400, 10000), 5480 (8960, 15000), 5481 (11520, 20000), 5482 (12800, 25000), 5483 (14080, 30000)), 5484 test_streams.Simple_Sine(5000, 44100, 0x3F, 16, 5485 (6400, 10000), 5486 (11520, 15000), 5487 (16640, 20000), 5488 (21760, 25000), 5489 (26880, 30000), 5490 (30720, 35000))]: 5491 test_python_reader(g, 5000, block_size=256) 5492 5493 # test block sizes 5494 noise = struct.unpack(">32h", os.urandom(64)) 5495 5496 for block_size in [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 5497 256, 1024]: 5498 test_python_reader( 5499 test_streams.FrameListReader(noise, 44100, 1, 16), 5500 len(noise), 5501 block_size=block_size) 5502 5503 # test noise 5504 for block_size in [4, 256, 1024]: 5505 for (channels, mask) in [ 5506 (1, int(audiotools.ChannelMask.from_channels(1))), 5507 (2, int(audiotools.ChannelMask.from_channels(2))), 5508 (4, int(audiotools.ChannelMask.from_fields(front_left=True, 5509 front_right=True, 5510 back_left=True, 5511 back_right=True))), 5512 (8, int(audiotools.ChannelMask(0)))]: 5513 for bps in [8, 16]: 5514 test_python_reader( 5515 EXACT_RANDOM_PCM_Reader( 5516 pcm_frames=5000, 5517 sample_rate=44100, 5518 channels=channels, 5519 channel_mask=mask, 5520 bits_per_sample=bps), 5521 5000, 5522 block_size=block_size) 5523 5524 5525class VorbisFileTest(OggVerify, LossyFileTest): 5526 def setUp(self): 5527 self.audio_class = audiotools.VorbisAudio 5528 self.suffix = "." + self.audio_class.SUFFIX 5529 5530 @FORMAT_VORBIS 5531 def test_channels(self): 5532 with tempfile.NamedTemporaryFile(suffix=self.suffix) as temp: 5533 for channels in [1, 2, 3, 4, 5, 6]: 5534 track = self.audio_class.from_pcm( 5535 temp.name, BLANK_PCM_Reader(1, 5536 channels=channels, 5537 channel_mask=0)) 5538 self.assertEqual(track.channels(), channels) 5539 track = audiotools.open(temp.name) 5540 self.assertEqual(track.channels(), channels) 5541 5542 @FORMAT_VORBIS 5543 def test_big_comment(self): 5544 with tempfile.NamedTemporaryFile( 5545 suffix="." + self.audio_class.SUFFIX) as track_file: 5546 track = self.audio_class.from_pcm(track_file.name, 5547 BLANK_PCM_Reader(1)) 5548 5549 original_pcm_sum = md5() 5550 audiotools.transfer_framelist_data(track.to_pcm(), 5551 original_pcm_sum.update) 5552 5553 comment = audiotools.MetaData( 5554 track_name=u"Name", 5555 track_number=1, 5556 comment=u"abcdefghij" * 13005) 5557 track.set_metadata(comment) 5558 track = audiotools.open(track_file.name) 5559 self.assertEqual(comment, track.get_metadata()) 5560 5561 new_pcm_sum = md5() 5562 audiotools.transfer_framelist_data(track.to_pcm(), 5563 new_pcm_sum.update) 5564 5565 self.assertEqual(original_pcm_sum.hexdigest(), 5566 new_pcm_sum.hexdigest()) 5567 5568 5569class OpusFileTest(OggVerify, LossyFileTest): 5570 def setUp(self): 5571 self.audio_class = audiotools.OpusAudio 5572 self.suffix = "." + self.audio_class.SUFFIX 5573 5574 @FORMAT_OPUS 5575 def test_channels(self): 5576 # FIXME - test Opus channel assignment 5577 pass 5578 5579 @FORMAT_OPUS 5580 def test_big_comment(self): 5581 with tempfile.NamedTemporaryFile( 5582 suffix="." + self.audio_class.SUFFIX) as track_file: 5583 track = self.audio_class.from_pcm(track_file.name, 5584 BLANK_PCM_Reader(1)) 5585 original_pcm_sum = md5() 5586 audiotools.transfer_framelist_data(track.to_pcm(), 5587 original_pcm_sum.update) 5588 5589 comment = audiotools.MetaData( 5590 track_name=u"Name", 5591 track_number=1, 5592 comment=u"abcdefghij" * 13005) 5593 track.set_metadata(comment) 5594 track = audiotools.open(track_file.name) 5595 self.assertEqual(comment, track.get_metadata()) 5596 5597 new_pcm_sum = md5() 5598 audiotools.transfer_framelist_data(track.to_pcm(), 5599 new_pcm_sum.update) 5600 5601 self.assertEqual(original_pcm_sum.hexdigest(), 5602 new_pcm_sum.hexdigest()) 5603 5604 5605class WaveFileTest(TestForeignWaveChunks, 5606 LosslessFileTest): 5607 def setUp(self): 5608 self.audio_class = audiotools.WaveAudio 5609 self.suffix = "." + self.audio_class.SUFFIX 5610 5611 @FORMAT_WAVE 5612 def test_overlong_file(self): 5613 # trying to generate too large of a file 5614 # should throw an exception right away if total_pcm_frames known 5615 # instead of building it first 5616 5617 self.assertEqual(os.path.isfile("invalid.wav"), False) 5618 5619 self.assertRaises(audiotools.EncodingError, 5620 self.audio_class.from_pcm, 5621 "invalid.wav", 5622 EXACT_SILENCE_PCM_Reader( 5623 pcm_frames=715827883, 5624 sample_rate=44100, 5625 channels=2, 5626 bits_per_sample=24), 5627 total_pcm_frames=715827883) 5628 5629 self.assertEqual(os.path.isfile("invalid.wav"), False) 5630 5631 @FORMAT_WAVE 5632 def test_verify(self): 5633 # test various truncated files with verify() 5634 for wav_file in ["wav-8bit.wav", 5635 "wav-1ch.wav", 5636 "wav-2ch.wav", 5637 "wav-6ch.wav"]: 5638 with tempfile.NamedTemporaryFile(suffix=".wav") as temp: 5639 with open(wav_file, 'rb') as f: 5640 wav_data = f.read() 5641 temp.write(wav_data) 5642 temp.flush() 5643 wave = audiotools.open(temp.name) 5644 5645 # try changing the file out from under it 5646 for i in range(0, len(wav_data)): 5647 with open(temp.name, 'wb') as f: 5648 f.write(wav_data[0:i]) 5649 self.assertEqual(os.path.getsize(temp.name), i) 5650 self.assertRaises(audiotools.InvalidFile, 5651 wave.verify) 5652 5653 # test running convert() on a truncated file 5654 # triggers EncodingError 5655 # FIXME - truncate file underfoot 5656 # temp = tempfile.NamedTemporaryFile(suffix=".flac") 5657 # try: 5658 # temp.write(open("wav-2ch.wav", "rb").read()[0:-10]) 5659 # temp.flush() 5660 # flac = audiotools.open(temp.name) 5661 # if os.path.isfile("dummy.wav"): 5662 # os.unlink("dummy.wav") 5663 # self.assertEqual(os.path.isfile("dummy.wav"), False) 5664 # self.assertRaises(audiotools.EncodingError, 5665 # flac.convert, 5666 # "dummy.wav", 5667 # audiotools.WaveAudio) 5668 # self.assertEqual(os.path.isfile("dummy.wav"), False) 5669 # finally: 5670 # temp.close() 5671 5672 # test other truncated file combinations 5673 for (fmt_size, wav_file) in [(0x24, "wav-8bit.wav"), 5674 (0x24, "wav-1ch.wav"), 5675 (0x24, "wav-2ch.wav"), 5676 (0x3C, "wav-6ch.wav")]: 5677 f = open(wav_file, 'rb') 5678 wav_data = f.read() 5679 f.close() 5680 5681 with tempfile.NamedTemporaryFile(suffix=".wav") as temp: 5682 # first, check that a truncated fmt chunk raises an exception 5683 # at init-time 5684 for i in range(0, fmt_size + 8): 5685 temp.seek(0, 0) 5686 temp.write(wav_data[0:i]) 5687 temp.flush() 5688 self.assertEqual(os.path.getsize(temp.name), i) 5689 5690 self.assertRaises(audiotools.InvalidFile, 5691 audiotools.WaveAudio, 5692 temp.name) 5693 5694 # test for non-ASCII chunk IDs 5695 from struct import pack 5696 from test_core import bytes_to_ints, ints_to_bytes 5697 5698 chunks = list(audiotools.open("wav-2ch.wav").chunks()) + \ 5699 [audiotools.wav.RIFF_Chunk(b"fooz", 5700 10, 5701 b"\x00" * 10)] 5702 with tempfile.NamedTemporaryFile(suffix=".wav") as temp: 5703 audiotools.WaveAudio.wave_from_chunks(temp.name, 5704 iter(chunks)) 5705 with open(temp.name, 'rb') as f: 5706 wav_data = bytes_to_ints(f.read()) 5707 wav_data[-15] = 0 5708 temp.seek(0, 0) 5709 temp.write(ints_to_bytes(wav_data)) 5710 temp.flush() 5711 self.assertRaises(audiotools.InvalidFile, 5712 audiotools.open(temp.name).verify) 5713 5714 FMT = audiotools.wav.RIFF_Chunk( 5715 b"fmt ", 5716 16, 5717 b'\x01\x00\x01\x00D\xac\x00\x00\x88X\x01\x00\x02\x00\x10\x00') 5718 5719 DATA = audiotools.wav.RIFF_Chunk( 5720 b"data", 5721 26, 5722 b'\x00\x00\x01\x00\x02\x00\x03\x00\x02\x00\x01\x00\x00\x00\xff\xff\xfe\xff\xfd\xff\xfe\xff\xff\xff\x00\x00') 5723 5724 # test multiple fmt chunks 5725 with tempfile.NamedTemporaryFile(suffix=".wav") as temp: 5726 for chunks in [[FMT, FMT, DATA], 5727 [FMT, DATA, FMT]]: 5728 audiotools.WaveAudio.wave_from_chunks(temp.name, chunks) 5729 self.assertRaises( 5730 audiotools.InvalidFile, 5731 audiotools.open(temp.name).verify) 5732 5733 # test multiple data chunks 5734 with tempfile.NamedTemporaryFile(suffix=".wav") as temp: 5735 audiotools.WaveAudio.wave_from_chunks(temp.name, [FMT, DATA, DATA]) 5736 self.assertRaises( 5737 audiotools.InvalidFile, 5738 audiotools.open(temp.name).verify) 5739 5740 # test data chunk before fmt chunk 5741 with tempfile.NamedTemporaryFile(suffix=".wav") as temp: 5742 audiotools.WaveAudio.wave_from_chunks(temp.name, [DATA, FMT]) 5743 self.assertRaises( 5744 audiotools.InvalidFile, 5745 audiotools.open(temp.name).verify) 5746 5747 # test no fmt chunk 5748 with tempfile.NamedTemporaryFile(suffix=".wav") as temp: 5749 audiotools.WaveAudio.wave_from_chunks(temp.name, [DATA]) 5750 self.assertRaises( 5751 audiotools.InvalidFile, 5752 audiotools.open(temp.name).verify) 5753 5754 # test no data chunk 5755 with tempfile.NamedTemporaryFile(suffix=".wav") as temp: 5756 audiotools.WaveAudio.wave_from_chunks(temp.name, [FMT]) 5757 self.assertRaises( 5758 audiotools.InvalidFile, 5759 audiotools.open(temp.name).verify) 5760 5761 @FORMAT_WAVE 5762 def test_clean(self): 5763 FMT = audiotools.wav.RIFF_Chunk( 5764 b"fmt ", 5765 16, 5766 b'\x01\x00\x01\x00D\xac\x00\x00\x88X\x01\x00\x02\x00\x10\x00') 5767 5768 DATA = audiotools.wav.RIFF_Chunk( 5769 b"data", 5770 26, 5771 b'\x00\x00\x01\x00\x02\x00\x03\x00\x02\x00\x01\x00\x00\x00\xff\xff\xfe\xff\xfd\xff\xfe\xff\xff\xff\x00\x00') 5772 5773 # test multiple fmt chunks 5774 # test multiple data chunks 5775 # test data chunk before fmt chunk 5776 temp = tempfile.NamedTemporaryFile(suffix=".wav") 5777 fixed = tempfile.NamedTemporaryFile(suffix=".wav") 5778 try: 5779 for chunks in [[FMT, FMT, DATA], 5780 [FMT, DATA, FMT], 5781 [FMT, DATA, DATA], 5782 [DATA, FMT], 5783 [DATA, FMT, FMT]]: 5784 audiotools.WaveAudio.wave_from_chunks(temp.name, chunks) 5785 fixes = audiotools.open(temp.name).clean(fixed.name) 5786 wave = audiotools.open(fixed.name) 5787 chunks = list(wave.chunks()) 5788 self.assertEqual([c.id for c in chunks], 5789 [c.id for c in [FMT, DATA]]) 5790 self.assertEqual([c.__size__ for c in chunks], 5791 [c.__size__ for c in [FMT, DATA]]) 5792 self.assertEqual([c.__data__ for c in chunks], 5793 [c.__data__ for c in [FMT, DATA]]) 5794 finally: 5795 temp.close() 5796 fixed.close() 5797 5798 # test converting 24bps file to WAVEFORMATEXTENSIBLE 5799 # FIXME 5800 5801 5802class WavPackFileTest(TestForeignWaveChunks, 5803 LosslessFileTest): 5804 def setUp(self): 5805 self.audio_class = audiotools.WavPackAudio 5806 self.suffix = "." + self.audio_class.SUFFIX 5807 5808 from audiotools.decoders import WavPackDecoder 5809 from audiotools.encoders import encode_wavpack 5810 5811 self.decoder = WavPackDecoder 5812 self.encode = encode_wavpack 5813 self.encode_opts = [{"block_size": 44100, 5814 "false_stereo": True, 5815 "wasted_bits": True, 5816 "joint_stereo": False, 5817 "correlation_passes": 0}, 5818 {"block_size": 44100, 5819 "false_stereo": True, 5820 "wasted_bits": True, 5821 "joint_stereo": True, 5822 "correlation_passes": 0}, 5823 {"block_size": 44100, 5824 "false_stereo": True, 5825 "wasted_bits": True, 5826 "joint_stereo": True, 5827 "correlation_passes": 1}, 5828 {"block_size": 44100, 5829 "false_stereo": True, 5830 "wasted_bits": True, 5831 "joint_stereo": True, 5832 "correlation_passes": 2}, 5833 {"block_size": 44100, 5834 "false_stereo": True, 5835 "wasted_bits": True, 5836 "joint_stereo": True, 5837 "correlation_passes": 5}, 5838 {"block_size": 44100, 5839 "false_stereo": True, 5840 "wasted_bits": True, 5841 "joint_stereo": True, 5842 "correlation_passes": 10}, 5843 {"block_size": 44100, 5844 "false_stereo": True, 5845 "wasted_bits": True, 5846 "joint_stereo": True, 5847 "correlation_passes": 16}] 5848 5849 @FORMAT_WAVPACK 5850 def test_init(self): 5851 # check missing file 5852 self.assertRaises(audiotools.wavpack.InvalidWavPack, 5853 audiotools.WavPackAudio, 5854 "/dev/null/foo") 5855 5856 # check invalid file 5857 with tempfile.NamedTemporaryFile(suffix=".wv") as invalid_file: 5858 for c in [b"i", b"n", b"v", b"a", b"l", b"i", b"d", 5859 b"s", b"t", b"r", b"i", b"n", b"g", b"x", b"x", b"x"]: 5860 invalid_file.write(c) 5861 invalid_file.flush() 5862 self.assertRaises(audiotools.wavpack.InvalidWavPack, 5863 audiotools.WavPackAudio, 5864 invalid_file.name) 5865 5866 # check some decoder errors, 5867 # mostly to ensure a failed init doesn't make Python explode 5868 self.assertRaises(TypeError, self.decoder) 5869 5870 self.assertRaises(IOError, self.decoder, None) 5871 5872 self.assertRaises(IOError, self.decoder, "filename") 5873 5874 @FORMAT_WAVPACK 5875 def test_verify(self): 5876 # test truncating a WavPack file causes verify() 5877 # to raise InvalidFile as necessary 5878 with open("wavpack-combo.wv", "rb") as f: 5879 wavpackdata = f.read() 5880 with tempfile.NamedTemporaryFile( 5881 suffix="." + self.audio_class.SUFFIX) as temp: 5882 self.assertEqual(audiotools.open("wavpack-combo.wv").verify(), 5883 True) 5884 temp.write(wavpackdata) 5885 temp.flush() 5886 test_wavpack = audiotools.open(temp.name) 5887 for i in range(0, 0x20B): 5888 with open(temp.name, "wb") as f: 5889 f.write(wavpackdata[0:i]) 5890 self.assertEqual(os.path.getsize(temp.name), i) 5891 self.assertRaises(audiotools.InvalidFile, 5892 test_wavpack.verify) 5893 5894 # Swapping random bits doesn't affect WavPack's decoding 5895 # in many instances - which is surprising since I'd 5896 # expect its adaptive routines to be more susceptible 5897 # to values being out-of-whack during decorrelation. 5898 # This resilience may be related to its hybrid mode, 5899 # but it doesn't inspire confidence. 5900 5901 # test truncating a WavPack file causes the WavPackDecoder 5902 # to raise IOError as necessary 5903 from audiotools.decoders import WavPackDecoder 5904 from test_core import ints_to_bytes, bytes_to_ints 5905 5906 with open("silence.wv", "rb") as f: 5907 wavpack_data = bytes_to_ints(f.read()) 5908 5909 with tempfile.NamedTemporaryFile(suffix=".wv") as temp: 5910 for i in range(0, len(wavpack_data)): 5911 temp.seek(0, 0) 5912 temp.write(ints_to_bytes(wavpack_data[0:i])) 5913 temp.flush() 5914 self.assertEqual(os.path.getsize(temp.name), i) 5915 f = open(temp.name, "rb") 5916 try: 5917 decoder = WavPackDecoder(f) 5918 except IOError: 5919 # chopping off the first few bytes might trigger 5920 # an IOError at init-time, which is ok 5921 f.close() 5922 continue 5923 self.assertIsNotNone(decoder) 5924 self.assertRaises(IOError, 5925 audiotools.transfer_framelist_data, 5926 decoder, lambda f: f) 5927 5928 # test a truncated WavPack file's convert() method 5929 # generates EncodingErrors 5930 with tempfile.NamedTemporaryFile( 5931 suffix="." + self.audio_class.SUFFIX) as temp: 5932 with open("wavpack-combo.wv", "rb") as f: 5933 temp.write(f.read()) 5934 temp.flush() 5935 wavpack = audiotools.open(temp.name) 5936 with open(temp.name, "wb") as w: 5937 with open("wavpack-combo.wv", "rb") as r: 5938 w.write(r.read()[0:-0x20B]) 5939 5940 if os.path.isfile("dummy.wav"): 5941 os.unlink("dummy.wav") 5942 self.assertFalse(os.path.isfile("dummy.wav")) 5943 self.assertRaises(audiotools.EncodingError, 5944 wavpack.convert, 5945 "dummy.wav", 5946 audiotools.WaveAudio) 5947 self.assertFalse(os.path.isfile("dummy.wav")) 5948 5949 def __stream_variations__(self): 5950 for stream in [ 5951 test_streams.Silence8_Mono(200000, 44100), 5952 test_streams.Silence8_Mono(200000, 96000), 5953 test_streams.Silence8_Stereo(200000, 44100), 5954 test_streams.Silence8_Stereo(200000, 96000), 5955 test_streams.Silence16_Mono(200000, 44100), 5956 test_streams.Silence16_Mono(200000, 96000), 5957 test_streams.Silence16_Stereo(200000, 44100), 5958 test_streams.Silence16_Stereo(200000, 96000), 5959 test_streams.Silence24_Mono(200000, 44100), 5960 test_streams.Silence24_Mono(200000, 96000), 5961 test_streams.Silence24_Stereo(200000, 44100), 5962 test_streams.Silence24_Stereo(200000, 96000), 5963 5964 test_streams.Sine8_Mono(200000, 48000, 441.0, 0.50, 441.0, 0.49), 5965 test_streams.Sine8_Mono(200000, 96000, 441.0, 0.61, 661.5, 0.37), 5966 test_streams.Sine8_Mono(200000, 44100, 441.0, 0.50, 882.0, 0.49), 5967 test_streams.Sine8_Mono(200000, 44100, 441.0, 0.50, 4410.0, 0.49), 5968 test_streams.Sine8_Mono(200000, 44100, 8820.0, 0.70, 4410.0, 0.29), 5969 5970 test_streams.Sine8_Stereo(200000, 48000, 441.0, 0.50, 441.0, 0.49, 1.0), 5971 test_streams.Sine8_Stereo(200000, 48000, 441.0, 0.61, 661.5, 0.37, 1.0), 5972 test_streams.Sine8_Stereo(200000, 96000, 441.0, 0.50, 882.0, 0.49, 1.0), 5973 test_streams.Sine8_Stereo(200000, 44100, 441.0, 0.50, 4410.0, 0.49, 1.0), 5974 test_streams.Sine8_Stereo(200000, 44100, 8820.0, 0.70, 4410.0, 0.29, 1.0), 5975 test_streams.Sine8_Stereo(200000, 44100, 441.0, 0.50, 441.0, 0.49, 0.5), 5976 test_streams.Sine8_Stereo(200000, 44100, 441.0, 0.61, 661.5, 0.37, 2.0), 5977 test_streams.Sine8_Stereo(200000, 44100, 441.0, 0.50, 882.0, 0.49, 0.7), 5978 test_streams.Sine8_Stereo(200000, 44100, 441.0, 0.50, 4410.0, 0.49, 1.3), 5979 test_streams.Sine8_Stereo(200000, 44100, 8820.0, 0.70, 4410.0, 0.29, 0.1), 5980 5981 test_streams.Sine16_Mono(200000, 48000, 441.0, 0.50, 441.0, 0.49), 5982 test_streams.Sine16_Mono(200000, 96000, 441.0, 0.61, 661.5, 0.37), 5983 test_streams.Sine16_Mono(200000, 44100, 441.0, 0.50, 882.0, 0.49), 5984 test_streams.Sine16_Mono(200000, 44100, 441.0, 0.50, 4410.0, 0.49), 5985 test_streams.Sine16_Mono(200000, 44100, 8820.0, 0.70, 4410.0, 0.29), 5986 5987 test_streams.Sine16_Stereo(200000, 48000, 441.0, 0.50, 441.0, 0.49, 1.0), 5988 test_streams.Sine16_Stereo(200000, 48000, 441.0, 0.61, 661.5, 0.37, 1.0), 5989 test_streams.Sine16_Stereo(200000, 96000, 441.0, 0.50, 882.0, 0.49, 1.0), 5990 test_streams.Sine16_Stereo(200000, 44100, 441.0, 0.50, 4410.0, 0.49, 1.0), 5991 test_streams.Sine16_Stereo(200000, 44100, 8820.0, 0.70, 4410.0, 0.29, 1.0), 5992 test_streams.Sine16_Stereo(200000, 44100, 441.0, 0.50, 441.0, 0.49, 0.5), 5993 test_streams.Sine16_Stereo(200000, 44100, 441.0, 0.61, 661.5, 0.37, 2.0), 5994 test_streams.Sine16_Stereo(200000, 44100, 441.0, 0.50, 882.0, 0.49, 0.7), 5995 test_streams.Sine16_Stereo(200000, 44100, 441.0, 0.50, 4410.0, 0.49, 1.3), 5996 test_streams.Sine16_Stereo(200000, 44100, 8820.0, 0.70, 4410.0, 0.29, 0.1), 5997 5998 test_streams.Sine24_Mono(200000, 48000, 441.0, 0.50, 441.0, 0.49), 5999 test_streams.Sine24_Mono(200000, 96000, 441.0, 0.61, 661.5, 0.37), 6000 test_streams.Sine24_Mono(200000, 44100, 441.0, 0.50, 882.0, 0.49), 6001 test_streams.Sine24_Mono(200000, 44100, 441.0, 0.50, 4410.0, 0.49), 6002 test_streams.Sine24_Mono(200000, 44100, 8820.0, 0.70, 4410.0, 0.29), 6003 6004 test_streams.Sine24_Stereo(200000, 48000, 441.0, 0.50, 441.0, 0.49, 1.0), 6005 test_streams.Sine24_Stereo(200000, 48000, 441.0, 0.61, 661.5, 0.37, 1.0), 6006 test_streams.Sine24_Stereo(200000, 96000, 441.0, 0.50, 882.0, 0.49, 1.0), 6007 test_streams.Sine24_Stereo(200000, 44100, 441.0, 0.50, 4410.0, 0.49, 1.0), 6008 test_streams.Sine24_Stereo(200000, 44100, 8820.0, 0.70, 4410.0, 0.29, 1.0), 6009 test_streams.Sine24_Stereo(200000, 44100, 441.0, 0.50, 441.0, 0.49, 0.5), 6010 test_streams.Sine24_Stereo(200000, 44100, 441.0, 0.61, 661.5, 0.37, 2.0), 6011 test_streams.Sine24_Stereo(200000, 44100, 441.0, 0.50, 882.0, 0.49, 0.7), 6012 test_streams.Sine24_Stereo(200000, 44100, 441.0, 0.50, 4410.0, 0.49, 1.3), 6013 test_streams.Sine24_Stereo(200000, 44100, 8820.0, 0.70, 4410.0, 0.29, 0.1), 6014 6015 test_streams.Simple_Sine(200000, 44100, 0x7, 8, 6016 (25, 10000), 6017 (50, 20000), 6018 (120, 30000)), 6019 test_streams.Simple_Sine(200000, 44100, 0x33, 8, 6020 (25, 10000), 6021 (50, 20000), 6022 (75, 30000), 6023 (65, 40000)), 6024 test_streams.Simple_Sine(200000, 44100, 0x37, 8, 6025 (25, 10000), 6026 (35, 15000), 6027 (45, 20000), 6028 (50, 25000), 6029 (55, 30000)), 6030 test_streams.Simple_Sine(200000, 44100, 0x3F, 8, 6031 (25, 10000), 6032 (45, 15000), 6033 (65, 20000), 6034 (85, 25000), 6035 (105, 30000), 6036 (120, 35000)), 6037 6038 test_streams.Simple_Sine(200000, 44100, 0x7, 16, 6039 (6400, 10000), 6040 (12800, 20000), 6041 (30720, 30000)), 6042 test_streams.Simple_Sine(200000, 44100, 0x33, 16, 6043 (6400, 10000), 6044 (12800, 20000), 6045 (19200, 30000), 6046 (16640, 40000)), 6047 test_streams.Simple_Sine(200000, 44100, 0x37, 16, 6048 (6400, 10000), 6049 (8960, 15000), 6050 (11520, 20000), 6051 (12800, 25000), 6052 (14080, 30000)), 6053 test_streams.Simple_Sine(200000, 44100, 0x3F, 16, 6054 (6400, 10000), 6055 (11520, 15000), 6056 (16640, 20000), 6057 (21760, 25000), 6058 (26880, 30000), 6059 (30720, 35000)), 6060 6061 test_streams.Simple_Sine(200000, 44100, 0x7, 24, 6062 (1638400, 10000), 6063 (3276800, 20000), 6064 (7864320, 30000)), 6065 test_streams.Simple_Sine(200000, 44100, 0x33, 24, 6066 (1638400, 10000), 6067 (3276800, 20000), 6068 (4915200, 30000), 6069 (4259840, 40000)), 6070 test_streams.Simple_Sine(200000, 44100, 0x37, 24, 6071 (1638400, 10000), 6072 (2293760, 15000), 6073 (2949120, 20000), 6074 (3276800, 25000), 6075 (3604480, 30000)), 6076 test_streams.Simple_Sine(200000, 44100, 0x3F, 24, 6077 (1638400, 10000), 6078 (2949120, 15000), 6079 (4259840, 20000), 6080 (5570560, 25000), 6081 (6881280, 30000), 6082 (7864320, 35000))]: 6083 yield stream 6084 6085 def __test_reader__(self, pcmreader, total_pcm_frames, **encode_options): 6086 if not audiotools.BIN.can_execute(audiotools.BIN["wvunpack"]): 6087 self.assertTrue(False, 6088 "reference WavPack binary wvunpack(1) " + 6089 "required for this test") 6090 6091 temp_file = tempfile.NamedTemporaryFile(suffix=".wv") 6092 6093 self.encode(temp_file.name, 6094 audiotools.BufferedPCMReader(pcmreader), 6095 **encode_options) 6096 6097 devnull = open(os.devnull, "wb") 6098 6099 sub = subprocess.Popen([audiotools.BIN["wvunpack"], 6100 "-vmq", temp_file.name], 6101 stdout=devnull, 6102 stderr=devnull) 6103 6104 self.assertEqual(sub.wait(), 0, 6105 "wvunpack decode error on %s with options %s" % 6106 (repr(pcmreader), 6107 repr(encode_options))) 6108 6109 for wavpack in [self.decoder(open(temp_file.name, "rb")), 6110 self.decoder(Filewrapper(open(temp_file.name, "rb")))]: 6111 self.assertEqual(wavpack.sample_rate, pcmreader.sample_rate) 6112 self.assertEqual(wavpack.bits_per_sample, pcmreader.bits_per_sample) 6113 self.assertEqual(wavpack.channels, pcmreader.channels) 6114 self.assertEqual(wavpack.channel_mask, pcmreader.channel_mask) 6115 6116 md5sum = md5() 6117 f = wavpack.read(audiotools.FRAMELIST_SIZE) 6118 while len(f) > 0: 6119 md5sum.update(f.to_bytes(False, True)) 6120 f = wavpack.read(audiotools.FRAMELIST_SIZE) 6121 wavpack.close() 6122 self.assertEqual(md5sum.digest(), pcmreader.digest()) 6123 6124 # perform test again with total_pcm_frames indicated 6125 pcmreader.reset() 6126 6127 self.encode(temp_file.name, 6128 audiotools.BufferedPCMReader(pcmreader), 6129 total_pcm_frames=total_pcm_frames, 6130 **encode_options) 6131 6132 sub = subprocess.Popen([audiotools.BIN["wvunpack"], 6133 "-vmq", temp_file.name], 6134 stdout=devnull, 6135 stderr=devnull) 6136 6137 devnull.close() 6138 6139 self.assertEqual(sub.wait(), 0, 6140 "wvunpack decode error on %s with options %s" % 6141 (repr(pcmreader), 6142 repr(encode_options))) 6143 6144 for wavpack in [self.decoder(open(temp_file.name, "rb")), 6145 self.decoder(Filewrapper(open(temp_file.name, "rb")))]: 6146 self.assertEqual(wavpack.sample_rate, pcmreader.sample_rate) 6147 self.assertEqual(wavpack.bits_per_sample, pcmreader.bits_per_sample) 6148 self.assertEqual(wavpack.channels, pcmreader.channels) 6149 self.assertEqual(wavpack.channel_mask, pcmreader.channel_mask) 6150 6151 md5sum = md5() 6152 f = wavpack.read(audiotools.FRAMELIST_SIZE) 6153 while len(f) > 0: 6154 md5sum.update(f.to_bytes(False, True)) 6155 f = wavpack.read(audiotools.FRAMELIST_SIZE) 6156 wavpack.close() 6157 self.assertEqual(md5sum.digest(), pcmreader.digest()) 6158 6159 temp_file.close() 6160 6161 @FORMAT_WAVPACK 6162 def test_small_files(self): 6163 for opts in self.encode_opts: 6164 for g in [test_streams.Generate01, 6165 test_streams.Generate02]: 6166 self.__test_reader__(g(44100), 1, **opts) 6167 for g in [test_streams.Generate03, 6168 test_streams.Generate04]: 6169 self.__test_reader__(g(44100), 5, **opts) 6170 6171 @FORMAT_WAVPACK 6172 def test_full_scale_deflection(self): 6173 for opts in self.encode_opts: 6174 for (bps, fsd) in [(8, test_streams.fsd8), 6175 (16, test_streams.fsd16), 6176 (24, test_streams.fsd24)]: 6177 for pattern in [test_streams.PATTERN01, 6178 test_streams.PATTERN02, 6179 test_streams.PATTERN03, 6180 test_streams.PATTERN04, 6181 test_streams.PATTERN05, 6182 test_streams.PATTERN06, 6183 test_streams.PATTERN07]: 6184 self.__test_reader__( 6185 test_streams.MD5Reader(fsd(pattern, 100)), 6186 len(pattern) * 100, 6187 **opts) 6188 6189 @FORMAT_WAVPACK 6190 def test_wasted_bps(self): 6191 for opts in self.encode_opts: 6192 self.__test_reader__(test_streams.WastedBPS16(1000), 6193 1000, 6194 **opts) 6195 6196 @FORMAT_WAVPACK 6197 def test_blocksizes(self): 6198 noise = struct.unpack(">32h", os.urandom(64)) 6199 6200 opts = {"false_stereo": False, 6201 "wasted_bits": False, 6202 "joint_stereo": False} 6203 for block_size in [16, 17, 18, 19, 20, 21, 22, 23, 6204 24, 25, 26, 27, 28, 29, 30, 31, 32, 33]: 6205 for decorrelation_passes in [0, 1, 5]: 6206 opts_copy = opts.copy() 6207 opts_copy["block_size"] = block_size 6208 opts_copy["correlation_passes"] = decorrelation_passes 6209 self.__test_reader__( 6210 test_streams.MD5Reader( 6211 test_streams.FrameListReader(noise, 6212 44100, 1, 16)), 6213 len(noise), 6214 **opts_copy) 6215 6216 @FORMAT_WAVPACK 6217 def test_silence(self): 6218 for opts in self.encode_opts: 6219 for (channels, mask) in [ 6220 (1, audiotools.ChannelMask.from_channels(1)), 6221 (2, audiotools.ChannelMask.from_channels(2)), 6222 (4, audiotools.ChannelMask.from_fields(front_left=True, 6223 front_right=True, 6224 back_left=True, 6225 back_right=True)), 6226 (8, audiotools.ChannelMask(0))]: 6227 for bps in [8, 16, 24]: 6228 opts_copy = opts.copy() 6229 for block_size in [44100, 32, 32768, 65535, 6230 16777215]: 6231 opts_copy['block_size'] = block_size 6232 6233 self.__test_reader__( 6234 MD5_Reader( 6235 EXACT_SILENCE_PCM_Reader( 6236 pcm_frames=65536, 6237 sample_rate=44100, 6238 channels=channels, 6239 channel_mask=mask, 6240 bits_per_sample=bps)), 6241 65536, 6242 **opts_copy) 6243 6244 @FORMAT_WAVPACK 6245 def test_noise(self): 6246 for opts in self.encode_opts: 6247 for (channels, mask) in [ 6248 (1, audiotools.ChannelMask.from_channels(1)), 6249 (2, audiotools.ChannelMask.from_channels(2)), 6250 (4, audiotools.ChannelMask.from_fields(front_left=True, 6251 front_right=True, 6252 back_left=True, 6253 back_right=True)), 6254 (8, audiotools.ChannelMask(0))]: 6255 for bps in [8, 16, 24]: 6256 opts_copy = opts.copy() 6257 for block_size in [44100, 32, 32768, 65535, 6258 16777215]: 6259 opts_copy['block_size'] = block_size 6260 6261 self.__test_reader__( 6262 MD5_Reader( 6263 EXACT_RANDOM_PCM_Reader( 6264 pcm_frames=65536, 6265 sample_rate=44100, 6266 channels=channels, 6267 channel_mask=mask, 6268 bits_per_sample=bps)), 6269 65536, 6270 **opts_copy) 6271 6272 @FORMAT_WAVPACK 6273 def test_fractional(self): 6274 def __perform_test__(block_size, pcm_frames): 6275 self.__test_reader__( 6276 MD5_Reader( 6277 EXACT_RANDOM_PCM_Reader( 6278 pcm_frames=pcm_frames, 6279 sample_rate=44100, 6280 channels=2, 6281 bits_per_sample=16)), 6282 pcm_frames, 6283 block_size=block_size, 6284 correlation_passes=5, 6285 false_stereo=False, 6286 wasted_bits=False, 6287 joint_stereo=False) 6288 6289 for pcm_frames in [31, 32, 33, 34, 35, 2046, 2047, 2048, 2049, 2050]: 6290 __perform_test__(33, pcm_frames) 6291 6292 for pcm_frames in [254, 255, 256, 257, 258, 510, 511, 512, 513, 6293 514, 1022, 1023, 1024, 1025, 1026, 2046, 2047, 6294 2048, 2049, 2050, 4094, 4095, 4096, 4097, 4098]: 6295 __perform_test__(256, pcm_frames) 6296 6297 for pcm_frames in [1022, 1023, 1024, 1025, 1026, 2046, 2047, 6298 2048, 2049, 2050, 4094, 4095, 4096, 4097, 4098]: 6299 __perform_test__(2048, pcm_frames) 6300 6301 for pcm_frames in [1022, 1023, 1024, 1025, 1026, 2046, 2047, 6302 2048, 2049, 2050, 4094, 4095, 4096, 4097, 6303 4098, 4606, 4607, 4608, 4609, 4610, 8190, 6304 8191, 8192, 8193, 8194, 16382, 16383, 16384, 6305 16385, 16386]: 6306 __perform_test__(4608, pcm_frames) 6307 6308 for pcm_frames in [44098, 44099, 44100, 44101, 44102, 44103, 6309 88198, 88199, 88200, 88201, 88202, 88203]: 6310 __perform_test__(44100, pcm_frames) 6311 6312 @FORMAT_WAVPACK 6313 def test_multichannel(self): 6314 def __permutations__(executables, options, total): 6315 if total == 0: 6316 yield [] 6317 else: 6318 for (executable, option) in zip(executables, 6319 options): 6320 for permutation in __permutations__(executables, 6321 options, 6322 total - 1): 6323 yield [executable(**option)] + permutation 6324 6325 # test a mix of identical and non-identical channels 6326 # using different decorrelation, joint stereo and false stereo options 6327 combos = 0 6328 for (false_stereo, joint_stereo) in [(False, False), 6329 (False, True), 6330 (True, False), 6331 (True, True)]: 6332 for (channels, mask) in [(2, 0x3), (3, 0x7), (4, 0x33), 6333 (5, 0x3B), (6, 0x3F)]: 6334 for readers in __permutations__( 6335 [EXACT_BLANK_PCM_Reader, 6336 EXACT_RANDOM_PCM_Reader, 6337 test_streams.Sine16_Mono], 6338 [{"pcm_frames": 100, 6339 "sample_rate": 44100, 6340 "channels": 1, 6341 "bits_per_sample": 16}, 6342 {"pcm_frames": 100, 6343 "sample_rate": 44100, 6344 "channels": 1, 6345 "bits_per_sample": 16}, 6346 {"pcm_frames": 100, 6347 "sample_rate": 44100, 6348 "f1": 441.0, 6349 "a1": 0.61, 6350 "f2": 661.5, 6351 "a2": 0.37}], channels): 6352 joined = MD5_Reader(Join_Reader(readers, mask)) 6353 self.__test_reader__(joined, 6354 100, 6355 block_size=44100, 6356 false_stereo=false_stereo, 6357 joint_stereo=joint_stereo, 6358 correlation_passes=1, 6359 wasted_bits=False) 6360 6361 @FORMAT_WAVPACK 6362 def test_sines(self): 6363 for opts in self.encode_opts: 6364 for g in self.__stream_variations__(): 6365 self.__test_reader__(g, 200000, **opts) 6366 6367 @FORMAT_WAVPACK 6368 def test_option_variations(self): 6369 for block_size in [11025, 22050, 44100, 88200, 176400]: 6370 for false_stereo in [False, True]: 6371 for wasted_bits in [False, True]: 6372 for joint_stereo in [False, True]: 6373 for decorrelation_passes in [0, 1, 2, 5, 10, 16]: 6374 self.__test_reader__( 6375 test_streams.Sine16_Stereo(200000, 6376 48000, 6377 441.0, 6378 0.50, 6379 441.0, 6380 0.49, 6381 1.0), 6382 200000, 6383 block_size=block_size, 6384 false_stereo=false_stereo, 6385 wasted_bits=wasted_bits, 6386 joint_stereo=joint_stereo, 6387 correlation_passes=decorrelation_passes) 6388 6389 @FORMAT_WAVPACK 6390 def test_python_codec(self): 6391 def test_python_reader(pcmreader, total_pcm_frames, **encode_options): 6392 from audiotools.py_encoders import encode_wavpack 6393 6394 # encode file using Python-based encoder 6395 temp_file = tempfile.NamedTemporaryFile(suffix=".wv") 6396 6397 encode_wavpack(temp_file.name, 6398 audiotools.BufferedPCMReader(pcmreader), 6399 **encode_options) 6400 6401 # verify contents of file decoded by 6402 # Python-based decoder against contents decoded by 6403 # C-based decoder 6404 from audiotools.py_decoders import WavPackDecoder as WavPackDecoder1 6405 from audiotools.decoders import WavPackDecoder as WavPackDecoder2 6406 6407 self.assertTrue( 6408 audiotools.pcm_cmp( 6409 WavPackDecoder1(temp_file.name), 6410 WavPackDecoder2(open(temp_file.name, "rb")))) 6411 6412 # redo test with total_pcm_frames indicated 6413 pcmreader.reset() 6414 6415 encode_wavpack(temp_file.name, 6416 audiotools.BufferedPCMReader(pcmreader), 6417 total_pcm_frames=total_pcm_frames, 6418 **encode_options) 6419 6420 # verify contents of file decoded by 6421 # Python-based decoder against contents decoded by 6422 # C-based decoder 6423 from audiotools.py_decoders import WavPackDecoder as WavPackDecoder1 6424 from audiotools.decoders import WavPackDecoder as WavPackDecoder2 6425 6426 self.assertTrue( 6427 audiotools.pcm_cmp( 6428 WavPackDecoder1(temp_file.name), 6429 WavPackDecoder2(open(temp_file.name, "rb")))) 6430 6431 temp_file.close() 6432 6433 # test small files 6434 for opts in self.encode_opts: 6435 for g in [test_streams.Generate01, 6436 test_streams.Generate02]: 6437 test_python_reader(g(44100), 1, **opts) 6438 for g in [test_streams.Generate03, 6439 test_streams.Generate04]: 6440 test_python_reader(g(44100), 5, **opts) 6441 6442 # test full scale deflection 6443 for opts in self.encode_opts: 6444 for (bps, fsd) in [(8, test_streams.fsd8), 6445 (16, test_streams.fsd16), 6446 (24, test_streams.fsd24)]: 6447 for pattern in [test_streams.PATTERN01, 6448 test_streams.PATTERN02, 6449 test_streams.PATTERN03, 6450 test_streams.PATTERN04, 6451 test_streams.PATTERN05, 6452 test_streams.PATTERN06, 6453 test_streams.PATTERN07]: 6454 test_python_reader(fsd(pattern, 100), 6455 len(pattern) * 100, 6456 **opts) 6457 6458 # test wasted BPS 6459 for opts in self.encode_opts: 6460 test_python_reader(test_streams.WastedBPS16(1000), 6461 1000, 6462 **opts) 6463 6464 # test block sizes 6465 noise = struct.unpack(">32h", os.urandom(64)) 6466 6467 opts = {"false_stereo": False, 6468 "wasted_bits": False, 6469 "joint_stereo": False} 6470 for block_size in [16, 17, 18, 19, 20, 21, 22, 23, 6471 24, 25, 26, 27, 28, 29, 30, 31, 32, 33]: 6472 for decorrelation_passes in [0, 1, 5]: 6473 opts_copy = opts.copy() 6474 opts_copy["block_size"] = block_size 6475 opts_copy["correlation_passes"] = decorrelation_passes 6476 test_python_reader( 6477 test_streams.FrameListReader(noise, 6478 44100, 1, 16), 6479 len(noise), 6480 **opts_copy) 6481 6482 # test silence 6483 for opts in self.encode_opts: 6484 for (channels, mask) in [ 6485 (1, audiotools.ChannelMask.from_channels(1)), 6486 (2, audiotools.ChannelMask.from_channels(2))]: 6487 opts_copy = opts.copy() 6488 opts_copy['block_size'] = 4095 6489 test_python_reader( 6490 EXACT_SILENCE_PCM_Reader( 6491 pcm_frames=4096, 6492 sample_rate=44100, 6493 channels=channels, 6494 channel_mask=mask, 6495 bits_per_sample=16), 6496 4096, 6497 **opts_copy) 6498 6499 # test noise 6500 for opts in self.encode_opts: 6501 for (channels, mask) in [ 6502 (1, audiotools.ChannelMask.from_channels(1)), 6503 (2, audiotools.ChannelMask.from_channels(2))]: 6504 opts_copy = opts.copy() 6505 opts_copy['block_size'] = 4095 6506 test_python_reader( 6507 EXACT_RANDOM_PCM_Reader( 6508 pcm_frames=4096, 6509 sample_rate=44100, 6510 channels=channels, 6511 channel_mask=mask, 6512 bits_per_sample=16), 6513 4096, 6514 **opts_copy) 6515 6516 # test fractional 6517 for (block_size, 6518 pcm_frames_list) in [(33, [31, 32, 33, 34, 35, 2046, 6519 2047, 2048, 2049, 2050]), 6520 (256, [254, 255, 256, 257, 258, 510, 6521 511, 512, 513, 514, 1022, 1023, 6522 1024, 1025, 1026, 2046, 2047, 2048, 6523 2049, 2050, 4094, 4095, 4096, 4097, 6524 4098])]: 6525 for pcm_frames in pcm_frames_list: 6526 test_python_reader( 6527 EXACT_RANDOM_PCM_Reader( 6528 pcm_frames=pcm_frames, 6529 sample_rate=44100, 6530 channels=2, 6531 bits_per_sample=16), 6532 pcm_frames, 6533 block_size=block_size, 6534 correlation_passes=5, 6535 false_stereo=False, 6536 wasted_bits=False, 6537 joint_stereo=False) 6538 6539 # test sines 6540 for opts in self.encode_opts: 6541 for g in [test_streams.Sine8_Mono(5000, 48000, 6542 441.0, 0.50, 441.0, 0.49), 6543 test_streams.Sine8_Stereo(5000, 48000, 6544 441.0, 0.50, 441.0, 0.49, 1.0), 6545 test_streams.Sine16_Mono(5000, 48000, 6546 441.0, 0.50, 441.0, 0.49), 6547 test_streams.Sine16_Stereo(5000, 48000, 6548 441.0, 0.50, 441.0, 0.49, 1.0), 6549 test_streams.Sine24_Mono(5000, 48000, 6550 441.0, 0.50, 441.0, 0.49), 6551 test_streams.Sine24_Stereo(5000, 48000, 6552 441.0, 0.50, 441.0, 0.49, 1.0), 6553 test_streams.Simple_Sine(5000, 44100, 0x7, 8, 6554 (25, 10000), 6555 (50, 20000), 6556 (120, 30000)), 6557 test_streams.Simple_Sine(5000, 44100, 0x33, 8, 6558 (25, 10000), 6559 (50, 20000), 6560 (75, 30000), 6561 (65, 40000)), 6562 test_streams.Simple_Sine(5000, 44100, 0x37, 8, 6563 (25, 10000), 6564 (35, 15000), 6565 (45, 20000), 6566 (50, 25000), 6567 (55, 30000)), 6568 test_streams.Simple_Sine(5000, 44100, 0x3F, 8, 6569 (25, 10000), 6570 (45, 15000), 6571 (65, 20000), 6572 (85, 25000), 6573 (105, 30000), 6574 (120, 35000)), 6575 6576 test_streams.Simple_Sine(5000, 44100, 0x7, 16, 6577 (6400, 10000), 6578 (12800, 20000), 6579 (30720, 30000)), 6580 test_streams.Simple_Sine(5000, 44100, 0x33, 16, 6581 (6400, 10000), 6582 (12800, 20000), 6583 (19200, 30000), 6584 (16640, 40000)), 6585 test_streams.Simple_Sine(5000, 44100, 0x37, 16, 6586 (6400, 10000), 6587 (8960, 15000), 6588 (11520, 20000), 6589 (12800, 25000), 6590 (14080, 30000)), 6591 test_streams.Simple_Sine(5000, 44100, 0x3F, 16, 6592 (6400, 10000), 6593 (11520, 15000), 6594 (16640, 20000), 6595 (21760, 25000), 6596 (26880, 30000), 6597 (30720, 35000)), 6598 6599 test_streams.Simple_Sine(5000, 44100, 0x7, 24, 6600 (1638400, 10000), 6601 (3276800, 20000), 6602 (7864320, 30000)), 6603 test_streams.Simple_Sine(5000, 44100, 0x33, 24, 6604 (1638400, 10000), 6605 (3276800, 20000), 6606 (4915200, 30000), 6607 (4259840, 40000)), 6608 test_streams.Simple_Sine(5000, 44100, 0x37, 24, 6609 (1638400, 10000), 6610 (2293760, 15000), 6611 (2949120, 20000), 6612 (3276800, 25000), 6613 (3604480, 30000)), 6614 test_streams.Simple_Sine(5000, 44100, 0x3F, 24, 6615 (1638400, 10000), 6616 (2949120, 15000), 6617 (4259840, 20000), 6618 (5570560, 25000), 6619 (6881280, 30000), 6620 (7864320, 35000))]: 6621 test_python_reader(g, 5000, **opts) 6622 6623 6624class TTAFileTest(LosslessFileTest): 6625 def setUp(self): 6626 self.audio_class = audiotools.TrueAudio 6627 self.suffix = "." + self.audio_class.SUFFIX 6628 6629 from audiotools.decoders import TTADecoder 6630 6631 self.decoder = TTADecoder 6632 self.encode = audiotools.TrueAudio.from_pcm 6633 6634 @FORMAT_TTA 6635 def test_init(self): 6636 # check missing file 6637 self.assertRaises(audiotools.tta.InvalidTTA, 6638 audiotools.TrueAudio, 6639 "/dev/null/foo") 6640 6641 # check invalid file 6642 with tempfile.NamedTemporaryFile(suffix=".tta") as invalid_file: 6643 for c in [b"i", b"n", b"v", b"a", b"l", b"i", b"d", 6644 b"s", b"t", b"r", b"i", b"n", b"g", b"x", b"x", b"x"]: 6645 invalid_file.write(c) 6646 invalid_file.flush() 6647 self.assertRaises(audiotools.tta.InvalidTTA, 6648 audiotools.TrueAudio, 6649 invalid_file.name) 6650 6651 # check some decoder errors 6652 self.assertRaises(TypeError, self.decoder) 6653 6654 self.assertRaises(IOError, self.decoder, None) 6655 6656 self.assertRaises(IOError, self.decoder, "filename") 6657 6658 @FORMAT_TTA 6659 def test_verify(self): 6660 from test_core import ints_to_bytes, bytes_to_ints 6661 6662 with open("trueaudio.tta", "rb") as f: 6663 tta_data = f.read() 6664 6665 self.assertTrue(audiotools.open("trueaudio.tta").verify()) 6666 6667 # try changing the file underfoot 6668 with tempfile.NamedTemporaryFile(suffix=".tta") as temp: 6669 temp.write(tta_data) 6670 temp.flush() 6671 tta_file = audiotools.open(temp.name) 6672 self.assertTrue(tta_file.verify()) 6673 6674 for i in range(0, len(tta_data)): 6675 with open(temp.name, "wb") as f: 6676 f.write(tta_data[0:i]) 6677 self.assertRaises(audiotools.InvalidFile, 6678 tta_file.verify) 6679 6680 for i in range(0x2A, len(tta_data)): 6681 for j in range(8): 6682 new_data = bytes_to_ints(tta_data) 6683 new_data[i] = new_data[i] ^ (1 << j) 6684 with open(temp.name, "wb") as f: 6685 f.write(ints_to_bytes(new_data)) 6686 self.assertRaises(audiotools.InvalidFile, 6687 tta_file.verify) 6688 6689 # check a TTA file with a short header 6690 with tempfile.NamedTemporaryFile(suffix=".tta") as temp: 6691 for i in range(0, 18): 6692 temp.seek(0, 0) 6693 temp.write(tta_data[0:i]) 6694 temp.flush() 6695 self.assertEqual(os.path.getsize(temp.name), i) 6696 if i < 4: 6697 with open(temp.name, "rb") as f: 6698 self.assertIsNone(audiotools.file_type(f)) 6699 with open(temp.name, "rb") as f: 6700 self.assertRaises(IOError, 6701 audiotools.decoders.TTADecoder, 6702 f) 6703 6704 # check a TTA file that's been truncated 6705 with tempfile.NamedTemporaryFile(suffix=".tta") as temp: 6706 for i in range(30, len(tta_data)): 6707 temp.seek(0, 0) 6708 temp.write(tta_data[0:i]) 6709 temp.flush() 6710 self.assertEqual(os.path.getsize(temp.name), i) 6711 decoder = audiotools.open(temp.name).to_pcm() 6712 self.assertNotEqual(decoder, None) 6713 self.assertRaises(IOError, 6714 audiotools.transfer_framelist_data, 6715 decoder, lambda x: x) 6716 6717 self.assertRaises(audiotools.InvalidFile, 6718 audiotools.open(temp.name).verify) 6719 6720 # check a TTA file with a single swapped bit 6721 with tempfile.NamedTemporaryFile(suffix=".tta") as temp: 6722 for i in range(0x30, len(tta_data)): 6723 for j in range(8): 6724 bytes = bytes_to_ints(tta_data) 6725 bytes[i] ^= (1 << j) 6726 temp.seek(0, 0) 6727 temp.write(ints_to_bytes(bytes)) 6728 temp.flush() 6729 self.assertEqual(len(tta_data), 6730 os.path.getsize(temp.name)) 6731 6732 with audiotools.open(temp.name).to_pcm() as decoders: 6733 try: 6734 self.assertRaises( 6735 ValueError, 6736 audiotools.transfer_framelist_data, 6737 decoders, lambda x: x) 6738 except IOError: 6739 # Randomly swapping bits may send the decoder 6740 # off the end of the stream before triggering 6741 # a CRC-16 error. 6742 # We simply need to catch that case and continue 6743 continue 6744 6745 def __stream_variations__(self): 6746 for stream in [ 6747 test_streams.Silence8_Mono(200000, 44100), 6748 test_streams.Silence8_Mono(200000, 96000), 6749 test_streams.Silence8_Stereo(200000, 44100), 6750 test_streams.Silence8_Stereo(200000, 96000), 6751 test_streams.Silence16_Mono(200000, 44100), 6752 test_streams.Silence16_Mono(200000, 96000), 6753 test_streams.Silence16_Stereo(200000, 44100), 6754 test_streams.Silence16_Stereo(200000, 96000), 6755 test_streams.Silence24_Mono(200000, 44100), 6756 test_streams.Silence24_Mono(200000, 96000), 6757 test_streams.Silence24_Stereo(200000, 44100), 6758 test_streams.Silence24_Stereo(200000, 96000), 6759 6760 test_streams.Sine8_Mono(200000, 48000, 441.0, 0.50, 441.0, 0.49), 6761 test_streams.Sine8_Mono(200000, 96000, 441.0, 0.61, 661.5, 0.37), 6762 test_streams.Sine8_Mono(200000, 44100, 441.0, 0.50, 882.0, 0.49), 6763 test_streams.Sine8_Mono(200000, 44100, 441.0, 0.50, 4410.0, 0.49), 6764 test_streams.Sine8_Mono(200000, 44100, 8820.0, 0.70, 4410.0, 0.29), 6765 6766 test_streams.Sine8_Stereo(200000, 48000, 441.0, 0.50, 441.0, 0.49, 1.0), 6767 test_streams.Sine8_Stereo(200000, 48000, 441.0, 0.61, 661.5, 0.37, 1.0), 6768 test_streams.Sine8_Stereo(200000, 96000, 441.0, 0.50, 882.0, 0.49, 1.0), 6769 test_streams.Sine8_Stereo(200000, 44100, 441.0, 0.50, 4410.0, 0.49, 1.0), 6770 test_streams.Sine8_Stereo(200000, 44100, 8820.0, 0.70, 4410.0, 0.29, 1.0), 6771 test_streams.Sine8_Stereo(200000, 44100, 441.0, 0.50, 441.0, 0.49, 0.5), 6772 test_streams.Sine8_Stereo(200000, 44100, 441.0, 0.61, 661.5, 0.37, 2.0), 6773 test_streams.Sine8_Stereo(200000, 44100, 441.0, 0.50, 882.0, 0.49, 0.7), 6774 test_streams.Sine8_Stereo(200000, 44100, 441.0, 0.50, 4410.0, 0.49, 1.3), 6775 test_streams.Sine8_Stereo(200000, 44100, 8820.0, 0.70, 4410.0, 0.29, 0.1), 6776 6777 test_streams.Sine16_Mono(200000, 48000, 441.0, 0.50, 441.0, 0.49), 6778 test_streams.Sine16_Mono(200000, 96000, 441.0, 0.61, 661.5, 0.37), 6779 test_streams.Sine16_Mono(200000, 44100, 441.0, 0.50, 882.0, 0.49), 6780 test_streams.Sine16_Mono(200000, 44100, 441.0, 0.50, 4410.0, 0.49), 6781 test_streams.Sine16_Mono(200000, 44100, 8820.0, 0.70, 4410.0, 0.29), 6782 6783 test_streams.Sine16_Stereo(200000, 48000, 441.0, 0.50, 441.0, 0.49, 1.0), 6784 test_streams.Sine16_Stereo(200000, 48000, 441.0, 0.61, 661.5, 0.37, 1.0), 6785 test_streams.Sine16_Stereo(200000, 96000, 441.0, 0.50, 882.0, 0.49, 1.0), 6786 test_streams.Sine16_Stereo(200000, 44100, 441.0, 0.50, 4410.0, 0.49, 1.0), 6787 test_streams.Sine16_Stereo(200000, 44100, 8820.0, 0.70, 4410.0, 0.29, 1.0), 6788 test_streams.Sine16_Stereo(200000, 44100, 441.0, 0.50, 441.0, 0.49, 0.5), 6789 test_streams.Sine16_Stereo(200000, 44100, 441.0, 0.61, 661.5, 0.37, 2.0), 6790 test_streams.Sine16_Stereo(200000, 44100, 441.0, 0.50, 882.0, 0.49, 0.7), 6791 test_streams.Sine16_Stereo(200000, 44100, 441.0, 0.50, 4410.0, 0.49, 1.3), 6792 test_streams.Sine16_Stereo(200000, 44100, 8820.0, 0.70, 4410.0, 0.29, 0.1), 6793 6794 test_streams.Sine24_Mono(200000, 48000, 441.0, 0.50, 441.0, 0.49), 6795 test_streams.Sine24_Mono(200000, 96000, 441.0, 0.61, 661.5, 0.37), 6796 test_streams.Sine24_Mono(200000, 44100, 441.0, 0.50, 882.0, 0.49), 6797 test_streams.Sine24_Mono(200000, 44100, 441.0, 0.50, 4410.0, 0.49), 6798 test_streams.Sine24_Mono(200000, 44100, 8820.0, 0.70, 4410.0, 0.29), 6799 6800 test_streams.Sine24_Stereo(200000, 48000, 441.0, 0.50, 441.0, 0.49, 1.0), 6801 test_streams.Sine24_Stereo(200000, 48000, 441.0, 0.61, 661.5, 0.37, 1.0), 6802 test_streams.Sine24_Stereo(200000, 96000, 441.0, 0.50, 882.0, 0.49, 1.0), 6803 test_streams.Sine24_Stereo(200000, 44100, 441.0, 0.50, 4410.0, 0.49, 1.0), 6804 test_streams.Sine24_Stereo(200000, 44100, 8820.0, 0.70, 4410.0, 0.29, 1.0), 6805 test_streams.Sine24_Stereo(200000, 44100, 441.0, 0.50, 441.0, 0.49, 0.5), 6806 test_streams.Sine24_Stereo(200000, 44100, 441.0, 0.61, 661.5, 0.37, 2.0), 6807 test_streams.Sine24_Stereo(200000, 44100, 441.0, 0.50, 882.0, 0.49, 0.7), 6808 test_streams.Sine24_Stereo(200000, 44100, 441.0, 0.50, 4410.0, 0.49, 1.3), 6809 test_streams.Sine24_Stereo(200000, 44100, 8820.0, 0.70, 4410.0, 0.29, 0.1), 6810 6811 test_streams.Simple_Sine(200000, 44100, 0x7, 8, 6812 (25, 10000), 6813 (50, 20000), 6814 (120, 30000)), 6815 test_streams.Simple_Sine(200000, 44100, 0x33, 8, 6816 (25, 10000), 6817 (50, 20000), 6818 (75, 30000), 6819 (65, 40000)), 6820 test_streams.Simple_Sine(200000, 44100, 0x37, 8, 6821 (25, 10000), 6822 (35, 15000), 6823 (45, 20000), 6824 (50, 25000), 6825 (55, 30000)), 6826 test_streams.Simple_Sine(200000, 44100, 0x3F, 8, 6827 (25, 10000), 6828 (45, 15000), 6829 (65, 20000), 6830 (85, 25000), 6831 (105, 30000), 6832 (120, 35000)), 6833 6834 test_streams.Simple_Sine(200000, 44100, 0x7, 16, 6835 (6400, 10000), 6836 (12800, 20000), 6837 (30720, 30000)), 6838 test_streams.Simple_Sine(200000, 44100, 0x33, 16, 6839 (6400, 10000), 6840 (12800, 20000), 6841 (19200, 30000), 6842 (16640, 40000)), 6843 test_streams.Simple_Sine(200000, 44100, 0x37, 16, 6844 (6400, 10000), 6845 (8960, 15000), 6846 (11520, 20000), 6847 (12800, 25000), 6848 (14080, 30000)), 6849 test_streams.Simple_Sine(200000, 44100, 0x3F, 16, 6850 (6400, 10000), 6851 (11520, 15000), 6852 (16640, 20000), 6853 (21760, 25000), 6854 (26880, 30000), 6855 (30720, 35000)), 6856 6857 test_streams.Simple_Sine(200000, 44100, 0x7, 24, 6858 (1638400, 10000), 6859 (3276800, 20000), 6860 (7864320, 30000)), 6861 test_streams.Simple_Sine(200000, 44100, 0x33, 24, 6862 (1638400, 10000), 6863 (3276800, 20000), 6864 (4915200, 30000), 6865 (4259840, 40000)), 6866 test_streams.Simple_Sine(200000, 44100, 0x37, 24, 6867 (1638400, 10000), 6868 (2293760, 15000), 6869 (2949120, 20000), 6870 (3276800, 25000), 6871 (3604480, 30000)), 6872 test_streams.Simple_Sine(200000, 44100, 0x3F, 24, 6873 (1638400, 10000), 6874 (2949120, 15000), 6875 (4259840, 20000), 6876 (5570560, 25000), 6877 (6881280, 30000), 6878 (7864320, 35000))]: 6879 yield stream 6880 6881 def __test_reader__(self, pcmreader, total_pcm_frames): 6882 if not audiotools.BIN.can_execute(audiotools.BIN["tta"]): 6883 self.assertTrue( 6884 False, 6885 "reference TrueAudio binary tta(1) required for this test") 6886 6887 devnull = open(os.devnull, "wb") 6888 temp_tta_file = tempfile.NamedTemporaryFile(suffix=".tta") 6889 self.encode(temp_tta_file.name, pcmreader) 6890 6891 if (pcmreader.bits_per_sample > 8) and (pcmreader.channels <= 6): 6892 # reference decoder doesn't like 8 bit .wav files?! 6893 # or files with too many channels? 6894 6895 temp_wav_file = tempfile.NamedTemporaryFile(suffix=".wav") 6896 sub = subprocess.Popen([audiotools.BIN["tta"], 6897 "-d", temp_tta_file.name, 6898 temp_wav_file.name], 6899 stdout=devnull, 6900 stderr=devnull) 6901 self.assertEqual(sub.wait(), 0, 6902 "tta decode error on %s" % (repr(pcmreader))) 6903 else: 6904 temp_wav_file = None 6905 6906 for tta in [self.decoder(open(temp_tta_file.name, "rb")), 6907 self.decoder(Filewrapper(open(temp_tta_file.name, "rb")))]: 6908 self.assertEqual(tta.sample_rate, pcmreader.sample_rate) 6909 self.assertEqual(tta.bits_per_sample, pcmreader.bits_per_sample) 6910 self.assertEqual(tta.channels, pcmreader.channels) 6911 6912 md5sum = md5() 6913 f = tta.read(audiotools.FRAMELIST_SIZE) 6914 while len(f) > 0: 6915 md5sum.update(f.to_bytes(False, True)) 6916 f = tta.read(audiotools.FRAMELIST_SIZE) 6917 tta.close() 6918 self.assertEqual(md5sum.digest(), pcmreader.digest()) 6919 6920 if temp_wav_file is not None: 6921 wav_md5sum = md5() 6922 audiotools.transfer_framelist_data( 6923 audiotools.WaveAudio(temp_wav_file.name).to_pcm(), 6924 wav_md5sum.update) 6925 self.assertEqual(md5sum.digest(), wav_md5sum.digest()) 6926 6927 if temp_wav_file is not None: 6928 temp_wav_file.close() 6929 6930 # perform test again with total_pcm_frames indicated 6931 pcmreader.reset() 6932 self.encode(temp_tta_file.name, 6933 pcmreader, 6934 total_pcm_frames=total_pcm_frames) 6935 6936 if (pcmreader.bits_per_sample > 8) and (pcmreader.channels <= 6): 6937 # reference decoder doesn't like 8 bit .wav files?! 6938 # or files with too many channels? 6939 temp_wav_file = tempfile.NamedTemporaryFile(suffix=".wav") 6940 sub = subprocess.Popen([audiotools.BIN["tta"], 6941 "-d", temp_tta_file.name, 6942 temp_wav_file.name], 6943 stdout=devnull, 6944 stderr=devnull) 6945 self.assertEqual(sub.wait(), 0, 6946 "tta decode error on %s" % (repr(pcmreader))) 6947 else: 6948 temp_wav_file = None 6949 6950 for tta in [self.decoder(open(temp_tta_file.name, "rb")), 6951 self.decoder(Filewrapper(open(temp_tta_file.name, "rb")))]: 6952 self.assertEqual(tta.sample_rate, pcmreader.sample_rate) 6953 self.assertEqual(tta.bits_per_sample, pcmreader.bits_per_sample) 6954 self.assertEqual(tta.channels, pcmreader.channels) 6955 6956 md5sum = md5() 6957 f = tta.read(audiotools.FRAMELIST_SIZE) 6958 while len(f) > 0: 6959 md5sum.update(f.to_bytes(False, True)) 6960 f = tta.read(audiotools.FRAMELIST_SIZE) 6961 tta.close() 6962 self.assertEqual(md5sum.digest(), pcmreader.digest()) 6963 temp_tta_file.close() 6964 6965 if temp_wav_file is not None: 6966 wav_md5sum = md5() 6967 audiotools.transfer_framelist_data( 6968 audiotools.WaveAudio(temp_wav_file.name).to_pcm(), 6969 wav_md5sum.update) 6970 self.assertEqual(md5sum.digest(), wav_md5sum.digest()) 6971 6972 if temp_wav_file is not None: 6973 temp_wav_file.close() 6974 6975 devnull.close() 6976 6977 @FORMAT_TTA 6978 def test_small_files(self): 6979 for g in [test_streams.Generate01, 6980 test_streams.Generate02]: 6981 self.__test_reader__(g(44100), 1) 6982 for g in [test_streams.Generate03, 6983 test_streams.Generate04]: 6984 self.__test_reader__(g(44100), 5) 6985 6986 @FORMAT_TTA 6987 def test_full_scale_deflection(self): 6988 for (bps, fsd) in [(8, test_streams.fsd8), 6989 (16, test_streams.fsd16), 6990 (24, test_streams.fsd24)]: 6991 for pattern in [test_streams.PATTERN01, 6992 test_streams.PATTERN02, 6993 test_streams.PATTERN03, 6994 test_streams.PATTERN04, 6995 test_streams.PATTERN05, 6996 test_streams.PATTERN06, 6997 test_streams.PATTERN07]: 6998 self.__test_reader__( 6999 test_streams.MD5Reader(fsd(pattern, 100)), 7000 len(pattern) * 100) 7001 7002 @FORMAT_TTA 7003 def test_wasted_bps(self): 7004 self.__test_reader__(test_streams.WastedBPS16(1000), 1000) 7005 7006 @FORMAT_TTA 7007 def test_silence(self): 7008 for (channels, mask) in [ 7009 (1, audiotools.ChannelMask.from_channels(1)), 7010 (2, audiotools.ChannelMask.from_channels(2)), 7011 (4, audiotools.ChannelMask.from_fields(front_left=True, 7012 front_right=True, 7013 back_left=True, 7014 back_right=True)), 7015 (8, audiotools.ChannelMask(0))]: 7016 for bps in [8, 16, 24]: 7017 self.__test_reader__( 7018 MD5_Reader( 7019 EXACT_SILENCE_PCM_Reader( 7020 pcm_frames=65536, 7021 sample_rate=44100, 7022 channels=channels, 7023 channel_mask=mask, 7024 bits_per_sample=bps)), 7025 65536) 7026 7027 @FORMAT_TTA 7028 def test_noise(self): 7029 for (channels, mask) in [ 7030 (1, audiotools.ChannelMask.from_channels(1)), 7031 (2, audiotools.ChannelMask.from_channels(2)), 7032 (4, audiotools.ChannelMask.from_fields(front_left=True, 7033 front_right=True, 7034 back_left=True, 7035 back_right=True)), 7036 (8, audiotools.ChannelMask(0))]: 7037 for bps in [8, 16, 24]: 7038 self.__test_reader__( 7039 MD5_Reader( 7040 EXACT_RANDOM_PCM_Reader( 7041 pcm_frames=65536, 7042 sample_rate=44100, 7043 channels=channels, 7044 channel_mask=mask, 7045 bits_per_sample=bps)), 7046 65536) 7047 7048 @FORMAT_TTA 7049 def test_sines(self): 7050 for g in self.__stream_variations__(): 7051 self.__test_reader__(g, 200000) 7052 7053 @FORMAT_TTA 7054 def test_multichannel(self): 7055 def __permutations__(executables, options, total): 7056 if total == 0: 7057 yield [] 7058 else: 7059 for (executable, option) in zip(executables, 7060 options): 7061 for permutation in __permutations__(executables, 7062 options, 7063 total - 1): 7064 yield [executable(**option)] + permutation 7065 7066 for (channels, mask) in [(2, 0x3), (3, 0x7), (4, 0x33), 7067 (5, 0x3B), (6, 0x3F)]: 7068 for readers in __permutations__( 7069 [EXACT_BLANK_PCM_Reader, 7070 EXACT_RANDOM_PCM_Reader, 7071 test_streams.Sine16_Mono], 7072 [{"pcm_frames": 100, 7073 "sample_rate": 44100, 7074 "channels": 1, 7075 "bits_per_sample": 16}, 7076 {"pcm_frames": 100, 7077 "sample_rate": 44100, 7078 "channels": 1, 7079 "bits_per_sample": 16}, 7080 {"pcm_frames": 100, 7081 "sample_rate": 44100, 7082 "f1": 441.0, 7083 "a1": 0.61, 7084 "f2": 661.5, 7085 "a2": 0.37}], channels): 7086 self.__test_reader__( 7087 MD5_Reader(Join_Reader(readers, mask)), 7088 100) 7089 7090 @FORMAT_TTA 7091 def test_fractional(self): 7092 for pcm_frames in [46078, 46079, 46080, 46081, 46082]: 7093 self.__test_reader__( 7094 MD5_Reader( 7095 EXACT_RANDOM_PCM_Reader( 7096 pcm_frames=pcm_frames, 7097 sample_rate=44100, 7098 channels=2, 7099 bits_per_sample=16)), 7100 pcm_frames) 7101 7102 @FORMAT_TTA 7103 def test_python_codec(self): 7104 def test_python_reader(pcmreader, pcm_frames): 7105 if not audiotools.BIN.can_execute(audiotools.BIN["tta"]): 7106 self.assertTrue( 7107 False, 7108 "reference TrueAudio binary tta(1) required for this test") 7109 7110 from audiotools.py_encoders import encode_tta 7111 from audiotools.py_decoders import TTADecoder as TTADecoder1 7112 from audiotools.decoders import TTADecoder as TTADecoder2 7113 7114 devnull = open(os.devnull, "wb") 7115 7116 # encode file using Python-based encoder 7117 temp_tta_file = tempfile.NamedTemporaryFile(suffix=".tta") 7118 7119 self.encode(temp_tta_file.name, 7120 pcmreader, 7121 encoding_function=encode_tta) 7122 7123 # verify against output of Python encoder 7124 # against reference tta decoder 7125 if (pcmreader.bits_per_sample > 8) and (pcmreader.channels <= 6): 7126 # reference decoder doesn't like 8 bit .wav files?! 7127 # or files with too many channels? 7128 temp_wav_file = tempfile.NamedTemporaryFile(suffix=".wav") 7129 sub = subprocess.Popen([audiotools.BIN["tta"], 7130 "-d", temp_tta_file.name, 7131 temp_wav_file.name], 7132 stdout=devnull, 7133 stderr=devnull) 7134 self.assertEqual(sub.wait(), 0, 7135 "tta decode error on %s" % (repr(pcmreader))) 7136 7137 self.assertTrue( 7138 audiotools.pcm_cmp( 7139 TTADecoder2(open(temp_tta_file.name, "rb")), 7140 audiotools.WaveAudio(temp_wav_file.name).to_pcm())) 7141 7142 # verify contents of file decoded by 7143 # Python-based decoder against contents decoded by 7144 # C-based decoder 7145 self.assertTrue( 7146 audiotools.pcm_cmp( 7147 TTADecoder1(temp_tta_file.name), 7148 TTADecoder2(open(temp_tta_file.name, "rb")))) 7149 7150 # perform tests again with total_pcm_frames indicated 7151 pcmreader.reset() 7152 7153 self.encode(temp_tta_file.name, 7154 pcmreader, 7155 total_pcm_frames=pcm_frames, 7156 encoding_function=encode_tta) 7157 7158 # verify against output of Python encoder 7159 # against reference tta decoder 7160 if (pcmreader.bits_per_sample > 8) and (pcmreader.channels <= 6): 7161 # reference decoder doesn't like 8 bit .wav files?! 7162 # or files with too many channels? 7163 temp_wav_file = tempfile.NamedTemporaryFile(suffix=".wav") 7164 sub = subprocess.Popen([audiotools.BIN["tta"], 7165 "-d", temp_tta_file.name, 7166 temp_wav_file.name], 7167 stdout=devnull, 7168 stderr=devnull) 7169 self.assertEqual(sub.wait(), 0, 7170 "tta decode error on %s" % (repr(pcmreader))) 7171 7172 self.assertTrue( 7173 audiotools.pcm_cmp( 7174 TTADecoder2(open(temp_tta_file.name, "rb")), 7175 audiotools.WaveAudio(temp_wav_file.name).to_pcm())) 7176 7177 # verify contents of file decoded by 7178 # Python-based decoder against contents decoded by 7179 # C-based decoder 7180 self.assertTrue( 7181 audiotools.pcm_cmp( 7182 TTADecoder1(temp_tta_file.name), 7183 TTADecoder2(open(temp_tta_file.name, "rb")))) 7184 7185 temp_tta_file.close() 7186 7187 devnull.close() 7188 7189 # test small files 7190 for g in [test_streams.Generate01, 7191 test_streams.Generate02]: 7192 test_python_reader(g(44100), 1) 7193 for g in [test_streams.Generate03, 7194 test_streams.Generate04]: 7195 test_python_reader(g(44100), 5) 7196 7197 # test full-scale deflection 7198 for (bps, fsd) in [(8, test_streams.fsd8), 7199 (16, test_streams.fsd16), 7200 (24, test_streams.fsd24)]: 7201 for pattern in [test_streams.PATTERN01, 7202 test_streams.PATTERN02, 7203 test_streams.PATTERN03, 7204 test_streams.PATTERN04, 7205 test_streams.PATTERN05, 7206 test_streams.PATTERN06, 7207 test_streams.PATTERN07]: 7208 test_python_reader(fsd(pattern, 100), len(pattern) * 100) 7209 7210 # test silence 7211 for g in [test_streams.Silence8_Mono(5000, 48000), 7212 test_streams.Silence8_Stereo(5000, 48000), 7213 test_streams.Silence16_Mono(5000, 48000), 7214 test_streams.Silence16_Stereo(5000, 48000), 7215 test_streams.Silence24_Mono(5000, 48000), 7216 test_streams.Silence24_Stereo(5000, 48000)]: 7217 test_python_reader(g, 5000) 7218 7219 # test sines 7220 for g in [test_streams.Sine8_Mono(5000, 48000, 7221 441.0, 0.50, 441.0, 0.49), 7222 test_streams.Sine8_Stereo(5000, 48000, 7223 441.0, 0.50, 441.0, 0.49, 1.0), 7224 test_streams.Sine16_Mono(5000, 48000, 7225 441.0, 0.50, 441.0, 0.49), 7226 test_streams.Sine16_Stereo(5000, 48000, 7227 441.0, 0.50, 441.0, 0.49, 1.0), 7228 test_streams.Sine24_Mono(5000, 48000, 7229 441.0, 0.50, 441.0, 0.49), 7230 test_streams.Sine24_Stereo(5000, 48000, 7231 441.0, 0.50, 441.0, 0.49, 1.0), 7232 test_streams.Simple_Sine(5000, 44100, 0x7, 8, 7233 (25, 10000), 7234 (50, 20000), 7235 (120, 30000)), 7236 test_streams.Simple_Sine(5000, 44100, 0x33, 8, 7237 (25, 10000), 7238 (50, 20000), 7239 (75, 30000), 7240 (65, 40000)), 7241 test_streams.Simple_Sine(5000, 44100, 0x37, 8, 7242 (25, 10000), 7243 (35, 15000), 7244 (45, 20000), 7245 (50, 25000), 7246 (55, 30000)), 7247 test_streams.Simple_Sine(5000, 44100, 0x3F, 8, 7248 (25, 10000), 7249 (45, 15000), 7250 (65, 20000), 7251 (85, 25000), 7252 (105, 30000), 7253 (120, 35000)), 7254 test_streams.Simple_Sine(5000, 44100, 0x7, 16, 7255 (6400, 10000), 7256 (12800, 20000), 7257 (30720, 30000)), 7258 test_streams.Simple_Sine(5000, 44100, 0x33, 16, 7259 (6400, 10000), 7260 (12800, 20000), 7261 (19200, 30000), 7262 (16640, 40000)), 7263 test_streams.Simple_Sine(5000, 44100, 0x37, 16, 7264 (6400, 10000), 7265 (8960, 15000), 7266 (11520, 20000), 7267 (12800, 25000), 7268 (14080, 30000)), 7269 test_streams.Simple_Sine(5000, 44100, 0x3F, 16, 7270 (6400, 10000), 7271 (11520, 15000), 7272 (16640, 20000), 7273 (21760, 25000), 7274 (26880, 30000), 7275 (30720, 35000)), 7276 test_streams.Simple_Sine(5000, 44100, 0x7, 24, 7277 (1638400, 10000), 7278 (3276800, 20000), 7279 (7864320, 30000)), 7280 test_streams.Simple_Sine(5000, 44100, 0x33, 24, 7281 (1638400, 10000), 7282 (3276800, 20000), 7283 (4915200, 30000), 7284 (4259840, 40000)), 7285 test_streams.Simple_Sine(5000, 44100, 0x37, 24, 7286 (1638400, 10000), 7287 (2293760, 15000), 7288 (2949120, 20000), 7289 (3276800, 25000), 7290 (3604480, 30000)), 7291 test_streams.Simple_Sine(5000, 44100, 0x3F, 24, 7292 (1638400, 10000), 7293 (2949120, 15000), 7294 (4259840, 20000), 7295 (5570560, 25000), 7296 (6881280, 30000), 7297 (7864320, 35000))]: 7298 test_python_reader(g, 5000) 7299 7300 # test wasted BPS 7301 test_python_reader(test_streams.WastedBPS16(1000), 1000) 7302 7303 # test fractional blocks 7304 for pcm_frames in [46078, 46079, 46080, 46081, 46082]: 7305 test_python_reader( 7306 MD5_Reader( 7307 EXACT_RANDOM_PCM_Reader( 7308 pcm_frames=pcm_frames, 7309 sample_rate=44100, 7310 channels=2, 7311 bits_per_sample=16)), 7312 pcm_frames) 7313 7314 @FORMAT_TTA 7315 def test_clean(self): 7316 # check TTA file with double ID3 tags 7317 7318 from audiotools.text import CLEAN_REMOVE_DUPLICATE_ID3V2 7319 7320 original_size = os.path.getsize("tta-id3-2.tta") 7321 7322 track = audiotools.open("tta-id3-2.tta") 7323 7324 # ensure second ID3 tag is ignored 7325 self.assertEqual(track.get_metadata().track_name, u"Title1") 7326 7327 # ensure duplicate ID3v2 tag is detected and removed 7328 fixes = track.clean() 7329 self.assertEqual(fixes, 7330 [CLEAN_REMOVE_DUPLICATE_ID3V2]) 7331 temp = tempfile.NamedTemporaryFile(suffix=".tta") 7332 try: 7333 fixes = track.clean(temp.name) 7334 self.assertEqual(fixes, 7335 [CLEAN_REMOVE_DUPLICATE_ID3V2]) 7336 track2 = audiotools.open(temp.name) 7337 self.assertEqual(track2.get_metadata(), track.get_metadata()) 7338 # ensure new file is exactly one tag smaller 7339 # and the padding is preserved in the old tag 7340 self.assertEqual(os.path.getsize(temp.name), 7341 original_size - 0x46A) 7342 finally: 7343 temp.close() 7344 7345 7346class SineStreamTest(unittest.TestCase): 7347 @FORMAT_SINES 7348 def test_init(self): 7349 from audiotools.decoders import Sine_Mono 7350 from audiotools.decoders import Sine_Stereo 7351 from audiotools.decoders import Sine_Simple 7352 7353 # ensure that failed inits don't make Python explode 7354 self.assertRaises(ValueError, Sine_Mono, 7355 -1, 4000, 44100, 1.0, 1.0, 1.0, 1.0) 7356 self.assertRaises(ValueError, Sine_Mono, 7357 16, -1, 44100, 1.0, 1.0, 1.0, 1.0) 7358 self.assertRaises(ValueError, Sine_Mono, 7359 16, 4000, -1, 1.0, 1.0, 1.0, 1.0) 7360 7361 self.assertRaises(ValueError, Sine_Stereo, 7362 -1, 4000, 44100, 1.0, 1.0, 1.0, 1.0, 1.0) 7363 self.assertRaises(ValueError, Sine_Stereo, 7364 16, -1, 44100, 1.0, 1.0, 1.0, 1.0, 1.0) 7365 self.assertRaises(ValueError, Sine_Stereo, 7366 16, 4000, -1, 1.0, 1.0, 1.0, 1.0, 1.0) 7367 7368 self.assertRaises(ValueError, Sine_Simple, 7369 -1, 4000, 44100, 100, 100) 7370 self.assertRaises(ValueError, Sine_Simple, 7371 16, -1, 44100, 100, 100) 7372 self.assertRaises(ValueError, Sine_Simple, 7373 16, 4000, -1, 100, 100) 7374