Coverage for tests/test_audio_utils.py: 100%

97 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-09 04:47 +0000

1#!/usr/bin/env python3 

2 

3import os 

4import pytest 

5 

6from unittest import TestCase 

7 

8from media_utils import empty_audio_file 

9from media_utils import get_audio_file_info 

10from media_utils import get_audio_duration 

11from file_utils import binary_to_base64 

12from file_utils import base64_to_binary 

13from media_utils import chunk_audio_base64 

14from media_utils import base64_to_audio_file 

15from file_utils import read_file_base64 

16from media_utils import fit_audio_to_duration 

17from media_utils import get_aligned_duration 

18 

19 

20class TestAudioUtils(TestCase): 

21 """Test cases for the empty_audio_file function.""" 

22 

23 def test_empty(self) -> None: 

24 """Test creating an empty audio file.""" 

25 # Create an empty audio file 

26 audio_path = empty_audio_file(duration_seconds=0.5) 

27 

28 # Verify file was created 

29 self.assertTrue(os.path.exists(audio_path)) 

30 self.assertTrue(audio_path.endswith('.wav')) 

31 

32 # Verify audio file info 

33 audio_info = get_audio_file_info(audio_path) 

34 self.assertEqual(audio_info['duration_seconds'], 0.5) 

35 self.assertEqual(audio_info['samplerate'], 44100) 

36 self.assertEqual(audio_info['channels'], 1) 

37 

38 with self.assertRaises(FileNotFoundError): 

39 get_audio_file_info("nofile.wav") 

40 with self.assertRaises(TypeError): 

41 get_audio_file_info(["list", "of", "files"]) # type: ignore[arg-type] 

42 

43 # Clean up the created file 

44 os.unlink(audio_path) 

45 

46 def test_chunk(self) -> None: 

47 """Test chunking audio base64 data.""" 

48 audio_path = empty_audio_file(duration_seconds=3.2) 

49 with open(audio_path, "rb") as f: 

50 audio_bytes = f.read() 

51 audio_base64 = binary_to_base64(audio_bytes) 

52 audio_base64 = chunk_audio_base64( 

53 audio_base64, 

54 start_seconds=1.0, 

55 end_seconds=2.0) 

56 audio_duration = get_audio_duration(audio_base64) 

57 self.assertAlmostEqual(audio_duration, 1.0, delta=0.1) 

58 

59 audio_bytes = base64_to_binary(audio_base64) 

60 audio_duration = get_audio_duration(audio_bytes) 

61 self.assertAlmostEqual(audio_duration, 1.0, delta=0.1) 

62 

63 with self.assertRaises(TypeError): 

64 chunk_audio_base64(audio_bytes) # type: ignore[arg-type] 

65 with self.assertRaises(ValueError): 

66 chunk_audio_base64(audio_base64, start_seconds=-1, end_seconds=0.2) 

67 with self.assertRaises(ValueError): 

68 chunk_audio_base64(audio_base64, start_seconds=1.0, end_seconds=5.0) 

69 

70 def test_get_aligned_duration(self) -> None: 

71 VAE = 4 

72 FPS = 23 

73 self.assertEqual(get_aligned_duration(0.0, fps=FPS, vae=VAE), 0.0) 

74 self.assertAlmostEqual(get_aligned_duration(3.000, fps=FPS, vae=VAE), 3.000, delta=0.01) 

75 self.assertAlmostEqual(get_aligned_duration(3.001, fps=FPS, vae=VAE), 3.174, delta=0.01) 

76 self.assertAlmostEqual(get_aligned_duration(3.001, fps=FPS, vae=VAE), 3 + (VAE / FPS), delta=0.01) 

77 self.assertAlmostEqual(get_aligned_duration(4.500, fps=FPS, vae=VAE), 4.565, delta=0.01) 

78 self.assertAlmostEqual(get_aligned_duration(4.565, fps=FPS, vae=VAE), 4.565, delta=0.01) 

79 self.assertAlmostEqual(get_aligned_duration(4.566, fps=FPS, vae=VAE), 4.739, delta=0.01) 

80 

81 

82@pytest.mark.asyncio 

83async def test_base64() -> None: 

84 """Test converting audio file to base64 and back.""" 

85 audio_path = empty_audio_file(duration_seconds=0.8) 

86 audio_base64 = await read_file_base64(audio_path) 

87 audio_duration = get_audio_duration(audio_base64) 

88 assert audio_duration == 0.8 

89 

90 audio_path_2 = await base64_to_audio_file(audio_base64) 

91 

92 with pytest.raises(TypeError): 

93 await read_file_base64(12345) # type: ignore[arg-type] 

94 with pytest.raises(TypeError): 

95 await base64_to_audio_file(12345) # type: ignore[arg-type] 

96 with pytest.raises(FileNotFoundError): 

97 await read_file_base64("nonexisting.wav") 

98 

99 # Clean up files 

100 os.unlink(audio_path) 

101 os.unlink(audio_path_2) 

102 

103 

104@pytest.mark.asyncio 

105async def test_resize_wav() -> None: 

106 audio_path = "tests/data/sample.wav" 

107 audio_base64 = await read_file_base64(audio_path) 

108 duration_secs = get_audio_duration(audio_base64) 

109 assert duration_secs == 24.025 

110 

111 audio_shorter_path = fit_audio_to_duration(audio_path, 10.0) 

112 audio_shorter_base64 = await read_file_base64(audio_shorter_path) 

113 duration_shorter_secs = get_audio_duration(audio_shorter_base64) 

114 assert duration_shorter_secs == 10.0 

115 

116 audio_longer_path = fit_audio_to_duration(audio_shorter_path, 10.6) 

117 audio_longer_base64 = await read_file_base64(audio_longer_path) 

118 duration_longer_secs = get_audio_duration(audio_longer_base64) 

119 assert duration_longer_secs == 10.6 

120 

121 os.unlink(audio_shorter_path) 

122 os.unlink(audio_longer_path) 

123 

124 with pytest.raises(TypeError): 

125 get_audio_duration(None) # type: ignore[arg-type] 

126 with pytest.raises(TypeError): 

127 get_audio_duration([]) # type: ignore[arg-type] 

128 with pytest.raises(TypeError): 

129 fit_audio_to_duration(1, 2) # type: ignore[arg-type] 

130 with pytest.raises(TypeError): 

131 fit_audio_to_duration(None, 1) # type: ignore[arg-type] 

132 with pytest.raises(FileNotFoundError): 

133 fit_audio_to_duration("nonexisting.wav", 10.6) 

134 with pytest.raises(ValueError): 

135 fit_audio_to_duration(audio_path, -0.2)