Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

250 rader
10 KiB

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