amine@86: import unittest amine@88: from random import random amine@86: from genty import genty, genty_dataset amine@97: from auditok import AudioRegion, AudioParameterError amine@86: amine@86: amine@88: def _make_random_length_regions( amine@88: byte_seq, sampling_rate, sample_width, channels amine@88: ): amine@88: regions = [] amine@88: for b in byte_seq: amine@88: duration = round(random() * 10, 6) amine@95: data = b * int(duration * sampling_rate) * sample_width * channels amine@88: start = round(random() * 13, 3) amine@88: region = AudioRegion( amine@88: data, start, sampling_rate, sample_width, channels amine@88: ) amine@88: regions.append(region) amine@88: return regions amine@88: amine@88: amine@86: @genty amine@86: class TestAudioRegion(unittest.TestCase): amine@86: @genty_dataset( amine@86: simple=(b"\0" * 8000, 0, 8000, 1, 1, 1, 1, 1000), amine@86: one_ms_less_than_1_sec=( amine@86: b"\0" * 7992, amine@86: 0, amine@86: 8000, amine@86: 1, amine@86: 1, amine@86: 0.999, amine@86: 0.999, amine@86: 999, amine@86: ), amine@86: tree_quarter_ms_less_than_1_sec=( amine@86: b"\0" * 7994, amine@86: 0, amine@86: 8000, amine@86: 1, amine@86: 1, amine@86: 0.99925, amine@86: 0.99925, amine@86: 999, amine@86: ), amine@86: half_ms_less_than_1_sec=( amine@86: b"\0" * 7996, amine@86: 0, amine@86: 8000, amine@86: 1, amine@86: 1, amine@86: 0.9995, amine@86: 0.9995, amine@86: 1000, amine@86: ), amine@86: quarter_ms_less_than_1_sec=( amine@86: b"\0" * 7998, amine@86: 0, amine@86: 8000, amine@86: 1, amine@86: 1, amine@86: 0.99975, amine@86: 0.99975, amine@86: 1000, amine@86: ), amine@86: simple_sample_width_2=(b"\0" * 8000 * 2, 0, 8000, 2, 1, 1, 1, 1000), amine@86: simple_stereo=(b"\0" * 8000 * 2, 0, 8000, 1, 2, 1, 1, 1000), amine@86: simple_multichannel=(b"\0" * 8000 * 5, 0, 8000, 1, 5, 1, 1, 1000), amine@86: simple_sample_width_2_multichannel=( amine@86: b"\0" * 8000 * 2 * 5, amine@86: 0, amine@86: 8000, amine@86: 2, amine@86: 5, amine@86: 1, amine@86: 1, amine@86: 1000, amine@86: ), amine@86: one_ms_less_than_1s_sw_2_multichannel=( amine@86: b"\0" * 7992 * 2 * 5, amine@86: 0, amine@86: 8000, amine@86: 2, amine@86: 5, amine@86: 0.999, amine@86: 0.999, amine@86: 999, amine@86: ), amine@86: tree_qrt_ms_lt_1_s_sw_2_multichannel=( amine@86: b"\0" * 7994 * 2 * 5, amine@86: 0, amine@86: 8000, amine@86: 2, amine@86: 5, amine@86: 0.99925, amine@86: 0.99925, amine@86: 999, amine@86: ), amine@86: half_ms_lt_1s_sw_2_multichannel=( amine@86: b"\0" * 7996 * 2 * 5, amine@86: 0, amine@86: 8000, amine@86: 2, amine@86: 5, amine@86: 0.9995, amine@86: 0.9995, amine@86: 1000, amine@86: ), amine@86: quarter_ms_lt_1s_sw_2_multichannel=( amine@86: b"\0" * 7998 * 2 * 5, amine@86: 0, amine@86: 8000, amine@86: 2, amine@86: 5, amine@86: 0.99975, amine@86: 0.99975, amine@86: 1000, amine@86: ), amine@86: arbitrary_length_1=( amine@86: b"\0" * int(8000 * 1.33), amine@86: 2.7, amine@86: 8000, amine@86: 1, amine@86: 1, amine@86: 4.03, amine@86: 1.33, amine@86: 1330, amine@86: ), amine@86: arbitrary_length_2=( amine@86: b"\0" * int(8000 * 0.476), amine@86: 11.568, amine@86: 8000, amine@86: 1, amine@86: 1, amine@86: 12.044, amine@86: 0.476, amine@86: 476, amine@86: ), amine@86: arbitrary_length_sw_2_multichannel=( amine@86: b"\0" * int(8000 * 1.711) * 2 * 3, amine@86: 9.415, amine@86: 8000, amine@86: 2, amine@86: 3, amine@86: 11.126, amine@86: 1.711, amine@86: 1711, amine@86: ), amine@86: arbitrary_samplig_rate=( amine@86: b"\0" * int(3172 * 1.318), amine@86: 17.236, amine@86: 3172, amine@86: 1, amine@86: 1, amine@86: 17.236 + int(3172 * 1.318) / 3172, amine@86: int(3172 * 1.318) / 3172, amine@86: 1318, amine@86: ), amine@86: arbitrary_sr_sw_2_multichannel=( amine@86: b"\0" * int(11317 * 0.716) * 2 * 3, amine@86: 18.811, amine@86: 11317, amine@86: 2, amine@86: 3, amine@86: 18.811 + int(11317 * 0.716) / 11317, amine@86: int(11317 * 0.716) / 11317, amine@86: 716, amine@86: ), amine@86: ) amine@86: def test_creation( amine@86: self, amine@86: data, amine@86: start, amine@86: sampling_rate, amine@86: sample_width, amine@86: channels, amine@86: expected_end, amine@86: expected_duration_s, amine@86: expected_duration_ms, amine@86: ): amine@86: region = AudioRegion( amine@86: data, start, sampling_rate, sample_width, channels amine@86: ) amine@86: self.assertEqual(region.sampling_rate, sampling_rate) amine@86: self.assertEqual(region.sr, sampling_rate) amine@86: self.assertEqual(region.sample_width, sample_width) amine@86: self.assertEqual(region.sw, sample_width) amine@86: self.assertEqual(region.channels, channels) amine@86: self.assertEqual(region.ch, channels) amine@86: self.assertEqual(region.start, start) amine@86: self.assertEqual(region.end, expected_end) amine@86: self.assertEqual(region.duration, expected_duration_s) amine@86: self.assertEqual(len(region), expected_duration_ms) amine@86: self.assertEqual(bytes(region), data) amine@88: amine@97: def test_creation_invalid_data_exception(self): amine@97: with self.assertRaises(AudioParameterError) as audio_param_err: amine@97: _ = AudioRegion( amine@97: data=b"ABCDEFGHI", amine@97: start=0, amine@97: sampling_rate=8, amine@97: sample_width=2, amine@97: channels=1, amine@97: ) amine@97: self.assertEqual( amine@97: "The length of audio data must be an integer " amine@97: "multiple of `sample_width * channels`", amine@97: str(audio_param_err.exception), amine@97: ) amine@97: amine@88: @genty_dataset( amine@88: simple=(8000, 1, 1), amine@88: stereo_sw_2=(8000, 2, 2), amine@88: arbitray_sr_multichannel=(5413, 2, 3), amine@88: ) amine@88: def test_concatenation(self, sampling_rate, sample_width, channels): amine@88: amine@88: region_1, region_2 = _make_random_length_regions( amine@88: [b"a", b"b"], sampling_rate, sample_width, channels amine@88: ) amine@88: amine@88: expected_start = region_1.start amine@88: expected_duration = region_1.duration + region_2.duration amine@88: expected_end = expected_start + expected_duration amine@88: expected_data = bytes(region_1) + bytes(region_2) amine@88: concat_region = region_1 + region_2 amine@88: amine@88: self.assertEqual(concat_region.start, expected_start) amine@88: self.assertAlmostEqual(concat_region.end, expected_end, places=6) amine@88: self.assertAlmostEqual( amine@88: concat_region.duration, expected_duration, places=6 amine@88: ) amine@88: self.assertEqual(bytes(concat_region), expected_data) amine@88: # due to the behavior of `round` len(concat_region) does not always amine@88: # equal len(region_1) + len(region_2) amine@88: # Exmaple if both regions are 1.0005 seconds long, then: amine@88: # len(region_1) == len(region_2) == round(1.0005) == 1000 amine@88: # and: amine@88: # region_1.duration + region_2.duration == 1.0005 * 2 = 2.001 amine@88: # and: amine@88: # len(region_3) == round(2.001 * 1000) = 2001 amine@88: # != len(region_1) + len(region_2) amine@88: self.assertEqual(len(concat_region), round(expected_duration * 1000)) amine@88: amine@88: @genty_dataset( amine@88: simple=(8000, 1, 1), amine@88: stereo_sw_2=(8000, 2, 2), amine@88: arbitray_sr_multichannel=(5413, 2, 3), amine@88: ) amine@88: def test_concatenation_many(self, sampling_rate, sample_width, channels): amine@88: amine@88: regions = _make_random_length_regions( amine@88: [b"a", b"b", b"c"], sampling_rate, sample_width, channels amine@88: ) amine@88: expected_start = regions[0].start amine@88: expected_duration = sum(r.duration for r in regions) amine@88: expected_end = expected_start + expected_duration amine@88: expected_data = b"".join(bytes(r) for r in regions) amine@88: concat_region = sum(regions) amine@88: amine@88: self.assertEqual(concat_region.start, expected_start) amine@88: self.assertAlmostEqual(concat_region.end, expected_end, places=6) amine@88: self.assertAlmostEqual( amine@88: concat_region.duration, expected_duration, places=6 amine@88: ) amine@88: self.assertEqual(bytes(concat_region), expected_data) amine@88: # see test_concatenation amine@88: self.assertEqual(len(concat_region), round(expected_duration * 1000)) amine@88: amine@88: def test_concatenation_different_sampling_rate_error(self): amine@88: amine@88: region_1 = AudioRegion(b"a" * 100, 0, 8000, 1, 1) amine@88: region_2 = AudioRegion(b"b" * 100, 0, 3000, 1, 1) amine@88: amine@88: with self.assertRaises(ValueError) as val_err: amine@88: region_1 + region_2 amine@88: self.assertEqual( amine@88: "Can only concatenate AudioRegions of the same " amine@88: "sampling rate (8000 != 3000)", amine@88: str(val_err.exception), amine@88: ) amine@88: amine@88: def test_concatenation_different_sample_width_error(self): amine@88: amine@88: region_1 = AudioRegion(b"a" * 100, 0, 8000, 2, 1) amine@88: region_2 = AudioRegion(b"b" * 100, 0, 8000, 4, 1) amine@88: amine@88: with self.assertRaises(ValueError) as val_err: amine@88: region_1 + region_2 amine@88: self.assertEqual( amine@88: "Can only concatenate AudioRegions of the same " amine@88: "sample width (2 != 4)", amine@88: str(val_err.exception), amine@88: ) amine@88: amine@88: def test_concatenation_different_number_of_channels_error(self): amine@88: amine@88: region_1 = AudioRegion(b"a" * 100, 0, 8000, 1, 1) amine@88: region_2 = AudioRegion(b"b" * 100, 0, 8000, 1, 2) amine@88: amine@88: with self.assertRaises(ValueError) as val_err: amine@88: region_1 + region_2 amine@88: self.assertEqual( amine@88: "Can only concatenate AudioRegions of the same " amine@88: "number of channels (1 != 2)", amine@88: str(val_err.exception), amine@88: )