test_video.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. import os
  2. import os.path
  3. import tempfile
  4. # import threading
  5. import unittest
  6. import mock
  7. from lvc import video
  8. import base
  9. class GetMediaInfoTest(base.Test):
  10. def assertClose(self, output, expected):
  11. diff = output - expected
  12. # abs(diff) < 0.2
  13. self.assertTrue(diff ** 2 < 0.04,
  14. "%s != %s" % (output, expected))
  15. def assertEqualOutput(self, filename, expected):
  16. full_path = os.path.join(self.testdata_dir, filename)
  17. try:
  18. output = video.get_media_info(full_path)
  19. except Exception as e:
  20. raise AssertionError(
  21. 'Error parsing %r\nException: %r\nOutput: %s' % (
  22. filename, e, video.get_ffmpeg_output(full_path)))
  23. duration_output = output.pop('duration', None)
  24. duration_expected = expected.pop('duration', None)
  25. if duration_output is not None and duration_expected is not None:
  26. self.assertClose(duration_output, duration_expected)
  27. else:
  28. # put them back in, let assertEqual handle the difference
  29. output['duration'] = duration_output
  30. expected['duration'] = duration_expected
  31. self.assertEqual(output, expected)
  32. def test_mp3_0(self):
  33. self.assertEqualOutput('mp3-0.mp3',
  34. {'container': 'mp3',
  35. 'audio_codec': 'mp3',
  36. 'title': 'Invisible Walls',
  37. 'artist': 'Revolution Void',
  38. 'album': 'Increase The Dosage',
  39. 'track': '1',
  40. 'genre': 'Blues',
  41. 'duration': 1.07})
  42. def test_mp3_1(self):
  43. self.assertEqualOutput('mp3-1.mp3',
  44. {'container': 'mp3',
  45. 'audio_codec': 'mp3',
  46. 'title': 'Race Lieu',
  47. 'artist': 'Ckz',
  48. 'album': 'The Heart EP',
  49. 'track': '2/5',
  50. 'duration': 1.07})
  51. def test_mp3_2(self):
  52. self.assertEqualOutput('mp3-2.mp3',
  53. {'container': 'mp3',
  54. 'audio_codec': 'mp3',
  55. 'artist': 'This American Life',
  56. 'genre': 'Podcast',
  57. 'title': '#426: Tough Room 2011',
  58. 'duration': 1.09})
  59. def test_theora(self):
  60. self.assertEqualOutput('theora.ogv',
  61. {'container': 'ogg',
  62. 'video_codec': 'theora',
  63. 'audio_codec': 'vorbis',
  64. 'width': 400,
  65. 'height': 304,
  66. 'duration': 5.0})
  67. def test_theora_with_ogg_extension(self):
  68. self.assertEqualOutput('theora_with_ogg_extension.ogg',
  69. {'container': 'ogg',
  70. 'video_codec': 'theora',
  71. 'width': 320,
  72. 'height': 240,
  73. 'duration': 0.1})
  74. def test_webm_0(self):
  75. self.assertEqualOutput('webm-0.webm',
  76. {'container': ['matroska', 'webm'],
  77. 'video_codec': 'vp8',
  78. 'width': 1920,
  79. 'height': 912,
  80. 'duration': 0.43})
  81. def test_mp4_0(self):
  82. self.assertEqualOutput('mp4-0.mp4',
  83. {'container': ['mov',
  84. 'mp4',
  85. 'm4a',
  86. '3gp',
  87. '3g2',
  88. 'mj2',
  89. 'isom',
  90. 'mp41'],
  91. 'video_codec': 'h264',
  92. 'audio_codec': 'aac',
  93. 'width': 640,
  94. 'height': 480,
  95. 'title': 'Africa: Cash for Climate Change?',
  96. 'duration': 312.37})
  97. def test_nuls(self):
  98. self.assertEqualOutput('nuls.mp3',
  99. {'container': 'mp3',
  100. 'title': 'Invisible'})
  101. @unittest.skip('inconsistent parsing of DRMed files')
  102. def test_drm(self):
  103. self.assertEqualOutput('drm.m4v',
  104. {'container': ['mov',
  105. 'mp4',
  106. 'm4a',
  107. '3gp',
  108. '3g2',
  109. 'mj2',
  110. 'M4V',
  111. 'M4V ',
  112. 'mp42',
  113. 'isom'],
  114. 'video_codec': 'none',
  115. 'audio_codec': 'aac',
  116. 'has_drm': ['audio', 'video'],
  117. 'width': 640,
  118. 'height': 480,
  119. 'title': 'Thinkers',
  120. 'artist': 'The Most Extreme',
  121. 'album': 'The Most Extreme',
  122. 'track': '10',
  123. 'genre': 'Nonfiction',
  124. 'duration': 2668.8})
  125. class GetThumbnailTest(base.Test):
  126. def setUp(self):
  127. base.Test.setUp(self)
  128. self.video_path = os.path.join(
  129. self.testdata_dir,
  130. 'theora.ogv')
  131. self.temp_path = tempfile.NamedTemporaryFile(
  132. suffix='.png')
  133. def generate_thumbnail(self, width, height):
  134. completion = mock.Mock()
  135. with mock.patch('lvc.video.idle_add') as mock_idle_add:
  136. with mock.patch('threading.Thread') as mock_thread:
  137. video.get_thumbnail(self.video_path, width, height,
  138. self.temp_path.name, completion,
  139. skip=0)
  140. # get_thumbnail() creates a thread to create the thumbnail.
  141. # Run the function for that thread now.
  142. mock_thread.call_args[1]['target']()
  143. self.assertEquals(mock_idle_add.call_count, 1)
  144. # At the end of the thread it uses add_idle() to call the
  145. # completion function. Run that now.
  146. mock_idle_add.call_args[0][0]()
  147. # Now when we call get_thumbnail() it should return
  148. # immediately with the thumbnail
  149. path = completion.call_args[0][0]
  150. self.assertNotEquals(path, None)
  151. return video.VideoFile(path)
  152. def test_original_size(self):
  153. thumbnail = self.generate_thumbnail(-1, -1)
  154. self.assertEqual(thumbnail.width, 400)
  155. self.assertEqual(thumbnail.height, 304)
  156. def test_height_resize(self):
  157. thumbnail = self.generate_thumbnail(200, -1)
  158. self.assertEqual(thumbnail.width, 200)
  159. self.assertEqual(thumbnail.height, 152)
  160. def test_width_resize(self):
  161. thumbnail = self.generate_thumbnail(-1, 152)
  162. self.assertEqual(thumbnail.width, 200)
  163. self.assertEqual(thumbnail.height, 152)
  164. def test_both_resize(self):
  165. thumbnail = self.generate_thumbnail(100, 100)
  166. self.assertEqual(thumbnail.width, 100)
  167. self.assertEqual(thumbnail.height, 100)
  168. class VideoFileTest(base.Test):
  169. def setUp(self):
  170. base.Test.setUp(self)
  171. self.video_path = os.path.join(
  172. self.testdata_dir,
  173. 'theora.ogv')
  174. self.video = video.VideoFile(self.video_path)
  175. self.video.thumbnails = {}
  176. def get_thumbnail_from_video(self, **kwargs):
  177. """Run Video.get_thumbnail()
  178. This method uses mock to intercept the threading and idle_add calls and
  179. just runs the code in the current thread
  180. """
  181. completion = mock.Mock()
  182. with mock.patch('lvc.video.idle_add') as mock_idle_add:
  183. with mock.patch('threading.Thread') as mock_thread:
  184. initial_rv = self.video.get_thumbnail(completion, **kwargs)
  185. if initial_rv is not None:
  186. # we already had a thumbnail and didn't have to do
  187. # anything synchrously
  188. return video.VideoFile(initial_rv)
  189. # We don't already have a thumbnail, so get_thumbnail()
  190. # created a thread to create it. Run the function for that
  191. # thread.
  192. mock_thread.call_args[1]['target']()
  193. self.assertEquals(mock_idle_add.call_count, 1)
  194. # At the end of the thread it uses add_idle() to call the
  195. # completion function. Run that now.
  196. mock_idle_add.call_args[0][0]()
  197. # Now when we call get_thumbnail() it should return
  198. # immediately with the thumbnail
  199. path = self.video.get_thumbnail(completion, **kwargs)
  200. self.assertNotEquals(path, None)
  201. return video.VideoFile(path)
  202. def test_get_thumbnail_original_size(self):
  203. thumbnail = self.get_thumbnail_from_video()
  204. self.assertEqual(thumbnail.width, 400)
  205. self.assertEqual(thumbnail.height, 304)
  206. def test_get_thumbnail_scaled_width(self):
  207. thumbnail = self.get_thumbnail_from_video(width=200)
  208. self.assertEqual(thumbnail.width, 200)
  209. self.assertEqual(thumbnail.height, 152)
  210. def test_get_thumbnail_scaled_height(self):
  211. thumbnail = self.get_thumbnail_from_video(height=152)
  212. self.assertEqual(thumbnail.width, 200)
  213. self.assertEqual(thumbnail.height, 152)
  214. def test_get_thumbnail_scaled_both(self):
  215. thumbnail = self.get_thumbnail_from_video(width=100, height=100)
  216. self.assertEqual(thumbnail.width, 100)
  217. self.assertEqual(thumbnail.height, 100)
  218. def test_get_thumbnail_cache(self):
  219. thumbnail = self.get_thumbnail_from_video()
  220. thumbnail2 = self.get_thumbnail_from_video()
  221. self.assertEqual(thumbnail.filename,
  222. thumbnail2.filename)
  223. def test_get_thumbnail_audio(self):
  224. audio_path = os.path.join(self.testdata_dir, 'mp3-0.mp3')
  225. audio = video.VideoFile(audio_path)
  226. def complete():
  227. pass
  228. self.assertEqual(audio.get_thumbnail(complete), None)
  229. self.assertEqual(audio.get_thumbnail(complete, 90, 70), None)