您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

285 行
12 KiB

  1. """test_feature_extractors.py - test pipeline feature extractors"""
  2. import unittest
  3. import os
  4. import random
  5. import pytest
  6. import pipeline.feature_extractors as extractors
  7. from pipeline.utils import Source, SourceMedia # technically makes this an integration test, but...
  8. from unittest.mock import patch, mock_open
  9. class TestSource():
  10. """Provide utils.Source for testing"""
  11. def one_colour_silent_audio(self):
  12. """Provide a source with a silent mono-colour video"""
  13. TEST_DIR = os.path.dirname(os.path.realpath(__file__))
  14. SAMPLE_VIDEO = f"{TEST_DIR}/sample_videos/test_video_red_silentaudio.mp4" # silent video definitely has no laughter
  15. return Source(source=SAMPLE_VIDEO, path=SAMPLE_VIDEO, provider="test")
  16. class TestSourceMedia():
  17. """Provide utils.SourceMedia for testing"""
  18. def one_colour_silent_audio(self):
  19. """Provide a source with a silent mono-colour video"""
  20. return SourceMedia(sources=[TestSource().one_colour_silent_audio()])
  21. class MockReadJSON():
  22. """Mock read_json"""
  23. def mock_read_json_from_file(self, *args, **kwargs):
  24. """Mock _read_json_from_file()"""
  25. rJSON = [{"interval": {"start": 0.0, "duration": 1.0},
  26. "source": {"source": "test_video_red_silentaudio.mp4",
  27. "path": "test_video_red_silentaudio.mp4",
  28. "provider": "mock"},
  29. "feature_extractor": "MockFeatureExtractor",
  30. "score": 0.5
  31. }]
  32. return rJSON
  33. class TestLaughterFeatureExtractor(unittest.TestCase):
  34. def _mock_laughdetect_callout(self, *args, **kwargs):
  35. """Mock _laughdetect callout
  36. **kwargs:
  37. - n : int >=0, number of laughter instances to generate
  38. Return a list of 2-tuple floats (start, end) representing laughter instances
  39. """
  40. laughs = []
  41. n = kwargs.get("n", 0)
  42. for i in range(n):
  43. laughs.append((i, i+1))
  44. return laughs
  45. def _mock_run_get_output(self, *args, **kwargs) -> str:
  46. """Mock run_get_output callout
  47. kwargs:
  48. - n : int >=0, number of laughter instances to generate
  49. Return a string of laughter instance of the form:
  50. instance: (1.234, 5.678)
  51. """
  52. # TODO: decide if we want non-"instance" output for testing parsing?
  53. # (maybe)
  54. output = []
  55. n = kwargs.get("n", 0)
  56. for i in range(n):
  57. output.append(f"instance: ({i}.{i+1}{i+2}{i+3}, {i+4}.{i+5}{i+6}{i+7})")
  58. return "\n".join(output)
  59. def _sgo5(self, *args, **kwargs):
  60. """Mock run_get_output callout"""
  61. return self._mock_run_get_output(*args, **kwargs, n=5)
  62. """Test LaughterFeatureExtractor"""
  63. def test_init(self):
  64. test_extractor = extractors.LaughterFeatureExtractor()
  65. self.assertTrue(test_extractor)
  66. def test_setup_noinput(self):
  67. """test setup - no input files"""
  68. test_extractor = extractors.LaughterFeatureExtractor()
  69. with self.assertRaises(ValueError):
  70. test_extractor.setup()
  71. # NB test WITH sources implicitly tested in test_extract
  72. @pytest.mark.slow
  73. def test_extract_mocked_nolaughs(self):
  74. """Test extract with mocked laughter detection - no laughs"""
  75. video_source = TestSource().one_colour_silent_audio()
  76. test_extractor = extractors.LaughterFeatureExtractor(input_files=[video_source])
  77. test_extractor._laughdetect = self._mock_laughdetect_callout
  78. test_extractor.setup()
  79. test_extractor.run()
  80. test_extractor.teardown()
  81. self.assertEqual(len(test_extractor.features), 0)
  82. def test_extract_mocked_run_get_output_none(self):
  83. """Test extract with mocked laughter detection - no laughs"""
  84. video_source = TestSource().one_colour_silent_audio()
  85. test_extractor = extractors.LaughterFeatureExtractor(input_files=[video_source])
  86. test_extractor._run_get_output = self._mock_run_get_output
  87. test_extractor.setup()
  88. test_extractor.run()
  89. test_extractor.teardown()
  90. self.assertEqual(len(test_extractor.features), 0)
  91. def test_extract_mocked_run_get_output_5(self):
  92. """Test extract with mocked laughter detection - 5 laughs"""
  93. video_source = TestSource().one_colour_silent_audio()
  94. test_extractor = extractors.LaughterFeatureExtractor(input_files=[video_source])
  95. test_extractor._run_get_output = self._sgo5
  96. test_extractor.setup()
  97. test_extractor.run()
  98. test_extractor.teardown()
  99. self.assertEqual(len(test_extractor.features), 5)
  100. def test_run_get_output(self):
  101. """Test run_get_output"""
  102. video_source = TestSource().one_colour_silent_audio()
  103. test_extractor = extractors.LaughterFeatureExtractor(input_files=[video_source])
  104. test_cmd = ["echo", "foo"]
  105. test_extractor.setup()
  106. output = test_extractor._run_get_output(test_cmd)
  107. self.assertEqual(output, "foo\n")
  108. # TODO: add sample video with laughs to test _laughdetect()
  109. class TestRandomFeatureExtractor(unittest.TestCase):
  110. """Test RandomFeatureExtractor"""
  111. def test_init(self):
  112. test_extractor = extractors.RandomFeatureExtractor()
  113. self.assertTrue(test_extractor)
  114. def test_setup_noinput(self):
  115. """test setup - no input files"""
  116. test_extractor = extractors.RandomFeatureExtractor()
  117. with self.assertRaises(ValueError):
  118. test_extractor.setup()
  119. # NB test WITH sources implicitly tested in test_extract
  120. def test_extract_noinput(self):
  121. """Test extract with no input files"""
  122. test_extractor = extractors.RandomFeatureExtractor()
  123. with self.assertRaises(ValueError):
  124. test_extractor.run()
  125. def test_extract(self):
  126. """Test extract with input files"""
  127. video_source = TestSourceMedia().one_colour_silent_audio()
  128. test_extractor = extractors.RandomFeatureExtractor(input_files=video_source)
  129. test_extractor.setup()
  130. test_extractor.run()
  131. test_extractor.teardown()
  132. self.assertTrue(test_extractor.features)
  133. class TestLoudAudioFeatureExtractor(unittest.TestCase):
  134. """Test LoudAudioFeatureExtractor"""
  135. def _mock_loudnorm(self, *args, **kwargs):
  136. """Mock _loudnorm
  137. It returns a list of 2-tuple floats (time, loudness) representing loud audio instances
  138. """
  139. return [(0.0, 0.0), (1.0, 1.0), (2.0, 2.0), (3.0, 3.0), (4.0, 4.0)]
  140. def _mock_get_loudnessess(self, *args, length=100, min_loudness=-101, max_loudness=100,
  141. seed=42, **kwargs) -> list:
  142. """Mock _get_loudnesses()
  143. Parameters:
  144. - length : int >=0, number of loudness instances to generate
  145. - min_loudness : int, minimum loudness value (special value: -101 for "-inf")
  146. - max_loudness : int, maximum loudness value
  147. Note that int min/max loudness are divided by float 100
  148. to get the actual loudness value between -1.0 and 1.0
  149. Return a list of 2-tuple floats (timecode, loudness) representing loud audio instances
  150. """
  151. loudnesses = []
  152. random.seed(seed)
  153. for i in range(length):
  154. loudness = random.randint(min_loudness, max_loudness) / 100
  155. if min_loudness == -101:
  156. loudness = "-inf" if loudness == -1.01 else f"{loudness}"
  157. loudnesses.append((float(f"{i}.0"), float(loudness)))
  158. return loudnesses
  159. def test_init(self):
  160. video_source = TestSourceMedia().one_colour_silent_audio()
  161. test_extractor = extractors.LoudAudioFeatureExtractor(input_files=video_source)
  162. self.assertTrue(test_extractor)
  163. def test_init_noinput(self):
  164. """test init - no input files"""
  165. with self.assertRaises(ValueError):
  166. test_extractor = extractors.LoudAudioFeatureExtractor()
  167. def test_extract(self):
  168. """Test extract with input files"""
  169. video_source = TestSourceMedia().one_colour_silent_audio()
  170. test_extractor = extractors.LoudAudioFeatureExtractor(input_files=video_source)
  171. test_extractor.setup()
  172. test_extractor.run()
  173. test_extractor.teardown()
  174. self.assertEqual(test_extractor.features, [])
  175. def test_extract_mocked_loudnorm(self):
  176. """Test extract with mocked loudness detection"""
  177. video_source = TestSourceMedia().one_colour_silent_audio()
  178. test_extractor = extractors.LoudAudioFeatureExtractor(input_files=video_source)
  179. test_extractor._loudnorm = self._mock_loudnorm
  180. test_extractor.setup()
  181. test_extractor.run()
  182. test_extractor.teardown()
  183. self.assertEqual(len(test_extractor.features), 5)
  184. def test_extract_mocked_get_loudnesses(self):
  185. """Test extract with mocked loudness detection"""
  186. video_source = TestSourceMedia().one_colour_silent_audio()
  187. test_extractor = extractors.LoudAudioFeatureExtractor(input_files=video_source)
  188. test_extractor._get_loudnesses = self._mock_get_loudnessess
  189. test_extractor.setup()
  190. test_extractor.run()
  191. test_extractor.teardown()
  192. self.assertEqual(len(test_extractor.features), 100)
  193. # TODO: add sample video with loud audio to test _loudnessdetect()
  194. class TestVideoActivityFeatureExtractor(unittest.TestCase):
  195. """Test VideoActivityFeatureExtractor"""
  196. def test_init(self):
  197. video_source = TestSourceMedia().one_colour_silent_audio()
  198. test_extractor = extractors.VideoActivityFeatureExtractor(input_files=video_source)
  199. self.assertTrue(test_extractor)
  200. def test_init_noinput(self):
  201. """test init - no input files"""
  202. with self.assertRaises(ValueError):
  203. test_extractor = extractors.VideoActivityFeatureExtractor()
  204. def test_extract(self):
  205. """Test extract with basic input file runs with no errors"""
  206. video_source = TestSourceMedia().one_colour_silent_audio()
  207. test_extractor = extractors.VideoActivityFeatureExtractor(input_files=video_source)
  208. test_extractor.setup()
  209. test_extractor.run()
  210. test_extractor.teardown()
  211. self.assertTrue(test_extractor.features)
  212. # TODO: add sample video with activity to test _activitydetect()
  213. class TestJSONFeatureExtractor(unittest.TestCase):
  214. """Test JSONFeatureExtractor"""
  215. def test_init(self):
  216. video_source = TestSourceMedia().one_colour_silent_audio()
  217. test_extractor = extractors.JSONFeatureExtractor(input_files=video_source)
  218. self.assertTrue(test_extractor)
  219. def test_init_noinput(self):
  220. """test init - no input files"""
  221. with self.assertRaises(ValueError):
  222. test_extractor = extractors.JSONFeatureExtractor()
  223. def test_extract(self):
  224. """Test extract with basic input file runs with no errors"""
  225. video_source = TestSourceMedia().one_colour_silent_audio()
  226. test_extractor = extractors.JSONFeatureExtractor(input_files=video_source)
  227. # mock _read_json_from_file
  228. test_extractor._read_json_from_file = MockReadJSON().mock_read_json_from_file
  229. test_extractor.setup()
  230. test_extractor.run()
  231. test_extractor.teardown()
  232. self.assertTrue(test_extractor.features)
  233. def test_read_json_from_file(self):
  234. """Test _read_json_from_file"""
  235. video_source = TestSourceMedia().one_colour_silent_audio()
  236. test_extractor = extractors.JSONFeatureExtractor(input_files=video_source)
  237. m = unittest.mock.mock_open(read_data='[{"foo": "bar"}]')
  238. with unittest.mock.patch("builtins.open", m):
  239. test_extractor._read_json_from_file("foo.json")